init: a repo for various pipelines of workspaces

This commit is contained in:
Pallav Vasa 2025-05-17 15:51:29 +00:00
commit 6ed0d234d4
7 changed files with 567 additions and 0 deletions

9
.bin/gitops Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
ssh -F /dev/null \
-o HostName=10.88.0.1 \
-o Port=22 \
-o User=infilytics \
-o IdentityFile=~/.ssh/id_ed25519 \
-o ProxyCommand=none \
gitops -- "$@"

30
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,30 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "GitOps(Update): ssh_router.sh",
"type": "shell",
"command": ".bin/gitops update ssh_router",
"group": "build",
"problemMatcher": [],
"detail": "Copy ssh_router.sh to $HOME/.local/bin/"
},
{
"label": "GitOps(Update): gitops_router.sh",
"type": "shell",
"command": ".bin/gitops update gitops_router",
"group": "build",
"problemMatcher": [],
"detail": "Copy gitops_router.sh to $HOME/.local/bin"
},
{
"label": "GitOps(Update): validate_command_access.sh",
"type": "shell",
"command": ".bin/gitops update validate_command",
"group": "build",
"problemMatcher": [],
"detail": "Copy validate_command_access.sh to $HOME/.local/bin"
}
],
"inputs": []
}

13
access.yml Normal file
View File

@ -0,0 +1,13 @@
pallav:
fixedArgsCommands:
build:
- base
- workspace
- all
clean:
status:
multiArgsCommands:
remove:
- palak
- param
- darshan

178
gitops_router.sh Normal file
View File

