announcer/install.sh
Kent Sigurdsen ecf9d394c1 Intel-Mac: hold onnxruntime tilbake; install.sh avviser ikke-Linux
onnxruntime sluttet å lage hjul for Intel-Mac etter 1.23, så uv run
feilet der. Begrenset til <1.24 bare på darwin/x86_64; Linux og Apple
Silicon får fortsatt nyeste.

install.sh stopper med en forklaring på macOS: Docker der kjører i en VM
uten tilgang til vertens lydserver, så containeren kan ikke spille.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 04:19:27 +02:00

242 lines
10 KiB
Bash
Executable file
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# 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 annen tittel
# BIND_IP=127.0.0.1 ./install.sh bare maskina selv
# ANNOUNCER_MESH_SECRET=… ./install.sh sone i et mesh (samme nøkkel som sentralen)
# NETBIRD_SETUP_KEY=… ./install.sh meld maskina inn i NetBird først
# ANNOUNCER_SENTRAL=http://sentral.skynet.corp:8090 ./install.sh
# sone over NetBird: hvor sentralen er
# ANNOUNCER_NAME=hovedscene ./install.sh sonenavnet slik det vises på sentralen
#
# Soner på samme LAN finner sentralen selv med mDNS. Over NetBird virker ikke
# mDNS, så der settes ANNOUNCER_SENTRAL. Se docs/mesh.md.
#
# Kan kjøres på nytt; da bygges bildet om og containeren startes om. En .env
# som finnes blir ikke overskrevet.
set -euo pipefail
if [[ "$(uname -s)" != Linux ]]; then
echo "install.sh er for Linux (Debian/Ubuntu/Mint). Docker på $(uname -s) kjører" >&2
echo "i en VM uten tilgang til vertens lydserver, så containeren kan ikke spille." >&2
exit 1
fi
if [[ $EUID -eq 0 ]]; then
echo "Kjør som vanlig bruker, ikke root/sudo." >&2
exit 1
fi
REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$REPO_DIR"
step() { printf '\n\033[1m==> %s\033[0m\n' "$*"; }
warn() { printf '\033[33m! %s\033[0m\n' "$*"; }
# --------------------------------------------------------------------------
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 "NetBird"
if [[ -n "${NETBIRD_SETUP_KEY:-}" ]]; then
if ! command -v netbird >/dev/null 2>&1; then
echo "installerer NetBird …"
curl -fsSL https://pkgs.netbird.io/install.sh | sh
fi
# Selvhostet management-server: les URL-en fra en eksisterende config hvis
# NETBIRD_MANAGEMENT_URL ikke er satt, så man slipper å huske den.
MGMT="${NETBIRD_MANAGEMENT_URL:-}"
if [[ -z "$MGMT" && -r /etc/netbird/config.json ]] || sudo test -r /etc/netbird/config.json 2>/dev/null; then
MGMT="${MGMT:-$(sudo python3 -c 'import json;print(json.load(open("/etc/netbird/config.json")).get("ManagementURL") or "")' 2>/dev/null || true)}"
fi
nb_args=(up --setup-key "$NETBIRD_SETUP_KEY" --hostname "announcer-${ANNOUNCER_NAME:-$(hostname -s)}")
[[ -n "$MGMT" ]] && nb_args+=(--management-url "$MGMT")
sudo netbird "${nb_args[@]}"
for _ in $(seq 1 30); do ip link show wt0 >/dev/null 2>&1 && break; sleep 1; done
if ip link show wt0 >/dev/null 2>&1; then
echo "på NetBird som $(sudo netbird status 2>/dev/null | awk -F': ' '/FQDN/{print $2}')"
else
warn "wt0 kom ikke opp innen 30 s. Se: sudo netbird status"
fi
elif command -v netbird >/dev/null 2>&1; then
echo "NetBird er installert fra før; bruker den som den er."
else
echo "ingen NETBIRD_SETUP_KEY – LAN-modus."
fi
step "Docker"
if ! command -v docker >/dev/null 2>&1; then
echo "installerer Docker …"
curl -fsSL https://get.docker.com | sudo sh
fi
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 "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
BIND_IP="0.0.0.0"
fi
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
TZ=$(timedatectl show -p Timezone --value 2>/dev/null || cat /etc/timezone 2>/dev/null || echo Europe/Oslo)
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=
# Mesh. auto = sentral hvis [zones] i data/config.toml, ellers sone hvis
# nøkkelen er satt. Se docs/mesh.md.
ANNOUNCER_MESH_SECRET=${ANNOUNCER_MESH_SECRET:-}
ANNOUNCER_ROLE=${ANNOUNCER_ROLE:-auto}
ANNOUNCER_NAME=${ANNOUNCER_NAME:-$(hostname -s)}
ANNOUNCER_SENTRAL=${ANNOUNCER_SENTRAL:-}
ANNOUNCER_URL=
ANNOUNCER_MDNS=${ANNOUNCER_MDNS:-1}
ANNOUNCER_LOCAL=${ANNOUNCER_LOCAL:-1}
ENV
chmod 600 .env # PIN og nøkler står i klartekst
echo "skrev .env (PUID=$(id -u), BIND_IP=$BIND_IP)"
fi
step "Lyd uten innlogging"
# Lydsocketen ligger i /run/user/<uid>, som bare finnes når brukerens
# systemd-instans kjører – normalt fra grafisk innlogging. En sone i et skap
# (Raspberry Pi uten skjerm) har ingen som logger inn. Linger starter
# brukerens tjenester (PipeWire inkludert) ved boot uansett. Harmløst på en
# laptop, nødvendig på en Pi.
if command -v loginctl >/dev/null 2>&1; then
sudo loginctl enable-linger "$USER" && echo "linger på for $USER"
fi
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
[[ $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"
# Containeren bruker vertens nett, så ufw gjelder porten som for enhver annen
# tjeneste. BIND_IP avgrenser i tillegg hvilke adresser appen svarer på.
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 "announcer via netbird"
sudo ufw delete allow "$PORT/tcp" >/dev/null 2>&1 || true
# mDNS på LAN-et, hvis sentral og soner også skal finne hverandre der
sudo ufw allow 5353/udp comment "mdns" >/dev/null 2>&1 || true
sudo ufw --force enable
echo "ufw aktiv: port $PORT og ssh kun på wt0 (NetBird). Appen er bundet til $BIND_IP."
echo "Vil du også nå sida fra lokalt LAN uten VPN: sett BIND_IP=0.0.0.0 i .env og"
echo " sudo ufw allow from <lan-subnett> to any port $PORT proto tcp"
elif command -v ufw >/dev/null 2>&1 && sudo ufw status 2>/dev/null | grep -q "Status: active"; then
sudo ufw allow "$PORT/tcp" comment "announcer"
sudo ufw allow 5353/udp comment "mdns" >/dev/null 2>&1 || true
echo "ufw aktiv: åpnet $PORT/tcp og mDNS."
else
echo "ufw er ikke aktiv."
[[ "$BIND_IP" == "0.0.0.0" ]] && warn "Sida er åpen på alle nett maskina er på."
fi
# --------------------------------------------------------------------------
IP="$(ip -4 addr show scope global 2>/dev/null | awk '/inet /{sub(/\/.*/,"",$2);print $2;exit}')"
step "Ferdig"
docker compose ps
cat <<MSG
Åpne http://${BIND_IP/0.0.0.0/${IP:-<denne-maskinens-ip>}}:$PORT
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:
- 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
Mesh (flere anlegg): sett [zones] i data/config.toml på sentralen og samme
ANNOUNCER_MESH_SECRET i .env på sentralen og hver sone. Se docs/mesh.md.
På en Pi med både HDMI og jack: velg lydutgang med sudo raspi-config
(System Options -> Audio), ellers går lyden til skjermen som ikke er der.
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