Ctrl K
ComunidadGuías…

deploy-remote.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# BoardRipper NAS Remote Deploy Script
# This script runs ON the NAS via SSH. It is SCP'd by NASdeploy.sh.
# Arguments: $1 = sudo password
set -e

DOCKER="/usr/local/bin/docker"
PW="$1"
IMAGE_NAME="boardripper"
IMAGE_TAG="latest"
CONFIG_FILE="/volume1/docker/boardripper/container-config.json"

# Fallback config for first deploy (no existing container AND no saved config)
DEFAULT_MOUNTS='-v /volume1/docker/boardripper/data:/data -v /volume1/AL ZEUG/LogiCloud/Schematics-BV-EFI:/library:ro -v /var/run/docker.sock:/var/run/docker.sock'
DEFAULT_PORTS='-p 8090:8080'
DEFAULT_ENV='-e PORT=8080 -e LIBRARY_DIR=/library'

# Helper: run docker with sudo, filtering prompt noise
sdocker() {
    echo "${PW}" | sudo -S ${DOCKER} "$@" 2>&1 | grep -v '^\[sudo\]' | grep -v '^Password:' || true
}

echo "[NAS] Decompressing image..."
gunzip -f /tmp/${IMAGE_NAME}.tar.gz

echo "[NAS] Loading Docker image..."
sdocker load -i /tmp/${IMAGE_NAME}.tar

# Image now ships USER 65532 (non-root). Existing /volume1/docker/boardripper/data
# was owned by root from prior root-running deploys; chown so the container can
# still write databank.db / .update-secret. Idempotent — no-op if already 65532.
echo "[NAS] Ensuring /volume1/docker/boardripper/data is owned by 65532:65532..."
echo "${PW}" | sudo -S chown -R 65532:65532 /volume1/docker/boardripper/data 2>&1 \
  | grep -v '^\[sudo\]' | grep -v '^Password:' || true

# ── Capture existing container config and persist to file ──
EXISTING=$(echo "${PW}" | sudo -S ${DOCKER} ps -a --filter "name=^${IMAGE_NAME}$" --format '{{.Names}}' 2>/dev/null | grep -v '^\[sudo\]' | grep -v '^Password:' || true)

if [ -n "${EXISTING}" ]; then
    echo "[NAS] Saving container config to ${CONFIG_FILE}..."
    echo "${PW}" | sudo -S ${DOCKER} inspect "${IMAGE_NAME}" 2>/dev/null \
        | grep -v '^\[sudo\]' | grep -v '^Password:' \
        > "${CONFIG_FILE}" 2>/dev/null || true
fi

# ── Read config: from saved file, or fall back to defaults ──
MOUNT_ARGS=""
PORT_ARGS=""
ENV_ARGS=""
RESTART_POLICY="unless-stopped"

