269 lines
9.8 KiB
Bash
Executable file
269 lines
9.8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
TARGET_HOST=${1:-pi@noisebell-pi.local}
|
|
DEPLOY_HOSTNAME=${DEPLOY_HOSTNAME:-noisebell-pi}
|
|
HOME_ASSISTANT_BASE_URL=${HOME_ASSISTANT_BASE_URL:-http://10.21.0.43:8123}
|
|
SCRIPT_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
|
REPO_ROOT=$(cd -- "$SCRIPT_DIR/.." && pwd)
|
|
RELEASE_ID=${RELEASE_ID:-$(date +%Y%m%d-%H%M%S)}
|
|
REMOTE_RELEASE_DIR=${REMOTE_RELEASE_DIR:-/opt/noisebell/releases/$RELEASE_ID}
|
|
REMOTE_CURRENT_LINK=${REMOTE_CURRENT_LINK:-/opt/noisebell/current}
|
|
REMOTE_TMP_DIR=${REMOTE_TMP_DIR:-/home/pi/noisebell-deploy-tmp}
|
|
TMP_DIR=$(mktemp -d)
|
|
|
|
cleanup() {
|
|
rm -rf "$TMP_DIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
SSH_OPTS=(
|
|
-o StrictHostKeyChecking=accept-new
|
|
)
|
|
|
|
echo "Building static aarch64 Noisebell binary locally..."
|
|
PACKAGE_PATH=$(nix build .#packages.aarch64-linux.noisebell-static --print-out-paths --no-link)
|
|
BIN_PATH="$PACKAGE_PATH/bin/noisebell"
|
|
RELAY_PACKAGE_PATH=$(nix build .#packages.aarch64-linux.noisebell-relay-static --print-out-paths --no-link)
|
|
RELAY_BIN_PATH="$RELAY_PACKAGE_PATH/bin/noisebell-relay"
|
|
|
|
if [[ ! -x "$BIN_PATH" ]]; then
|
|
echo "built binary not found: $BIN_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if [[ ! -x "$RELAY_BIN_PATH" ]]; then
|
|
echo "built relay binary not found: $RELAY_BIN_PATH" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if ! command -v agenix >/dev/null 2>&1; then
|
|
echo "agenix is required in your shell to decrypt secrets locally" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Decrypting Pi secrets locally..."
|
|
(
|
|
cd "$REPO_ROOT/secrets"
|
|
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d pi-to-cache-key.age > "$TMP_DIR/pi-to-cache-key"
|
|
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d cache-to-pi-key.age > "$TMP_DIR/cache-to-pi-key"
|
|
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d relay-webhook-secret.age > "$TMP_DIR/relay-webhook-secret"
|
|
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d homeassistant-webhook-id.age > "$TMP_DIR/homeassistant-webhook-id"
|
|
RULES="$REPO_ROOT/secrets/secrets.nix" agenix -d tailscale-auth-key.age > "$TMP_DIR/tailscale-auth-key"
|
|
)
|
|
chmod 600 "$TMP_DIR"/*
|
|
|
|
echo "Preparing remote directories on $TARGET_HOST..."
|
|
ssh "${SSH_OPTS[@]}" "$TARGET_HOST" "mkdir -p '$REMOTE_TMP_DIR' && rm -f '$REMOTE_TMP_DIR/noisebell' '$REMOTE_TMP_DIR/noisebell-relay' '$REMOTE_TMP_DIR/pi-to-cache-key' '$REMOTE_TMP_DIR/cache-to-pi-key' '$REMOTE_TMP_DIR/relay-webhook-secret' '$REMOTE_TMP_DIR/homeassistant-webhook-id' '$REMOTE_TMP_DIR/tailscale-auth-key' && sudo mkdir -p '$REMOTE_RELEASE_DIR' /etc/noisebell /opt/noisebell/releases /var/lib/noisebell"
|
|
|
|
echo "Uploading binary and secret files..."
|
|
scp "${SSH_OPTS[@]}" "$BIN_PATH" "$TARGET_HOST:$REMOTE_TMP_DIR/noisebell"
|
|
scp "${SSH_OPTS[@]}" "$RELAY_BIN_PATH" "$TARGET_HOST:$REMOTE_TMP_DIR/noisebell-relay"
|
|
scp "${SSH_OPTS[@]}" "$TMP_DIR/pi-to-cache-key" "$TARGET_HOST:$REMOTE_TMP_DIR/pi-to-cache-key"
|
|
scp "${SSH_OPTS[@]}" "$TMP_DIR/cache-to-pi-key" "$TARGET_HOST:$REMOTE_TMP_DIR/cache-to-pi-key"
|
|
scp "${SSH_OPTS[@]}" "$TMP_DIR/relay-webhook-secret" "$TARGET_HOST:$REMOTE_TMP_DIR/relay-webhook-secret"
|
|
scp "${SSH_OPTS[@]}" "$TMP_DIR/homeassistant-webhook-id" "$TARGET_HOST:$REMOTE_TMP_DIR/homeassistant-webhook-id"
|
|
scp "${SSH_OPTS[@]}" "$TMP_DIR/tailscale-auth-key" "$TARGET_HOST:$REMOTE_TMP_DIR/tailscale-auth-key"
|
|
|
|
echo "Installing service and Tailscale on $TARGET_HOST..."
|
|
ssh "${SSH_OPTS[@]}" "$TARGET_HOST" "DEPLOY_HOSTNAME='$DEPLOY_HOSTNAME' HOME_ASSISTANT_BASE_URL='$HOME_ASSISTANT_BASE_URL' REMOTE_RELEASE_DIR='$REMOTE_RELEASE_DIR' REMOTE_CURRENT_LINK='$REMOTE_CURRENT_LINK' REMOTE_TMP_DIR='$REMOTE_TMP_DIR' bash -s" <<'EOF'
|
|
set -euo pipefail
|
|
|
|
sudo apt-get update
|
|
sudo apt-get install -y curl jq rsync avahi-daemon prometheus-node-exporter
|
|
|
|
sudo hostnamectl set-hostname "$DEPLOY_HOSTNAME"
|
|
sudo tee /etc/hostname >/dev/null <<<"$DEPLOY_HOSTNAME"
|
|
sudo tee /etc/hosts >/dev/null <<HOSTSEOF
|
|
127.0.0.1 localhost
|
|
::1 localhost ip6-localhost ip6-loopback
|
|
ff02::1 ip6-allnodes
|
|
ff02::2 ip6-allrouters
|
|
|
|
127.0.1.1 $DEPLOY_HOSTNAME
|
|
HOSTSEOF
|
|
|
|
if ! command -v tailscale >/dev/null 2>&1; then
|
|
curl -fsSL https://tailscale.com/install.sh | sh
|
|
fi
|
|
sudo mkdir -p /etc/systemd/journald.conf.d /var/log/journal
|
|
sudo tee /etc/systemd/journald.conf.d/noisebell-persistent.conf >/dev/null <<'JOURNALCONF'
|
|
[Journal]
|
|
Storage=persistent
|
|
SystemMaxUse=200M
|
|
MaxRetentionSec=30day
|
|
JOURNALCONF
|
|
sudo systemctl restart systemd-journald
|
|
|
|
sudo systemctl enable --now ssh avahi-daemon tailscaled prometheus-node-exporter
|
|
|
|
sudo install -m 755 "$REMOTE_TMP_DIR/noisebell" "$REMOTE_RELEASE_DIR/noisebell"
|
|
sudo install -m 755 "$REMOTE_TMP_DIR/noisebell-relay" "$REMOTE_RELEASE_DIR/noisebell-relay"
|
|
sudo mv "$REMOTE_TMP_DIR/pi-to-cache-key" /etc/noisebell/pi-to-cache-key
|
|
sudo mv "$REMOTE_TMP_DIR/cache-to-pi-key" /etc/noisebell/cache-to-pi-key
|
|
sudo mv "$REMOTE_TMP_DIR/relay-webhook-secret" /etc/noisebell/relay-webhook-secret
|
|
sudo mv "$REMOTE_TMP_DIR/homeassistant-webhook-id" /etc/noisebell/homeassistant-webhook-id
|
|
sudo mv "$REMOTE_TMP_DIR/tailscale-auth-key" /etc/noisebell/tailscale-auth-key
|
|
sudo chown root:root /etc/noisebell/pi-to-cache-key /etc/noisebell/cache-to-pi-key /etc/noisebell/relay-webhook-secret /etc/noisebell/homeassistant-webhook-id /etc/noisebell/tailscale-auth-key
|
|
sudo chmod 600 /etc/noisebell/pi-to-cache-key /etc/noisebell/cache-to-pi-key /etc/noisebell/relay-webhook-secret /etc/noisebell/homeassistant-webhook-id /etc/noisebell/tailscale-auth-key
|
|
|
|
sudo tee /etc/noisebell/noisebell.env >/dev/null <<'ENVEOF'
|
|
NOISEBELL_GPIO_PIN=17
|
|
NOISEBELL_DEBOUNCE_MS=50
|
|
NOISEBELL_PORT=80
|
|
NOISEBELL_RETRY_ATTEMPTS=3
|
|
NOISEBELL_RETRY_BASE_DELAY_SECS=1
|
|
NOISEBELL_HTTP_TIMEOUT_SECS=10
|
|
NOISEBELL_ENDPOINT_URL=https://noisebell.extremist.software/webhook
|
|
NOISEBELL_BIND_ADDRESS=0.0.0.0
|
|
NOISEBELL_ACTIVE_LOW=true
|
|
RUST_LOG=info
|
|
ENVEOF
|
|
sudo chmod 600 /etc/noisebell/noisebell.env
|
|
|
|
sudo tee /etc/noisebell/noisebell-relay.env >/dev/null <<'ENVEOF'
|
|
NOISEBELL_RELAY_PORT=8090
|
|
NOISEBELL_RELAY_BIND_ADDRESS=0.0.0.0
|
|
NOISEBELL_RELAY_TARGET_BASE_URL=$HOME_ASSISTANT_BASE_URL
|
|
NOISEBELL_RELAY_RETRY_ATTEMPTS=3
|
|
NOISEBELL_RELAY_RETRY_BASE_DELAY_SECS=1
|
|
NOISEBELL_RELAY_HTTP_TIMEOUT_SECS=10
|
|
RUST_LOG=info
|
|
ENVEOF
|
|
sudo chmod 600 /etc/noisebell/noisebell-relay.env
|
|
|
|
sudo tee /etc/systemd/system/noisebell.service >/dev/null <<'UNITEOF'
|
|
[Unit]
|
|
Description=Noisebell GPIO door monitor
|
|
After=network-online.target tailscaled.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=notify
|
|
NotifyAccess=all
|
|
EnvironmentFile=/etc/noisebell/noisebell.env
|
|
ExecStart=/bin/bash -lc 'export NOISEBELL_API_KEY="$$(cat /etc/noisebell/pi-to-cache-key)"; export NOISEBELL_INBOUND_API_KEY="$$(cat /etc/noisebell/cache-to-pi-key)"; exec /opt/noisebell/current/noisebell'
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
WatchdogSec=30
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNITEOF
|
|
|
|
sudo tee /etc/systemd/system/noisebell-relay.service >/dev/null <<'UNITEOF'
|
|
[Unit]
|
|
Description=Noisebell relay webhook bridge
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
EnvironmentFile=/etc/noisebell/noisebell-relay.env
|
|
ExecStart=/bin/bash -lc 'export NOISEBELL_RELAY_INBOUND_API_KEY="$$(cat /etc/noisebell/relay-webhook-secret)"; export NOISEBELL_RELAY_TARGET_WEBHOOK_ID="$$(cat /etc/noisebell/homeassistant-webhook-id)"; exec /opt/noisebell/current/noisebell-relay'
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNITEOF
|
|
|
|
sudo tee /usr/local/bin/noisebell-loki-journal >/dev/null <<'SCRIPTEOF'
|
|
#!/usr/bin/env bash
|
|
set -uo pipefail
|
|
|
|
LOKI_URL=${LOKI_URL:-http://noisebell-do:3100/loki/api/v1/push}
|
|
HOST_LABEL=${HOST_LABEL:-$(hostname)}
|
|
CURSOR_DIR=/var/lib/noisebell-loki-journal
|
|
CURSOR_FILE=$CURSOR_DIR/cursor
|
|
|
|
mkdir -p "$CURSOR_DIR"
|
|
|
|
while true; do
|
|
args=(--output=json --no-pager --lines=100)
|
|
if [ -s "$CURSOR_FILE" ]; then
|
|
args+=(--after-cursor="$(cat "$CURSOR_FILE")")
|
|
else
|
|
args+=(--since=-5min)
|
|
fi
|
|
|
|
saw_entry=0
|
|
hit_error=0
|
|
|
|
while IFS= read -r entry; do
|
|
saw_entry=1
|
|
cursor=$(jq -r '.__CURSOR // empty' <<<"$entry")
|
|
timestamp=$(jq -r '.__REALTIME_TIMESTAMP // empty' <<<"$entry")
|
|
if [ -n "$timestamp" ] && [ "$timestamp" != "null" ]; then
|
|
timestamp="${timestamp}000"
|
|
else
|
|
timestamp=$(date +%s%N)
|
|
fi
|
|
|
|
unit=$(jq -r '._SYSTEMD_UNIT // .SYSLOG_IDENTIFIER // "journal"' <<<"$entry")
|
|
message=$(jq -r '.MESSAGE // .' <<<"$entry")
|
|
|
|
payload=$(jq -cn \
|
|
--arg host "$HOST_LABEL" \
|
|
--arg unit "$unit" \
|
|
--arg ts "$timestamp" \
|
|
--arg line "$message" \
|
|
'{streams:[{stream:{job:"journal",host:$host,unit:$unit},values:[[$ts,$line]]}]}')
|
|
|
|
if curl -fsS --max-time 5 \
|
|
-H 'Content-Type: application/json' \
|
|
-X POST \
|
|
--data "$payload" \
|
|
"$LOKI_URL" >/dev/null 2>&1; then
|
|
if [ -n "$cursor" ]; then
|
|
printf '%s\n' "$cursor" > "$CURSOR_FILE"
|
|
fi
|
|
else
|
|
hit_error=1
|
|
break
|
|
fi
|
|
done < <(journalctl "${args[@]}" 2>/dev/null)
|
|
|
|
if [ "$hit_error" -eq 1 ] || [ "$saw_entry" -eq 0 ]; then
|
|
sleep 5
|
|
fi
|
|
done
|
|
SCRIPTEOF
|
|
sudo chmod 755 /usr/local/bin/noisebell-loki-journal
|
|
|
|
sudo tee /etc/systemd/system/noisebell-loki-journal.service >/dev/null <<'UNITEOF'
|
|
[Unit]
|
|
Description=Noisebell journal shipper to Loki
|
|
After=network-online.target tailscaled.service
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
Environment=LOKI_URL=http://noisebell-do:3100/loki/api/v1/push
|
|
Environment=HOST_LABEL=noisebell-pi
|
|
ExecStart=/usr/local/bin/noisebell-loki-journal
|
|
Restart=always
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNITEOF
|
|
|
|
sudo ln -sfn "$REMOTE_RELEASE_DIR" "$REMOTE_CURRENT_LINK"
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable noisebell.service
|
|
sudo systemctl enable noisebell-relay.service
|
|
sudo systemctl enable noisebell-loki-journal.service
|
|
sudo systemctl restart noisebell.service
|
|
sudo systemctl restart noisebell-relay.service
|
|
sudo systemctl restart noisebell-loki-journal.service
|
|
sudo systemctl restart avahi-daemon
|
|
|
|
sudo tailscale up --auth-key="$(sudo cat /etc/noisebell/tailscale-auth-key)" --hostname=noisebell-pi || true
|
|
|
|
rmdir "$REMOTE_TMP_DIR" 2>/dev/null || true
|
|
|
|
echo "Noisebell deployed on Raspberry Pi OS."
|
|
EOF
|
|
|
|
echo "Done."
|