#!/usr/bin/env bash set -euo pipefail # Deploys the current working tree to a remote host using a small config file (YAML or JSON). # Usage: scripts/deploy.sh [config-file] # Config default: deploy_hosts.yml with structure: # hosts: # - name: kid-laptop # host: 192.168.13.14 # user: stephan # port: 22 # install_dir: /opt/sk # service_name: skd # service_user: skd # service_group: skd if [[ $# -lt 1 ]]; then echo "Usage: $0 [config-file]" >&2 exit 1 fi HOST_NAME="$1" CONFIG_FILE="${2:-deploy_hosts.yml}" PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" if [[ ! -f "${CONFIG_FILE}" ]]; then echo "Config file not found: ${CONFIG_FILE}" >&2 exit 1 fi read_config() { python3 - <<'PY' "$HOST_NAME" "$CONFIG_FILE" import importlib import json import pathlib import shlex import sys host_name = sys.argv[1] config_path = pathlib.Path(sys.argv[2]) text = config_path.read_text() data = None try: data = json.loads(text) except json.JSONDecodeError: try: yaml = importlib.import_module("yaml") except ImportError: sys.exit("Config is not JSON; install PyYAML (pip install pyyaml) or provide JSON.") data = yaml.safe_load(text) hosts = data.get("hosts") if isinstance(data, dict) else None if not hosts: sys.exit("Config missing 'hosts' list") match = None for entry in hosts: if entry.get("name") == host_name: match = entry break if not match: sys.exit(f"No host named '{host_name}' in config") def req(key, default=None): val = match.get(key, default) if val is None: sys.exit(f"Missing required key '{key}' for host '{host_name}'") return val host = req("host") user = req("user") port = int(match.get("port", 22)) install_dir = req("install_dir", "/opt/sk") service_name = match.get("service_name", "skd") service_user = match.get("service_user", "skd") service_group = match.get("service_group", service_user) for key, val in { "REMOTE_HOST": host, "REMOTE_USER": user, "REMOTE_PORT": str(port), "INSTALL_DIR": install_dir, "SERVICE_NAME": service_name, "SERVICE_USER": service_user, "SERVICE_GROUP": service_group, }.items(): print(f'{key}={shlex.quote(val)}') PY } eval "$(HOST_NAME="${HOST_NAME}" CONFIG_FILE="${CONFIG_FILE}" read_config)" ARCHIVE="$(mktemp /tmp/skpkg.XXXXXX.tar.gz)" trap 'rm -f "${ARCHIVE}"' EXIT echo "[*] Packaging working tree (excluding .venv/.git)..." tar czf "${ARCHIVE}" \ --exclude '.venv' \ --exclude '.git' \ --exclude 'sk_deploy.zip' \ -C "${PROJECT_ROOT}" . REMOTE_PACKAGE="/tmp/skpkg.tar.gz" echo "[*] Copying package to ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PACKAGE} ..." scp -P "${REMOTE_PORT}" "${ARCHIVE}" "${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_PACKAGE}" read -r -d '' REMOTE_CMD <<'RCMD' set -euo pipefail INSTALL_DIR="${INSTALL_DIR}" SERVICE_NAME="${SERVICE_NAME}" SERVICE_USER="${SERVICE_USER}" SERVICE_GROUP="${SERVICE_GROUP}" PKG="${REMOTE_PACKAGE}" sudo mkdir -p "${INSTALL_DIR}" sudo rm -rf "${INSTALL_DIR:?}"/* sudo tar xzf "${PKG}" -C "${INSTALL_DIR}" sudo chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${INSTALL_DIR}" if ! sudo -u "${SERVICE_USER}" test -x "${INSTALL_DIR}/.venv/bin/python3"; then sudo -u "${SERVICE_USER}" python3 -m venv "${INSTALL_DIR}/.venv" fi sudo -u "${SERVICE_USER}" "${INSTALL_DIR}/.venv/bin/pip" install --upgrade pip sudo -u "${SERVICE_USER}" "${INSTALL_DIR}/.venv/bin/pip" install -r "${INSTALL_DIR}/backend/requirements.txt" sudo systemctl daemon-reload sudo systemctl restart "${SERVICE_NAME}.service" RCMD echo "[*] Deploying on remote host..." ssh -p "${REMOTE_PORT}" "${REMOTE_USER}@${REMOTE_HOST}" \ INSTALL_DIR="${INSTALL_DIR}" \ SERVICE_NAME="${SERVICE_NAME}" \ SERVICE_USER="${SERVICE_USER}" \ SERVICE_GROUP="${SERVICE_GROUP}" \ REMOTE_PACKAGE="${REMOTE_PACKAGE}" \ bash -s <<<"${REMOTE_CMD}" echo "[*] Deployment finished. Check status on remote: sudo systemctl status ${SERVICE_NAME}.service"