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
8 changes: 6 additions & 2 deletions SELF_HOST.md
Original file line number Diff line number Diff line change
Expand Up @@ -458,9 +458,13 @@ Prove it once, while the user is watching:
return to the desired release.

`manage uninstall` removes the service definition and installed code and keeps
`config` and `state`, reporting where they are. `manage purge` is the separate,
`config` and `state`, reporting where they are. It also keeps `manage` itself,
which is what makes the second step possible. `manage purge` is the separate,
irreversible operation that deletes them; it requires typing a confirmation
phrase and is never part of a reinstall.
phrase and is never part of a reinstall. Run them in that order — uninstall,
then purge — and purge finishes by printing the single command that clears
whatever is left: the install root, plus the log directory on Linux and macOS,
where it sits outside that root.

## Checkpoint 6: limits and backup

Expand Down
27 changes: 22 additions & 5 deletions deploy/local/install-linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -702,11 +702,14 @@ UNIT="$LABEL.service"
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="$ROOT/config/server.env"
STATE_DIR="$ROOT/state"
LOG_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/dormouse-server/logs"
# LOG_ROOT is the dormouse-owned directory the logs sit in — outside ROOT on a
# real install, and what "purge" names so it does not leave an empty one behind.
LOG_ROOT="${XDG_STATE_HOME:-$HOME/.local/state}/dormouse-server"
LOG_DIR="$LOG_ROOT/logs"
UNIT_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/systemd/user/$UNIT"
# A test install (DORMOUSE_INSTALL_ROOT) keeps its logs and unit inside its own
# root, so `manage` must follow them there rather than at the real HOME paths.
[ -d "$ROOT/logs" ] && LOG_DIR="$ROOT/logs"
[ -d "$ROOT/logs" ] && { LOG_DIR="$ROOT/logs"; LOG_ROOT="$ROOT"; }
[ -f "$ROOT/systemd/$UNIT" ] && UNIT_FILE="$ROOT/systemd/$UNIT"

