Compare commits

...

2 Commits

Author SHA1 Message Date
e463c42740 dendritic migration
dendritic migration
2026-03-05 15:41:41 +00:00
05544d0597 go 2026-03-05 15:41:41 +00:00
142 changed files with 4411 additions and 2739 deletions

10
.sisyphus/boulder.json Normal file
View File

@@ -0,0 +1,10 @@
{
"active_plan": "/home/cschmatzler/nixos-config/.sisyphus/plans/dendritic-migration.md",
"started_at": "2026-03-05T10:57:53.044Z",
"session_ids": [
"ses_34287fe8effeSATHvHUmGqZ5Fp"
],
"plan_name": "dendritic-migration",
"agent": "atlas",
"worktree_path": "/home/cschmatzler/nixos-config"
}

View File

@@ -0,0 +1,2 @@
- Do not include user aspects (e.g. `den.aspects.cschmatzler`) in host aspect `includes`. Den HM integration already applies the user aspect for each `den.hosts.<system>.<host>.users.<user>` via `den.ctx.user`; duplicating it via host `includes` leads to duplicated HM option merges (notably `types.lines` options like `programs.nushell.extraConfig`).

View File

@@ -0,0 +1,3 @@
- Nushell `programs.nushell.extraConfig` duplication traced to Den applying the user aspect twice: it is included via host aspect `includes` (e.g. `den.aspects.tahani.includes = [ ... den.aspects.cschmatzler ... ]`) AND Den's HM integration already includes the user aspect via `den.ctx.user` (see Den `hm-integration.nix`). Result: user-aspect-provided HM config (like `den.aspects.shell.homeManager`) merges twice, so `extraConfig` contains two identical copies.
- `dev-tools` nushell functions (e.g. `ggpull`) missing in `michael` is expected: `modules/michael.nix` does not include `den.aspects.dev-tools`.

View File