if [ -f "${CONFIG_FILE}" ] && python3 -c "import sys,json; json.load(open(sys.argv[1]))" "${CONFIG_FILE}" 2>/dev/null; then
    echo "[NAS] Reading config from ${CONFIG_FILE}..."

    MOUNT_ARGS=$(python3 -c "
import sys, json
data = json.load(open(sys.argv[1]))
if isinstance(data, list): data = data[0]
for m in data.get('Mounts', []):
    if m.get('Type') == 'bind':
        s = '-v ' + m['Source'] + ':' + m['Destination']
        if not m.get('RW', True): s += ':ro'
        print(s, end=' ')
" "${CONFIG_FILE}" 2>/dev/null)

    PORT_ARGS=$(python3 -c "
import sys, json
data = json.load(open(sys.argv[1]))
if isinstance(data, list): data = data[0]
for cp, bindings in (data.get('HostConfig', {}).get('PortBindings') or {}).items():
    port = cp.replace('/tcp', '')
    for b in (bindings or []):
        hp = b.get('HostPort', '')
        hip = b.get('HostIp', '')
        if hp:
            prefix = (hip + ':') if hip and hip != '0.0.0.0' else ''
            print(f'-p {prefix}{hp}:{port}', end=' ')
" "${CONFIG_FILE}" 2>/dev/null)

    ENV_ARGS=$(python3 -c "
import sys, json
data = json.load(open(sys.argv[1]))
if isinstance(data, list): data = data[0]
skip = {'PATH', 'HOME', 'HOSTNAME'}
for e in data.get('Config', {}).get('Env', []):
    key = e.split('=', 1)[0]
    if key not in skip:
        print(f'-e {e}', end=' ')
" "${CONFIG_FILE}" 2>/dev/null)

    RP=$(python3 -c "
import sys, json
data = json.load(open(sys.argv[1]))
if isinstance(data, list): data = data[0]
print(data.get('HostConfig', {}).get('RestartPolicy', {}).get('Name', ''))
" "${CONFIG_FILE}" 2>/dev/null)
    if [ -n "${RP}" ] && [ "${RP}" != "no" ]; then
        RESTART_POLICY="${RP}"
    fi
else
    echo "[NAS] No saved config — using defaults"
fi

# Fall back to defaults for any empty fields
[ -z "${PORT_ARGS}" ] && PORT_ARGS="${DEFAULT_PORTS}" && echo "[NAS]   ports: using defaults"
[ -z "${MOUNT_ARGS}" ] && MOUNT_ARGS="${DEFAULT_MOUNTS}" && echo "[NAS]   mounts: using defaults"
[ -z "${ENV_ARGS}" ] && ENV_ARGS="${DEFAULT_ENV}" && echo "[NAS]   env: using defaults"

# Ensure Docker socket is always mounted (required for self-update)
if ! echo "${MOUNT_ARGS}" | grep -q 'docker.sock'; then
    MOUNT_ARGS="${MOUNT_ARGS} -v /var/run/docker.sock:/var/run/docker.sock"
fi

# Strip any legacy GITHUB_TOKEN that an old deploy may have baked into the
# saved container config — the offline-signed update path doesn't use it,
# and a stale token in the env can confuse external tools that key off it.
# Trailing-space-OR-end-of-string on the value match handles the case where
# the token is the last item in ENV_ARGS — earlier `[^ ]* `-with-mandatory-
# trailing-space pattern silently kept tokens at end-of-string. Add a
# trailing space first so a single regex form catches every position.
ENV_ARGS=$(echo "${ENV_ARGS} " | sed -E 's/-e GITHUB_TOKEN=[^ ]* //g; s/[[:space:]]+$//')

echo "[NAS]   mounts:  ${MOUNT_ARGS}"
echo "[NAS]   ports:   ${PORT_ARGS}"
echo "[NAS]   env:     ${ENV_ARGS}"
echo "[NAS]   restart: ${RESTART_POLICY}"

echo "[NAS] Stopping old container..."
sdocker stop ${IMAGE_NAME} 2>/dev/null || true
sdocker rm ${IMAGE_NAME} 2>/dev/null || true

echo "[NAS] Starting new container..."
# USER override: the in-image default is UID 65532 (good for fresh OSS
# deployments), but this NAS's library mounts contain subdirs owned by
# many different users (inwerp, sc-syncthing, etc.) with restrictive
# perms — only root can read across all of them. Override the image's
# USER for this specific install. The permission audit's H9 finding
# stays open as a follow-up: support non-root with mixed-ownership
# library mounts (likely via --group-add detection or a per-mount UID
# probe in the deploy script).
RUN_CMD=$(python3 -c "
import shlex
args = ['run', '-d', '--name', '${IMAGE_NAME}', '--restart', '${RESTART_POLICY}', '--user', '0:0']
for raw in '''${PORT_ARGS}
${MOUNT_ARGS}
${ENV_ARGS}'''.strip().splitlines():
    raw = raw.strip()
    if not raw: continue
    parts = raw.split()
    i = 0
    while i < len(parts):
        if parts[i] in ('-v', '-p', '-e') and i + 1 < len(parts):
            val_parts = [parts[i + 1]]
            j = i + 2
            while j < len(parts) and parts[j] not in ('-v', '-p', '-e'):
                val_parts.append(parts[j])
                j += 1
            args.extend([parts[i], ' '.join(val_parts)])
            i = j
        else:
            args.append(parts[i])
            i += 1
args.append('${IMAGE_NAME}:${IMAGE_TAG}')
print(' '.join(shlex.quote(a) for a in args))
" 2>/dev/null)
echo "[NAS]   cmd: docker ${RUN_CMD}"
echo "${DOCKER} ${RUN_CMD}" > /tmp/docker_run_cmd.sh
echo "${PW}" | sudo -S bash /tmp/docker_run_cmd.sh 2>&1 | grep -v '^\[sudo\]' | grep -v '^Password:' || true
rm -f /tmp/docker_run_cmd.sh

echo "[NAS] Pruning old images..."
sdocker image prune -f 2>/dev/null || true

echo "[NAS] Container status:"
sdocker ps --filter "name=${IMAGE_NAME}" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"

echo "[NAS] Mounts:"
echo "${PW}" | sudo -S ${DOCKER} inspect ${IMAGE_NAME} 2>/dev/null | python3 -c "
import sys, json
data = json.load(sys.stdin)
if isinstance(data, list): data = data[0]
for m in data.get('Mounts', []):
    mode = 'ro' if not m.get('RW', True) else 'rw'
    print(f'  {m[\"Source\"]} -> {m[\"Destination\"]} ({mode})')
" 2>/dev/null || echo "  (could not read mounts)"

echo "[NAS] Cleaning up..."
rm -f /tmp/${IMAGE_NAME}.tar /tmp/deploy-remote.sh