Skip to content

install,provision: modules and roles, monitor-manager extracted, container CI - #19

Merged
krishna-bala merged 8 commits into
mainfrom
claude/roles-and-modules
Sep 4, 2026
Merged

krishna-bala merged 8 commits into
mainfrom
claude/roles-and-modules

Conversation

@krishna-bala

Copy link
Copy Markdown
Owner

Replaces the shell/desktop halves with modules/<name>/ (one tool's config + its provisioning) selected by roles/{server,workstation,desktop}; per-host values via hosts/*.env merged into ~/.config/dotfiles/host.env and a dotbot render: plugin for the two files that carry them; overlay seams that actually win (env.sh pre-gate, aliases last, gitconfig include at the bottom, tmux/kitty local includes, profiles.d for monitor profiles); apps/monitor-manager/ as a real package (monitor_manager, console script) installed with uv tool and constrained to uv.lock; ./doctor; tmux plugin submodules fixed and trimmed to tpm + nord-tmux.

CI now installs the server role from scratch on ubuntu:22.04 and 24.04 as a non-root user, checks the shell and tools, seeds an identity through ~/.gitconfig.local, runs doctor, and asserts a second provision.sh + install is a no-op; plus sha256-refusal and role-resolution tests, the monitor-manager suite (211 tests) and its uv tool install, and shellcheck over every tracked script.

Verified locally in docker on both images end to end. Three bugs the containers caught are fixed in-branch: git not upgrading from the PPA on 22.04 (pkg_ensure only installs missing packages), python3 undeclared for dotbot on a minimal 24.04, and git config --global not following includes in doctor.

Not yet verified: the desktop role on a real X11 machine (bspwmrc → monitor-manager on PATH, rendered Xresources/redshift.conf, ~/.config/bspwm as a real directory). ./doctor there is the quick check. Deferred: theme-as-data (P6 of the review), unused rich dependency, CLI/lid-state test gaps.

Review page: https://claude.ai/code/artifact/406327af-2e10-42f9-a611-feb4586321da

🤖 Generated with Claude Code

https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od

.gitmodules has listed tpm, nord-tmux, and tmux-sensible since #1, but
the tree only ever carried a gitlink for dotbot, so `git submodule
update --init --recursive` created nothing under tmux/plugins/ and
tmux.conf's `run '~/.tmux/plugins/tpm/tpm'` failed on every fresh clone.
Add the three gitlinks at their current upstream heads.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
The repo modelled one axis of variation - desktop or not - as a flag, but
the machines it serves vary along more: the new headless VPS is neither
half, and --no-desktop still gave it kitty, fonts, xclip, a rust
toolchain, and a source-built tree-sitter. Per-machine values (DPI,
redshift's location, polybar's hardware ids) were either symlinked to
every machine or seeded once and never updated, and the desktop half had
no overlay seam at all, which is why the work-laptop profiles #11 moved
out came back in #15.

Layout: modules/<name>/ holds one tool's config (install.conf.yaml,
sources module-relative) and the provisioning that makes it work
(provision.sh, standalone-runnable). roles/{server,workstation,desktop}
list modules, nesting via @ROLE; modules may declare `requires`, and a
selection that leaves one out is refused up front. ./install and
./provision.sh take --roles/--modules, save the selection to
~/.config/dotfiles/roles, and re-apply it on a bare run; --no-desktop
remains as an alias for --roles workstation. lib/roles.sh resolves,
lib/common.sh logs, lib/provision-lib.sh keeps every pin and sha256
helper and gains a package layer: pkg_ensure installs only what is
missing and refreshes the index once per run on first need, so a
converged machine touches neither apt nor sudo; pkg_candidate_version and
version_ge let the x11 module take the distro polybar (>= 3.7.0) and
picom (>= 13) where they are new enough instead of always source-building
22.04's replacements; require_supported_platform states the Debian-family
/ x86_64 limit up front. cli-tools goes first in every role because it
installs curl.

Per-machine values are data: hosts/defaults.env < hosts/<hostname>.env <
untracked ~/.config/dotfiles/local.env, merged by ./install into
~/.config/dotfiles/host.env. A dotbot `render:` directive
(lib/dotbot-plugins/render.py) substitutes ${NAME} into the two files
that carry such values - Xresources and redshift.conf are now templates
copied into place, refreshed on every install - and bspwmrc exports the
same file into the session so polybar's backlight/battery ids and
network-env.sh's fallback interface come from it too.

