Commit Graph
8 Commits
Author SHA1 Message Date
pastilhasandClaude Opus 5 f896d4882f one section per concern, each announced and confirmed before it acts
Three sections where there were two, and none of them touches the machine until
you say so:

  2. System update      upgrades what is already installed
  3. Core utils         what the distribution provides
  4. Command-line tools lazydocker, lazygit, starship, fastfetch

The split matters because these are different kinds of change and deserve
separate answers. System update is the only step in the whole script that moves
versions of software already on the machine; core utils only ever adds what is
absent; and the four tools are upstream binaries the distribution does not ship
at all. Previously the update and the core packages were one step and the tools
were tacked onto the end of it, so agreeing to "essentials" meant agreeing to all
three at once.

Every section now prints what it will install and what it is leaving alone, then
asks. Enter means yes — unlike the machine-role question, which has no default,
because these are "do the thing you already asked for" and making twenty of them
require a deliberate keystroke would train people to hold the y key down.
ASSUME_YES=1 answers all of them for an unattended run, and EOF fails with that
named rather than spinning.

Refusing is recorded rather than glossed: LAST_SKIPPED feeds the summary, so a
declined section reads "Core utils: SKIPPED by request — cowsay neofetch" instead
of quietly reporting nothing installed.

Nothing to install means no prompt at all — there is nothing to agree to.

announce_plan takes the array NAMES rather than their contents, because once a
list has been through word splitting an empty one cannot be told from a missing
one.

Verified all three paths: accept, refuse, and nothing-to-do.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:37:31 +00:00
pastilhasandClaude Opus 5 a5ef9f7662 report what a section actually installed, not what it was asked for
The summary claimed credit for everything in a section's list, including the
packages it had just decided to leave alone — so a run that installed nothing
still ended with "Command-line tools: lazydocker lazygit starship fastfetch".
The announce above it said "nothing, all present" in the same breath.

pkg_install and tools_install now record LAST_INSTALLED and LAST_KEPT, and
summarise_last turns those into one honest line:

  Core packages installed: btop tmux (17 already present)
  Command-line tools: already present, nothing installed

Verified all three shapes — everything present, nothing present, and mixed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:34:34 +00:00
pastilhasandClaude Opus 5 5cb243eed9 port into a clean script instead of editing the original in place
Your call, and the right one. Editing in place let mis-grouped code sit unnoticed
until it scrolled past in a live run — which is exactly how the four upstream
binaries buried in "System Update & Essentials" were found. Porting forces the
question of where each thing belongs before it runs, not after.

machine-setup.sh now contains only what has actually been worked through:
pre-flight, system update and core packages, command-line tools, and the summary.
1149 lines down to 172. The sections still to come are listed in a NOT PORTED YET
block, in order, and each arrives as its own commit.

The original is beside the other superseded scripts as
scripts/setup-old/setup-ubuntu.sh — verified byte-identical to the live
/root/ubuntu-setup copy — so porting reads from a file in the repo rather than
from root's home.

Two claims trimmed from the ported summary, because they were true of the old
script and not of this one yet: it reported the shell as "zsh (Oh My Zsh +
Starship)" unconditionally, and told you to reconnect as a user it had not
created. Replaced with what pre-flight actually knows — system, role, user.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:30:29 +00:00
pastilhasandClaude Opus 5 7622239949 split the upstream binaries out of the package section
lazydocker, lazygit, starship and fastfetch were buried inside "System Update &
Essentials", after the package install and with no announcement — so a run
appeared to be installing system packages and then started pulling tarballs and
printing a five-shell starship tutorial. They are a different thing: upstream
binaries on their own release cadence, not anything the distribution ships. Now
their own step, announced in the same shape as the package section.

Each is checked before it is fetched. The original re-ran every installer on
every run, which is why a machine that already had starship got it reinstalled
along with its "add this to your ~/.zshrc" instructions — advice this script
does not want followed, since it writes the shell config itself. Its output is
now dropped; errors still surface.

Two real bugs fixed on the way:

  lazygit's asset name was hardcoded to x86_64, so on arm64 the download 404s
  and tar fails partway through the run. It now maps ARCH, and spells the
  architectures the way lazygit does rather than the way we do.

  The version was extracted with `tr -d 'v'`, which deletes every v in the
  string rather than the leading one. `${version#v}` instead.

fastfetch stays a package but stops assuming the PPA is needed: Ubuntu picked
it up in 24.10, so the repository is now checked first and the PPA added only
where the archive has nothing. Verified on this host — noble genuinely has no
candidate, so the PPA is still the only source here.

Verified both branches of tools_install by stubbing the presence check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:28:46 +00:00
pastilhasandClaude Opus 5 bba6d854bc stop the run at the end of the rewritten sections
A WIP boundary after section 2 so the finished part can be run start to finish
on its own, without the untouched sections below acting on the machine. It moves
down as each section is worked through and goes away when the walk ends.

