From 797da9cf5b64209d9328c1be80c0cda183ff4686 Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 29 May 2026 11:00:42 +0100 Subject: [PATCH] add caddy script --- .bashrc | 2 + scripts/caddy.sh | 363 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 365 insertions(+) create mode 100755 scripts/caddy.sh diff --git a/.bashrc b/.bashrc index 3d03bb1..33768e7 100644 --- a/.bashrc +++ b/.bashrc @@ -56,6 +56,7 @@ function helpme() { echo -e "\e[32m---------- My Scripts -----------\e[0m" echo -e "myapps : Install Fedora apps" echo -e "phoneapps : Install mobile apps" + echo -e "caddy : Manage caddy configs" echo -e "compress : Compress videos with handbrake" echo -e "star-update : Update terminal from gitea" echo -e "star-edit : Edit starship config" @@ -98,6 +99,7 @@ alias compress="~/.bash/scripts/compress.sh" alias wii="~/.bash/scripts/wii.sh" alias nds-patcher="~/.bash/scripts/nds.sh" alias megadrive="~/.bash/scripts/megadrive.sh" +alias caddy="~/.bash/scripts/caddy.sh" export GSK_RENDERER=cairo eval "$(starship init bash)" diff --git a/scripts/caddy.sh b/scripts/caddy.sh new file mode 100755 index 0000000..790acc5 --- /dev/null +++ b/scripts/caddy.sh @@ -0,0 +1,363 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────── +# Caddy Manager – gum-powered TUI +# SSH target : root@192.168.0.17 +# Sites dir : ~/sites +# ───────────────────────────────────────────────────────────── + +set -uo pipefail + +SSH_TARGET="root@192.168.0.17" +SITES_DIR="~/sites" +RELOAD_CMD="docker exec -w /etc/caddy caddy caddy reload" + +# ── Colour palette ──────────────────────────────────────────── +ORANGE="#FF6B00" +ORANGE_DIM="#CC5500" +GREY_DARK="#2A2A2A" +GREY_MID="#4A4A4A" +GREY_LIGHT="#AAAAAA" +WHITE="#F5F5F5" + +# ── Dependency check ───────────────────────────────────────── +if ! command -v gum &>/dev/null; then + echo "ERROR: 'gum' is not installed." + exit 1 +fi + +if ! command -v ssh &>/dev/null; then + echo "ERROR: 'ssh' is not installed." + exit 1 +fi + +# ── Helpers ─────────────────────────────────────────────────── + +draw_title() { + clear + gum style \ + --foreground "$ORANGE" \ + --border-foreground "$ORANGE" \ + --border double \ + --align center \ + --width 50 \ + --margin "1 2" \ + --padding "1 4" \ + --bold \ + "⚙ CADDY MANAGER ⚙" \ + "" \ + "$(gum style --foreground "$GREY_LIGHT" --faint "$(date '+%a %d %b %Y %H:%M')")" +} + +ssh_run() { + # Run a command on the remote host + ssh -o ConnectTimeout=10 -o BatchMode=yes "$SSH_TARGET" "$@" +} + +reload_caddy() { + gum spin \ + --spinner dot \ + --title "Reloading Caddy..." \ + --title.foreground "$ORANGE" \ + -- ssh -o ConnectTimeout=10 -o BatchMode=yes "$SSH_TARGET" "$RELOAD_CMD" +} + +press_enter() { + echo "" + gum style --foreground "$GREY_LIGHT" "Press Enter to return to the menu..." + read -r +} + +# ── Option 1 – Add a new config ─────────────────────────────── + +add_config() { + draw_title + gum style \ + --foreground "$ORANGE" \ + --bold \ + --margin "0 2" \ + " ADD NEW SITE CONFIG" + echo "" + + # Service name (used as filename) + SERVICE=$(gum input \ + --prompt " Service name (filename): " \ + --prompt.foreground "$ORANGE" \ + --placeholder "e.g. nextcloud" \ + --width 40 \ + --cursor.foreground "$ORANGE") + + [[ -z "$SERVICE" ]] && { gum style --foreground "red" " Aborted – no service name given."; press_enter; return; } + + # Domain – pre-filled with common suffix; type the subdomain then arrow to end + DOMAIN=$(gum input \ + --prompt " Domain: " \ + --prompt.foreground "$ORANGE" \ + --value ".marlow.quest" \ + --width 40 \ + --cursor.foreground "$ORANGE") + + [[ -z "$DOMAIN" ]] && { gum style --foreground "red" " Aborted – no domain given."; press_enter; return; } + + # Upstream IP:port – pre-filled with common subnet prefix + UPSTREAM=$(gum input \ + --prompt " Upstream IP:port: " \ + --prompt.foreground "$ORANGE" \ + --value "192.168.0." \ + --width 40 \ + --cursor.foreground "$ORANGE") + + [[ -z "$UPSTREAM" ]] && { gum style --foreground "red" " Aborted – no upstream given."; press_enter; return; } + + # TinyAuth? + echo "" + gum style --foreground "$GREY_LIGHT" --margin "0 2" " Include TinyAuth middleware?" + USE_TINYAUTH=$(gum choose \ + --cursor "▶ " \ + --cursor.foreground "$ORANGE" \ + --selected.foreground "$ORANGE" \ + --header " Select an option:" \ + --header.foreground "$GREY_LIGHT" \ + "Yes" "No") + + # Build the Caddy config block + if [[ "$USE_TINYAUTH" == "Yes" ]]; then + CONFIG_BLOCK="${DOMAIN} { + import tinyauth + } + + reverse_proxy ${UPSTREAM} +}" + else + CONFIG_BLOCK="${DOMAIN} { + reverse_proxy ${UPSTREAM} +}" + fi + + # Preview + echo "" + gum style \ + --foreground "$ORANGE" \ + --border normal \ + --border-foreground "$GREY_MID" \ + --padding "1 2" \ + --margin "0 2" \ + "$CONFIG_BLOCK" + echo "" + + gum confirm \ + --prompt.foreground "$ORANGE" \ + --selected.background "$ORANGE" \ + --selected.foreground "$GREY_DARK" \ + " Write ${SERVICE}.conf to ${SITES_DIR} and reload Caddy?" \ + || { gum style --foreground "$GREY_LIGHT" " Cancelled."; press_enter; return; } + + # Write file via SSH heredoc + ssh_run "cat > ${SITES_DIR}/${SERVICE}.conf" </dev/null || true) + + # Remove blank entries + CLEAN_FILES=() + for f in "${FILES[@]+"${FILES[@]}"}"; do + [[ -n "$f" ]] && CLEAN_FILES+=("$f") + done + + if [[ ${#CLEAN_FILES[@]} -eq 0 ]]; then + gum style --foreground "$GREY_LIGHT" --margin "0 2" \ + " No config files found in ${SITES_DIR}/" + press_enter + return + fi + + # Parse each file individually — build display arrays and a lookup map + T_NAME=() + T_DOMAIN=() + T_UPSTREAM=() + T_TINYAUTH=() + + for FILE in "${CLEAN_FILES[@]}"; do + FILE_CONTENT=$(ssh_run "cat ${SITES_DIR}/${FILE}" 2>/dev/null || true) + + DOMAIN=$(echo "$FILE_CONTENT" \ + | grep -v '^\s*#' | grep -v '^\s*$' | grep -v '^\s' \ + | grep '\.' | head -1 | awk '{print $1}' | tr -d '{}' || true) + [[ -z "$DOMAIN" ]] && DOMAIN="—" + + UPSTREAM=$(echo "$FILE_CONTENT" \ + | grep -i 'reverse_proxy' | awk '{print $2}' | head -1 || true) + [[ -z "$UPSTREAM" ]] && UPSTREAM="—" + + if echo "$FILE_CONTENT" | grep -qi 'tinyauth' 2>/dev/null; then + TA="yes" + else + TA="no" + fi + + T_NAME+=("$FILE") + T_DOMAIN+=("$DOMAIN") + T_UPSTREAM+=("$UPSTREAM") + T_TINYAUTH+=("$TA") + done + + # Write CSV to temp file (gum table needs a real fd, not a pipe) + TMPCSV=$(mktemp /tmp/caddy-XXXXXX.csv) + for i in "${!T_NAME[@]}"; do + printf '%s,%s,%s,%s\n' \ + "${T_NAME[$i]}" "${T_DOMAIN[$i]}" "${T_UPSTREAM[$i]}" "${T_TINYAUTH[$i]}" \ + >> "$TMPCSV" + done + + gum style --foreground "$GREY_LIGHT" --margin "0 2" \ + " ↑↓ navigate · Enter to view full config · Esc to go back" + echo "" + + SELECTED=$(gum table \ + --columns "FILE,DOMAIN,UPSTREAM,TINYAUTH" \ + --widths "26,30,22,9" \ + --height 18 \ + --border.foreground "$ORANGE" \ + --header.foreground "$ORANGE" \ + --selected.foreground "$GREY_DARK" \ + --selected.background "$ORANGE" \ + < "$TMPCSV" || true) + + rm -f "$TMPCSV" + + [[ -z "$SELECTED" ]] && return + + # Extract filename from first CSV column + CHOICE=$(echo "$SELECTED" | cut -d',' -f1 | xargs) + + # ── Show full file contents ──────────────────────────── + clear + gum style \ + --foreground "$ORANGE" \ + --bold \ + --margin "1 2 0 2" \ + " ▸ ${CHOICE}" + echo "" + + CONTENT=$(ssh_run "cat ${SITES_DIR}/${CHOICE}" 2>/dev/null || true) + [[ -z "$CONTENT" ]] && CONTENT="(unable to read file)" + + gum style \ + --foreground "$WHITE" \ + --border normal \ + --border-foreground "$GREY_MID" \ + --padding "1 2" \ + --margin "0 2" \ + "$CONTENT" + + echo "" + press_enter + done +} + +# ── Option 3 – Delete a config ──────────────────────────────── + +delete_config() { + draw_title + gum style \ + --foreground "$ORANGE" \ + --bold \ + --margin "0 2" \ + " DELETE SITE CONFIG" + echo "" + + # Fetch file list + mapfile -t FILES < <(ssh_run "ls ${SITES_DIR}/" 2>/dev/null | sort) + + if [[ ${#FILES[@]} -eq 0 ]]; then + gum style --foreground "$GREY_LIGHT" --margin "0 2" " No config files found in ${SITES_DIR}/" + press_enter + return + fi + + CHOICE=$(printf '%s\n' "${FILES[@]}" "── Cancel ──" | \ + gum choose \ + --cursor "▶ " \ + --cursor.foreground "$ORANGE" \ + --selected.foreground "red" \ + --header " Select a config to delete:" \ + --header.foreground "$GREY_LIGHT" \ + --height 15) + + [[ "$CHOICE" == "── Cancel ──" || -z "$CHOICE" ]] && { gum style --foreground "$GREY_LIGHT" " Cancelled."; press_enter; return; } + + echo "" + + # Show contents before confirming + CONTENT=$(ssh_run "cat ${SITES_DIR}/${CHOICE}" 2>/dev/null || echo "(unable to read file)") + gum style \ + --foreground "$GREY_LIGHT" \ + --border normal \ + --border-foreground "red" \ + --padding "1 2" \ + --margin "0 2" \ + "$CONTENT" + echo "" + + gum confirm \ + --prompt.foreground "$ORANGE" \ + --selected.background "$ORANGE" \ + --selected.foreground "$GREY_DARK" \ + " Permanently delete ${CHOICE} and reload Caddy?" \ + || { gum style --foreground "$GREY_LIGHT" " Cancelled."; press_enter; return; } + + ssh_run "rm -f ${SITES_DIR}/${CHOICE}" + + echo "" + gum style --foreground "green" " ✔ Deleted ${SITES_DIR}/${CHOICE}" + + reload_caddy && gum style --foreground "green" " ✔ Caddy reloaded successfully." \ + || gum style --foreground "red" " ✗ Caddy reload failed – check logs." + press_enter +} + +# ── Main menu loop ──────────────────────────────────────────── + +while true; do + draw_title + + MENU_CHOICE=$(gum choose \ + --cursor "▶ " \ + --cursor.foreground "$ORANGE" \ + --selected.foreground "$ORANGE" \ + --header " What would you like to do?" \ + --header.foreground "$GREY_LIGHT" \ + --height 6 \ + " 1 Add a new site config" \ + " 2 List / view site configs" \ + " 3 Delete a site config" \ + " ✕ Exit") + + case "$MENU_CHOICE" in + *"1"*) add_config ;; + *"2"*) list_configs ;; + *"3"*) delete_config ;; + *"✕"*|"") clear; exit 0 ;; + esac +done