@ -0,0 +1,178 @@
#!/usr/bin/env bash
set -euo pipefail
PERSON="${1:?Missing PERSON argument}"
HOST="alps:3222"
PROTOCOL="http"
REPO=("babbarc/workspaces" "babbarc/workspaces-pipelines" "babbarc/workspaces-sec-alps-infilytics")
BRANCH="master"
LOG_FILE="/tmp/.gitops-router-${PERSON}.log"
# ─────────────────────────────────────────────
# ANSI color codes
readonly C_RESET='\033[0m'
readonly C_INFO='\033[1;34m' # bold blue
readonly C_WARN='\033[1;33m' # bold yellow
readonly C_ERROR='\033[1;31m' # bold red
# ─────────────────────────────────────────────
# log <level> <message...> with emojis
log() {
local lvl="${1^^}"
shift
local icon color
case "$lvl" 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] %s%b\n' \
"$color" "$icon" "$ts" "$lvl" "$*" "$C_RESET" |
tee -a "$LOG_FILE"
}
# ─────────────────────────────────────────────
# Build the raw URL for fetching files
geturl() {
local repo="$1" type="$2" file="$3"
printf '%s://%s/%s/%s/branch/%s/%s\n' \
"$PROTOCOL" "$HOST" "${REPO[$repo]}" "$type" "$BRANCH" "$file"
}
# ─────────────────────────────────────────────
# Run a local script
run() {
local script="$1"
"$HOME/.local/bin/$script"
}
# ─────────────────────────────────────────────
# Download & install an artifact
# update <repo> <file> <target-dir> <mode> [<type>]
update() {
local repo="$1" file="$2" dir="$3" mode="$4" type="${5:-raw}"
local url out
out="$HOME/$dir/$(basename "$file")"
url="$(geturl "$repo" "$type" "$file")"
[[ -f "$out" ]] && chmod 700 "$out"
if curl -fsSL "$url" -o "$out"; then
log INFO "Downloaded $url$out"
chmod "$mode" "$out"
else
log ERROR "Failed to download $url"
return 1
fi
}
# ─────────────────────────────────────────────
# Clean up dangling podman images
clean_images() {
local dangling
dangling="$(podman images -f dangling=true -q)"
if [[ -z "$dangling" ]]; then
log INFO "No dangling images to remove."
else
log WARN "Removing dangling images..."
echo "$dangling" | xargs podman rmi
log INFO "Dangling images removed."
fi
}
# ─────────────────────────────────────────────
# Remove host podman containers
remove_containers() {
local tokens=("$@")
local flags=() patterns=() containers=()
local valid='^[A-Za-z0-9._-]+$'
# allow unmatched globs to disappear
shopt -s nullglob
# separate flags (-f, etc.) from name patterns
for tok in "${tokens[@]}"; do
if [[ "$tok" == -* ]]; then
flags+=("$tok")
else
patterns+=("$tok")
fi
done
# validate & expand each pattern
for pat in "${patterns[@]}"; do
if [[ ! "$pat" =~ $valid ]]; then
log ERROR "Invalid container name: '$pat'"
shopt -u nullglob
return 1
fi
containers+=("$pat")
done
shopt -u nullglob
if ((${#containers[@]} == 0)); then
log WARN "No containers matched: ${patterns[*]}"
return 0
fi
# pass flags *then* containers to podman rm
podman rm "${flags[@]}" "${containers[@]}"
}
# ─────────────────────────────────────────────
# validate_command <workspace> <cmd> [<tok1> <tok2> …]
source "$HOME"/.local/bin/validate_command_access.sh
# ─────────────────────────────────────────────
# Entry & command parsing
if [[ -z "${SSH_ORIGINAL_COMMAND:-}" ]]; then
log ERROR "No SSH_ORIGINAL_COMMAND provided."
exit 1
fi
log INFO "SSH_ORIGINAL_COMMAND: $SSH_ORIGINAL_COMMAND"
read -ra parts <<<"$SSH_ORIGINAL_COMMAND"
cmd="${parts[0]}"
args=("${parts[@]:1}")
validate_command "$PERSON" "$cmd" "${args[@]}"
# ─────────────────────────────────────────────
# Dispatch
case "$cmd" in
build)
case "${args[0]}" in
base) podman build --target base -t analytics-backend-base . ;;
workspace) podman build --target base -t analytics-backend-base . ;;
all) podman build -t analytics-backend-workspace . ;;
*) log ERROR "build: invalid arg '${args[0]}'" ;;
esac
;;
update)
case "${args[0]}" in
containerfile) update 0 Containerfile . 500 ;;
access) update 2 access.yml . 400 ;;
authorized_keys) update 2 access.yml . 400 ;;
ssh_router) update 1 ssh_router.sh .local/bin 500 ;;
gitops_router) update 1 gitops_router.sh .local/bin 500 ;;
validate_command) update 1 validate_command_access.sh .local/bin 500 ;;
home_tar) update 0 home.tar.gz . 500 media ;;
gitconfig) update 0 gitconfig.template . 500 ;;
*) log ERROR "update: invalid arg '${args[0]}'" ;;
esac
;;
clean) clean_images ;;
status) podman images ;;
remove) remove_containers "${args[@]}" ;;
*)
log ERROR "Unknown command: '$cmd'"
exit 127
;;
esac

207
ssh_router.sh Normal file
View File

@ -0,0 +1,207 @@
#!/usr/bin/env bash
set -euo pipefail
PERSON="${1:?Usage: $0 <person>}"
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" >>"$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/data/$WORKSPACE:/app:Z" \
-v "$HOME/secrets/$WORKSPACE/gitconfig:/home/$DEV_USER/.gitconfig:ro,Z" \
-v "$HOME/secrets/$WORKSPACE/id_ed25519:/home/$DEV_USER/.ssh/id_ed25519:ro,Z" \
-v "$HOME/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="$HOME/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

