From 123e04d17f5378a342d3bb6188ccfae2d6006145 Mon Sep 17 00:00:00 2001 From: jarek Date: Sat, 25 Jul 2026 08:45:12 +0200 Subject: [PATCH] 1.0.38 --- .dockerignore | 61 +++++++++++++ Dockerfile.backup | 51 +++++++++++ Dockerfile.backup.baseline | 28 ++++++ Dockerfile.baseline | 18 +++- Dockerfile.riscv64 | 163 +++++++++++++++++++++++++++++++++++ src/routes/+layout.server.ts | 2 +- 6 files changed, 318 insertions(+), 5 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile.backup create mode 100644 Dockerfile.backup.baseline create mode 100644 Dockerfile.riscv64 diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d7fddf0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,61 @@ +# Dependencies +node_modules/ + +# Build output +build/ +.svelte-kit/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Git +.git/ +.gitignore + +# CI/CD +.drone.yml +deployment/ + +# Tests +tests/ + +# Hawser agent (separate project) +hawser/ + +# Updater (separate project) +updater/ + +# Test image +test-image/ + +# Landing page +webpage/ + +# Misc +.DS_Store +*.log +.env +.env.* +!.env.example + +# Sensitive files +scripts/keys/ +data/ +*.pem +*.key +*.crt + +# Documentation +*.md +!README.md + +# Development files +docker-compose*.yml +!docker-compose.yml +bun.lock +tsconfig.json +.prettierrc +.eslintrc* diff --git a/Dockerfile.backup b/Dockerfile.backup new file mode 100644 index 0000000..b3efc87 --- /dev/null +++ b/Dockerfile.backup @@ -0,0 +1,51 @@ +# syntax=docker/dockerfile:1.4 +# Dockhand Backup Helper - Minimal restic image (Wolfi-based, zero CVEs) +# +# Rebuild trigger: the dockhand-backup-image pipeline is change-gated on this +# file. Bumping this comment forces a build so docker.io/fnsys/dockhand-backup:latest +# is (re)published to Docker Hub — the tag DEFAULT_HELPER_IMAGE (restic.ts) pulls. +# (re-triggered after fixing the ARM-builder Docker Hub login secret) +# (buildx steps consolidated into one block scalar) +# (remote ARM login: pipe password over stdin, no quoted interpolation) +# (use write-capable dockerhub token so the Docker Hub push authorizes) +# (revert rclone — the rclone: backend is unfinished/removed; see vault rclone.md) +FROM alpine:3.21 AS os-builder + +ARG TARGETARCH +WORKDIR /work + +ARG APKO_VERSION=0.30.34 +RUN apk add --no-cache curl \ + && ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "arm64" || echo "amd64") \ + && curl -sL "https://github.com/chainguard-dev/apko/releases/download/v${APKO_VERSION}/apko_${APKO_VERSION}_linux_${ARCH}.tar.gz" \ + | tar -xz --strip-components=1 -C /usr/local/bin \ + && chmod +x /usr/local/bin/apko + +RUN APKO_ARCH=$([ "$TARGETARCH" = "arm64" ] && echo "aarch64" || echo "x86_64") \ + && printf '%s\n' \ + "contents:" \ + " repositories:" \ + " - https://packages.wolfi.dev/os" \ + " keyring:" \ + " - https://packages.wolfi.dev/os/wolfi-signing.rsa.pub" \ + " packages:" \ + " - wolfi-base" \ + " - ca-certificates" \ + " - busybox" \ + " - restic" \ + "entrypoint:" \ + " command: /bin/sh" \ + "archs:" \ + " - ${APKO_ARCH}" \ + > apko.yaml + +RUN apko build apko.yaml dockhand-backup:latest output.tar \ + && mkdir -p rootfs \ + && tar -xf output.tar \ + && LAYER=$(tar -tf output.tar | grep '.tar.gz$' | head -1) \ + && tar -xzf "$LAYER" -C rootfs + +FROM scratch +COPY --from=os-builder /work/rootfs / +COPY scripts/backup-entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.backup.baseline b/Dockerfile.backup.baseline new file mode 100644 index 0000000..bf645b9 --- /dev/null +++ b/Dockerfile.backup.baseline @@ -0,0 +1,28 @@ +# syntax=docker/dockerfile:1.4 +# Dockhand Backup Helper — BASELINE build (Alpine/musl, amd64 only) +# +# The normal helper (Dockerfile.backup) is Wolfi-based; Wolfi compiles its restic +# and glibc for x86-64-v2 (SSE4.2/POPCNT). On older x86_64 CPUs without those +# instructions (TrueNAS, old Intel Atom/Celeron — the same hardware that needs the +# baseline Dockhand image) the Wolfi helper dies immediately: +# restic: "This program can only be run on AMD64 processors with v2 microarchitecture" +# busybox: "Fatal glibc error: CPU does not support x86-64-v2" +# so backups are completely broken for baseline users. +# +# Alpine's musl + its restic build run on x86-64-v1 (verified on a QEMU v1 CPU), so +# the baseline helper is a plain Alpine image with restic. amd64 only — baseline is a +# workaround for old x86_64 hardware, not an ARM concern. +# +# Rebuild trigger: the pipeline is change-gated on this file — bumping this comment +# forces a rebuild so the versioned/-baseline tags are (re)published. +# (revert rclone — the rclone: backend is unfinished/removed; see vault rclone.md) +FROM alpine:3.21 + +# restic + tar (busybox) + a real shell + CA certs cover everything the helper does: +# restic backup/restore/forget, the metadata shell script (set -e, tar), and TLS to +# the backup repository. +RUN apk add --no-cache restic ca-certificates tar + +COPY scripts/backup-entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/Dockerfile.baseline b/Dockerfile.baseline index 73093ab..615a376 100644 --- a/Dockerfile.baseline +++ b/Dockerfile.baseline @@ -26,8 +26,8 @@ RUN gcc -shared -fPIC -O2 -o /tmp/libgetrandom-shim.so /tmp/getrandom-shim.c # Copy package files and install dependencies (--ignore-scripts blocks malicious postinstall hooks) COPY package.json package-lock.json ./ -RUN npm ci --ignore-scripts \ - && npm rebuild better-sqlite3 argon2 +RUN MAKEFLAGS="-j$(nproc)" npm ci --ignore-scripts \ + && MAKEFLAGS="-j$(nproc)" npm rebuild better-sqlite3 argon2 # Copy source code and build COPY . . @@ -44,7 +44,7 @@ RUN cp -r node_modules/better-sqlite3/build /tmp/better-sqlite3-build \ # ----------------------------------------------------------------------------- # Stage 2: Go Collector Builder # ----------------------------------------------------------------------------- -FROM golang:1.25.8 AS go-builder +FROM golang:1.25.12 AS go-builder WORKDIR /app COPY collector/ ./collector/ RUN cd collector && CGO_ENABLED=0 go build -o /app/bin/collection-worker . @@ -90,7 +90,8 @@ ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \ HOME=/home/dockhand \ PUID=1001 \ PGID=1001 \ - LD_PRELOAD=/usr/lib/libgetrandom-shim.so + LD_PRELOAD=/usr/lib/libgetrandom-shim.so \ + DOCKHAND_VARIANT=baseline # Copy application files with correct ownership COPY --from=app-builder --chown=dockhand:dockhand /app/node_modules ./node_modules @@ -123,6 +124,15 @@ RUN chmod +x ./scripts/*.sh ./scripts/**/*.sh 2>/dev/null || true RUN mkdir -p /home/dockhand/.dockhand/stacks /app/data \ && chown dockhand:dockhand /app/data /home/dockhand /home/dockhand/.dockhand /home/dockhand/.dockhand/stacks +# OCI image annotations (#1217) — lets tooling map this image back to its +# source repo (image registry name differs from GitHub repo name). +LABEL org.opencontainers.image.source="https://github.com/Finsys/dockhand" \ + org.opencontainers.image.url="https://dockhand.pro" \ + org.opencontainers.image.title="Dockhand" \ + org.opencontainers.image.description="Docker management" \ + org.opencontainers.image.vendor="Finsys" \ + org.opencontainers.image.licenses="BUSL-1.1" + EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ diff --git a/Dockerfile.riscv64 b/Dockerfile.riscv64 new file mode 100644 index 0000000..d8f9a4e --- /dev/null +++ b/Dockerfile.riscv64 @@ -0,0 +1,163 @@ +# syntax=docker/dockerfile:1.4 +# ============================================================================= +# Dockhand Docker Image - RISC-V 64-bit (Experimental) +# ============================================================================= +# Experimental riscv64 build. Wolfi has no riscv64 packages, and official +# node:24-alpine/slim images don't include riscv64 either. Alpine edge repos +# have nodejs 24 for riscv64, so we use those directly. +# +# docker-compose is not packaged for riscv64 in Alpine, so we build it from +# source using Go (which has full riscv64 support). +# +# lightningcss (used by Vite) has no riscv64 prebuilt binary, so we split +# the build: Vite runs on the build platform (fast, has lightningcss), then +# native addons are rebuilt on riscv64 (QEMU, slower but necessary). +# +# Build: +# docker buildx build --platform linux/riscv64 -f Dockerfile.riscv64 \ +# -t registry.bor6.pl/dockhand:riscv64-test --push . +# ============================================================================= + +# ----------------------------------------------------------------------------- +# Stage 1: Docker Compose Builder (Go - cross-compile from build platform) +# ----------------------------------------------------------------------------- +# Alpine edge has docker-cli for riscv64 but NOT docker-compose. +# Build it from source since Go has excellent riscv64 cross-compilation. +FROM --platform=$BUILDPLATFORM golang:1.25.9-alpine AS compose-builder +ARG TARGETARCH + +RUN apk add --no-cache git + +RUN git clone --depth 1 --branch v2.36.1 https://github.com/docker/compose.git /src/compose + +WORKDIR /src/compose +RUN CGO_ENABLED=0 GOARCH=$TARGETARCH go build -trimpath -ldflags="-s -w" -o /usr/bin/docker-compose ./cmd + +# ----------------------------------------------------------------------------- +# Stage 2: JS Builder (runs on build platform - has lightningcss binaries) +# ----------------------------------------------------------------------------- +# Vite/lightningcss have no riscv64 prebuilt binaries. Since Vite produces +# plain JS bundles with no arch dependency, we build on the host platform. +FROM --platform=$BUILDPLATFORM node:24-alpine AS js-builder + +WORKDIR /app + +RUN apk add --no-cache git curl python3 make g++ gcc musl-dev + +COPY package.json package-lock.json ./ +RUN MAKEFLAGS="-j$(nproc)" npm ci --ignore-scripts \ + && MAKEFLAGS="-j$(nproc)" npm rebuild + +COPY . . +RUN npm run build + +# ----------------------------------------------------------------------------- +# Stage 3: Native Addon Builder (runs on riscv64 via QEMU) +# ----------------------------------------------------------------------------- +# better-sqlite3 and argon2 have C/C++ native addons that must be compiled +# for the target architecture (riscv64). +FROM --platform=$TARGETPLATFORM alpine:edge AS addon-builder + +RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main \ + --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \ + nodejs npm python3 make g++ gcc musl-dev + +WORKDIR /app + +COPY package.json package-lock.json ./ +RUN MAKEFLAGS="-j$(nproc)" npm ci --omit=dev --ignore-scripts \ + && MAKEFLAGS="-j$(nproc)" npm rebuild better-sqlite3 argon2 \ + && rm -rf node_modules/@types + +# ----------------------------------------------------------------------------- +# Stage 4: Go Collector Builder (cross-compile from build platform) +# ----------------------------------------------------------------------------- +FROM --platform=$BUILDPLATFORM golang:1.25.9 AS go-builder +ARG TARGETARCH +WORKDIR /app +COPY collector/ ./collector/ +RUN cd collector && CGO_ENABLED=0 GOARCH=$TARGETARCH go build -o /app/bin/collection-worker . + +# ----------------------------------------------------------------------------- +# Stage 5: Final Image (Alpine edge runtime) +# ----------------------------------------------------------------------------- +FROM alpine:edge + +# Install runtime packages from main + community repos +RUN apk add --no-cache \ + --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main \ + --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \ + nodejs \ + ca-certificates \ + tzdata \ + docker-cli \ + docker-cli-buildx \ + sqlite \ + postgresql17-client \ + git \ + openssh-client \ + curl \ + tini \ + su-exec \ + libstdc++ + +# Copy docker-compose built from source (not available as Alpine package for riscv64) +COPY --from=compose-builder /usr/bin/docker-compose /usr/bin/docker-compose + +# Create docker compose plugin symlink +RUN mkdir -p /usr/libexec/docker/cli-plugins \ + && ln -sf /usr/bin/docker-compose /usr/libexec/docker/cli-plugins/docker-compose + +# Create dockhand user and group +RUN addgroup -g 1001 dockhand \ + && adduser -u 1001 -G dockhand -h /home/dockhand -D dockhand + +WORKDIR /app + +# Set up environment variables +ENV SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt \ + NODE_ENV=production \ + PORT=3000 \ + HOST=0.0.0.0 \ + DATA_DIR=/app/data \ + HOME=/home/dockhand \ + PUID=1001 \ + PGID=1001 + +# Copy node_modules with riscv64 native addons +COPY --from=addon-builder --chown=dockhand:dockhand /app/node_modules ./node_modules + +# Copy built JS app from cross-platform builder +COPY --from=js-builder --chown=dockhand:dockhand /app/package.json ./ +COPY --from=js-builder --chown=dockhand:dockhand /app/build ./build +COPY --from=js-builder --chown=dockhand:dockhand /app/server.js ./ + +# Copy Go collector binary +COPY --from=go-builder --chown=dockhand:dockhand /app/bin/collection-worker ./bin/collection-worker + +# Copy database migrations +COPY --chown=dockhand:dockhand drizzle/ ./drizzle/ +COPY --chown=dockhand:dockhand drizzle-pg/ ./drizzle-pg/ + +# Copy legal documents +COPY --chown=dockhand:dockhand LICENSE.txt PRIVACY.txt ./ + +# Copy entrypoint script +COPY docker-entrypoint-node.sh /usr/local/bin/docker-entrypoint.sh +RUN chmod +x /usr/local/bin/docker-entrypoint.sh + +# Copy emergency scripts +COPY --chown=dockhand:dockhand scripts/emergency/ ./scripts/ +RUN chmod +x ./scripts/*.sh ./scripts/**/*.sh 2>/dev/null || true + +# Create data directories +RUN mkdir -p /home/dockhand/.dockhand/stacks /app/data \ + && chown dockhand:dockhand /app/data /home/dockhand /home/dockhand/.dockhand /home/dockhand/.dockhand/stacks + +EXPOSE 3000 + +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:${PORT:-3000}/ || exit 1 + +ENTRYPOINT ["/sbin/tini", "--", "/usr/local/bin/docker-entrypoint.sh"] +CMD [] diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index cef8ee4..338913a 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -2,7 +2,7 @@ import type { LayoutServerLoad } from './$types'; import { redirect } from '@sveltejs/kit'; import { isAuthEnabled, validateSession } from '$lib/server/auth'; import { hasAdminUser } from '$lib/server/db'; - import { BACKUPS_ENABLED } from '$lib/server/features'; +import { BACKUPS_ENABLED } from '$lib/server/features'; // Routes that don't require authentication const PUBLIC_PATHS = ['/login'];