apps/monitor-manager/ is the Python monitor-profile system, extracted
from ~/.config/bspwm where its tests and lockfile were being symlinked:
the package is renamed lib -> monitor_manager, monitor-manager.py becomes
monitor_manager/cli.py behind a `monitor-manager` console script, and the
x11 module installs it as an editable uv tool constrained to uv.lock.
bspwmrc, apply-auto.sh, and monitor-switch.sh call the tool instead of
three different python/uv invocations. ProfileService reads a search
path - ~/.config/bspwm/profiles (the tracked profiles, linked
individually so ~/.config/bspwm is a real directory) then
~/.config/bspwm/profiles.d - which is the desktop half's overlay seam.
The reconciler's polybar env vars are named in one place (bar_env,
POLYBAR_ENV_VARS) and tests/test_polybar_contract.py holds the polybar
config to that list.

Overlay seams now win: bashrc sources ~/.config/dotfiles/env.sh before
the interactive gate (for ssh commands, cron, units), ~/.bash_aliases
after every tool block instead of before them, prepends rather than
assigns PROMPT_COMMAND, and guards the lsd aliases; gitconfig's include
moves to the bottom so ~/.gitconfig.local can override any key;
tmux.conf gains `source -q ~/.tmux.conf.local` and kitty.conf
`globinclude ~/.config/kitty.local.conf`. clipimg, the remote-side half
of the clipboard kitten, moves to cli-tools so servers get it; AGENTS.md
is also linked to ~/.codex/AGENTS.md; wacominit is its own per-host
module. launch.sh reads font_size from kitty.conf instead of duplicating
it and honours the cwd it computes.

Dead config goes: polybar's never-included user_modules.ini and the
unused wired/wireless-network modules, starship's [custom.foxdev] that
the explicit format never rendered, the stray top-level kitty theme
(now themes/aquarium_dark.conf). rofi's themes referenced an undefined
@ac; colors.rasi now defines it as bspwm's accent. dunstrc resolves rofi
and xdg-open on PATH rather than /usr/bin. The networkmanager_dmenu click
action pointed at a binary nothing provisions; nm-connection-editor ships
with network-manager-gnome. The one test that shells out to xrandr skips
without a DISPLAY instead of failing.

CI installs the server role from scratch in ubuntu:22.04 and ubuntu:24.04
containers as a non-root user, checks the shell and tools, and re-runs
both scripts asserting only [skip]/[note] output; tests verify_sha256's
refusal and install_release_binary's refusal to install on a mismatch;
tests role resolution; runs the monitor-manager suite and its uv tool
install; shellchecks every tracked script.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
For the saved (or given) selection: every link in each module's
install.conf.yaml resolves into this checkout, every render: destination
exists as a real file, and every tool a module's provision.sh pins
(found by grepping for `pin_satisfied <cmd> "$VAR"`) is installed at or
above the pin. Then which overlay seams are present, whether git has an
identity, and that the hooks and submodules are wired. Nonzero exit on
any problem; changes nothing.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
tpm stays only to load nord-tmux, the status-bar theme. tmux-sensible's
contribution was a few options (history-limit, display-time,
status-interval, focus-events, aggressive-resize, default-terminal); they
are now set directly in settings.conf, with tmux-256color and a Tc
override for kitty so truecolor works without the plugin.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
pkg_ensure installs only missing packages, so on a machine that already
had a distro git the git-core PPA was added and then never used - the
22.04 container run kept git 2.34.1, which cannot read the
feature.manyFiles index gitconfig turns on. Check the installed version
against the floor and apt-get install (an upgrade) when it is below.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
The container user has no git identity, which doctor rightly reports;
give it one the way a real machine would - ~/.gitconfig.local - and
assert git reads it, which also proves the include at the bottom of
gitconfig works. Then run doctor against the fresh install.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
dotbot runs on python3 and a minimal ubuntu:24.04 has none. It only ever
worked because the git module's PPA step pulled it in through
software-properties-common; now that a new-enough distro git skips that
step, the dependency has to be stated.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
`git config --global KEY` does not follow [include] directives (a plain
lookup does), so doctor reported a missing identity on every machine that
sets it the documented way, in ~/.gitconfig.local. Ask for includes
explicitly; CI's seam check does the same.

Claude-Session: https://claude.ai/code/session_01R1D4FTi7umCLFpkuR355od
@krishna-bala
krishna-bala merged commit c5cfc1a into main Sep 4, 2026
5 checks passed
@krishna-bala
krishna-bala deleted the claude/roles-and-modules branch September 4, 2026 00:22
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.

2 participants