From c3755b48a59bc1e2c77d75dfbdbc8cf17b00fe40 Mon Sep 17 00:00:00 2001 From: Jos Vooges | STH Date: Tue, 8 Sep 2026 03:47:07 +0200 Subject: [PATCH] Fix Synology Admin upgrade without docker.sock (rw binary mount). Co-authored-by: Cursor --- apps/admin-ui/src/app/nodes/page.tsx | 2 +- apps/master-api/src/nodes/version.ts | 2 +- deploy/docker/Dockerfile.master-api | 2 +- deploy/node/install-synology.sh | 8 +++-- deploy/node/install-unraid.sh | 4 ++- deploy/node/upgrade-node.sh | 21 ++++++++--- node/media-node/cmd/media-node/main.go | 2 +- node/media-node/internal/control/upgrade.go | 39 +++++++++++++++++++-- 8 files changed, 67 insertions(+), 13 deletions(-) diff --git a/apps/admin-ui/src/app/nodes/page.tsx b/apps/admin-ui/src/app/nodes/page.tsx index 3dd5e27..6b1d35d 100644 --- a/apps/admin-ui/src/app/nodes/page.tsx +++ b/apps/admin-ui/src/app/nodes/page.tsx @@ -65,7 +65,7 @@ export default function NodesPage() { const [editRoots, setEditRoots] = useState([]); const [editBusy, setEditBusy] = useState(false); const [upgradingId, setUpgradingId] = useState(null); - const [targetNodeVersion, setTargetNodeVersion] = useState("1.3.5"); + const [targetNodeVersion, setTargetNodeVersion] = useState("1.3.6"); function loadNodes() { fetch("/api/v1/admin/nodes", { credentials: "include" }) diff --git a/apps/master-api/src/nodes/version.ts b/apps/master-api/src/nodes/version.ts index e826be0..00e12f3 100644 --- a/apps/master-api/src/nodes/version.ts +++ b/apps/master-api/src/nodes/version.ts @@ -1,6 +1,6 @@ /** Target media-node version served from /install/media-node-linux-* */ export const MEDIA_NODE_TARGET_VERSION = - process.env.MEDIA_NODE_VERSION?.trim() || "1.3.5"; + process.env.MEDIA_NODE_VERSION?.trim() || "1.3.6"; /** Minimum node version that handles UPGRADE over WebSocket */ const REMOTE_UPGRADE_MIN_VERSION = "1.3.3"; diff --git a/deploy/docker/Dockerfile.master-api b/deploy/docker/Dockerfile.master-api index 5f2237c..bbb88a9 100644 --- a/deploy/docker/Dockerfile.master-api +++ b/deploy/docker/Dockerfile.master-api @@ -4,7 +4,7 @@ WORKDIR /app FROM golang:1.22-alpine AS node-build WORKDIR /src -ARG MEDIA_NODE_VERSION=1.3.5 +ARG MEDIA_NODE_VERSION=1.3.6 RUN apk add --no-cache git ca-certificates COPY node/media-node/ ./ RUN go mod tidy \ diff --git a/deploy/node/install-synology.sh b/deploy/node/install-synology.sh index 32a22cd..f732865 100644 --- a/deploy/node/install-synology.sh +++ b/deploy/node/install-synology.sh @@ -163,13 +163,16 @@ docker_cmd rm -f media-node >/dev/null 2>&1 || true VOLUMES=( -v "$APPDATA/config:/etc/media-node" -v "$APPDATA/data:/var/lib/media-node" - -v "$BIN_PATH:/usr/local/bin/media-node:ro" + # :rw zodat Admin→Upgrade de binary kan overschrijven zonder docker.sock + # (op DSM is /var/run/docker.sock meestal root-only → permission denied in de container) + -v "$BIN_PATH:/usr/local/bin/media-node:rw" + -v "$APPDATA/bin:/opt/media-node-bin:rw" -v "$MEDIA_ROOT:$MEDIA_ROOT:rw" ) if [ -d /etc/ssl/certs ]; then VOLUMES+=(-v /etc/ssl/certs:/etc/ssl/certs:ro) fi -# Admin → Herstart via docker.sock (DSM: vaak root:root — group-add helpt niet altijd) +# Optioneel: docker.sock voor Admin→Herstart (faalt vaak op DSM; restart-policy vangt exit op) if [ -S /var/run/docker.sock ]; then VOLUMES+=(-v /var/run/docker.sock:/var/run/docker.sock) fi @@ -202,6 +205,7 @@ docker_cmd run -d \ -e "MEDIA_NODE_FULL_SCAN_INTERVAL=off" \ -e "MEDIA_NODE_FULL_SCAN_AT=03:30" \ -e "MEDIA_NODE_CONTAINER_NAME=media-node" \ + -e "MEDIA_NODE_BIN_DIR=/opt/media-node-bin" \ -e "TZ=${TZ:-Europe/Amsterdam}" \ --user "${PUID}:${PGID}" \ "${GROUP_ARGS[@]}" \ diff --git a/deploy/node/install-unraid.sh b/deploy/node/install-unraid.sh index 13f1891..cd7be7e 100644 --- a/deploy/node/install-unraid.sh +++ b/deploy/node/install-unraid.sh @@ -145,7 +145,8 @@ docker rm -f media-node >/dev/null 2>&1 || true VOLUMES=( -v "$APPDATA/config:/etc/media-node" -v "$APPDATA/data:/var/lib/media-node" - -v "$BIN_PATH:/usr/local/bin/media-node:ro" + -v "$BIN_PATH:/usr/local/bin/media-node:rw" + -v "$APPDATA/bin:/opt/media-node-bin:rw" -v /etc/ssl/certs:/etc/ssl/certs:ro -v "$MEDIA_ROOT:$MEDIA_ROOT:ro" ) @@ -182,6 +183,7 @@ docker run -d \ -e "MEDIA_NODE_FULL_SCAN_INTERVAL=off" \ -e "MEDIA_NODE_FULL_SCAN_AT=03:30" \ -e "MEDIA_NODE_CONTAINER_NAME=media-node" \ + -e "MEDIA_NODE_BIN_DIR=/opt/media-node-bin" \ -e "TZ=${TZ:-Europe/Amsterdam}" \ --user 99:100 \ "${GROUP_ARGS[@]}" \ diff --git a/deploy/node/upgrade-node.sh b/deploy/node/upgrade-node.sh index 812b854..24c4eff 100644 --- a/deploy/node/upgrade-node.sh +++ b/deploy/node/upgrade-node.sh @@ -31,7 +31,20 @@ done MASTER_URL="$(echo -n "$MASTER_URL" | sed 's:/*$::')" APPDATA="$(echo -n "$APPDATA" | tr -d '\r\n')" -if ! command -v docker >/dev/null 2>&1; then +docker_cmd() { + if docker info >/dev/null 2>&1; then + docker "$@" + return + fi + if command -v sudo >/dev/null 2>&1 && sudo docker info >/dev/null 2>&1; then + sudo docker "$@" + return + fi + echo "Docker niet gevonden of geen rechten. Draai als root (Synology: sudo -i)." + exit 1 +} + +if ! command -v docker >/dev/null 2>&1 && ! command -v sudo >/dev/null 2>&1; then echo "Docker niet gevonden." exit 1 fi @@ -58,10 +71,10 @@ curl -fsSL "$MASTER_URL/install/$BIN_NAME" -o "$BIN_PATH.new" chmod +x "$BIN_PATH.new" mv "$BIN_PATH.new" "$BIN_PATH" -if docker ps -a --format '{{.Names}}' | grep -qx "$CONTAINER"; then +if docker_cmd ps -a --format '{{.Names}}' | grep -qx "$CONTAINER"; then echo "Container herstarten..." - docker restart "$CONTAINER" >/dev/null - echo "Klaar. Controleer versie in Admin → Nodes (verwacht ${MEDIA_NODE_VERSION:-1.3.5}+)." + docker_cmd restart "$CONTAINER" >/dev/null + echo "Klaar. Controleer versie in Admin → Nodes (verwacht ${MEDIA_NODE_VERSION:-1.3.6}+)." else echo "Waarschuwing: container '$CONTAINER' niet gevonden — binary is wel bijgewerkt." fi diff --git a/node/media-node/cmd/media-node/main.go b/node/media-node/cmd/media-node/main.go index 748f152..5d2ee48 100644 --- a/node/media-node/cmd/media-node/main.go +++ b/node/media-node/cmd/media-node/main.go @@ -24,7 +24,7 @@ import ( "github.com/sthmedia/media-node/internal/streaming" ) -var version = "1.3.5" +var version = "1.3.6" func main() { if len(os.Args) < 2 { diff --git a/node/media-node/internal/control/upgrade.go b/node/media-node/internal/control/upgrade.go index 4fa5842..dcca6b7 100644 --- a/node/media-node/internal/control/upgrade.go +++ b/node/media-node/internal/control/upgrade.go @@ -69,8 +69,21 @@ func (c *Client) runUpgrade(downloadURL string) error { return fmt.Errorf("chmod staging: %w", err) } + // Prefer in-place install (no Docker API). Needs binary bind-mount :rw + // (install-*.sh) — werkt op Synology waar docker.sock root-only is. + targets := []string{"/usr/local/bin/media-node"} + if binDir := strings.TrimSpace(os.Getenv("MEDIA_NODE_BIN_DIR")); binDir != "" { + targets = append(targets, filepath.Join(binDir, "media-node")) + } + for _, dest := range targets { + if err := installBinaryFile(stagingFile, dest); err == nil { + log.Printf("UPGRADE: binary geplaatst op %s", dest) + return nil + } + } + if _, err := os.Stat("/var/run/docker.sock"); err != nil { - return fmt.Errorf("docker.sock niet beschikbaar — gebruik handmatig upgrade-script") + return fmt.Errorf("geen schrijfrechten op binary-mount en geen docker.sock — SSH: curl -fsSL \"$MASTER/install/upgrade-node.sh\" | bash -s -- --appdata=") } containerName := strings.TrimSpace(os.Getenv("MEDIA_NODE_CONTAINER_NAME")) @@ -80,7 +93,7 @@ func (c *Client) runUpgrade(downloadURL string) error { binHostDir, dataHostDir, err := dockerContainerBindDirs(containerName) if err != nil { - return err + return fmt.Errorf("%w — op Synology vaak docker.sock permission denied; upgrade via SSH met upgrade-node.sh", err) } stagingHost := filepath.Join(dataHostDir, "upgrade", "media-node") @@ -95,6 +108,28 @@ func (c *Client) runUpgrade(downloadURL string) error { return nil } +func installBinaryFile(src, dest string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + // Direct overwrite: file-bind-mounts hebben vaak geen writable sibling (.new). + out, err := os.OpenFile(dest, os.O_WRONLY|os.O_TRUNC, 0o755) + if err != nil { + return err + } + if _, err := io.Copy(out, in); err != nil { + out.Close() + return err + } + if err := out.Close(); err != nil { + return err + } + return os.Chmod(dest, 0o755) +} + func downloadFile(url, dest string) error { log.Printf("UPGRADE: download %s", url) tmp := dest + ".download"