first commit

This commit is contained in:
2025-12-15 12:07:32 +01:00
commit 6f49528bbd
21 changed files with 1258 additions and 0 deletions

21
scripts/create_venv.sh Executable file
View File

@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
PROJECT_ROOT="${PROJECT_ROOT:-${DEFAULT_ROOT}}"
PYTHON_BIN="${PYTHON_BIN:-python3}"
if ! command -v "${PYTHON_BIN}" >/dev/null 2>&1; then
echo "Python executable not found: ${PYTHON_BIN}" >&2
exit 1
fi
cd "${PROJECT_ROOT}"
"${PYTHON_BIN}" -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r backend/requirements.txt
echo "Virtualenv ready at ${PROJECT_ROOT}/.venv"

143
scripts/deploy.sh Normal file
View File

@ -0,0 +1,143 @@
#!/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 <host-name> [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 <host-name> [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"

84
scripts/install.sh Executable file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)"
SERVICE_NAME="${SERVICE_NAME:-skd}"
SERVICE_USER="${SERVICE_USER:-skd}"
SERVICE_GROUP="${SERVICE_GROUP:-$SERVICE_USER}"
INSTALL_DIR="${INSTALL_DIR:-/opt/sk}"
ENV_DIR="/etc/${SERVICE_NAME}"
ENV_FILE="${ENV_DIR}/env"
SYSTEMD_PATH="/etc/systemd/system/${SERVICE_NAME}.service"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "Required command not found: $1" >&2
exit 1
fi
}
log "Ensuring service user/group (${SERVICE_USER}) exist..."
if ! getent group "${SERVICE_GROUP}" >/dev/null 2>&1; then
sudo groupadd -r "${SERVICE_GROUP}"
fi
if ! id -u "${SERVICE_USER}" >/dev/null 2>&1; then
sudo useradd -r -s /usr/sbin/nologin -d "${INSTALL_DIR}" -g "${SERVICE_GROUP}" "${SERVICE_USER}"
log "Created system user ${SERVICE_USER}"
fi
require_cmd rsync
log "Syncing project to ${INSTALL_DIR}..."
sudo mkdir -p "${INSTALL_DIR}"
sudo rsync -a --delete --exclude '.venv' "${SOURCE_DIR}/" "${INSTALL_DIR}/"
sudo chown -R "${SERVICE_USER}:${SERVICE_GROUP}" "${INSTALL_DIR}"
log "Creating/refreshing virtualenv in ${INSTALL_DIR}..."
sudo -u "${SERVICE_USER}" PROJECT_ROOT="${INSTALL_DIR}" "${SOURCE_DIR}/scripts/create_venv.sh"
log "Preparing environment file..."
sudo mkdir -p "${ENV_DIR}"
if [[ ! -f "${ENV_FILE}" ]]; then
if [[ -f "${SOURCE_DIR}/env.example" ]]; then
sudo cp "${SOURCE_DIR}/env.example" "${ENV_FILE}"
log "Copied env.example to ${ENV_FILE}; edit SKD_AUTH_TOKEN and SKD_ALLOWED_USERS."
else
sudo touch "${ENV_FILE}"
log "Created empty ${ENV_FILE}; populate required variables."
fi
sudo chmod 640 "${ENV_FILE}"
sudo chown root:"${SERVICE_GROUP}" "${ENV_FILE}"
fi
log "Writing systemd unit to ${SYSTEMD_PATH}..."
sudo tee "${SYSTEMD_PATH}" >/dev/null <<EOF
[Unit]
Description=Safe Kiddo Daemon
After=network.target
[Service]
Type=simple
User=${SERVICE_USER}
Group=${SERVICE_GROUP}
WorkingDirectory=${INSTALL_DIR}
EnvironmentFile=${ENV_FILE}
ExecStart=${INSTALL_DIR}/.venv/bin/uvicorn backend.app:app --host 0.0.0.0 --port 8000
Restart=on-failure
RestartSec=3
[Install]
WantedBy=multi-user.target
EOF
log "Enabling and starting service..."
sudo systemctl daemon-reload
sudo systemctl enable "${SERVICE_NAME}.service"
sudo systemctl restart "${SERVICE_NAME}.service"
log "Installation finished. Check status: sudo systemctl status ${SERVICE_NAME}.service"

14
scripts/run.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${PROJECT_ROOT}"
source .venv/bin/activate
UVICORN_CMD=${UVICORN_CMD:-uvicorn}
HOST=${HOST:-0.0.0.0}
PORT=${PORT:-8000}
exec "${UVICORN_CMD}" backend.app:app --host "${HOST}" --port "${PORT}"

23
scripts/update.sh Executable file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
PROJECT_ROOT="${PROJECT_ROOT:-${DEFAULT_ROOT}}"
SERVICE_NAME="${SERVICE_NAME:-skd}"
SERVICE_USER="${SERVICE_USER:-skd}"
BRANCH="${BRANCH:-main}"
cd "${PROJECT_ROOT}"
echo "[*] Fetching updates..."
git fetch --all
git reset --hard "origin/${BRANCH}"
echo "[*] Refreshing dependencies..."
sudo -u "${SERVICE_USER}" PROJECT_ROOT="${PROJECT_ROOT}" "${SCRIPT_DIR}/create_venv.sh"
echo "[*] Restarting service..."
sudo systemctl restart "${SERVICE_NAME}.service"
echo "[*] Update finished."