Fix agent install/uninstall sudo placement, add dependency install hints

curl ... | API_URL=... bash runs as the invoking user, not root, so
"sudo" has to go right after the pipe (elevating bash) rather than
before curl — the old commands in the app UI, README, and scripts'
own usage comments put it in a spot that still failed the root check.
Also have install.sh/report-tasks.sh suggest the right package-manager
command (apt/dnf/yum/apk/pacman/zypper) when curl or jq is missing
instead of just failing, and bring the README up to date with both.
This commit is contained in:
2026-09-02 23:12:12 +02:00
parent 0615866311
commit 329dcab936
5 changed files with 71 additions and 18 deletions
+12 -6
View File
@@ -11,8 +11,9 @@ support is a documented future addition — see [`agent/windows/README.md`](agen
- `server/` — Express + TypeScript API, SQLite (via libSQL) storage, Authentik OIDC login.
- `web/` — React + Vite single-page app, served as static files by the Express server.
- `agent/linux/` — `report-tasks.sh` (collects cron + systemd timer data) and
`install.sh` (installs it as a systemd timer on a target server).
- `agent/linux/` — `report-tasks.sh` (collects cron + systemd timer data),
`install.sh` (installs it as a systemd timer on a target server), and
`uninstall.sh` (removes it).
Data model, API contract, and sync behavior are documented in code:
[`server/src/db/schema.ts`](server/src/db/schema.ts),
@@ -64,11 +65,13 @@ automatically on server start.
1. Sign in, go to **Servers**, and add a server. The generated API token is
shown once — copy it.
2. On the target Linux server (as root):
2. On the target Linux server, as root. If you're not already root, put
`sudo` right after the pipe so it elevates `bash`, not `curl` — `sudo curl
... | bash` would still fail the root check:
```sh
curl -fsSL https://schedule.example.lan/agent/linux/install.sh | \
API_URL=https://schedule.example.lan API_TOKEN=stm_xxx bash
sudo API_URL=https://schedule.example.lan API_TOKEN=stm_xxx bash
```
This installs `report-tasks.sh` to `/usr/local/bin`, writes the API
@@ -76,7 +79,10 @@ automatically on server start.
`schedule-task-manager-agent.timer` systemd timer that reports every 15
minutes (override with `INTERVAL_MINUTES=5` before the `bash` above).
Requires `curl`, `jq`, and `systemd` on the target server.
Requires `curl`, `jq`, and `systemd` on the target server — if either
`curl` or `jq` is missing, both this script and `report-tasks.sh` print
the right install command for the server's package manager (apt, dnf,
yum, apk, pacman, or zypper) and exit.
3. Tasks appear on the Dashboard, grouped by server and then by schedule type,
within one reporting interval.
@@ -88,7 +94,7 @@ hidden by default — toggle "Show stale/missing tasks" to see them.
To remove the agent from a server, run:
```sh
curl -fsSL https://schedule.example.lan/agent/linux/uninstall.sh | bash
curl -fsSL https://schedule.example.lan/agent/linux/uninstall.sh | sudo bash
```
This stops and deletes the systemd timer/service, the installed script, and
+31 -5
View File
@@ -1,9 +1,10 @@
#!/usr/bin/env bash
# Installs the Schedule Task Manager agent as a systemd timer.
#
# Usage:
# Usage (must run as root — if not already root, put sudo right after the
# pipe so it applies to bash, not to curl):
# curl -fsSL https://schedule.example.lan/agent/linux/install.sh | \
# API_URL=https://schedule.example.lan API_TOKEN=stm_xxx bash
# sudo API_URL=https://schedule.example.lan API_TOKEN=stm_xxx bash
#
set -euo pipefail
@@ -17,16 +18,41 @@ INTERVAL_MINUTES="${INTERVAL_MINUTES:-15}"
if [[ "$EUID" -ne 0 ]]; then
echo "This installer must be run as root (it installs a systemd timer)." >&2
echo "If you're piping from curl, put sudo right after the pipe so it elevates bash, not curl:" >&2
echo " curl -fsSL ... | sudo API_URL=... API_TOKEN=... bash" >&2
exit 1
fi
for bin in curl jq systemctl; do
suggest_package_install() {
local pkg="$1"
if command -v apt-get >/dev/null 2>&1; then
echo " sudo apt-get update && sudo apt-get install -y $pkg"
elif command -v dnf >/dev/null 2>&1; then
echo " sudo dnf install -y $pkg"
elif command -v yum >/dev/null 2>&1; then
echo " sudo yum install -y $pkg"
elif command -v apk >/dev/null 2>&1; then
echo " sudo apk add $pkg"
elif command -v pacman >/dev/null 2>&1; then
echo " sudo pacman -S $pkg"
elif command -v zypper >/dev/null 2>&1; then
echo " sudo zypper install -y $pkg"
fi
}
for bin in curl jq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "Required dependency '$bin' is not installed." >&2
echo "Required dependency '$bin' is not installed. Try:" >&2
suggest_package_install "$bin" >&2
exit 1
fi
done
if ! command -v systemctl >/dev/null 2>&1; then
echo "systemctl was not found — this installer requires a systemd-based Linux distribution." >&2
exit 1
fi
echo "Installing Schedule Task Manager agent from $API_URL ..."
curl -fsSL "$API_URL/agent/linux/report-tasks.sh" -o "$INSTALL_DIR/schedule-task-manager-agent.sh"
@@ -69,4 +95,4 @@ echo "Installed. Running an initial report now..."
"$INSTALL_DIR/schedule-task-manager-agent.sh"
echo "Done. The agent reports every ${INTERVAL_MINUTES} minute(s) via the 'schedule-task-manager-agent.timer' systemd timer."
echo "To remove it later: curl -fsSL $API_URL/agent/linux/uninstall.sh | bash"
echo "To remove it later: curl -fsSL $API_URL/agent/linux/uninstall.sh | sudo bash"
+19 -1
View File
@@ -23,9 +23,27 @@ if [[ -z "$API_URL" || -z "$API_TOKEN" ]]; then
exit 1
fi
suggest_package_install() {
local pkg="$1"
if command -v apt-get >/dev/null 2>&1; then
echo " sudo apt-get update && sudo apt-get install -y $pkg"
elif command -v dnf >/dev/null 2>&1; then
echo " sudo dnf install -y $pkg"
elif command -v yum >/dev/null 2>&1; then
echo " sudo yum install -y $pkg"
elif command -v apk >/dev/null 2>&1; then
echo " sudo apk add $pkg"
elif command -v pacman >/dev/null 2>&1; then
echo " sudo pacman -S $pkg"
elif command -v zypper >/dev/null 2>&1; then
echo " sudo zypper install -y $pkg"
fi
}
for bin in curl jq; do
if ! command -v "$bin" >/dev/null 2>&1; then
echo "Required dependency '$bin' is not installed." >&2
echo "Required dependency '$bin' is not installed. Try:" >&2
suggest_package_install "$bin" >&2
exit 1
fi
done
+5 -2
View File
@@ -2,8 +2,9 @@
# Removes the Schedule Task Manager agent from this server: stops and deletes
# its systemd timer/service, the installed script, and its credentials file.
#
# Usage:
# curl -fsSL https://schedule.example.lan/agent/linux/uninstall.sh | bash
# Usage (must run as root — if not already root, put sudo right after the
# pipe so it applies to bash, not to curl):
# curl -fsSL https://schedule.example.lan/agent/linux/uninstall.sh | sudo bash
#
set -euo pipefail
@@ -13,6 +14,8 @@ SYSTEMD_DIR="/etc/systemd/system"
if [[ "$EUID" -ne 0 ]]; then
echo "This uninstaller must be run as root." >&2
echo "If you're piping from curl, put sudo right after the pipe so it elevates bash, not curl:" >&2
echo " curl -fsSL ... | sudo bash" >&2
exit 1
fi
+4 -4
View File
@@ -89,8 +89,8 @@ export default function ServersAdmin() {
<h3>Agent token for {newToken.server.name}</h3>
<p>This token is only shown once — copy it now.</p>
<code className="token-value">{newToken.token}</code>
<p>Install the agent on the server:</p>
<pre className="install-command">{`curl -fsSL ${window.location.origin}/agent/linux/install.sh | API_URL=${window.location.origin} API_TOKEN=${newToken.token} bash`}</pre>
<p>Install the agent on the server (run as root — put sudo right after the pipe, not before curl):</p>
<pre className="install-command">{`curl -fsSL ${window.location.origin}/agent/linux/install.sh | sudo API_URL=${window.location.origin} API_TOKEN=${newToken.token} bash`}</pre>
<button onClick={() => setNewToken(null)} className="btn">
Dismiss
</button>
@@ -100,8 +100,8 @@ export default function ServersAdmin() {
{uninstallFor && (
<section className="token-reveal uninstall-reveal">
<h3>Uninstall agent from {uninstallFor.name}</h3>
<p>Run this on the server to stop and remove the agent (its entry here is kept):</p>
<pre className="install-command">{`curl -fsSL ${window.location.origin}/agent/linux/uninstall.sh | bash`}</pre>
<p>Run this on the server as root to stop and remove the agent (its entry here is kept):</p>
<pre className="install-command">{`curl -fsSL ${window.location.origin}/agent/linux/uninstall.sh | sudo bash`}</pre>
<button onClick={() => setUninstallFor(null)} className="btn">
Dismiss
</button>