Docker er driftsformen, ikke bare en testrigg
Announcer skal kjøre standalone på klienten som er koblet til anlegget, i Docker – aldri på utviklingsmaskina. install.sh installerte nativt med apt og uv, og det passer ikke lenger. - install.sh er nå en Docker-bootstrap: installerer Docker om den mangler, lager .env med riktig bruker-ID og portbinding, oppretter data/, bygger, starter, og setter ufw. Den rydder også bort gamle native enheter, både announcer.service og sg-announcer.service. - Dockerfile kopierer koden inn istedenfor å låne den fra et bind-mount, så det som kjører i drift er uavhengig av hva som ligger i mappa. - Alt som skal overleve en ny container ligger i data/ på verten. Bind-mount og ikke navngitt volum, med vilje: da kan man lese data/logs/announcements.jsonl og slippe egne jingler i data/chimes/ uten å gå inn i containeren, og eierskapet følger brukeren istedenfor root. - Testriggen er flyttet til compose.test.yaml, med eget prosjektnavn, så den ikke kan forveksles med eller forstyrre drift. Lyden ut av containeren krever tre ting, og de er dokumentert i AGENTS.md: hele XDG_RUNTIME_DIR montert inn (ikke bare pipewire-socketen – PulseAudio trenger pulse/native og en cookie i samme mappe, og da virker begge lydservere), user: PUID:PGID lik eieren av socketen, og en innlogget grafisk økt. Containeren starter ved boot, altså før innlogging, så avspilling feiler til noen logger inn – det retter seg selv, siden player_cmd() slås opp per melding. Verifisert: lyden spiller fra driftscontaineren. Brannmuren er den viktige endringen fra nativ tid. Dockers publiserte porter settes opp i nat-tabellen, før ufws regler, så en ufw-regel på porten gjør ingenting. Tilgangen styres nå av BIND_IP i .env, og install.sh setter den til NetBird-adressen når wt0 finnes. ufw settes fortsatt for ssh, men er ikke involvert i tilgangen til announcer. Testriggen bindes nå til 127.0.0.1. Den sto med PIN 1234 publisert på 0.0.0.0, og gikk altså forbi ufw ut på LAN-et. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6aaa98d8b1
commit
6c1253f54c
7 changed files with 372 additions and 177 deletions
226
install.sh
226
install.sh
|
|
@ -1,15 +1,20 @@
|
|||
#!/usr/bin/env bash
|
||||
# Installerer Announcer på en Debian/Ubuntu-maskin (testet mot Linux Mint).
|
||||
# Setter opp Announcer på klienten som er koblet til høyttaleranlegget.
|
||||
# Debian/Ubuntu/Mint. Appen kjører i Docker; dette skriptet ordner Docker,
|
||||
# .env, datamapper, brannmur og autostart.
|
||||
#
|
||||
# git clone https://git.1x1.no/kent/announcer.git
|
||||
# cd announcer && ./install.sh
|
||||
#
|
||||
# Kjør som vanlig bruker (ikke sudo); skriptet spør om passord der det trengs.
|
||||
# Valgfritt: ANNOUNCER_PIN=1234 ./install.sh -> sida krever PIN
|
||||
# ANNOUNCER_PORT=8090 ./install.sh -> annen port
|
||||
# ANNOUNCER_TITLE="Utrop" ./install.sh
|
||||
# Resten av konfigurasjonen hører hjemme i config.toml, se config.example.toml.
|
||||
# Kan kjøres på nytt; da oppdateres avhengigheter og tjenesten startes om.
|
||||
# Valgfritt:
|
||||
# ANNOUNCER_PIN=1234 ./install.sh sida krever PIN
|
||||
# ANNOUNCER_PORT=8090 ./install.sh annen port
|
||||
# ANNOUNCER_TITLE="Utrop" ./install.sh annen tittel
|
||||
# BIND_IP=127.0.0.1 ./install.sh bare maskina selv
|
||||
#
|
||||
# Kan kjøres på nytt; da bygges bildet om og containeren startes om. En .env
|
||||
# som finnes blir ikke overskrevet.
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $EUID -eq 0 ]]; then
|
||||
|
|
@ -17,129 +22,150 @@ if [[ $EUID -eq 0 ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
APP="announcer"
|
||||
OLD_APP="sg-announcer" # het dette før; enheten ryddes bort nedenfor
|
||||
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PORT="${ANNOUNCER_PORT:-8090}"
|
||||
PIN="${ANNOUNCER_PIN:-}"
|
||||
TITLE="${ANNOUNCER_TITLE:-}"
|
||||
UNIT_DIR="$HOME/.config/systemd/user"
|
||||
UNIT="$UNIT_DIR/$APP.service"
|
||||
cd "$REPO_DIR"
|
||||
|
||||
step() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
|
||||
warn() { printf '\033[33m! %s\033[0m\n' "$*"; }
|
||||
|
||||
step "Systempakker (apt)"
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -y -qq git curl ca-certificates ffmpeg pulseaudio-utils
|
||||
# pw-play finnes i pipewire-bin på Mint 22 / Ubuntu 24.04. På eldre (PulseAudio)
|
||||
# faller appen tilbake til paplay, så dette er ikke kritisk.
|
||||
sudo apt-get install -y -qq pipewire-bin 2>/dev/null || true
|
||||
# --------------------------------------------------------------------------
|
||||
step "Rydder bort eventuell nativ installasjon"
|
||||
# Tidligere versjoner kjørte appen direkte som en systemd-brukerenhet. To
|
||||
# instanser ville slåss om porten, så den gamle skal bort.
|
||||
for old in sg-announcer announcer; do
|
||||
unit="$HOME/.config/systemd/user/$old.service"
|
||||
if [[ -f "$unit" ]]; then
|
||||
systemctl --user disable --now "$old" >/dev/null 2>&1 || true
|
||||
rm -f "$unit"
|
||||
echo "fjernet $old.service (nativ)"
|
||||
fi
|
||||
done
|
||||
systemctl --user daemon-reload 2>/dev/null || true
|
||||
|
||||
step "uv (Python-pakkehåndterer)"
|
||||
if command -v uv >/dev/null 2>&1; then
|
||||
UV="$(command -v uv)"
|
||||
elif [[ -x "$HOME/.local/bin/uv" ]]; then
|
||||
UV="$HOME/.local/bin/uv"
|
||||
else
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
UV="$HOME/.local/bin/uv"
|
||||
# --------------------------------------------------------------------------
|
||||
step "Docker"
|
||||
if ! command -v docker >/dev/null 2>&1; then
|
||||
echo "installerer Docker …"
|
||||
curl -fsSL https://get.docker.com | sudo sh
|
||||
fi
|
||||
echo "uv: $UV ($("$UV" --version))"
|
||||
if ! id -nG "$USER" | tr ' ' '\n' | grep -qx docker; then
|
||||
sudo usermod -aG docker "$USER"
|
||||
warn "La $USER i docker-gruppa. Du må logge ut og inn igjen,"
|
||||
warn "og så kjøre ./install.sh på nytt. Stopper her."
|
||||
exit 0
|
||||
fi
|
||||
if ! docker info >/dev/null 2>&1; then
|
||||
warn "Docker svarer ikke. Er tjenesten startet? sudo systemctl enable --now docker"
|
||||
exit 1
|
||||
fi
|
||||
docker --version
|
||||
docker compose version | head -1
|
||||
|
||||
step "Python 3.13 og avhengigheter"
|
||||
cd "$REPO_DIR"
|
||||
"$UV" sync
|
||||
mkdir -p cache chimes voices logs instance
|
||||
|
||||
step "Laster ned Piper-stemmene (lokal reserve, ca. 600 MB, kun første gang)"
|
||||
"$UV" run python -c "import logging, announcer; logging.basicConfig(level=logging.INFO, format='%(message)s'); announcer.ensure_voices()"
|
||||
|
||||
step "Rydder bort gammel tjeneste"
|
||||
# Tjenesten het sg-announcer før. Uten dette slåss to enheter om porten.
|
||||
if [[ -f "$UNIT_DIR/$OLD_APP.service" ]]; then
|
||||
systemctl --user disable --now "$OLD_APP" >/dev/null 2>&1 || true
|
||||
rm -f "$UNIT_DIR/$OLD_APP.service"
|
||||
echo "fjernet $OLD_APP.service"
|
||||
# --------------------------------------------------------------------------
|
||||
step "Oppsett (.env)"
|
||||
# BIND_IP er det som avgjør hvem som når sida. ufw gjør det ikke: Dockers
|
||||
# publiserte porter settes opp i nat-tabellen og går forbi ufws regler.
|
||||
NB_IP="$(ip -4 addr show wt0 2>/dev/null | awk '/inet /{sub(/\/.*/,"",$2);print $2;exit}')"
|
||||
if [[ -n "${BIND_IP:-}" ]]; then
|
||||
:
|
||||
elif [[ -n "$NB_IP" ]]; then
|
||||
BIND_IP="$NB_IP"
|
||||
echo "NetBird funnet – binder til $BIND_IP, så bare VPN-et når sida."
|
||||
else
|
||||
echo "ingenting å rydde."
|
||||
BIND_IP="0.0.0.0"
|
||||
fi
|
||||
|
||||
step "systemd-brukertjeneste"
|
||||
mkdir -p "$UNIT_DIR"
|
||||
{
|
||||
cat <<UNIT
|
||||
[Unit]
|
||||
Description=Announcer – TTS-utrop på høyttaleranlegget
|
||||
After=pipewire.service pulseaudio.service
|
||||
if [[ -f .env ]]; then
|
||||
echo ".env finnes allerede, lar den stå. Slett den for å lage en ny."
|
||||
else
|
||||
cat > .env <<ENV
|
||||
# Laget av install.sh $(date +%Y-%m-%d). Se .env.example for forklaringer.
|
||||
PUID=$(id -u)
|
||||
PGID=$(id -g)
|
||||
XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR:-/run/user/$(id -u)}
|
||||
BIND_IP=$BIND_IP
|
||||
ANNOUNCER_PORT=${ANNOUNCER_PORT:-8090}
|
||||
ANNOUNCER_SINK=${ANNOUNCER_SINK:-}
|
||||
ANNOUNCER_TITLE=${ANNOUNCER_TITLE:-}
|
||||
ANNOUNCER_PUBLIC_URL=${ANNOUNCER_PUBLIC_URL:-}
|
||||
ANNOUNCER_PIN=${ANNOUNCER_PIN:-}
|
||||
ANNOUNCER_JWT_SECRET=${ANNOUNCER_JWT_SECRET:-}
|
||||
ANNOUNCER_OIDC_CLIENT_SECRET=${ANNOUNCER_OIDC_CLIENT_SECRET:-}
|
||||
ANNOUNCER_SECRET_KEY=
|
||||
ENV
|
||||
chmod 600 .env # PIN og nøkler står i klartekst
|
||||
echo "skrev .env (PUID=$(id -u), BIND_IP=$BIND_IP)"
|
||||
fi
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=$REPO_DIR
|
||||
ExecStart=$UV run announcer.py
|
||||
Environment=ANNOUNCER_PORT=$PORT
|
||||
UNIT
|
||||
if [[ -n "$PIN" ]]; then
|
||||
echo "Environment=ANNOUNCER_PIN=$PIN"
|
||||
else
|
||||
echo "# Environment=ANNOUNCER_PIN=1234"
|
||||
step "Datamapper"
|
||||
mkdir -p data/{voices,cache,chimes,logs,instance}
|
||||
[[ -f data/chimes/README.txt ]] || cp -n chimes/README.txt data/chimes/ 2>/dev/null || true
|
||||
echo "data/ – stemmer, buffer, egne jingler, logg og sesjonsnøkkel"
|
||||
if [[ ! -f data/config.toml ]]; then
|
||||
echo "ingen data/config.toml: sida er åpen for alle som når den."
|
||||
echo " vil du ha innlogging: cp config.example.toml data/config.toml"
|
||||
fi
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
step "Bygger og starter"
|
||||
docker compose up -d --build
|
||||
|
||||
step "Venter på at tjenesten svarer"
|
||||
# Første oppstart laster ned Piper-stemmene (~670 MB) før den svarer.
|
||||
PORT="$(grep -E '^ANNOUNCER_PORT=' .env | cut -d= -f2)"
|
||||
PROBE_IP="$BIND_IP"; [[ "$PROBE_IP" == "0.0.0.0" ]] && PROBE_IP=127.0.0.1
|
||||
for i in $(seq 1 60); do
|
||||
if curl -fsS -o /dev/null "http://$PROBE_IP:$PORT/api/config" 2>/dev/null; then
|
||||
echo "svarer etter ~$((i*5)) s"
|
||||
break
|
||||
fi
|
||||
if [[ -n "$TITLE" ]]; then
|
||||
echo "Environment=ANNOUNCER_TITLE=$TITLE"
|
||||
else
|
||||
echo "# Environment=ANNOUNCER_TITLE=Utrop"
|
||||
fi
|
||||
cat <<'UNIT'
|
||||
# Environment=ANNOUNCER_SINK=alsa_output.xxx
|
||||
# Hemmeligheter hører hjemme her og ikke i config.toml:
|
||||
# Environment=ANNOUNCER_JWT_SECRET=...
|
||||
# Environment=ANNOUNCER_OIDC_CLIENT_SECRET=...
|
||||
Restart=on-failure
|
||||
RestartSec=3
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
UNIT
|
||||
} > "$UNIT"
|
||||
chmod 600 "$UNIT" # PIN og nøkler står i klartekst her
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now "$APP"
|
||||
systemctl --user restart "$APP"
|
||||
[[ $i -eq 1 ]] && echo "laster ned stemmer, dette tar noen minutter første gang …"
|
||||
[[ $i -eq 60 ]] && warn "svarte ikke innen 5 min. Se: docker compose logs -f"
|
||||
sleep 5
|
||||
done
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
step "Brannmur"
|
||||
if ip link show wt0 >/dev/null 2>&1; then
|
||||
# NetBird er installert: slipp inn kun via VPN-grensesnittet, blokker alt annet.
|
||||
# Merk at ufw *ikke* styrer tilgangen til announcer – det gjør BIND_IP i
|
||||
# .env. ufw settes her bare for maskinens øvrige porter.
|
||||
if [[ -n "$NB_IP" ]]; then
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
sudo ufw allow in on wt0 to any port 22 proto tcp comment "ssh via netbird"
|
||||
sudo ufw allow in on wt0 to any port 22022 proto tcp comment "netbird ssh-server"
|
||||
sudo ufw allow in on wt0 to any port "$PORT" proto tcp comment "$APP via netbird"
|
||||
sudo ufw delete allow "$PORT/tcp" >/dev/null 2>&1 || true
|
||||
sudo ufw --force enable
|
||||
echo "ufw aktiv: port $PORT, 22 og 22022 er åpne kun på wt0 (NetBird)."
|
||||
echo "Vil du også nå sida fra lokalt LAN uten VPN:"
|
||||
echo " sudo ufw allow from <lan-subnett> to any port $PORT proto tcp"
|
||||
elif sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
||||
sudo ufw allow "$PORT/tcp" comment "$APP"
|
||||
echo "ufw aktiv: ssh kun på wt0."
|
||||
echo "announcer er bundet til $BIND_IP:$PORT – det er NetBird-adressen,"
|
||||
echo "så sida nås bare over VPN-et. ufw er ikke involvert i det."
|
||||
elif command -v ufw >/dev/null 2>&1 && sudo ufw status 2>/dev/null | grep -q "Status: active"; then
|
||||
warn "ufw er aktiv, men styrer ikke Docker-porter."
|
||||
warn "Vil du begrense tilgangen: sett BIND_IP i .env og kjør docker compose up -d."
|
||||
else
|
||||
echo "ufw er ikke aktiv, ingen regel nødvendig."
|
||||
echo "ufw er ikke aktiv."
|
||||
[[ "$BIND_IP" == "0.0.0.0" ]] && warn "Sida er åpen på alle nett maskina er på."
|
||||
fi
|
||||
|
||||
sleep 2
|
||||
IP="$(hostname -I 2>/dev/null | awk '{print $1}')"
|
||||
# --------------------------------------------------------------------------
|
||||
IP="$(ip -4 addr show scope global 2>/dev/null | awk '/inet /{sub(/\/.*/,"",$2);print $2;exit}')"
|
||||
step "Ferdig"
|
||||
systemctl --user --no-pager status "$APP" | head -5 || true
|
||||
docker compose ps
|
||||
cat <<MSG
|
||||
|
||||
Åpne http://${IP:-<denne-maskinens-ip>}:$PORT fra hva som helst på nettet.
|
||||
Åpne http://${BIND_IP/0.0.0.0/${IP:-<denne-maskinens-ip>}}:$PORT
|
||||
|
||||
Uten config.toml er sida åpen for alle som når porten. Vil du ha innlogging:
|
||||
cp config.example.toml config.toml og les kommentarene der.
|
||||
Mot crew-panelet: docs/crew-integration.md
|
||||
Lyden spilles i din innloggede økt, så maskinen må være logget inn grafisk.
|
||||
Containeren starter ved boot, altså før innlogging – da feiler avspilling,
|
||||
men den retter seg selv ved første utrop etter at du er logget inn.
|
||||
|
||||
Tips for en laptop på arrangementet:
|
||||
- Lyden spilles i din innloggede økt, så maskinen må være logget inn.
|
||||
Slå på automatisk innlogging (Innstillinger -> Påloggingsvindu).
|
||||
- Slå på automatisk innlogging (Innstillinger -> Påloggingsvindu).
|
||||
- Hindre at den sover med lokket igjen:
|
||||
sudo sed -i 's/^#\?HandleLidSwitch=.*/HandleLidSwitch=ignore/' /etc/systemd/logind.conf
|
||||
sudo systemctl restart systemd-logind
|
||||
- Logg: journalctl --user -u $APP -f
|
||||
|
||||
Drift:
|
||||
docker compose logs -f følg loggen
|
||||
docker compose restart etter endringer i data/config.toml
|
||||
docker compose up -d --build etter endringer i koden
|
||||
cat data/logs/announcements.jsonl hvem som sendte hva
|
||||
MSG
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue