Files
platform/scripts/setup/setup-dockers.sh
T
pastilhasandClaude Opus 5 9c353f5f0d move host setup into scripts/setup/
scripts/ was holding two unrelated kinds of thing: install-this-machine, and run-this-occasionally. The
eight installers now live in scripts/setup/; what stays at the top level is the build steps (gen-index,
prebuild, build/) and the two maintenance scripts (reindex-music, rebuild-soulseek-tree).

The move is not just a rename. Three of these derive the repo root from their own location:

  setup.sh:51            PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
  setup_mac_light.sh:51  same
  cleanup-desktop.sh:134 ENV_FILE="$(dirname "$0")/../.env"

Left alone, all three would now resolve to scripts/ — and nothing downstream complains. PROJECT_DIR is
where .env is written, where `bun install`, `gen:index` and `db:push` run, and what pm2 is pointed at, so
a fresh install would have quietly provisioned scripts/ and reported success. cleanup-desktop.sh fails
the other way: it would find no .env, print "No .env — skipping", and leave the real VNC_PASSWORD in the
real file. All three are now `../..` with a comment saying why the level matters.

provision-user-dirs.ts imports data-path.ts relatively; that one tsgo caught.

Also disambiguated `setup.sh` where it had become two files. app-store/templates/<name>/setup.sh is a
per-sidecar installer with its own contract, and preflight.ts + docs/sidecar-app-store.md discussed both
in the same paragraph. The host one is now spelled with its full path at those sites.

Verified: bash -n on all six shell scripts, tsgo clean, os-user tests pass, both derivations resolve to
the repo root, starship.toml still resolves from os-user-shell.ts, and provision-user-dirs.ts runs under
DRY_RUN.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 04:02:19 +00:00

248 lines
7.3 KiB
Bash
Executable File