if [ -t 1 ]; then
Expand Down Expand Up @@ -1167,7 +1170,8 @@ cmd_uninstall() {
printf 'It PRESERVES your configuration and state:\n'
printf ' config : %s\n' "$ROOT/config"
printf ' state : %s\n' "$STATE_DIR"
printf '\nUse "manage purge" separately to delete those irreversibly.\n\n'
printf '\nThis script is left in place so "purge" can still delete them\n'
printf 'irreversibly afterwards:\n\n "%s" purge\n\n' "$ROOT/bin/manage"
if [ ! -t 0 ]; then
printf 'refusing to uninstall with no terminal to confirm at\n' >&2
return 1
Expand All @@ -1189,8 +1193,12 @@ cmd_uninstall() {
else
printf 'left the Serve config alone (it does not point at 127.0.0.1:%s)\n' "$PORT"
fi
rm -rf "$ROOT/releases" "$ROOT/current" "$ROOT/previous" "$ROOT/bin" "$ROOT/run"
# bin/run-server, not bin: this script lives there too, and "purge" — the
# command the message above points at — is unreachable once it is deleted.
rm -rf "$ROOT/releases" "$ROOT/current" "$ROOT/previous" "$ROOT/run"
rm -f "$ROOT/bin/run-server"
printf '\nuninstalled. config and state remain at:\n %s\n %s\n\n' "$ROOT/config" "$STATE_DIR"
printf 'delete them irreversibly with:\n\n "%s" purge\n\n' "$ROOT/bin/manage"
printf 'lingering, if you enabled it, is left as it is: loginctl disable-linger %s\n\n' "$USER"
}

Expand All @@ -1204,6 +1212,15 @@ cmd_purge() {
if [ "$reply" != "DELETE DORMOUSE STATE" ]; then printf 'aborted\n'; return 1; fi
rm -rf "$STATE_DIR" "$ROOT/config"
printf 'purged.\n'
# bin/run-server is what "uninstall" removes, so its absence means the service
# and the code are already gone and this script is the last thing standing. It
# cannot delete itself out from under the shell running it, so say how. The
# logs live outside ROOT on a real install, so LOG_ROOT has to be named too or
# the printed command leaves them behind.
if [ ! -e "$ROOT/bin/run-server" ]; then
printf '\nthe service and code were already uninstalled; what remains is\n'
printf 'this script and the logs:\n\n rm -rf "%s" "%s"\n\n' "$ROOT" "$LOG_ROOT"
fi
}

case "${1:-status}" in
Expand All @@ -1227,7 +1244,7 @@ usage: manage <command>
show-password warn, then display the setup password locally
serve re-apply the Tailscale Serve mapping for this server
rollback switch to the retained previous release, preserving state
uninstall remove the unit + code (keeps config and state)
uninstall remove the unit + code (keeps config, state, and this script)
purge irreversibly delete config and state
USAGE
exit 64
Expand Down
21 changes: 18 additions & 3 deletions deploy/local/install-macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -962,7 +962,8 @@ cmd_uninstall() {
printf 'It PRESERVES your configuration and state:\n'
printf ' config : %s\n' "$ROOT/config"
printf ' state : %s\n' "$STATE_DIR"
printf '\nUse "manage purge" separately to delete those irreversibly.\n\n'
printf '\nThis script is left in place so "purge" can still delete them\n'
printf 'irreversibly afterwards:\n\n "%s" purge\n\n' "$ROOT/bin/manage"
if [ ! -t 0 ]; then
printf 'refusing to uninstall with no terminal to confirm at\n' >&2
return 1
Expand All @@ -983,8 +984,12 @@ cmd_uninstall() {
else
printf 'left the Serve config alone (it does not point at 127.0.0.1:%s)\n' "$PORT"
fi
rm -rf "$ROOT/releases" "$ROOT/current" "$ROOT/previous" "$ROOT/bin" "$ROOT/run"
# bin/run-server, not bin: this script lives there too, and "purge" — the
# command the message above points at — is unreachable once it is deleted.
rm -rf "$ROOT/releases" "$ROOT/current" "$ROOT/previous" "$ROOT/run"
rm -f "$ROOT/bin/run-server"
printf '\nuninstalled. config and state remain at:\n %s\n %s\n\n' "$ROOT/config" "$STATE_DIR"
printf 'delete them irreversibly with:\n\n "%s" purge\n\n' "$ROOT/bin/manage"
}

cmd_purge() {
Expand All @@ -997,6 +1002,16 @@ cmd_purge() {
if [ "$reply" != "DELETE DORMOUSE STATE" ]; then printf 'aborted\n'; return 1; fi
rm -rf "$STATE_DIR" "$ROOT/config"
printf 'purged.\n'
# bin/run-server is what "uninstall" removes, so its absence means the
# LaunchAgent and the code are already gone and this script is the last thing
# standing. It cannot delete itself out from under the shell running it. The
# logs live outside ROOT on a real install, so LOG_DIR has to be named too or
# the printed command leaves them behind. (~/Library/Logs/Dormouse Server is
# dormouse-owned, so deleting it leaves no empty directory behind.)
if [ ! -e "$ROOT/bin/run-server" ]; then
printf '\nthe LaunchAgent and code were already uninstalled; what remains\n'
printf 'is this script and the logs:\n\n rm -rf "%s" "%s"\n\n' "$ROOT" "$LOG_DIR"
fi
}

case "${1:-status}" in
Expand All @@ -1020,7 +1035,7 @@ usage: manage <command>
show-password warn, then display the setup password locally
serve re-apply the Tailscale Serve mapping for this server
rollback switch to the retained previous release, preserving state
uninstall remove LaunchAgent + code (keeps config and state)
uninstall remove LaunchAgent + code (keeps config, state, this script)
purge irreversibly delete config and state
USAGE
exit 64
Expand Down
45 changes: 35 additions & 10 deletions deploy/local/install-windows.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1747,7 +1747,10 @@ function Invoke-Uninstall {
Write-Host " config : $Root\config"
Write-Host " state : $StateDir"
Write-Host ""
Write-Host 'Use "manage purge" separately to delete those irreversibly.'
Write-Host 'This script is left in place so "purge" can still delete them'
Write-Host 'irreversibly afterwards:'
Write-Host ""
Write-Host (' "{0}\bin\manage.cmd" purge' -f $Root)
Write-Host ""
if ([Console]::IsInputRedirected) {
[Console]::Error.WriteLine('refusing to uninstall with no terminal to confirm at')
Expand Down Expand Up @@ -1776,9 +1779,13 @@ function Invoke-Uninstall {
Write-Host "left the Serve config alone (it does not point at 127.0.0.1:$PORT)"
}

foreach ($p in @((Join-Path $Root 'releases'), (Join-Path $Root 'bin'), (Join-Path $Root 'run'))) {
foreach ($p in @((Join-Path $Root 'releases'), (Join-Path $Root 'run'))) {
Remove-Tree $p
}
# bin\run-server.ps1, not bin: this script lives there too, and "purge" -- the
# command the message above points at -- is unreachable once it is deleted.
$runServer = Join-Path $Root 'bin\run-server.ps1'
if (Test-Path -LiteralPath $runServer) { [IO.File]::Delete($runServer) }
foreach ($p in @($CurrentPointer, $PreviousPointer)) {
if (Test-Path -LiteralPath $p) { [IO.File]::Delete($p) }
}
Expand All @@ -1787,6 +1794,10 @@ function Invoke-Uninstall {
Write-Host " $Root\config"
Write-Host " $StateDir"
Write-Host ""
Write-Host "delete them irreversibly with:"
Write-Host ""
Write-Host (' "{0}\bin\manage.cmd" purge' -f $Root)
Write-Host ""
return 0
}

Expand All @@ -1805,6 +1816,17 @@ function Invoke-Purge {
Remove-Tree $p
}
Write-Host 'purged.'
# bin\run-server.ps1 is what "uninstall" removes, so its absence means the
# Scheduled Task and the code are already gone and this script is the last
# thing standing. It cannot delete itself out from under the shell running it.
if (-not (Test-Path -LiteralPath (Join-Path $Root 'bin\run-server.ps1'))) {
Write-Host ""
Write-Host "the Scheduled Task and code were already uninstalled; this script"
Write-Host "is all that remains:"
Write-Host ""
Write-Host (' Remove-Item -Recurse -Force "{0}"' -f $Root)
Write-Host ""
}
return 0
}

Expand All @@ -1830,7 +1852,7 @@ usage: manage <command>
show-password warn, then display the setup password locally
serve re-apply the Tailscale Serve mapping for this server
rollback switch to the retained previous release, preserving state
uninstall remove the Scheduled Task + code (keeps config and state)
uninstall remove the Scheduled Task + code (keeps config, state, this script)
purge irreversibly delete config and state
"@
exit 64
Expand All @@ -1845,13 +1867,16 @@ usage: manage <command>
@echo off
rem Installed by deploy/local/install-windows.ps1.
rem The trailing "& exit" matters. cmd.exe seeks back into this file after
rem every command, and "manage uninstall" deletes bin -- this very file -- so
rem that read fails with "The system cannot find the path specified." and
rem returns 1, making a clean uninstall look broken. "exit" ends cmd.exe
rem outright so it never seeks back, and with no argument it exits with the
rem current ERRORLEVEL. "exit /b" does NOT work here: it only returns from the
rem batch, which still requires reading the file. Tradeoff: calling this .cmd
rem from another batch script ends that script too; it is meant to be run
rem every command, so a manage command that removes the file it is being read
rem from makes that read fail with "The system cannot find the path specified."
rem and return 1, making a clean run look broken. "uninstall" used to do exactly
rem that -- it deleted bin, this very file included. It now deletes only
rem bin\run-server.ps1, so nothing manage does removes this file; the guard is
rem kept so the exit code does not depend on that staying true. "exit" ends
rem cmd.exe outright so it never seeks back, and with no argument it exits with
rem the current ERRORLEVEL. "exit /b" does NOT work here: it only returns from
rem the batch, which still requires reading the file. Tradeoff: calling this
rem .cmd from another batch script ends that script too; it is meant to be run
rem directly.
"$POWERSHELL_EXE" -NoProfile -ExecutionPolicy Bypass -File "%~dp0manage.ps1" %* & exit
"@
Expand Down
17 changes: 17 additions & 0 deletions docs/specs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,23 @@ nonzero on any failure), `logs`, `restart`, `show-password`, `serve` (re-apply
the Serve mapping after a dev session repointed it), `rollback`, `uninstall`,
and the separately-confirmed `purge`.

Teardown is two steps in that order, and `uninstall` has to leave `manage`
itself behind for the second one to be reachable at all: it removes the service
definition, the releases, the pointers, `run/` and `bin/run-server`, but not the
`bin` directory `manage` lives in — deleting that would strand `config/` and
`state/`, the data the message it prints tells you to run `purge` for. `purge`
deletes `state/` and `config/` after its typed confirmation and, when
`bin/run-server` is already gone, closes by printing the one command that
removes what is left; it cannot delete itself out from under the shell running
it. That command names the dormouse-owned log directory alongside the install
root, because on Linux and macOS the logs live outside it — `LOG_ROOT`
(`$XDG_STATE_HOME/dormouse-server`) and `~/Library/Logs/Dormouse Server`
respectively, each named at the level dormouse owns so no empty directory
survives. On Windows `logs` is inside the root, so the root alone is enough.
Source of truth:
`cmd_uninstall` / `cmd_purge` (`Invoke-Uninstall` / `Invoke-Purge` on Windows)
in the `manage` script each installer generates.

The Host that connects to such a server needs a build whose baked relay
allowlist admits the origin — see "Where a Host may reach a relay server"
above; a `*.ts.net` origin requires `DORMOUSE_REMOTE_CONNECT_SRC` at build time.
Expand Down