feat(zddc-server): publishable runtime image + Codeberg CI pipeline
Batch 1 of the chart-vs-project split. The project now ships a
hardened runtime image as part of every zddc-server release; downstream
deployments (e.g. the Burns & McDonnell Helm chart) will FROM this
image instead of cloning and building from source.
zddc/Containerfile (target: server)
- Tag the runtime stage `server` so `podman build --target server`
is unambiguous (the existing `binaries` target still works).
- Bake the bundled landing + archive tool HTML at /opt/zddc-server/web.
Useful for self-contained demos (`ZDDC_ROOT=/opt/zddc-server/web`)
and as a fallback web root when no external mount is supplied.
- Set fixed UID/GID 1000 for the non-root zddc user so volume
permissions are predictable across hosts.
- Add ENV ZDDC_ROOT=/srv default so a `podman run -v data:/srv` works
with no further config; explicit ZDDC_ROOT overrides.
- Declare VOLUME /srv to make the data-mount expectation explicit.
- Add OCI image labels (title, description, source, documentation,
license, vendor).
- Install ca-certificates so any future outbound HTTPS works.
- Add a HEALTHCHECK for `docker run` users (Kubernetes overrides).
build.sh
- Make the cross-platform podman binary build conditional on `podman`
being present. CI doesn't need it (the runtime container image's
own builder stage produces linux/amd64 internally), but having
build.sh sh-only-runnable means CI doesn't have to do nested
containers just to assemble dist/web.
- Reorder so `zddc/dist/web/` is assembled before the binary build
(allows the binary build to be skipped without breaking the bundle).
.woodpecker.yml (new)
- Triggers on tag push matching `zddc-server-v*`.
- Step 1 (alpine + sh): runs `sh build.sh` to assemble dist/web,
computes the image tag (`${TAG#zddc-server-v}` plus `latest`).
- Step 2 (docker-buildx plugin): builds and publishes
codeberg.org/varasys/zddc-server:{X.Y.Z, latest}. Auth via the
codeberg_user / codeberg_token Woodpecker secrets — these need
one-time setup in repo Settings; documented in zddc/README.md.
zddc/README.md
- New "Container image" section: pull URL, image properties (alpine,
non-root UID 1000, EXPOSE 8443, VOLUME /srv, baked web bundle),
example `podman run` invocation.
- New "Env-var contract (for chart consumers)" table: the variables
Helm charts and Compose files should set explicitly when running
behind a TLS-terminating reverse proxy with SSO. This is the
documented interface between project and downstream charts.
- "Release Tagging" section now points at .woodpecker.yml and lists
the two Woodpecker secrets that must be configured.
Validated locally:
podman build --target server -t zddc-server-test .
podman run -e ZDDC_ROOT=/opt/zddc-server/web -e ZDDC_TLS_CERT=none \
-e ZDDC_INSECURE_DIRECT=1 -e ZDDC_ADDR=:8080 \
-p 18080:8080 zddc-server-test
curl http://localhost:18080/ → HTTP 200, bundled landing tool.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d122804bdb
commit
cc35f7179b
4 changed files with 186 additions and 12 deletions
54
.woodpecker.yml
Normal file
54
.woodpecker.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Woodpecker CI for ZDDC.
|
||||||
|
#
|
||||||
|
# This pipeline only runs on `zddc-server-v*` tag pushes — it builds the
|
||||||
|
# zddc-server runtime container image and publishes it to Codeberg's
|
||||||
|
# container registry. Other tags (archive-v*, transmittal-v*, etc.) and
|
||||||
|
# regular pushes are ignored here; the HTML tool releases happen by
|
||||||
|
# pushing static files to the website (no image involved).
|
||||||
|
#
|
||||||
|
# To enable: in Codeberg → repo Settings → Woodpecker → set the secrets
|
||||||
|
# codeberg_user = your Codeberg username (e.g. VARASYS)
|
||||||
|
# codeberg_token = a personal token with package:write scope
|
||||||
|
# Generate the token at https://codeberg.org/user/settings/applications.
|
||||||
|
#
|
||||||
|
# After setup, cut a release with:
|
||||||
|
# git tag zddc-server-v0.0.1
|
||||||
|
# git push --tags
|
||||||
|
# and the pipeline will publish:
|
||||||
|
# codeberg.org/varasys/zddc-server:0.0.1
|
||||||
|
# codeberg.org/varasys/zddc-server:latest
|
||||||
|
|
||||||
|
when:
|
||||||
|
- event: tag
|
||||||
|
ref: refs/tags/zddc-server-v*
|
||||||
|
|
||||||
|
steps:
|
||||||
|
prepare-bundle:
|
||||||
|
image: docker.io/alpine:3.20
|
||||||
|
commands:
|
||||||
|
# build.sh assembles zddc/dist/web/ from landing and archive
|
||||||
|
# built outputs (which are committed force-tracked dist files).
|
||||||
|
# Falls back gracefully when podman isn't present — we don't
|
||||||
|
# need the cross-compiled binaries here, the runtime container
|
||||||
|
# builds its own linux/amd64 binary internally.
|
||||||
|
- sh build.sh
|
||||||
|
# Image tag = the bare semver after the "zddc-server-v" prefix.
|
||||||
|
# Plus a "latest" tag for convenience.
|
||||||
|
- VERSION="${CI_COMMIT_TAG#zddc-server-v}"
|
||||||
|
- printf '%s\nlatest\n' "$VERSION" > .image-tags
|
||||||
|
- echo "Will tag image with: $(cat .image-tags | tr '\n' ' ')"
|
||||||
|
|
||||||
|
publish-image:
|
||||||
|
image: woodpeckerci/plugin-docker-buildx
|
||||||
|
settings:
|
||||||
|
registry: codeberg.org
|
||||||
|
repo: codeberg.org/varasys/zddc-server
|
||||||
|
dockerfile: zddc/Containerfile
|
||||||
|
context: zddc
|
||||||
|
target: server
|
||||||
|
tags_file: .image-tags
|
||||||
|
auto_tag: false
|
||||||
|
username:
|
||||||
|
from_secret: codeberg_user
|
||||||
|
password:
|
||||||
|
from_secret: codeberg_token
|
||||||
19
build.sh
19
build.sh
|
|
@ -14,11 +14,6 @@ sh "$SCRIPT_DIR/classifier/build.sh" "${1:-}" "${2:-}"
|
||||||
sh "$SCRIPT_DIR/mdedit/build.sh" "${1:-}" "${2:-}"
|
sh "$SCRIPT_DIR/mdedit/build.sh" "${1:-}" "${2:-}"
|
||||||
sh "$SCRIPT_DIR/landing/build.sh" "${1:-}" "${2:-}"
|
sh "$SCRIPT_DIR/landing/build.sh" "${1:-}" "${2:-}"
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "=== Building zddc-server binaries ==="
|
|
||||||
mkdir -p "$SCRIPT_DIR/zddc/dist/web"
|
|
||||||
podman build --target binaries -o "$SCRIPT_DIR/zddc/dist/" "$SCRIPT_DIR/zddc/" 2>&1 | grep -v "^-->"
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "=== Assembling zddc/dist/web/ ==="
|
echo "=== Assembling zddc/dist/web/ ==="
|
||||||
# Only landing and archive ship inside the server bundle: they call the
|
# Only landing and archive ship inside the server bundle: they call the
|
||||||
|
|
@ -26,11 +21,25 @@ echo "=== Assembling zddc/dist/web/ ==="
|
||||||
# archive) and are useless without it. transmittal, classifier, and mdedit
|
# archive) and are useless without it. transmittal, classifier, and mdedit
|
||||||
# are pure client-side tools that work from file:// or any static host;
|
# are pure client-side tools that work from file:// or any static host;
|
||||||
# they are released to website/ for download but not bundled with the server.
|
# they are released to website/ for download but not bundled with the server.
|
||||||
|
mkdir -p "$SCRIPT_DIR/zddc/dist/web"
|
||||||
cp "$SCRIPT_DIR/landing/dist/index.html" "$SCRIPT_DIR/zddc/dist/web/index.html"
|
cp "$SCRIPT_DIR/landing/dist/index.html" "$SCRIPT_DIR/zddc/dist/web/index.html"
|
||||||
cp "$SCRIPT_DIR/archive/dist/archive.html" "$SCRIPT_DIR/zddc/dist/web/archive.html"
|
cp "$SCRIPT_DIR/archive/dist/archive.html" "$SCRIPT_DIR/zddc/dist/web/archive.html"
|
||||||
echo "Wrote zddc/dist/web/index.html"
|
echo "Wrote zddc/dist/web/index.html"
|
||||||
echo "Wrote zddc/dist/web/archive.html"
|
echo "Wrote zddc/dist/web/archive.html"
|
||||||
|
|
||||||
|
# Cross-compiled zddc-server binaries are built via podman if available
|
||||||
|
# (no-op otherwise — CI builds the runtime container directly via the
|
||||||
|
# Containerfile's builder stage and doesn't need host-side binaries).
|
||||||
|
echo ""
|
||||||
|
echo "=== Building zddc-server binaries ==="
|
||||||
|
if command -v podman >/dev/null 2>&1; then
|
||||||
|
podman build --target binaries -o "$SCRIPT_DIR/zddc/dist/" "$SCRIPT_DIR/zddc/" 2>&1 | grep -v "^-->"
|
||||||
|
else
|
||||||
|
echo "podman not found — skipping cross-compiled binary build."
|
||||||
|
echo " (CI builds the container image directly; this step only matters"
|
||||||
|
echo " for releasing the standalone Linux/macOS/Windows binaries.)"
|
||||||
|
fi
|
||||||
|
|
||||||
# ─── Bootstrap zips ──────────────────────────────────────────────────────────
|
# ─── Bootstrap zips ──────────────────────────────────────────────────────────
|
||||||
# Generated from bootstrap/level{1,2}.html.tmpl on every build so they are
|
# Generated from bootstrap/level{1,2}.html.tmpl on every build so they are
|
||||||
# always in sync with the current bootstrap pattern.
|
# always in sync with the current bootstrap pattern.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,20 @@
|
||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
#
|
||||||
|
# Multi-stage build with three useful targets:
|
||||||
|
#
|
||||||
|
# --target binaries — scratch image holding cross-compiled binaries.
|
||||||
|
# Use `podman build --target binaries -o dist/ .`
|
||||||
|
# to extract zddc-server-{linux,darwin,windows}-*
|
||||||
|
# to the host. No image published from this stage.
|
||||||
|
#
|
||||||
|
# --target server — alpine-based runtime. Default target. Published
|
||||||
|
# as codeberg.org/varasys/zddc-server:vX.Y.Z.
|
||||||
|
#
|
||||||
|
# Build context expectations (when targeting `server`):
|
||||||
|
# dist/web/index.html and dist/web/archive.html must exist —
|
||||||
|
# produced by `sh build.sh` from the repo root. CI assembles these
|
||||||
|
# before invoking podman. See .woodpecker.yml.
|
||||||
|
#
|
||||||
|
|
||||||
# ─── Stage 1: build ──────────────────────────────────────────────────────────
|
# ─── Stage 1: build ──────────────────────────────────────────────────────────
|
||||||
FROM docker.io/library/golang:1.24-alpine AS builder
|
FROM docker.io/library/golang:1.24-alpine AS builder
|
||||||
|
|
@ -35,16 +51,51 @@ RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w"
|
||||||
FROM scratch AS binaries
|
FROM scratch AS binaries
|
||||||
COPY --from=builder /out/ /
|
COPY --from=builder /out/ /
|
||||||
|
|
||||||
# ─── Stage 3: runtime ────────────────────────────────────────────────────────
|
# ─── Stage 3: runtime (published image) ─────────────────────────────────────
|
||||||
FROM docker.io/library/alpine:3.20
|
FROM docker.io/library/alpine:3.20 AS server
|
||||||
|
|
||||||
# Non-root user
|
LABEL org.opencontainers.image.title="zddc-server" \
|
||||||
RUN addgroup -S zddc && adduser -S -G zddc zddc
|
org.opencontainers.image.description="HTTP server for ZDDC archives — ACL via .zddc files, virtual archive index, audit logging" \
|
||||||
|
org.opencontainers.image.source="https://codeberg.org/VARASYS/ZDDC" \
|
||||||
|
org.opencontainers.image.documentation="https://zddc.varasys.io/zddc-server.html" \
|
||||||
|
org.opencontainers.image.licenses="AGPL-3.0-only" \
|
||||||
|
org.opencontainers.image.vendor="VARASYS"
|
||||||
|
|
||||||
|
# wget is in the base image (busybox); explicitly install ca-certificates
|
||||||
|
# so outbound HTTPS (e.g. an upstream auth check) works if the operator
|
||||||
|
# adds anything later. Keep the install footprint minimal.
|
||||||
|
RUN apk add --no-cache ca-certificates && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
# Non-root user. UID/GID are deliberately fixed so volume permissions are
|
||||||
|
# predictable across hosts.
|
||||||
|
RUN addgroup -S -g 1000 zddc && adduser -S -u 1000 -G zddc zddc
|
||||||
|
|
||||||
|
# Binary
|
||||||
COPY --from=builder /out/zddc-server-linux-amd64 /usr/local/bin/zddc-server
|
COPY --from=builder /out/zddc-server-linux-amd64 /usr/local/bin/zddc-server
|
||||||
|
|
||||||
|
# Bundled landing + archive tools — useful for self-contained demos and as
|
||||||
|
# a fallback web root. Set ZDDC_ROOT=/opt/zddc-server/web to serve only
|
||||||
|
# these (no external data). For real archives, mount the data tree at
|
||||||
|
# /srv (the default ZDDC_ROOT below).
|
||||||
|
COPY dist/web/index.html /opt/zddc-server/web/index.html
|
||||||
|
COPY dist/web/archive.html /opt/zddc-server/web/archive.html
|
||||||
|
|
||||||
|
# Conventional mount point for the served archive. Operators mount their
|
||||||
|
# data here (Azure Files, NFS, hostPath, …). Override with ZDDC_ROOT.
|
||||||
|
VOLUME /srv
|
||||||
|
|
||||||
USER zddc
|
USER zddc
|
||||||
|
|
||||||
|
# Default config: data mount at /srv. Override at run time as needed.
|
||||||
|
# Other env vars (ZDDC_TLS_CERT, ZDDC_EMAIL_HEADER, ZDDC_CORS_ORIGIN, …)
|
||||||
|
# are intentionally not defaulted — see zddc/README.md.
|
||||||
|
ENV ZDDC_ROOT=/srv
|
||||||
|
|
||||||
EXPOSE 8443
|
EXPOSE 8443
|
||||||
|
|
||||||
|
# Liveness probe for `docker run` users. Kubernetes deployments override
|
||||||
|
# this with their own livenessProbe / readinessProbe.
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
CMD wget --no-check-certificate -q --spider https://localhost:8443/ || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/usr/local/bin/zddc-server"]
|
ENTRYPOINT ["/usr/local/bin/zddc-server"]
|
||||||
|
|
|
||||||
|
|
@ -292,12 +292,59 @@ the actual built tool from `install.zip`, or a level-1/level-2 bootstrap stub th
|
||||||
it. Then open it via the zddc-server URL; the app will auto-connect and scan the directory
|
it. Then open it via the zddc-server URL; the app will auto-connect and scan the directory
|
||||||
tree.
|
tree.
|
||||||
|
|
||||||
## Building
|
## Container image
|
||||||
|
|
||||||
### Run as a container
|
Each `zddc-server-vX.Y.Z` git tag publishes a runtime image to Codeberg's
|
||||||
|
container registry via the Woodpecker CI pipeline at `.woodpecker.yml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
codeberg.org/varasys/zddc-server:X.Y.Z
|
||||||
|
codeberg.org/varasys/zddc-server:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Pull and run:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
podman build -t zddc-server .
|
podman run --rm -p 8443:8443 \
|
||||||
|
-v /srv/archive:/srv:Z \
|
||||||
|
-e ZDDC_TLS_CERT=none \
|
||||||
|
-e ZDDC_ADDR=:8080 \
|
||||||
|
-e ZDDC_INSECURE_DIRECT=1 \
|
||||||
|
codeberg.org/varasys/zddc-server:latest
|
||||||
|
```
|
||||||
|
|
||||||
|
The image:
|
||||||
|
|
||||||
|
- alpine-based, runs as non-root (UID 1000)
|
||||||
|
- exposes 8443 by default
|
||||||
|
- defaults `ZDDC_ROOT=/srv` (override or mount your archive there)
|
||||||
|
- bundles the landing + archive tool HTML at `/opt/zddc-server/web` for
|
||||||
|
self-contained demos (`ZDDC_ROOT=/opt/zddc-server/web`)
|
||||||
|
- declares `VOLUME /srv` so the operator's data mount is explicit
|
||||||
|
- ships a `HEALTHCHECK` for `docker run`; Kubernetes deployments override it
|
||||||
|
|
||||||
|
### Env-var contract (for chart consumers)
|
||||||
|
|
||||||
|
Downstream Helm charts and Compose files should set these explicitly rather
|
||||||
|
than relying on image defaults:
|
||||||
|
|
||||||
|
| Variable | Typical value (behind ingress + SSO) | Purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| `ZDDC_ROOT` | `/srv` | Path of the served archive (volume mount) |
|
||||||
|
| `ZDDC_TLS_CERT` | `none` | TLS terminated upstream |
|
||||||
|
| `ZDDC_INSECURE_DIRECT` | `1` | Acknowledge plain HTTP behind a trusted proxy |
|
||||||
|
| `ZDDC_ADDR` | `:8080` | Match service / probe port |
|
||||||
|
| `ZDDC_EMAIL_HEADER` | `X-Auth-Request-Email` | Header your auth proxy sets |
|
||||||
|
| `ZDDC_CORS_ORIGIN` | `https://your-host` | Origins permitted to call back into the server |
|
||||||
|
|
||||||
|
See "Environment Variables" above for the full list.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
### Run as a container (build locally)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
podman build --target server -t zddc-server .
|
||||||
```
|
```
|
||||||
|
|
||||||
### Compile native binaries (no Go installation required)
|
### Compile native binaries (no Go installation required)
|
||||||
|
|
@ -346,6 +393,19 @@ git tag zddc-server-v1.0.0
|
||||||
git push --tags
|
git push --tags
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The `zddc-server-v*` tag triggers the `.woodpecker.yml` pipeline which
|
||||||
|
builds and publishes the container image. See "Container image" above
|
||||||
|
for the resulting image URLs.
|
||||||
|
|
||||||
|
The first time the pipeline runs you must configure two Woodpecker
|
||||||
|
secrets in repo Settings → Woodpecker:
|
||||||
|
|
||||||
|
- `codeberg_user` — your Codeberg username (e.g. `VARASYS`)
|
||||||
|
- `codeberg_token` — a personal token with `package:write` scope, generated at <https://codeberg.org/user/settings/applications>
|
||||||
|
|
||||||
|
These never live in the repo; they are referenced from the pipeline via
|
||||||
|
`from_secret:`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Notes:**
|
**Notes:**
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue