Merge pull request #292 from outis1one/claude/ionos-script-integration-x32ofw
Claude/ionos script integration x32ofw
This commit is contained in:
+197
@@ -334,6 +334,160 @@ ufw_allow_from_caddy_net() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ── Remove a service ──────────────────────────────────────────────────────────
|
||||||
|
# Removes a specific site block from a Caddyfile, keyed on the block whose
|
||||||
|
# body reverse_proxy's to the given container name. Bounded by tracking
|
||||||
|
# actual brace depth (handles nested log{}/header{}/forward_auth{} blocks
|
||||||
|
# correctly), not a "delete to next blank line" scan — see this repo's own
|
||||||
|
# history for why an unbounded range delete on a live Caddy/Samba config is
|
||||||
|
# exactly the kind of thing that silently destroys unrelated content.
|
||||||
|
_remove_caddy_site_block() {
|
||||||
|
local caddy_file="$1" container="$2"
|
||||||
|
awk -v container="$container" '
|
||||||
|
BEGIN { depth = 0; buf = ""; skip = 0; pending_comment = "" }
|
||||||
|
{
|
||||||
|
line = $0
|
||||||
|
opens = gsub(/\{/, "{", line)
|
||||||
|
closes = gsub(/\}/, "}", line)
|
||||||
|
|
||||||
|
if (depth == 0 && opens == 0) {
|
||||||
|
if ($0 ~ /^#/) {
|
||||||
|
if (pending_comment != "") print pending_comment
|
||||||
|
pending_comment = $0
|
||||||
|
next
|
||||||
|
} else {
|
||||||
|
if (pending_comment != "") { print pending_comment; pending_comment = "" }
|
||||||
|
print $0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (depth == 0 && opens > 0) {
|
||||||
|
buf = $0 "\n"
|
||||||
|
depth += opens - closes
|
||||||
|
if (index($0, "reverse_proxy " container ":") > 0) skip = 1
|
||||||
|
next
|
||||||
|
}
|
||||||
|
if (depth > 0) {
|
||||||
|
buf = buf $0 "\n"
|
||||||
|
if (index($0, "reverse_proxy " container ":") > 0) skip = 1
|
||||||
|
depth += opens - closes
|
||||||
|
if (depth <= 0) {
|
||||||
|
depth = 0
|
||||||
|
if (!skip) {
|
||||||
|
if (pending_comment != "") print pending_comment
|
||||||
|
printf "%s", buf
|
||||||
|
}
|
||||||
|
pending_comment = ""
|
||||||
|
buf = ""
|
||||||
|
skip = 0
|
||||||
|
next
|
||||||
|
}
|
||||||
|
next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
END { if (pending_comment != "") print pending_comment }
|
||||||
|
' "$caddy_file"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Generic per-service removal: stops/removes its containers, its Caddy site
|
||||||
|
# block (if any), any UFW rule tagged with its name, and optionally its
|
||||||
|
# ~/docker/<name> directory. Scoped to the common case (a Docker service
|
||||||
|
# living at $DOCKER_DIR/<name> with a standard configure_caddy_for_service
|
||||||
|
# site block) — a service with a hand-built Caddy block or non-standard
|
||||||
|
# layout may need manual cleanup for the parts this can't find.
|
||||||
|
remove_service() {
|
||||||
|
local name="$1"
|
||||||
|
local dir="$DOCKER_DIR/$name"
|
||||||
|
|
||||||
|
if [ "$DRY_RUN" = true ]; then
|
||||||
|
echo "[DRY-RUN] Would stop/remove $name's containers, Caddy site block, and any tagged UFW rule"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -d "$dir" ]; then
|
||||||
|
log_error "No $dir found — nothing to remove. (Non-Docker services, e.g. base/ssh-key-import, aren't handled by this — remove those manually.)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " Remove $name"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
echo " This will, as applicable:"
|
||||||
|
[ -f "$dir/docker-compose.yml" ] && echo " - Stop and remove its Docker container(s)"
|
||||||
|
echo " - Remove its Caddy site block, if any (Caddyfile backed up first)"
|
||||||
|
echo " - Remove any UFW rule tagged with '$name'"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
local CONFIRM=""
|
||||||
|
prompt_yn " Continue? (y/n):" "n" CONFIRM
|
||||||
|
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
|
||||||
|
log_info "Cancelled."
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Docker teardown ────────────────────────────────────────────────────
|
||||||
|
local container=""
|
||||||
|
if [ -f "$dir/docker-compose.yml" ]; then
|
||||||
|
container="$(grep -m1 '^\s*container_name:' "$dir/docker-compose.yml" 2>/dev/null | awk '{print $2}')"
|
||||||
|
local WIPE_DATA=""
|
||||||
|
prompt_yn " Also delete its data volumes (database, uploaded files, etc. — irreversible)? (y/n):" "n" WIPE_DATA
|
||||||
|
if [[ "$WIPE_DATA" =~ ^[Yy]$ ]]; then
|
||||||
|
( cd "$dir" && docker compose down -v ) \
|
||||||
|
&& log_success "Containers and volumes removed" \
|
||||||
|
|| log_warning "docker compose down -v failed — check manually"
|
||||||
|
else
|
||||||
|
( cd "$dir" && docker compose down ) \
|
||||||
|
&& log_success "Containers stopped and removed (data left on disk)" \
|
||||||
|
|| log_warning "docker compose down failed — check manually"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Caddy site block ────────────────────────────────────────────────────
|
||||||
|
local caddy_file="$DOCKER_DIR/caddy/Caddyfile"
|
||||||
|
if [ -n "$container" ] && [ -f "$caddy_file" ] && grep -q "reverse_proxy ${container}:" "$caddy_file"; then
|
||||||
|
local bk="$caddy_file.backup.$(date +%Y%m%d-%H%M%S)"
|
||||||
|
cp "$caddy_file" "$bk"
|
||||||
|
_remove_caddy_site_block "$caddy_file" "$container" > "$caddy_file.tmp" \
|
||||||
|
&& mv "$caddy_file.tmp" "$caddy_file"
|
||||||
|
log_success "Removed $name's Caddy site block (backup: $(basename "$bk"))"
|
||||||
|
if docker ps --format '{{.Names}}' 2>/dev/null | grep -qx caddy; then
|
||||||
|
docker exec caddy caddy fmt --overwrite /etc/caddy/Caddyfile 2>/dev/null || true
|
||||||
|
docker exec caddy caddy reload --config /etc/caddy/Caddyfile 2>/dev/null \
|
||||||
|
|| docker restart caddy &>/dev/null \
|
||||||
|
|| log_warning "Reload/restart Caddy manually to apply this."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── UFW rules ───────────────────────────────────────────────────────────
|
||||||
|
if command -v ufw &>/dev/null; then
|
||||||
|
local rule_nums
|
||||||
|
# [[:space:]]* after the opening bracket — ufw pads single-digit
|
||||||
|
# rule numbers with a leading space to align with double-digit
|
||||||
|
# ones ("[ 3]" vs "[10]"); without it, every single-digit rule
|
||||||
|
# silently fails to match and never gets deleted.
|
||||||
|
rule_nums="$(ufw status numbered 2>/dev/null | grep -i "# .*\b${name}\b" | grep -oE '^\[[[:space:]]*[0-9]+\]' | tr -d '[] ' | sort -rn)"
|
||||||
|
if [ -n "$rule_nums" ]; then
|
||||||
|
local n
|
||||||
|
for n in $rule_nums; do
|
||||||
|
ufw --force delete "$n" >/dev/null 2>&1
|
||||||
|
done
|
||||||
|
log_success "Removed UFW rule(s) tagged for $name"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# ── Directory itself ────────────────────────────────────────────────────
|
||||||
|
local DELETE_DIR=""
|
||||||
|
prompt_yn " Also delete $dir itself (its README, configs, and any data left on disk)? (y/n):" "n" DELETE_DIR
|
||||||
|
if [[ "$DELETE_DIR" =~ ^[Yy]$ ]]; then
|
||||||
|
rm -rf "$dir"
|
||||||
|
log_success "Removed $dir"
|
||||||
|
else
|
||||||
|
log_info "Left $dir in place."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ── SSH client config (~/.ssh/config) Host aliases ────────────────────────────
|
# ── SSH client config (~/.ssh/config) Host aliases ────────────────────────────
|
||||||
# Lets "ssh <alias>" connect directly to user@host without typing it out each
|
# Lets "ssh <alias>" connect directly to user@host without typing it out each
|
||||||
# time — handy for VPN/NetBird peers with unmemorable IPs. Operates on the
|
# time — handy for VPN/NetBird peers with unmemorable IPs. Operates on the
|
||||||
@@ -434,6 +588,49 @@ ensure_docker_dir_ownership() {
|
|||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Waits briefly after a container starts, then reports whether it's
|
||||||
|
# actually running or stuck restarting/crash-looping — printing recent
|
||||||
|
# logs on failure instead of leaving a silent "started" message that
|
||||||
|
# doesn't reflect whether it's actually working. Confirmed live:
|
||||||
|
# several services' own "Started"-looking `docker compose up -d`
|
||||||
|
# success message meant nothing — the container was already
|
||||||
|
# crash-looping by the time that message printed, with no indication
|
||||||
|
# anything was wrong until someone separately ran `docker ps -a` much
|
||||||
|
# later and had to go dig through logs by hand.
|
||||||
|
#
|
||||||
|
# Usage: check_container_health CONTAINER_NAME [WAIT_SECONDS]
|
||||||
|
# Returns 0 if the container is up and hasn't restarted, 1 otherwise.
|
||||||
|
check_container_health() {
|
||||||
|
local container="$1" wait_seconds="${2:-8}"
|
||||||
|
[ "$DRY_RUN" = true ] && return 0
|
||||||
|
|
||||||
|
sleep "$wait_seconds"
|
||||||
|
|
||||||
|
local status
|
||||||
|
status="$(docker inspect -f '{{.State.Status}}' "$container" 2>/dev/null)"
|
||||||
|
if [ -z "$status" ]; then
|
||||||
|
log_warning "Container '$container' doesn't exist — something failed before it could even be created."
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
local restart_count
|
||||||
|
restart_count="$(docker inspect -f '{{.RestartCount}}' "$container" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
if [ "$status" = "running" ] && [ "$restart_count" -eq 0 ]; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$status" = "running" ]; then
|
||||||
|
log_warning "Container '$container' is running now but already restarted $restart_count time(s) — check the logs below."
|
||||||
|
else
|
||||||
|
log_warning "Container '$container' is not running (status: $status) — recent logs:"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
docker logs "$container" --tail 20 2>&1 | sed 's/^/ /'
|
||||||
|
echo ""
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
# Generate a secure alphanumeric password (no special characters)
|
# Generate a secure alphanumeric password (no special characters)
|
||||||
generate_password() {
|
generate_password() {
|
||||||
local length="${1:-32}"
|
local length="${1:-32}"
|
||||||
|
|||||||
+28
-9
@@ -322,13 +322,22 @@ install_mattermost() {
|
|||||||
ensure_docker_dir_ownership "$DIR"
|
ensure_docker_dir_ownership "$DIR"
|
||||||
cd "$DIR" || return 1
|
cd "$DIR" || return 1
|
||||||
|
|
||||||
# Reuse existing secrets on update — Postgres's volume keeps the password
|
# Reuse existing secrets whenever the Postgres data volume already has
|
||||||
# from its first init, so overwriting .env with a fresh one locks
|
# real data in it — not just when MODE=update. Confirmed live: picking
|
||||||
# Mattermost out of its own database. Confirmed this was previously
|
# "fresh" after removing only the mattermost APP container (docker rm,
|
||||||
# unconditional (regenerated every single rerun, silently breaking the DB
|
# not the whole ~/docker/mattermost directory) regenerates
|
||||||
# connection) — fixed here as part of adding proper update detection.
|
# POSTGRES_PASSWORD in a new .env while db/ still holds the OLD
|
||||||
|
# password baked in from its first init (postgres:15-alpine's
|
||||||
|
# entrypoint skips re-initializing an existing data directory, so the
|
||||||
|
# old credential is still the one actually enforced) — "password
|
||||||
|
# authentication failed for user mattermost" on every start
|
||||||
|
# afterward. Whether the data volume already has real data in it is
|
||||||
|
# what actually determines whether the old password is still live,
|
||||||
|
# not which reinstall mode was chosen.
|
||||||
local DB_PASS="" MM_SECRET=""
|
local DB_PASS="" MM_SECRET=""
|
||||||
if [ "$MODE" = "update" ]; then
|
local _db_has_data=false
|
||||||
|
[ -d db ] && [ -n "$(ls -A db 2>/dev/null)" ] && _db_has_data=true
|
||||||
|
if [ "$MODE" = "update" ] || [ "$_db_has_data" = true ]; then
|
||||||
DB_PASS="$(grep '^POSTGRES_PASSWORD=' .env 2>/dev/null | cut -d= -f2-)"
|
DB_PASS="$(grep '^POSTGRES_PASSWORD=' .env 2>/dev/null | cut -d= -f2-)"
|
||||||
[ "$_HAD_EMBEDDED_COTURN" = true ] && MM_SECRET="$(grep '^COTURN_SECRET=' .env 2>/dev/null | cut -d= -f2-)"
|
[ "$_HAD_EMBEDDED_COTURN" = true ] && MM_SECRET="$(grep '^COTURN_SECRET=' .env 2>/dev/null | cut -d= -f2-)"
|
||||||
fi
|
fi
|
||||||
@@ -733,9 +742,19 @@ MIGRATE_BODY
|
|||||||
local START=""
|
local START=""
|
||||||
prompt_yn "Start Mattermost now? (y/n):" "y" START
|
prompt_yn "Start Mattermost now? (y/n):" "y" START
|
||||||
if [ "$START" = "y" ] || [ "$START" = "Y" ]; then
|
if [ "$START" = "y" ] || [ "$START" = "Y" ]; then
|
||||||
docker compose up -d \
|
if docker compose up -d; then
|
||||||
&& log_success "Mattermost started" \
|
log_success "Mattermost started"
|
||||||
|| log_warning "Start failed — check: docker compose logs"
|
# Reference implementation of the shared health check — a
|
||||||
|
# "Started" message alone doesn't mean the app is actually up;
|
||||||
|
# it can still crash-loop (bad DB password, missing required
|
||||||
|
# env var, etc.) with no visible sign until someone separately
|
||||||
|
# runs `docker ps -a` much later. Mattermost's own first DB
|
||||||
|
# connection attempt can take a few seconds, hence the longer
|
||||||
|
# wait than check_container_health's 8s default.
|
||||||
|
declare -F check_container_health >/dev/null 2>&1 && check_container_health "$MM_CONTAINER" 12
|
||||||
|
else
|
||||||
|
log_warning "Start failed — check: docker compose logs"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
+13
-3
@@ -317,13 +317,23 @@ install_vaultwarden() {
|
|||||||
echo " SMTP (optional) — for password-reset and invite emails."
|
echo " SMTP (optional) — for password-reset and invite emails."
|
||||||
echo " Press Enter to skip each field and configure SMTP later in .env."
|
echo " Press Enter to skip each field and configure SMTP later in .env."
|
||||||
echo ""
|
echo ""
|
||||||
local SMTP_HOST="" SMTP_FROM="" SMTP_USER="" SMTP_PASS="" SMTP_PORT="587"
|
# Every SMTP_* value (including PORT/SECURITY) stays genuinely empty
|
||||||
|
# unless SMTP_HOST is actually provided — confirmed live, this used to
|
||||||
|
# default SMTP_PORT to "587" and hardcode SMTP_SECURITY=starttls in the
|
||||||
|
# .env template unconditionally, so even a fully-skipped SMTP setup
|
||||||
|
# (SMTP_HOST left blank) still wrote real, non-empty values for those
|
||||||
|
# two. Vaultwarden reads that as "some SMTP config is present" and
|
||||||
|
# refuses to start ("Both SMTP_HOST and SMTP_FROM need to be set"),
|
||||||
|
# crash-looping even though the actual host/from fields were blank —
|
||||||
|
# the "skip SMTP" path was never actually clean.
|
||||||
|
local SMTP_HOST="" SMTP_FROM="" SMTP_USER="" SMTP_PASS="" SMTP_PORT="" SMTP_SECURITY=""
|
||||||
prompt_text "SMTP host (e.g. smtp.gmail.com) [skip]:" "" SMTP_HOST
|
prompt_text "SMTP host (e.g. smtp.gmail.com) [skip]:" "" SMTP_HOST
|
||||||
if [ -n "$SMTP_HOST" ]; then
|
if [ -n "$SMTP_HOST" ]; then
|
||||||
prompt_text "SMTP port [587]:" "587" SMTP_PORT
|
prompt_text "SMTP port [587]:" "587" SMTP_PORT
|
||||||
prompt_text "SMTP from address:" "" SMTP_FROM
|
prompt_text "SMTP from address:" "" SMTP_FROM
|
||||||
prompt_text "SMTP username:" "" SMTP_USER
|
prompt_text "SMTP username:" "" SMTP_USER
|
||||||
prompt_text "SMTP password:" "" SMTP_PASS
|
prompt_text "SMTP password:" "" SMTP_PASS
|
||||||
|
SMTP_SECURITY=starttls
|
||||||
# Vaultwarden refuses to start at all if SMTP_HOST is set without
|
# Vaultwarden refuses to start at all if SMTP_HOST is set without
|
||||||
# SMTP_FROM ("Both SMTP_HOST and SMTP_FROM need to be set") —
|
# SMTP_FROM ("Both SMTP_HOST and SMTP_FROM need to be set") —
|
||||||
# confirmed live, crash-loops on every start, not just a warning at
|
# confirmed live, crash-loops on every start, not just a warning at
|
||||||
@@ -333,7 +343,7 @@ install_vaultwarden() {
|
|||||||
# container — better than guessing a from-address on your behalf.
|
# container — better than guessing a from-address on your behalf.
|
||||||
if [ -z "$SMTP_FROM" ]; then
|
if [ -z "$SMTP_FROM" ]; then
|
||||||
log_warning "No SMTP from address entered — disabling SMTP entirely (Vaultwarden requires both or neither). Re-run this installer to set it up later."
|
log_warning "No SMTP from address entered — disabling SMTP entirely (Vaultwarden requires both or neither). Re-run this installer to set it up later."
|
||||||
SMTP_HOST=""; SMTP_PORT="587"; SMTP_USER=""; SMTP_PASS=""
|
SMTP_HOST=""; SMTP_PORT=""; SMTP_SECURITY=""; SMTP_USER=""; SMTP_PASS=""
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -398,7 +408,7 @@ SIGNUPS_VERIFY=false
|
|||||||
# ── SMTP (optional — for password-reset and invite emails) ────────────────────
|
# ── SMTP (optional — for password-reset and invite emails) ────────────────────
|
||||||
SMTP_HOST=$SMTP_HOST
|
SMTP_HOST=$SMTP_HOST
|
||||||
SMTP_PORT=$SMTP_PORT
|
SMTP_PORT=$SMTP_PORT
|
||||||
SMTP_SECURITY=starttls
|
SMTP_SECURITY=$SMTP_SECURITY
|
||||||
SMTP_FROM=$SMTP_FROM
|
SMTP_FROM=$SMTP_FROM
|
||||||
SMTP_USERNAME=$SMTP_USER
|
SMTP_USERNAME=$SMTP_USER
|
||||||
SMTP_PASSWORD=$SMTP_PASS
|
SMTP_PASSWORD=$SMTP_PASS
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
# Flags:
|
# Flags:
|
||||||
# --dry-run preview actions without making changes
|
# --dry-run preview actions without making changes
|
||||||
# --unattended use defaults, no prompts (pair with explicit service names)
|
# --unattended use defaults, no prompts (pair with explicit service names)
|
||||||
|
# --remove remove instead of install (pair with a service name, e.g.
|
||||||
|
# ./setup.sh filebrowser --remove) — stops/removes its
|
||||||
|
# containers, its Caddy site block (if any), any UFW rule
|
||||||
|
# tagged for it, and optionally its ~/docker/<name> directory
|
||||||
#
|
#
|
||||||
# Every service lives in services/<name>.sh, registers itself with
|
# Every service lives in services/<name>.sh, registers itself with
|
||||||
# register_service, and defines install_<name>. Adding a service = adding one
|
# register_service, and defines install_<name>. Adding a service = adding one
|
||||||
@@ -34,7 +38,7 @@ declare -A SERVICE_PRIORITY=( [caddy]=1 [crowdsec]=2 [authelia]=3 )
|
|||||||
declare -A SERVICE_ALIAS=( [asterisk-digital-ocean]=asterisk )
|
declare -A SERVICE_ALIAS=( [asterisk-digital-ocean]=asterisk )
|
||||||
|
|
||||||
# ── Parse flags / collect service names ──────────────────────────────────────
|
# ── Parse flags / collect service names ──────────────────────────────────────
|
||||||
DRY_RUN=false; UNATTENDED=false; DO_LIST=false; DO_STATUS=false
|
DRY_RUN=false; UNATTENDED=false; DO_LIST=false; DO_STATUS=false; DO_REMOVE=false
|
||||||
REQUESTED=()
|
REQUESTED=()
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
case "$arg" in
|
case "$arg" in
|
||||||
@@ -42,6 +46,7 @@ for arg in "$@"; do
|
|||||||
--unattended) UNATTENDED=true ;;
|
--unattended) UNATTENDED=true ;;
|
||||||
--list|-l) DO_LIST=true ;;
|
--list|-l) DO_LIST=true ;;
|
||||||
--status) DO_STATUS=true ;;
|
--status) DO_STATUS=true ;;
|
||||||
|
--remove) DO_REMOVE=true ;;
|
||||||
--version|-V) cat "$HERE/VERSION" 2>/dev/null || echo "unknown"; exit 0 ;;
|
--version|-V) cat "$HERE/VERSION" 2>/dev/null || echo "unknown"; exit 0 ;;
|
||||||
-h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
-h|--help) sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
||||||
-*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
-*) echo "Unknown flag: $arg" >&2; exit 1 ;;
|
||||||
@@ -267,6 +272,29 @@ if [ "${REQUESTED[*]:-}" = "configure" ]; then
|
|||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── --remove: ./setup.sh filebrowser --remove ────────────────────────────────
|
||||||
|
if [ "$DO_REMOVE" = true ]; then
|
||||||
|
require_root
|
||||||
|
if [ "${#REQUESTED[@]}" -eq 0 ]; then
|
||||||
|
log_error "--remove needs a service name, e.g. ./setup.sh filebrowser --remove"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
rc=0
|
||||||
|
for name in "${REQUESTED[@]}"; do
|
||||||
|
if [ -n "${SERVICE_ALIAS[$name]:-}" ]; then
|
||||||
|
log_info "'$name' is now part of '${SERVICE_ALIAS[$name]}' — removing that instead."
|
||||||
|
name="${SERVICE_ALIAS[$name]}"
|
||||||
|
fi
|
||||||
|
if [ -z "${SERVICE_GROUP[$name]:-}" ]; then
|
||||||
|
log_error "Unknown service: $name (try --list)"
|
||||||
|
rc=1
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
remove_service "$name" || rc=1
|
||||||
|
done
|
||||||
|
exit "$rc"
|
||||||
|
fi
|
||||||
|
|
||||||
# ── Direct install: ./setup.sh caddy homeassistant ──────────────────────────
|
# ── Direct install: ./setup.sh caddy homeassistant ──────────────────────────
|
||||||
if [ "${#REQUESTED[@]}" -gt 0 ]; then
|
if [ "${#REQUESTED[@]}" -gt 0 ]; then
|
||||||
require_root
|
require_root
|
||||||
|
|||||||
Reference in New Issue
Block a user