6 Commits
Author SHA1 Message Date
f.weber f5b19464ba chore: bump chart appVersion to 1.6.3-2 [skip ci] 2026-08-13 13:50:19 +00:00
f.weber 09400b7882 Update Dockerfile to include gpgv for enhanced security during package installation
CI / lint (push) Failing after 3s
CI / smoke-test (push) Failing after 13s
Release image / release (push) Successful in 22m40s
Release chart / release (release) Has been skipped
2026-08-13 15:33:28 +02:00
f.weber f345f469da Add ArtifactHub repository metadata and improve Helm chart configuration
CI / lint (push) Failing after 3s
Release chart / release (release) Successful in 9s
CI / smoke-test (push) Failing after 5s
- Introduced artifacthub-repo.yml for ownership verification on ArtifactHub.
- Updated release-chart.yaml to publish the ArtifactHub metadata as an OCI artifact.
- Bumped chart version to 0.2.2 and added relevant changes to Chart.yaml.
- Enhanced job-reconcile.yaml by adding a writable /tmp directory for the reconcile job.
2026-08-13 12:42:41 +02:00
f.weber a08ed898e8 Update Helm chart version and modify probes to use exec for readiness and liveness checks
CI / lint (push) Failing after 3s
CI / smoke-test (push) Failing after 5s
Release chart / release (release) Successful in 6s
2026-08-13 12:30:17 +02:00
f.weber 1675ea9a4e Enhance Helm chart with ServiceAccount support and configurable ports
CI / lint (push) Failing after 2s
CI / smoke-test (push) Failing after 9s
Release chart / release (release) Successful in 12s
- Updated Chart.yaml to version 0.2.0 and added annotations for changes.
- Modified release-chart.yaml to trigger releases via Gitea and handle pre-releases.
- Introduced ServiceAccount configuration in values.yaml and related templates.
- Adjusted internal container ports to prevent conflicts between nginx and aptly.
- Updated README.md and NOTES.txt to reflect new configurations and usage instructions.
2026-08-13 12:12:30 +02:00
f.weber 5af33e9128 Update Helm chart paths in workflows and documentation to avoid ambiguity in Gitea's package registry
CI / lint (push) Failing after 3s
CI / smoke-test (push) Failing after 5s
Release chart / release (push) Successful in 6s
2026-08-12 13:23:07 +02:00
16 changed files with 202 additions and 51 deletions
+2 -2
View File
@@ -82,10 +82,10 @@ jobs:
- name: Package and push a throwaway chart version
run: |
helm package charts/aptly --version 0.0.0-preflight --app-version preflight
helm push aptly-0.0.0-preflight.tgz oci://git.morlana.online/f.weber
helm push aptly-0.0.0-preflight.tgz oci://git.morlana.online/f.weber/charts
- name: Verify it is pullable
run: |
helm show chart oci://git.morlana.online/f.weber/aptly --version 0.0.0-preflight
helm show chart oci://git.morlana.online/f.weber/charts/aptly --version 0.0.0-preflight
issues-api:
runs-on: ubuntu-22.04
+59 -16
View File
@@ -1,17 +1,37 @@
name: Release chart
# Triggered by `git tag chart/vX.Y.Z && git push --tags`, independently of
# image releases — see docs/versioning.md. Never bumps the image: Chart.yaml's
# committed appVersion (last set by release-image.yaml) is what gets packaged,
# so a chart-only release always pins the last released image, never `latest`.
# Triggered by publishing a Gitea Release whose tag matches `chart/vX.Y.Z`
# (create the tag + release together via the Gitea UI's "New Release" page,
# or `git tag chart/vX.Y.Z && git push --tags` followed by turning that tag
# into a Release) — independently of image releases (release-image.yaml), see
# docs/versioning.md.
#
# Ticking "This is a pre-release" on that Gitea Release bakes
# `artifacthub.io/prerelease: "true"` into the packaged Chart.yaml (never
# committed back — see the "Set ArtifactHub prerelease annotation" step
# below), so ArtifactHub lists that specific chart version as a pre-release.
#
# This job also matches the `release: published` event fired by
# release-image.yaml's own auto-created Gitea Release for `image/v*` tags —
# the `if:` guard below skips anything whose tag isn't `chart/v*`.
#
# Chart version is pure SemVer, deliberately WITHOUT the `+up<aptly>` build
# metadata bookstack-chart uses: Helm rewrites `+` to `_` on OCI push (and
# back on pull), which breaks listing in some third-party tooling (e.g.
# Rancher). The aptly version lives in appVersion instead.
# Rancher). The aptly version lives in appVersion instead. A SemVer
# pre-release suffix (`chart/v0.2.0-rc.1`) works fine and is unaffected.
#
# The chart is pushed under f.weber/charts/aptly, NOT f.weber/aptly: Gitea's
# package registry stores both container images and Helm OCI charts as
# generic OCI artifacts, and a chart sharing the exact repository path with
# the container image of the same name makes the package listing/type
# ambiguous. A distinct `charts/` path keeps the two package kinds apart.
#
# Also pushes charts/aptly/artifacthub-repo.yml as a companion OCI artifact
# (tag `artifacthub.io`) on every run, proving ownership of this repository
# to ArtifactHub — see the "Publish ArtifactHub repository metadata" step.
on:
push:
tags:
- 'chart/v*'
release:
types: [published]
concurrency:
group: release-chart
@@ -19,15 +39,16 @@ concurrency:
jobs:
release:
if: startsWith(gitea.event.release.tag_name, 'chart/v')
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- name: Parse chart version from tag
- name: Parse chart version from the release's tag
id: version
run: echo "version=${GITEA_REF_NAME#chart/v}" >> "$GITEA_OUTPUT"
run: echo "version=${TAG_NAME#chart/v}" >> "$GITEA_OUTPUT"
env:
GITEA_REF_NAME: ${{ gitea.ref_name }}
TAG_NAME: ${{ gitea.event.release.tag_name }}
- uses: azure/setup-helm@v4.3.0
with:
@@ -43,11 +64,16 @@ jobs:
- name: Idempotency check — refuse to overwrite an existing chart version
run: |
if helm show chart "oci://git.morlana.online/f.weber/aptly" --version "${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
if helm show chart "oci://git.morlana.online/f.weber/charts/aptly" --version "${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
echo "::error::chart version ${{ steps.version.outputs.version }} already exists in the registry. Bump the version and re-tag — this workflow never overwrites a published chart."
exit 1
fi
- name: Set ArtifactHub prerelease annotation (packaged copy only, never committed)
if: gitea.event.release.prerelease
run: |
yq -i '.annotations["artifacthub.io/prerelease"] = "true"' charts/aptly/Chart.yaml
- name: Import GPG signing key
uses: crazy-max/ghaction-import-gpg@v6
with:
@@ -80,15 +106,32 @@ jobs:
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | helm registry login git.morlana.online \
--username "${{ secrets.REGISTRY_USER }}" --password-stdin
helm push "aptly-${{ steps.version.outputs.version }}.tgz" oci://git.morlana.online/f.weber
helm push "aptly-${{ steps.version.outputs.version }}.tgz" oci://git.morlana.online/f.weber/charts
- name: Create Gitea release with chart artifacts
# Publishes charts/aptly/artifacthub-repo.yml as its own OCI artifact
# (tag `artifacthub.io`) next to the chart — this is how ArtifactHub
# verifies ownership of an OCI-based Helm repository; there's no
# index.yaml it could otherwise crawl a metadata file out of. Uses the
# same registry credentials as the chart push above, nothing
# ArtifactHub-specific is needed (ArtifactHub has no publish API to
# authenticate against — it only reads the registry).
- uses: oras-project/setup-oras@v1
- name: Publish ArtifactHub repository metadata (ownership verification)
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | oras login git.morlana.online \
--username "${{ secrets.REGISTRY_USER }}" --password-stdin
oras push git.morlana.online/f.weber/charts/aptly:artifacthub.io \
--config /dev/null:application/vnd.cncf.artifacthub.config.v1+yaml \
charts/aptly/artifacthub-repo.yml:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml
- name: Attach chart artifacts to the Gitea release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ gitea.ref_name }}
tag_name: ${{ gitea.event.release.tag_name }}
name: "aptly chart ${{ steps.version.outputs.version }}"
prerelease: ${{ gitea.event.release.prerelease }}
body: |
`helm pull oci://git.morlana.online/f.weber/aptly --version ${{ steps.version.outputs.version }}`
`helm pull oci://git.morlana.online/f.weber/charts/aptly --version ${{ steps.version.outputs.version }}`
files: |
aptly-${{ steps.version.outputs.version }}.tgz
aptly-${{ steps.version.outputs.version }}.tgz.prov
+1 -1
View File
@@ -27,7 +27,7 @@ curl http://localhost:8080/api/ready
Details and the production path: [docs/quickstart-compose.md](docs/quickstart-compose.md).
```bash
helm install aptly oci://git.morlana.online/f.weber/aptly --version <version>
helm install aptly oci://git.morlana.online/f.weber/charts/aptly --version <version>
```
Details: [docs/quickstart-helm.md](docs/quickstart-helm.md).
+8 -3
View File
@@ -5,8 +5,8 @@ description: >-
an nginx read/auth sidecar, a fully aptly-native values API, and declarative
repo/mirror/publish state reconciled via a Helm hook.
type: application
version: 0.1.0
appVersion: "1.6.3-1"
version: 0.2.2
appVersion: "1.6.3-2"
home: https://git.morlana.online/f.weber/aptly-containerized
sources:
- https://git.morlana.online/f.weber/aptly-containerized
@@ -20,7 +20,7 @@ keywords:
maintainers:
- name: Florian Weber
email: f.weber@flweber.me
icon: https://www.aptly.info/img/aptly_medium.png
icon: https://www.aptly.info/img/logo.png
annotations:
artifacthub.io/license: MIT
artifacthub.io/links: |
@@ -31,3 +31,8 @@ annotations:
artifacthub.io/signKey: |
fingerprint: FC35C0FAA26605C4C21C7BBFBF43884145E5AA94
url: https://git.morlana.online/f.weber/aptly-containerized/raw/branch/main/pubkeys/chart-signing.asc
artifacthub.io/changes: |
- kind: added
description: Repository ownership verification for ArtifactHub (charts/aptly/artifacthub-repo.yml), published as a companion OCI artifact by release-chart.yaml.
- kind: fixed
description: The reconcile Job's container had readOnlyRootFilesystem but no writable /tmp, so its own API client's scratch files failed to write ("Read-only file system") and every reconcile call died with a misleading "connection" error. A /tmp emptyDir is now mounted there too, matching the aptly/nginx containers in the StatefulSet.
+15 -3
View File
@@ -8,7 +8,7 @@ no library-chart dependency, no concepts to learn beyond aptly's and Kubernetes'
## TL;DR
```bash
helm install my-aptly oci://git.morlana.online/f.weber/aptly --version <version>
helm install my-aptly oci://git.morlana.online/f.weber/charts/aptly --version <version>
```
## Introduction
@@ -45,7 +45,7 @@ Three things this chart is built around:
## Installing the chart
```bash
helm install my-aptly oci://git.morlana.online/f.weber/aptly --version <version> \
helm install my-aptly oci://git.morlana.online/f.weber/charts/aptly --version <version> \
--set ingress.enabled=true \
--set ingress.repo.host=apt.example.com
```
@@ -311,6 +311,15 @@ plain, supported operation.
| `persistence.size` | `20Gi` | immutable once installed unless using `existingClaim` |
| `persistence.annotations` | `{}` | |
### ServiceAccount
| Key | Default | Description |
|---|---|---|
| `serviceAccount.create` | `true` | creates a dedicated `ServiceAccount` for this release |
| `serviceAccount.name` | `""` | `create: true` → defaults to the release's fullname; `create: false` → set this to bind an existing `ServiceAccount`, or leave `""` to use the namespace's `default` one |
| `serviceAccount.annotations` | `{}` | e.g. for IRSA/Workload Identity |
| `serviceAccount.automountServiceAccountToken` | `true` | |
### Workload
| Key | Default | Description |
@@ -330,8 +339,11 @@ plain, supported operation.
| Key | Default | Description |
|---|---|---|
| `service.type` | `ClusterIP` | |
| `service.port` | `8080` | |
| `service.port` | `8080` | external port — unaffected by `ports.*` below, which are internal-only |
| `service.annotations` | `{}` | |
| `ports.aptly` | `8080` | container port aptly itself listens on (loopback-only unless `proxy.enabled: false`) |
| `ports.nginx` | `8081` | container port nginx listens on for repo + API traffic; must differ from `ports.aptly` — they're two containers sharing one pod network namespace, and a clash makes nginx fail to start with "address already in use" |
| `ports.metrics` | `9090` | container port nginx listens on for the `/api/metrics` passthrough, when `metrics.service.enabled` |
| `ingress.enabled` | `false` | |
| `ingress.mode` | `single` | `single` \| `split` — see [docs/security.md](https://git.morlana.online/f.weber/aptly-containerized/src/branch/main/docs/security.md) |
| `ingress.className` | `""` | |
+6
View File
@@ -0,0 +1,6 @@
repositoryID: "1ed468a7-7886-48ad-8835-369bc8a06bc5"
owners:
- name: Morlana
email: contact@morlana.net
- name: Florian Weber
email: f.weber@flweber.me
+1 -1
View File
@@ -15,7 +15,7 @@ aptly ({{ .Chart.AppVersion }}, chart {{ .Chart.Version }}) is deploying as {{ $
--- Check it's up -------------------------------------------------------
kubectl exec -n {{ .Release.Namespace }} {{ $fullname }}-0 -c aptly -- \
curl -fsS http://127.0.0.1:8080/api/ready
curl -fsS http://127.0.0.1:{{ .Values.ports.aptly }}/api/ready
kubectl logs -n {{ .Release.Namespace }} job/{{ $fullname }}-reconcile
# (only present right after install/upgrade in `hook` mode)
+20 -7
View File
@@ -73,6 +73,19 @@ confirms it's intended.
{{- end -}}
{{- end -}}
{{/*
Resolves to the ServiceAccount name the pod should bind to: a generated or
overridden name when serviceAccount.create is true, the explicit override
when false, or "" (falls back to the namespace's "default" SA) otherwise.
*/}}
{{- define "aptly.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "aptly.fullname" .) .Values.serviceAccount.name -}}
{{- else -}}
{{- .Values.serviceAccount.name -}}
{{- end -}}
{{- end -}}
{{- define "aptly.imagePullSecrets" -}}
{{- $secrets := concat (.Values.global.imagePullSecrets | default list) (.Values.image.pullSecrets | default list) -}}
{{- if $secrets }}
@@ -94,7 +107,7 @@ templates/statefulset.yaml (the aptly config) and templates/ingress.yaml
{{- end -}}
{{- define "aptly.apiListen" -}}
{{- if .Values.proxy.enabled -}}127.0.0.1:8080{{- else -}}0.0.0.0:8080{{- end -}}
{{- if .Values.proxy.enabled -}}127.0.0.1:{{ .Values.ports.aptly }}{{- else -}}0.0.0.0:{{ .Values.ports.aptly }}{{- end -}}
{{- end -}}
{{/*
@@ -163,7 +176,7 @@ compose/config/nginx.*.conf for the same constraint hit empirically).
{{- $sec := include "aptly.security" . | fromJson -}}
{{- $p := .Values.proxy -}}
server {
listen 8080;
listen {{ .Values.ports.nginx }};
server_name _;
client_max_body_size {{ $p.maxUploadSize }};
absolute_redirect off;
@@ -176,8 +189,8 @@ server {
{{- end }}
location = /healthz { access_log off; return 200 "ok\n"; }
location = /api/ready { access_log off; proxy_pass http://127.0.0.1:8080; }
location = /api/healthy { access_log off; proxy_pass http://127.0.0.1:8080; }
location = /api/ready { access_log off; proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }}; }
location = /api/healthy { access_log off; proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }}; }
{{- if $sec.w }}
location /api/ {
@@ -200,7 +213,7 @@ server {
{{- else }}
auth_basic off;
{{- end }}
proxy_pass http://127.0.0.1:8080;
proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }};
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_request_buffering off;
@@ -254,9 +267,9 @@ server {
# Separate, unauthenticated listener so scraping never needs the write-path
# credentials and a ServiceMonitor never needs a basicAuth secret.
server {
listen 9090;
listen {{ .Values.ports.metrics }};
server_name _;
location = /api/metrics { proxy_pass http://127.0.0.1:8080; }
location = /api/metrics { proxy_pass http://127.0.0.1:{{ .Values.ports.aptly }}; }
location / { return 404; }
}
{{- end }}
@@ -39,6 +39,10 @@ spec:
spec:
restartPolicy: Never
{{- include "aptly.imagePullSecrets" . | nindent 6 }}
{{- with include "aptly.serviceAccountName" . }}
serviceAccountName: {{ . }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
containers:
@@ -71,10 +75,14 @@ spec:
mountPath: /state.yaml
subPath: state.yaml
readOnly: true
- name: tmp
mountPath: /tmp
resources:
{{- toYaml .Values.reconcile.resources | nindent 12 }}
volumes:
- name: state
configMap:
name: {{ $fullname }}-state
- name: tmp
emptyDir: {}
{{- end }}
+1 -1
View File
@@ -15,7 +15,7 @@ spec:
ingress:
{{- if .Values.networkPolicy.allowedNamespaces }}
# Restricted to these namespaces (plus this one). NOTE: this applies to
# the whole nginx:8080 endpoint — read and write share one port, so this
# the whole nginx endpoint — read and write share one port, so this
# cannot itself express "reads are public, writes are cluster-only" any
# more precisely than security.write.allowCIDRs can (see the warning
# rendered into nginx.conf for that). Use it to fence the Service off
@@ -0,0 +1,13 @@
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ include "aptly.serviceAccountName" . }}
labels:
{{- include "aptly.labels" . | nindent 4 }}
{{- with .Values.serviceAccount.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
{{- end -}}
+16 -6
View File
@@ -41,6 +41,10 @@ spec:
{{- end }}
spec:
{{- include "aptly.imagePullSecrets" . | nindent 6 }}
{{- with include "aptly.serviceAccountName" . }}
serviceAccountName: {{ . }}
{{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automountServiceAccountToken }}
terminationGracePeriodSeconds: {{ .Values.workload.terminationGracePeriodSeconds }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
@@ -165,7 +169,7 @@ spec:
{{- end }}
ports:
- name: aptly
containerPort: 8080
containerPort: {{ .Values.ports.aptly }}
volumeMounts:
- name: data
mountPath: /var/lib/aptly
@@ -176,17 +180,23 @@ spec:
{{- with .Values.extraVolumeMounts }}
{{- toYaml . | nindent 12 }}
{{- end }}
# exec, not httpGet: aptly listens on 127.0.0.1 only (see
# aptly.apiListen) when proxy.enabled=true, but kubelet's httpGet
# probes connect to the POD IP, not localhost — that dials a
# loopback-only listener from outside and gets "connection
# refused". exec runs curl inside this container's own netns,
# where 127.0.0.1 is correct in either proxy mode.
startupProbe:
httpGet: { path: /api/ready, port: aptly }
exec: { command: ["curl", "-fsS", "http://127.0.0.1:{{ .Values.ports.aptly }}/api/ready"] }
periodSeconds: {{ .Values.probes.startup.periodSeconds }}
failureThreshold: {{ .Values.probes.startup.failureThreshold }}
readinessProbe:
httpGet: { path: /api/ready, port: aptly }
exec: { command: ["curl", "-fsS", "http://127.0.0.1:{{ .Values.ports.aptly }}/api/ready"] }
periodSeconds: {{ .Values.probes.readiness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.readiness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.readiness.failureThreshold }}
livenessProbe:
httpGet: { path: /api/healthy, port: aptly }
exec: { command: ["curl", "-fsS", "http://127.0.0.1:{{ .Values.ports.aptly }}/api/healthy"] }
periodSeconds: {{ .Values.probes.liveness.periodSeconds }}
timeoutSeconds: {{ .Values.probes.liveness.timeoutSeconds }}
failureThreshold: {{ .Values.probes.liveness.failureThreshold }}
@@ -199,10 +209,10 @@ spec:
{{- toYaml .Values.nginx.securityContext | nindent 12 }}
ports:
- name: http
containerPort: 8080
containerPort: {{ .Values.ports.nginx }}
{{- if .Values.metrics.service.enabled }}
- name: metrics
containerPort: 9090
containerPort: {{ .Values.ports.metrics }}
{{- end }}
volumeMounts:
- name: data
+19
View File
@@ -226,6 +226,15 @@
"publishEndpointName": { "type": "string" }
}
},
"ports": {
"type": "object",
"additionalProperties": false,
"properties": {
"aptly": { "type": "integer" },
"nginx": { "type": "integer" },
"metrics": { "type": "integer" }
}
},
"persistence": {
"type": "object",
"additionalProperties": false,
@@ -255,6 +264,16 @@
"podLabels": { "type": "object" }
}
},
"serviceAccount": {
"type": "object",
"additionalProperties": false,
"properties": {
"create": { "type": "boolean" },
"name": { "type": "string" },
"annotations": { "type": "object" },
"automountServiceAccountToken": { "type": "boolean" }
}
},
"podSecurityContext": { "type": "object" },
"containerSecurityContext": { "type": "object" },
"resources": { "type": "object" },
+21
View File
@@ -176,6 +176,16 @@ proxy:
readTimeout: "3600s"
publishEndpointName: public
# Container ports inside the pod. aptly and nginx are two containers sharing
# one pod network namespace — they MUST NOT use the same port (nginx would
# fail to start with "address already in use"). These are internal-only;
# external access is still controlled by service.port / metrics.service.port,
# which stay decoupled from these via named ports (targetPort: http/metrics).
ports:
aptly: 8080 # aptly's own listener (loopback-only, or 0.0.0.0 when proxy.enabled=false)
nginx: 8081 # nginx's main (repo + API) listener
metrics: 9090 # nginx's /api/metrics passthrough listener, when metrics.service.enabled
persistence:
enabled: true
existingClaim: "" # set this in production — see docs/operations.md
@@ -184,6 +194,17 @@ persistence:
size: 20Gi
annotations: {}
serviceAccount:
# Own ServiceAccount per release, so RBAC (if you grant any) is scoped to
# this instance rather than the namespace's shared "default" identity.
create: true
# "" -> aptly.fullname (this release's name) when create=true. When
# create=false, set this to bind to a ServiceAccount you already manage;
# leave "" to fall back to the namespace's "default" ServiceAccount.
name: ""
annotations: {}
automountServiceAccountToken: true
workload:
updateStrategy:
type: RollingUpdate # safe here: a StatefulSet with replicas=1 always
+1 -1
View File
@@ -64,7 +64,7 @@ LABEL org.opencontainers.image.title="aptly-server" \
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && apt-get install -y --no-install-recommends \
ca-certificates gnupg bzip2 xz-utils curl jq gettext-base openssl \
ca-certificates gnupg gpgv bzip2 xz-utils curl jq gettext-base openssl \
&& rm -rf /var/lib/apt/lists/* \
&& addgroup --system --gid 10001 aptly \
&& adduser --system --uid 10001 --ingroup aptly --home /var/lib/aptly --disabled-password aptly \
+11 -10
View File
@@ -1,11 +1,13 @@
# Public keys
## Chart signing key (needed, not yet set up)
## Chart signing key
`.gitea/workflows/release-chart.yaml` runs `helm package --sign` to produce a
`.tgz.prov` file for every chart release, and `charts/aptly/Chart.yaml` is meant to
carry an `artifacthub.io/signKey` annotation pointing at the public half of that
key (both are currently commented out / referencing a placeholder — see below).
`.tgz.prov` file for every chart release, and `charts/aptly/Chart.yaml` carries an
`artifacthub.io/signKey` annotation pointing at `chart-signing.asc` in this
directory (fingerprint `FC35C0FAA26605C4C21C7BBFBF43884145E5AA94`). The matching
private key is stored as the `GPG_PRIVATE_KEY`/`GPG_PASSPHRASE`/`GPG_KEY_ID`
secrets in this repo's Gitea settings.
**This key must NOT be Ed25519/EdDSA.** Helm's chart signing is built on the
deprecated `golang.org/x/crypto/openpgp` library, which cannot read Ed25519 keys at
@@ -25,7 +27,7 @@ Ed25519 — see [docs/packaging.md](../docs/packaging.md#gpg) — just not for
`helm package --sign`. This repo therefore needs its own, separate, RSA key
dedicated to chart-package signing.
### Generating it
### Generating a replacement (e.g. on rotation)
Run this yourself (locally, not in CI) so the private key material never has to
pass through anything but your own machine and the Gitea secrets store:
@@ -44,16 +46,15 @@ gpg --armor --export <key-id> > pubkeys/chart-signing.asc
gpg --armor --export-secret-keys <key-id> # -> paste as GPG_PRIVATE_KEY
```
Then, in the repo's Gitea settings:
Then, in the repo's Gitea settings, update:
- Secret **`GPG_PRIVATE_KEY`** — the armored output of the last command above
- Secret **`GPG_PASSPHRASE`** — whatever passphrase you set (empty string if none)
- Secret **`GPG_KEY_ID`** — the key ID or fingerprint from `gpg --list-secret-keys`
Commit `pubkeys/chart-signing.asc`, then uncomment the `artifacthub.io/signKey`
block in `charts/aptly/Chart.yaml` with the real fingerprint, and uncomment
`pubkeys/chart-signing.asc` in `release-chart.yaml`'s release-assets step.
Commit the new `pubkeys/chart-signing.asc` over the old one, and update the
fingerprint in the `artifacthub.io/signKey` annotation in `charts/aptly/Chart.yaml`.
### Verifying a downloaded chart (once the key exists)
### Verifying a downloaded chart
```bash
gpg --import pubkeys/chart-signing.asc