Commit Graph
13 Commits
Author SHA1 Message Date
Claude c50704e1b3 Add automatic tab-completion setup and old config-backup pruning
Two things surfaced from actual use this session:

1. Tab completion (tools/setup-completion.bash, added earlier) required
   manually editing ~/.bashrc — easy to skip or get wrong (confirmed
   live: the source line never actually landed the first time). base
   now wires it in automatically (idempotent, checked by grep first),
   matching how it already touches ~/.bashrc for SSH Host aliases.

2. No pruning existed anywhere for the *.backup.<timestamp> files ~60
   different services create before overwriting a live config
   (Caddyfile, /etc/fstab, etc) — every one of them backs up, none
   clean up, so they accumulate forever on a box reconfigured
   regularly. tools/prune-old-backups.sh prunes by file mtime (not by
   parsing the timestamp out of the filename — robust to the
   %Y%m%d-%H%M%S vs %Y%m%d_%H%M%S inconsistency across services),
   always keeping the single newest backup per distinct file
   regardless of age. Verified both the normal case (mixed old/new,
   prunes only the old ones) and the edge case (every backup for a
   file is old, keeps the newest one anyway) against real fixtures.
   base offers it as a daily systemd timer (prompted, since it deletes
   files — unlike the tab-completion wiring, which doesn't).

Also added logrotate for Caddy's own access logs
(/var/log/caddy/*.log), which had no rotation at all and grow
unbounded on an active box. Uses copytruncate specifically: the log
directory is bind-mounted into the running Caddy container and read
live by CrowdSec, so truncating in place avoids either of them needing
to notice or react to a rotation happening.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 04:48:11 +00:00
Claude 8bdf4c0a07 Add explicit UFW rules for Caddy's 80/443 — Docker was silently bypassing UFW
services/caddy.sh never called ufw allow for any of its published ports.
Confirmed live: ufw status showed no rule for 80 or 443 on a box with
UFW active (default deny incoming), yet HTTPS sites were reachable
fine — Docker manipulates iptables directly for published container
ports (the ports: mapping in Caddy's own compose file), which bypasses
UFW's filtering entirely regardless of what ufw status reports.

This wasn't an actual exposure gap — 80/443 are supposed to be open to
everyone, that's the point of a reverse proxy — but it means ufw status
was actively misrepresenting this box's real firewall state on its two
most externally-facing ports, which is exactly the kind of thing that
looks like a problem (and did, when investigating an unrelated
Let's-Encrypt failure) even though nothing was actually unprotected.

Add explicit ufw allow rules for 80/tcp, 443/tcp, and 443/udp (HTTP/3)
so ufw status reflects reality, matching every other service in this
repo managing its own firewall rules instead of relying on undocumented
Docker/iptables interaction.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4k6J1qXXyYxhGEgnJaMvn
2026-08-11 01:51:04 +00:00
Claude 9e06ed4b83 Bake cross-service port collision avoidance into every service script
With 70+ services sharing a handful of common default ports (emby and
jellyfin both default to 8096, changedetection and frigate both default
to 5000, arm and nextcloud both default to 8080...), nothing previously
checked whether a service's default port was actually free on the host.
Whichever service installed second would silently write a compose file
claiming an already-held port, only failing at `docker compose up` time.

Adds two shared helpers to lib/common.sh:
- port_in_use PORT [PROTO] — true if something's already listening
- find_free_port VARNAME START [PROTO] — scans upward, writes back the
  first free port

Every service that publishes a fixed host port now scans before writing
docker-compose.yml, on every install (not just when adding an explicit
additional instance). On a normal single-install host this is a silent
no-op; it only changes behavior when something else already holds the
port.

- The 19 services already given multi-instance support this session had
  their port scan moved out of the "add instance" branch to run
  unconditionally, since the same collision risk exists on a plain first
  install.
- 20 more services with previously-hardcoded ports gained scanning for
  the first time: archivebox, arm, calibre-web, changedetection,
  drum-rhythm-game, gatus, n8n, nextcloud, onlyoffice, stirling-pdf,
  uptimekuma, portainer, iopaint (both GPU/CPU compose branches), koha
  (paired), syncthing (paired), wg-easy (paired, plus WG_PORT env so
  generated peer configs keep the right Endpoint), homeassistant
  (bridge-mode only — host mode can only warn), frigate and
  frigate-audio (multi-port stacks, moved together).
- caddy.sh is the deliberate exception: 80/443 stay fixed and only warn
  on collision, since silently moving Caddy itself would leave nothing
  listening where any client actually looks.
- authelia.sh needs no change — it has no published host port at all.
- Every service's standalone bootstrap fallback (sudo bash services/x.sh
  with no sibling files) got the same two helpers duplicated into its
  stub block, matching how every other shared helper is already handled
  there.

Documents the full pattern in CLAUDE.md's new "Port collision avoidance"
section, including the quoted-heredoc/backtick-escaping gotcha and the
network_mode:host limitation (can only scan ports the app takes as a
configurable env var).

Verified via bash -n on every changed file, plus functional runs seeding
occupied ports for each collision shape used here (single, paired,
multi-port stacks) and confirming the scan/shift and generated
compose/README output are correct — including the emby/jellyfin,
nextcloud/arm, and frigate/changedetection collision scenarios that
originally motivated this.
2026-08-09 23:55:49 +00:00
Claude 8b843ca1c1 Fold asterisk-digital-ocean into asterisk with droplet auto-detection
services/asterisk-digital-ocean.sh was a near-verbatim copy of
services/asterisk.sh — same vendor refresh, compose template, messaging
dialplan, presence alerts, UFW rules and dashboard/trunk chaining, with the
helper functions renamed _asterisk_do_*. Two copies meant every fix had to
land twice, and several never did.

There is now one `asterisk` service. It reads the DigitalOcean metadata
service and asks either way (so a droplet with metadata blocked, or another
provider's public VM, can still opt in), then gates the genuinely
droplet-specific behaviour on that one answer: swapfile for low-RAM plans,
public-FQDN-only setup with no LAN/VLAN prompts, a Caddy site block pinned
to that FQDN, the remote-Authelia option, and the doctl Cloud Firewall.

Two things that were droplet-only for no real reason now apply everywhere:
the entrypoint patch that writes security-level events to logs/full, and
the logrotate config for that file. Without them the Security Dashboard's
Security Log tab and CrowdSec's Asterisk acquisition were silently empty on
every home/LAN install; crowdsec.sh now detects either install directory.

Existing droplets are left alone: an install at ~/docker/asterisk-digital-ocean
keeps its directory and easy-asterisk-do container names, since its Caddyfile
block, UFW rules, Cloud Firewall, CrowdSec acquisition and PSTN trunk all
name those exactly. New installs use ~/docker/asterisk / easy-asterisk.
`sudo ./setup.sh asterisk-digital-ocean` still works via a new SERVICE_ALIAS
map in setup.sh, without a second menu entry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NAddJGE1G6eGaPzmScG5Vh
2026-07-25 01:06:38 +00:00
Claude 45609c2cb1 Rename asterisk-do service to asterisk-digital-ocean
Renamed services/asterisk-do.sh -> services/asterisk-digital-ocean.sh
(register_service name, install function, install dir, and all prose/
comments) so the whiptail menu shows a clearer, more discoverable name.

Updated the functional cross-references that depend on the old name:
crowdsec.sh's SIP-log auto-detection path and acquisition filename,
caddy.sh's host.docker.internal comment, and the CLAUDE.md/README.md
docs (services table, directory listing, network-wiring example).

Container names, the Docker Compose project name, and the internal
_asterisk_do_* helper function identifiers are left unchanged since
they aren't user-facing and renaming them would add risk for no
benefit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015X1jRGHwrvovz2qkhKfDZi
2026-07-20 13:57:35 +00:00
Claude 52d46c5552 Remove asterisk-do's auto-install of Caddy/CrowdSec/base/extras
asterisk-do previously offered to auto-install base, Caddy, CrowdSec,
and a numbered extras menu (authelia/ntfy/watchtower/wg-easy/netbird/
backup) on top of its own setup, layering a second install flow on
top of the whiptail menu setup.sh already provides. Strips all of
that back out — asterisk-do now only installs Asterisk + coturn, same
scope as any other service. Caddy/Authelia integration (reverse
proxy, cert sync, SSO) is kept, since it only activates when those
are already installed — no auto-install behind it. CrowdSec SIP
protection still wires up automatically via crowdsec.sh's own
asterisk-do detection, regardless of which one installs first.

Also fixes a real regression from ensure_caddy_network (added
earlier): it created caddy_net via a bare `docker network create`,
which doesn't carry Compose's ownership labels, so Caddy's own
non-external network declaration conflicted with it and failed to
start ("network exists but was not created by compose"). Caddy's
compose file now declares caddy_net as external: true like every
other service, since ensure_caddy_network is the single creator for
all of them, Caddy included.
2026-07-20 13:39:23 +00:00
Claude 6c48bdc707 Fix Caddy reverse-proxy target for host-network services
Caddy runs in its own container on the caddy_net bridge network, so
"localhost" in a Caddyfile site block resolves to Caddy's own
container — never the host, and never a sibling container. That
broke every reverse proxy pointed at a network_mode: host service
(confirmed live with asterisk-do's web admin): once nothing else
(like a forward_auth redirect) intercepted the request first, Caddy
couldn't actually reach the upstream.

- services/caddy.sh: add extra_hosts so host.docker.internal resolves
  inside the Caddy container (Linux Docker needs this explicitly —
  it's automatic only on Docker Desktop).
- lib/common.sh's configure_caddy_for_service: bare-port upstreams
  (its documented "host-network service" case) now target
  host.docker.internal instead of localhost.
- services/asterisk-do.sh: its self-contained Caddy block (doesn't go
  through configure_caddy_for_service) gets the same fix for local
  Caddy, and now correctly targets the droplet's public IP instead of
  localhost for the remote-Caddy snippet case, which had the same bug.

services/asterisk.sh needs no direct change — it already goes through
configure_caddy_for_service, so it inherits the fix.
2026-07-20 04:11:24 +00:00
Claude ec3f9bfd3f Add remote Caddy support — generate snippet files when Caddy is on another host
New site config key: CADDY_REMOTE_HOST (set via 'sudo ./setup.sh configure').
When set, configure_caddy_for_service operates in "remote" mode instead of
writing to a local Caddyfile:
- Upstream uses CADDY_REMOTE_HOST:PORT (host IP, not container name)
- Snippet saved to ~/docker/caddy-snippets/<subdomain>.caddy
- User is shown scp/rsync commands to copy it to the Caddy machine

Three modes in configure_caddy_for_service (lib/common.sh and inline stubs):
  local:  ~/docker/caddy/ exists → write Caddyfile + reload (existing behavior)
  remote: CADDY_REMOTE_HOST set → save snippet, print copy instructions
  none:   neither configured → silent return (unchanged)

All 31 service standalone bootstrap stubs updated with the new logic.
CADDY_REMOTE_HOST global added to all 42 standalone bootstrap sections.
setup.sh configure now prompts for CADDY_REMOTE_HOST with a clear explanation.
wolf.sh: add missing stubs (configure_caddy_for_service, write_readme,
  prompt_yn, ensure_docker_dir_ownership) and the Authelia/Caddy/start calls
  that were missing from the install function.

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-09 00:28:30 +00:00
Claude 5aa4a8c91e services: add standalone bootstrap to arm, authelia, backup, borg-backup, caddy, ntfy
Each service can now be run directly with sudo bash <service>.sh on any
machine with Docker installed, without needing the full post-install repo.
Uses the shared bootstrap pattern from docs/standalone-template.sh.

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-08 17:01:34 +00:00
Claude 2e63ca1756 caddy: document internal vs host port in Caddyfile template and README
Services on caddy_net talk container-to-container using the internal
port. The host-mapped port (left side of ports:) is only for direct
access from another machine. Added this as a comment block in the
generated Caddyfile starter and in the README.

https://claude.ai/code/session_014CCYqVwW6d6f5dw1qRokYt
2026-06-08 02:57:12 +00:00
Claude 9c52ac22e1 Fix Caddy routing: use container:port via caddy_net (DoTheEvo pattern)
Undo the host.docker.internal approach from the previous commit — proper
Docker networking routes Caddy to services by container name on the shared
caddy_net, not via the host gateway.

- lib/common.sh: configure_caddy_for_service now accepts either a plain
  port number (localhost:PORT fallback) or container:port (preferred).
  The Caddyfile entry uses the container name for direct Docker DNS routing.
- services/caddy.sh: remove extra_hosts hack; update Caddyfile template
  comments to show container_name:port format
- All service files: update configure_caddy_for_service calls to pass
  container_name:internal_port (e.g. "filebrowser:80", "mealie:9000").
  Services using network_mode:host keep plain port numbers.
- tools/manage_users.sh: new FileBrowser user-management script (deployed
  to ~/docker/filebrowser/ during installation). Manages users via the
  FileBrowser REST API: list, add, delete, passwd, scope, info commands.
  Documents username format (letters/numbers/hyphens/underscores),
  password rules (min 8 chars, letter + number required), and scope path
  convention relative to /srv (= FB_PATH on the host).

https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
2026-06-07 22:12:01 +00:00
Claude 56bec03c9b Fix Caddy routing and add caddy_net to all Docker services
- lib/common.sh: fix broken reverse_proxy target — localhost inside Caddy's
  container is the container's own loopback, not the host; change to
  host.docker.internal so proxied ports are actually reachable
- services/caddy.sh: add extra_hosts host.docker.internal:host-gateway so
  the above resolves correctly; create caddy_net bridge network in Caddy's
  own compose so other services can reference it as external; update all
  Caddyfile template comments and README examples to match
- services/filebrowser.sh: update image tag from deprecated :s6 to :latest;
  remove non-functional PUID/PGID env vars (filebrowser/filebrowser does not
  honour them); add configure_caddy_for_service call; add caddy_net
- services/ntfy.sh: add configure_caddy_for_service call; add caddy_net
- services/portainer.sh: add configure_caddy_for_service call; add caddy_net
- services/frigate-notify.sh, watchtower.sh: add caddy_net for
  container-to-container comms (frigate, ntfy) without a Caddy call
- All remaining web-facing Docker services: add caddy_net network block to
  docker-compose and CADDY_NET to .env where applicable; services using
  network_mode: host (wolf-pair, lyrion) have the top-level block only

https://claude.ai/code/session_01UZus2Q9gNTfUdqSMrhuX29
2026-06-07 22:02:26 +00:00
Claude 9dc8c4063d v0.9.7: Caddy + CrowdSec modules; category menu with required-gate
- services/caddy.sh (homelab): reverse proxy + auto HTTPS, own ~/docker/caddy
  folder (compose + starter Caddyfile + README).
- services/crowdsec.sh (homelab): system-level IPS (agent + firewall bouncer +
  Caddy acquisition + optional ntfy alerts), README in ~/docker/crowdsec.
- setup.sh guided flow redesign:
  * Prints REQUIRED set (essentials + glow + docker check) with a cancel option.
  * Offers Caddy first (most services proxy through it).
  * Category menu LOOP: pick category -> checklist ([installed] marked) ->
    install -> back to menu, until Done. whiptail + text fallback.
- Categories reorganized: base/homelab/utilities/media/cameras/gaming/backup;
  moved ntfy/filebrowser/portainer/uptimekuma/watchtower to utilities;
  caddy->crowdsec->authelia ordered first in homelab.

Verified: bash -n all; --list groups by category with caddy first; cancel path
prints 'Cancelled, nothing changed'; dry-run guided flow runs required + loops
menu; run-one still works.

https://claude.ai/code/session_017eA2qqq9jfF2tNtpUYL8vK
2026-06-03 17:16:32 +00:00