From 1b6a5670680132b7302aa4054d0ad4fc1c9fcc95 Mon Sep 17 00:00:00 2001 From: Sejong Yoo Date: Wed, 14 Oct 2026 13:30:26 +0000 Subject: [PATCH] =?UTF-8?q?Jenkinsfile=C2=B7scripts/deploy.sh:=20NAS=20?= =?UTF-8?q?=EB=B0=B0=ED=8F=AC=EB=A5=BC=20Jenkins=EB=A1=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Sonnet 5.5 --- Jenkinsfile | 70 +++++++++++++++++++++++++++++++++++++++++++++++ scripts/deploy.sh | 35 ++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 Jenkinsfile create mode 100755 scripts/deploy.sh diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..ebca827 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,70 @@ +def isMain() { + def branch = env.BRANCH_NAME ?: env.GIT_BRANCH ?: '' + return !env.CHANGE_ID && branch in ['main', 'origin/main', 'refs/heads/main'] +} + +pipeline { + agent any + + environment { + SSH_CONFIG_NAME = 'SEJONG-NAS' + DEPLOY_DIR = '/volume1/docker/live-rec' + } + + options { + timestamps() + disableConcurrentBuilds() + buildDiscarder(logRotator(numToKeepStr: '20')) + } + + stages { + stage('Validate') { + steps { + sh ''' + set -eu + [ -f .env ] || : > .env + docker compose config -q + sh -n scripts/deploy.sh + ''' + } + } + + stage('Build') { + // main is built by deploy.sh on the NAS; other branches only prove the image builds. + when { not { expression { isMain() } } } + steps { + sh ''' + set -eu + tag="live-rec-ci:$(printf '%s' "$BUILD_TAG" | tr -c 'A-Za-z0-9_.-' '-' | cut -c1-120)" + trap 'docker image rm "$tag" >/dev/null 2>&1 || true' EXIT + docker build -q -t "$tag" . + ''' + } + } + + stage('Deploy') { + when { expression { isMain() } } + steps { + // The NAS checkout is the deployment: fast-forward it to the built commit (refusing local edits), + // then deploy.sh rebuilds and restarts the container. + sshPublisher( + failOnError: true, + publishers: [ + sshPublisherDesc( + configName: env.SSH_CONFIG_NAME, + verbose: true, + transfers: [ + sshTransfer( + execTimeout: 1800000, + execCommand: """ + set -eu; cd "${env.DEPLOY_DIR}"; git diff --quiet HEAD || { echo 'deploy: NAS checkout has local changes' >&2; git status --short >&2; exit 1; }; prev=\$(git rev-parse HEAD); git fetch -q origin main; git merge -q --ff-only "${env.GIT_COMMIT}"; sh scripts/deploy.sh "\$prev" + """.stripIndent().trim() + ) + ] + ) + ] + ) + } + } + } +} diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..f585492 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,35 @@ +#!/bin/sh +# Apply the current checkout on the NAS: fix folder permissions, rebuild, up -d. +# Usage: scripts/deploy.sh +# The caller fast-forwards the checkout first. If the build or start fails, the checkout is +# reset to ; a failed build leaves the running container untouched. +# NAS-only settings (PUID, REC_DIR, OIDC) live in .env next to the compose file. +set -eu + +cd "$(dirname "$0")/.." + +D="sudo -n /usr/local/bin/docker" +prev="${1:?usage: deploy.sh }" + +rollback() { + trap - EXIT + echo "deploy: FAILED, resetting checkout to $(git log -1 --format='%h' "$prev")" >&2 + git reset -q --hard "$prev" + exit 1 +} +trap rollback EXIT + +echo "deploy: $(git log -1 --format='%h %s') (was $(git log -1 --format='%h' "$prev"))" + +REC_DIR=$(grep -E '^REC_DIR=' .env | cut -d= -f2-) +mkdir -p "$REC_DIR" data/cookies +# A shared-folder ACL inherited by these folders makes them unwritable inside the container. +for p in data "$REC_DIR"; do /usr/syno/bin/synoacltool -del "$p" >/dev/null 2>&1 || true; done +chmod 755 data "$REC_DIR" +chmod 700 data/cookies + +$D compose config -q +$D compose up -d --build 2>&1 | tail -3 + +trap - EXIT +echo "deploy: ok"