# services/harbor/hermes-webui-base-mirror-job.yaml # # One-shot mirror of the upstream Hermes WebUI base image INDEX from ghcr.io into # the in-cluster Harbor "mirror" project, so the reviewed image build # (dockerfiles/Dockerfile.hermes-webui) pulls its `AS webui` FROM base internally # with no ghcr.io fallback (ghcr/docker.io egress is IPv6-broken/flaky from build # pods, which is why the agent base was mirrored the same way). # # The upstream digest sha256:a83a3893... is ALREADY a multi-arch OCI index # (amd64 sha256:54fd4990..., arm64 sha256:9094ae6a...). `skopeo copy --all` is # content-addressed, so it reproduces the identical index and both arch leaves in # Harbor -- the build stays digest-pinned AND multi-arch. # # Kept `suspend: true` exactly like hermes-agent-base-mirror-job.yaml: it needs # egress to ghcr.io and is only run deliberately, once per base-digest bump. To # run it, an operator clears suspend (or `kubectl create job --from`) AFTER # updating the digest in BOTH args below and in the Dockerfile `AS webui` FROM. apiVersion: batch/v1 kind: Job metadata: name: harbor-hermes-webui-base-mirror-1 namespace: harbor spec: suspend: true backoffLimit: 2 activeDeadlineSeconds: 1800 template: metadata: annotations: vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/agent-pre-populate-only: "true" # This Job has an app init container (ensure-project) that reads the # injected secret, so Vault's init must run FIRST — otherwise it is # appended after ensure-project and the secret file isn't written yet. vault.hashicorp.com/agent-init-first: "true" vault.hashicorp.com/agent-run-as-user: "65532" vault.hashicorp.com/agent-run-as-group: "65532" vault.hashicorp.com/role: harbor-policy-bootstrap vault.hashicorp.com/agent-inject-secret-harbor-admin-password: kv/data/atlas/harbor/harbor-core vault.hashicorp.com/agent-inject-template-harbor-admin-password: | {{- with secret "kv/data/atlas/harbor/harbor-core" -}} {{ .Data.data.harbor_admin_password }} {{- end -}} spec: serviceAccountName: harbor-policy-bootstrap enableServiceLinks: false restartPolicy: Never nodeSelector: hardware: rpi5 kubernetes.io/arch: arm64 node-role.kubernetes.io/worker: "true" affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: NotIn values: [titan-04, titan-14, titan-18, titan-19, titan-24] securityContext: fsGroup: 65532 fsGroupChangePolicy: OnRootMismatch seccompProfile: type: RuntimeDefault initContainers: # Ensure the public "mirror" project exists before skopeo tries to push # into it (Harbor only auto-creates repositories inside an existing # project). Reuses the exact same generic ensure-project helper and # Vault-injected admin password as the sibling agent base-mirror Job -- # no new credential and no new project is introduced. - name: ensure-project image: docker.io/library/python@sha256:efcdfa6a6b2fd2afb9c7dfa9a5b288a6f68338b5cfdebe6b637d986067d85757 imagePullPolicy: IfNotPresent command: [python3, /scripts/harbor_mirror_project_ensure.py] env: - name: HARBOR_API_ORIGIN value: http://harbor-core.harbor.svc.cluster.local/api/v2.0 - name: HARBOR_ADMIN_PASSWORD_FILE value: /vault/secrets/harbor-admin-password securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] readOnlyRootFilesystem: true runAsGroup: 65532 runAsNonRoot: true runAsUser: 65532 seccompProfile: type: RuntimeDefault volumeMounts: - name: scripts mountPath: /scripts readOnly: true - name: tmp mountPath: /tmp resources: requests: {cpu: 25m, memory: 32Mi} limits: {cpu: 250m, memory: 128Mi} containers: # skopeo copies the WHOLE multi-arch index (--all) straight from ghcr.io # to the internal Harbor registry. Source is the public upstream image, so # no source credential is needed; the digest is asserted on both ends so a # drifted upstream tag cannot be mirrored. - name: mirror image: quay.io/skopeo/stable@sha256:94f5c5e26997e2e78c234ec9abf19a391c234b39eb22e6d1210d0b527c97dcc8 imagePullPolicy: IfNotPresent command: ["/bin/sh", "-c"] args: - | set -eu src="docker://ghcr.io/nesquena/hermes-webui@sha256:a83a3893111dcb250e7aa7aa657d3d6f4570b0e2fd00d9b7569246fc5e7339b2" # Push to the EXTERNAL Harbor endpoint (valid TLS) — the same path # kaniko already publishes to — to avoid harbor-core's internal # HTTP/token-realm quirks. The image lands in the same Harbor # 'mirror' project, so kaniko still pulls it internally via harbor-core. dst="docker://registry.bstein.dev/mirror/hermes-webui@sha256:a83a3893111dcb250e7aa7aa657d3d6f4570b0e2fd00d9b7569246fc5e7339b2" pw="$(cat /vault/secrets/harbor-admin-password)" exec skopeo copy --all \ --src-tls-verify=true \ --dest-tls-verify=true \ --dest-creds "admin:${pw}" \ "${src}" "${dst}" env: - name: HOME value: /tmp - name: TMPDIR value: /tmp - name: XDG_RUNTIME_DIR value: /tmp securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] readOnlyRootFilesystem: true runAsGroup: 65532 runAsNonRoot: true runAsUser: 65532 seccompProfile: type: RuntimeDefault volumeMounts: - name: tmp mountPath: /tmp resources: requests: {cpu: 100m, memory: 256Mi} limits: {cpu: "1", memory: 1Gi} volumes: - name: scripts configMap: name: harbor-hermes-webui-base-mirror-script defaultMode: 0555 - name: tmp emptyDir: {}