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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
- name: Check installer symlink safety
run: ./scripts/check-install-symlinks.sh

- name: Check pure Nix preflight contract
- name: Check Nix Home Manager preflight contracts
run: bash scripts/test-nix-preflight.sh

- name: Validate TOML configs
Expand Down
53 changes: 28 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# dotfiles

Configuración local extraida de `cortex`: terminal, shell, prompt, helpers de AI CLI y herramientas de desarrollo para macOS.
Configuración local extraída de `cortex`: terminal, shell, prompt, helpers de AI CLI y herramientas de desarrollo para macOS, con un primer perfil Home Manager para Linux headless.

## CI

Expand Down Expand Up @@ -54,41 +54,44 @@ El instalador macOS:
5. Intenta arrancar/recargar `sketchybar`, `yabai` y `skhd` sin cortar la instalación si macOS requiere permisos
6. Crea `local/env.zsh` desde el template

## Límite Nix + Home Manager (W1)
## Home Manager: límites y validación

W1 agrega solamente una base de Flake y Home Manager al repositorio. No instala Nix, no genera `flake.lock`, no activa Home Manager y no cambia ningún archivo del host.
La Flake declara configuraciones puras para macOS y Linux como parte de [cortex-dotfiles #58](https://github.com/barbatdev/cortex-dotfiles/issues/58). Esta unidad no instala Nix, no activa Home Manager, no cambia el login shell y no modifica archivos del host. `flake.lock` ya está versionado y las comprobaciones no lo escriben.

### Verificación segura ahora
### Camino rápido para Linux headless

```bash
bash scripts/nix-preflight.sh
bash scripts/test-nix-preflight.sh
bash -n scripts/nix-preflight.sh scripts/test-nix-preflight.sh
```
Objetivo: Ubuntu 24.04 `x86_64` con el perfil `homeConfigurations.jbarbat-linux`.

El preflight es de solo lectura y sin red: usa `--offline` y `--no-write-lock-file`. En una máquina sin Nix termina con estado no cero de forma esperada, pero no cambia el host. Su contrato de readiness exige macOS `arm64` o `x86_64`, un usuario y `HOME` válidos, Nix disponible y los inputs bloqueados ya disponibles localmente. Una configuración Fish existente es una advertencia, no un bloqueo: W1 no la administra.
1. Use un checkout limpio; no actualice un checkout remoto existente en el lugar.
2. Instale Nix fuera de este repositorio y verifique sus inputs ya bloqueados localmente.
3. Ejecute las comprobaciones de solo lectura:

### Ownership y próximo bootstrap
```bash
bash scripts/nix-preflight.sh
bash scripts/test-nix-preflight.sh
bash -n scripts/nix-preflight.sh scripts/test-nix-preflight.sh
```

| Área | Owner en W1 |
| --- | --- |
| `/opt/homebrew/bin/fish` | Homebrew actual |
| Symlinks, servicios y fuentes actuales | `install.sh` |
| Zsh y Ghostty | Configuración actual, sin cambios |
| `~/.config/fish/conf.d/99-local.fish` | Host; reservado para secretos/estado privado futuro, no creado ni gestionado por Nix |
| `flake.nix` y `nix/home.nix` | Base inactiva de Home Manager |
4. Espere la verificación independiente antes de cualquier activación. La instalación, `home-manager switch`, `nix run ... switch`, `chsh` y servicios quedan fuera de esta entrega.

El bootstrap queda explícitamente diferido a un work unit revisado. Después de instalar Nix por fuera de este repositorio, ese trabajo podrá generar el lock con:
El preflight selecciona solamente `Darwin arm64` o `Linux x86_64`, usa `--offline` y `--no-write-lock-file`. En una máquina sin Nix termina con estado no cero de forma esperada y sin cambios. Rechaza sistemas o arquitecturas no soportados.

```bash
nix --extra-experimental-features 'nix-command flakes' flake lock
```
### Configuraciones y ownership

| Área | Decisión |
| --- | --- |
| macOS | `homeConfigurations.jbarbat` conserva `aarch64-darwin`, `/Users/jbarbat` y la compatibilidad existente. |
| Linux | La configuración pura para Linux es `homeConfigurations.jbarbat-linux`: usa `x86_64-linux`, `/home/jbarbat` y declara solo `fish` y `starship` como runtime mínimo. |
| Fish compartido | Home Manager mapea cada archivo declarado de forma explícita; no gestiona el directorio completo, historial ni variables Fish. |
| Override privado | `~/.config/fish/conf.d/99-local.fish` sigue siendo del host, queda fuera del store y no se lee ni crea. |
| Links no declarados | Los links o archivos previos sin declarar permanecen bajo ownership del host; esta entrega no elimina nada. |
| Zsh | Permanece disponible como rollback; no se cambia el shell de login. |

Ese comando resuelve inputs y modifica `flake.lock`; por eso no es parte de W1. También quedan diferidos cualquier `home-manager switch`, `nix run ... switch`, instalación de paquetes, cambio de shell, `chsh`, servicios o extensiones Fish fuera del core W2.
Linux excluye screenshots y clipboard (`_screenshots_*`, `_time_ago`, `ss`, `last`, `ssd`, `imgclip` y `ccclip`). No instala backends gráficos ni gestiona funciones macOS. También están fuera de alcance Yabai, scripting addition/SIP/Dock, skhd, SketchyBar, Karabiner, tmux, Ghostty, herramientas X11/Wayland y flujos de permisos o servicios macOS.

La configuración pura de W1 es `homeConfigurations.jbarbat`: declara `username = "jbarbat"`, `homeDirectory = "/Users/jbarbat"` y `system = "aarch64-darwin"` de forma explícita y revisable. Una futura activación debe seleccionar ese target sin derivar valores de la máquina en tiempo de evaluación.
### Rollback

Para volver atrás de W1 basta quitar `flake.nix`, `nix/`, `scripts/nix-preflight.sh`, `scripts/test-nix-preflight.sh` y esta sección. No hay estado de host que revertir porque W1 no activó nada.
Antes de una activación no existe estado de host que revertir. Después de una futura activación, el límite de rollback es el perfil `homeConfigurations.jbarbat-linux` y los archivos Fish explícitamente declarados; Zsh y `99-local.fish` permanecen fuera de ese límite. No elimine overrides privados ni links no declarados.

## Fish core profile (W2)

Expand Down
5 changes: 4 additions & 1 deletion fish/conf.d/10-core.fish
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ alias .. 'cd ..'
alias ... 'cd ../..'
alias .... 'cd ../../..'
alias c clear
alias o open

if test (uname) = Darwin
alias o open
end

if command -q eza
alias ll 'eza -la --icons --git'
Expand Down
5 changes: 5 additions & 0 deletions fish/tests/w2-core.fish
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ set -l aliases (run_fish -c 'functions gs; functions gpl' | string collect)
string match -q '*git status*' "$aliases"; or fail 'gs alias'
string match -q '*git pull*' "$aliases"; or fail 'gpl alias'

printf '#!/bin/sh\nprintf "%s\\n" Linux\n' >"$bin/uname"
chmod +x "$bin/uname"
set -l linux_open_alias (run_fish -c 'functions -q o; and echo defined; or echo absent')
assert_equal "$linux_open_alias" absent 'Linux Fish startup excludes the macOS open alias'

printf '#!/bin/sh\n[ "$1" = init ] && printf "set -gx W2_STARSHIP_READY 1\\n"\n' >"$bin/starship"
chmod +x "$bin/starship"
set -l noninteractive (run_fish -c 'set -q W2_STARSHIP_READY; and echo ready; or echo skipped')
Expand Down
6 changes: 6 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@
homeDirectory = "/Users/jbarbat";
system = "aarch64-darwin";
};

