Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,32 @@ Every 6 hours (and 5 minutes after boot) `scripts/disk-health.sh` checks the `/l

Check now: `sudo ./scripts/disk-health.sh`. Disks behind some USB adapters don't report SMART; those checks are then skipped silently.

### Heartbeat (optional)

When the box connects to the internet, it sends a “I’m alive” signal to a service of your choice. If the signals stop arriving for a certain period, that service sends an alert, allowing you to remotely detect that the box has gone down.

```bash
sudo ./scripts/install-heartbeat.sh
```

When the script is run again, the existing settings become the default answers to the prompts. Press Enter to keep the current value. Settings are stored in `/etc/has-heartbeat.conf`. You can manually edit `url`, `format`, `method`, `headers`, `query`, `body`, `warnings`, `metrics`, and `box_id` (changes take effect with the next heartbeat). To change `interval`, you must run the script again.
To disable the feature, run the script again and enter `off` when prompted for the URL.
To test: `sudo ./scripts/heartbeat.sh`
Logs: `journalctl -u has-heartbeat.service`

For a custom API, configure `method`, `headers`, and a `query` or `body` template in `/etc/has-heartbeat.conf`.
The following variables are replaced with escaped values:
`{status}` (up/down), `{msg}`, `{box}`, `{uptime}`, `{disk}` (%), `{cpu}` (°C)

```bash
url="https://monitor.example.com/api/box"
method="POST"
headers=("Authorization: Bearer token")
body='{"status":"{status}","message":"{msg}"}'
```

`query` builds the part after the `?` of the URL (e.g. `query="durum={status}&mesaj={msg}"`), `body` becomes the request body, and `method` defaults to GET for `params`, POST for `json` and POST when `body` is set. Values in `query` are percent-encoded and values in `body` are JSON-escaped automatically. When `query` or `body` is set, `format` is ignored.

### Repo layout

```
Expand All @@ -118,8 +144,10 @@ scripts/download-content.sh downloads/updates ZIM files from Kiwix
scripts/install-homepage.sh installs the custom homepage
scripts/status.sh health check: disk, services, hotspot, content, power
scripts/disk-health.sh disk and power health check (saglik/: its systemd timer)
scripts/install-heartbeat.sh (optional) installs the heartbeat
config/iiab-overrides.yml Internet-in-a-Box settings
config/content.conf list of content to download
config/heartbeat.conf heartbeat settings
www/home/index.html homepage
www/home/acil.html emergency page (Turkish, sourced from AFAD and EPA)
```
Expand Down Expand Up @@ -161,6 +189,7 @@ It checks free space on the SD card and SSD, the web, Kiwix, hotspot and DHCP se
`br0` should now be UP with 10.10.10.10 and `wlan0` should show `state forwarding`.
- **SSH without a cable:** join the Pi's hotspot and use `ssh user@10.10.10.10`.
- **Restoring the original homepage:** `sudo cp /library/www/html/home/index.html.iiab-original /library/www/html/home/index.html`
- **No heartbeat signal arrives.** While the box is offline that is normal. When it has internet, test with `sudo ./scripts/heartbeat.sh` and check `journalctl -u has-heartbeat.service -n 20`. If `/etc/has-heartbeat.conf` has an empty `url`, the heartbeat is disabled; re-run `sudo ./scripts/install-heartbeat.sh`.

### License

Expand Down Expand Up @@ -247,6 +276,27 @@ Pi'nin dahili Wi-Fi kartı sadece hotspot için kullanılıyor. Pi'nin geçici o

Hemen kontrol: `sudo ./scripts/disk-health.sh`. Bazı USB adaptörlerinin arkasındaki diskler SMART bilgisi vermez; o zaman bu kontrol sessizce atlanır.

### Heartbeat (isteğe bağlı)

Kutu internete çıktığında seçtiğin bir servise "çalışıyorum" sinyali yollar. Sinyaller belirli süre gelmezse o servis uyarır ve kutunun düştüğünü uzaktan öğrenirsin.

```bash
sudo ./scripts/install-heartbeat.sh
```

Betik tekrar çalıştırıldığında mevcut ayarlar soruların varsayılanı olur; değeri korumak için Enter'a bas. Ayarlar `/etc/has-heartbeat.conf` dosyasında durur. `url`, `format`, `method`, `headers`, `query`, `body`, `warnings`, `metrics` ve `box_id` elle düzenlenebilir (bir sonraki sinyalde geçerli olur), `interval`'ı değiştirmek için betiği tekrar çalıştırmak gerekir. Özelliği kapatmak için betiği tekrar çalıştır ve URL sorusuna `off` yaz.
Test: `sudo ./scripts/heartbeat.sh`.
Loglar: `journalctl -u has-heartbeat.service`.

Özel bir API için `/etc/has-heartbeat.conf` dosyasında `method`, `headers` ve bir `query` ya da `body` şablonu belirle. `{status}` (up/down), `{msg}`, `{box}`, `{uptime}`, `{disk}` (%) ve `{cpu}` (°C) değişkenleri escaped değerlerle değiştirilir:

```bash
url="https://monitor.example.com/api/box"
method="POST"
headers=("Authorization: Bearer token")
body='{"durum":"{status}","mesaj":"{msg}"}'
```

### Repo yapısı

```
Expand All @@ -256,8 +306,10 @@ scripts/download-content.sh Kiwix'ten ZIM dosyalarını indirir/günceller
scripts/install-homepage.sh özel ana sayfayı kurar
scripts/status.sh sağlık kontrolü: disk, servisler, hotspot, içerik, güç
scripts/disk-health.sh disk ve güç sağlığı denetimi (saglik/: systemd zamanlayıcısı)
scripts/install-heartbeat.sh (isteğe bağlı) heartbeat kurar
config/iiab-overrides.yml Internet-in-a-Box ayarları
config/content.conf indirilecek içerik listesi
config/heartbeat.conf heartbeat ayarları
www/home/index.html ana sayfa
www/home/acil.html acil durum sayfası (AFAD ve EPA kaynaklı)
```
Expand Down Expand Up @@ -299,6 +351,7 @@ SD kart ve SSD'deki boş alanı, web, Kiwix, hotspot ve DHCP servislerini, hotsp
Artık `br0` 10.10.10.10 adresiyle UP, `wlan0` da `state forwarding` görünmeli.
- **Kablosuz SSH:** Pi'nin hotspot'una bağlan ve `ssh kullanici@10.10.10.10` kullan.
- **Orijinal ana sayfaya dönmek için:** `sudo cp /library/www/html/home/index.html.iiab-original /library/www/html/home/index.html`
- **Heartbeat sinyali gitmiyor.** Kutu internetsizken bu normaldir. İnterneti olduğunda `sudo ./scripts/heartbeat.sh` ile test et ve `journalctl -u has-heartbeat.service -n 20` kaydına bak. `/etc/has-heartbeat.conf` içinde `url` boşsa nabız kapalıdır; `sudo ./scripts/install-heartbeat.sh`'ı tekrar çalıştır.

### Lisans

Expand Down
63 changes: 63 additions & 0 deletions config/heartbeat.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# HAS_PrepperDisk uzaktan nabız ayarları / remote heartbeat settings
# scripts/install-heartbeat.sh bu dosyayı /etc/has-heartbeat.conf'a yazar.
# It writes the settings to /etc/has-heartbeat.conf; on an installed box that file is live.
#
# url: sinyalin gideceği adres. Boş = nabız kapalı / empty = disabled.
# Değişkenler / variables:
# {status}: up | down {msg}: uyarı metni / warning text
# {box}: box_id {uptime}: örn. 3d12h / e.g. 3d12h
# {disk}: doluluk yüzdesi / disk usage percent
# {cpu}: sıcaklık, °C / temperature in °C (yoksa boş kalır / empty if unavailable)
# Examples:
# healthchecks.io : https://hc-ping.com/<uuid> format="params"
# Uptime Kuma : https://<kuma>/api/push/<token> (push monitor) format="params"
# Discord webhook : https://discord.com/api/webhooks/<id>/<token> format="json"
# Telegram : https://api.telegram.org/bot<token>/sendMessage?chat_id=<id> format="json"

url=""

# --- hazır biçimler / preset formats (yeni başlayanlar için / for beginners) ---
# params: sinyal ve uyarılar URL parametresi olarak gider (?status=up&msg=...)
# json: sinyal POST gövdesinde JSON olarak gider ({"content": ..., "text": ...})
# NOT: query ya da body doluysa format yok sayılır. / if query or body is set, format is ignored.
format="params"

# --- tam özelleştirme / full customization ---
# HTTP yöntemi; boşsa otomatik: preset biçimde params->GET, json->POST;
# query/body modunda body doluysa POST, yoksa GET.
# HTTP method; empty = auto: params->GET, json->POST in preset mode;
# in query/body mode POST when body is set, otherwise GET.
method=""

# HTTP başlıkları / headers:
# headers=("Authorization: Bearer token" "X-Box-ID: has1")
headers=()

# GET/POST sorgu şablonu / query template (URL'nin ? sonrası / after the ?):
# query="durum={status}&mesaj={msg}"
# Değerler otomatik percent-encode edilir / values are percent-encoded.
query=""

# Gövde şablonu / body template, örn. / e.g.:
# body='{"durum":"{status}","mesaj":"{msg}"}'
# Değerler otomatik JSON-escape edilir / values are JSON-escaped.
body=""

# --- veri seçimi / what to send ---
# disk.json'daki uyarılar sinyalle gitsin mi / send the disk.json warnings (1/0)?
# Uyarı varsa sinyal her durumda "down" gider; bu ayar yalnızca uyarı metninin
# sinyale eklenip eklenmeyeceğini belirler.
# If warnings exist the signal is "down" regardless; this only controls whether
# the warning text is included in the signal.
warnings=1

# temel metrikler de gitsin mi / also send basic metrics (1/0)?
# {uptime} {disk} {cpu} değişkenlerini doldurur / fills these variables
metrics=0

# isteğe bağlı kutu etiketi / optional box label, örn. "has1"
box_id=""

# systemd zamanlayıcı aralığı / systemd timer interval: 30min, 6h, 12h, 1d ...
# Değiştirmek için install-heartbeat.sh'ı tekrar çalıştır / re-run to change.
interval="6h"
13 changes: 10 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
# HAS_PrepperDisk kurulum betiği / installer
# 1. Internet-in-a-Box ayarlarını hazırlar / prepares Internet-in-a-Box settings
# 2. Internet-in-a-Box'ı kurar / installs Internet-in-a-Box
# 3. HAS_PrepperDisk ana sayfasını kurar / installs the HAS_PrepperDisk homepage
# 3. Ana sayfayı, disk sağlığını ve (isteğe bağlı) heartbeat'i kurar /
# installs the homepage, disk health and (optional) heartbeat
#
# Kurulum sırasında Pi yeniden başlarsa bu betiği tekrar çalıştır.
# If the Pi reboots during install, just run this script again.
Expand Down Expand Up @@ -75,16 +76,22 @@ else
[ -f /etc/iiab/install-flags/iiab-complete ] || die "Kurulum bitmedi / install not finished. Tekrar çalıştır / run again: sudo ./install.sh"
fi

# -------------------------------------------------------------- 3. ana sayfa
say "3/3 Ana sayfa / homepage"
# -------------------------------------------------------------- 3. bileşenler
say "3/3 HAS_PrepperDisk bileşenleri / components"
"$REPO/scripts/install-homepage.sh"
"$REPO/scripts/install-disk-health.sh"
read -rp "Heartbeat kurulsun mu? / install heartbeat? [e/y/N] " HB
case "$HB" in
e|E|y|Y) "$REPO/scripts/install-heartbeat.sh";;
*) echo "Atlandı, sonradan kurmak için / skipped, to install later: sudo ./scripts/install-heartbeat.sh";;
esac

IP=$(hostname -I | awk '{print $1}')
say "Bitti / done"
cat <<EOF
Ana sayfa / homepage: http://$IP/home (hotspot üzerinden / via hotspot: http://box.lan/home)
Yönetim / admin console: http://$IP/admin (kullanıcı / user: iiab-admin)
Heartbeat: sudo ./scripts/install-heartbeat.sh (kur veya değiştir / install or change)

Sıradaki adım / next step: içerikleri indir / download content
sudo ./scripts/download-content.sh
Expand Down
6 changes: 6 additions & 0 deletions saglik/has-heartbeat.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[Unit]
Description=HAS_PrepperDisk: Heartbeat service

[Service]
Type=oneshot
ExecStart=@REPO@/scripts/heartbeat.sh
9 changes: 9 additions & 0 deletions saglik/has-heartbeat.timer
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[Unit]
Description=HAS_PrepperDisk: Heartbeat zamanlayıcı (@INTERVAL@'de bir) / heartbeat timer (every @INTERVAL@)

[Timer]
OnBootSec=5min
OnUnitActiveSec=@INTERVAL@

[Install]
WantedBy=timers.target
182 changes: 182 additions & 0 deletions scripts/heartbeat.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
#!/usr/bin/env bash
set -uo pipefail

REPO="$(cd "$(dirname "$0")/.." && pwd)"
DISK_JSON="${DISK_JSON:-/library/www/html/home/disk.json}"
TIMEOUT="${TIMEOUT:-15}"

# shellcheck disable=SC2034
url="" format="params" method="" query="" body=""
# shellcheck disable=SC2034
warnings=1 metrics=0 box_id="" interval="6h"
headers=()
# shellcheck disable=SC1090,SC1091
[ -f "$REPO/config/heartbeat.conf" ] && . "$REPO/config/heartbeat.conf"
# shellcheck disable=SC1090,SC1091
[ -f /etc/has-heartbeat.conf ] && . /etc/has-heartbeat.conf

[ -n "$url" ] || exit 0
case "$format" in params|json) ;; *) format="params" ;; esac

# warnings
warns=()
if [ -f "$DISK_JSON" ]; then
if command -v python3 >/dev/null 2>&1; then
while IFS= read -r w; do
[ -n "$w" ] && warns+=("$w")
done < <(python3 -c 'import json, sys
for w in json.load(open(sys.argv[1])).get("warnings", []):
print(w)' "$DISK_JSON" 2>/dev/null)
elif grep -q '"warnings":\[[^]]' "$DISK_JSON"; then
warns+=("disk.json uyarı içeriyor / disk.json contains warnings")
fi
else
warns+=("disk.json yok, disk sağlığı bilinmiyor / missing, disk health unknown")
fi

status="up"
[ ${#warns[@]} -gt 0 ] && status="down"

# message
msg=""
[ -n "$box_id" ] && msg+="[$box_id] "
if [ ${#warns[@]} -gt 0 ]; then
msg+="PROBLEM: "
for w in "${warns[@]}"; do msg+="$w | "; done
msg=${msg%" | "}
else
msg+="ok"
fi

# metrics
m_up="" m_pct="" m_cpu=""
need=0
[ "$metrics" = 1 ] && need=1
case "$url $query $body" in *"{uptime}"*|*"{disk}"*|*"{cpu}"*) need=1 ;; esac
if [ "$need" = 1 ]; then
m_up=$(awk '{printf "%dd%dh", int($1/86400), int(($1%86400)/3600)}' /proc/uptime 2>/dev/null)
m_pct=$(df --output=pcent /library 2>/dev/null | tail -1 | tr -dc '0-9')
[ -r /sys/class/thermal/thermal_zone0/temp ] && m_cpu=$(( $(cat /sys/class/thermal/thermal_zone0/temp) / 1000 ))
fi
extra=""
if [ "$metrics" = 1 ]; then
extra=" | uptime ${m_up:-?}, disk %${m_pct:-?}"
[ -n "$m_cpu" ] && extra+=", CPU ${m_cpu}°C"
fi
msg+="$extra"

# escaping
json_esc(){
if command -v python3 >/dev/null 2>&1; then
python3 -c 'import json, sys
sys.stdout.write(json.dumps(sys.argv[1])[1:-1])' "$1"
return
fi
local s="$1"
s=${s//\\/\\\\}
s=${s//\"/\\\"}
s=${s//$'\n'/\\n}
s=${s//$'\r'/\\r}
s=${s//$'\t'/\\t}
printf '%s' "$s"
}

pct_esc(){
if command -v python3 >/dev/null 2>&1; then
python3 -c 'import sys, urllib.parse
sys.stdout.write(urllib.parse.quote_plus(sys.argv[1]))' "$1"
return
fi
local s="$1" out="" bytes hex i c
bytes=$(printf '%s' "$s" | od -An -v -tx1 | tr -d ' \n')
for ((i=0; i<${#bytes}; i+=2)); do
hex=${bytes:i:2}
printf -v c '%b' "\\x$hex"
case "$c" in
[a-zA-Z0-9.~_-]) out+="$c" ;;
' ') out+="+" ;;
*) out+="%$hex" ;;
esac
done
printf '%s' "$out"
}

subst(){
local esc="$1" t="$2" out="" i=1 c rest k v
while [ "$i" -le "${#t}" ]; do
c=${t:i-1:1}
if [ "$c" = "{" ]; then
rest=${t:i-1}
for k in status msg box uptime disk cpu; do
if [[ "$rest" == "{$k}"* ]]; then
case "$k" in
status) v=$("$esc" "$status") ;;
msg) v=$("$esc" "$msg") ;;
box) v=$("$esc" "$box_id") ;;
uptime) v=$("$esc" "$m_up") ;;
disk) v=$("$esc" "$m_pct") ;;
cpu) v=$("$esc" "$m_cpu") ;;
esac
out+="$v"
i=$(( i + ${#k} + 2 ))
continue 2
fi
done
fi
out+="$c"
i=$(( i + 1 ))
done
printf '%s' "$out"
}

case "$url" in *"{"*) url=$(subst pct_esc "$url") ;; esac

# send heartbeat
sent=1
if [ -n "$query" ] || [ -n "$body" ]; then
# customization
args=(-fsS -m "$TIMEOUT" -o /dev/null)
for h in "${headers[@]}"; do args+=(-H "$h"); done
[ -n "$method" ] && args+=(-X "$method")
if [ -n "$body" ]; then
ct=1
for h in "${headers[@]}"; do
[[ "${h,,}" == content-type:* ]] && ct=0
done
[ "$ct" = 1 ] && args+=(-H 'Content-Type: application/json')
args+=(--data-binary "$(subst json_esc "$body")")
fi
u="$url"
if [ -n "$query" ]; then
q=$(subst pct_esc "$query")
case "$u" in *"?"*) u="$u&$q" ;; *) u="$u?$q" ;; esac
fi
if curl "${args[@]}" "$u"; then sent=0; fi
elif [ "$format" = params ]; then
args=(-G -fsS -m "$TIMEOUT" -o /dev/null
--data-urlencode "status=$status" --data-urlencode "msg=$msg")
[ -n "$box_id" ] && args+=(--data-urlencode "box=$box_id")
for h in "${headers[@]}"; do args+=(-H "$h"); done
if curl "${args[@]}" "$url"; then sent=0; fi
else
if command -v python3 >/dev/null 2>&1; then
body_json=$(python3 -c 'import json, sys
print(json.dumps({"content": sys.argv[1], "text": sys.argv[1]}))' "$msg")
else
esc=${msg//\\/\\\\}
esc=${esc//\"/\\\"}
body_json="{\"content\":\"$esc\",\"text\":\"$esc\"}"
fi
args=(-fsS -m "$TIMEOUT" -o /dev/null -H 'Content-Type: application/json'
--data-binary "$body_json")
[ -n "$method" ] && args+=(-X "$method")
for h in "${headers[@]}"; do args+=(-H "$h"); done
if curl "${args[@]}" "$url"; then sent=0; fi
fi

if [ "$sent" -eq 0 ]; then
echo "heartbeat: sinyal gitti / signal sent ($status)"
else
echo "heartbeat: gönderilemedi, kutu internetsiz olabilir / not sent, box is probably offline"
fi
exit 0
Loading