@@ -0,0 +1,128 @@
- For den migration, move legacy non-flake-parts modules into `modules/_legacy/` before enabling `inputs.import-tree ./modules`; import-tree ignores underscore-prefixed paths.
- `flake-parts` must include `inputs.nixpkgs-lib.follows = "nixpkgs"` in this repository to match den bootstrap expectations.
- The den bootstrap works with `modules/dendritic.nix` importing `(inputs.flake-file.flakeModules.dendritic or { })` and `(inputs.den.flakeModules.dendritic or { })`, plus initial `flake-file.inputs` declarations.
- Den host wiring uses `den.hosts.<system>.<hostname>.users.<username> = {}` declarations in `modules/hosts.nix` for each host-user pair.
- `den.default.includes` accepts batteries directly via `den.provides.*`; this bootstrap uses `den.provides.define-user` and `den.provides.inputs'`.
- In this flake-parts setup, declaring `options.flake.darwinConfigurations` as `lib.types.lazyAttrsOf lib.types.raw` allows multiple Darwin hosts to merge correctly.
## Task 3: Utility functions under _lib/ - COMPLETED
**What was done:**
- Created `modules/_lib/` directory
- Copied 5 pure function files (not NixOS modules):
- `lib/constants.nix``modules/_lib/constants.nix` (14 lines)
- `lib/build-rust-package.nix``modules/_lib/build-rust-package.nix` (20 lines)
- `profiles/wallpaper.nix``modules/_lib/wallpaper.nix` (11 lines)
- `profiles/open-project.nix``modules/_lib/open-project.nix` (10 lines)
- `profiles/packages.nix``modules/_lib/packages.nix` (67 lines)
**Key insight:**
- import-tree ignores paths with `/_` prefix, so `modules/_lib/` is safe for pure functions
- These files are NOT NixOS/home-manager modules - they're utility functions that would crash import-tree if placed directly under `modules/`
- Files were COPIED (not moved) because old locations are still referenced by existing host configs until Task 26
**Verification:**
- All 5 files copied with identical content (byte-for-byte match)
- `alejandra --check modules/_lib/` passed (formatting compliant)
- `nix flake show` exits 0 (import-tree correctly ignores `_lib/`)
**Dependencies:**
- Unblocks Task 4 (overlays need `build-rust-package.nix` from `_lib/`)
## Task 2: Hosts and defaults bootstrap notes
- Den host wiring uses `den.hosts.<system>.<hostname>.users.<username> = {}` declarations in `modules/hosts.nix` for each host-user pair.
- `den.default.includes` accepts batteries directly via `den.provides.*`; this bootstrap uses `den.provides.define-user` and `den.provides.inputs'`.
- In this flake-parts setup, declaring `options.flake.darwinConfigurations` as `lib.types.lazyAttrsOf lib.types.raw` allows multiple Darwin hosts to merge correctly.
## Task 5: Core aspect module - COMPLETED
**What was done:**
- Created `modules/core.nix` as a flake-parts module defining `den.aspects.core`
- Ported all nix settings from `profiles/core.nix` into the `os` class (applies to both nixos and darwin)
- Updated `modules/defaults.nix` to include `den.aspects.core` in `den.default.includes`
**Key decisions:**
- Used `os` class for shared settings (fish, nushell, nixpkgs.config.allowUnfree, nix package, substituters, trusted-public-keys, gc.automatic, gc.options, experimental-features)
- Deliberately EXCLUDED `trusted-users` from core.nix (platform-specific: darwin uses "@admin", NixOS uses specific user — handled by darwin.nix and nixos-system.nix)
- Deliberately EXCLUDED gc interval/dates (platform-specific: darwin uses `interval`, NixOS uses `dates` — handled by darwin.nix and nixos-system.nix)
**Verification:**
- `modules/core.nix` created with 35 lines (exact port of profiles/core.nix settings)
- `modules/defaults.nix` updated to include `den.aspects.core` in includes list
- `alejandra .` formatted both files successfully
- `nix flake show` exits 0 (flake evaluates cleanly)
**Dependencies:**
- Unblocks Task 6 (darwin.nix and nixos-system.nix can now reference den.aspects.core)
## Task 6a: NixOS system aspect - COMPLETED
**What was done:**
- Created `modules/nixos-system.nix` as a flake-parts module defining `den.aspects.nixos-system`
- Ported NixOS-specific config from `profiles/nixos.nix` into the `nixos` class
**Key decisions:**
- Used `nixos` class (not `os`) since all settings are NixOS-specific (sudo, boot, systemd-boot, users)
- `nixos` class uses NixOS module function form `{pkgs, ...}: { ... }` to access `pkgs` for `linuxPackages_latest` and `nushell`
- `inputs` accessed from outer flake-parts module args for `home-manager.nixosModules.home-manager` import
- Hardcoded "cschmatzler" instead of variable interpolation (user is always the same)
- Hardcoded SSH keys inline instead of referencing constants (simplifies dependency)
- Deliberately EXCLUDED: system.stateVersion (in defaults.nix), sops.age.sshKeyPaths (in secrets.nix), home-manager.sharedModules/_module.args (den handles via inputs' battery)
**Pattern:**
- Outer function: `{inputs, ...}:` — flake-parts module args
- Inner class: `nixos = {pkgs, ...}: { ... }` — NixOS module function
- `imports = [inputs.home-manager.nixosModules.home-manager]` inside the nixos class
**Verification:**
- `alejandra --check .` passes (already compliant on write)
- `nix flake show` exits 0 (both michael and tahani evaluate cleanly)
## Task 6b: Darwin system aspect - COMPLETED
**What was done:**
- Created `modules/darwin.nix` as a flake-parts module defining `den.aspects.darwin-system`
- Created `modules/_darwin/dock.nix` — the dock module (NixOS-style with options/config)
- Ported profiles/darwin.nix, profiles/dock.nix, profiles/homebrew.nix, and nix-homebrew config
**Files created:**
- `modules/darwin.nix` — flake-parts module with `den.aspects.darwin-system.darwin` class
- `modules/_darwin/dock.nix` — dock options+activation script module (underscore prefix avoids import-tree)
**Key decisions:**
- `darwin` class uses NixOS module function form `{pkgs, ...}: { ... }` to access `pkgs.nushell`
- `inputs` accessed from outer flake-parts module args via closure (for nix-homebrew, home-manager, homebrew taps)
- Dock module placed in `modules/_darwin/dock.nix` and imported via `imports = [./_darwin/dock.nix]` inside the darwin class
- All `user` variable references replaced with hardcoded "cschmatzler"
- Excluded: home-manager.extraSpecialArgs (den handles via batteries), system.stateVersion (in defaults.nix)
- nix-homebrew config wired with taps from flake inputs (homebrew-core, homebrew-cask)
**Pattern for complex sub-modules:**
- Use `modules/_<platform>/` prefix (underscore avoids import-tree auto-import)
- Import from aspect class via `imports = [./_darwin/dock.nix]`
- The inner NixOS module function captures `inputs` from outer flake-parts scope via Nix closure
**Verification:**
- `alejandra .` — already compliant on write (no changes needed)
- `nix flake show` exits 0 (flake evaluates cleanly with new aspect)
## Task 23: Michael aspect with absorbed gitea module - COMPLETED
- Created `modules/_hosts/michael/` and copied `hosts/michael/disk-config.nix` plus `hosts/michael/hardware-configuration.nix` byte-for-byte into underscore-prefixed paths so import-tree ignores them.
- Added `modules/michael.nix` defining `den.aspects.michael` with includes `den.aspects.nixos-system`, `den.aspects.core`, and `den.aspects.cschmatzler`.
- Inlined the full gitea/redis/litestream/caddy/restic/systemd config directly in the michael aspect and removed dependency on `options.my.gitea`.
- Preserved intentional `lib.mkForce` overrides for litestream and restic service users/groups.
- Replaced legacy `cfg.*` references with concrete values and SOPS paths: litestream bucket `michael-gitea-litestream`, restic bucket `michael-gitea-repositories`, endpoint `s3.eu-central-003.backblazeb2.com`, and `config.sops.secrets.michael-gitea-*.path`.
## Task 25: Tahani aspect with host sub-files - COMPLETED
- Created `modules/_hosts/tahani/` and copied `hosts/tahani/{adguardhome,cache,networking,paperless}.nix` byte-for-byte into underscore-prefixed paths so import-tree ignores host-only sub-files.
- Added `modules/tahani.nix` defining `den.aspects.tahani` with includes `den.aspects.nixos-system`, `den.aspects.core`, and `den.aspects.cschmatzler` (network aspects intentionally deferred).
- Ported tahani-specific NixOS settings into the aspect (`networking.hostName`, docker enablement, docker group membership for `cschmatzler`, and 16 GiB swapfile declaration).
- Ported tahani-specific Home Manager settings into the aspect (`programs.git.settings.user.email`, zellij Nushell integration override enabled for tahani).
- Inbox-triage systemd unit now uses `pkgs.himalaya` from overlay in `PATH` (`${pkgs.himalaya}/bin`) with `inputs'.llm-agents.packages.opencode` for `ExecStart`; no `config.home-manager.users...` lookup.
- Verification: `alejandra .`, `alejandra --check .`, and `nix flake show` all pass; `lsp_diagnostics` is clean on all newly created tahani files.
- Home Manager `programs.nushell` module writes `config.nu` as a merge of: `environmentVariables` (via `load-env`), flattened `settings`, optional `configFile.text`, then `extraConfig`, then generated `shellAliases` (see HM `modules/programs/nushell.nix`). So any duplication in `config.nu` that is isolated to `extraConfig` almost always means the option value was merged multiple times (module included multiple times), not that HM writes it twice.
- In Den, HM user contexts include both the host aspect chain and the user aspect (`den.ctx.user`). If you also include the user aspect from the host aspect `includes`, user HM config is applied twice.

File diff suppressed because it is too large Load Diff

View File

@@ -12,12 +12,12 @@ nix flake check # Validate flake
### Remote Deployment (NixOS only)
```bash
colmena build # Build all NixOS hosts
colmena apply --on <host> # Deploy to specific NixOS host (michael, tahani)
colmena apply # Deploy to all NixOS hosts
nix run .#deploy # Deploy to all NixOS hosts
nix run .#deploy -- .#michael # Deploy to specific NixOS host
nix run .#deploy -- .#tahani # Deploy to specific NixOS host
```
When you're on tahani and asked to apply, that means running `colmena apply`.
When you're on tahani and asked to apply, that means running `nix run .#deploy`.
### Formatting
```bash
@@ -32,14 +32,35 @@ alejandra . # Format all Nix files
- **Command**: Run `alejandra .` before committing
### File Structure
- **Hosts**: `hosts/<hostname>/` - Per-machine configurations
- Darwin: `chidi`, `jason`
- NixOS: `michael`, `tahani`
- **Profiles**: `profiles/` - Reusable program/service configurations (imported by hosts)
- **Modules**: `modules/` - Custom NixOS/darwin modules
- **Lib**: `lib/` - Shared constants and utilities
- **Modules**: `modules/` - All configuration (flake-parts modules, auto-imported by import-tree)
- `_lib/` - Utility functions (underscore = ignored by import-tree)
- `_darwin/` - Darwin-specific sub-modules
- `_neovim/` - Neovim plugin configs
- `_opencode/` - OpenCode agent/command/skill configs
- `_hosts/` - Host-specific sub-files (disk-config, hardware, etc.)
- **Apps**: `apps/` - Per-system app scripts (Nushell)
- **Secrets**: `secrets/` - SOPS-encrypted secrets (`.sops.yaml` for config)
### Architecture
**Framework**: den (vic/den) — every .nix file in `modules/` is a flake-parts module
**Pattern**: Feature/aspect-centric, not host-centric
**Aspects**: `den.aspects.<name>.<class>` where class is:
- `nixos` - NixOS-only configuration
- `darwin` - macOS-only configuration
- `homeManager` - Home Manager configuration
- `os` - Applies to both NixOS and darwin
**Hosts**: `den.hosts.<system>.<name>` defined in `modules/hosts.nix`
**Defaults**: `den.default.*` defined in `modules/defaults.nix`
**Imports**: Auto-imported by import-tree; underscore-prefixed dirs (`_lib/`, `_darwin/`, etc.) are excluded from auto-import
**Deployment**: deploy-rs for NixOS hosts (michael, tahani); darwin hosts (chidi, jason) are local-only
### Nix Language Conventions
**Function Arguments**:
@@ -48,20 +69,11 @@ alejandra . # Format all Nix files
```
Destructure arguments on separate lines. Use `...` to capture remaining args.
**Imports**:
```nix
../../profiles/foo.nix
```
Use relative paths from file location, not absolute paths.
**Attribute Sets**:
```nix
options.my.gitea = {
enable = lib.mkEnableOption "Gitea git hosting service";
bucket = lib.mkOption {
type = lib.types.str;
description = "S3 bucket name";
};
den.aspects.myfeature.os = {
enable = true;
config = "value";
};
```
One attribute per line with trailing semicolons.
@@ -77,7 +89,7 @@ with pkgs;
```
Use `with pkgs;` for package lists, one item per line.
**Modules**:
**Aspect Definition**:
```nix
{
config,
@@ -86,9 +98,9 @@ Use `with pkgs;` for package lists, one item per line.
...
}:
with lib; let
cfg = config.my.feature;
cfg = config.den.aspects.myfeature;
in {
options.my.feature = {
options.den.aspects.myfeature = {
enable = mkEnableOption "Feature description";
};
config = mkIf cfg.enable {
@@ -113,22 +125,27 @@ in {
```
### Naming Conventions
- **Option names**: `my.<feature>.<option>` for custom modules
- **Hostnames**: Lowercase, descriptive (e.g., `michael`, `tahani`)
- **Profile files**: Descriptive, lowercase with hyphens (e.g., `homebrew.nix`)
- **Aspect names**: `den.aspects.<name>.<class>` for feature configuration
- **Hostnames**: Lowercase, descriptive (e.g., `michael`, `tahani`, `chidi`, `jason`)
- **Module files**: Descriptive, lowercase with hyphens (e.g., `neovim-config.nix`)
### Secrets Management
- Use SOPS for secrets (see `.sops.yaml`)
- Never commit unencrypted secrets
- Secrets files in `hosts/<host>/secrets.nix` import SOPS-generated files
- Secret definitions in `modules/secrets.nix`
### Imports Pattern
Host configs import:
1. System modules (`modulesPath + "/..."`)
2. Host-specific files (`./disk-config.nix`, `./hardware-configuration.nix`)
3. SOPS secrets (`./secrets.nix`)
4. Custom modules (`../../modules/*.nix`)
5. Base profiles (`../../profiles/*.nix`)
6. Input modules (`inputs.<module>.xxxModules.module`)
### Aspect Composition
Use `den.aspects.<name>.includes` to compose aspects:
```nix
den.aspects.myfeature.includes = [
"other-aspect"
"another-aspect"
];
```
Home-manager users import profiles in a similar manner.
### Key Conventions
- No `specialArgs` — den batteries handle input passing
- No hostname string comparisons in shared aspects
- Host-specific config goes in `den.aspects.<hostname>.*`
- Shared config uses `os` class (applies to both NixOS and darwin)
- Non-module files go in `_`-prefixed subdirs

View File

@@ -1,12 +1,15 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
HOSTNAME="${1:-$(scutil --get LocalHostName 2>/dev/null || hostname -s)}"
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
try { scutil --get LocalHostName | str trim } catch { hostname -s | str trim }
} else { $hostname }
print_info "Applying configuration for $HOSTNAME"
print_info $"Applying configuration for ($host)"
nix run nix-darwin -- switch --flake ".#$HOSTNAME" "${@:2}"
nix run nix-darwin -- switch --flake $".#($host)" ...$rest
print_success "Configuration applied successfully"
print_success "Configuration applied successfully"
}

View File

@@ -1,16 +1,19 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
HOSTNAME="${1:-$(scutil --get LocalHostName 2>/dev/null || hostname -s)}"
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
try { scutil --get LocalHostName | str trim } catch { hostname -s | str trim }
} else { $hostname }
print_info "Building configuration for $HOSTNAME"
print_info $"Building configuration for ($host)"
nix build ".#darwinConfigurations.$HOSTNAME.system" --show-trace "${@:2}"
nix build $".#darwinConfigurations.($host).system" --show-trace ...$rest
if [[ -L ./result ]]; then
unlink ./result
fi
if ("./result" | path exists) {
rm ./result
}
print_success "Build completed successfully"
print_success "Build completed successfully"
}

View File

@@ -1,27 +1,30 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
HOSTNAME="${1:-$(scutil --get LocalHostName 2>/dev/null || hostname -s)}"
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
try { scutil --get LocalHostName | str trim } catch { hostname -s | str trim }
} else { $hostname }
print_info "Building and switching configuration for $HOSTNAME"
print_info $"Building and switching configuration for ($host)"
# Build
print_info "Building configuration..."
if ! nix build ".#darwinConfigurations.$HOSTNAME.system" --show-trace "${@:2}"; then
print_error "Build failed"
exit 1
fi
# Build
print_info "Building configuration..."
if (nix build $".#darwinConfigurations.($host).system" --show-trace ...$rest | complete).exit_code != 0 {
print_error "Build failed"
exit 1
}
print_success "Build completed"
print_success "Build completed"
# Switch
print_info "Switching to new configuration..."
sudo ./result/sw/bin/darwin-rebuild switch --flake ".#$HOSTNAME" "${@:2}"
# Switch
print_info "Switching to new configuration..."
sudo ./result/sw/bin/darwin-rebuild switch --flake $".#($host)" ...$rest
if [[ -L ./result ]]; then
unlink ./result
fi
if ("./result" | path exists) {
rm ./result
}
print_success "Build and switch completed successfully"
print_success "Build and switch completed successfully"
}

View File

@@ -1,20 +1,20 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
print_info "Available generations:"
darwin-rebuild --list-generations
def main [] {
print_info "Available generations:"
darwin-rebuild --list-generations
echo -n "Enter generation number to rollback to: "
read -r GEN_NUM
let gen_num = input "Enter generation number to rollback to: "
if [[ -z "$GEN_NUM" ]]; then
print_error "No generation number provided"
exit 1
fi
if ($gen_num | is-empty) {
print_error "No generation number provided"
exit 1
}
print_warning "Rolling back to generation $GEN_NUM..."
darwin-rebuild switch --switch-generation "$GEN_NUM"
print_warning $"Rolling back to generation ($gen_num)..."
darwin-rebuild switch --switch-generation $gen_num
print_success "Rollback to generation $GEN_NUM complete"
print_success $"Rollback to generation ($gen_num) complete"
}

17
apps/common.nu Normal file
View File

@@ -0,0 +1,17 @@
#!/usr/bin/env nu
export def print_info [msg: string] {
print $"(ansi blue)[INFO](ansi reset) ($msg)"
}
export def print_success [msg: string] {
print $"(ansi green)[OK](ansi reset) ($msg)"
}
export def print_error [msg: string] {
print $"(ansi red)[ERROR](ansi reset) ($msg)"
}
export def print_warning [msg: string] {
print $"(ansi yellow)[WARN](ansi reset) ($msg)"
}

View File

@@ -1,23 +0,0 @@
#!/usr/bin/env bash
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}

View File

@@ -1,16 +1,21 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
HOSTNAME="${1:-$(hostname)}"
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
hostname | str trim
} else { $hostname }
print_info "Applying configuration for $HOSTNAME"
print_info $"Applying configuration for ($host)"
if [[ "$EUID" -ne 0 ]]; then
sudo nixos-rebuild switch --flake ".#$HOSTNAME" "${@:2}"
else
nixos-rebuild switch --flake ".#$HOSTNAME" "${@:2}"
fi
let euid = (id -u | str trim | into int)
print_success "Configuration applied successfully"
if $euid != 0 {
sudo nixos-rebuild switch --flake $".#($host)" ...$rest
} else {
nixos-rebuild switch --flake $".#($host)" ...$rest
}
print_success "Configuration applied successfully"
}

View File

@@ -1,16 +1,19 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
HOSTNAME="${1:-$(hostname)}"
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
hostname | str trim
} else { $hostname }
print_info "Building configuration for $HOSTNAME"
print_info $"Building configuration for ($host)"
nix build ".#nixosConfigurations.$HOSTNAME.config.system.build.toplevel" --show-trace "${@:2}"
nix build $".#nixosConfigurations.($host).config.system.build.toplevel" --show-trace ...$rest
if [[ -L ./result ]]; then
unlink ./result
fi
if ("./result" | path exists) {
rm ./result
}
print_success "Build completed successfully"
print_success "Build completed successfully"
}

View File

@@ -1,26 +1,32 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
HOSTNAME="${1:-$(hostname)}"
def main [hostname?: string, ...rest: string] {
let host = if ($hostname | is-empty) {
hostname | str trim
} else { $hostname }
print_info "Building and switching configuration for $HOSTNAME"
print_info $"Building and switching configuration for ($host)"
# Build
print_info "Building configuration..."
if ! nix build ".#nixosConfigurations.$HOSTNAME.config.system.build.toplevel" --no-link "${@:2}"; then
print_error "Build failed"
exit 1
fi
# Build
print_info "Building configuration..."
if (nix build $".#nixosConfigurations.($host).config.system.build.toplevel" --no-link ...$rest | complete).exit_code != 0 {
print_error "Build failed"
exit 1
}
print_success "Build completed"
print_success "Build completed"
print_info "Switching to new configuration..."
if [[ "$EUID" -ne 0 ]]; then
sudo nixos-rebuild switch --flake ".#$HOSTNAME" "${@:2}"
else
nixos-rebuild switch --flake ".#$HOSTNAME" "${@:2}"
fi
# Switch
print_info "Switching to new configuration..."
let euid = (id -u | str trim | into int)
print_success "Build and switch completed successfully"
if $euid != 0 {
sudo nixos-rebuild switch --flake $".#($host)" ...$rest
} else {
nixos-rebuild switch --flake $".#($host)" ...$rest
}
print_success "Build and switch completed successfully"
}

52
apps/x86_64-linux/rollback Normal file → Executable file
View File

@@ -1,30 +1,34 @@
#!/usr/bin/env bash
#!/usr/bin/env nu
set -euo pipefail
source "$(dirname "$0")/../common.sh"
use ../common.nu *
print_info "Available generations:"
if [[ "$EUID" -ne 0 ]]; then
sudo nix-env --profile /nix/var/nix/profiles/system --list-generations
else
nix-env --profile /nix/var/nix/profiles/system --list-generations
fi
def main [] {
print_info "Available generations:"
echo -n "Enter generation number to rollback to: "
read -r GEN_NUM
let euid = (id -u | str trim | into int)
if [[ -z "$GEN_NUM" ]]; then
print_error "No generation number provided"
exit 1
fi
if $euid != 0 {
sudo nix-env --profile /nix/var/nix/profiles/system --list-generations
} else {
nix-env --profile /nix/var/nix/profiles/system --list-generations
}
print_warning "Rolling back to generation $GEN_NUM..."
if [[ "$EUID" -ne 0 ]]; then
sudo nix-env --profile /nix/var/nix/profiles/system --switch-generation "$GEN_NUM" && \
sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch
else
nix-env --profile /nix/var/nix/profiles/system --switch-generation "$GEN_NUM" && \
/nix/var/nix/profiles/system/bin/switch-to-configuration switch
fi
let gen_num = input "Enter generation number to rollback to: "
print_success "Rollback to generation $GEN_NUM complete"
if ($gen_num | is-empty) {
print_error "No generation number provided"
exit 1
}
print_warning $"Rolling back to generation ($gen_num)..."
if $euid != 0 {
sudo nix-env --profile /nix/var/nix/profiles/system --switch-generation $gen_num
sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch
} else {
nix-env --profile /nix/var/nix/profiles/system --switch-generation $gen_num
/nix/var/nix/profiles/system/bin/switch-to-configuration switch
}
print_success $"Rollback to generation ($gen_num) complete"
}

299
flake.lock generated
View File

@@ -6,7 +6,7 @@
"llm-agents",
"nixpkgs"
],
"systems": "systems_2"
"systems": "systems_3"
},
"locked": {
"lastModified": 1771437256,
@@ -42,12 +42,12 @@
"bun2nix": {
"inputs": {
"flake-parts": "flake-parts_2",
"import-tree": "import-tree",
"import-tree": "import-tree_2",
"nixpkgs": [
"llm-agents",
"nixpkgs"
],
"systems": "systems_3",
"systems": "systems_4",
"treefmt-nix": "treefmt-nix"
},
"locked": {
@@ -64,30 +64,6 @@
"type": "github"
}
},
"colmena": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils",
"nix-github-actions": "nix-github-actions",
"nixpkgs": [
"nixpkgs"
],
"stable": "stable"
},
"locked": {
"lastModified": 1762034856,
"narHash": "sha256-QVey3iP3UEoiFVXgypyjTvCrsIlA4ecx6Acaz5C8/PQ=",
"owner": "zhaofengli",
"repo": "colmena",
"rev": "349b035a5027f23d88eeb3bc41085d7ee29f18ed",
"type": "github"
},
"original": {
"owner": "zhaofengli",
"repo": "colmena",
"type": "github"
}
},
"crane": {
"locked": {
"lastModified": 1765739568,
@@ -124,6 +100,41 @@
"type": "github"
}
},
"den": {
"locked": {
"lastModified": 1772696629,
"narHash": "sha256-wJvv0ZAcjpHBo/MnDSEZ+Dti+daJvlxznDUgKUvkT3c=",
"owner": "vic",
"repo": "den",
"rev": "94b8721bcff4940e1cce97c509d72545efa4a8e4",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "den",
"type": "github"
}
},
"deploy-rs": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs",
"utils": "utils"
},
"locked": {
"lastModified": 1770019181,
"narHash": "sha256-hwsYgDnby50JNVpTRYlF3UR/Rrpt01OrxVuryF40CFY=",
"owner": "serokell",
"repo": "deploy-rs",
"rev": "77c906c0ba56aabdbc72041bf9111b565cdd6171",
"type": "github"
},
"original": {
"owner": "serokell",
"repo": "deploy-rs",
"type": "github"
}
},
"disko": {
"inputs": {
"nixpkgs": [
@@ -212,14 +223,29 @@
"type": "github"
}
},
"flake-aspects": {
"locked": {
"lastModified": 1772679072,
"narHash": "sha256-qrbfroFGetX5DS/n6gP6BifqGCO084b5ad8C5kwaLAk=",
"owner": "vic",
"repo": "flake-aspects",
"rev": "82a472654139897fab1bcb2d96ae337fb7928800",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-aspects",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1650374568,
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"type": "github"
},
"original": {
@@ -228,9 +254,26 @@
"type": "github"
}
},
"flake-file": {
"locked": {
"lastModified": 1772677111,
"narHash": "sha256-tFVzJ+A39OrBPK1lYlM5giUu6yl9pwjUGf6VR3b8Yho=",
"owner": "vic",
"repo": "flake-file",
"rev": "f18f9bad86481621b9c378842987b172e91ca82c",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "flake-file",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
"nixpkgs-lib": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1772408722,
@@ -248,7 +291,7 @@
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1769996383,
@@ -286,23 +329,8 @@
}
},
"flake-utils": {
"locked": {
"lastModified": 1659877975,
"narHash": "sha256-zllb8aq3YO3h8B/U0/J1WBgAL8EX5yWf5pMj3G0NAmc=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "c0e246b9b83f637f4681389ecabcb2681b4f3af0",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems"
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
@@ -318,9 +346,9 @@
"type": "github"
}
},
"flake-utils_3": {
"flake-utils_2": {
"inputs": {
"systems": "systems_6"
"systems": "systems_7"
},
"locked": {
"lastModified": 1731533236,
@@ -339,7 +367,7 @@
"himalaya": {
"inputs": {
"fenix": "fenix",
"nixpkgs": "nixpkgs",
"nixpkgs": "nixpkgs_2",
"pimalaya": "pimalaya"
},
"locked": {
@@ -409,6 +437,21 @@
}
},
"import-tree": {
"locked": {
"lastModified": 1772344373,
"narHash": "sha256-OQQ1MhB9t1J71b2wxRRTdH/Qd8UGG0p+dGspfCf5U1c=",
"owner": "vic",
"repo": "import-tree",
"rev": "10fda59eee7d7970ec443b925f32a1bc7526648c",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"import-tree_2": {
"locked": {
"lastModified": 1763762820,
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
@@ -441,8 +484,8 @@
},
"jj-starship": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1769370163,
@@ -462,7 +505,7 @@
"inputs": {
"blueprint": "blueprint",
"bun2nix": "bun2nix",
"nixpkgs": "nixpkgs_3",
"nixpkgs": "nixpkgs_4",
"treefmt-nix": "treefmt-nix_2"
},
"locked": {
@@ -524,27 +567,6 @@
"type": "github"
}
},
"nix-github-actions": {
"inputs": {
"nixpkgs": [
"colmena",
"nixpkgs"
]
},
"locked": {
"lastModified": 1729742964,
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
"owner": "nix-community",
"repo": "nix-github-actions",
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nix-github-actions",
"type": "github"
}
},
"nix-homebrew": {
"inputs": {
"brew-src": "brew-src"
@@ -565,36 +587,21 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1736437047,
"narHash": "sha256-JJBziecfU+56SUNxeJlDIgixJN5WYuADd+/TVd5sQos=",
"owner": "nixos",
"lastModified": 1743014863,
"narHash": "sha256-jAIUqsiN2r3hCuHji80U7NNEafpIMBXiwKlSrjWMlpg=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "f17b95775191ea44bc426831235d87affb10faba",
"rev": "bd3bac8bfb542dbde7ffffb6987a1a1f9d41699f",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "staging-next",
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1772328832,
"narHash": "sha256-e+/T/pmEkLP6BHhYjx6GmwP5ivonQQn0bJdH9YrRB+Q=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "c185c7a5e5dd8f9add5b2f8ebeff00888b070742",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": {
"lastModified": 1769909678,
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
@@ -610,6 +617,22 @@
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1736437047,
"narHash": "sha256-JJBziecfU+56SUNxeJlDIgixJN5WYuADd+/TVd5sQos=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "f17b95775191ea44bc426831235d87affb10faba",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "staging-next",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1766840161,
"narHash": "sha256-Ss/LHpJJsng8vz1Pe33RSGIWUOcqM1fjrehjUkdrWio=",
@@ -625,7 +648,7 @@
"type": "github"
}
},
"nixpkgs_3": {
"nixpkgs_4": {
"locked": {
"lastModified": 1772643971,
"narHash": "sha256-+bllfMsclzbAAPMZTm3K9G/a5lG+s6l18/AyyYLPSIE=",
@@ -641,7 +664,7 @@
"type": "github"
}
},
"nixpkgs_4": {
"nixpkgs_5": {
"locked": {
"lastModified": 1772701849,
"narHash": "sha256-CO0mU0p/Fe5ekivxJmKO0WMA6kMOI/DbI+qEKhOxYCo=",
@@ -657,7 +680,7 @@
"type": "github"
}
},
"nixpkgs_5": {
"nixpkgs_6": {
"locked": {
"lastModified": 1770380644,
"narHash": "sha256-P7dWMHRUWG5m4G+06jDyThXO7kwSk46C1kgjEWcybkE=",
@@ -673,7 +696,7 @@
"type": "github"
}
},
"nixpkgs_6": {
"nixpkgs_7": {
"locked": {
"lastModified": 1771923393,
"narHash": "sha256-Fy0+UXELv9hOE8WjYhJt8fMDLYTU2Dqn3cX4BwoGBos=",
@@ -689,7 +712,7 @@
"type": "github"
}
},
"nixpkgs_7": {
"nixpkgs_8": {
"locked": {
"lastModified": 1765934234,
"narHash": "sha256-pJjWUzNnjbIAMIc5gRFUuKCDQ9S1cuh3b2hKgA7Mc4A=",
@@ -708,8 +731,8 @@
"nixvim": {
"inputs": {
"flake-parts": "flake-parts_3",
"nixpkgs": "nixpkgs_5",
"systems": "systems_4"
"nixpkgs": "nixpkgs_6",
"systems": "systems_5"
},
"locked": {
"lastModified": 1772402258,
@@ -743,20 +766,27 @@
},
"root": {
"inputs": {
"colmena": "colmena",
"darwin": "darwin",
"den": "den",
"deploy-rs": "deploy-rs",
"disko": "disko",
"flake-aspects": "flake-aspects",
"flake-file": "flake-file",
"flake-parts": "flake-parts",
"himalaya": "himalaya",
"home-manager": "home-manager",
"homebrew-cask": "homebrew-cask",
"homebrew-core": "homebrew-core",
"import-tree": "import-tree",
"jj-ryu": "jj-ryu",
"jj-starship": "jj-starship",
"llm-agents": "llm-agents",
"naersk": "naersk",
"nix-homebrew": "nix-homebrew",
"nixpkgs": "nixpkgs_4",
"nixpkgs": "nixpkgs_5",
"nixpkgs-lib": [
"nixpkgs"
],
"nixvim": "nixvim",
"sops-nix": "sops-nix",
"tuicr": "tuicr",
@@ -855,22 +885,6 @@
"type": "github"
}
},
"stable": {
"locked": {
"lastModified": 1750133334,
"narHash": "sha256-urV51uWH7fVnhIvsZIELIYalMYsyr2FCalvlRTzqWRw=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "36ab78dab7da2e4e27911007033713bab534187b",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
@@ -961,6 +975,21 @@
"type": "github"
}
},
"systems_7": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
@@ -1007,8 +1036,8 @@
"tuicr": {
"inputs": {
"naersk": "naersk_2",
"nixpkgs": "nixpkgs_6",
"utils": "utils"
"nixpkgs": "nixpkgs_7",
"utils": "utils_2"
},
"locked": {
"lastModified": 1772695807,
@@ -1026,7 +1055,25 @@
},
"utils": {
"inputs": {
"systems": "systems_5"
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"utils_2": {
"inputs": {
"systems": "systems_6"
},
"locked": {
"lastModified": 1731533236,
@@ -1045,8 +1092,8 @@
"zjstatus": {
"inputs": {
"crane": "crane",
"flake-utils": "flake-utils_3",
"nixpkgs": "nixpkgs_7",
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_8",
"rust-overlay": "rust-overlay"
},
"locked": {

184
flake.nix
View File

@@ -1,168 +1,58 @@
# DO-NOT-EDIT. This file was auto-generated using github:vic/flake-file.
# Use `nix run .#write-flake` to regenerate it.
{
description = "Configuration for my macOS laptops and NixOS server";
outputs = inputs: inputs.flake-parts.lib.mkFlake {inherit inputs;} (inputs.import-tree ./modules);
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/master";
flake-parts.url = "github:hercules-ci/flake-parts";
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
darwin = {
url = "github:LnL7/nix-darwin/master";
inputs.nixpkgs.follows = "nixpkgs";
url = "github:LnL7/nix-darwin/master";
};
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
homebrew-core = {
url = "github:homebrew/homebrew-core";
flake = false;
den.url = "github:vic/den";
deploy-rs.url = "github:serokell/deploy-rs";
disko = {
inputs.nixpkgs.follows = "nixpkgs";
url = "github:nix-community/disko";
};
flake-aspects.url = "github:vic/flake-aspects";
flake-file.url = "github:vic/flake-file";
flake-parts = {
inputs.nixpkgs-lib.follows = "nixpkgs";
url = "github:hercules-ci/flake-parts";
};
himalaya.url = "github:pimalaya/himalaya";
home-manager = {
inputs.nixpkgs.follows = "nixpkgs";
url = "github:nix-community/home-manager";
};
homebrew-cask = {
flake = false;
url = "github:homebrew/homebrew-cask";
};
homebrew-core = {
flake = false;
url = "github:homebrew/homebrew-core";
};
nixvim.url = "github:nix-community/nixvim";
zjstatus.url = "github:dj95/zjstatus";
llm-agents.url = "github:numtide/llm-agents.nix";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
colmena = {
url = "github:zhaofengli/colmena";
inputs.nixpkgs.follows = "nixpkgs";
};
import-tree.url = "github:vic/import-tree";
jj-ryu = {
url = "github:dmmulroy/jj-ryu";
flake = false;
url = "github:dmmulroy/jj-ryu";
};
jj-starship.url = "github:dmmulroy/jj-starship";
himalaya.url = "github:pimalaya/himalaya";
llm-agents.url = "github:numtide/llm-agents.nix";
naersk = {
url = "github:nix-community/naersk/master";
inputs.nixpkgs.follows = "nixpkgs";
url = "github:nix-community/naersk/master";
};
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
nixpkgs.url = "github:nixos/nixpkgs/master";
nixpkgs-lib.follows = "nixpkgs";
nixvim.url = "github:nix-community/nixvim";
sops-nix = {
inputs.nixpkgs.follows = "nixpkgs";
url = "github:Mic92/sops-nix";
};
tuicr.url = "github:agavra/tuicr";
zjstatus.url = "github:dj95/zjstatus";
};
outputs = inputs @ {flake-parts, ...}:
flake-parts.lib.mkFlake {inherit inputs;} (
let
inherit (inputs.nixpkgs) lib;
constants = import ./lib/constants.nix;
inherit (constants) user;
darwinHosts = ["chidi" "jason"];
nixosHosts = ["michael" "tahani"];
overlays = import ./overlays {inherit inputs;};
nixpkgsConfig = hostPlatform: {
nixpkgs = {inherit hostPlatform overlays;};
};
in {
systems = [
"x86_64-linux"
"aarch64-darwin"
];
flake.darwinConfigurations =
lib.genAttrs darwinHosts (
hostname:
inputs.darwin.lib.darwinSystem {
specialArgs = {inherit inputs user hostname constants;};
modules = [
inputs.home-manager.darwinModules.home-manager
inputs.nix-homebrew.darwinModules.nix-homebrew
(nixpkgsConfig "aarch64-darwin")
{
nix-homebrew = {
inherit user;
enable = true;
mutableTaps = true;
taps = {
"homebrew/homebrew-core" = inputs.homebrew-core;
"homebrew/homebrew-cask" = inputs.homebrew-cask;
};
};
}
./hosts/${hostname}
];
}
);
flake.nixosConfigurations =
lib.genAttrs nixosHosts (
hostname:
lib.nixosSystem {
specialArgs = {inherit inputs user hostname constants;};
modules = [
inputs.home-manager.nixosModules.home-manager
(nixpkgsConfig "x86_64-linux")
./hosts/${hostname}
];
}
);
flake.colmena =
{
meta = {
nixpkgs = inputs.nixpkgs.legacyPackages.x86_64-linux;
specialArgs = {inherit inputs user constants;};
};
}
// lib.genAttrs nixosHosts (
hostname: {
deployment = {
targetHost = hostname;
targetUser = user;
};
imports = [
inputs.home-manager.nixosModules.home-manager
(nixpkgsConfig "x86_64-linux")
{_module.args.hostname = hostname;}
./hosts/${hostname}
];
}
);
flake.nixosModules = {
pgbackrest = ./modules/pgbackrest.nix;
};
flake.overlays = {
default = lib.composeManyExtensions overlays;
list = overlays;
};
flake.lib = {inherit constants;};
perSystem = {
pkgs,
system,
...
}: let
mkApp = name: {
type = "app";
program = "${(pkgs.writeShellScriptBin name ''
PATH=${pkgs.git}/bin:$PATH
echo "Running ${name} for ${system}"
exec ${inputs.self}/apps/${system}/${name} "$@"
'')}/bin/${name}";
};
appNames = [
"apply"
"build"
"build-switch"
"rollback"
];
in {
apps = pkgs.lib.genAttrs appNames mkApp;
};
}
);
}

View File

@@ -1,57 +0,0 @@
{
pkgs,
inputs,
user,
hostname,
...
}: {
imports = [
./secrets.nix
../../profiles/core.nix
../../profiles/darwin.nix
../../profiles/dock.nix
../../profiles/homebrew.nix
../../profiles/tailscale.nix
inputs.sops-nix.darwinModules.sops
];
networking.hostName = hostname;
networking.computerName = hostname;
home-manager.users.${user} = {
imports = [
../../profiles/atuin.nix
../../profiles/aerospace.nix
../../profiles/bash.nix
../../profiles/bat.nix
../../profiles/direnv.nix
../../profiles/nushell.nix
../../profiles/fzf.nix
../../profiles/ghostty.nix
../../profiles/git.nix
../../profiles/home.nix
../../profiles/jjui.nix
../../profiles/jujutsu.nix
../../profiles/lazygit.nix
../../profiles/mise.nix
../../profiles/neovim
../../profiles/opencode.nix
../../profiles/claude-code.nix
../../profiles/ripgrep.nix
../../profiles/ssh.nix
../../profiles/starship.nix
../../profiles/yazi.nix
../../profiles/zellij.nix
../../profiles/zk.nix
../../profiles/zoxide.nix
../../profiles/zsh.nix
inputs.nixvim.homeModules.nixvim
];
fonts.fontconfig.enable = true;
programs.git.settings.user.email = "christoph@tuist.dev";
};
environment.systemPackages = with pkgs; [
slack
];
}

View File

@@ -1,5 +0,0 @@
{user, ...}: {
sops.age.keyFile = "/Users/${user}/.config/sops/age/keys.txt";
sops.age.sshKeyPaths = [];
sops.gnupg.sshKeyPaths = [];
}

View File

@@ -1,52 +0,0 @@
{
inputs,
user,
hostname,
...
}: {
imports = [
./secrets.nix
../../profiles/core.nix
../../profiles/darwin.nix
../../profiles/dock.nix
../../profiles/homebrew.nix
../../profiles/tailscale.nix
inputs.sops-nix.darwinModules.sops
];
networking.hostName = hostname;
networking.computerName = hostname;
home-manager.users.${user} = {
imports = [
../../profiles/atuin.nix
../../profiles/aerospace.nix
../../profiles/bash.nix
../../profiles/bat.nix
../../profiles/direnv.nix
../../profiles/nushell.nix
../../profiles/fzf.nix
../../profiles/ghostty.nix
../../profiles/git.nix
../../profiles/home.nix
../../profiles/jjui.nix
../../profiles/jujutsu.nix
../../profiles/lazygit.nix
../../profiles/mise.nix
../../profiles/neovim
../../profiles/opencode.nix
../../profiles/claude-code.nix
../../profiles/ripgrep.nix
../../profiles/ssh.nix
../../profiles/starship.nix
../../profiles/yazi.nix
../../profiles/zellij.nix
../../profiles/zk.nix
../../profiles/zoxide.nix
../../profiles/zsh.nix
inputs.nixvim.homeModules.nixvim
];
fonts.fontconfig.enable = true;
programs.git.settings.user.email = "christoph@schmatzler.com";
};
}

View File

@@ -1,5 +0,0 @@
{user, ...}: {
sops.age.keyFile = "/Users/${user}/.config/sops/age/keys.txt";
sops.age.sshKeyPaths = [];
sops.gnupg.sshKeyPaths = [];
}

View File

@@ -1,48 +0,0 @@
{
config,
inputs,
user,
hostname,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./disk-config.nix
./hardware-configuration.nix
./secrets.nix
../../modules/gitea.nix
../../profiles/core.nix
../../profiles/fail2ban.nix
../../profiles/nixos.nix
../../profiles/openssh.nix
../../profiles/tailscale.nix
inputs.disko.nixosModules.disko
inputs.sops-nix.nixosModules.sops
];
my.gitea = {
enable = true;
litestream = {
bucket = "michael-gitea-litestream";
secretFile = config.sops.secrets.michael-gitea-litestream.path;
};
restic = {
bucket = "michael-gitea-repositories";
passwordFile = config.sops.secrets.michael-gitea-restic-password.path;
environmentFile = config.sops.secrets.michael-gitea-restic-env.path;
};
};
networking.hostName = hostname;
home-manager.users.${user} = {
imports = [
../../profiles/nushell.nix
../../profiles/home.nix
../../profiles/ssh.nix
inputs.nixvim.homeModules.nixvim
];
};
}

View File

@@ -1,22 +0,0 @@
{...}: {
sops.secrets = {
michael-gitea-litestream = {
sopsFile = ../../secrets/michael-gitea-litestream;
format = "binary";
owner = "gitea";
group = "gitea";
};
michael-gitea-restic-password = {
sopsFile = ../../secrets/michael-gitea-restic-password;
format = "binary";
owner = "gitea";
group = "gitea";
};
michael-gitea-restic-env = {
sopsFile = ../../secrets/michael-gitea-restic-env;
format = "binary";
owner = "gitea";
group = "gitea";
};
};
}

View File

@@ -1,94 +0,0 @@
{
config,
pkgs,
inputs,
user,
hostname,
...
}: let
himalaya = config.home-manager.users.${user}.programs.himalaya.package;
opencode = inputs.llm-agents.packages.${pkgs.stdenv.hostPlatform.system}.opencode;
in {
imports = [
./adguardhome.nix
./cache.nix
./networking.nix
./paperless.nix
./secrets.nix
../../profiles/core.nix
../../profiles/nixos.nix
../../profiles/openssh.nix
../../profiles/tailscale.nix
inputs.sops-nix.nixosModules.sops
];
networking.hostName = hostname;
home-manager.users.${user} = {
imports = [
../../profiles/atuin.nix
../../profiles/bash.nix
../../profiles/bat.nix
../../profiles/direnv.nix
../../profiles/nushell.nix
../../profiles/fzf.nix
../../profiles/git.nix
../../profiles/himalaya.nix
../../profiles/mbsync.nix
../../profiles/home.nix
../../profiles/jjui.nix
../../profiles/jujutsu.nix
../../profiles/lazygit.nix
../../profiles/mise.nix
../../profiles/neovim
../../profiles/opencode.nix
../../profiles/claude-code.nix
../../profiles/ripgrep.nix
../../profiles/ssh.nix
../../profiles/starship.nix
../../profiles/yazi.nix
../../profiles/zellij.nix
../../profiles/zk.nix
../../profiles/zoxide.nix
../../profiles/zsh.nix
inputs.nixvim.homeModules.nixvim
];
programs.git.settings.user.email = "christoph@schmatzler.com";
systemd.user.services.opencode-inbox-triage = {
Unit = {
Description = "OpenCode inbox triage";
};
Service = {
Type = "oneshot";
ExecStart = "${opencode}/bin/opencode run --command inbox-triage";
Environment = "PATH=${himalaya}/bin:${opencode}/bin:${pkgs.coreutils}/bin";
};
};
systemd.user.timers.opencode-inbox-triage = {
Unit = {
Description = "Run OpenCode inbox triage every 10 minutes";
};
Timer = {
OnCalendar = "*:0/10";
Persistent = true;
};
Install = {
WantedBy = ["timers.target"];
};
};
};
virtualisation.docker.enable = true;
users.users.${user}.extraGroups = ["docker"];
swapDevices = [
{
device = "/swapfile";
size = 16 * 1024;
}
];
}

View File

@@ -1,13 +0,0 @@
{user, ...}: {
sops.secrets = {
tahani-paperless-password = {
sopsFile = ../../secrets/tahani-paperless-password;
format = "binary";
};
tahani-email-password = {
sopsFile = ../../secrets/tahani-email-password;
format = "binary";
owner = user;
};
};
}

View File

@@ -2,7 +2,6 @@
config,
lib,
pkgs,
user,
...
}:
with lib; let
@@ -46,7 +45,7 @@ in {
{path = "/System/Applications/Music.app/";}
{path = "/System/Applications/System Settings.app/";}
{
path = "${config.users.users.${user}.home}/Downloads";
path = "${config.users.users.cschmatzler.home}/Downloads";
section = "others";
options = "--sort name --view grid --display stack";
}
@@ -57,7 +56,7 @@ in {
mkOption {
description = "Username to apply the dock settings to";
type = types.str;
default = user;
default = "cschmatzler";
};
};
};

View File

@@ -11,7 +11,6 @@ with pkgs;
alejandra
ast-grep
bun
colmena
delta
devenv
dig

View File

130
modules/ai-tools.nix Normal file
View File

@@ -0,0 +1,130 @@
{inputs, ...}: {
den.aspects.ai-tools.homeManager = {
pkgs,
inputs',
...
}: {
programs.opencode = {
enable = true;
package = inputs'.llm-agents.packages.opencode;
settings = {
model = "anthropic/claude-opus-4-6";
small_model = "anthropic/claude-haiku-4-5";
theme = "catppuccin";
plugin = ["oh-my-opencode@latest" "opencode-anthropic-auth@latest"];
permission = {
read = {
"*" = "allow";
"*.env" = "deny";
"*.env.*" = "deny";
"*.envrc" = "deny";
"secrets/*" = "deny";
};
};
agent = {
plan = {
model = "anthropic/claude-opus-4-6";
};
explore = {
model = "anthropic/claude-haiku-4-5";
};
};
instructions = [
"CLAUDE.md"
"AGENT.md"
# "AGENTS.md"
"AGENTS.local.md"
];
formatter = {
mix = {
disabled = true;
};
};
mcp = {
opensrc = {
enabled = true;
type = "local";
command = ["bunx" "opensrc-mcp"];
};
};
};
};
home.packages = [
inputs'.llm-agents.packages.claude-code
];
xdg.configFile = {
"opencode/agent" = {
source = ./_opencode/agent;
recursive = true;
};
"opencode/command" = {
source = ./_opencode/command;
recursive = true;
};
"opencode/skill" = {
source = ./_opencode/skill;
recursive = true;
};
"opencode/tool" = {
source = ./_opencode/tool;
recursive = true;
};
"opencode/plugin" = {
source = ./_opencode/plugin;
recursive = true;
};
"opencode/AGENTS.md".source = ./_opencode/AGENTS.md;
"opencode/oh-my-opencode.json".text =
builtins.toJSON {
"$schema" = "https://raw.githubusercontent.com/code-yeongyu/oh-my-opencode/master/assets/oh-my-opencode.schema.json";
disabled_skills = ["playwright" "dev-browser"];
git_master = {
commit_footer = false;
include_co_authored_by = false;
};
runtime_fallback = true;
agents = {
explore = {
model = "opencode-go/minimax-m2.5";
fallback_models = ["anthropic/claude-haiku-4-5"];
};
librarian = {
model = "opencode-go/minimax-m2.5";
fallback_models = ["opencode-go/glm-5"];
};
sisyphus = {
fallback_models = ["opencode-go/kimi-k2.5" "opencode-go/glm-5"];
};
};
categories = {
"visual-engineering" = {
fallback_models = ["opencode-go/glm-5" "opencode-go/kimi-k2.5"];
};
ultrabrain = {
fallback_models = ["opencode-go/kimi-k2.5" "opencode-go/glm-5"];
};
deep = {
fallback_models = ["opencode-go/kimi-k2.5" "opencode-go/glm-5"];
};
artistry = {
fallback_models = ["opencode-go/kimi-k2.5" "opencode-go/glm-5"];
};
quick = {
fallback_models = ["opencode-go/minimax-m2.5"];
};
"unspecified-low" = {
fallback_models = ["opencode-go/minimax-m2.5" "opencode-go/kimi-k2.5"];
};
"unspecified-high" = {
fallback_models = ["opencode-go/kimi-k2.5" "opencode-go/glm-5"];
};
writing = {
fallback_models = ["opencode-go/kimi-k2.5" "opencode-go/minimax-m2.5"];
};
};
};
};
};
}

26
modules/apps.nix Normal file
View File

@@ -0,0 +1,26 @@
{inputs, ...}: {
perSystem = {
pkgs,
system,
...
}: let
mkApp = name: {
type = "app";
program = "${(pkgs.writeShellScriptBin name ''
PATH=${pkgs.git}/bin:$PATH
echo "Running ${name} for ${system}"
exec ${inputs.self}/apps/${system}/${name} "$@"
'')}/bin/${name}";
};
appNames = ["apply" "build" "build-switch" "rollback"];
in {
apps =
pkgs.lib.genAttrs appNames mkApp
// {
deploy = {
type = "app";
program = "${inputs.deploy-rs.packages.${system}.deploy-rs}/bin/deploy";
};
};
};
}

17
modules/atuin.nix Normal file
View File

@@ -0,0 +1,17 @@
{...}: {
den.aspects.atuin.homeManager = {...}: {
programs.atuin = {
enable = true;
enableNushellIntegration = true;
flags = [
"--disable-up-arrow"
];
settings = {
style = "compact";
inline_height = 0;
show_help = false;
show_tabs = false;
};
};
};
}

29
modules/chidi.nix Normal file
View File

@@ -0,0 +1,29 @@
{den, ...}: {
den.aspects.chidi.includes = [
den.aspects.darwin-system
den.aspects.core
den.aspects.tailscale
den.aspects.desktop
den.aspects.terminal
den.aspects.atuin
den.aspects.dev-tools
den.aspects.neovim
den.aspects.ai-tools
den.aspects.zellij
den.aspects.zk
];
den.aspects.chidi.darwin = {pkgs, ...}: {
networking.hostName = "chidi";
networking.computerName = "chidi";
environment.systemPackages = with pkgs; [
slack
];
};
den.aspects.chidi.homeManager = {...}: {
fonts.fontconfig.enable = true;
programs.git.settings.user.email = "christoph@tuist.dev";
};
}

33
modules/core.nix Normal file
View File

@@ -0,0 +1,33 @@
{...}: {
den.aspects.core.os = {pkgs, ...}: {
programs.fish.enable = true;
environment.shells = [pkgs.nushell];
nixpkgs = {
config = {
allowUnfree = true;
};
};
nix = {
package = pkgs.nix;
settings = {
substituters = [
"https://nix-community.cachix.org"
"https://cache.nixos.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
gc = {
automatic = true;
options = "--delete-older-than 30d";
};
extraOptions = ''
experimental-features = nix-command flakes
'';
};
};
}

View File

@@ -1,17 +1,14 @@
{
pkgs,
inputs,
user,
constants,
...
}: {
home-manager.extraSpecialArgs = {inherit user constants inputs;};
{inputs, ...}: {
den.aspects.darwin-system.darwin = {pkgs, ...}: {
imports = [
inputs.nix-homebrew.darwinModules.nix-homebrew
inputs.home-manager.darwinModules.home-manager
./_darwin/dock.nix
];
system = {
primaryUser = user;
stateVersion = constants.stateVersions.darwin;
system.primaryUser = "cschmatzler";
defaults = {
system.defaults = {
NSGlobalDomain = {
AppleInterfaceStyle = null;
AppleShowAllExtensions = true;
@@ -103,23 +100,42 @@
};
};
};
};
nix = {
settings.trusted-users = ["@admin" "${user}"];
gc.interval = {
Weekday = 0;
Hour = 2;
Minute = 0;
nix = {
settings.trusted-users = ["@admin" "cschmatzler"];
gc.interval = {
Weekday = 0;
Hour = 2;
Minute = 0;
};
};
users.users.cschmatzler = {
name = "cschmatzler";
home = "/Users/cschmatzler";
isHidden = false;
shell = pkgs.nushell;
};
home-manager.useGlobalPkgs = true;
nix-homebrew = {
enable = true;
user = "cschmatzler";
mutableTaps = true;
taps = {
"homebrew/homebrew-core" = inputs.homebrew-core;
"homebrew/homebrew-cask" = inputs.homebrew-cask;
};
};
homebrew = {
enable = true;
casks = [
"ghostty@tip"
"helium-browser"
"tidal"
];
};
};
users.users.${user} = {
name = user;
home = "/Users/${user}";
isHidden = false;
shell = pkgs.nushell;
};
home-manager.useGlobalPkgs = true;
}

31
modules/defaults.nix Normal file
View File

@@ -0,0 +1,31 @@
{
den,
lib,
...
}: {
options.flake = {
darwinConfigurations =
lib.mkOption {
type = lib.types.lazyAttrsOf lib.types.raw;
default = {};
};
deploy =
lib.mkOption {
type = lib.types.lazyAttrsOf lib.types.raw;
default = {};
};
};
config = {
den.default.nixos.system.stateVersion = "25.11";
den.default.darwin.system.stateVersion = 6;
den.default.homeManager.home.stateVersion = "25.11";
den.default.includes = [
den.provides.define-user
den.provides.inputs'
];
den.base.user.classes = lib.mkDefault ["homeManager"];
};
}

61
modules/dendritic.nix Normal file
View File

@@ -0,0 +1,61 @@
{inputs, ...}: {
imports = [
inputs.den.flakeModule
(inputs.flake-file.flakeModules.dendritic or {})
];
# Declare all framework and module inputs via flake-file
flake-file.inputs = {
den.url = "github:vic/den";
flake-file.url = "github:vic/flake-file";
import-tree.url = "github:vic/import-tree";
flake-aspects.url = "github:vic/flake-aspects";
nixpkgs.url = "github:nixos/nixpkgs/master";
flake-parts = {
url = "github:hercules-ci/flake-parts";
inputs.nixpkgs-lib.follows = "nixpkgs";
};
home-manager = {
url = "github:nix-community/home-manager";
inputs.nixpkgs.follows = "nixpkgs";
};
darwin = {
url = "github:LnL7/nix-darwin/master";
inputs.nixpkgs.follows = "nixpkgs";
};
deploy-rs.url = "github:serokell/deploy-rs";
disko = {
url = "github:nix-community/disko";
inputs.nixpkgs.follows = "nixpkgs";
};
nix-homebrew.url = "github:zhaofengli-wip/nix-homebrew";
homebrew-core = {
url = "github:homebrew/homebrew-core";
flake = false;
};
homebrew-cask = {
url = "github:homebrew/homebrew-cask";
flake = false;
};
nixvim.url = "github:nix-community/nixvim";
llm-agents.url = "github:numtide/llm-agents.nix";
# Overlay inputs
himalaya.url = "github:pimalaya/himalaya";
jj-ryu = {
url = "github:dmmulroy/jj-ryu";
flake = false;
};
jj-starship.url = "github:dmmulroy/jj-starship";
zjstatus.url = "github:dj95/zjstatus";
tuicr.url = "github:agavra/tuicr";
naersk = {
url = "github:nix-community/naersk/master";
inputs.nixpkgs.follows = "nixpkgs";
};
# Secrets inputs
sops-nix = {
url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
}

26
modules/deploy.nix Normal file
View File

@@ -0,0 +1,26 @@
{
inputs,
config,
...
}: {
flake.deploy.nodes = {
michael = {
hostname = "michael";
sshUser = "cschmatzler";
profiles.system = {
user = "root";
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos config.flake.nixosConfigurations.michael;
};
};
tahani = {
hostname = "tahani";
sshUser = "cschmatzler";
profiles.system = {
user = "root";
path = inputs.deploy-rs.lib.x86_64-linux.activate.nixos config.flake.nixosConfigurations.tahani;
};
};
};
flake.checks.x86_64-linux = inputs.deploy-rs.lib.x86_64-linux.deployChecks config.flake.deploy;
}

144
modules/desktop.nix Normal file
View File

@@ -0,0 +1,144 @@
{...}: {
den.aspects.desktop.homeManager = {...}: {
programs.aerospace = {
enable = true;
launchd.enable = true;
settings = {
start-at-login = true;
accordion-padding = 30;
default-root-container-layout = "tiles";
default-root-container-orientation = "auto";
on-focused-monitor-changed = [
"move-mouse monitor-lazy-center"
];
workspace-to-monitor-force-assignment = {
"1" = "main";
"2" = "main";
"3" = "main";
"4" = "main";
"5" = "main";
"6" = "main";
"7" = "main";
"8" = "main";
"9" = "secondary";
};
gaps = {
inner = {
horizontal = 8;
vertical = 8;
};
outer = {
left = 8;
right = 8;
top = 8;
bottom = 8;
};
};
on-window-detected = [
{
"if" = {
"app-id" = "com.apple.systempreferences";
};
run = "layout floating";
}
{
"if" = {
"app-id" = "com.mitchellh.ghostty";
};
run = ["layout tiling" "move-node-to-workspace 3"];
}
{
"if" = {
"app-id" = "net.imput.helium";
};
run = "move-node-to-workspace 2";
}
{
"if" = {
"app-id" = "com.tinyspeck.slackmacgap";
};
run = "move-node-to-workspace 5";
}
{
"if" = {
"app-id" = "net.whatsapp.WhatsApp";
};
run = "move-node-to-workspace 5";
}
{
"if" = {
"app-id" = "com.tidal.desktop";
};
run = "move-node-to-workspace 6";
}
];
mode = {
main.binding = {
"alt-enter" = "exec-and-forget open -a Ghostty";
"alt-h" = "focus left";
"alt-j" = "focus down";
"alt-k" = "focus up";
"alt-l" = "focus right";
"alt-shift-h" = "move left";
"alt-shift-j" = "move down";
"alt-shift-k" = "move up";
"alt-shift-l" = "move right";
"alt-ctrl-h" = "focus-monitor --wrap-around left";
"alt-ctrl-j" = "focus-monitor --wrap-around down";
"alt-ctrl-k" = "focus-monitor --wrap-around up";
"alt-ctrl-l" = "focus-monitor --wrap-around right";
"alt-ctrl-shift-h" = "move-node-to-monitor --focus-follows-window --wrap-around left";
"alt-ctrl-shift-j" = "move-node-to-monitor --focus-follows-window --wrap-around down";
"alt-ctrl-shift-k" = "move-node-to-monitor --focus-follows-window --wrap-around up";
"alt-ctrl-shift-l" = "move-node-to-monitor --focus-follows-window --wrap-around right";
"alt-space" = "layout tiles accordion";
"alt-shift-space" = "layout floating tiling";
"alt-slash" = "layout horizontal vertical";
"alt-f" = "fullscreen";
"alt-tab" = "workspace-back-and-forth";
"alt-shift-tab" = "move-workspace-to-monitor --wrap-around next";
"alt-r" = "mode resize";
"alt-shift-semicolon" = "mode service";
"alt-1" = "workspace 1";
"alt-2" = "workspace 2";
"alt-3" = "workspace 3";
"alt-4" = "workspace 4";
"alt-5" = "workspace 5";
"alt-6" = "workspace 6";
"alt-7" = "workspace 7";
"alt-8" = "workspace 8";
"alt-9" = "workspace 9";
"alt-shift-1" = "move-node-to-workspace --focus-follows-window 1";
"alt-shift-2" = "move-node-to-workspace --focus-follows-window 2";
"alt-shift-3" = "move-node-to-workspace --focus-follows-window 3";
"alt-shift-4" = "move-node-to-workspace --focus-follows-window 4";
"alt-shift-5" = "move-node-to-workspace --focus-follows-window 5";
"alt-shift-6" = "move-node-to-workspace --focus-follows-window 6";
"alt-shift-7" = "move-node-to-workspace --focus-follows-window 7";
"alt-shift-8" = "move-node-to-workspace --focus-follows-window 8";
"alt-shift-9" = "move-node-to-workspace --focus-follows-window 9";
};
resize.binding = {
"h" = "resize width -50";
"j" = "resize height +50";
"k" = "resize height -50";
"l" = "resize width +50";
"enter" = "mode main";
"esc" = "mode main";
};
service.binding = {
"esc" = "mode main";
"r" = ["reload-config" "mode main"];
"b" = ["balance-sizes" "mode main"];
"f" = ["layout floating tiling" "mode main"];
"backspace" = ["close-all-windows-but-current" "mode main"];
};
};
};
};
};
}

371
modules/dev-tools.nix Normal file
View File

@@ -0,0 +1,371 @@
{...}: {
den.aspects.dev-tools.homeManager = {...}: let
name = "Christoph Schmatzler";
in {
# Git configuration
programs.git = {
enable = true;
ignores = ["*.swp"];
settings = {
user.name = name;
init.defaultBranch = "main";
core = {
editor = "vim";
autocrlf = "input";
pager = "delta";
};
credential = {
helper = "!gh auth git-credential";
"https://github.com".useHttpPath = true;
"https://gist.github.com".useHttpPath = true;
};
pull.rebase = true;
rebase.autoStash = true;
interactive.diffFilter = "delta --color-only";
delta = {
navigate = true;
line-numbers = true;
syntax-theme = "GitHub";
side-by-side = true;
pager = "less -FRX";
};
pager = {
diff = "delta";
log = "delta";
show = "delta";
};
};
lfs = {
enable = true;
};
};
# Git shell aliases
home.shellAliases = {
g = "git";
ga = "git add";
gaa = "git add --all";
gapa = "git add --patch";
gau = "git add --update";
gav = "git add --verbose";
gap = "git apply";
gapt = "git apply --3way";
gb = "git branch";
gba = "git branch --all";
gbd = "git branch --delete";
gbD = "git branch --delete --force";
gbl = "git blame -w";
gbnm = "git branch --no-merged";
gbr = "git branch --remote";
gbs = "git bisect";
gbsb = "git bisect bad";
gbsg = "git bisect good";
gbsn = "git bisect new";
gbso = "git bisect old";
gbsr = "git bisect reset";
gbss = "git bisect start";
gc = "git commit --verbose";
gca = "git commit --verbose --all";
gcam = "git commit --all --message";
gcas = "git commit --all --signoff";
gcasm = "git commit --all --signoff --message";
gcb = "git checkout -b";
gcB = "git checkout -B";
gcf = "git config --list";
gclean = "git clean --interactive -d";
gcl = "git clone --recurse-submodules";
gclf = "git clone --recursive --shallow-submodules --filter=blob:none --also-filter-submodules";
gcm = "git checkout main";
gcmsg = "git commit --message";
gcn = "git commit --verbose --no-edit";
gco = "git checkout";
gcor = "git checkout --recurse-submodules";
gcount = "git shortlog --summary --numbered";
gcp = "git cherry-pick";
gcpa = "git cherry-pick --abort";
gcpc = "git cherry-pick --continue";
gcs = "git commit --gpg-sign";
gcss = "git commit --gpg-sign --signoff";
gcssm = "git commit --gpg-sign --signoff --message";
gcsm = "git commit --signoff --message";
gd = "git diff";
gdca = "git diff --cached";
gdcw = "git diff --cached --word-diff";
gds = "git diff --staged";
gdw = "git diff --word-diff";
gdt = "git diff-tree --no-commit-id --name-only -r";
gdup = "git diff @{upstream}";
gf = "git fetch";
gfa = "git fetch --all --tags --prune";
gfo = "git fetch origin";
gg = "git gui citool";
gga = "git gui citool --amend";
ghh = "git help";
gignore = "git update-index --assume-unchanged";
gl = "git pull";
glg = "git log --stat";
glgp = "git log --stat --patch";
glgg = "git log --graph";
glgga = "git log --graph --decorate --all";
glgm = "git log --graph --max-count=10";
glo = "git log --oneline --decorate";
glog = "git log --oneline --decorate --graph";
gloga = "git log --oneline --decorate --graph --all";
glol = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\"";
glola = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\" --all";
glols = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ar) %C(bold blue)<%an>%Creset\" --stat";
glod = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset\"";
glods = "git log --graph --pretty=\"%Cred%h%Creset -%C(auto)%d%Creset %s %Cgreen(%ad) %C(bold blue)<%an>%Creset\" --date=short";
glum = "git pull upstream main";
gm = "git merge";
gma = "git merge --abort";
gmc = "git merge --continue";
gms = "git merge --squash";
gmff = "git merge --ff-only";
gmtl = "git mergetool --no-prompt";
gmtlvim = "git mergetool --no-prompt --tool=vimdiff";
gmum = "git merge upstream/main";
gmom = "git merge origin/main";
gp = "git push";
gpd = "git push --dry-run";
gpf = "git push --force-with-lease";
gpod = "git push origin --delete";
gpr = "git pull --rebase";
gpra = "git pull --rebase --autostash";
gprav = "git pull --rebase --autostash -v";
gprom = "git pull --rebase origin main";
gpromi = "git pull --rebase=interactive origin main";
gprv = "git pull --rebase -v";
gprum = "git pull --rebase upstream main";
gprumi = "git pull --rebase=interactive upstream main";
gpv = "git push --verbose";
gpu = "git push upstream";
gr = "git remote";
gra = "git remote add";
grb = "git rebase";
grba = "git rebase --abort";
grbc = "git rebase --continue";
grbd = "git rebase develop";
grbi = "git rebase --interactive";
grbm = "git rebase main";
grbo = "git rebase --onto";
grbom = "git rebase origin/main";
grbs = "git rebase --skip";
grbum = "git rebase upstream/main";
grev = "git revert";
greva = "git revert --abort";
grevc = "git revert --continue";
grf = "git reflog";
grh = "git reset";
grhh = "git reset --hard";
grhk = "git reset --keep";
grhs = "git reset --soft";
grm = "git rm";
grmc = "git rm --cached";
grmv = "git remote rename";
grrm = "git remote remove";
grs = "git restore";
grset = "git remote set-url";
grss = "git restore --source";
grst = "git restore --staged";
gru = "git reset --";
grup = "git remote update";
grv = "git remote --verbose";
gsb = "git status --short --branch";
gsh = "git show";
gsi = "git submodule init";
gsps = "git show --pretty=short --show-signature";
gss = "git status --short";
gst = "git status";
gsta = "git stash push";
gstaa = "git stash apply";
gstall = "git stash --all";
gstc = "git stash clear";
gstd = "git stash drop";
gstl = "git stash list";
gstp = "git stash pop";
gsts = "git stash show --patch";
gstu = "git stash push --include-untracked";
gsu = "git submodule update";
gsw = "git switch";
gswc = "git switch --create";
gswd = "git switch develop";
gswm = "git switch main";
gta = "git tag --annotate";
gts = "git tag --sign";
gunignore = "git update-index --no-assume-unchanged";
gwch = "git whatchanged -p --abbrev-commit --pretty=medium";
gwt = "git worktree";
gwta = "git worktree add";
gwtls = "git worktree list";
gwtmv = "git worktree move";
gwtrm = "git worktree remove";
lg = "lazygit";
};
# Complex git aliases that require pipes/subshells — nushell `alias` can't
# handle these, so they're defined as custom commands instead.
programs.nushell.extraConfig = ''
def ggpull [] { git pull origin (git branch --show-current | str trim) }
def ggpush [] { git push origin (git branch --show-current | str trim) }
def ggsup [] { git branch $"--set-upstream-to=origin/(git branch --show-current | str trim)" }
def gluc [] { git pull upstream (git branch --show-current | str trim) }
def gpsup [] { git push --set-upstream origin (git branch --show-current | str trim) }
def gpsupf [] { git push --set-upstream origin (git branch --show-current | str trim) --force-with-lease }
def groh [] { git reset $"origin/(git branch --show-current | str trim)" --hard }
def --env grt [] {
let toplevel = (do { git rev-parse --show-toplevel } | complete | get stdout | str trim)
if ($toplevel | is-not-empty) { cd $toplevel } else { cd . }
}
def gfg [...pattern: string] { git ls-files | lines | where {|f| $f =~ ($pattern | str join ".*") } }
def gignored [] { git ls-files -v | lines | where {|l| ($l | str substring 0..1) =~ "[a-z]" } }
def gpoat [] { git push origin --all; git push origin --tags }
def gtv [] { git tag | lines | sort }
def gwipe [] { git reset --hard; git clean --force -df }
def gunwip [] {
let msg = (git rev-list --max-count=1 --format="%s" HEAD | lines | get 1)
if ($msg | str contains "--wip--") { git reset HEAD~1 }
}
def gwip [] {
git add -A
let deleted = (git ls-files --deleted | lines)
if ($deleted | is-not-empty) { git rm ...$deleted }
git commit --no-verify --no-gpg-sign --message "--wip-- [skip ci]"
}
'';
# Jujutsu configuration
programs.jujutsu = {
enable = true;
settings = {
user = {
name = name;
email = "christoph@schmatzler.com";
};
git = {
sign-on-push = true;
subprocess = true;
write-change-id-header = true;
private-commits = "description(glob:'wip:*') | description(glob:'WIP:*') | description(exact:'')";
};
fsmonitor = {
backend = "watchman";
};
ui = {
default-command = "status";
diff-formatter = ":git";
pager = ["delta" "--pager" "less -FRX"];
diff-editor = ["nvim" "-c" "DiffEditor $left $right $output"];
movement = {
edit = true;
};
};
aliases = {
n = ["new"];
tug = ["bookmark" "move" "--from" "closest_bookmark(@-)" "--to" "@-"];
stack = ["log" "-r" "stack()"];
retrunk = ["rebase" "-d" "trunk()"];
bm = ["bookmark"];
gf = ["git" "fetch"];
gp = ["git" "push"];
};
revset-aliases = {
"closest_bookmark(to)" = "heads(::to & bookmarks())";
"closest_pushable(to)" = "heads(::to & mutable() & ~description(exact:\"\") & (~empty() | merges()))";
"mine()" = "author(\"christoph@schmatzler.com\")";
"wip()" = "mine() ~ immutable()";
"open()" = "mine() ~ ::trunk()";
"current()" = "@:: & mutable()";
"stack()" = "reachable(@, mutable())";
};
templates = {
draft_commit_description = ''
concat(
coalesce(description, default_commit_description, "\n"),
surround(
"\nJJ: This commit contains the following changes:\n", "",
indent("JJ: ", diff.stat(72)),
),
"\nJJ: ignore-rest\n",
diff.git(),
)
'';
};
};
};
# Lazygit configuration
programs.lazygit = {
enable = true;
settings = {
git = {
commit.signOff = true;
pagers = [
{
delta = {
colorArg = "always";
pager = "DELTA_FEATURES=decorations delta --light --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format=\"lazygit-edit://{path}:{line}\"";
};
}
];
};
gui = {
authorColors = {
"*" = "#7287fd";
};
theme = {
activeBorderColor = [
"#8839ef"
"bold"
];
inactiveBorderColor = [
"#6c6f85"
];
optionsTextColor = [
"#1e66f5"
];
selectedLineBgColor = [
"#ccd0da"
];
cherryPickedCommitBgColor = [
"#bcc0cc"
];
cherryPickedCommitFgColor = [
"#8839ef"
];
defaultFgColor = [
"#4c4f69"
];
searchingActiveBorderColor = [
"#df8e1d"
];
unstagedChangesColor = [
"#d20f39"
];
};
};
};
};
# JJUI configuration
programs.jjui = {
enable = true;
};
# Direnv configuration
programs.direnv = {
enable = true;
nix-direnv.enable = true;
};
# Mise configuration
programs.mise = {
enable = true;
enableNushellIntegration = true;
globalConfig.settings = {
auto_install = false;
};
};
};
}

54
modules/email.nix Normal file
View File

@@ -0,0 +1,54 @@
{...}: {
den.aspects.email.homeManager = {pkgs, ...}: {
programs.himalaya = {
enable = true;
package =
pkgs.writeShellApplication {
name = "himalaya";
runtimeInputs = [pkgs.bash pkgs.coreutils pkgs.himalaya];
text = ''
exec env RUST_LOG="warn,imap_codec::response=error" ${pkgs.himalaya}/bin/himalaya "$@"
'';
};
};
programs.mbsync.enable = true;
services.mbsync = {
enable = true;
frequency = "*:0/5";
};
accounts.email = {
accounts."christoph@schmatzler.com" = {
primary = true;
maildir.path = "christoph@schmatzler.com";
address = "christoph@schmatzler.com";
userName = "christoph.schmatzler@icloud.com";
realName = "Christoph Schmatzler";
passwordCommand = ["cat" "/run/secrets/tahani-email-password"];
folders = {
inbox = "INBOX";
drafts = "Drafts";
sent = "Sent Messages";
trash = "Deleted Messages";
};
smtp = {
host = "smtp.mail.me.com";
port = 587;
tls.useStartTls = true;
};
himalaya.enable = true;
mbsync = {
enable = true;
create = "both";
expunge = "both";
};
imap = {
host = "imap.mail.me.com";
port = 993;
tls.enable = true;
};
};
};
};
}

View File

@@ -1,198 +0,0 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.my.gitea;
in {
options.my.gitea = {
enable = mkEnableOption "Gitea git hosting service";
litestream = {
bucket =
mkOption {
type = types.str;
description = "S3 bucket name for Litestream database replication";
};
secretFile =
mkOption {
type = types.path;
description = "Path to the environment file containing S3 credentials for Litestream";
};
};
restic = {
bucket =
mkOption {
type = types.str;
description = "S3 bucket name for Restic repository backups";
};
passwordFile =
mkOption {
type = types.path;
description = "Path to the file containing the Restic repository password";
};
environmentFile =
mkOption {
type = types.path;
description = "Path to the environment file containing S3 credentials for Restic";
};
};
s3 = {
endpoint =
mkOption {
type = types.str;
default = "s3.eu-central-003.backblazeb2.com";
description = "S3 endpoint URL";
};
};
};
config =
mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [80 443];
services.redis.servers.gitea = {
enable = true;
port = 6380;
bind = "127.0.0.1";
settings = {
maxmemory = "64mb";
maxmemory-policy = "allkeys-lru";
};
};
services.gitea = {
enable = true;
database = {
type = "sqlite3";
path = "/var/lib/gitea/data/gitea.db";
};
settings = {
server = {
ROOT_URL = "https://git.schmatzler.com/";
DOMAIN = "git.schmatzler.com";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
LANDING_PAGE = "explore";
};
service.DISABLE_REGISTRATION = true;
security.INSTALL_LOCK = true;
cache = {
ADAPTER = "redis";
HOST = "redis://127.0.0.1:6380/0?pool_size=100&idle_timeout=180s";
ITEM_TTL = "16h";
};
"cache.last_commit" = {
ITEM_TTL = "8760h";
COMMITS_COUNT = 100;
};
session = {
PROVIDER = "redis";
PROVIDER_CONFIG = "redis://127.0.0.1:6380/1?pool_size=100&idle_timeout=180s";
COOKIE_SECURE = true;
SAME_SITE = "strict";
};
api.ENABLE_SWAGGER = false;
};
};
services.litestream = {
enable = true;
environmentFile = cfg.litestream.secretFile;
settings = {
dbs = [
{
path = "/var/lib/gitea/data/gitea.db";
replicas = [
{
type = "s3";
bucket = cfg.litestream.bucket;
path = "gitea";
endpoint = cfg.s3.endpoint;
}
];
}
];
};
};
systemd.services.litestream = {
serviceConfig = {
User = mkForce "gitea";
Group = mkForce "gitea";
};
};
services.caddy = {
enable = true;
virtualHosts."git.schmatzler.com".extraConfig = ''
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:3000
'';
};
services.restic.backups.gitea = {
repository = "s3:${cfg.s3.endpoint}/${cfg.restic.bucket}";
paths = ["/var/lib/gitea"];
exclude = [
"/var/lib/gitea/log"
"/var/lib/gitea/data/gitea.db"
"/var/lib/gitea/data/gitea.db-shm"
"/var/lib/gitea/data/gitea.db-wal"
];
passwordFile = cfg.restic.passwordFile;
environmentFile = cfg.restic.environmentFile;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.restic-backups-gitea = {
wants = ["restic-init-gitea.service"];
after = ["restic-init-gitea.service"];
serviceConfig = {
User = mkForce "gitea";
Group = mkForce "gitea";
};
};
systemd.services.restic-init-gitea = {
description = "Initialize Restic repository for Gitea backups";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
path = [pkgs.restic];
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "gitea";
RemainAfterExit = true;
EnvironmentFile = cfg.restic.environmentFile;
};
script = ''
export RESTIC_PASSWORD=$(cat ${cfg.restic.passwordFile})
restic -r s3:${cfg.s3.endpoint}/${cfg.restic.bucket} snapshots &>/dev/null || \
restic -r s3:${cfg.s3.endpoint}/${cfg.restic.bucket} init
'';
};
};
}

6
modules/hosts.nix Normal file
View File

@@ -0,0 +1,6 @@
{...}: {
den.hosts.aarch64-darwin.chidi.users.cschmatzler = {};
den.hosts.aarch64-darwin.jason.users.cschmatzler = {};
den.hosts.x86_64-linux.michael.users.cschmatzler = {};
den.hosts.x86_64-linux.tahani.users.cschmatzler = {};
}

25
modules/jason.nix Normal file
View File

@@ -0,0 +1,25 @@
{den, ...}: {
den.aspects.jason.includes = [
den.aspects.darwin-system
den.aspects.core
den.aspects.tailscale
den.aspects.desktop
den.aspects.terminal
den.aspects.atuin
den.aspects.dev-tools
den.aspects.neovim
den.aspects.ai-tools
den.aspects.zellij
den.aspects.zk
];
den.aspects.jason.darwin = {...}: {
networking.hostName = "jason";
networking.computerName = "jason";
};
den.aspects.jason.homeManager = {...}: {
fonts.fontconfig.enable = true;
programs.git.settings.user.email = "christoph@schmatzler.com";
};
}

191
modules/michael.nix Normal file
View File

@@ -0,0 +1,191 @@
{
inputs,
den,
...
}: {
den.aspects.michael.includes = [
den.aspects.nixos-system
den.aspects.core
den.aspects.openssh
den.aspects.fail2ban
den.aspects.tailscale
];
den.aspects.michael.nixos = {
config,
pkgs,
lib,
modulesPath,
...
}: {
imports = [
(modulesPath + "/installer/scan/not-detected.nix")
(modulesPath + "/profiles/qemu-guest.nix")
./_hosts/michael/disk-config.nix
./_hosts/michael/hardware-configuration.nix
inputs.disko.nixosModules.default
];
networking.hostName = "michael";
sops.secrets = {
michael-gitea-litestream = {
sopsFile = ../secrets/michael-gitea-litestream;
format = "binary";
owner = "gitea";
group = "gitea";
};
michael-gitea-restic-password = {
sopsFile = ../secrets/michael-gitea-restic-password;
format = "binary";
owner = "gitea";
group = "gitea";
};
michael-gitea-restic-env = {
sopsFile = ../secrets/michael-gitea-restic-env;
format = "binary";
owner = "gitea";
group = "gitea";
};
};
networking.firewall.allowedTCPPorts = [80 443];
services.redis.servers.gitea = {
enable = true;
port = 6380;
bind = "127.0.0.1";
settings = {
maxmemory = "64mb";
maxmemory-policy = "allkeys-lru";
};
};
services.gitea = {
enable = true;
database = {
type = "sqlite3";
path = "/var/lib/gitea/data/gitea.db";
};
settings = {
server = {
ROOT_URL = "https://git.schmatzler.com/";
DOMAIN = "git.schmatzler.com";
HTTP_ADDR = "127.0.0.1";
HTTP_PORT = 3000;
LANDING_PAGE = "explore";
};
service.DISABLE_REGISTRATION = true;
security.INSTALL_LOCK = true;
cache = {
ADAPTER = "redis";
HOST = "redis://127.0.0.1:6380/0?pool_size=100&idle_timeout=180s";
ITEM_TTL = "16h";
};
"cache.last_commit" = {
ITEM_TTL = "8760h";
COMMITS_COUNT = 100;
};
session = {
PROVIDER = "redis";
PROVIDER_CONFIG = "redis://127.0.0.1:6380/1?pool_size=100&idle_timeout=180s";
COOKIE_SECURE = true;
SAME_SITE = "strict";
};
api.ENABLE_SWAGGER = false;
};
};
services.litestream = {
enable = true;
environmentFile = config.sops.secrets.michael-gitea-litestream.path;
settings = {
dbs = [
{
path = "/var/lib/gitea/data/gitea.db";
replicas = [
{
type = "s3";
bucket = "michael-gitea-litestream";
path = "gitea";
endpoint = "s3.eu-central-003.backblazeb2.com";
}
];
}
];
};
};
systemd.services.litestream = {
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
services.caddy = {
enable = true;
virtualHosts."git.schmatzler.com".extraConfig = ''
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "strict-origin-when-cross-origin"
}
reverse_proxy localhost:3000
'';
};
services.restic.backups.gitea = {
repository = "s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories";
paths = ["/var/lib/gitea"];
exclude = [
"/var/lib/gitea/log"
"/var/lib/gitea/data/gitea.db"
"/var/lib/gitea/data/gitea.db-shm"
"/var/lib/gitea/data/gitea.db-wal"
];
passwordFile = config.sops.secrets.michael-gitea-restic-password.path;
environmentFile = config.sops.secrets.michael-gitea-restic-env.path;
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 4"
"--keep-monthly 6"
];
timerConfig = {
OnCalendar = "daily";
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.restic-backups-gitea = {
wants = ["restic-init-gitea.service"];
after = ["restic-init-gitea.service"];
serviceConfig = {
User = lib.mkForce "gitea";
Group = lib.mkForce "gitea";
};
};
systemd.services.restic-init-gitea = {
description = "Initialize Restic repository for Gitea backups";
wantedBy = ["multi-user.target"];
after = ["network-online.target"];
wants = ["network-online.target"];
path = [pkgs.restic];
serviceConfig = {
Type = "oneshot";
User = "gitea";
Group = "gitea";
RemainAfterExit = true;
EnvironmentFile = config.sops.secrets.michael-gitea-restic-env.path;
};
script = ''
export RESTIC_PASSWORD=$(cat ${config.sops.secrets.michael-gitea-restic-password.path})
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories snapshots &>/dev/null || \
restic -r s3:s3.eu-central-003.backblazeb2.com/michael-gitea-repositories init
'';
};
};
}

8
modules/neovim.nix Normal file
View File

@@ -0,0 +1,8 @@
{inputs, ...}: {
den.aspects.neovim.homeManager = {pkgs, ...}: {
imports = [
inputs.nixvim.homeModules.nixvim
./_neovim/default.nix
];
};
}

67
modules/network.nix Normal file
View File

@@ -0,0 +1,67 @@
{...}: {
den.aspects.openssh.nixos = {
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
};
};
};
den.aspects.fail2ban.nixos = {
services.fail2ban = {
enable = true;
maxretry = 5;
bantime = "10m";
bantime-increment = {
enable = true;
multipliers = "1 2 4 8 16 32 64";
maxtime = "168h";
overalljails = true;
};
jails = {
sshd = {
settings = {
enabled = true;
port = "ssh";
filter = "sshd";
maxretry = 3;
};
};
gitea = {
settings = {
enabled = true;
filter = "gitea";
logpath = "/var/lib/gitea/log/gitea.log";
maxretry = 10;
findtime = 3600;
bantime = 900;
action = "iptables-allports";
};
};
};
};
environment.etc."fail2ban/filter.d/gitea.local".text = ''
[Definition]
failregex = .*(Failed authentication attempt|invalid credentials|Attempted access of unknown user).* from <HOST>
ignoreregex =
'';
};
den.aspects.tailscale.nixos = {
services.tailscale = {
enable = true;
openFirewall = true;
permitCertUid = "caddy";
useRoutingFeatures = "server";
};
};
den.aspects.tailscale.darwin = {
services.tailscale = {
enable = true;
};
};
}

87
modules/nixos-system.nix Normal file
View File

@@ -0,0 +1,87 @@
{inputs, ...}: {
den.aspects.nixos-system.nixos = {pkgs, ...}: {
imports = [inputs.home-manager.nixosModules.home-manager];
security.sudo.enable = true;
security.sudo.extraRules = [
{
users = ["cschmatzler"];
commands = [
{
command = "/run/current-system/sw/bin/nix-env";
options = ["NOPASSWD"];
}
{
command = "/nix/store/*/bin/switch-to-configuration";
options = ["NOPASSWD"];
}
{
command = "/nix/store/*/bin/activate";
options = ["NOPASSWD"];
}
{
command = "/nix/store/*/bin/activate-rs";
options = ["NOPASSWD"];
}
{
command = "/nix/store/*/bin/wait-activate";
options = ["NOPASSWD"];
}
];
}
];
time.timeZone = "UTC";
nix = {
settings.trusted-users = ["cschmatzler"];
gc.dates = "weekly";
nixPath = ["nixos-config=/home/cschmatzler/.local/share/src/nixos-config:/etc/nixos"];
};
boot = {
loader = {
systemd-boot = {
enable = true;
configurationLimit = 42;
};
efi.canTouchEfiVariables = true;
};
initrd.availableKernelModules = [
"xhci_pci"
"ahci"
"nvme"
"usbhid"
"usb_storage"
"sd_mod"
];
kernelPackages = pkgs.linuxPackages_latest;
};
users.users = {
cschmatzler = {
isNormalUser = true;
home = "/home/cschmatzler";
extraGroups = [
"wheel"
"sudo"
"network"
"systemd-journal"
];
shell = pkgs.nushell;
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHfRZQ+7ejD3YHbyMTrV0gN1Gc0DxtGgl5CVZSupo5ws"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL/I+/2QT47raegzMIyhwMEPKarJP/+Ox9ewA4ZFJwk/"
];
};
root = {
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHfRZQ+7ejD3YHbyMTrV0gN1Gc0DxtGgl5CVZSupo5ws"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIL/I+/2QT47raegzMIyhwMEPKarJP/+Ox9ewA4ZFJwk/"
];
};
};
home-manager.useGlobalPkgs = true;
};
}

