pipekit/deploy.sh
Paul Trowbridge 95292bd3f8 deploy.sh: fix SQLite journal permissions; cli: upsert drivers by kind
SQLite needs write access to the repo directory to create journal files
alongside pipekit.db. Fixed by setting group pipekit + g+w on the
directory itself only (not recursive).

Driver registration now matches existing rows by kind before falling
back to name, so re-deploys update the correct row regardless of what
name was used at initial registration.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-06-03 21:42:55 -04:00

215 lines
9.1 KiB
Bash
Executable File

#!/usr/bin/env bash
# Pipekit deployment — idempotent. Re-run after any code update.
#
# What it does:
# 1. Creates the 'pipekit' system user and group (if absent)
# 2. Adds the invoking user to the 'pipekit' group
# 3. Sets ownership of /opt/pipekit to pipekit:pipekit (group-writable)
# 4. Creates Python venv (as pipekit) and installs requirements
# 5. Installs /usr/local/bin/pipekit launcher
# 6. Creates /etc/pipekit/secrets.env (mode 0640, group pipekit)
# 7. Runs 'pipekit init' to create/upgrade the SQLite schema
# 8. Registers JDBC driver rows for every jar shipped with jrunner
# 9. Installs and enables the systemd unit (does not start it)
#
# Usage:
# ./deploy.sh # re-execs itself with sudo if needed
#
# After deploy:
# sudo pipekit secrets set KEY VALUE # add connection passwords
# sudo systemctl start pipekit
set -euo pipefail
REPO_DIR="${PIPEKIT_REPO:-$(cd "$(dirname "$0")" && pwd)}"
VENV_DIR="$REPO_DIR/.venv"
LAUNCHER="/usr/local/bin/pipekit"
CONFIG_DIR="/etc/pipekit"
SECRETS_FILE="$CONFIG_DIR/secrets.env"
SERVICE_NAME="pipekit"
UNIT_SRC="$REPO_DIR/systemd/pipekit.service"
UNIT_DST="/etc/systemd/system/pipekit.service"
# Capture the invoking user before re-execing as root
INVOKING_USER="${SUDO_USER:-${USER:-}}"
# Re-exec as root if needed
if [ "$EUID" -ne 0 ]; then
exec sudo -E "$0" "$@"
fi
step() { echo ""; echo "── $* ──"; }
echo "== pipekit deploy =="
echo "repo: $REPO_DIR"
echo "venv: $VENV_DIR"
echo "secrets: $SECRETS_FILE"
echo "user: $SERVICE_NAME (invoking user: ${INVOKING_USER:-unknown})"
# ── Prerequisites ─────────────────────────────────────────────────────────────
step "Checking prerequisites"
command -v python3 >/dev/null || { echo "ERROR: python3 not on PATH"; exit 1; }
echo " python3: $(python3 --version)"
command -v jrunner >/dev/null || { echo "ERROR: jrunner not on PATH — install /opt/jrunner first"; exit 1; }
echo " jrunner: $(command -v jrunner)"
# ── 1. System user ────────────────────────────────────────────────────────────
step "System user"
if ! id -u "$SERVICE_NAME" >/dev/null 2>&1; then
echo " Creating system user: $SERVICE_NAME"
useradd --system --no-create-home --home-dir /nonexistent \
--shell /usr/sbin/nologin "$SERVICE_NAME"
echo " Done."
else
echo " User '$SERVICE_NAME' already exists (uid=$(id -u "$SERVICE_NAME"))."
fi
# ── 2. Add invoking user to pipekit group ─────────────────────────────────────
step "Group membership"
if [ -n "$INVOKING_USER" ] && [ "$INVOKING_USER" != "$SERVICE_NAME" ]; then
if id -nG "$INVOKING_USER" | grep -qw "$SERVICE_NAME"; then
echo " $INVOKING_USER is already in the '$SERVICE_NAME' group."
else
echo " Adding $INVOKING_USER to the '$SERVICE_NAME' group."
usermod -aG "$SERVICE_NAME" "$INVOKING_USER"
echo " Done. Log out and back in for this to take effect."
fi
else
echo " Skipping — could not determine invoking user."
fi
# ── 3. Ownership ──────────────────────────────────────────────────────────────
step "File ownership and permissions"
# Source code stays owned by the invoking user.
# pipekit needs to own pipekit.db and write to the repo directory
# (SQLite creates journal files alongside the db file).
DB_FILE="$REPO_DIR/pipekit.db"
if [ -f "$DB_FILE" ]; then
echo " $DB_FILE$SERVICE_NAME:$SERVICE_NAME"
chown "$SERVICE_NAME:$SERVICE_NAME" "$DB_FILE"
else
echo " $DB_FILE not yet created (pipekit init will create it as $SERVICE_NAME)"
fi
echo " $REPO_DIR (directory only) → group $SERVICE_NAME, group-writable"
chgrp "$SERVICE_NAME" "$REPO_DIR"
chmod g+w "$REPO_DIR"
echo " $VENV_DIR$SERVICE_NAME (created/managed below)"
echo " Done."
# ── 4. Venv + deps ────────────────────────────────────────────────────────────
step "Python venv"
if [ -d "$VENV_DIR" ] && [ "$(stat -c '%U' "$VENV_DIR")" != "$SERVICE_NAME" ]; then
echo " Venv exists but is not owned by $SERVICE_NAME — recreating."
rm -rf "$VENV_DIR"
fi
if [ ! -d "$VENV_DIR" ]; then
echo " Creating venv at $VENV_DIR"
mkdir -p "$VENV_DIR"
chown "$SERVICE_NAME:$SERVICE_NAME" "$VENV_DIR"
sudo -u "$SERVICE_NAME" HOME=/nonexistent python3 -m venv "$VENV_DIR"
else
echo " Venv already exists at $VENV_DIR."
fi
echo " Upgrading pip"
sudo -u "$SERVICE_NAME" HOME=/nonexistent \
"$VENV_DIR/bin/pip" install --quiet --no-cache-dir --upgrade pip
echo " Installing requirements"
sudo -u "$SERVICE_NAME" HOME=/nonexistent \
"$VENV_DIR/bin/pip" install --quiet --no-cache-dir -r "$REPO_DIR/requirements.txt"
echo " Done."
# ── 5. Launcher ───────────────────────────────────────────────────────────────
step "Launcher"
chmod +x "$REPO_DIR/bin/pipekit"
ln -sf "$REPO_DIR/bin/pipekit" "$LAUNCHER"
echo " $LAUNCHER -> $REPO_DIR/bin/pipekit"
# ── 6. Secrets file ───────────────────────────────────────────────────────────
step "Secrets file"
install -d -m 0755 "$CONFIG_DIR"
if [ ! -f "$SECRETS_FILE" ]; then
echo " Creating $SECRETS_FILE (mode 0640, group $SERVICE_NAME)"
install -m 0640 /dev/null "$SECRETS_FILE"
chown "root:$SERVICE_NAME" "$SECRETS_FILE"
cat > "$SECRETS_FILE" <<'EOF'
# pipekit secrets — loaded by the systemd unit as EnvironmentFile.
# Connection passwords are stored as $KEY references in the DB.
# Add entries with: sudo pipekit secrets set KEY VALUE
EOF
else
echo " $SECRETS_FILE already exists — keeping contents."
chown "root:$SERVICE_NAME" "$SECRETS_FILE"
chmod 0640 "$SECRETS_FILE"
echo " Permissions ensured: 0640 group $SERVICE_NAME."
fi
# ── 7. Schema init ────────────────────────────────────────────────────────────
step "Database schema"
DB_FILE="$REPO_DIR/pipekit.db"
if [ ! -f "$DB_FILE" ]; then
echo " Creating $DB_FILE owned by $SERVICE_NAME"
touch "$DB_FILE"
chown "$SERVICE_NAME:$SERVICE_NAME" "$DB_FILE"
fi
echo " Running pipekit init"
sudo -u "$SERVICE_NAME" HOME=/nonexistent "$LAUNCHER" init
echo " Done."
# ── 8. Driver registration ────────────────────────────────────────────────────
step "JDBC driver registration"
JR_LIB="$(dirname "$(readlink -f "$(command -v jrunner)")")/../lib"
echo " jrunner lib dir: $JR_LIB"
register_jar() {
local kind="$1" pattern="$2"
local jar
jar="$(find "$JR_LIB" -maxdepth 1 -name "$pattern" 2>/dev/null | head -1)"
if [ -n "$jar" ]; then
echo " Registering $kind: $(basename "$jar")"
sudo -u "$SERVICE_NAME" HOME=/nonexistent "$LAUNCHER" drivers register "$kind" --jar "$jar"
else
echo " No $pattern found in $JR_LIB — skipping $kind"
fi
}
register_jar db2 "jt400-*.jar"
register_jar pg "postgresql-*.jar"
register_jar mssql "mssql-jdbc-*.jar"
# ── 9. Systemd unit ───────────────────────────────────────────────────────────
step "Systemd unit"
if [ ! -f "$UNIT_SRC" ]; then
echo " WARNING: $UNIT_SRC not found — skipping"
else
echo " Installing $UNIT_DST"
cp "$UNIT_SRC" "$UNIT_DST"
echo " Running systemctl daemon-reload"
systemctl daemon-reload
echo " Enabling $SERVICE_NAME service"
systemctl enable "$SERVICE_NAME"
echo " Done."
fi
echo ""
echo "════════════════════════════════════"
echo " pipekit deployed successfully."
echo "════════════════════════════════════"
echo ""
echo "Next steps:"
echo " 1. Add connection passwords:"
echo " sudo pipekit secrets set DB2PW <value>"
echo " sudo pipekit secrets set PGPW <value>"
echo " 2. Start the service:"
echo " sudo systemctl start pipekit"
echo " 3. Check it:"
echo " sudo systemctl status pipekit"
echo " journalctl -u pipekit -f"
if [ -n "$INVOKING_USER" ]; then
if ! id -nG "$INVOKING_USER" 2>/dev/null | grep -qw "$SERVICE_NAME"; then
echo ""
echo " NOTE: $INVOKING_USER was added to the '$SERVICE_NAME' group."
echo " Log out and back in for write access to take effect."
fi
fi