Bitcoin UI: - Replace cdn.tailwindcss.com with locally bundled tailwind.css (CSP blocks external scripts) - Make all asset paths relative for nginx proxy compatibility - Add bitcoin-ui build/deploy to deploy-to-target.sh (was missing entirely) - Use --network host (bitcoin-ui proxies Bitcoin RPC at 127.0.0.1:8332) HTTPS mixed content fix: - Add HTTPS_PROXY_PATHS in AppSession.vue — when parent page is HTTPS, iframe loads through nginx proxy instead of direct HTTP port - Prevents browser blocking HTTP iframes inside HTTPS pages - All Tailscale servers use HTTPS, this was breaking all app iframes Deploy & first-boot improvements: - first-boot-containers.sh auto-detects disk size for pruning vs txindex - first-boot-containers.sh checks fallback source path for UI containers - Added mempool-electrs to APP_PORTS mapping - ElectrumX container creation in first-boot - Podman doctor/fix/uptime skills added Also includes: session persistence, identity management, LND transactions, ElectrumX status UI, nostr-provider improvements, Web5 enhancements Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
310 lines
9.1 KiB
Markdown
310 lines
9.1 KiB
Markdown
---
|
|
name: podman-uptime
|
|
description: >
|
|
Ensure 100% container uptime on Archipelago. Sets up systemd watchdog timers, verifies
|
|
restart policies, creates health check monitors, and configures auto-recovery for all
|
|
containers. Use when asked to "ensure uptime", "containers keep dying", "auto-restart",
|
|
"watchdog", "container monitoring", "uptime guarantee", "keep containers running",
|
|
"survive reboot", or to harden container reliability.
|
|
allowed-tools: Bash Read Edit Write Glob Grep
|
|
---
|
|
|
|
# Podman Uptime — Container Reliability Guardian
|
|
|
|
Ensures every Archipelago container survives reboots, recovers from crashes, and stays healthy. Sets up the three layers of uptime defense: restart policies, systemd watchdog, and health-based auto-recovery.
|
|
|
|
**SSH command**: `ssh -i ~/.ssh/archipelago-deploy archipelago@192.168.1.228`
|
|
|
|
## Layer 1: Restart Policies (Survive Reboots)
|
|
|
|
Every container MUST have `--restart unless-stopped`. This is non-negotiable.
|
|
|
|
### Audit and fix all containers
|
|
|
|
```bash
|
|
# Audit
|
|
for c in $(sudo podman ps -a --format "{{.Names}}"); do
|
|
policy=$(sudo podman inspect "$c" --format "{{.HostConfig.RestartPolicy.Name}}")
|
|
echo "$c: $policy"
|
|
done
|
|
|
|
# Fix any with "no" or empty policy
|
|
for c in $(sudo podman ps -a --format "{{.Names}}"); do
|
|
policy=$(sudo podman inspect "$c" --format "{{.HostConfig.RestartPolicy.Name}}")
|
|
if [ "$policy" = "no" ] || [ -z "$policy" ]; then
|
|
echo "Fixing: $c"
|
|
sudo podman update --restart unless-stopped "$c"
|
|
fi
|
|
done
|
|
```
|
|
|
|
### Ensure podman auto-starts containers on boot
|
|
|
|
```bash
|
|
# Enable podman-restart service (restarts containers with restart policy on boot)
|
|
sudo systemctl enable podman-restart.service 2>/dev/null || true
|
|
|
|
# If podman-restart doesn't exist, create it
|
|
cat <<'EOF' | sudo tee /etc/systemd/system/podman-restart.service
|
|
[Unit]
|
|
Description=Podman Start All Containers With Restart Policy
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/bin/podman start --all --filter restart-policy=unless-stopped
|
|
RemainAfterExit=yes
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable podman-restart.service
|
|
```
|
|
|
|
## Layer 2: Systemd Watchdog (Detect and Recover)
|
|
|
|
Create a systemd timer that checks container health every 2 minutes and restarts unhealthy or stopped containers.
|
|
|
|
### Create the watchdog script
|
|
|
|
```bash
|
|
cat <<'SCRIPT' | sudo tee /usr/local/bin/archipelago-container-watchdog.sh
|
|
#!/bin/bash
|
|
# Archipelago Container Watchdog
|
|
# Checks all containers and restarts any that are stopped or unhealthy
|
|
|
|
LOG_TAG="container-watchdog"
|
|
|
|
# Restart any stopped containers that should be running (have restart policy)
|
|
for c in $(sudo podman ps -a --filter status=exited --filter restart-policy=unless-stopped --format "{{.Names}}"); do
|
|
logger -t "$LOG_TAG" "Restarting stopped container: $c"
|
|
sudo podman start "$c" 2>&1 | logger -t "$LOG_TAG"
|
|
done
|
|
|
|
# Restart unhealthy containers
|
|
for c in $(sudo podman ps --filter health=unhealthy --format "{{.Names}}"); do
|
|
logger -t "$LOG_TAG" "Restarting unhealthy container: $c"
|
|
sudo podman restart "$c" 2>&1 | logger -t "$LOG_TAG"
|
|
done
|
|
|
|
# Check for containers in "created" state (never started)
|
|
for c in $(sudo podman ps -a --filter status=created --format "{{.Names}}"); do
|
|
logger -t "$LOG_TAG" "Starting created container: $c"
|
|
sudo podman start "$c" 2>&1 | logger -t "$LOG_TAG"
|
|
done
|
|
SCRIPT
|
|
|
|
sudo chmod +x /usr/local/bin/archipelago-container-watchdog.sh
|
|
```
|
|
|
|
### Create the systemd timer
|
|
|
|
```bash
|
|
# Service unit
|
|
cat <<'EOF' | sudo tee /etc/systemd/system/archipelago-watchdog.service
|
|
[Unit]
|
|
Description=Archipelago Container Watchdog
|
|
After=podman-restart.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/bin/archipelago-container-watchdog.sh
|
|
EOF
|
|
|
|
# Timer unit — runs every 2 minutes
|
|
cat <<'EOF' | sudo tee /etc/systemd/system/archipelago-watchdog.timer
|
|
[Unit]
|
|
Description=Run Archipelago Container Watchdog every 2 minutes
|
|
|
|
[Timer]
|
|
OnBootSec=120
|
|
OnUnitActiveSec=120
|
|
AccuracySec=30
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now archipelago-watchdog.timer
|
|
```
|
|
|
|
### Verify watchdog is running
|
|
|
|
```bash
|
|
sudo systemctl status archipelago-watchdog.timer
|
|
sudo systemctl list-timers | grep archipelago
|
|
# Check watchdog logs
|
|
sudo journalctl -t container-watchdog --since "1 hour ago" --no-pager
|
|
```
|
|
|
|
## Layer 3: Dependency-Aware Startup Order
|
|
|
|
Some containers depend on others. The watchdog handles restarts, but initial boot order matters.
|
|
|
|
### Create ordered startup script
|
|
|
|
```bash
|
|
cat <<'SCRIPT' | sudo tee /usr/local/bin/archipelago-ordered-start.sh
|
|
#!/bin/bash
|
|
# Ordered container startup for Archipelago
|
|
# Respects dependency chain: bitcoin → electrs/lnd → mempool/btcpay
|
|
|
|
LOG_TAG="ordered-start"
|
|
|
|
wait_for_container() {
|
|
local name=$1
|
|
local max_wait=${2:-60}
|
|
local waited=0
|
|
while [ $waited -lt $max_wait ]; do
|
|
status=$(sudo podman inspect "$name" --format "{{.State.Running}}" 2>/dev/null)
|
|
if [ "$status" = "true" ]; then
|
|
logger -t "$LOG_TAG" "$name is running"
|
|
return 0
|
|
fi
|
|
sleep 5
|
|
waited=$((waited + 5))
|
|
done
|
|
logger -t "$LOG_TAG" "WARNING: $name not running after ${max_wait}s"
|
|
return 1
|
|
}
|
|
|
|
# Tier 0: Infrastructure
|
|
logger -t "$LOG_TAG" "Starting Tier 0: Infrastructure"
|
|
sudo podman start tailscale 2>/dev/null
|
|
|
|
# Tier 1: Bitcoin (foundation)
|
|
logger -t "$LOG_TAG" "Starting Tier 1: Bitcoin"
|
|
sudo podman start bitcoin-knots 2>/dev/null
|
|
wait_for_container bitcoin-knots 120
|
|
|
|
# Tier 2: Bitcoin-dependent services
|
|
logger -t "$LOG_TAG" "Starting Tier 2: Bitcoin-dependent"
|
|
sudo podman start electrs 2>/dev/null
|
|
sudo podman start lnd 2>/dev/null
|
|
wait_for_container electrs 90
|
|
wait_for_container lnd 90
|
|
|
|
# Tier 3: Services depending on Tier 2
|
|
logger -t "$LOG_TAG" "Starting Tier 3: Second-order dependencies"
|
|
sudo podman start mempool-db 2>/dev/null
|
|
sleep 5
|
|
sudo podman start mempool 2>/dev/null
|
|
sudo podman start nbxplorer 2>/dev/null
|
|
sleep 10
|
|
sudo podman start btcpay-server 2>/dev/null
|
|
sudo podman start btcpay-postgres 2>/dev/null
|
|
|
|
# Tier 4: Independent apps (start all remaining)
|
|
logger -t "$LOG_TAG" "Starting Tier 4: Independent apps"
|
|
sudo podman start --all 2>/dev/null
|
|
|
|
# Tier 5: UI containers (need parent apps running first)
|
|
logger -t "$LOG_TAG" "Starting Tier 5: UI containers"
|
|
sudo podman start bitcoin-ui 2>/dev/null
|
|
sudo podman start lnd-ui 2>/dev/null
|
|
|
|
logger -t "$LOG_TAG" "Startup sequence complete"
|
|
SCRIPT
|
|
|
|
sudo chmod +x /usr/local/bin/archipelago-ordered-start.sh
|
|
```
|
|
|
|
### Wire into boot sequence
|
|
|
|
```bash
|
|
cat <<'EOF' | sudo tee /etc/systemd/system/archipelago-containers.service
|
|
[Unit]
|
|
Description=Archipelago Ordered Container Startup
|
|
After=network-online.target podman.service
|
|
Wants=network-online.target
|
|
Before=archipelago.service
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
ExecStart=/usr/local/bin/archipelago-ordered-start.sh
|
|
RemainAfterExit=yes
|
|
TimeoutStartSec=300
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable archipelago-containers.service
|
|
```
|
|
|
|
## Verification Checklist
|
|
|
|
After setting up all 3 layers, verify:
|
|
|
|
```bash
|
|
echo "=== Layer 1: Restart Policies ==="
|
|
for c in $(sudo podman ps -a --format "{{.Names}}"); do
|
|
policy=$(sudo podman inspect "$c" --format "{{.HostConfig.RestartPolicy.Name}}")
|
|
echo " $c: $policy"
|
|
done
|
|
|
|
echo ""
|
|
echo "=== Layer 2: Watchdog Timer ==="
|
|
sudo systemctl is-active archipelago-watchdog.timer
|
|
sudo systemctl list-timers | grep archipelago
|
|
|
|
echo ""
|
|
echo "=== Layer 3: Boot Services ==="
|
|
sudo systemctl is-enabled podman-restart.service 2>/dev/null || echo "podman-restart: not found"
|
|
sudo systemctl is-enabled archipelago-containers.service 2>/dev/null || echo "ordered-start: not found"
|
|
sudo systemctl is-enabled archipelago-watchdog.timer 2>/dev/null || echo "watchdog: not found"
|
|
|
|
echo ""
|
|
echo "=== Container Health Summary ==="
|
|
total=$(sudo podman ps -a --format "{{.Names}}" | wc -l)
|
|
running=$(sudo podman ps --format "{{.Names}}" | wc -l)
|
|
stopped=$((total - running))
|
|
unhealthy=$(sudo podman ps --filter health=unhealthy --format "{{.Names}}" | wc -l)
|
|
echo " Total: $total | Running: $running | Stopped: $stopped | Unhealthy: $unhealthy"
|
|
```
|
|
|
|
## Reboot Test
|
|
|
|
The ultimate uptime test — reboot the server and verify everything comes back:
|
|
|
|
```bash
|
|
# Before reboot: record running containers
|
|
sudo podman ps --format "{{.Names}}" | sort > /tmp/before-reboot.txt
|
|
|
|
# Reboot
|
|
sudo reboot
|
|
|
|
# After reboot (wait ~3 minutes, then SSH back in):
|
|
sudo podman ps --format "{{.Names}}" | sort > /tmp/after-reboot.txt
|
|
|
|
# Compare
|
|
diff /tmp/before-reboot.txt /tmp/after-reboot.txt
|
|
# Should show no differences
|
|
```
|
|
|
|
## Monitoring
|
|
|
|
Check uptime status anytime:
|
|
```bash
|
|
# Quick status
|
|
sudo podman ps -a --format "table {{.Names}}\t{{.Status}}" | sort
|
|
|
|
# Watchdog activity
|
|
sudo journalctl -t container-watchdog --since "24 hours ago" --no-pager
|
|
|
|
# Container events (starts, stops, deaths)
|
|
sudo podman events --since 24h --filter event=start --filter event=stop --filter event=died 2>/dev/null | tail -30
|
|
```
|
|
|
|
## Integration
|
|
|
|
- Run `/podman-doctor` first to identify issues
|
|
- Run `/podman-fix` for specific container repairs
|
|
- Run `/podman-uptime` to set up permanent reliability infrastructure
|
|
- Add to ISO build: copy watchdog scripts to `image-recipe/configs/` and enable in first-boot
|