From 3615c9b3d8445eeeab8a648ae146bd541119b0d2 Mon Sep 17 00:00:00 2001 From: ginnoir Date: Thu, 1 Oct 2026 13:09:17 -0500 Subject: [PATCH] feat(devicebackup): build urbackup-server with guestmount for image file restores The stock image has no libguestfs, so UrBackup disables image mounting and single files can't be restored from image backups. Add libguestfs-tools + kernel/supermin/qemu on top of uroni/urbackup-server:2.5.x, built by Gitea Actions into registry.ginnoir.com/ginnoir/urbackup-server (Portainer only pulls). Verified on valhalla: KVM-accelerated appliance, MOUNT TEST OK. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build-urbackup-server.yml | 81 ++++++++++++++++++++++ stacks/devicebackup/urbackup/Dockerfile | 17 +++++ 2 files changed, 98 insertions(+) create mode 100644 .gitea/workflows/build-urbackup-server.yml create mode 100644 stacks/devicebackup/urbackup/Dockerfile diff --git a/.gitea/workflows/build-urbackup-server.yml b/.gitea/workflows/build-urbackup-server.yml new file mode 100644 index 0000000..69de757 --- /dev/null +++ b/.gitea/workflows/build-urbackup-server.yml @@ -0,0 +1,81 @@ +name: Build urbackup-server image + +# Stock uroni/urbackup-server + libguestfs-tools (guestmount), so UrBackup can mount +# image backups for single-file restores. Portainer only pulls, never builds, so the +# image is built here and pushed to the self-hosted registry, same as the romhacks +# orchestrator. Compose (stacks/devicebackup) pulls :latest. +# +# Rebuild after a UrBackup bump: bump URBACKUP_TAG in the Dockerfile, or run this +# workflow manually to pick up a moved 2.5.x tag / Debian security updates. + +on: + push: + branches: [main] + paths: + - stacks/devicebackup/urbackup/** + - .gitea/workflows/build-urbackup-server.yml + workflow_dispatch: + +concurrency: + group: build-urbackup-server + cancel-in-progress: false + +jobs: + build-and-push: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Ensure docker CLI + buildx + run: | + # See build-romhacks-orchestrator.yml: Debian's docker.io has no buildx plugin. + if docker buildx version >/dev/null 2>&1; then + docker version + docker buildx version + exit 0 + fi + + export DEBIAN_FRONTEND=noninteractive + apt-get update -qq + apt-get install -y -qq ca-certificates curl gnupg + install -m 0755 -d /etc/apt/keyrings + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ + > /etc/apt/sources.list.d/docker.list + apt-get update -qq + apt-get install -y -qq docker-ce-cli docker-buildx-plugin + docker version + docker buildx version + + - name: Login to registry + run: | + # Only the two REGISTRY_PUSH_* keys from the committed .env (it also holds + # vault unseal keys); password goes via stdin, never argv or the log. + eval "$(grep -E '^REGISTRY_PUSH_(USERNAME|PASSWORD)=' ./.env | sed 's/^/export /')" + if [ -z "${REGISTRY_PUSH_USERNAME:-}" ] || [ -z "${REGISTRY_PUSH_PASSWORD:-}" ]; then + echo "REGISTRY_PUSH_USERNAME/PASSWORD missing from .env" >&2 + exit 1 + fi + printf '%s' "$REGISTRY_PUSH_PASSWORD" | docker login registry.ginnoir.com \ + --username "$REGISTRY_PUSH_USERNAME" \ + --password-stdin + + - name: Set up buildx + run: | + docker buildx create --name urbackup-builder --use 2>/dev/null || docker buildx use urbackup-builder + docker buildx inspect --bootstrap + + - name: Build and push image + env: + IMAGE: registry.ginnoir.com/ginnoir/urbackup-server + CACHE: registry.ginnoir.com/ginnoir/urbackup-server:buildcache + run: | + SHA_TAG="sha-$(printf '%s' "$GITHUB_SHA" | cut -c1-7)" + docker buildx build \ + --push \ + --tag "${IMAGE}:latest" \ + --tag "${IMAGE}:${SHA_TAG}" \ + --cache-from "type=registry,ref=${CACHE}" \ + --cache-to "type=registry,ref=${CACHE},mode=max" \ + stacks/devicebackup/urbackup diff --git a/stacks/devicebackup/urbackup/Dockerfile b/stacks/devicebackup/urbackup/Dockerfile new file mode 100644 index 0000000..0aa8109 --- /dev/null +++ b/stacks/devicebackup/urbackup/Dockerfile @@ -0,0 +1,17 @@ +# UrBackup server + libguestfs, so the web UI can mount image backups and restore +# single files out of them. The stock image ships without guestmount, and the server +# logs "Image mounting disabled: TEST FAILED: guestmount is missing". +# +# libguestfs boots a tiny helper VM (supermin appliance) to read the VHDZ, which needs +# a kernel in /boot plus /dev/kvm and /dev/fuse at runtime (see docker-compose.yml). +# Built by .gitea/workflows/build-urbackup-server.yml → registry.ginnoir.com. +ARG URBACKUP_TAG=2.5.x +FROM uroni/urbackup-server:${URBACKUP_TAG} + +RUN apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ + libguestfs-tools linux-image-amd64 supermin qemu-system-x86 \ + && rm -rf /var/lib/apt/lists/* + +# Use the direct (qemu) backend; there is no libvirt in the container. +ENV LIBGUESTFS_BACKEND=direct