#!/bin/bash
# Officer — Docker Compose service setup
# Sets up Docker Compose services (Nginx Proxy Manager, PostgreSQL, Mailhog, Redis, SearXNG).
# Outputs parseable key=value lines to stdout; all prompts go to stderr.
#
# Usage:
# bash scripts/setup/setup-dockers.sh
# eval "$(bash scripts/setup/setup-dockers.sh)"
#
# Environment overrides:
# SETUP_DOCKER_SERVICES="1 2 3" — pre-select services (or "all"/"none")
# SETUP_DOCKER_NETWORK="services" — docker network name
set -e
# Resolve the real user's home even when running under sudo
if [[ -n "${SUDO_USER:-}" ]]; then
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
else
REAL_HOME="$HOME"
fi
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m'
info() { echo -e "${CYAN}::${NC} $*" >&2; }
ok() { echo -e " ${GREEN}${NC} $1" >&2; }
warn() { echo -e " ${YELLOW}!${NC} $1" >&2; }
fail() { echo -e " ${RED}${NC} $1" >&2; }
prompt_value() {
local varname="$1" prompt="$2" default="$3"
if [[ -n "$default" ]]; then
echo -en " ${BOLD}${prompt}${NC} [${default}]: " >&2
else
echo -en " ${BOLD}${prompt}${NC}: " >&2
fi
read -r val
if [[ -z "$val" ]]; then val="$default"; fi
eval "$varname=\"\$val\""
}
# ─── docker network ─────────────────────────────────────────────────────────
DOCKER_NETWORK="${SETUP_DOCKER_NETWORK:-services}"
# Ensure network exists
if ! docker network inspect "$DOCKER_NETWORK" &>/dev/null; then
docker network create "$DOCKER_NETWORK" >/dev/null 2>&1
ok "Created docker network '$DOCKER_NETWORK'"
else
ok "Docker network '$DOCKER_NETWORK' exists"
fi
# ─── service selection ───────────────────────────────────────────────────────
SERVICES="${SETUP_DOCKER_SERVICES:-}"
if [[ "$SERVICES" == "none" ]]; then
info "Skipping Docker Compose services (SETUP_DOCKER_SERVICES=none)"
exit 0
fi
if [[ -z "$SERVICES" ]]; then
info "Select Docker Compose services to include:"
echo " [1] Nginx Proxy Manager (jc21/nginx-proxy-manager:latest)" >&2
echo " [2] PostgreSQL 18 (postgres:18-alpine)" >&2
echo " [3] Mailhog (mailhog/mailhog:latest)" >&2
echo " [4] Redis (redis:alpine)" >&2
echo " [5] SearXNG (searxng/searxng:latest)" >&2
prompt_value SERVICES "Enter numbers separated by spaces (e.g. '1 2 4'), or 'all'" "all"
fi
if [[ "$SERVICES" == "all" ]]; then
SERVICES="1 2 3 4 5"
fi
# ─── compose directory ──────────────────────────────────────────────────────
prompt_value COMPOSE_DIR "Docker Compose directory" "$REAL_HOME/.local/dockers"
mkdir -p "$COMPOSE_DIR"
# ─── build compose file ─────────────────────────────────────────────────────
cat > "$COMPOSE_DIR/docker-compose.yaml" <<'HEADER'
services:
HEADER
COMPOSE_SERVICES=()
PG_PASSWORD=""
MAILHOG_SELECTED=false
for svc in $SERVICES; do
case "$svc" in
1)
COMPOSE_SERVICES+=("nginx-proxy-manager")
cat >> "$COMPOSE_DIR/docker-compose.yaml" <<SVC
nginx-proxy-manager:
image: jc21/nginx-proxy-manager:latest
container_name: nginx-proxy-manager
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "81:81"
volumes:
- ./npm_data:/data
- ./npm_letsencrypt:/etc/letsencrypt
networks:
- $DOCKER_NETWORK
SVC
;;
2)
COMPOSE_SERVICES+=("postgres")
PG_DEFAULT_PASS=$(openssl rand -base64 36 | tr -d '/+=' | head -c 36)
prompt_value PG_PASSWORD "PostgreSQL password" "$PG_DEFAULT_PASS"
prompt_value PG_DATABASE "PostgreSQL database name" "officer_dev"
cat >> "$COMPOSE_DIR/docker-compose.yaml" <<SVC
postgres:
image: postgres:18.3-alpine
container_name: postgres
restart: unless-stopped
ports:
- "127.0.0.1:5432:5432"
environment:
POSTGRES_PASSWORD: $PG_PASSWORD
PGDATA: /var/lib/postgresql/data
volumes:
- ./postgres_data:/var/lib/postgresql/data
- ./db_dumps:/db_dumps
networks:
- $DOCKER_NETWORK
SVC
;;
3)
COMPOSE_SERVICES+=("mailhog")
MAILHOG_SELECTED=true
cat >> "$COMPOSE_DIR/docker-compose.yaml" <<SVC
mailhog:
image: mailhog/mailhog:latest
container_name: mailhog
restart: unless-stopped
ports:
- "127.0.0.1:1025:1025"
- "127.0.0.1:8025:8025"
networks:
- $DOCKER_NETWORK
SVC
;;
4)
COMPOSE_SERVICES+=("redis")
cat >> "$COMPOSE_DIR/docker-compose.yaml" <<SVC
redis:
image: redis:alpine
container_name: redis
restart: unless-stopped
ports:
- "127.0.0.1:6379:6379"
volumes:
- ./redis_data:/data
networks:
- $DOCKER_NETWORK
SVC
;;
5)
COMPOSE_SERVICES+=("searxng")
mkdir -p "$COMPOSE_DIR/searxng"
SEARXNG_SECRET=$(openssl rand -hex 32)
cat > "$COMPOSE_DIR/searxng/settings.yml" <<SEARXCFG
use_default_settings: true
server:
secret_key: "$SEARXNG_SECRET"
limiter: false
search:
formats:
- html
- json
SEARXCFG
cat >> "$COMPOSE_DIR/docker-compose.yaml" <<SVC
searxng:
image: searxng/searxng:latest
container_name: searxng
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
volumes:
- ./searxng:/etc/searxng
environment:
SEARXNG_BASE_URL: http://localhost:8080/
networks:
- $DOCKER_NETWORK
SVC
;;
esac
done
# Add networks block
cat >> "$COMPOSE_DIR/docker-compose.yaml" <<FOOTER
networks:
$DOCKER_NETWORK:
external: true
FOOTER
# ─── start services ─────────────────────────────────────────────────────────
info "Starting docker compose services..."
(cd "$COMPOSE_DIR" && docker compose up -d) >&2
ok "Docker services: ${COMPOSE_SERVICES[*]}"
# ─── create database if PostgreSQL was selected ─────────────────────────────
if [[ -n "$PG_PASSWORD" ]]; then
info "Waiting for PostgreSQL to be ready..."
for i in $(seq 1 15); do
if docker exec postgres pg_isready -U postgres >/dev/null 2>&1; then
break
fi
sleep 1
done
if docker exec postgres pg_isready -U postgres >/dev/null 2>&1; then
# Create the database if it doesn't exist
docker exec postgres psql -U postgres -tc "SELECT 1 FROM pg_database WHERE datname = '$PG_DATABASE'" | grep -q 1 \
|| docker exec postgres psql -U postgres -c "CREATE DATABASE $PG_DATABASE" >/dev/null 2>&1
ok "Database '$PG_DATABASE' ready"
else
warn "PostgreSQL not ready after 15s — create database manually"
fi
fi
# ─── output parseable values to stdout ───────────────────────────────────────
echo "COMPOSE_DIR=$COMPOSE_DIR"
if [[ -n "$PG_PASSWORD" ]]; then
echo "POSTGRES_URL=postgresql://postgres:${PG_PASSWORD}@127.0.0.1:5432/${PG_DATABASE}"
fi
if [[ "$MAILHOG_SELECTED" == true ]]; then
echo "MAIL_TRANSPORT=smtp://127.0.0.1:1025"
fi