summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-04-16 08:41:34 +0300
committerPaul Buetow <paul@buetow.org>2026-04-16 08:41:34 +0300
commit71211a54519e13c9ba5ba928352fa4fef001240b (patch)
tree247196705644ac036ce65960daf3be1e996bdcec
parent6b743ff9eb937b35595ac74453e24ca3e19c4a18 (diff)
Add unified upload client script under scripts/, update contrib and docs
- scripts/goprecords-upload-client.sh: unified POSIX sh script working on all host types (FreeBSD f0-f3, Rocky Linux pi0-pi3, Fedora earth, OpenBSD blowfish/fishfinger). Key addition over the old contrib/ version: non-root support via XDG_CONFIG_HOME token path so user-session installs (earth) work without requiring a hardcoded /etc path. - contrib/goprecords-upload-client.sh: updated to match the canonical script in scripts/ (same content). - README.md: updated manual-upload section to reference scripts/ as canonical location, added host-class table, added user-session systemd example, and expanded environment variable table to show root vs non-root defaults. - Deployed new script to f0, f1, f2, pi0-pi3; tested uploads from all hosts. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--README.md110
-rwxr-xr-xcontrib/goprecords-upload-client.sh38
-rwxr-xr-xscripts/goprecords-upload-client.sh82
3 files changed, 196 insertions, 34 deletions
diff --git a/README.md b/README.md
index 9f5570d..4db2dbc 100644
--- a/README.md
+++ b/README.md
@@ -254,52 +254,74 @@ If there are **no** keys in the auth database, uploads are accepted without **`A
### Manual hourly upload (single host, not config-managed)
-Use this when the machine is **not** deployed from a Rex/Ansible repo. The reference script is **`contrib/goprecords-upload-client.sh`** (POSIX **`sh`**: Linux, FreeBSD, OpenBSD).
+Use this when the machine is **not** deployed from a Rex/Ansible repo. The unified script is **`scripts/goprecords-upload-client.sh`** (POSIX **`sh`**: Linux, FreeBSD, OpenBSD; runs as root or as a regular user). A copy is also kept in **`contrib/`** for backward compatibility.
-**SSH vs upload name:** **`GOPRECORDS_HOST`** must stay the **short** stats name (**`f0`**, **`pi2`**). For **SSH**, use a real host — e.g. **`ssh -p 22 paul@f0.lan.buetow.org`** or **`paul@192.168.1.130`**, not **`f0.lan`** (incomplete / invalid). OpenBSD frontends may use **port 2** in Rex; **f0–f3** and **Pis** use **port 22**.
+The script works on all host types:
+
+| Host class | OS | Privilege | Token path |
+|------------|----|-----------|------------|
+| **f0–f3** | FreeBSD | root via `doas` | `/etc/goprecords-upload.token` |
+| **pi0–pi3** | Rocky Linux (aarch64) | root via `sudo` | `/etc/goprecords-upload.token` |
+| **earth** (laptop) | Fedora / Linux | user (no root) | `$XDG_CONFIG_HOME/goprecords-upload-<HOST>/token` |
+| **blowfish**, **fishfinger** | OpenBSD | root (Rex) | deployed by Rex from template |
+
+**SSH vs upload name:** **`GOPRECORDS_HOST`** must stay the **short** stats name (**`f0`**, **`pi2`**, **`earth`**). For **SSH**, use a real host — e.g. **`ssh -p 22 paul@f0.lan.buetow.org`** or **`paul@192.168.1.130`**, not **`f0.lan`** (incomplete / invalid). OpenBSD frontends may use **port 2** in Rex; **f0–f3** and **Pis** use **port 22**.
**Privilege on FreeBSD:** many hosts (e.g. **f0–f3**) ship **`doas`** and no **`sudo`**. Use **`doas`** for install, writing the token, and one-off test runs below; on Linux (Pis) use **`sudo`**.
+**Token path (non-root):** when run as a non-root user the script defaults to **`${XDG_CONFIG_HOME:-$HOME/.config}/goprecords-upload-${GOPRECORDS_HOST}/token`** (mode **`600`**). Override with **`GOPRECORDS_TOKEN_FILE`**.
+
**On each host**
1. Install **`curl`** and **`uptimed`**, and ensure **`uptimed`** is running.
-2. Install the script (path is up to you; **`755`**, owned by **`root`**):
+2. Install the script (path is up to you; **`755`**, owned by **`root`** for system-wide use or **`700`** in `~/.local/bin` for user installs):
- **Linux**
+ **Linux (root)**
```bash
- sudo install -m 755 contrib/goprecords-upload-client.sh /usr/local/bin/goprecords-upload-client.sh
+ sudo install -m 755 scripts/goprecords-upload-client.sh /usr/local/bin/goprecords-upload-client.sh
```
- **FreeBSD**
+ **FreeBSD (root)**
```bash
- doas install -m 755 contrib/goprecords-upload-client.sh /usr/local/bin/goprecords-upload-client.sh
+ doas install -m 755 scripts/goprecords-upload-client.sh /usr/local/bin/goprecords-upload-client.sh
```
-3. Create a token on the goprecords server (hostname must match **`GOPRECORDS_HOST`** exactly, e.g. **`f0`**, **`pi2`**):
+ **Linux (user, e.g. earth)**
+
+ ```bash
+ install -m 700 scripts/goprecords-upload-client.sh ~/.local/bin/goprecords-upload-client.sh
+ ```
+
+3. Create a token on the goprecords server (hostname must match **`GOPRECORDS_HOST`** exactly, e.g. **`f0`**, **`pi2`**, **`earth`**):
```bash
kubectl exec -n services deployment/goprecords -- \
goprecords --create-client-key f0 -stats-dir=/data/stats
```
-4. Save the printed secret once as **`root`**, mode **`600`**:
+4. Save the printed secret, mode **`600`**:
- **Linux**
+ **Linux / FreeBSD (root)**
```bash
+ # Linux
umask 077
- sudo sh -c 'cat > /etc/goprecords-upload.token'
+ sudo sh -c ‘cat > /etc/goprecords-upload.token’
sudo chmod 600 /etc/goprecords-upload.token
+ # FreeBSD
+ umask 077
+ doas sh -c ‘cat > /etc/goprecords-upload.token’
+ doas chmod 600 /etc/goprecords-upload.token
```
- **FreeBSD**
+ **Linux (user, e.g. earth)**
```bash
+ mkdir -p ~/.config/goprecords-upload-earth
umask 077
- doas sh -c 'cat > /etc/goprecords-upload.token'
- doas chmod 600 /etc/goprecords-upload.token
+ cat > ~/.config/goprecords-upload-earth/token
```
5. **FreeBSD — hourly cron** (example for **`f0`**; use **`/etc/crontab`** or **`root`**’s crontab). **`curl`** must be on **`PATH`** for cron (often **`/usr/local/bin`**):
@@ -310,7 +332,7 @@ Use this when the machine is **not** deployed from a Rex/Ansible repo. The refer
Repeat for **`f1`**, **`f2`**, **`f3`**, … with the matching **`GOPRECORDS_HOST`**.
-6. **Linux (systemd) — hourly timer** (example for **`pi0`**). **`/etc/goprecords-upload.env`** (mode **`644`**, root):
+6. **Linux (systemd) — hourly timer, system-wide** (example for **`pi0`**). **`/etc/goprecords-upload.env`** (mode **`644`**, root):
```ini
GOPRECORDS_HOST=pi0
@@ -356,28 +378,70 @@ Use this when the machine is **not** deployed from a Rex/Ansible repo. The refer
Use a different **`GOPRECORDS_HOST`** in **`/etc/goprecords-upload.env`** on **`pi1`**, **`pi2`**, **`pi3`**.
+7. **Linux (systemd) — hourly timer, user session** (example for **`earth`** laptop). **`~/.config/systemd/user/goprecords-upload-earth.service`**:
+
+ ```ini
+ [Unit]
+ Description=Upload uptimed stats to goprecords
+
+ [Service]
+ Type=oneshot
+ Environment=GOPRECORDS_HOST=earth
+ ExecStart=%h/.local/bin/goprecords-upload-client.sh
+ ```
+
+ **`~/.config/systemd/user/goprecords-upload-earth.timer`**:
+
+ ```ini
+ [Unit]
+ Description=Hourly uptimed upload to goprecords (earth)
+
+ [Timer]
+ OnCalendar=hourly
+ OnActiveSec=90s
+ RandomizedDelaySec=300
+ Persistent=true
+
+ [Install]
+ WantedBy=timers.target
+ ```
+
+ Then (enable lingering so uploads continue without an active login session):
+
+ ```bash
+ sudo loginctl enable-linger "$USER"
+ systemctl --user daemon-reload
+ systemctl --user enable --now goprecords-upload-earth.timer
+ ```
+
**Environment (optional)**
-| Variable | Default |
-|----------|---------|
-| **`GOPRECORDS_HOST`** | (required) |
-| **`GOPRECORDS_TOKEN_FILE`** | **`/etc/goprecords-upload.token`** |
-| **`GOPRECORDS_BASE_URL`** | **`https://goprecords.f3s.buetow.org`** |
+| Variable | Default (root) | Default (non-root) |
+|----------|----------------|--------------------|
+| **`GOPRECORDS_HOST`** | (required) | (required) |
+| **`GOPRECORDS_TOKEN_FILE`** | **`/etc/goprecords-upload.token`** | **`$XDG_CONFIG_HOME/goprecords-upload-<HOST>/token`** |
+| **`GOPRECORDS_BASE_URL`** | **`https://goprecords.f3s.buetow.org`** | same |
**Test one run**
-**Linux**
+**Linux (root)**
```bash
-sudo env GOPRECORDS_HOST=f0 /usr/local/bin/goprecords-upload-client.sh
+sudo env GOPRECORDS_HOST=pi0 /usr/local/bin/goprecords-upload-client.sh
```
-**FreeBSD**
+**FreeBSD (root)**
```bash
doas env GOPRECORDS_HOST=f0 /usr/local/bin/goprecords-upload-client.sh
```
+**Linux (user)**
+
+```bash
+GOPRECORDS_HOST=earth ~/.local/bin/goprecords-upload-client.sh
+```
+
## Test
Run the test subcommand (fixture comparison and import/query parity):
diff --git a/contrib/goprecords-upload-client.sh b/contrib/goprecords-upload-client.sh
index e235c51..7e04cd9 100755
--- a/contrib/goprecords-upload-client.sh
+++ b/contrib/goprecords-upload-client.sh
@@ -1,10 +1,20 @@
#!/bin/sh
set -e
GOPRECORDS_BASE_URL="${GOPRECORDS_BASE_URL:-https://goprecords.f3s.buetow.org}"
-GOPRECORDS_HOST="${GOPRECORDS_HOST:?set GOPRECORDS_HOST (e.g. f0, pi0)}"
-GOPRECORDS_TOKEN_FILE="${GOPRECORDS_TOKEN_FILE:-/etc/goprecords-upload.token}"
+GOPRECORDS_HOST="${GOPRECORDS_HOST:?set GOPRECORDS_HOST (e.g. f0, pi0, earth)}"
PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:${PATH}"
+_default_token_file() {
+ if [ "$(id -u)" = "0" ]; then
+ printf '/etc/goprecords-upload.token'
+ else
+ config="${XDG_CONFIG_HOME:-${HOME}/.config}"
+ printf '%s/goprecords-upload-%s/token' "$config" "$GOPRECORDS_HOST"
+ fi
+}
+
+GOPRECORDS_TOKEN_FILE="${GOPRECORDS_TOKEN_FILE:-$(_default_token_file)}"
+
if ! test -r "$GOPRECORDS_TOKEN_FILE"; then
echo "goprecords-upload-client: cannot read $GOPRECORDS_TOKEN_FILE" >&2
exit 1
@@ -23,17 +33,21 @@ upload() {
"${GOPRECORDS_BASE_URL}/upload/${GOPRECORDS_HOST}/${kind}"
}
-records_path=
-if test -f /var/spool/uptimed/records; then
- records_path=/var/spool/uptimed/records
-elif test -f /var/db/uptimed/records; then
- records_path=/var/db/uptimed/records
-elif test -f /usr/local/var/uptimed/records; then
- records_path=/usr/local/var/uptimed/records
-else
+_find_records() {
+ for p in \
+ /var/spool/uptimed/records \
+ /var/db/uptimed/records \
+ /usr/local/var/uptimed/records; do
+ if test -f "$p"; then
+ printf '%s' "$p"
+ return 0
+ fi
+ done
echo "goprecords-upload-client: no uptimed records file found" >&2
exit 1
-fi
+}
+
+records_path=$(_find_records)
tmp=$(mktemp)
trap 'rm -f "$tmp"' 0 INT TERM HUP
@@ -51,6 +65,8 @@ fi
if test -r /etc/os-release; then
upload os.txt /etc/os-release
+elif test -r /var/run/dmesg.boot; then
+ upload os.txt /var/run/dmesg.boot
else
uname -a >"$tmp"
upload os.txt "$tmp"
diff --git a/scripts/goprecords-upload-client.sh b/scripts/goprecords-upload-client.sh
new file mode 100755
index 0000000..7e04cd9
--- /dev/null
+++ b/scripts/goprecords-upload-client.sh
@@ -0,0 +1,82 @@
+#!/bin/sh
+set -e
+GOPRECORDS_BASE_URL="${GOPRECORDS_BASE_URL:-https://goprecords.f3s.buetow.org}"
+GOPRECORDS_HOST="${GOPRECORDS_HOST:?set GOPRECORDS_HOST (e.g. f0, pi0, earth)}"
+PATH="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:${PATH}"
+
+_default_token_file() {
+ if [ "$(id -u)" = "0" ]; then
+ printf '/etc/goprecords-upload.token'
+ else
+ config="${XDG_CONFIG_HOME:-${HOME}/.config}"
+ printf '%s/goprecords-upload-%s/token' "$config" "$GOPRECORDS_HOST"
+ fi
+}
+
+GOPRECORDS_TOKEN_FILE="${GOPRECORDS_TOKEN_FILE:-$(_default_token_file)}"
+
+if ! test -r "$GOPRECORDS_TOKEN_FILE"; then
+ echo "goprecords-upload-client: cannot read $GOPRECORDS_TOKEN_FILE" >&2
+ exit 1
+fi
+TOKEN=$(tr -d '\n\r' <"$GOPRECORDS_TOKEN_FILE")
+
+upload() {
+ kind=$1
+ file=$2
+ if ! test -f "$file"; then
+ echo "goprecords-upload-client: skip $kind (no $file)" >&2
+ return 0
+ fi
+ curl -fsS -X PUT --data-binary "@${file}" \
+ -H "Authorization: Bearer ${TOKEN}" \
+ "${GOPRECORDS_BASE_URL}/upload/${GOPRECORDS_HOST}/${kind}"
+}
+
+_find_records() {
+ for p in \
+ /var/spool/uptimed/records \
+ /var/db/uptimed/records \
+ /usr/local/var/uptimed/records; do
+ if test -f "$p"; then
+ printf '%s' "$p"
+ return 0
+ fi
+ done
+ echo "goprecords-upload-client: no uptimed records file found" >&2
+ exit 1
+}
+
+records_path=$(_find_records)
+
+tmp=$(mktemp)
+trap 'rm -f "$tmp"' 0 INT TERM HUP
+
+upload records "$records_path"
+
+if command -v uprecords >/dev/null 2>&1; then
+ uprecords -a -m 100 >"$tmp"
+ upload txt "$tmp"
+ uprecords -a | grep '^->' >"$tmp" || true
+ if test -s "$tmp"; then
+ upload cur.txt "$tmp"
+ fi
+fi
+
+if test -r /etc/os-release; then
+ upload os.txt /etc/os-release
+elif test -r /var/run/dmesg.boot; then
+ upload os.txt /var/run/dmesg.boot
+else
+ uname -a >"$tmp"
+ upload os.txt "$tmp"
+fi
+
+if test -r /proc/cpuinfo; then
+ upload cpuinfo.txt /proc/cpuinfo
+elif test -r /var/run/dmesg.boot; then
+ upload cpuinfo.txt /var/run/dmesg.boot
+else
+ sysctl hw.model hw.ncpu hw.machine >"$tmp" 2>/dev/null || uname -a >"$tmp"
+ upload cpuinfo.txt "$tmp"
+fi