63701dd086
Addresses the gaps identified in the last audit. Restore (was a stub returning "not yet implemented"). Every repository shares one connection pool, so the database cannot be swapped underneath a live server. Restore is therefore two-phase: RestoreBackup validates the file and stages it beside the database; db.New applies it before the pool is opened, which is the only safe moment. The database being replaced is preserved as <db>.replaced-<timestamp>, and stale -wal/-shm are removed so SQLite cannot replay the old journal over the restored file. Validation is strict — SQLite integrity_check plus a schema probe — because applying an unrelated file would destroy the install. GET/DELETE /api/v1/backups/restore inspect and cancel a staged restore. The CLI does both phases at once, since it runs standalone; `orchestrad backup` was also a stub and now works. Secret key. With nothing configured the key is generated once and persisted to <data>/secret.key, so restarts reuse it and moving the stack to another server is a matter of copying the data directory. Upgrades are handled: if a database already exists the install was silently running on the legacy built-in default, so that value is adopted and written out rather than replaced — generating a fresh key there would make every stored credential undecryptable. The file is owner-only (ACL-restricted on Windows). Multi-arch image: buildx now emits linux/amd64 + linux/arm64, matching the architectures the release binaries already covered. The Dockerfile cross-compiles via TARGETARCH rather than emulating, so arm64 costs little. CSRF: the middleware previously checked only that a header was *present* and was never wired up, and /auth/csrf returned "csrf-token-placeholder". Tokens are now nonce + HMAC-SHA256 signed with the application secret, validated properly, and the middleware is mounted on /api/v1. Bearer and API-key requests are not CSRF-reachable and pass through untouched, so this is transparent to the SPA and to API clients. Also: the Windows store import drops CRYPT_EXPORTABLE (the store copy is not the source of truth — <data>/tls holds the key, so portability is unaffected and a non-exportable server key is the better posture), the PFX password is written to server.pfx.password beside the bundle so an operator importing it by hand does not have to hunt for a password they never chose, and the "renewed" log line now reflects whether a leaf was actually issued instead of guessing from its age. Verified live: backup -> stage -> restart applies and preserves the previous database; secret key generated, adopted, and read back across restarts with the credential check confirming decryptability; CSRF endpoint issues real signed tokens. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
104 lines
3.8 KiB
Docker
104 lines
3.8 KiB
Docker
# OrchestrAD Docker Image
|
|
# Three-stage build: (1) compile the Next.js static export, (2) embed it into
|
|
# the Go binary, (3) ship a minimal Alpine runtime. The single resulting binary
|
|
# serves the full product UI plus the API on one port.
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stage 1: build the frontend (Next.js static export -> frontend/out)
|
|
# Debian base avoids musl/sharp native-module friction; this stage is discarded.
|
|
# ---------------------------------------------------------------------------
|
|
FROM --platform=$BUILDPLATFORM node:22-bookworm-slim AS frontend
|
|
WORKDIR /frontend
|
|
|
|
# Install deps first for better layer caching. .npmrc carries
|
|
# legacy-peer-deps=true, which npm ci needs to resolve the React 19 RC peer
|
|
# graph, so it must be present before the install runs.
|
|
COPY frontend/package.json frontend/package-lock.json frontend/.npmrc ./
|
|
RUN npm ci --no-audit --no-fund
|
|
|
|
COPY frontend/ ./
|
|
RUN npm run build
|
|
# next.config.mjs sets output:"export", so the static site lands in ./out
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stage 2: build the Go binary with the UI embedded
|
|
# ---------------------------------------------------------------------------
|
|
# Pinned to the *build* platform and cross-compiled with GOARCH below, so an
|
|
# arm64 image is produced natively on an amd64 runner with no QEMU emulation
|
|
# (which would make the Go build minutes-long instead of seconds).
|
|
FROM --platform=$BUILDPLATFORM golang:1.25-alpine AS builder
|
|
|
|
# Pure-Go SQLite (modernc.org/sqlite) means no C toolchain is needed, which is
|
|
# also what makes cross-compilation this simple.
|
|
RUN apk add --no-cache git
|
|
|
|
WORKDIR /build
|
|
|
|
# Cache modules first.
|
|
COPY backend/go.mod backend/go.sum* ./
|
|
RUN go mod download
|
|
|
|
# Copy source, then drop the staged UI into the embed directory. webui.go does
|
|
# //go:embed all:dist, so the compiled binary carries the real UI instead of
|
|
# the placeholder page.
|
|
COPY backend/ ./
|
|
COPY --from=frontend /frontend/out/ ./internal/webui/dist/
|
|
|
|
# Build with version info injected via ldflags.
|
|
ARG VERSION=dev
|
|
ARG BUILD_TIME=unknown
|
|
ARG GIT_COMMIT=unknown
|
|
|
|
# TARGETOS/TARGETARCH are supplied automatically by buildx for each --platform.
|
|
ARG TARGETOS
|
|
ARG TARGETARCH
|
|
|
|
RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} go build \
|
|
-ldflags "-s -w \
|
|
-X github.com/Grace-Solutions/OrchestrAD/internal/version.Version=${VERSION} \
|
|
-X github.com/Grace-Solutions/OrchestrAD/internal/version.BuildTime=${BUILD_TIME} \
|
|
-X github.com/Grace-Solutions/OrchestrAD/internal/version.GitCommit=${GIT_COMMIT}" \
|
|
-o /orchestrad ./cmd/orchestrad
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Stage 3: minimal runtime
|
|
# ---------------------------------------------------------------------------
|
|
FROM alpine:3.19
|
|
|
|
RUN apk add --no-cache ca-certificates tzdata wget
|
|
|
|
# Create non-root user
|
|
RUN addgroup -g 1000 orchestrad && \
|
|
adduser -u 1000 -G orchestrad -D orchestrad
|
|
|
|
WORKDIR /app
|
|
|
|
# Copy binary
|
|
COPY --from=builder /orchestrad /app/orchestrad
|
|
|
|
# Create data directories
|
|
RUN mkdir -p /data/logs /data/backups && \
|
|
chown -R orchestrad:orchestrad /data
|
|
|
|
# Default environment
|
|
ENV ORCHESTRAD_DATA_PATH=/data
|
|
ENV ORCHESTRAD_LOG_LEVEL=info
|
|
# Containers typically sit behind an ingress/proxy that terminates TLS, so the
|
|
# image serves plain HTTP by default and the healthcheck below is HTTP. Set
|
|
# ORCHESTRAD_TLS_ENABLED=true to have the container manage its own certificate.
|
|
ENV ORCHESTRAD_TLS_ENABLED=false
|
|
|
|
# Expose port
|
|
EXPOSE 18090
|
|
|
|
# Health check
|
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
|
CMD wget --no-verbose --tries=1 --spider http://localhost:18090/health || exit 1
|
|
|
|
# Switch to non-root user
|
|
USER orchestrad
|
|
|
|
# Default to foreground mode
|
|
ENTRYPOINT ["/app/orchestrad"]
|
|
CMD ["run"]
|