replaces the single hardcoded systemd VNC service with a dynamic sidecar that manages per-user VNC sessions on demand. any authenticated user can now access their own desktop, not just Super Admin. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
206 lines
6.5 KiB
Bash
Executable File
206 lines
6.5 KiB
Bash
Executable File
#!/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:-<from email>})"
|
|
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"
|
|
|
|
# VNC environment
|
|
VNC_DIR="$HOME_DIR/.vnc"
|
|
sudo mkdir -p "$VNC_DIR"
|
|
if [ ! -f "$VNC_DIR/passwd" ]; then
|
|
VNC_PASS=$(head -c 32 /dev/urandom | base64 | tr -dc 'a-zA-Z0-9' | head -c 8)
|
|
echo -n "$VNC_PASS" | sudo tee "$VNC_DIR/password" > /dev/null
|
|
echo -n "$VNC_PASS" | vncpasswd -f | sudo tee "$VNC_DIR/passwd" > /dev/null
|
|
sudo tee "$VNC_DIR/xstartup" > /dev/null << 'XSTARTUP'
|
|
#!/bin/sh
|
|
unset SESSION_MANAGER
|
|
unset DBUS_SESSION_BUS_ADDRESS
|
|
eval $(dbus-launch --sh-syntax)
|
|
export DBUS_SESSION_BUS_ADDRESS
|
|
exec startxfce4
|
|
XSTARTUP
|
|
sudo chmod +x "$VNC_DIR/xstartup"
|
|
sudo chmod 600 "$VNC_DIR/passwd"
|
|
sudo chmod 600 "$VNC_DIR/password"
|
|
ok "Provisioned VNC environment"
|
|
else
|
|
skip "VNC environment"
|
|
fi
|
|
|
|
# Set ownership and permissions last
|
|
# chmod 770 so only owner and group can access (service user is added to group above)
|
|
sudo chown -R "$shell_user:$shell_user" "$USER_ROOT"
|
|
sudo chmod -R 770 "$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 ""
|