2 Commits

Author SHA1 Message Date
Axodouble a1d74cf36d Fixed odd issue with the cli not finding the socket properly
Release / release (push) Successful in 3m21s
Container image / image (push) Successful in 5m57s
2026-05-15 08:05:55 +00:00
Axodouble f60b0a0609 Added better documentation for fixing my own broken installs, and updated the install script to patch issues
Container image / image (push) Successful in 4m40s
2026-05-15 07:56:13 +00:00
5 changed files with 119 additions and 24 deletions
+27
View File
@@ -4,6 +4,32 @@ All notable changes to this project are documented here. The format
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [v0.1.1] — 2026-05-15
### Changed
- **`install.sh` now repairs data-dir permissions on every run.**
Re-running the installer reasserts the canonical ownership
(`quptime:quptime`) and modes across `/etc/quptime/``0750` on
the dir, `0700` on `keys/`, `0600` on `node.yaml`, `cluster.yaml`,
`trust.yaml`, and `keys/private.pem`, `0644` on `keys/public.pem`
and `keys/cert.pem`. Makes the installer the one-step recovery
path when something has tampered with modes (e.g. a stray
`chmod -R`, a backup restore, or an accidental `sudo qu init`
that left files owned by root). Unknown files in the dir are left
alone.
### Fixed
- **CLI socket lookup as the daemon user.** `sudo -u quptime qu …`
no longer fails with `dial daemon socket /tmp/quptime-quptime/…:
no such file or directory` while the system daemon is running.
`config.SocketPath()` now probes the canonical systemd location
(`/run/quptime/quptime.sock`, then `/var/run/quptime/quptime.sock`)
regardless of euid before falling back to per-user paths, so the
CLI reaches the daemon's socket even when `sudo` has stripped
`RUNTIME_DIRECTORY` and `XDG_RUNTIME_DIR` from the environment.
## [v0.1.0] — 2026-05-15
### Changed
@@ -113,3 +139,4 @@ Initial public release.
[v0.0.1]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.0.1
[v0.1.0]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.1.0
[v0.1.1]: https://git.cer.sh/axodouble/quptime/releases/tag/v0.1.1
+9
View File
@@ -70,6 +70,15 @@ What it does:
`/etc/systemd/system/quptime.service` (hardened — matches the unit
in [systemd.md](deployment/systemd.md)). Enables but does not start
the service, so you can configure identity before first boot.
5. Repairs ownership and modes under `/etc/quptime/` to the canonical
layout (`0750` on the dir, `0700` on `keys/`, `0600` on
`node.yaml` / `cluster.yaml` / `trust.yaml` / `keys/private.pem`,
`0644` on `keys/public.pem` / `keys/cert.pem`). This makes the
installer idempotent for permission damage — if something
tightened or loosened modes (a stray `chmod -R`, a misguided
backup restore, an accidental `sudo qu init`), re-running
`install.sh` puts everything back without touching the contents
of those files.
## Build from source
+6 -2
View File
@@ -172,7 +172,9 @@ still see this error, the most likely causes are:
- The data directory is read-only or owned by a different user — the
bootstrap can't write `node.yaml`. Fix permissions on
`$QUPTIME_DIR`.
`$QUPTIME_DIR`. The fastest fix on a standard install is just to
re-run `install.sh` — it reasserts the canonical ownership and
modes on the whole tree without touching your config.
- Something else removed `node.yaml` mid-run (a config-management
tool, a misconfigured volume). Re-run `qu serve` and it will
rebuild from env, or run `qu init` manually with the flags you
@@ -197,7 +199,9 @@ load private key: ...
```
Permissions on `keys/private.pem` are wrong — should be 0600 and owned
by the daemon user. Fix and restart.
by the daemon user. Fix and restart. Re-running `install.sh` on a
standard install is the easiest path: it repairs ownership and modes
on the entire data dir.
## Probes look much slower than expected
+48 -6
View File
@@ -175,21 +175,63 @@ fi
install -d -o "$SERVICE_USER" -g "$SERVICE_GROUP" -m 0750 "$DATA_DIR"
# Reassert ownership on the dir's contents. Two cases this catches:
# Repair ownership and permissions on the data dir's contents. Catches:
# - re-running the installer over a previous install where the
# service user/group changed
# service user/group changed.
# - the operator ran `qu init` or `qu serve` as root once (easy
# mistake: `sudo qu init` is shorter than the documented
# `sudo -u quptime qu init`). When the daemon runs as root its
# DataDir() resolves to /etc/quptime, so any files it writes land
# here owned by root:root mode 0600 — the systemd service then
# fails with `open node.yaml: permission denied`.
# chown -R only changes ownership, not perms, so file modes set by
# the daemon (0600 for node.yaml, 0700 for keys/) are preserved.
# owned by root:root — the systemd service then fails with
# `open node.yaml: permission denied`.
# - someone or something (a stray `chmod -R`, a misguided backup
# restore) tightened or loosened modes. Re-running the installer
# should be enough to get back to a working baseline.
# The canonical layout (mirrors the modes the daemon writes itself
# in internal/config and internal/crypto):
# /etc/quptime/ quptime:quptime 0750
# /etc/quptime/keys/ quptime:quptime 0700
# /etc/quptime/node.yaml quptime:quptime 0600
# /etc/quptime/cluster.yaml quptime:quptime 0600
# /etc/quptime/trust.yaml quptime:quptime 0600
# /etc/quptime/keys/private.pem quptime:quptime 0600
# /etc/quptime/keys/public.pem quptime:quptime 0644
# /etc/quptime/keys/cert.pem quptime:quptime 0644
# The runtime dir /var/run/quptime is owned by systemd via
# RuntimeDirectory= and rebuilt at each service start, so we leave it
# alone.
repair_perms() {
# Always reset the top-level dir mode — `install -d` only sets it
# on creation, not on re-run.
chown "$SERVICE_USER:$SERVICE_GROUP" "$DATA_DIR"
chmod 0750 "$DATA_DIR"
# Reassert ownership across the whole tree in one pass.
if [ -n "$(ls -A "$DATA_DIR" 2>/dev/null)" ]; then
chown -R "$SERVICE_USER:$SERVICE_GROUP" "$DATA_DIR"
fi
# keys/ is a directory with its own tighter mode.
if [ -d "$DATA_DIR/keys" ]; then
chmod 0700 "$DATA_DIR/keys"
fi
# Each known file gets its canonical mode if it exists. We don't
# create anything that isn't already there — that's `qu init`'s
# job — and we don't touch unknown files an operator may have
# parked in the dir.
local f
for f in node.yaml cluster.yaml trust.yaml keys/private.pem; do
[ -f "$DATA_DIR/$f" ] && chmod 0600 "$DATA_DIR/$f"
done
for f in keys/public.pem keys/cert.pem; do
[ -f "$DATA_DIR/$f" ] && chmod 0644 "$DATA_DIR/$f"
done
}
repair_perms
echo "> reasserted ownership ($SERVICE_USER:$SERVICE_GROUP) and modes under $DATA_DIR"
echo "> writing $SERVICE_FILE"
cat > "$SERVICE_FILE" <<'EOF'
[Unit]
+25 -12
View File
@@ -58,19 +58,20 @@ func DataDir() string {
// SocketPath returns the unix socket used for local CLI ↔ daemon control.
//
// Resolution order:
// 1. $QUPTIME_SOCKET — explicit operator override
// 1. $QUPTIME_SOCKET — explicit operator override.
// 2. $RUNTIME_DIRECTORY — set by systemd when the unit declares
// RuntimeDirectory=quptime. This is the path that matters in
// practice: with User=quptime + PrivateTmp=true, the daemon's
// /tmp is namespaced and invisible to the root CLI shell, so a
// /tmp fallback yields "no such file" even though the daemon is
// happily listening. Anchoring on $RUNTIME_DIRECTORY puts the
// socket at /run/quptime/quptime.sock, which is the same inode
// the root-CLI default (/var/run/quptime/…) reaches via the
// /var/run → /run symlink.
// 3. /var/run/quptime/… when euid is 0 (CLI side, packaged installs)
// 4. $XDG_RUNTIME_DIR/quptime/… for user-mode installs
// 5. /tmp/quptime-<user>/… as a last resort
// RuntimeDirectory=quptime. This is the path the daemon uses
// when run under the packaged unit: /run/quptime/quptime.sock.
// 3. The canonical system socket path — /run/quptime/quptime.sock —
// if it exists. This catches the CLI side regardless of who is
// invoking it: `sudo -u quptime qu status` strips RUNTIME_DIRECTORY
// and XDG_RUNTIME_DIR, so without this probe the CLI falls all
// the way through to /tmp/quptime-<user>/… and reports "no such
// file" even while the daemon is happily listening.
// 4. /var/run/quptime/… when euid is 0 (CLI side, packaged installs
// on systems where /var/run isn't a symlink to /run).
// 5. $XDG_RUNTIME_DIR/quptime/… for user-mode installs.
// 6. /tmp/quptime-<user>/… as a last resort.
func SocketPath() string {
if v := os.Getenv("QUPTIME_SOCKET"); v != "" {
return v
@@ -84,6 +85,18 @@ func SocketPath() string {
}
return filepath.Join(v, SocketName)
}
// If a system-managed daemon is already listening, route there
// regardless of euid. Without this, `sudo -u quptime qu …` can't
// find the socket the daemon (also running as quptime) created
// via RuntimeDirectory=.
for _, p := range []string{
"/run/quptime/" + SocketName,
"/var/run/quptime/" + SocketName,
} {
if _, err := os.Stat(p); err == nil {
return p
}
}
if os.Geteuid() == 0 {
return "/var/run/quptime/" + SocketName
}