homeConfigurations.jbarbat-linux = mkHomeConfiguration {
username = "jbarbat";
homeDirectory = "/home/jbarbat";
system = "x86_64-linux";
};
};
}
65 changes: 39 additions & 26 deletions nix/home.nix
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
{ username, homeDirectory, system, ... }:
{ username, homeDirectory, system, pkgs, lib, ... }:

{
assertions = [
{
assertion = username != "" && homeDirectory != "";
message = "homeConfigurations.jbarbat must provide explicit non-empty username and homeDirectory values.";
}
];

home = {
username = username;
homeDirectory = homeDirectory;
stateVersion = "24.11";
};

programs.home-manager.enable = true;

xdg.configFile = {
"nix/nix.conf".text = "experimental-features = nix-command flakes\n";
let
sharedFishFiles = {
"fish/conf.d/10-core.fish".source = ../fish/conf.d/10-core.fish;
"fish/functions/_go_dev_dir.fish".source = ../fish/functions/_go_dev_dir.fish;
"fish/functions/_go_first_existing_dir.fish".source = ../fish/functions/_go_first_existing_dir.fish;
Expand Down Expand Up @@ -67,20 +51,49 @@
"fish/functions/sshc.fish".source = ../fish/functions/sshc.fish;
"fish/functions/sshx.fish".source = ../fish/functions/sshx.fish;
"fish/functions/sshx-doctor.fish".source = ../fish/functions/sshx-doctor.fish;
"fish/functions/_screenshots_dir.fish".source = ../fish/functions/_screenshots_dir.fish;
"fish/functions/_screenshot_files.fish".source = ../fish/functions/_screenshot_files.fish;
"fish/functions/_time_ago.fish".source = ../fish/functions/_time_ago.fish;
"fish/functions/ss.fish".source = ../fish/functions/ss.fish;
"fish/functions/last.fish".source = ../fish/functions/last.fish;
"fish/functions/ssd.fish".source = ../fish/functions/ssd.fish;
"fish/functions/imgclip.fish".source = ../fish/functions/imgclip.fish;
"fish/functions/_cortex_resolve_target.fish".source = ../fish/functions/_cortex_resolve_target.fish;
"fish/functions/_cortex_run_agent.fish".source = ../fish/functions/_cortex_run_agent.fish;
"fish/functions/cc.fish".source = ../fish/functions/cc.fish;
"fish/functions/oc.fish".source = ../fish/functions/oc.fish;
"fish/functions/ocb.fish".source = ../fish/functions/ocb.fish;
"fish/functions/ccx.fish".source = ../fish/functions/ccx.fish;
"fish/functions/ccd.fish".source = ../fish/functions/ccd.fish;
};

darwinFishFiles = {
"fish/functions/_screenshots_dir.fish".source = ../fish/functions/_screenshots_dir.fish;
"fish/functions/_screenshot_files.fish".source = ../fish/functions/_screenshot_files.fish;
"fish/functions/_time_ago.fish".source = ../fish/functions/_time_ago.fish;
"fish/functions/ss.fish".source = ../fish/functions/ss.fish;
"fish/functions/last.fish".source = ../fish/functions/last.fish;
"fish/functions/ssd.fish".source = ../fish/functions/ssd.fish;
"fish/functions/imgclip.fish".source = ../fish/functions/imgclip.fish;
"fish/functions/ccclip.fish".source = ../fish/functions/ccclip.fish;
};
in
{
assertions = [
{
assertion = username != "" && homeDirectory != "";
message = "Home Manager configurations must provide explicit non-empty username and homeDirectory values.";
}
{
assertion = system == "aarch64-darwin" || system == "x86_64-linux";
message = "Only aarch64-darwin and x86_64-linux Home Manager configurations are supported.";
}
];

home = {
username = username;
homeDirectory = homeDirectory;
stateVersion = "24.11";
packages = lib.optionals pkgs.stdenv.isLinux [ pkgs.fish pkgs.starship ];
};

programs.home-manager.enable = true;

xdg.configFile = {
"nix/nix.conf".text = "experimental-features = nix-command flakes\n";
}
// sharedFishFiles // lib.optionalAttrs (!pkgs.stdenv.isLinux) darwinFishFiles;
}
30 changes: 16 additions & 14 deletions scripts/nix-preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ else
fail "HOME must be an existing absolute directory; received: $home_directory"
fi

if [[ "$host_os" == "Darwin" ]]; then
pass 'Host platform: Darwin.'
else
fail "W1 currently targets macOS; detected platform: $host_os"
fi

case "$host_architecture" in
arm64 | x86_64)
pass "Supported macOS architecture: $host_architecture"
home_configuration=''
case "$host_os:$host_architecture" in
Darwin:arm64)
home_configuration='jbarbat'
pass 'Host platform: Darwin arm64 (homeConfigurations.jbarbat).'
;;
Linux:x86_64)
home_configuration='jbarbat-linux'
pass 'Host platform: Linux x86_64 (homeConfigurations.jbarbat-linux).'
;;
*)
fail "Unsupported macOS architecture for W1: $host_architecture"
fail "Unsupported platform: $host_os $host_architecture. Supported targets are Darwin arm64 and Linux x86_64."
;;
esac

