#!/bin/bash # One-time migration: provision Linux users for existing database members. # Reads users from PostgreSQL, skips Super Admins (they use the service account), # and creates Linux users + seeds shell configs for everyone else. # # Usage: bash scripts/provision-existing-users.sh # Requires: sudoers entry from setup.sh, POSTGRES_URL in .env set -euo pipefail GREEN='\033[0;32m' YELLOW='\033[1;33m' RED='\033[0;31m' NC='\033[0m' ok() { echo -e " ${GREEN}✓${NC} $1"; } warn() { echo -e " ${YELLOW}!${NC} $1"; } fail() { echo -e " ${RED}✗${NC} $1"; } skip() { echo -e " - $1 (skipped)"; } SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" ENV_FILE="$PROJECT_DIR/.env" TEMPLATE_DIR="$PROJECT_DIR/src/servers/api/terminal/templates" # Load .env if [ ! -f "$ENV_FILE" ]; then fail ".env not found at $ENV_FILE" exit 1 fi source <(grep -E '^[A-Z_]+=.*' "$ENV_FILE" | sed 's/^/export /') # Resolve DATA_PATH DATA_PATH="${DATA_PATH:-$PROJECT_DIR/data}" # Parse POSTGRES_URL for psql if [ -z "${POSTGRES_URL:-}" ]; then fail "POSTGRES_URL not set in .env" exit 1 fi # Extract components from postgresql://user:pass@host:port/dbname PG_USER=$(echo "$POSTGRES_URL" | sed -n 's|.*://\([^:]*\):.*|\1|p') PG_PASS=$(echo "$POSTGRES_URL" | sed -n 's|.*://[^:]*:\([^@]*\)@.*|\1|p') PG_HOST=$(echo "$POSTGRES_URL" | sed -n 's|.*@\([^:]*\):.*|\1|p') PG_PORT=$(echo "$POSTGRES_URL" | sed -n 's|.*:\([0-9]*\)/.*|\1|p') PG_DB=$(echo "$POSTGRES_URL" | sed -n 's|.*/\([^?]*\).*|\1|p') echo "" echo "═══════════════════════════════════════════" echo " Provision existing users" echo "═══════════════════════════════════════════" echo "" echo "DATA_PATH: $DATA_PATH" echo "Database: $PG_DB @ $PG_HOST:$PG_PORT" echo "" # Query non-Super Admin active users USERS=$(PGPASSWORD="$PG_PASS" psql -h "$PG_HOST" -p "$PG_PORT" -U "$PG_USER" -d "$PG_DB" -t -A -F '|' \ -c "SELECT email, COALESCE(username, '') FROM users WHERE role != 'Super Admin' AND status = 'Active';" 2>&1) if [ $? -ne 0 ]; then fail "Failed to query database: $USERS" exit 1 fi if [ -z "$USERS" ]; then echo "No non-admin active users found. Nothing to do." exit 0 fi echo "── Found users ──" echo "$USERS" | while IFS='|' read -r email username; do echo " $email (username: ${username:-})" done echo "" echo "── Provisioning ──" while IFS='|' read -r email username; do # Derive shell username (same logic as toShellUsername in data-path.ts) if [ -n "$username" ]; then shell_user=$(echo "$username" | sed 's/@.*$//' | sed 's/[^a-zA-Z0-9._-]/_/g' | tr '[:upper:]' '[:lower:]' | cut -c1-32) else shell_user=$(echo "$email" | sed 's/@.*$//' | sed 's/[^a-zA-Z0-9._-]/_/g' | tr '[:upper:]' '[:lower:]' | cut -c1-32) fi USER_ROOT="$DATA_PATH/$email" HOME_DIR="$USER_ROOT/home" echo "" echo " [$email → $shell_user]" # Ensure data dirs exist (sudo in case dir is owned by a previous provisioning run) sudo mkdir -p "$USER_ROOT" "$HOME_DIR" # Create Linux user if needed if id "$shell_user" &>/dev/null; then skip "Linux user $shell_user already exists" else if sudo useradd -d "$HOME_DIR" -s /bin/zsh -M "$shell_user"; then ok "Created Linux user $shell_user" else fail "Failed to create Linux user $shell_user" continue fi fi # Seed shell configs (only if not already present) if [ ! -f "$HOME_DIR/.zshenv" ] && [ -f "$TEMPLATE_DIR/.zshenv" ]; then sudo cp "$TEMPLATE_DIR/.zshenv" "$HOME_DIR/.zshenv" ok "Seeded .zshenv" else skip ".zshenv" fi if [ ! -f "$HOME_DIR/.zshrc" ] && [ -f "$TEMPLATE_DIR/.zshrc" ]; then sudo cp "$TEMPLATE_DIR/.zshrc" "$HOME_DIR/.zshrc" ok "Seeded .zshrc" else skip ".zshrc" fi if [ ! -f "$HOME_DIR/.tmux.conf" ] && [ -f "$TEMPLATE_DIR/.tmux.conf" ]; then sudo cp "$TEMPLATE_DIR/.tmux.conf" "$HOME_DIR/.tmux.conf" ok "Seeded .tmux.conf" else skip ".tmux.conf" fi sudo mkdir -p "$HOME_DIR/.config" if [ ! -f "$HOME_DIR/.config/starship-officer.toml" ] && [ -f "$TEMPLATE_DIR/starship-officer.toml" ]; then sudo cp "$TEMPLATE_DIR/starship-officer.toml" "$HOME_DIR/.config/starship-officer.toml" ok "Seeded starship config" else skip "starship config" fi # Oh My Zsh if [ ! -d "$HOME_DIR/.oh-my-zsh" ]; then if [ -d "$HOME/.oh-my-zsh" ]; then sudo cp -r "$HOME/.oh-my-zsh" "$HOME_DIR/.oh-my-zsh" ok "Copied oh-my-zsh from host" fi else skip "oh-my-zsh" fi # LazyVim sudo mkdir -p "$HOME_DIR/.config" if [ ! -d "$HOME_DIR/.config/nvim" ]; then if [ -d "$HOME/.config/nvim" ]; then sudo cp -r "$HOME/.config/nvim" "$HOME_DIR/.config/nvim" ok "Copied nvim config from host" fi else skip "nvim config" fi # Ensure dirs sudo mkdir -p "$HOME_DIR/.local/bin" sudo mkdir -p "$HOME_DIR/.pi/agent/sessions" # Set ownership and permissions last # chmod 775 so the service user (in the user's group) can read/write for background jobs sudo chown -R "$shell_user:$shell_user" "$USER_ROOT" sudo chmod -R 775 "$USER_ROOT" # Add service user to this user's group so server jobs can access user data SERVICE_USER="${SUDO_USER:-$(whoami)}" if [ "$SERVICE_USER" != "$shell_user" ]; then sudo usermod -aG "$shell_user" "$SERVICE_USER" ok "Added $SERVICE_USER to group $shell_user" fi ok "Provisioning complete" done <<< "$USERS" echo "" echo "═══════════════════════════════════════════" echo " Done!" echo "═══════════════════════════════════════════" echo ""