Skip to content
Merged
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
26 changes: 26 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -962,3 +962,29 @@ jobs:
run: bun install --frozen-lockfile
- name: Run process and terminal regressions
run: bun test tests/daemon-command.test.ts tests/daemon-orphan.test.ts tests/host-exec-lifetime.test.ts tests/host-exec-tty.test.ts tests/shell-observation.test.ts tests/shared-host-lifecycle.test.ts tests/project-shared-helpers.test.ts
# The daemon install flows run the real CLI, which checks the host's own
# system PATH (and what /usr/bin links into). GitHub's Ubuntu image
# leaves trees under /usr and /opt writable by everyone, so this one
# job makes them owner-writable, as on a stock install, and requires
# the flows to run: the protected system directory decision then has a
# hosted Linux check (HACK-2052). The regular test shards skip them.
- name: Harden runner system trees for the daemon install flows
run: |
sudo find /usr /opt -xdev -perm /022 \( -type f -o \( -type d ! -perm -1000 \) \) -print0 > "$RUNNER_TEMP/writable.list"
echo "removing group/other write from $(tr -cd '\0' < "$RUNNER_TEMP/writable.list" | wc -c) entries"
sudo xargs -0 -r chmod go-w < "$RUNNER_TEMP/writable.list"
# Files there owned by another regular account (uid >= UID_MIN, as
# the product's isSystemAccount draws the line; such as a uid 1000
# geckodriver reached from /usr/bin) go to root, as on a stock
# install. System accounts' files stay: the protected system
# directory rule must still meet them. Links are changed
# themselves, never followed.
uid_min=$(awk '$1 == "UID_MIN" { print $2 }' /etc/login.defs 2>/dev/null)
uid_min=${uid_min:-1000}
sudo find /usr /opt -xdev -uid +$((uid_min - 1)) ! -uid "$(id -u)" -print0 > "$RUNNER_TEMP/foreign.list"
echo "giving $(tr -cd '\0' < "$RUNNER_TEMP/foreign.list" | wc -c) entries owned by other regular accounts (uid >= $uid_min) to root"
sudo xargs -0 -r chown -h root < "$RUNNER_TEMP/foreign.list"
- name: Run daemon install flows
env:
HACK_TEST_REQUIRE_INSTALL_FLOWS: "1"
run: bun test tests/daemon-systemd-cli.test.ts
26 changes: 24 additions & 2 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,15 +366,37 @@ cancels a restart systemd has scheduled after a crash. When systemd runs or is
about to restart `hackd.service` but the managed unit file is gone (or the unit
is loaded from another directory), `stop` refuses and prints the
`systemctl --user` commands to review and stop it, instead of reporting hackd as
stopped. `uninstall` does the same in that case instead of reporting
stopped, unless the unit is shown to be another home's (below). `uninstall` does the same in that case instead of reporting
that nothing was installed. If systemd cannot report the unit's state at all (the query
fails, times out, loses the user bus, or `systemctl` cannot be run), both refuse
in the same way. Only a unit systemd reports as inactive or not found, or a
failed query whose only message names `hackd.service` itself as absent
("Unit hackd.service could not be found."), counts as nothing to do. `start` and `stop` act on the unit only when its effective environment
serves the shell's Hack home. A unit that serves another Hack home is left alone:
`start` starts this home's hackd directly (its socket, pid file and lock live in
this home's `daemon/` directory), and `stop` stops only this home's own hackd.
this home's `daemon/` directory), and `stop` stops only this home's own hackd. When the unit has a live main
process, `stop` proves from that process which Hack home it serves (a hackd
holding a socket in this home's `daemon/` directory), not from the loaded
configuration: a reload without a restart changes the configuration, not the
running daemon. A hackd main process that does not hold this home's socket is
left alone only with positive evidence that it is another home's daemon: the
configuration names another home, and the process is proven (identity stable)
to hold a socket in that home's `daemon/` directory, or it exited while being
checked. Then the unit is left alone and this home's own hackd is still
stopped, whether or not the managed unit file is present. Without that
evidence `stop` refuses: the daemon may still be starting (it has not bound
its socket yet), its sockets may be unreadable, or a reload changed the
configuration to this home under a running daemon; the message says to run
`stop` again or how to stop it by hand. A main process that cannot be
identified (its process details cannot be read, or it is not a hackd) is
refused, with the `systemctl --user status` and `stop` commands. With no live
process (a pending restart), the configuration decides, and `stop` still
cancels the restart. A failed query of the unit's state or main process is
not "no process": it is refused with the manual commands, installed unit or
not. Just before `systemctl --user stop`, `stop` reads the unit's state again
and refuses if the main pid changed or the read fails. That narrows the gap
without closing it: `systemctl stop` acts on whatever runs when it lands, so a
change after that read, or a pid reused in between, is not detected.
When the Hack home cannot be read with certainty (for example an
`EnvironmentFile=`, an `ExecSearchPath=`, a `PassEnvironment=` naming
`HACK_HOME`, `HOME` or `PATH`, a relative `HOME`, or an escaped quote or
Expand Down
Loading
Loading