Add agent uninstall script and UI reveal

Lets a server be cleanly removed from agent management: uninstall.sh
stops/disables the systemd timer and deletes the unit files, the
installed script, and the credentials env file, without touching the
server's entry or task history in the app. install.sh now prints the
uninstall command on success, and the Servers page has an "Uninstall"
button that reveals the same command (safe to show anytime since it
carries no secret).
This commit is contained in:
2026-09-02 22:21:05 +02:00
parent 5269499520
commit 0615866311
5 changed files with 79 additions and 0 deletions
+20
View File
@@ -85,6 +85,26 @@ Tasks that stop appearing in a server's report are marked **stale** rather
than deleted (so a temporary agent failure doesn't wipe history) and are
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
```
This stops and deletes the systemd timer/service, the installed script, and
`/etc/schedule-task-manager-agent.env` (which holds the API token). It does
not delete the server's entry or task history in the app — the same "Uninstall"
button on the Servers page shows this command, and "Delete" removes the entry
entirely if you no longer want it tracked.
## 5. Manually tracked tasks
Not everything is discoverable by the agent — e.g. a scheduled job running
inside a Docker container. Use **Add task** on the Dashboard to log those by
hand. Manually entered tasks are never touched by agent reports (they have
their own `origin`), so they won't be marked stale or overwritten by the next
sync.
## Environment variables
See [`.env.example`](.env.example) for the full list with descriptions.
+1
View File
@@ -69,3 +69,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"
+38
View File
@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# 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
#
set -euo pipefail
INSTALL_DIR="/usr/local/bin"
CONFIG_DIR="/etc"
SYSTEMD_DIR="/etc/systemd/system"
if [[ "$EUID" -ne 0 ]]; then
echo "This uninstaller must be run as root." >&2
exit 1
fi
echo "Removing Schedule Task Manager agent..."
if command -v systemctl >/dev/null 2>&1; then
systemctl disable --now schedule-task-manager-agent.timer >/dev/null 2>&1 || true
fi
rm -f \
"$SYSTEMD_DIR/schedule-task-manager-agent.timer" \
"$SYSTEMD_DIR/schedule-task-manager-agent.service" \
"$CONFIG_DIR/schedule-task-manager-agent.env" \
"$INSTALL_DIR/schedule-task-manager-agent.sh" \
/tmp/stm-agent-response.json
if command -v systemctl >/dev/null 2>&1; then
systemctl daemon-reload
fi
echo "Done. The agent no longer runs or reports from this server."
echo "Its entry (and task history) in Schedule Task Manager is untouched —" \
"delete it from the Servers page if you no longer want it tracked."
+15
View File
@@ -7,6 +7,7 @@ export default function ServersAdmin() {
const [hostname, setHostname] = useState("");
const [description, setDescription] = useState("");
const [newToken, setNewToken] = useState<{ server: ServerRecord; token: string } | null>(null);
const [uninstallFor, setUninstallFor] = useState<ServerRecord | null>(null);
const [error, setError] = useState<string | null>(null);
const load = () => api.servers.list().then((res) => setServers(res.servers));
@@ -96,6 +97,17 @@ export default function ServersAdmin() {
</section>
)}
{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>
<button onClick={() => setUninstallFor(null)} className="btn">
Dismiss
</button>
</section>
)}
<section className="server-list">
<h2>Servers</h2>
<table>
@@ -119,6 +131,9 @@ export default function ServersAdmin() {
<button onClick={() => handleRotate(s.id)} className="btn btn-small">
Rotate token
</button>
<button onClick={() => setUninstallFor(s)} className="btn btn-small">
Uninstall
</button>
<button onClick={() => handleDelete(s.id)} className="btn btn-small btn-danger">
Delete
</button>
+5
View File
@@ -375,6 +375,11 @@ tr.stale {
margin-bottom: 1.5rem;
}
.uninstall-reveal {
background: var(--surface);
border: 1px solid var(--border);
}
.token-value {
display: block;
background: var(--surface);