#!/usr/bin/env bash set -euo pipefail PERSON="${1:?Usage: $0 }" WORKSPACE="${SSH_ORIGINAL_COMMAND:-}" IMAGE="localhost/analytics-backend-workspace:latest" DEV_USER="devuser" XDG_RUNTIME_DIR="/run/user/$(id -u)" LOG_FILE="/tmp/.ssh-router-${PERSON}.log" # ───────────────────────────────────────────── # ANSI colors & emojis readonly C_RESET='\033[0m' readonly C_INFO='\033[1;34m' # blue readonly C_WARN='\033[1;33m' # yellow readonly C_ERROR='\033[1;31m' # red log() { local level="${1^^}" shift local icon color case "$level" in INFO) icon="ℹ️" color="$C_INFO" ;; WARN) icon="⚠️" color="$C_WARN" ;; ERROR) icon="❌" color="$C_ERROR" ;; *) icon="🔹" color="$C_RESET" ;; esac local ts ts="$(date '+%Y-%m-%d %H:%M:%S')" printf '%b%s [%s] %s%b\n' \ "$color" "$icon" "$ts" "[$level] $*" "$C_RESET" | tee -a "$LOG_FILE" } # ───────────────────────────────────────────── # Check for interactive TTY if [[ ! -t 0 ]]; then log ERROR "No TTY allocated—refusing to run without an interactive terminal" echo "Error: No TTY. Use 'ssh -t'" >&2 exit 1 fi # ───────────────────────────────────────────── # Default WORKSPACE if empty if [[ -z "$WORKSPACE" ]]; then WORKSPACE="$PERSON" log INFO "Defaulted WORKSPACE → $WORKSPACE" fi TMUX_SESSION="${WORKSPACE}|analytics-backend" # ───────────────────────────────────────────── # Ensure Podman socket is up ensure_podman() { local sock="$XDG_RUNTIME_DIR/podman/podman.sock" if [[ ! -S "$sock" ]]; then log INFO "Starting podman.socket for user $(id -un)" systemctl --user start podman.socket || { log ERROR "Failed to start podman.socket" exit 1 } sleep 1 fi [[ -S "$sock" ]] || { log ERROR "Podman socket still missing" exit 1 } } ensure_podman # ───────────────────────────────────────────── # Ensure IMAGE is present ensure_image() { if ! podman image exists "$IMAGE"; then log WARN "Image $IMAGE not found—pulling" podman pull --tls-verify=false "$IMAGE" || { log ERROR "Failed to pull $IMAGE" exit 1 } log INFO "Pulled $IMAGE" fi } ensure_image # ───────────────────────────────────────────── # Disallow file transfers case "$SSH_ORIGINAL_COMMAND" in *scp* | *sftp* | *rsync* | *tar*) log ERROR "File transfers are disabled" exit 1 ;; esac # ───────────────────────────────────────────── # Generate per-user gitconfig generate_gitconfig() { local access="$HOME/access.yml" local template="$HOME/gitconfig.template" local userdir="$HOME/secrets/$PERSON" local name email name=$(yq -r ".\"$PERSON\".name" "$access" 2>/dev/null || echo) email=$(yq -r ".\"$PERSON\".email" "$access" 2>/dev/null || echo) if [[ -z "$name" || -z "$email" ]]; then log ERROR "Missing name/email for '$PERSON' in $access" exit 1 fi mkdir -p "$userdir" GIT_NAME="$name" GIT_EMAIL="$email" \ envsubst <"$template" >"$userdir/gitconfig" log INFO ".gitconfig created → $userdir/gitconfig" } # ───────────────────────────────────────────── # Start container if absent or stopped start_container_if_needed() { if ! podman container exists "$WORKSPACE"; then log INFO "Creating container '$WORKSPACE'" generate_gitconfig podman run -dit \ --name "$WORKSPACE" \ --userns=keep-id \ --user "$DEV_USER" \ --hostname "$WORKSPACE" \ --label auto-cleanup=true \ -v "/home/infilytics/data/$WORKSPACE:/app:Z" \ -v "/home/infilytics/secrets/$WORKSPACE/gitconfig:/home/$DEV_USER/.gitconfig:ro,Z" \ -v "/home/infilytics/secrets/$WORKSPACE/id_ed25519:/home/$DEV_USER/.ssh/id_ed25519:ro,Z" \ -v "/home/infilytics/secrets/$WORKSPACE/id_ed25519.pub:/home/$DEV_USER/.ssh/id_ed25519.pub:ro,Z" \ --entrypoint "/home/$DEV_USER/start.sh" \ "$IMAGE" "$TMUX_SESSION" elif ! podman inspect -f '{{.State.Running}}' "$WORKSPACE" | grep -q true; then log INFO "Starting existing container '$WORKSPACE'" podman start "$WORKSPACE" >/dev/null fi sleep 1 } # ───────────────────────────────────────────── # Detach logic: stop container when devuser has left check_devuser_attached() { local clients clients=$(podman exec "$WORKSPACE" tmux list-clients -t "$TMUX_SESSION" -F "#{client_user}" 2>/dev/null) if grep -q "^${DEV_USER}\$" <<<"$clients"; then log INFO "devuser still attached—keeping container running" else log INFO "devuser detached—stopping container" podman stop "$WORKSPACE" >/dev/null fi } # ───────────────────────────────────────────── # Determine access mode (rw|ro) or exit get_access_mode() { local yaml="access.yml" user="$PERSON" ws="$WORKSPACE" [[ ! "$ws" =~ ^[A-Za-z0-9._-]+$ ]] && { log ERROR "Invalid workspace name" exit 1 } if [[ "$user" == "$ws" ]]; then echo rw elif yq -e '.["'"$user"'"].rw[]?' "$yaml" | grep -qx "$ws"; then echo rw elif yq -e '.["'"$user"'"].ro[]?' "$yaml" | grep -qx "$ws"; then echo ro else log ERROR "$user has no access to $ws" exit 1 fi } MODE="$(get_access_mode)" # ───────────────────────────────────────────── # Main dispatch case "$MODE" in rw) start_container_if_needed # Ensure tmux session exists if ! podman exec -it --user "$DEV_USER" "$WORKSPACE" tmux has-session -t "$TMUX_SESSION" 2>/dev/null; then podman exec -it --user "$DEV_USER" "$WORKSPACE" \ tmux new-session -d -s "$TMUX_SESSION" fi log INFO "$PERSON attaching to workspace '$WORKSPACE'" podman exec -it -e TERM="$TERM" --user "$DEV_USER" "$WORKSPACE" \ tmux attach -t "$TMUX_SESSION" log INFO "$PERSON detached from '$WORKSPACE'" check_devuser_attached ;; ro) if podman inspect -f '{{.State.Running}}' "$WORKSPACE" 2>/dev/null | grep -q true; then log INFO "$PERSON viewing workspace '$WORKSPACE'" podman exec -it -e TERM="$TERM" --user "$DEV_USER" "$WORKSPACE" \ tmux attach -r -t "$TMUX_SESSION" log INFO "$PERSON stopped viewing '$WORKSPACE'" else log ERROR "Workspace '$WORKSPACE' is not running" exit 1 fi ;; *) log ERROR "Unknown access mode: '$MODE'" exit 1 ;; esac