29
modules/overlays.nix Normal file
View File

@@ -0,0 +1,29 @@
{inputs, ...}: let
overlays = [
# himalaya
(final: prev: {
himalaya = inputs.himalaya.packages.${prev.stdenv.hostPlatform.system}.default;
})
# jj-ryu (uses build-rust-package helper)
(final: prev: {
jj-ryu =
import ./_lib/build-rust-package.nix {
inherit inputs prev;
input = inputs.jj-ryu;
};
})
# jj-starship (passes through upstream overlay)
inputs.jj-starship.overlays.default
# zjstatus
(final: prev: {
zjstatus = inputs.zjstatus.packages.${prev.stdenv.hostPlatform.system}.default;
})
# tuicr
(final: prev: {
tuicr = inputs.tuicr.defaultPackage.${prev.stdenv.hostPlatform.system};
})
];
in {
den.default.nixos.nixpkgs.overlays = overlays;
den.default.darwin.nixpkgs.overlays = overlays;
}

View File

@@ -1,257 +0,0 @@
{
config,
lib,
pkgs,
...
}:
with lib; let
cfg = config.my.pgbackrest;
in {
options.my.pgbackrest = {
enable = mkEnableOption "pgBackRest PostgreSQL backup";
stanza =
mkOption {
type = types.str;
default = "main";
description = "Name of the pgBackRest stanza";
};
secretFile =
mkOption {
type = types.path;
description = "Path to the environment file containing S3 credentials and cipher passphrase";
};
s3 =
mkOption {
type =
types.submodule {
options = {
endpoint =
mkOption {
type = types.str;
default = "s3.eu-central-003.backblazeb2.com";
description = "S3 endpoint URL";
};
bucket =
mkOption {
type = types.str;
description = "S3 bucket name";
};
region =
mkOption {
type = types.str;
default = "eu-central-003";
description = "S3 region";
};
path =
mkOption {
type = types.str;
default = "/backups";
description = "Path within the S3 bucket";
};
};
};
default = {};
description = "S3 storage configuration";
};
retention =
mkOption {
type =
types.submodule {
options = {
full =
mkOption {
type = types.int;
default = 7;
description = "Number of full backups to retain";
};
diff =
mkOption {
type = types.int;
default = 7;
description = "Number of differential backups to retain";
};
};
};
default = {};
description = "Backup retention configuration";
};
compression =
mkOption {
type =
types.submodule {
options = {
type =
mkOption {
type = types.str;
default = "zst";
description = "Compression algorithm (none, gz, lz4, zst)";
};
level =
mkOption {
type = types.int;
default = 3;
description = "Compression level";
};
};
};
default = {};
description = "Compression configuration";
};
processMax =
mkOption {
type = types.int;
default = 2;
description = "Maximum number of processes for parallel operations";
};
schedule =
mkOption {
type =
types.submodule {
options = {
full =
mkOption {
type = types.str;
default = "daily";
description = "OnCalendar expression for full backups";
};
diff =
mkOption {
type = types.str;
default = "hourly";
description = "OnCalendar expression for differential backups";
};
};
};
default = {};
description = "Backup schedule configuration";
};
};
config =
mkIf cfg.enable (let
archivePushScript =
pkgs.writeShellScript "pgbackrest-archive-push" ''
set -a
source ${cfg.secretFile}
set +a
exec ${pkgs.pgbackrest}/bin/pgbackrest --stanza=${cfg.stanza} archive-push "$1"
'';
in {
environment.systemPackages = [
pkgs.pgbackrest
(pkgs.writeShellScriptBin "pgbackrest-wrapper" ''
set -a
source ${cfg.secretFile}
set +a
exec ${pkgs.pgbackrest}/bin/pgbackrest "$@"
'')
];
services.postgresql.settings = {
archive_mode = "on";
archive_command = "${archivePushScript} %p";
};
environment.etc."pgbackrest/pgbackrest.conf".text = ''
[global]
repo1-type=s3
repo1-s3-endpoint=${cfg.s3.endpoint}
repo1-s3-bucket=${cfg.s3.bucket}
repo1-s3-region=${cfg.s3.region}
repo1-path=${cfg.s3.path}
repo1-retention-full=${toString cfg.retention.full}
repo1-retention-diff=${toString cfg.retention.diff}
repo1-cipher-type=aes-256-cbc
compress-type=${cfg.compression.type}
compress-level=${toString cfg.compression.level}
process-max=${toString cfg.processMax}
log-level-console=info
log-level-file=detail
log-path=/var/log/pgbackrest
spool-path=/var/spool/pgbackrest
[${cfg.stanza}]
pg1-path=/var/lib/postgresql/${config.services.postgresql.package.psqlSchema}
pg1-user=postgres
'';
systemd.services.pgbackrest-stanza-create = {
description = "pgBackRest Stanza Create";
after = ["postgresql.service"];
requires = ["postgresql.service"];
path = [pkgs.pgbackrest];
serviceConfig = {
Type = "oneshot";
User = "postgres";
EnvironmentFile = cfg.secretFile;
RemainAfterExit = true;
};
script = ''
pgbackrest --stanza=${cfg.stanza} stanza-create || true
'';
};
systemd.services.pgbackrest-backup = {
description = "pgBackRest Full Backup";
after = ["postgresql.service" "pgbackrest-stanza-create.service"];
requires = ["postgresql.service"];
wants = ["pgbackrest-stanza-create.service"];
path = [pkgs.pgbackrest];
serviceConfig = {
Type = "oneshot";
User = "postgres";
EnvironmentFile = cfg.secretFile;
};
script = ''
pgbackrest --stanza=${cfg.stanza} backup --type=full
'';
};
systemd.timers.pgbackrest-backup = {
wantedBy = ["timers.target"];
timerConfig = {
OnCalendar = cfg.schedule.full;
Persistent = true;
RandomizedDelaySec = "1h";
};
};
systemd.services.pgbackrest-backup-diff = {
description = "pgBackRest Differential Backup";
after = ["postgresql.service" "pgbackrest-stanza-create.service"];
requires = ["postgresql.service"];
wants = ["pgbackrest-stanza-create.service"];
path = [pkgs.pgbackrest];
serviceConfig = {
Type = "oneshot";
User = "postgres";
EnvironmentFile = cfg.secretFile;
};
script = ''
pgbackrest --stanza=${cfg.stanza} backup --type=diff
'';
};
systemd.timers.pgbackrest-backup-diff = {
wantedBy = ["timers.target"];
timerConfig = {
OnCalendar = cfg.schedule.diff;
Persistent = true;
RandomizedDelaySec = "5m";
};
};
systemd.tmpfiles.rules = [
"d /var/lib/pgbackrest 0750 postgres postgres -"
"d /var/log/pgbackrest 0750 postgres postgres -"
"d /var/spool/pgbackrest 0750 postgres postgres -"
];
});
}

