Skip to content

Bootstrap repairs for Ubuntu 26.04: sudo, PHP packages, PHP-FPM, DNS and CA trust - #153

Merged
jakwinkler merged 7 commits into
mainfrom
fix/2.1.1-bootstrap-repair
Sep 24, 2026
Merged

jakwinkler merged 7 commits into
mainfrom
fix/2.1.1-bootstrap-repair

Conversation

@jakwinkler

Copy link
Copy Markdown
Contributor

Nine bugs found by running magebox bootstrap on a fresh Ubuntu 26.04 LTS machine. The 2.1.1 fixes were correct but never took effect, because each repair was guarded by a check that the broken state itself defeated. Four commits; the version stays 2.1.1 and the entries are folded into that release's changelog section.

Four of these are the same mistake: checking that a file exists instead of checking that the thing works.

Bootstrap could not repair anything

  • Sudoers were never rewritten. The step was skipped whenever /etc/sudoers.d/magebox existed, so every machine upgrading from an older release kept the wildcard rules sudo-rs rejects. It now always runs; the installer decides whether a rewrite is needed.
  • A failing repository ended the run. InstallPrerequisites ran apt update first and returned on error. A PPA with nothing for the running release makes apt exit 100, so bootstrap gave up before reaching the code that repairs that repository. Update failures are warnings now.

PHP could not be installed

  • Ondrej's PPA publishes nothing past Ubuntu 24.04 and is being folded into packages.sury.org, which does cover 26.04. Pinning the PPA's older suite (the first attempt) is worse than useless: those packages need libxml2, libicu74 and libzip4t64 versions 26.04 no longer ships, so every install failed on unsatisfiable dependencies. Bootstrap now picks the repository covering the running release and warns plainly when none does.
  • php-fpm.conf accumulated includes. Every run appended another MageBox pools include; duplicates define the same pool twice and an include from a run as another user matches nothing. Either stops PHP-FPM from starting, and a package whose service will not start makes every later dpkg operation fail, which is what broke the Blackfire and dnsmasq installs mid-run. The file is now rewritten to hold exactly one include.

Running as root broke the machine

Under sudo, bootstrap built the whole environment in /root: config, certificate authority, pools and an nginx include pointing into a directory nginx cannot read, which stopped nginx entirely. Bootstrap now refuses to run as root.

nginx, DNS and certificates

  • One missing certificate took every site offline. nginx refuses to start over a single vhost whose certificate is not on disk. Bootstrap now checks them, regenerates the ones MageBox manages and names the file to remove for any it does not.
  • systemd-resolved silently ignored MageBox. Config written through a temp file keeps mode 0600, and systemd-resolved parses its configuration after dropping privileges, so every restart logged Failed to open /etc/systemd/resolved.conf.d/magebox.conf: Permission denied. Config files are written 0644 now; callers needing stricter modes still set their own.
  • DNS was reported working when only dnsmasq answered. The check queried dnsmasq directly. Everything else goes through the system resolver, so browsers kept failing after a "resolves" line. Both are checked now.
  • A dnsmasq fallback left .test broken. The resolver override is written before dnsmasq is known to work; left behind it points every .test lookup at a resolver that is not running, so names failed instead of falling through to /etc/hosts. The fallback removes it.
  • dnsmasq counted as installed when only dnsmasq-base was. That package ships the binary without a service unit, so MageBox skipped the install and then failed to start the service.
  • "Local CA already installed" while browsers trusted another one. The check only looked for rootCA.pem. On a machine restored from another install the browser still trusted the previous authority and every local HTTPS site warned. The serial is now compared against the browser trust store.

Docker

A permission error on the socket was reported as a stopped daemon, sending users to restart something already running. The two are told apart, and the permission case prints the command that adds the user to the docker group.

Verification

Check Result
go test ./... 26 packages pass
golangci-lint v2 0 issues
go vet, gofmt clean
Live Ubuntu 26.04 machine nginx active, .test resolves, mailpit returns 200 over HTTPS

New tests cover the pure decisions: repository choice, the root guard, include normalization, Docker failure classification, certificate scanning, the systemd unit check, resolver cleanup and browser trust detection.

🤖 Generated with Claude Code

jakwinkler and others added 4 commits September 23, 2026 19:33
A run of 2.1.1 on Ubuntu 26.04 fixed nothing, because each repair was
guarded by a check that the broken state itself defeated.

Sudoers: bootstrap skipped the whole step whenever the file existed, so
machines carrying wildcard rules from an older release kept them. The
step now always runs and the installer decides whether to rewrite.

apt: InstallPrerequisites ran apt update first and returned on error. A
PPA with nothing for this release makes apt exit 100, so bootstrap gave
up before reaching the code that repairs that very repository. Both
update calls are warnings now, as is add-apt-repository's exit code.

