From f32706be015c0aaef4929410f7e84ed271504399 Mon Sep 17 00:00:00 2001 From: Paul Trowbridge Date: Wed, 3 Jun 2026 21:35:25 -0400 Subject: [PATCH] Verbose deploy.sh, fix driver upsert, and fix pip cache warning deploy.sh now prints each step with what it's doing, adds the invoking user to the pipekit group automatically, uses --home-dir /nonexistent for the system user, and passes --no-cache-dir to pip to suppress the home directory warning. cli.py: removed the kind-based early-exit in drivers register that was short-circuiting before the upsert logic, so re-running deploy now correctly updates existing driver rows rather than printing "already registered". Also removed the now-unused --force flag. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- deploy.sh | 136 ++++++++++++++++++++++++++++++++++--------------- pipekit/cli.py | 9 ---- 2 files changed, 96 insertions(+), 49 deletions(-) diff --git a/deploy.sh b/deploy.sh index 1256984..8508a97 100755 --- a/deploy.sh +++ b/deploy.sh @@ -2,14 +2,15 @@ # Pipekit deployment — idempotent. Re-run after any code update. # # What it does: -# 1. Creates the 'pipekit' system user (if absent) -# 2. Chowns /opt/pipekit to pipekit:pipekit -# 3. Creates Python venv (as pipekit) and installs requirements -# 4. Installs /usr/local/bin/pipekit launcher -# 5. Creates /etc/pipekit/secrets.env (mode 0640, group pipekit) -# 6. Runs 'pipekit init' to create/upgrade the SQLite schema -# 7. Registers JDBC driver rows for every jar shipped with jrunner -# 8. Installs and enables the systemd unit (does not start it) +# 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 @@ -29,57 +30,95 @@ 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 -H -E "$0" "$@" + exec sudo -E "$0" "$@" fi +step() { echo ""; echo "── $* ──"; } + echo "== pipekit deploy ==" echo "repo: $REPO_DIR" echo "venv: $VENV_DIR" echo "secrets: $SECRETS_FILE" -echo "" +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 --shell /usr/sbin/nologin "$SERVICE_NAME" + 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." + echo " User '$SERVICE_NAME' already exists (uid=$(id -u "$SERVICE_NAME"))." fi -# ── 2. Ownership ────────────────────────────────────────────────────────────── -echo "Setting ownership of $REPO_DIR to $SERVICE_NAME:$SERVICE_NAME" -chown -R "$SERVICE_NAME:$SERVICE_NAME" "$REPO_DIR" +# ── 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. Venv + deps ──────────────────────────────────────────────────────────── +# ── 3. Ownership ────────────────────────────────────────────────────────────── +step "File ownership and permissions" +echo " Setting $REPO_DIR → $SERVICE_NAME:$SERVICE_NAME (group-writable)" +chown -R "$SERVICE_NAME:$SERVICE_NAME" "$REPO_DIR" +chmod -R g+w "$REPO_DIR" +echo " Done." + +# ── 4. Venv + deps ──────────────────────────────────────────────────────────── +step "Python venv" if [ -d "$VENV_DIR" ] && [ "$(stat -c '%U' "$VENV_DIR")" != "$SERVICE_NAME" ]; then - echo "Removing root-owned venv and recreating as $SERVICE_NAME" + 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" - sudo -u "$SERVICE_NAME" python3 -m venv "$VENV_DIR" + echo " Creating venv at $VENV_DIR" + sudo -u "$SERVICE_NAME" HOME=/nonexistent python3 -m venv "$VENV_DIR" +else + echo " Venv already exists at $VENV_DIR." fi -echo "Installing Python dependencies" -sudo -u "$SERVICE_NAME" "$VENV_DIR/bin/pip" install --quiet --upgrade pip -sudo -u "$SERVICE_NAME" "$VENV_DIR/bin/pip" install --quiet -r "$REPO_DIR/requirements.txt" -echo "Dependencies installed." +echo " Upgrading pip" +sudo -u "$SERVICE_NAME" HOME=/nonexistent \ + "$VENV_DIR/bin/pip" install --quiet --no-cache-dir --upgrade pip -# ── 4. Launcher ─────────────────────────────────────────────────────────────── +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: $LAUNCHER -> $REPO_DIR/bin/pipekit" +echo " $LAUNCHER -> $REPO_DIR/bin/pipekit" -# ── 5. Secrets file ─────────────────────────────────────────────────────────── +# ── 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' @@ -87,46 +126,56 @@ if [ ! -f "$SECRETS_FILE" ]; then # Connection passwords are stored as $KEY references in the DB. # Add entries with: sudo pipekit secrets set KEY VALUE EOF - echo "Created $SECRETS_FILE" else - # Ensure correct permissions even if file pre-existed + echo " $SECRETS_FILE already exists — keeping contents." chown "root:$SERVICE_NAME" "$SECRETS_FILE" chmod 0640 "$SECRETS_FILE" - echo "Keeping existing $SECRETS_FILE" + echo " Permissions ensured: 0640 group $SERVICE_NAME." fi -# ── 6. Schema init ──────────────────────────────────────────────────────────── -sudo -u "$SERVICE_NAME" "$LAUNCHER" init -echo "Schema initialised." +# ── 7. Schema init ──────────────────────────────────────────────────────────── +step "Database schema" +echo " Running pipekit init" +sudo -u "$SERVICE_NAME" HOME=/nonexistent "$LAUNCHER" init +echo " Done." -# ── 7. Driver registration ──────────────────────────────────────────────────── +# ── 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 - sudo -u "$SERVICE_NAME" "$LAUNCHER" drivers register "$kind" --jar "$jar" + echo " Registering $kind: $(basename "$jar")" + sudo -u "$SERVICE_NAME" HOME=/nonexistent "$LAUNCHER" drivers register "$kind" --jar "$jar" else - echo " (no $pattern in $JR_LIB — skipping $kind)" + 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" -# ── 8. Systemd unit ─────────────────────────────────────────────────────────── +# ── 9. Systemd unit ─────────────────────────────────────────────────────────── +step "Systemd unit" if [ ! -f "$UNIT_SRC" ]; then - echo "WARNING: $UNIT_SRC not found — skipping systemd install" + 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 "Systemd unit installed and enabled." + echo " Done." fi echo "" -echo "pipekit deployed." +echo "════════════════════════════════════" +echo " pipekit deployed successfully." +echo "════════════════════════════════════" echo "" echo "Next steps:" echo " 1. Add connection passwords:" @@ -137,3 +186,10 @@ 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 diff --git a/pipekit/cli.py b/pipekit/cli.py index 2690c88..4c9df0c 100644 --- a/pipekit/cli.py +++ b/pipekit/cli.py @@ -59,13 +59,6 @@ def cmd_drivers_register(args) -> int: print(f"error: {e}") return 1 - existing = [d for d in repo.list_drivers() if d["kind"] == args.kind] - if existing and not args.force: - print(f"driver kind {args.kind!r} already registered as " - f"{existing[0]['name']!r} (id={existing[0]['id']}). " - f"Use --force to add a second row.") - return 0 - class_name = args.class_name or _DEFAULT_JDBC_CLASSES.get(args.kind) if not class_name: print(f"error: no built-in JDBC class for kind {args.kind!r}; " @@ -315,8 +308,6 @@ def main(argv: list[str] | None = None) -> int: help="JDBC Driver class (default: built-in per kind)") p_drv_reg.add_argument("--url-template", help="optional JDBC URL template for the wizard") - p_drv_reg.add_argument("--force", action="store_true", - help="register even if a row for this kind exists") p_drv_reg.set_defaults(func=cmd_drivers_register) p_run = sub.add_parser("run", help="run a module by name (synchronous)")