15
modules/secrets.nix Normal file
View File

@@ -0,0 +1,15 @@
{inputs, ...}: {
# Import sops-nix modules into den.default per-class
den.default.nixos.imports = [inputs.sops-nix.nixosModules.sops];
den.default.darwin.imports = [inputs.sops-nix.darwinModules.sops];
# Configure NixOS SOPS defaults
den.default.nixos.sops.age.sshKeyPaths = ["/etc/ssh/ssh_host_ed25519_key"];
# Configure Darwin SOPS defaults
den.default.darwin = {
sops.age.keyFile = "/Users/cschmatzler/.config/sops/age/keys.txt";
sops.age.sshKeyPaths = [];
sops.gnupg.sshKeyPaths = [];
};
}

285
modules/shell.nix Normal file
View File

@@ -0,0 +1,285 @@
{...}: {
den.aspects.shell.homeManager = {
lib,
pkgs,
...
}: {
programs.nushell = {
enable = true;
settings = {
show_banner = false;
completions = {
algorithm = "fuzzy";
case_sensitive = false;
};
history = {
file_format = "sqlite";
};
};
environmentVariables = {
COLORTERM = "truecolor";
COLORFGBG = "15;0";
TERM_BACKGROUND = "light";
EDITOR = "nvim";
};
extraEnv =
''
$env.LS_COLORS = (${pkgs.vivid}/bin/vivid generate catppuccin-latte)
''
+ lib.optionalString pkgs.stdenv.isDarwin ''
# Nushell on Darwin doesn't source /etc/zprofile or path_helper,
# so nix-managed paths must be added explicitly.
$env.PATH = ($env.PATH | split row (char esep) | prepend "/run/current-system/sw/bin" | prepend $"($env.HOME)/.nix-profile/bin")
'';
extraConfig = ''
# --- Catppuccin Latte Theme ---
let theme = {
rosewater: "#dc8a78"
flamingo: "#dd7878"
pink: "#ea76cb"
mauve: "#8839ef"
red: "#d20f39"
maroon: "#e64553"
peach: "#fe640b"
yellow: "#df8e1d"
green: "#40a02b"
teal: "#179299"
sky: "#04a5e5"
sapphire: "#209fb5"
blue: "#1e66f5"
lavender: "#7287fd"
text: "#4c4f69"
subtext1: "#5c5f77"
subtext0: "#6c6f85"
overlay2: "#7c7f93"
overlay1: "#8c8fa1"
overlay0: "#9ca0b0"
surface2: "#acb0be"
surface1: "#bcc0cc"
surface0: "#ccd0da"
base: "#eff1f5"
mantle: "#e6e9ef"
crust: "#dce0e8"
}
let scheme = {
recognized_command: $theme.blue
unrecognized_command: $theme.text
constant: $theme.peach
punctuation: $theme.overlay2
operator: $theme.sky
string: $theme.green
virtual_text: $theme.surface2
variable: { fg: $theme.flamingo attr: i }
filepath: $theme.yellow
}
$env.config.color_config = {
separator: { fg: $theme.surface2 attr: b }
leading_trailing_space_bg: { fg: $theme.lavender attr: u }
header: { fg: $theme.text attr: b }
row_index: $scheme.virtual_text
record: $theme.text
list: $theme.text
hints: $scheme.virtual_text
search_result: { fg: $theme.base bg: $theme.yellow }
shape_closure: $theme.teal
closure: $theme.teal
shape_flag: { fg: $theme.maroon attr: i }
shape_matching_brackets: { attr: u }
shape_garbage: $theme.red
shape_keyword: $theme.mauve
shape_match_pattern: $theme.green
shape_signature: $theme.teal
shape_table: $scheme.punctuation
cell-path: $scheme.punctuation
shape_list: $scheme.punctuation
shape_record: $scheme.punctuation
shape_vardecl: $scheme.variable
shape_variable: $scheme.variable
empty: { attr: n }
filesize: {||
if $in < 1kb {
$theme.teal
} else if $in < 10kb {
$theme.green
} else if $in < 100kb {
$theme.yellow
} else if $in < 10mb {
$theme.peach
} else if $in < 100mb {
$theme.maroon
} else if $in < 1gb {
$theme.red
} else {
$theme.mauve
}
}
duration: {||
if $in < 1day {
$theme.teal
} else if $in < 1wk {
$theme.green
} else if $in < 4wk {
$theme.yellow
} else if $in < 12wk {
$theme.peach
} else if $in < 24wk {
$theme.maroon
} else if $in < 52wk {
$theme.red
} else {
$theme.mauve
}
}
datetime: {|| (date now) - $in |
if $in < 1day {
$theme.teal
} else if $in < 1wk {
$theme.green
} else if $in < 4wk {
$theme.yellow
} else if $in < 12wk {
$theme.peach
} else if $in < 24wk {
$theme.maroon
} else if $in < 52wk {
$theme.red
} else {
$theme.mauve
}
}
shape_external: $scheme.unrecognized_command
shape_internalcall: $scheme.recognized_command
shape_external_resolved: $scheme.recognized_command
shape_block: $scheme.recognized_command
block: $scheme.recognized_command
shape_custom: $theme.pink
custom: $theme.pink
background: $theme.base
foreground: $theme.text
cursor: { bg: $theme.rosewater fg: $theme.base }
shape_range: $scheme.operator
range: $scheme.operator
shape_pipe: $scheme.operator
shape_operator: $scheme.operator
shape_redirection: $scheme.operator
glob: $scheme.filepath
shape_directory: $scheme.filepath
shape_filepath: $scheme.filepath
shape_glob_interpolation: $scheme.filepath
shape_globpattern: $scheme.filepath
shape_int: $scheme.constant
int: $scheme.constant
bool: $scheme.constant
float: $scheme.constant
nothing: $scheme.constant
binary: $scheme.constant
shape_nothing: $scheme.constant
shape_bool: $scheme.constant
shape_float: $scheme.constant
shape_binary: $scheme.constant
shape_datetime: $scheme.constant
shape_literal: $scheme.constant
string: $scheme.string
shape_string: $scheme.string
shape_string_interpolation: $theme.flamingo
shape_raw_string: $scheme.string
shape_externalarg: $scheme.string
}
$env.config.highlight_resolved_externals = true
$env.config.explore = {
status_bar_background: { fg: $theme.text, bg: $theme.mantle },
command_bar_text: { fg: $theme.text },
highlight: { fg: $theme.base, bg: $theme.yellow },
status: {
error: $theme.red,
warn: $theme.yellow,
info: $theme.blue,
},
selected_cell: { bg: $theme.blue fg: $theme.base },
}
# --- Custom Commands ---
def --env open_project [] {
let base = ($env.HOME | path join "Projects")
let choice = (
${pkgs.fd}/bin/fd -t d -d 1 -a . ($base | path join "Personal") ($base | path join "Work")
| lines
| each {|p| $p | str replace $"($base)/" "" }
| str join "\n"
| ${pkgs.fzf}/bin/fzf --prompt "project > "
)
if ($choice | str trim | is-not-empty) {
cd ($base | path join ($choice | str trim))
}
}
# --- Keybinding: Ctrl+O for open_project ---
$env.config.keybindings = ($env.config.keybindings | append [
{
name: open_project
modifier: control
keycode: char_o
mode: [emacs vi_insert vi_normal]
event: {
send: executehostcommand
cmd: "open_project"
}
}
])
'';
};
programs.zsh = {
enable = true;
};
programs.starship = {
enable = true;
enableNushellIntegration = true;
settings = {
format = "$directory\${custom.scm}$hostname$line_break$character";
buf = {
disabled = true;
};
character = {
error_symbol = "[󰘧](bold red)";
success_symbol = "[󰘧](bold green)";
};
directory = {
truncate_to_repo = false;
};
git_branch = {
disabled = true;
symbol = " ";
truncation_length = 18;
};
git_status = {
disabled = true;
};
git_commit = {
disabled = true;
};
git_state = {
disabled = true;
};
custom.scm = {
when = "jj-starship detect";
shell = ["jj-starship" "--strip-bookmark-prefix" "cschmatzler/" "--truncate-name" "20" "--bookmarks-display-limit" "1"];
format = "$output ";
};
lua = {
symbol = " ";
};
package = {
disabled = true;
};
};
};
};
}