Expand All @@ -63,10 +63,12 @@ else
if nix --extra-experimental-features 'nix-command flakes' flake metadata --no-write-lock-file --offline "$repo_root" >/dev/null 2>&1; then
pass 'Flake features and the local lock/input metadata are ready for read-only evaluation.'

if nix --extra-experimental-features 'nix-command flakes' eval --no-write-lock-file --offline --raw "$repo_root#homeConfigurations.jbarbat.activationPackage.drvPath" >/dev/null 2>&1; then
pass 'Pure Home Manager configuration jbarbat is available through the flake input.'
else
fail 'Pure Home Manager configuration jbarbat cannot be evaluated locally; generate flake.lock during the future bootstrap, then retry.'
if [[ -n "$home_configuration" ]]; then
if nix --extra-experimental-features 'nix-command flakes' eval --no-write-lock-file --offline --raw "$repo_root#homeConfigurations.$home_configuration.activationPackage.drvPath" >/dev/null 2>&1; then
pass "Pure Home Manager configuration $home_configuration is available through the flake input."
else
fail "Pure Home Manager configuration $home_configuration cannot be evaluated locally; generate flake.lock during the future bootstrap, then retry."
fi
fi
else
fail 'Flake metadata is not available offline; generate flake.lock during the future bootstrap, then retry.'
Expand Down
80 changes: 62 additions & 18 deletions scripts/test-nix-preflight.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,31 +27,53 @@ assert_not_contains() {
fi
}

