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>
242 lines
10 KiB
Bash
Executable file
242 lines
10 KiB
Bash
Executable file
#!/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
|