27
modules/ssh-client.nix Normal file
View File

@@ -0,0 +1,27 @@
{...}: {
den.aspects.ssh-client.homeManager = {
config,
lib,
pkgs,
...
}: {
programs.ssh = {
enable = true;
enableDefaultConfig = false;
includes = [
(lib.mkIf pkgs.stdenv.hostPlatform.isLinux "/home/${config.home.username}/.ssh/config_external")
(lib.mkIf pkgs.stdenv.hostPlatform.isDarwin "/Users/${config.home.username}/.ssh/config_external")
];
matchBlocks = {
"*" = {};
"github.com" = {
identitiesOnly = true;
identityFile = [
(lib.mkIf pkgs.stdenv.hostPlatform.isLinux "/home/${config.home.username}/.ssh/id_ed25519")
(lib.mkIf pkgs.stdenv.hostPlatform.isDarwin "/Users/${config.home.username}/.ssh/id_ed25519")
];
};
};
};
};
}

89
modules/tahani.nix Normal file
View File

@@ -0,0 +1,89 @@
{den, ...}: {
den.aspects.tahani.includes = [
den.aspects.nixos-system
den.aspects.core
den.aspects.openssh
den.aspects.tailscale
den.aspects.terminal
den.aspects.email
den.aspects.atuin
den.aspects.dev-tools
den.aspects.neovim
den.aspects.ai-tools
den.aspects.zellij
den.aspects.zk
];
den.aspects.tahani.nixos = {...}: {
imports = [
./_hosts/tahani/adguardhome.nix
./_hosts/tahani/cache.nix
./_hosts/tahani/networking.nix
./_hosts/tahani/paperless.nix
];
networking.hostName = "tahani";
sops.secrets = {
tahani-paperless-password = {
sopsFile = ../secrets/tahani-paperless-password;
format = "binary";
};
tahani-email-password = {
sopsFile = ../secrets/tahani-email-password;
format = "binary";
owner = "cschmatzler";
};
};
virtualisation.docker.enable = true;
users.users.cschmatzler.extraGroups = ["docker"];
swapDevices = [
{
device = "/swapfile";
size = 16 * 1024;
}
];
};
den.aspects.tahani.homeManager = {
pkgs,
inputs',
...
}: let
opencode = inputs'.llm-agents.packages.opencode;
in {
programs.git.settings.user.email = "christoph@schmatzler.com";
# Auto-start zellij in nushell on tahani (headless server)
programs.nushell.extraConfig = ''
if 'ZELLIJ' not-in ($env | columns) {
zellij
}
'';
# Inbox-triage systemd service
systemd.user.services.opencode-inbox-triage = {
Unit = {
Description = "OpenCode inbox triage";
};
Service = {
Type = "oneshot";
ExecStart = "${opencode}/bin/opencode run --command inbox-triage";
Environment = "PATH=${pkgs.himalaya}/bin:${opencode}/bin:${pkgs.coreutils}/bin";
};
};
systemd.user.timers.opencode-inbox-triage = {
Unit = {
Description = "Run OpenCode inbox triage every 10 minutes";
};
Timer = {
OnCalendar = "*:0/10";
Persistent = true;
};
Install = {
WantedBy = ["timers.target"];
};
};
};
}

