{{/* Standard name/label helpers, bookstack-chart style. */}} {{- define "aptly.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- define "aptly.fullname" -}} {{- if .Values.fullnameOverride -}} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- $name := default .Chart.Name .Values.nameOverride -}} {{- if contains $name .Release.Name -}} {{- .Release.Name | trunc 63 | trimSuffix "-" -}} {{- else -}} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- end -}} {{- end -}} {{- define "aptly.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} {{- end -}} {{- define "aptly.labels" -}} helm.sh/chart: {{ include "aptly.chart" . }} {{ include "aptly.selectorLabels" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end -}} {{- define "aptly.selectorLabels" -}} app.kubernetes.io/name: {{ include "aptly.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end -}} {{/* Image helpers — global.imageRegistry prefixes the repository when set, matching the bookstack-chart convention. */}} {{- define "aptly.image" -}} {{- $registry := .Values.global.imageRegistry -}} {{- $repo := .Values.image.repository -}} {{- $tag := .Values.image.tag | default .Chart.AppVersion -}} {{- if $registry -}} {{- printf "%s/%s:%s" $registry $repo $tag -}} {{- else -}} {{- printf "%s:%s" $repo $tag -}} {{- end -}} {{- end -}} {{- define "aptly.nginxImage" -}} {{- $registry := .Values.global.imageRegistry -}} {{- $repo := .Values.nginx.image.repository -}} {{- $tag := .Values.nginx.image.tag -}} {{- if $registry -}} {{- printf "%s/%s:%s" $registry $repo $tag -}} {{- else -}} {{- printf "%s:%s" $repo $tag -}} {{- end -}} {{- end -}} {{/* Shared by templates/ingress.yaml and templates/httproute.yaml: proxy.enabled=false means aptly's unauthenticated write API sits directly behind whatever routes to it, on a single listener with no path-based auth split possible — refuse to wire that up to an Ingress OR a Gateway API HTTPRoute unless security.preset=open confirms it's intended. */}} {{- define "aptly.exposureGuard" -}} {{- if and (not .Values.proxy.enabled) (or .Values.ingress.enabled .Values.gateway.enabled) (ne .Values.security.preset "open") -}} {{- fail "proxy.enabled=false publishes aptly's unauthenticated write API through the Ingress/HTTPRoute (no path-based auth split is possible on a single listener). Set security.preset=open to confirm this is intended, or keep proxy.enabled=true." -}} {{- end -}} {{- end -}} {{- define "aptly.imagePullSecrets" -}} {{- $secrets := concat (.Values.global.imagePullSecrets | default list) (.Values.image.pullSecrets | default list) -}} {{- if $secrets }} imagePullSecrets: {{- range $secrets }} - name: {{ if kindIs "map" . }}{{ .name }}{{ else }}{{ . }}{{ end }} {{- end }} {{- end }} {{- end -}} {{/* Whether aptly serves the published tree itself (no nginx proxy in front). Only sane in combination with security.preset=open — enforced in templates/statefulset.yaml (the aptly config) and templates/ingress.yaml (the hard `fail` guard). */}} {{- define "aptly.serveInApiMode" -}} {{- if .Values.proxy.enabled -}}false{{- else -}}true{{- end -}} {{- end -}} {{- define "aptly.apiListen" -}} {{- if .Values.proxy.enabled -}}127.0.0.1:8080{{- else -}}0.0.0.0:8080{{- end -}} {{- end -}} {{/* Resolve security.preset + explicit read/write overrides into a plain dict {r, ra, w, wa} (read-enabled, read-auth, write-enabled, write-auth). Both nginx.conf.tpl and templates/ingress.yaml key off this so preset expansion lives in exactly one place. */}} {{- define "aptly.security" -}} {{- $p := .Values.security.preset -}} {{- $presets := dict "open" (dict "r" true "ra" false "w" true "wa" false) "publicRead" (dict "r" true "ra" false "w" true "wa" true) "authenticated" (dict "r" true "ra" true "w" true "wa" true) "readOnly" (dict "r" true "ra" false "w" false "wa" false) -}} {{- $base := index $presets $p -}} {{- if not $base -}} {{- fail (printf "security.preset: unknown value %q (must be one of open, publicRead, authenticated, readOnly)" $p) -}} {{- end -}} {{- $d := deepCopy $base -}} {{- if kindIs "bool" .Values.security.read.enabled }}{{- $_ := set $d "r" .Values.security.read.enabled -}}{{- end -}} {{- if kindIs "bool" .Values.security.read.requireAuth }}{{- $_ := set $d "ra" .Values.security.read.requireAuth -}}{{- end -}} {{- if kindIs "bool" .Values.security.write.enabled }}{{- $_ := set $d "w" .Values.security.write.enabled -}}{{- end -}} {{- if kindIs "bool" .Values.security.write.requireAuth }}{{- $_ := set $d "wa" .Values.security.write.requireAuth -}}{{- end -}} {{- $d | toJson -}} {{- end -}} {{/* Render the aptly config (YAML) from the curated values.aptly.* keys, then deep-merge aptly.configOverrides on top so every current/future aptly config key stays reachable without a chart change. Curated keys are OMITTED when unset, so a default install matches aptly's own upstream defaults exactly (see utils/config.go in aptly-dev/aptly for the canonical defaults). */}} {{- define "aptly.config" -}} {{- $v := .Values.aptly -}} {{- $c := dict "root_dir" "/var/lib/aptly" -}} {{- with $v.logLevel }}{{- $_ := set $c "log_level" . -}}{{- end -}} {{- with $v.logFormat }}{{- $_ := set $c "log_format" . -}}{{- end -}} {{- if $v.architectures }}{{- $_ := set $c "architectures" $v.architectures -}}{{- end -}} {{- with $v.download.concurrency }}{{- $_ := set $c "download_concurrency" . -}}{{- end -}} {{- with $v.download.limit }}{{- $_ := set $c "download_limit" . -}}{{- end -}} {{- with $v.download.retries }}{{- $_ := set $c "download_retries" . -}}{{- end -}} {{- if kindIs "bool" $v.download.sourcePackages }}{{- $_ := set $c "download_sourcepackages" $v.download.sourcePackages -}}{{- end -}} {{- $_ := set $c "gpg_provider" ($v.gpg.provider | default "gpg") -}} {{- $_ := set $c "gpg_disable_sign" (not $v.gpg.enabled) -}} {{- $_ := set $c "gpg_disable_verify" (not $v.gpg.verify) -}} {{- if kindIs "bool" $v.publishing.skipContents }}{{- $_ := set $c "skip_contents_publishing" $v.publishing.skipContents -}}{{- end -}} {{- if kindIs "bool" $v.publishing.skipBz2 }}{{- $_ := set $c "skip_bz2_publishing" $v.publishing.skipBz2 -}}{{- end -}} {{- $_ := set $c "enable_metrics_endpoint" ($v.metrics.enabled | default false) -}} {{- $_ := set $c "enable_swagger_endpoint" ($v.swagger.enabled | default false) -}} {{- $_ := set $c "serve_in_api_mode" (eq (include "aptly.serveInApiMode" .) "true") -}} {{- $_ := set $c "filesystem_publish_endpoints" (dict $.Values.proxy.publishEndpointName (dict "root_dir" "/var/lib/aptly/public" "link_method" "hardlink")) -}} {{- $merged := mergeOverwrite $c (deepCopy ($v.configOverrides | default dict)) -}} {{- toYaml $merged -}} {{- end -}} {{/* Render nginx's server{} block (mounted at /etc/nginx/conf.d/default.conf, which the base image's own nginx.conf already includes from inside its own http{} block — this template must therefore emit ONLY a server{} block, see compose/config/nginx.*.conf for the same constraint hit empirically). */}} {{- define "aptly.nginxConf" -}} {{- $sec := include "aptly.security" . | fromJson -}} {{- $p := .Values.proxy -}} server { listen 8080; server_name _; client_max_body_size {{ $p.maxUploadSize }}; absolute_redirect off; {{- range .Values.security.trustedProxies }} set_real_ip_from {{ . }}; {{- end }} {{- if .Values.security.trustedProxies }} real_ip_header X-Forwarded-For; real_ip_recursive on; {{- 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; } {{- if $sec.w }} location /api/ { {{- if .Values.security.write.allowCIDRs }} {{- if and .Values.security.write.allowCIDRs (not .Values.security.trustedProxies) }} # WARNING: write.allowCIDRs is set without security.trustedProxies. Behind # an Ingress controller, $remote_addr is the CONTROLLER's pod IP, not the # real client — this will match every client on earth. Set # trustedProxies to the controller's CIDR, or use networkPolicy instead. {{- end }} {{- range .Values.security.write.allowCIDRs }} allow {{ . }}; {{- end }} deny all; satisfy {{ if $sec.wa }}any{{ else }}all{{ end }}; {{- end }} {{- if $sec.wa }} auth_basic "aptly"; auth_basic_user_file /run/aptly/htpasswd; {{- else }} auth_basic off; {{- end }} proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_request_buffering off; proxy_read_timeout {{ .Values.proxy.readTimeout }}; proxy_send_timeout {{ .Values.proxy.readTimeout }}; } {{- else }} location /api/ { return 404; } {{- end }} location = /signing-key.asc { alias /run/aptly/pub/signing-key.asc; default_type text/plain; } {{- if $sec.r }} location / { {{- if $sec.ra }} auth_basic "aptly"; auth_basic_user_file /run/aptly/htpasswd; {{- end }} root /var/lib/aptly/public; autoindex on; autoindex_exact_size off; location ~* /(InRelease|Release|Release\.gpg|Packages(\.[a-z0-9]+)?|Sources(\.[a-z0-9]+)?)$ { {{- if $sec.ra }} auth_basic "aptly"; auth_basic_user_file /run/aptly/htpasswd; {{- end }} root /var/lib/aptly/public; add_header Cache-Control "no-cache" always; } } {{- if .Values.proxy.compatPaths }} location /repos/{{ .Values.proxy.publishEndpointName }}/ { {{- if $sec.ra }} auth_basic "aptly"; auth_basic_user_file /run/aptly/htpasswd; {{- end }} alias /var/lib/aptly/public/; autoindex on; } {{- end }} {{- else }} location / { return 404; } {{- end }} } {{- if .Values.metrics.service.enabled }} # Separate, unauthenticated listener so scraping never needs the write-path # credentials and a ServiceMonitor never needs a basicAuth secret. server { listen 9090; server_name _; location = /api/metrics { proxy_pass http://127.0.0.1:8080; } location / { return 404; } } {{- end }} {{- end -}}