nameOverride: "" fullnameOverride: "" # -- Container image for the aptly server itself (also used for the # initContainer and the reconcile Job — all three run the same image). image: repository: git.morlana.online/f.weber/aptly tag: "" # "" -> .Chart.AppVersion, i.e. the last released image. Never "latest". pullPolicy: IfNotPresent pullSecrets: [] # -- The read/auth sidecar. A plain upstream image — this chart owns none of # its code, only its rendered config (see `security` below). nginx: image: repository: nginxinc/nginx-unprivileged tag: "1-alpine" pullPolicy: IfNotPresent resources: {} securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: { drop: [ALL] } # ============================================================================= # aptly configuration. Two layers, always merged in this order: # 1. the curated keys below (omitted from the rendered config when unset, # so a default install matches aptly's own upstream defaults exactly) # 2. aptly.configOverrides — raw aptly YAML, deep-merged last, always wins. # Every current and future aptly config key is reachable here without a # chart change. See rootfs/usr/local/bin/aptly-init and # https://github.com/aptly-dev/aptly/blob/master/utils/config.go for the # full field list (snake_case yaml tags). # ============================================================================= aptly: architectures: [] logLevel: info logFormat: json download: concurrency: 4 limit: 0 retries: 0 sourcePackages: false publishing: skipContents: false skipBz2: false metrics: enabled: false swagger: enabled: false gpg: # false -> gpg_disable_sign: true AND Signing.Skip: true on every publish # call the reconcile Job makes (both are required — aptly's publish API # does not consult gpg_disable_sign on its own, see docs/security.md). enabled: true verify: true # gpg (default): the real gnupg binary, in the image already — handles # armored keys, subkeys and passphrases the way upstream aptly expects. # internal: pure-Go openpgp, no gnupg binary needed, smaller attack # surface — verify your key type works with it before switching. provider: gpg signingKey: # Secret with key "privateKey" (armored .asc) or "secretKeyring" # (binary secring.gpg), optionally "passphrase". Never put a real key # inline in values.yaml — this is the production path. existingSecret: "" # Discouraged escape hatch for quick tests only. privateKey: "" passphrase: "" publishPublicKey: enabled: true path: /signing-key.asc # Trusted keys imported into GNUPGHOME on every start, for mirror # signature verification. The keyring is therefore a pure function of # values.yaml — restart the pod to pick up an edit here. # Inline ASCII-armored public keys only (Helm has no network access at # render time to support a `url:`/`keyserver:` form the way a plain script # could — fetch the key yourself once and paste it here). gpgKeys: [] # - name: debian-archive # armored: | # -----BEGIN PGP PUBLIC KEY BLOCK----- # ... # Declarative desired state, reconciled by the post-install/post-upgrade # Job against the REST API (never the CLI — see reconcile.mode below and # rootfs/usr/local/bin/aptly-reconcile for the exact field reference and # the documented limitation on editing mirrors[].components after creation). localRepos: [] # - name: stable # comment: "Production package repository" # defaultDistribution: stable # defaultComponent: main mirrors: [] # - name: debian-security # archiveURL: http://security.debian.org/debian-security # distribution: trixie-security # components: [main] # architectures: [amd64, arm64] publish: [] # - name: stable-root # prefix: "" # "" = repo root # distribution: stable # sourceKind: local # local | snapshot # sources: [{ name: stable, component: main }] # architectures: [amd64, arm64] # acquireByHash: true # Raw passthrough, deep-merged over the generated config last. See header. configOverrides: {} # Existing Secrets to envFrom into the aptly container, so configOverrides # can reference ${VAR} placeholders (e.g. S3 credentials) that resolve from # Secrets you already manage, without ever putting them in values.yaml. existingSecretEnv: [] # ============================================================================= # Security matrix — one preset switch, escape hatches for every axis. See # docs/security.md for the full decision table. # ============================================================================= security: # open: read+write, no auth, no exceptions — the explicit "unabgesichert" # mode. publicRead (default): read is open, write needs Basic Auth. # authenticated: both need Basic Auth. readOnly: write returns 404. preset: publicRead auth: # name: plaintext password. Hashed into htpasswd by the initContainer at # pod start (never a bcrypt/apr1 hash here — see rootfs/.../aptly-init # for why: a template-side hash would change, hence restart-loop, on # every single helm upgrade). users: {} # Secret key "htpasswd" (pre-hashed) — the recommended production path, # e.g. via ExternalSecrets/SealedSecrets. Wins over `users` when set. existingSecret: "" internalUser: # Always appended to htpasswd: the reconcile Job talks to nginx (not # directly to aptly, which is loopback-only), so it needs credentials # in every preset, including existingSecret + authenticated. enabled: true username: aptly-internal # CIDRs matched against $remote_addr. Behind an Ingress controller that is # the CONTROLLER's pod IP, not the real client — set trustedProxies to the # controller's CIDR (via X-Forwarded-For) or use networkPolicy instead. The # chart renders a warning comment into nginx.conf when allowCIDRs is set # without trustedProxies. trustedProxies: [] read: enabled: true requireAuth: null # null = take the preset's value; true/false overrides it allowCIDRs: [] write: enabled: true requireAuth: null allowCIDRs: [] # true: do not render the API Ingress at all (regardless of ingress.api.*) # and rely on networkPolicy for isolation — an honest implementation, not # an nginx trick. inClusterOnly: false # proxy.enabled=false hands aptly's unauthenticated write API directly to # whatever can reach the Service — the chart refuses to render an Ingress in # that combination unless security.preset is explicitly "open" (see # templates/NOTES.txt / the `fail` guard in templates/_helpers.tpl). proxy: enabled: true # nginx additionally serves the same tree under /repos//, matching # aptly's own serve_in_api_mode URL shape, so toggling this flag never # breaks an already-deployed sources.list. compatPaths: true maxUploadSize: "0" # nginx client_max_body_size; "0" = unlimited 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 storageClass: "" accessMode: ReadWriteOnce 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 # terminates the old pod before creating the new one podManagementPolicy: OrderedReady revisionHistoryLimit: 3 terminationGracePeriodSeconds: 60 annotations: {} podAnnotations: {} podLabels: {} podSecurityContext: runAsNonRoot: true runAsUser: 10001 runAsGroup: 10001 fsGroup: 10001 fsGroupChangePolicy: OnRootMismatch seccompProfile: { type: RuntimeDefault } containerSecurityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: { drop: [ALL] } resources: {} probes: startup: { periodSeconds: 5, failureThreshold: 60 } readiness: { periodSeconds: 10, timeoutSeconds: 3, failureThreshold: 3 } liveness: { periodSeconds: 30, timeoutSeconds: 5, failureThreshold: 6 } service: type: ClusterIP port: 8080 annotations: {} ingress: enabled: false mode: single # single | split — see docs/security.md className: "" annotations: nginx.ingress.kubernetes.io/proxy-body-size: "0" nginx.ingress.kubernetes.io/proxy-read-timeout: "3600" repo: host: "" path: / pathType: Prefix tls: [] # - hosts: [apt.example.com] # secretName: apt-tls api: enabled: false host: "" className: "" annotations: {} tls: [] # Gateway API HTTPRoute support — fully independent of `ingress.*` above, and # safe to enable at the same time as it (e.g. mid-migration between the two: # both can point at the same Service simultaneously, see docs/security.md). # This chart never creates a Gateway itself, only HTTPRoutes attaching to one # your cluster admin already manages — TLS is that Gateway listener's job, not # something set here. gateway: enabled: false # Core Gateway API resources are apiVersion gateway.networking.k8s.io/v1 (GA # since v1.0) — override only if your cluster's CRDs are still pre-GA. apiVersion: gateway.networking.k8s.io/v1 mode: single # single | split — same meaning as ingress.mode, see docs/security.md # Referenced Gateway(s). Required when gateway.enabled is true. parentRefs: [] # - name: my-gateway # namespace: gateway-infra # optional, defaults to this release's namespace # sectionName: https # optional, binds to one named listener repo: hostnames: [] # e.g. [apt.example.com] — omit to match the Gateway listener's own hostname(s) path: / pathType: PathPrefix # PathPrefix | Exact | RegularExpression — Gateway API's own enum, distinct from ingress.repo.pathType's api: enabled: false hostnames: [] parentRefs: [] # override for the API route only — falls back to gateway.parentRefs when empty metrics: service: enabled: false port: 9090 annotations: {} serviceMonitor: enabled: false interval: 30s labels: {} relabelings: [] reconcile: enabled: true # hook (default): post-install,post-upgrade Helm hook Job. # job: a plain Job named with a hash of the desired state, for GitOps # controllers (ArgoCD/Flux) that dislike Helm hooks. # manual: render the state ConfigMap only. mode: hook failOnError: false timeoutSeconds: 600 image: {} # override repository/tag/pullPolicy; defaults to the main `image` resources: {} podDisruptionBudget: enabled: false maxUnavailable: 1 networkPolicy: enabled: false allowedNamespaces: [] extraIngress: [] egress: # A default-deny egress policy silently breaks every mirror — this stays # true until you have a specific reason to lock it down. allowAll: true extra: [] extraEnv: [] extraEnvFrom: [] extraVolumes: [] extraVolumeMounts: [] extraInitContainers: [] extraContainers: [] nodeSelector: {} tolerations: [] affinity: {} topologySpreadConstraints: [] priorityClassName: "" global: imageRegistry: "" imagePullSecrets: [] defaultStorageClass: ""