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
1 change: 1 addition & 0 deletions host-setup/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ resolve_ref() {

# An unauthenticated request is rate limited per address, so a busy network can lose the lookup while the download itself is fine.
# The run continues and says it cannot name its own revision, which is worth a warning rather than a refusal.
# This fallback is deliberate and gates no mutation: download_tree falls back to fetching $REF by name when RESOLVED is empty, exactly as it would if resolve_ref did not exist, and it has its own die on a real download failure.
warn "Could not resolve $REF to a commit, so this run cannot be attributed to one"
RESOLVED=""
return 0
Expand Down
24 changes: 19 additions & 5 deletions host-setup/linux/install-tools.sh
Original file line number Diff line number Diff line change
Expand Up @@ -241,8 +241,13 @@ apt_install_displacing() {
return 0
fi

# The simulation is what removals are previewed from, so a simulation that fails to run at all must not read the same as a simulation that ran and found nothing to remove.
local sim
sim=$(apt-get -s install "$package" 2>&1) ||
die "apt-get -s install $package failed, so removals cannot be previewed before the real install runs: $sim"

local -a removals=()
readarray -t removals < <(apt-get -s install "$package" 2>/dev/null | awk '/^Remv / { print $2 }')
readarray -t removals < <(awk '/^Remv / { print $2 }' <<<"$sim")
if [[ ${#removals[@]} -gt 0 ]]; then
log " Installing $package removes ${#removals[@]} package(s): ${removals[*]}"
log " Their dependencies are left installed, for a later apt autoremove to clean up"
Expand Down Expand Up @@ -1410,11 +1415,20 @@ configure_sudo_timestamp() {
"${SUDO[@]}" cmp -s "$staged" "$SUDOERS_FILE" 2>/dev/null && own_current=true

# Another file setting either option is named rather than merged into, since which one wins is the order sudo reads them in and not something this can decide.
local elsewhere
local elsewhere status=0
# A name holding a dot or ending in a tilde is one sudo skips, this run's own staged file included, so a setting in it is an override sudo never reads.
elsewhere=$("${SUDO[@]}" grep -rnsE '^[[:space:]]*Defaults.*timestamp_(type|timeout)' \
--exclude='*.*' --exclude='*~' --exclude="${SUDOERS_FILE##*/}" \
/etc/sudoers /etc/sudoers.d 2>/dev/null) || elsewhere=""
# Grep's own "no match" exit (1) is folded to 0 inside the privileged shell, so the status sudo hands back distinguishes only "sudo could not even run this" from "the scan ran", never grep's ordinary no-match case from a sudo failure that also happens to exit 1.
# shellcheck disable=SC2016 # $1/$2 are meant for the inner sh -c script, not this outer shell.
elsewhere=$("${SUDO[@]}" sh -c '
out=$(grep -rnsE "$1" --exclude="*.*" --exclude="*~" --exclude="$2" /etc/sudoers /etc/sudoers.d 2>&1)
rc=$?
printf %s "$out"
[ "$rc" -eq 1 ] && exit 0
exit "$rc"
' _ '^[[:space:]]*Defaults.*timestamp_(type|timeout)' "${SUDOERS_FILE##*/}") || status=$?
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if [[ $status -ne 0 ]]; then
die "Scanning /etc/sudoers and /etc/sudoers.d for other timestamp_type/timestamp_timeout entries failed: $elsewhere"
fi

# Only this user's own entry is ever a delete candidate; a different user's entry, or one with no user named at all, changes something beyond what this run was asked to change, so it is reported and left alone.
local -a delete_files=() unsafe_files=()
Expand Down
21 changes: 17 additions & 4 deletions host-setup/linux/upgrade-host.sh
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,16 @@ refresh_snaps() {
}

upgradable_count() {
apt list --upgradable 2>/dev/null | grep -c '/' || true
# A failed listing and a listing that genuinely found nothing upgradable both read as "no matches" through grep alone, so the two are told apart here rather than both printing 0.
# This only ever backs a status report, so the answer here is "unknown" rather than a die: nothing downstream mutates on the strength of this count.
local out status=0
out=$(apt list --upgradable 2>/dev/null) || status=$?
if [[ $status -ne 0 ]]; then
# Re-run once more, stdout discarded this time, so the diagnostic comes from a pipe head bounds in memory rather than a file this would otherwise have to size-cap and clean up itself.
printf 'unknown, apt list --upgradable failed (exit %s): %s' "$status" "$(apt list --upgradable 2>&1 >/dev/null | head -c 200 | tr '\n' ' ')"
return 0
Comment thread
coderabbitai[bot] marked this conversation as resolved.
fi
printf '%s package(s), against the lists as they stand' "$(grep -c '/' <<<"$out" || true)"
}

# --- Reboot ---
Expand Down Expand Up @@ -241,8 +250,12 @@ release_preconditions() {
die "Held packages block a release upgrade, unhold them first: $held"
fi

local audit
audit=$("${SUDO[@]}" dpkg --audit 2>/dev/null || true)
# A dpkg --audit that fails to run is not the same as one that runs and finds nothing, and only the second one clears the way into a release upgrade.
local audit status=0
audit=$("${SUDO[@]}" dpkg --audit 2>&1) || status=$?
if [[ $status -ne 0 ]]; then
die "dpkg --audit failed to run (exit $status), so half-configured packages cannot be ruled out before a release upgrade: $audit"
fi
if [[ -n $audit ]]; then
die "dpkg reports half-configured packages, fix them first: $audit"
fi
Expand Down Expand Up @@ -547,7 +560,7 @@ release_summary() {

status() {
log "Host : $(host_description)"
log "Upgradable: $(upgradable_count) package(s), against the lists as they stand"
log "Upgradable: $(upgradable_count)"
log "Release : $(release_summary)"

report_reboot
Expand Down
6 changes: 3 additions & 3 deletions repo-config/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Hub-only repository and branch configuration held as committed files, kept out of `.github/` (which holds the GitHub-consumed configuration: workflows, Dependabot). Downstream repositories carry no `repo-config/` directory. Apply and check commands run from a hub checkout at `main` and name the target repository.

- `main.json`, `develop.json`, and `operational/develop.json`: the canonical branch rulesets as the managed part of the writable API subset (`name`, `target`, `enforcement`, `conditions`, `rules`). `main.json` is shared. `develop.json` serves release repos, and `operational/develop.json` serves operational repos. `configure.sh check` compares the selected payloads with the live rulesets. `bypass_actors` is writable and deliberately unmanaged, so no payload declares one and nothing diffs it: who may bypass a ruleset is a human decision taken in the UI, which `configure.sh` preserves on `apply` and reports without asserting on `check`.
- `configure.sh`: run from a hub checkout at `main`, per [GOVERNANCE.md "Hub-Hosted Tooling"][governance-hub-hosted-tooling]. It resolves every payload path against the hub's `repo-config/` directory. Name the target repository explicitly, since the command defaults to whichever repository the shell is sitting in. `configure.sh apply owner/repo release|operational` creates or updates the settings, Dependabot security features, and rulesets idempotently. `configure.sh check owner/repo release|operational` is the read-only inverse and exits non-zero on drift. The model defaults to the registry `workflowModel` lookup. Pass it explicitly for a repository outside the registry.
- `main.json`, `develop.json`, and `operational/develop.json`: the canonical branch rulesets as the managed part of the writable API subset (`name`, `target`, `enforcement`, `conditions`, `rules`). `main.json` is shared. `develop.json` serves release repos, and `operational/develop.json` serves operational repos. `repo-config/configure.sh check owner/repo release|operational` compares the selected payloads with the live rulesets. `bypass_actors` is writable and deliberately unmanaged, so no payload declares one and nothing diffs it: who may bypass a ruleset is a human decision taken in the UI, which `repo-config/configure.sh` preserves on `apply` and reports without asserting on `check`.
- `configure.sh`: run from a hub checkout at `main`, per [GOVERNANCE.md "Hub-Hosted Tooling"][governance-hub-hosted-tooling]. It resolves every payload path against the hub's `repo-config/` directory. Name the target repository explicitly, since the command defaults to whichever repository the shell is sitting in. `repo-config/configure.sh apply owner/repo release|operational` creates or updates the settings, Dependabot security features, and rulesets idempotently. `repo-config/configure.sh check owner/repo release|operational` is the read-only inverse and exits non-zero on drift. The model defaults to the registry `workflowModel` lookup. Pass it explicitly for a repository outside the registry.
Comment thread
ptr727 marked this conversation as resolved.

## Rulesets

Expand All @@ -22,7 +22,7 @@ Publish credentials required per mechanism are enumerated in `spec/secrets.json`

## Repo Settings

The fleet-standard general settings live in [`settings.json`][settings-json] and are applied idempotently by `configure.sh apply` alongside the rulesets (`gh api PATCH /repos/{owner}/{repo}`). The two settings that depend on per-repo state, `has_discussions` (visibility) and `default_branch` (main-must-exist), are computed by the script, not stored in the file. `configure.sh apply` also enables Dependabot vulnerability alerts and automated security updates, fleet policy applied via the API rather than a `settings.json` key. `configure.sh check` validates all of these and exits non-zero on drift.
The fleet-standard general settings live in [`settings.json`][settings-json] and are applied idempotently by `repo-config/configure.sh apply owner/repo release|operational` alongside the rulesets (`gh api PATCH /repos/{owner}/{repo}`). The two settings that depend on per-repo state, `has_discussions` (visibility) and `default_branch` (main-must-exist), are computed by the script, not stored in the file. `apply` also enables Dependabot vulnerability alerts and automated security updates, fleet policy applied via the API rather than a `settings.json` key. `repo-config/configure.sh check owner/repo release|operational` validates all of these and exits non-zero on drift.

- **Default branch `main`** (the script sets it only when a `main` branch exists, never pointing the default at a missing branch).
- **Merge methods**: `Allow merge commits` and `Allow squash merging` on, **rebase off**, and each branch ruleset then picks its method (merge on `main`, squash on `develop`).
Expand Down
Loading