aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNikolay Govorov <me@govorov.online>2026-08-21 21:23:04 +0100
committerNikolay Govorov <me@govorov.online>2026-08-22 04:05:07 +0100
commit08f66e83ea1e6205ed6499dbcbe7ef3b71b866fd (patch)
tree55fc97253c0b67a48fb18ed7a2eb16b78133aedf
parent1f077d3cdb898fd339d9491345d6ef26c47059d7 (diff)
downloadtar
tar.gz
tar.bz2
tar.lz
tar.xz
tar.zst
zip
Add docker container and helm chart
Diffstat
-rw-r--r--.dockerignore9+9 −0
-rw-r--r--.github/workflows/build.yml108+106 −2
-rw-r--r--.gitignore1+1 −0
-rw-r--r--Dockerfile29+29 −0
-rw-r--r--REUSE.toml1+1 −0
-rw-r--r--charts/recluse/Chart.yaml10+10 −0
-rw-r--r--charts/recluse/templates/_helpers.tpl37+37 −0
-rw-r--r--charts/recluse/templates/configmap.yaml46+46 −0
-rw-r--r--charts/recluse/templates/deployment.yaml97+97 −0
-rw-r--r--charts/recluse/templates/httproute.yaml26+26 −0
-rw-r--r--charts/recluse/templates/pvc.yaml22+22 −0
-rw-r--r--charts/recluse/templates/service.yaml19+19 −0
-rw-r--r--charts/recluse/values.yaml78+78 −0
-rw-r--r--mise.toml2+1 −1
14 files changed, 482 insertions, 3 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+.git
+.github
+.recluse-state
+dist
+target
+*.lcov
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index c1ff37a..419c209 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -64,7 +64,15 @@ jobs:
- name: Build packages
run: |
PKG_VERSION=$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "recluse") | .version')
- VERSION="${PKG_VERSION}~nightly.$(git log -1 --format=%ct)"
+ if [[ "$GITHUB_REF" == refs/tags/v* ]]; then
+ VERSION="${GITHUB_REF_NAME#v}"
+ if [[ "$VERSION" != "$PKG_VERSION" ]]; then
+ echo "Tag version $VERSION does not match Cargo.toml version $PKG_VERSION" >&2
+ exit 1
+ fi
+ else
+ VERSION="$PKG_VERSION-nightly.$(git log -1 --format=%ct)"
+ fi
mise run package -- \
--version "$VERSION" \
@@ -99,7 +107,6 @@ jobs:
name: Smoke tests
runs-on: ubuntu-24.04
needs: [build]
- if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
@@ -193,3 +200,100 @@ jobs:
S3_PUBLIC_URL: ${{ vars.S3_PUBLIC_URL }}
S3_ACCESS_KEY_ID: ${{ secrets.S3_ACCESS_KEY_ID }}
S3_SECRET_ACCESS_KEY: ${{ secrets.S3_SECRET_ACCESS_KEY }}
+
+ oci:
+ name: OCI artifacts
+ needs: [smoke]
+ runs-on: ubuntu-latest
+ permissions:
+ contents: read
+ packages: write
+ steps:
+ - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
+
+ - uses: jdx/mise-action@5228313ee0372e111a38da051671ca30fc5a96db # v3
+ with:
+ version: 2026.7.5
+ experimental: true
+ install: false
+
+ - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
+ with:
+ pattern: binary-*
+ path: .container
+
+ - uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
+
+ - name: Determine artifact version
+ id: artifact
+ shell: bash
+ run: |
+ base_version=$(python3 -c 'import tomllib; print(tomllib.load(open("Cargo.toml", "rb"))["workspace"]["package"]["version"])')
+ if [[ "$GITHUB_REF" == refs/heads/main ]]; then
+ version="$base_version-nightly.$(git log -1 --format=%ct)"
+ elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then
+ version="${GITHUB_REF_NAME#v}"
+ if [[ "$version" != "$base_version" ]]; then
+ echo "Tag version $version does not match Cargo.toml version $base_version" >&2
+ exit 1
+ fi
+ else
+ version="$base_version-pr.$GITHUB_RUN_NUMBER"
+ fi
+ echo "version=$version" >> "$GITHUB_OUTPUT"
+
+ - name: Log in to GHCR
+ if: github.event_name != 'pull_request'
+ uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
+ with:
+ registry: ghcr.io
+ username: ${{ github.actor }}
+ password: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Log Helm in to GHCR
+ if: github.event_name != 'pull_request'
+ env:
+ GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ printf '%s' "$GHCR_TOKEN" | mise x helm@4.1.1 -- \
+ helm registry login ghcr.io --username "$GITHUB_ACTOR" --password-stdin
+
+ - name: Build and publish OCI artifacts
+ shell: bash
+ run: |
+ version="${{ steps.artifact.outputs.version }}"
+ image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/recluse"
+ tags=(
+ --tag "$image:sha-$GITHUB_SHA"
+ --tag "$image:$version"
+ )
+ labels=(
+ --label "org.opencontainers.image.revision=$GITHUB_SHA"
+ --label "org.opencontainers.image.source=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY"
+ --label "org.opencontainers.image.version=$version"
+ )
+ image_output=()
+ chart_output=()
+
+ if [[ "$GITHUB_REF" == refs/heads/main ]]; then
+ tags+=(--tag "$image:nightly")
+ elif [[ "$GITHUB_REF" == refs/tags/v* ]]; then
+ tags+=(--tag "$image:latest")
+ fi
+ if [[ "${{ github.event_name }}" != pull_request ]]; then
+ image_output+=(--push)
+ chart_output+=(--push "oci://ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/charts")
+ fi
+
+ mise run container -- \
+ --context . \
+ --file Dockerfile \
+ --platform linux/amd64,linux/arm64 \
+ --cache-scope recluse \
+ "${tags[@]}" "${labels[@]}" "${image_output[@]}"
+
+ mise run chart -- \
+ --chart charts/recluse \
+ --version "$version" \
+ --app-version "$version" \
+ "${chart_output[@]}"
diff --git a/.gitignore b/.gitignore
index 8b1a3f5..aa377c6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@
/target
/dist
+/.container
/.recluse-state
mise.local.toml
mise.*.local.toml
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,29 @@
+# syntax=docker/dockerfile:1.7@sha256:a57df69d0ea827fb7266491f2813635de6f17269be881f696fbfdf2d83dda33e
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+ARG ALPINE_VERSION=3.23.5
+ARG ALPINE_DIGEST=sha256:fd791d74b68913cbb027c6546007b3f0d3bc45125f797758156952bc2d6daf40
+ARG DISTROLESS_DIGEST=sha256:a77defd6fedbb3392b175ba8ea3d1c22be963c1597c248c3ba987ddd80bfb512
+
+FROM --platform=$BUILDPLATFORM docker.io/library/alpine:${ALPINE_VERSION}@${ALPINE_DIGEST} AS rootfs
+RUN install -d -m 0750 /rootfs/var/lib/recluse
+
+FROM gcr.io/distroless/cc-debian13:nonroot@${DISTROLESS_DIGEST}
+
+ARG TARGETARCH
+
+LABEL org.opencontainers.image.source="https://github.com/dimidiumlabs/recluse" \
+ org.opencontainers.image.licenses="AGPL-3.0-or-later"
+
+COPY --chown=root:root --chmod=0755 .container/binary-${TARGETARCH}/recluse /usr/local/bin/recluse
+COPY --from=rootfs --chown=10000:10000 /rootfs/var/lib/recluse /var/lib/recluse
+COPY --chown=root:root pkg/recluse.toml /etc/recluse.toml
+COPY --chown=root:root LICENSE README.md /usr/share/doc/recluse/
+
+USER 10000:10000
+EXPOSE 2000
+VOLUME ["/var/lib/recluse"]
+
+ENTRYPOINT ["/usr/local/bin/recluse"]
+CMD ["--config=/etc/recluse.toml"]
diff --git a/REUSE.toml b/REUSE.toml
index 5b14da9..717f144 100644
--- a/REUSE.toml
+++ b/REUSE.toml
@@ -10,6 +10,7 @@ path = [
"mise.lock",
"pkg/*.svg",
+ "charts/recluse/templates/*",
"crates/recluse/src/assets/*.svg",
"crates/recluse/src/assets/*.ico",
diff --git a/charts/recluse/Chart.yaml b/charts/recluse/Chart.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/Chart.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+apiVersion: v2
+type: application
+version: 0.1.0
+
+name: recluse
+home: https://github.com/dimidiumlabs/recluse
+description: Tiny package caching proxy
diff --git a/charts/recluse/templates/_helpers.tpl b/charts/recluse/templates/_helpers.tpl
new file mode 100644
--- /dev/null
+++ b/charts/recluse/templates/_helpers.tpl
@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+{{- define "recluse.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{- define "recluse.fullname" -}}
+{{- if .Values.fullnameOverride -}}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}}
+{{- else -}}
+{{- printf "%s-%s" .Release.Name (include "recluse.name" .) | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+{{- end -}}
+
+{{- define "recluse.labels" -}}
+helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" }}
+app.kubernetes.io/name: {{ include "recluse.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end -}}
+
+{{- define "recluse.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "recluse.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end -}}
+
+{{- define "recluse.image" -}}
+{{- if .Values.image.digest -}}
+{{- printf "%s@%s" .Values.image.repository .Values.image.digest -}}
+{{- else -}}
+{{- printf "%s:%s" .Values.image.repository (default .Chart.AppVersion .Values.image.tag) -}}
+{{- end -}}
+{{- end -}}
+
+# vim: set filetype=helm:
diff --git a/charts/recluse/templates/configmap.yaml b/charts/recluse/templates/configmap.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/templates/configmap.yaml
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+{{- if not .Values.config.existingConfigMap }}
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: {{ include "recluse.fullname" . }}
+ labels:
+ {{- include "recluse.labels" . | nindent 4 }}
+data:
+ recluse.toml: |
+ appname = {{ .Values.config.appname | quote }}
+ dirname = "/var/lib/recluse"
+
+ [server]
+ max_body_size = {{ .Values.config.server.maxBodySize | quote }}
+ max_concurrent_requests = {{ .Values.config.server.maxConcurrentRequests }}
+
+ rate_limit_period = {{ .Values.config.server.rateLimitPeriod }}
+ rate_limit_burst_size = {{ .Values.config.server.rateLimitBurstSize }}
+
+ request_timeout = {{ .Values.config.server.requestTimeout }}
+ shutdown_timeout = {{ .Values.config.server.shutdownTimeout }}
+
+ [[listen]]
+ addr = "0.0.0.0:2000"
+ hostnames = {{ .Values.config.hostnames | toJson }}
+
+ {{- range $name, $backend := .Values.config.backends }}
+ [backends.{{ $name }}]
+ enabled = {{ $backend.enabled }}
+ refresh_interval = {{ $backend.refreshInterval }}
+
+ {{- end }}
+ [telemetry.stdout]
+ enabled = {{ .Values.config.log.enabled }}
+ log_level = {{ .Values.config.log.level | quote }}
+ log_format = {{ .Values.config.log.format | quote }}
+ {{- with .Values.config.extra }}
+
+{{ . | indent 4 }}
+ {{- end }}
+{{- end }}
+
+# vim: set filetype=helm:
diff --git a/charts/recluse/templates/deployment.yaml b/charts/recluse/templates/deployment.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/templates/deployment.yaml
@@ -0,0 +1,97 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ include "recluse.fullname" . }}
+ labels:
+ {{- include "recluse.labels" . | nindent 4 }}
+spec:
+ replicas: {{ .Values.replicaCount }}
+ strategy:
+ type: Recreate
+ selector:
+ matchLabels:
+ {{- include "recluse.selectorLabels" . | nindent 6 }}
+ template:
+ metadata:
+ annotations:
+ {{- if not .Values.config.existingConfigMap }}
+ checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
+ {{- end }}
+ {{- with .Values.podAnnotations }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ labels:
+ {{- include "recluse.selectorLabels" . | nindent 8 }}
+ {{- with .Values.podLabels }}
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ spec:
+ automountServiceAccountToken: false
+ terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
+ securityContext:
+ {{- toYaml .Values.podSecurityContext | nindent 8 }}
+ {{- with .Values.imagePullSecrets }}
+ imagePullSecrets:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ containers:
+ - name: recluse
+ image: {{ include "recluse.image" . | quote }}
+ imagePullPolicy: {{ .Values.image.pullPolicy }}
+ securityContext:
+ {{- toYaml .Values.securityContext | nindent 12 }}
+ args: ["--config=/etc/recluse/recluse.toml"]
+ ports:
+ - name: http
+ containerPort: 2000
+ protocol: TCP
+ startupProbe:
+ tcpSocket:
+ port: http
+ failureThreshold: 60
+ periodSeconds: 5
+ readinessProbe:
+ tcpSocket:
+ port: http
+ periodSeconds: 10
+ livenessProbe:
+ tcpSocket:
+ port: http
+ periodSeconds: 20
+ resources:
+ {{- toYaml .Values.resources | nindent 12 }}
+ volumeMounts:
+ - name: config
+ mountPath: /etc/recluse/recluse.toml
+ subPath: recluse.toml
+ readOnly: true
+ - name: state
+ mountPath: /var/lib/recluse
+ volumes:
+ - name: config
+ configMap:
+ name: {{ default (include "recluse.fullname" .) .Values.config.existingConfigMap }}
+ - name: state
+ {{- if .Values.persistence.enabled }}
+ persistentVolumeClaim:
+ claimName: {{ default (include "recluse.fullname" .) .Values.persistence.existingClaim }}
+ {{- else }}
+ emptyDir: {}
+ {{- end }}
+ {{- with .Values.nodeSelector }}
+ nodeSelector:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.affinity }}
+ affinity:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+ {{- with .Values.tolerations }}
+ tolerations:
+ {{- toYaml . | nindent 8 }}
+ {{- end }}
+
+# vim: set filetype=helm:
diff --git a/charts/recluse/templates/httproute.yaml b/charts/recluse/templates/httproute.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/templates/httproute.yaml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+{{- if .Values.route.enabled }}
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+ name: {{ include "recluse.fullname" . }}
+ labels:
+ {{- include "recluse.labels" . | nindent 4 }}
+spec:
+ {{- with .Values.route.parentRefs }}
+ parentRefs:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ {{- with .Values.route.hostnames }}
+ hostnames:
+ {{- toYaml . | nindent 4 }}
+ {{- end }}
+ rules:
+ - backendRefs:
+ - name: {{ include "recluse.fullname" . }}
+ port: {{ .Values.service.port }}
+{{- end }}
+
+# vim: set filetype=helm:
diff --git a/charts/recluse/templates/pvc.yaml b/charts/recluse/templates/pvc.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/templates/pvc.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+{{- if and .Values.persistence.enabled (not .Values.persistence.existingClaim) }}
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: {{ include "recluse.fullname" . }}
+ labels:
+ {{- include "recluse.labels" . | nindent 4 }}
+spec:
+ accessModes:
+ {{- toYaml .Values.persistence.accessModes | nindent 4 }}
+ resources:
+ requests:
+ storage: {{ .Values.persistence.size }}
+ {{- with .Values.persistence.storageClass }}
+ storageClassName: {{ . | quote }}
+ {{- end }}
+{{- end }}
+
+# vim: set filetype=helm:
diff --git a/charts/recluse/templates/service.yaml b/charts/recluse/templates/service.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/templates/service.yaml
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ include "recluse.fullname" . }}
+ labels:
+ {{- include "recluse.labels" . | nindent 4 }}
+spec:
+ selector:
+ {{- include "recluse.selectorLabels" . | nindent 4 }}
+ ports:
+ - name: http
+ port: {{ .Values.service.port }}
+ targetPort: http
+ protocol: TCP
+
+# vim: set filetype=helm:
diff --git a/charts/recluse/values.yaml b/charts/recluse/values.yaml
new file mode 100644
--- /dev/null
+++ b/charts/recluse/values.yaml
@@ -0,0 +1,78 @@
+# SPDX-FileCopyrightText: 2026 Nikolay Govorov
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+replicaCount: 1
+
+image:
+ repository: ghcr.io/dimidiumlabs/recluse
+ tag: ""
+ digest: ""
+ pullPolicy: IfNotPresent
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+podAnnotations: {}
+podLabels: {}
+
+podSecurityContext:
+ runAsNonRoot: true
+
+ runAsUser: 10000
+ runAsGroup: 10000
+
+ fsGroup: 10000
+ fsGroupChangePolicy: OnRootMismatch
+
+securityContext:
+ allowPrivilegeEscalation: false
+ capabilities:
+ drop: [ALL]
+ readOnlyRootFilesystem: true
+
+service:
+ port: 2000
+
+config:
+ existingConfigMap: ""
+ appname: recluse
+ hostnames: []
+ server:
+ shutdownTimeout: 60
+ requestTimeout: 30
+ maxBodySize: 64 MiB
+ maxConcurrentRequests: 512
+ rateLimitPeriod: 10
+ rateLimitBurstSize: 50
+ backends:
+ go:
+ enabled: true
+ refreshInterval: 3600
+ zig:
+ enabled: true
+ refreshInterval: 3600
+ log:
+ enabled: true
+ level: info
+ format: json
+ extra: ""
+
+persistence:
+ enabled: true
+ existingClaim: ""
+ accessModes: [ReadWriteOnce]
+ size: 20Gi
+ storageClass: ""
+
+route:
+ enabled: false
+ hostnames: []
+ parentRefs: []
+
+resources: {}
+nodeSelector: {}
+tolerations: []
+affinity: {}
+
+terminationGracePeriodSeconds: 70
diff --git a/mise.toml b/mise.toml
index b3899cb..09fa32a 100644
--- a/mise.toml
+++ b/mise.toml
@@ -38,5 +38,5 @@ rust = { version = "1.97.1", profile = "minimal", components = [
[task_config]
dir = "{{cwd}}"
includes = [
- "git::https://github.com/dimidiumlabs/infra.git//tasks?ref=4a4589fdb968686a7c91b380ff369aba23bd8662",
+ "git::https://github.com/dimidiumlabs/infra.git//tasks?ref=8bc35fe8be889c50db2d1fb4425cc1b4097dc6b8",
]