Public Access
Compare commits
5
Commits
chart/v0.1.0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5b19464ba | ||
|
|
09400b7882
|
||
|
|
f345f469da
|
||
|
|
a08ed898e8
|
||
|
|
1675ea9a4e
|
@@ -1,23 +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
|
||||
@@ -25,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:
|
||||
@@ -54,6 +69,11 @@ jobs:
|
||||
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:
|
||||
@@ -88,11 +108,28 @@ jobs:
|
||||
--username "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||
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/charts/aptly --version ${{ steps.version.outputs.version }}`
|
||||
files: |
|
||||
|
||||
@@ -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.
|
||||
|
||||
+13
-1
@@ -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` | `""` | |
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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 }}
|
||||
|
||||
@@ -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 -}}
|
||||
@@ -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
|
||||
|
||||
@@ -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" },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 \
|
||||
|
||||
Reference in New Issue
Block a user