nginx: one vhost naming a certificate that is not on disk stops nginx
from starting, taking every project offline. Bootstrap now checks the
certificates vhosts reference, regenerates the ones MageBox manages, and
names the file to remove for any it does not.

Docker: a permission error on the socket was reported as a stopped
daemon, sending the user to restart something already running. The two
are now told apart, with the docker group command printed for the first.

dnsmasq: Ubuntu's dnsmasq-base ships the binary without a service unit,
so MageBox considered dnsmasq installed, skipped the install and then
failed to start it. Installation now also requires the unit. And when
dnsmasq cannot be started, the systemd-resolved drop-in is removed
instead of being left pointing at a resolver that is not there, which
made .test names fail rather than fall through to /etc/hosts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-FPM

A bootstrap run on Ubuntu 26.04 got further but still failed, in three
new places.

PHP packages: pinning Ondrej's PPA to its newest suite made apt work but
produced packages that cannot be installed, because they depend on
libxml2, libicu74 and libzip4t64 versions 26.04 no longer ships. The PPA
itself says packages.sury.org is now canonical for this release, and
sury does publish for it. Bootstrap now picks the repository covering
the running release, and warns plainly when none does.

Root: run under sudo, bootstrap built the environment under /root and
added an nginx include pointing into a directory nginx cannot read, so
nginx would not start. It now refuses to run as root.

php-fpm.conf: every run appended another pools include. Duplicates
define the same pool twice and an include from a run as another user
matches nothing, so PHP-FPM refused to start — and a PHP-FPM that cannot
start makes every later dpkg operation on the package fail, which is
what broke the Blackfire and dnsmasq installs. The file is now rewritten
to hold exactly one include.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
systemd-resolved never read MageBox's drop-in. Config is written through
a temp file, which os.CreateTemp creates mode 0600, and cp preserves it;
systemd-resolved parses its configuration after dropping privileges, so
every restart logged "Failed to open /etc/systemd/resolved.conf.d/
magebox.conf: Permission denied" and ignored it. Files written this way
are now 0644, and callers needing something stricter still set it.

The DNS check hid this. It queried dnsmasq directly, which proves only
that dnsmasq works, so bootstrap printed "resolves to 127.0.0.1" while
browsers and curl could not resolve anything. It now asks the system
resolver as well and names which one answered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Bootstrap reported "Local CA already installed" whenever rootCA.pem
existed, which says nothing about trust. On a machine carried over from
another install, the browser store still trusted the previous authority,
so every local HTTPS site warned while bootstrap looked clean.

The CA's serial is now compared against the browser trust store, and
mkcert -install runs when they differ. The check answers "trusted" when
it cannot tell, so a machine without certutil never reinstalls pointlessly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

📦 PR build artifacts

Built from b87bbe4 — view run. Artifacts expire in 14 days.

Note: artifact download links require being signed in to GitHub with access to this repository.

jakwinkler and others added 3 commits September 23, 2026 21:37
MageBox filters out packages apt-cache does not know about, but apt gives
the same empty answer whether a package is genuinely missing or a sources
file could not be read. The PHP repository file MageBox wrote was
root-only, so apt-cache run as the user reported every php8.1 … php8.4
package as unavailable, and bootstrap skipped all of them while printing
success. The machine kept only the PHP version Ubuntu ships.

A permission warning now means "cannot tell" rather than "not available",
and apt decides: the install either works or fails with a real message.

The file mode itself is fixed in an earlier commit; this stops a
comparable mistake elsewhere from silently emptying the package list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Installing PHP still did nothing on Ubuntu 26.04, through a cascade this
commit breaks in two places.

php8.5-fpm was left half-configured by an earlier failure, and one such
package makes every apt install exit 100 regardless of what it is asked
to install. InstallPrerequisites returned on that error, so it never
reached the repository configuration below it — and without the
repository, every php8.1 … php8.4 package looked unavailable and was
skipped. Bootstrap now runs dpkg --configure -a when dpkg reports broken
packages, and treats a failure to install its base tools as a warning.

The repository file is also chmod'ed explicitly after writing, so an
install carrying a root-only file from an older MageBox is repaired even
if nothing rewrites the file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The guide still described pinning the PPA to an older suite, which was
the first attempt and does not work: those packages depend on library
versions a newer Ubuntu no longer ships. It now documents choosing the
repository that covers the release, and records why pinning a suite is
the wrong fix so it is not tried again.

Also documents the two repairs added since: the apt source file is made
readable, and half-configured packages are finished before installing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@jakwinkler
jakwinkler merged commit e8a5987 into main Sep 24, 2026
12 checks passed
@jakwinkler jakwinkler mentioned this pull request Sep 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant