feat: add Cloudflare tunnel hosting
This commit is contained in:
parent
e6c1b82679
commit
23e087ae4b
15 changed files with 839 additions and 30 deletions
|
|
@ -4,6 +4,7 @@ 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}
|
||||
NOISEBELL_CACHE_WEBHOOK_URL=${NOISEBELL_CACHE_WEBHOOK_URL:-http://noisebell-do:3000/webhook}
|
||||
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)}
|
||||
|
|
@ -66,11 +67,11 @@ scp "${SSH_OPTS[@]}" "$TMP_DIR/homeassistant-webhook-id" "$TARGET_HOST:$REMOTE_T
|
|||
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'
|
||||
ssh "${SSH_OPTS[@]}" "$TARGET_HOST" "DEPLOY_HOSTNAME='$DEPLOY_HOSTNAME' HOME_ASSISTANT_BASE_URL='$HOME_ASSISTANT_BASE_URL' NOISEBELL_CACHE_WEBHOOK_URL='$NOISEBELL_CACHE_WEBHOOK_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 apt-get install -y curl jq rsync avahi-daemon prometheus-node-exporter iptables
|
||||
|
||||
sudo hostnamectl set-hostname "$DEPLOY_HOSTNAME"
|
||||
sudo tee /etc/hostname >/dev/null <<<"$DEPLOY_HOSTNAME"
|
||||
|
|
@ -107,21 +108,29 @@ 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'
|
||||
if ! sudo tailscale up --auth-key="$(sudo cat /etc/noisebell/tailscale-auth-key)" --hostname=noisebell-pi; then
|
||||
sudo tailscale up --hostname=noisebell-pi
|
||||
fi
|
||||
if ! ip link show tailscale0 >/dev/null 2>&1; then
|
||||
echo "tailscale0 is not available; refusing to apply Tailscale-only firewall" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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_ENDPOINT_URL=$NOISEBELL_CACHE_WEBHOOK_URL
|
||||
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'
|
||||
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
|
||||
|
|
@ -168,6 +177,61 @@ RestartSec=5
|
|||
WantedBy=multi-user.target
|
||||
UNITEOF
|
||||
|
||||
sudo tee /usr/local/sbin/noisebell-tailscale-only-firewall >/dev/null <<'FIREWALLEOF'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
restricted_tcp_ports=(22 80 8090 9100)
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
exec sudo "$0" "$@"
|
||||
fi
|
||||
|
||||
if ! command -v iptables >/dev/null 2>&1; then
|
||||
echo "iptables is required for the Noisebell Tailscale-only firewall" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
apply_rules() {
|
||||
local cmd=$1
|
||||
|
||||
while "$cmd" -w -D INPUT -j NOISEBELL_TS_ONLY 2>/dev/null; do :; done
|
||||
"$cmd" -w -F NOISEBELL_TS_ONLY 2>/dev/null || true
|
||||
"$cmd" -w -X NOISEBELL_TS_ONLY 2>/dev/null || true
|
||||
"$cmd" -w -N NOISEBELL_TS_ONLY
|
||||
"$cmd" -w -A NOISEBELL_TS_ONLY -m conntrack --ctstate ESTABLISHED,RELATED -j RETURN
|
||||
"$cmd" -w -A NOISEBELL_TS_ONLY -i lo -j RETURN
|
||||
"$cmd" -w -A NOISEBELL_TS_ONLY -i tailscale0 -j RETURN
|
||||
for port in "${restricted_tcp_ports[@]}"; do
|
||||
"$cmd" -w -A NOISEBELL_TS_ONLY -p tcp --dport "$port" -j DROP
|
||||
done
|
||||
"$cmd" -w -A NOISEBELL_TS_ONLY -j RETURN
|
||||
"$cmd" -w -I INPUT 1 -j NOISEBELL_TS_ONLY
|
||||
}
|
||||
|
||||
apply_rules iptables
|
||||
if command -v ip6tables >/dev/null 2>&1; then
|
||||
apply_rules ip6tables || true
|
||||
fi
|
||||
FIREWALLEOF
|
||||
sudo chmod 755 /usr/local/sbin/noisebell-tailscale-only-firewall
|
||||
|
||||
sudo tee /etc/systemd/system/noisebell-tailscale-only-firewall.service >/dev/null <<'UNITEOF'
|
||||
[Unit]
|
||||
Description=Restrict Noisebell service ports to Tailscale
|
||||
After=tailscaled.service
|
||||
Wants=tailscaled.service
|
||||
Before=ssh.service prometheus-node-exporter.service noisebell.service noisebell-relay.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/usr/local/sbin/noisebell-tailscale-only-firewall
|
||||
RemainAfterExit=yes
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
UNITEOF
|
||||
|
||||
sudo tee /usr/local/bin/noisebell-loki-journal >/dev/null <<'SCRIPTEOF'
|
||||
#!/usr/bin/env bash
|
||||
set -uo pipefail
|
||||
|
|
@ -251,6 +315,7 @@ UNITEOF
|
|||
|
||||
sudo ln -sfn "$REMOTE_RELEASE_DIR" "$REMOTE_CURRENT_LINK"
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now noisebell-tailscale-only-firewall.service
|
||||
sudo systemctl enable noisebell.service
|
||||
sudo systemctl enable noisebell-relay.service
|
||||
sudo systemctl enable noisebell-loki-journal.service
|
||||
|
|
@ -259,8 +324,6 @@ 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."
|
||||
|
|
|
|||
87
scripts/share-grafana-public-dashboard
Executable file
87
scripts/share-grafana-public-dashboard
Executable file
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
target=${1:-jet@noisebell-do}
|
||||
public_url=${GRAFANA_PUBLIC_URL:-https://grafana-noisebell.extremist.software}
|
||||
ssh_opts=(
|
||||
-o StrictHostKeyChecking=accept-new
|
||||
)
|
||||
printf -v remote_command 'GRAFANA_PUBLIC_URL=%q bash -s' "$public_url"
|
||||
|
||||
ssh "${ssh_opts[@]}" "$target" "$remote_command" <<'REMOTE'
|
||||
set -euo pipefail
|
||||
|
||||
base_url=http://127.0.0.1:3030
|
||||
dashboard_uid=noisebell-public
|
||||
public_uid=noisebell-public
|
||||
access_token=6e6f69736562656c6c7075626c696330
|
||||
password=$(sudo tr -d '\r\n' < /var/lib/grafana/admin_password)
|
||||
ready=0
|
||||
|
||||
for _ in $(seq 1 60); do
|
||||
if curl -fsS -u "admin:$password" "$base_url/api/health" >/dev/null; then
|
||||
ready=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$ready" -ne 1 ]; then
|
||||
echo "Grafana did not become ready at $base_url" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
dashboard_ready=0
|
||||
for _ in $(seq 1 60); do
|
||||
if curl -fsS -u "admin:$password" "$base_url/api/dashboards/uid/$dashboard_uid" >/dev/null; then
|
||||
dashboard_ready=1
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
if [ "$dashboard_ready" -ne 1 ]; then
|
||||
echo "Grafana dashboard '$dashboard_uid' was not provisioned" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
existing=$(curl -fsS -u "admin:$password" \
|
||||
"$base_url/api/dashboards/uid/$dashboard_uid/public-dashboards/" 2>/dev/null || true)
|
||||
existing_uid=""
|
||||
existing_token=""
|
||||
if [ -n "$existing" ]; then
|
||||
existing_uid=$(jq -r '.uid // empty' <<<"$existing")
|
||||
existing_token=$(jq -r '.accessToken // empty' <<<"$existing")
|
||||
fi
|
||||
|
||||
if [ -n "$existing_uid" ] && { [ "$existing_uid" != "$public_uid" ] || [ "$existing_token" != "$access_token" ]; }; then
|
||||
curl -fsS -u "admin:$password" \
|
||||
-X DELETE \
|
||||
"$base_url/api/dashboards/uid/$dashboard_uid/public-dashboards/$existing_uid" >/dev/null
|
||||
existing_uid=""
|
||||
fi
|
||||
|
||||
if [ -n "$existing_uid" ]; then
|
||||
body=$(jq -cn '{timeSelectionEnabled:true,isEnabled:true,annotationsEnabled:false,share:"public"}')
|
||||
response=$(curl -fsS -u "admin:$password" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X PATCH \
|
||||
--data "$body" \
|
||||
"$base_url/api/dashboards/uid/$dashboard_uid/public-dashboards/$existing_uid")
|
||||
else
|
||||
body=$(jq -cn \
|
||||
--arg uid "$public_uid" \
|
||||
--arg accessToken "$access_token" \
|
||||
'{uid:$uid,accessToken:$accessToken,timeSelectionEnabled:true,isEnabled:true,annotationsEnabled:false,share:"public"}')
|
||||
response=$(curl -fsS -u "admin:$password" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-X POST \
|
||||
--data "$body" \
|
||||
"$base_url/api/dashboards/uid/$dashboard_uid/public-dashboards/")
|
||||
fi
|
||||
|
||||
returned_token=$(jq -r '.accessToken // empty' <<<"$response")
|
||||
if [ "$returned_token" != "$access_token" ]; then
|
||||
echo "Grafana did not return a public dashboard access token" >&2
|
||||
exit 1
|
||||
fi
|
||||
printf '%s/public-dashboards/%s\n' "${GRAFANA_PUBLIC_URL%/}" "$returned_token"
|
||||
REMOTE
|
||||
Loading…
Add table
Add a link
Reference in a new issue