Also ignores .setup-progress, which the script writes beside itself and is
per-machine. The exit message names it, because with it in place a second run
skips section 2 and the rewritten part cannot be re-felt from scratch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:25:38 +00:00
pastilhasandClaude Opus 5 dbdef23d29 install what is missing and keep what is there, per package manager
lib/packages.sh, and section 2 wired to it.

The rule it exists to enforce: `apt-get install <present-package>` is not a
no-op, it upgrades the package if the repository has a newer one. On a machine
somebody already uses that silently moves a version they chose, and a setup
script is the last thing that should do that behind their back. pkg_install
queries the package database first and names only the genuinely absent packages
on the command line — a package already installed is never passed to apt at all.

It also says so out loud, every time, because a provisioning run should not be
opaque about what it is doing to the machine:

  :: Core packages — installs what is missing, keeps what you already have
       already here: curl ca-certificates gnupg git jq …
       to install:   btop tmux

Section 2's flat list of 19 is now pkgs_core(), split per package manager rather
than through a canonical-name table with overrides. The names genuinely disagree
(build-essential/base-devel, fd-find/fd) and three of them are not packages
elsewhere at all — apt-transport-https, lsb-release and software-properties-common
are apt concepts that exist to let later steps add the Docker repo and the
fastfetch PPA. A `case $PM` shows what each system actually gets, in one place.

Of those 19, six are load-bearing and the rest are the environment. Only
build-essential reaches beyond itself: it is a meta-package, so on a box with a
pinned gcc it pulls the distribution default alongside. Noted where it is
declared; it is the first thing to move out of core if that ever bites.

apt-get upgrade stays, but as its own announced step — it is the one place that
deliberately moves versions, rather than something that happens as a side effect
of asking for a tool.

DEBIAN_FRONTEND=noninteractive and NEEDRESTART_MODE=a now live inside the
helpers. needrestart has been on by default since Ubuntu 22.04 and stops to ask
which services to restart, which is how an unattended run ends up silently
waiting for a keypress.

dpkg-query on the status field rather than `dpkg -s`, which also succeeds for a
package removed but leaving its config behind — that state would read as present
and never be reinstalled.

Verified against this host's real dpkg database: all 19 report present, and a
mixed list correctly passes only the absent ones through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:19:46 +00:00
pastilhasandClaude Opus 5 41ff8030e9 ask what the machine is for, once, in pre-flight
MACHINE_ROLE is homelab, vps or dev, and several steps have a different right
answer per role with no way to work it out themselves: whether the address is
yours to pin (static IP), whether the box faces the open internet (fail2ban, SSH
hardening, UFW), and whether it is allowed to sleep (suspend, logind).

Asked in pre-flight rather than at each point of use. The steps that care run
from swap through to the firewall, and being asked "is this a VPS?" for the
fourth time halfway down a provisioning run is how people start answering
without reading.

The default offered is guessed from whether this machine's own address is in
RFC1918 space, which beats asking whether it is virtualised — a homelab is very
often a VM on Proxmox and would be misread as rented — and is the same fact most
of the branches turn on anyway. A graphical session means dev; so does macOS.
It is only ever a suggestion the user confirms.

MACHINE_ROLE in the environment answers it ahead of time for an unattended run,
which is why it is declared with :- rather than a plain assignment. The first
version wiped the caller's value before ask_machine_role ever saw it; caught by
running with MACHINE_ROLE=vps and watching the menu appear anyway.

Verified: guesses vps on this host (public IPv4, no DISPLAY, no display
manager), env override takes, and a bad value fails with the three valid ones
named. Nothing consumes the role yet — the steps get wired as each is worked
through.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:11:47 +00:00
pastilhasandClaude Opus 5 44141faf0a split machine-setup into an entry point and a base library
Structure before the work rather than during it: scripts/machine-setup.sh becomes
scripts/setup/machine-setup/, with the script itself as the entry point and
lib/base.sh holding what every part of it needs.

  machine-setup.sh   pre-flight and the numbered sections, for now
  lib/base.sh        shared state, output, the step/resume machine, prompts,
                     and OS detection

The rule for lib/ is definitions only — nothing there installs, writes or
restarts anything, so sourcing it is safe from anywhere. That is why the ERR
trap stayed in the entry point: a trap is a side effect on whoever sources it.

Behaviour is unchanged. Verified by diffing the moved region against the previous
commit: identical set of functions, and the only differences are added comments,
section banners, fail() reformatted onto three lines, and one new line — a guard
against double-sourcing, which matters because steps will source this directly
once they move out, and a second pass would reset SUMMARY.

The sections are still one 1111-line block below pre-flight; they move into
steps/ as each is worked through. The script also still reads ssh-keys.zip,
.tmux.conf and ufw-docker-rules.conf from SCRIPT_DIR, which is now this
directory, so those three steps warn and skip until the files follow it here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 17:02:15 +00:00