View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
set -e
cat >access.yml <<EOF
pallav:
fixedArgsCommands:
build:
- base
- workspace
- all
clean:
status:
multiArgsCommands:
remove:
- palak
- param
- darshan
EOF
source ./validate_command_access.sh
testcase() {
local desc="$1"
shift
if validate_command pallav "$@"; then
echo "PASS: $desc"
else
echo "FAIL: $desc"
fi
}
testcase "build base (valid)" build base
testcase "build all (valid)" build all
testcase "build base workspace (invalid)" build base workspace || true
testcase "build (no arg, invalid)" build || true
testcase "clean (zero-arg, valid)" clean
testcase "clean with arg (invalid)" clean foo || true
testcase "remove palak (valid)" remove palak
testcase "remove param palak (valid, any order)" remove param palak
testcase "remove palak param darshan (valid, any order)" remove palak param darshan
testcase "remove (no arg, invalid)" remove || true
testcase "remove foo (invalid)" remove foo || true
testcase "remove palak palak (duplicate, invalid)" remove palak palak || true
testcase "status (zero-arg, valid)" status
testcase "status foo (invalid)" status foo || true

View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
validate_command() {
local PERSON="$1"
local cmd="$2"
shift 2
local tokens=("$@")
local yaml="access.yml"
# Check if fixedArgsCommands.<cmd> exists
local is_fixed
is_fixed="$(yq e ".\"$PERSON\".fixedArgsCommands | has(\"$cmd\")" "$yaml")"
# Check if multiArgsCommands.<cmd> exists
local is_multi
is_multi="$(yq e ".\"$PERSON\".multiArgsCommands | has(\"$cmd\")" "$yaml")"
if [[ "$is_fixed" != "true" && "$is_multi" != "true" ]]; then
echo "ERROR: Command '$cmd' not allowed for $PERSON" >&2
return 1
fi
# Exclude flags from positional args
local args=()
for tok in "${tokens[@]}"; do
[[ "$tok" == -* ]] && continue
args+=("$tok")
done
if [[ "$is_fixed" == "true" ]]; then
mapfile -t allowed < <(yq e ".\"$PERSON\".fixedArgsCommands.\"$cmd\"[]" "$yaml" 2>/dev/null)
local n_allowed="${#allowed[@]}"
if [[ $n_allowed -eq 0 ]]; then
# zero-arg command
if [[ ${#args[@]} -ne 0 ]]; then
echo "ERROR: Command '$cmd' takes no arguments" >&2
return 1
fi
else
# depth is 1: only one of the allowed choices must be present
if [[ ${#args[@]} -ne 1 ]]; then
echo "ERROR: Command '$cmd' requires exactly 1 argument: (${allowed[*]})" >&2
return 1
fi
local found=0
for want in "${allowed[@]}"; do
[[ "${args[0]}" == "$want" ]] && found=1 && break
done
if [[ $found -eq 0 ]]; then
echo "ERROR: Invalid argument '${args[0]}' for '$cmd'; allowed: (${allowed[*]})" >&2
return 1
fi
fi
return 0
fi
if [[ "$is_multi" == "true" ]]; then
mapfile -t allowed < <(yq e ".\"$PERSON\".multiArgsCommands.\"$cmd\"[]" "$yaml" 2>/dev/null)
local n_allowed="${#allowed[@]}"
if [[ ${#args[@]} -lt 1 || ${#args[@]} -gt $n_allowed ]]; then
echo "ERROR: Command '$cmd' requires 1 to $n_allowed arguments: (${allowed[*]})" >&2
return 1
fi
# Order doesn't matter, but all must be unique and from allowed.
# Build a set of allowed args.
declare -A allowed_set=()
for want in "${allowed[@]}"; do allowed_set["$want"]=1; done
declare -A seen=()
for a in "${args[@]}"; do
[[ -z "${allowed_set[$a]}" ]] && {
echo "ERROR: Invalid argument '$a' for '$cmd'; allowed: (${allowed[*]})" >&2
return 1
}
[[ -n "${seen[$a]}" ]] && {
echo "ERROR: Duplicate argument '$a' for '$cmd'" >&2
return 1
}
seen["$a"]=1
done
return 0
fi
}