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
8 changes: 5 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -461,10 +461,11 @@ Three findings raised while writing [`host-setup/windows/`][host-setup-windows],

- **Align the Linux scripts onto "name one action" instead of "the last one given wins".** Overwriting `MODE` in the arg loop discards an intent silently, and it discards it in the dangerous direction: `--report --install` drops the safe action and keeps the one that changes the host.
- **Blocked by** - Nothing.
- **Issue** - [#673][issue-673].
- **Checked** - `main` at `1d5b076` on 2026-08-11, where all three scripts document last-wins, no documented example passes two actions, `bootstrap.sh` passes exactly one per `run_tool` call, and no test asserts the behavior.
- **Issue** - [#673][issue-673], broadened by [#767][issue-767] to carry the loader as well, which is what this change ships.
- **Checked** - `develop` at `63d244b` on 2026-08-16, where all four scripts document last-wins, no documented example passes two actions, `bootstrap.sh` passes exactly one per `run_tool` call, and no test asserts the behavior.
- **Settled** - The Windows tooling already refuses this way. That began as a constraint, since a PowerShell `param()` block records which switches were given and not their order, and the constraint produced the better behavior.
- **Open** - Nothing about the change itself, which is three `usage()` heredocs and three `parse_args()` bodies. The decision is only whether the fleet wants the stricter contract, and taking it deletes the differences-table row in [`host-setup/windows/README.md`][host-setup-windows] rather than leaving a permanent divergence.
- **Settled** - The change is four `usage()` heredocs and four `parse_args()` bodies now that `bootstrap.sh` carries the same contract, and taking it deletes the differences-table row in [`host-setup/windows/README.md`][host-setup-windows] rather than leaving a permanent divergence.
- **Open** - Nothing.

### Neither Host Bootstrap Has Run Against a Truly Fresh Host

Expand Down Expand Up @@ -626,6 +627,7 @@ Nothing is awaiting close today. [#578][issue-578] was the last entry here and c
[issue-672]: https://github.com/ptr727/ProjectTemplate/issues/672
[issue-673]: https://github.com/ptr727/ProjectTemplate/issues/673
[issue-729]: https://github.com/ptr727/ProjectTemplate/issues/729
[issue-767]: https://github.com/ptr727/ProjectTemplate/issues/767
[issue-769]: https://github.com/ptr727/ProjectTemplate/issues/769

<!-- Pull requests -->
Expand Down
29 changes: 19 additions & 10 deletions host-setup/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Stands a host up: upgrades its packages, installs the host tools, and configures
Fetches this repository and runs the tooling from that tree, so the tools and the rules that
describe them come from one revision rather than from whatever a host happens to hold.

Actions, the last one given wins, default --report on a terminal is the menu:
Actions, name one, default --report on a terminal is the menu:
-r, --report Report what each tool would do, change nothing
--host Share the sudo cache, upgrade packages, install the tools, configure git and
GitHub, install the skills
Expand Down Expand Up @@ -246,17 +246,19 @@ menu() {
# --- Entry ---

parse_args() {
local -a actions=()

while [[ $# -gt 0 ]]; do
case "$1" in
-r | --report) MODE="report" ;;
--host) MODE="host" ;;
--dev) MODE="dev" ;;
--upgrade) MODE="upgrade" ;;
--tools) MODE="tools" ;;
--github) MODE="github" ;;
--skills) MODE="skills" ;;
--sudo) MODE="sudo" ;;
--release) MODE="release" ;;
-r | --report) actions+=(report) ;;
--host) actions+=(host) ;;
--dev) actions+=(dev) ;;
--upgrade) actions+=(upgrade) ;;
--tools) actions+=(tools) ;;
--github) actions+=(github) ;;
--skills) actions+=(skills) ;;
--sudo) actions+=(sudo) ;;
--release) actions+=(release) ;;
-y | --yes) ASSUME_YES=true ;;
-n | --dry-run) DRY_RUN=true ;;
--keep) KEEP=true ;;
Expand All @@ -281,6 +283,13 @@ parse_args() {
esac
shift
done

# The order the actions were given is not a contract, so more than one is a refusal rather than the last one winning.
if ((${#actions[@]} > 1)); then
die "More than one action given (${actions[*]}), name one"
fi
[[ ${#actions[@]} -eq 1 ]] && MODE="${actions[0]}"
return 0
}

main() {
Expand Down
23 changes: 15 additions & 8 deletions host-setup/linux/install-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Usage: install-tools.sh [options] [tool ...]
Installs the host tools the fleet's repositories expect, from upstream where the distro package
trails upstream. With no tool named, every managed tool is selected.

Actions, the last one given wins, default --report:
Actions, name one, default --report:
-r, --report Report installed and available versions, change nothing
-i, --install Install what is missing, leave an installed tool at its version
-u, --upgrade Install what is missing and upgrade what is behind
Expand Down Expand Up @@ -1257,15 +1257,15 @@ configure_sudo_timestamp() {
# --- Entry ---

parse_args() {
local -a requested=()
local -a requested=() actions=()

while [[ $# -gt 0 ]]; do
case "$1" in
-r | --report) MODE="report" ;;
-i | --install) MODE="install" ;;
-u | --upgrade) MODE="upgrade" ;;
-l | --list) MODE="list" ;;
--sudo-timestamp) MODE="sudo-timestamp" ;;
-r | --report) actions+=(report) ;;
-i | --install) actions+=(install) ;;
-u | --upgrade) actions+=(upgrade) ;;
-l | --list) actions+=(list) ;;
--sudo-timestamp) actions+=(sudo-timestamp) ;;
-n | --dry-run) DRY_RUN=true ;;
-y | --yes) ASSUME_YES=true ;;
-o | --optional) WITH_OPTIONAL=true ;;
Expand All @@ -1288,7 +1288,13 @@ parse_args() {
shift
done

# Checked against the action that won rather than inside the loop, since the last action given is the one that runs.
# The order the actions were given is not a contract, so more than one is a refusal rather than the last one winning.
if ((${#actions[@]} > 1)); then
die "More than one action given (${actions[*]}), name one"
fi
[[ ${#actions[@]} -eq 1 ]] && MODE="${actions[0]}"

# The sudo-timestamp refusal needs the whole set of tool names a run asked for, so it is checked after the loop rather than inside it.
if [[ $MODE == "sudo-timestamp" && ${#requested[@]} -gt 0 ]]; then
die "--sudo-timestamp changes the host rather than a tool, so it takes no tool, and \"${requested[*]}\" names one"
fi
Expand All @@ -1307,6 +1313,7 @@ parse_args() {
else
SELECTED=("${TOOLS[@]}")
fi
return 0
}

main() {
Expand Down
15 changes: 12 additions & 3 deletions host-setup/linux/setup-github.sh
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ Usage: setup-github.sh [options]

Sets up git and GitHub on this host: the SSH key, the git configuration, and commit signing.

Actions, the last one given wins, default --status:
Actions, name one, default --status:
-s, --status Report what is set up and what is not, change nothing
-c, --configure Create the key, apply the configuration, and check both registrations
-h, --help Show this help
Expand Down Expand Up @@ -594,10 +594,12 @@ configure() {
# --- Entry ---

parse_args() {
local -a actions=()

while [[ $# -gt 0 ]]; do
case "$1" in
-s | --status) MODE="status" ;;
-c | --configure) MODE="configure" ;;
-s | --status) actions+=(status) ;;
-c | --configure) actions+=(configure) ;;
-n | --dry-run) DRY_RUN=true ;;
-y | --yes) ASSUME_YES=true ;;
--name)
Expand All @@ -623,6 +625,13 @@ parse_args() {
esac
shift
done

# The order the actions were given is not a contract, so more than one is a refusal rather than the last one winning.
if ((${#actions[@]} > 1)); then
die "More than one action given (${actions[*]}), name one"
fi
[[ ${#actions[@]} -eq 1 ]] && MODE="${actions[0]}"
return 0
}

main() {
Expand Down
17 changes: 13 additions & 4 deletions host-setup/linux/upgrade-host.sh
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Usage: upgrade-host.sh [options]
Upgrades this host. Packages within the current release are routine; the release itself is a
separate action, and is refused on Proxmox and on a distribution this script does not know.

Actions, the last one given wins, default --packages:
Actions, name one, default --packages:
-s, --status Report the host, what is upgradable, and what release it could move to
-p, --packages Upgrade the packages of the current release
-r, --release Upgrade the packages, then upgrade to the next release
Expand Down Expand Up @@ -585,11 +585,13 @@ upgrade() {
# --- Entry ---

parse_args() {
local -a actions=()

while [[ $# -gt 0 ]]; do
case "$1" in
-s | --status) MODE="status" ;;
-p | --packages) MODE="packages" ;;
-r | --release) MODE="release" ;;
-s | --status) actions+=(status) ;;
-p | --packages) actions+=(packages) ;;
-r | --release) actions+=(release) ;;
-n | --dry-run) DRY_RUN=true ;;
-y | --yes) ASSUME_YES=true ;;
-h | --help)
Expand All @@ -600,6 +602,13 @@ parse_args() {
esac
shift
done

# The order the actions were given is not a contract, so more than one is a refusal rather than the last one winning.
if ((${#actions[@]} > 1)); then
die "More than one action given (${actions[*]}), name one"
fi
[[ ${#actions[@]} -eq 1 ]] && MODE="${actions[0]}"
return 0
}

main() {
Expand Down
1 change: 0 additions & 1 deletion host-setup/windows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ This moved a boundary the tool used to hold: WSL used to be read-only here, and
| --- | --- | --- |
| `upgrade-host.sh --release` moves to the next distribution release | no peer | Windows Update owns a feature update, and an action pretending to drive one is the one thing this must not carry |
| `install-tools.sh` carries four functions per tool | `install-tools.ps1` carries one registry record per tool | Every source is `winget`, so the per-tool variation those functions exist for does not arise |
| Actions, the last one given wins | Actions, name one | A `param()` block records which switches were given and not their order, and refusing beats silently discarding an intent |
| `git-restore-mtime` is managed | not managed | The spec declares it not applicable on Windows, since it serves a Linux deploy path |
| `install-tools.sh` refuses docker entirely inside a WSL *distribution* | `install-tools.ps1` checks the WSL *platform* version before installing docker | A WSL distribution takes docker only from Docker Desktop's own WSL integration, and Windows needs WSL2 present for Docker Desktop's own backend |
| `sudo` re-runs a command as root | nothing elevates | `winget` raises UAC per installer, which is the path with the fewest failures |
Expand Down