assert_pure_w1_configuration() {
assert_pure_home_manager_configurations() {
assert_contains ' mkHomeConfiguration = { username, homeDirectory, system }:' "$repo_root/flake.nix"
assert_contains ' homeConfigurations.jbarbat = mkHomeConfiguration {' "$repo_root/flake.nix"
assert_contains ' username = "jbarbat";' "$repo_root/flake.nix"
assert_contains ' homeDirectory = "/Users/jbarbat";' "$repo_root/flake.nix"
assert_contains ' system = "aarch64-darwin";' "$repo_root/flake.nix"
assert_contains '{ username, homeDirectory, system, ... }:' "$repo_root/nix/home.nix"
assert_contains ' - name: Check pure Nix preflight contract' "$repo_root/.github/workflows/ci.yml"
grep -Fq 'La configuración pura de W1 es `homeConfigurations.jbarbat`:' "$repo_root/README.md" ||
fail 'README does not document the pure W1 configuration'

for forbidden in 'home.packages' 'programs.fish' 'services.' 'home.activation'; do
assert_not_contains "$forbidden" "$repo_root/nix/home.nix"
assert_contains ' homeConfigurations.jbarbat-linux = mkHomeConfiguration {' "$repo_root/flake.nix"
assert_contains ' homeDirectory = "/home/jbarbat";' "$repo_root/flake.nix"
assert_contains ' system = "x86_64-linux";' "$repo_root/flake.nix"
assert_contains '{ username, homeDirectory, system, pkgs, lib, ... }:' "$repo_root/nix/home.nix"
assert_contains ' sharedFishFiles = {' "$repo_root/nix/home.nix"
assert_contains ' darwinFishFiles = {' "$repo_root/nix/home.nix"
assert_contains ' packages = lib.optionals pkgs.stdenv.isLinux [ pkgs.fish pkgs.starship ];' "$repo_root/nix/home.nix"
assert_contains ' // sharedFishFiles // lib.optionalAttrs (!pkgs.stdenv.isLinux) darwinFishFiles;' "$repo_root/nix/home.nix"
assert_contains ' - name: Check Nix Home Manager preflight contracts' "$repo_root/.github/workflows/ci.yml"
grep -Fq 'La configuración pura para Linux es `homeConfigurations.jbarbat-linux`:' "$repo_root/README.md" ||
fail 'README does not document the Linux Home Manager configuration'

for forbidden in 'builtins.currentSystem' 'builtins.getEnv'; do
assert_not_contains "$forbidden" "$repo_root/flake.nix"
done

assert_not_contains 'builtins.currentSystem' "$repo_root/flake.nix"
assert_not_contains 'builtins.getEnv' "$repo_root/flake.nix"

for file in "$repo_root/scripts/nix-preflight.sh" "$repo_root/.github/workflows/ci.yml" "$repo_root/README.md"; do
assert_not_contains '--impure' "$file"
done
}

assert_linux_fish_exclusions() {
local shared_sources
local darwin_sources

shared_sources="$(sed -n '/ sharedFishFiles = {/,/ };/p' "$repo_root/nix/home.nix")"
darwin_sources="$(sed -n '/ darwinFishFiles = {/,/ };/p' "$repo_root/nix/home.nix")"

for source in '_screenshots_dir.fish' '_screenshot_files.fish' '_time_ago.fish' 'ss.fish' 'last.fish' 'ssd.fish' 'imgclip.fish' 'ccclip.fish'; do
if grep -Fq -- "$source" <<<"$shared_sources"; then
fail "Linux Fish ownership must exclude $source"
fi
grep -Fq -- "$source" <<<"$darwin_sources" ||
fail "Darwin Fish ownership must retain $source"
done
}

make_fake_commands() {
local bin_dir="$1"
local fake_os="$2"
local fake_architecture="$3"

mkdir -p "$bin_dir"

Expand All @@ -62,11 +84,11 @@ if [ "$1" = "-un" ]; then
fi
EOF

cat >"$bin_dir/uname" <<'EOF'
cat >"$bin_dir/uname" <<EOF
#!/bin/sh
case "$1" in
-s) printf '%s\n' 'Darwin' ;;
-m) printf '%s\n' 'arm64' ;;
case "\$1" in
-s) printf '%s\\n' '$fake_os' ;;
-m) printf '%s\\n' '$fake_architecture' ;;
esac
EOF

