Compare commits

..

2 Commits

Author SHA1 Message Date
ash 72cba64e23 Merge branch 'main' of https://git.marlow.quest/ash/Starship 2026-05-29 11:04:02 +01:00
ash 797da9cf5b add caddy script 2026-05-29 11:00:42 +01:00
2 changed files with 365 additions and 0 deletions
+2
View File
@@ -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)"
+363
View File
@@ -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" <<EOF
${CONFIG_BLOCK}
EOF
echo ""
gum style --foreground "green" " ✔ Config written to ${SITES_DIR}/${SERVICE}.conf"
reload_caddy && gum style --foreground "green" " ✔ Caddy reloaded successfully." \
|| gum style --foreground "red" " ✗ Caddy reload failed check logs."
press_enter
}
# ── Option 2 List / view configs ───────────────────────────
list_configs() {
while true; do
draw_title
gum style \
--foreground "$ORANGE" \
--bold \
--margin "0 2" \
" SITE CONFIGS"
echo ""
# Fetch all file names
mapfile -t FILES < <(ssh_run "ls ${SITES_DIR}/" 2>/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