129
modules/terminal.nix Normal file
View File

@@ -0,0 +1,129 @@
{...}: {
den.aspects.terminal.homeManager = {pkgs, ...}: {
xdg.configFile."ghostty/config".text = ''
command = ${pkgs.nushell}/bin/nu
theme = Catppuccin Latte
window-padding-x = 12
window-padding-y = 3
window-padding-balance = true
font-family = TX-02
font-size = 16.5
cursor-style = block
mouse-hide-while-typing = true
mouse-scroll-multiplier = 1.25
shell-integration = none
shell-integration-features = no-cursor
clipboard-read = allow
clipboard-write = allow
'';
programs.bat = {
enable = true;
config = {
theme = "Catppuccin Latte";
pager = "ov";
};
themes = {
"Catppuccin Latte" = {
src =
pkgs.fetchFromGitHub {
owner = "catppuccin";
repo = "bat";
rev = "6810349b28055dce54076712fc05fc68da4b8ec0";
sha256 = "lJapSgRVENTrbmpVyn+UQabC9fpV1G1e+CdlJ090uvg=";
};
file = "themes/Catppuccin Latte.tmTheme";
};
};
};
programs.fzf = {
enable = true;
};
home.sessionVariables = {
FZF_DEFAULT_OPTS = ''
--bind=alt-k:up,alt-j:down
--expect=tab,enter
--layout=reverse
--delimiter='\t'
--with-nth=1
--preview-window='border-rounded' --prompt=' ' --marker=' ' --pointer=' '
--separator='' --scrollbar='' --layout='reverse'
--color=bg+:#CCD0DA,bg:#EFF1F5,spinner:#DC8A78,hl:#D20F39
--color=fg:#4C4F69,header:#D20F39,info:#8839EF,pointer:#DC8A78
--color=marker:#7287FD,fg+:#4C4F69,prompt:#8839EF,hl+:#D20F39
--color=selected-bg:#BCC0CC
--color=border:#9CA0B0,label:#4C4F69
'';
};
programs.ripgrep = {
enable = true;
arguments = [
"--max-columns=150"
"--max-columns-preview"
"--hidden"
"--smart-case"
"--colors=column:none"
"--colors=column:fg:4"
"--colors=column:style:underline"
"--colors=line:none"
"--colors=line:fg:4"
"--colors=match:none"
"--colors=match:bg:0"
"--colors=match:fg:6"
"--colors=path:none"
"--colors=path:fg:14"
"--colors=path:style:bold"
];
};
programs.zoxide = {
enable = true;
enableNushellIntegration = true;
};
programs.yazi = {
enable = true;
enableNushellIntegration = true;
shellWrapperName = "y";
settings = {
manager = {
show_hidden = true;
sort_by = "natural";
sort_dir_first = true;
};
};
theme = {
tabs = {
sep_inner = {
open = "";
close = "";
};
sep_outer = {
open = "";
close = "";
};
};
indicator = {
padding = {
open = "";
close = "";
};
};
status = {
sep_left = {
open = "";
close = "";
};
sep_right = {
open = "";
close = "";
};
};
};
};
};
}

Some files were not shown because too many files have changed in this diff Show More