Expand All @@ -81,13 +103,14 @@ run_preflight() {
HOME="$home_dir" PATH="$bin_dir:/usr/bin:/bin" bash "$preflight" >"$output" 2>&1
}

assert_pure_w1_configuration
assert_pure_home_manager_configurations
assert_linux_fish_exclusions

missing_nix_home="$tmp_dir/missing-nix-home"
missing_nix_bin="$tmp_dir/missing-nix-bin"
missing_nix_output="$tmp_dir/missing-nix.out"
mkdir -p "$missing_nix_home" "$missing_nix_bin"
make_fake_commands "$missing_nix_bin"
make_fake_commands "$missing_nix_bin" Darwin arm64

if run_preflight "$missing_nix_home" "$missing_nix_bin" "$missing_nix_output"; then
fail 'preflight unexpectedly passed without nix'
Expand All @@ -99,7 +122,7 @@ ready_home="$tmp_dir/ready-home"
ready_bin="$tmp_dir/ready-bin"
ready_output="$tmp_dir/ready.out"
mkdir -p "$ready_home" "$ready_bin"
make_fake_commands "$ready_bin"
make_fake_commands "$ready_bin" Darwin arm64
cat >"$ready_bin/nix" <<'EOF'
#!/bin/sh
printf '%s\n' "$*" >>"$NIX_FAKE_LOG"
Expand All @@ -118,6 +141,27 @@ if grep -F -- '--impure' "$tmp_dir/nix.log" >/dev/null; then
fail 'preflight evaluated the configuration impurely'
fi

linux_home="$tmp_dir/linux-home"
linux_bin="$tmp_dir/linux-bin"
linux_output="$tmp_dir/linux.out"
mkdir -p "$linux_home" "$linux_bin"
make_fake_commands "$linux_bin" Linux x86_64
cp "$ready_bin/nix" "$linux_bin/nix"
NIX_FAKE_LOG="$tmp_dir/linux-nix.log" run_preflight "$linux_home" "$linux_bin" "$linux_output"
assert_contains 'PASS Host platform: Linux x86_64 (homeConfigurations.jbarbat-linux).' "$linux_output"
assert_contains 'PASS Pure Home Manager configuration jbarbat-linux is available through the flake input.' "$linux_output"
grep -F -- '#homeConfigurations.jbarbat-linux.activationPackage.drvPath' "$tmp_dir/linux-nix.log" >/dev/null || fail 'preflight did not evaluate the Linux configuration'

unsupported_home="$tmp_dir/unsupported-home"
unsupported_bin="$tmp_dir/unsupported-bin"
unsupported_output="$tmp_dir/unsupported.out"
mkdir -p "$unsupported_home" "$unsupported_bin"
make_fake_commands "$unsupported_bin" Linux aarch64
if run_preflight "$unsupported_home" "$unsupported_bin" "$unsupported_output"; then
fail 'preflight unexpectedly passed on an unsupported Linux architecture'
fi
assert_contains 'FAIL Unsupported platform: Linux aarch64. Supported targets are Darwin arm64 and Linux x86_64.' "$unsupported_output"

mkdir -p "$ready_home/.config/fish/conf.d"
printf '%s\n' '# host-owned local configuration' >"$ready_home/.config/fish/conf.d/99-local.fish"
collision_output="$tmp_dir/collision.out"
Expand Down
Loading