Compare commits
6 Commits
005cd7b60e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99658b27dc | ||
|
|
28dad81816 | ||
|
|
1de34c1869 | ||
|
|
f558ab4ba9 | ||
|
|
f150afec0a | ||
|
|
9e0eb5b583 |
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
bootstrap/
|
||||||
467
README.md
467
README.md
@@ -1,413 +1,132 @@
|
|||||||
# nix-nodeiwest
|
# nix-nodeiwest
|
||||||
|
|
||||||
NixOS flake for NodeiWest VPS provisioning and ongoing deployment.
|
Composable company Nix for NodeiWest workstations and project shells.
|
||||||
|
|
||||||
This repo currently provisions NixOS hosts with:
|
This repo is now structured as a shared SDK:
|
||||||
|
|
||||||
- the `nodeiwest` employee helper CLI for safe provisioning
|
- `modules/` holds focused Home Manager building blocks
|
||||||
- shared base config in `modules/nixos/common.nix`
|
- `profiles/` bundles those modules into opinionated employee entrypoints
|
||||||
- Tailscale bootstrap via OpenBao AppRole in `modules/nixos/tailscale-init.nix`
|
- `shells/` exposes reusable flake dev shells for project repos
|
||||||
- Home Manager profile in `modules/home.nix`
|
- `systems/` adapts the shared modules into Darwin or standalone Linux Home Manager configs
|
||||||
- disk partitioning via `disko`
|
- `lib/` holds the small helpers that keep composition consistent
|
||||||
- deployment via `colmena`
|
- `templates/` bootstraps downstream user flakes
|
||||||
|
|
||||||
## Current Model
|
It does not define users or machines directly. Downstream flakes decide who uses which profile.
|
||||||
|
|
||||||
- Employees should use `nodeiwest` as the supported provisioning interface
|
## Layout
|
||||||
- New machines are installed with `nixos-anywhere`
|
|
||||||
- Ongoing changes are deployed with `colmena`
|
|
||||||
- Hosts authenticate to OpenBao as clients
|
|
||||||
- Tailscale auth keys are fetched from OpenBao namespace `it`, path `tailscale`, field `auth_key`
|
|
||||||
- Public SSH must work independently of Tailscale for first access and recovery
|
|
||||||
|
|
||||||
## Repo Layout
|
|
||||||
|
|
||||||
```text
|
```text
|
||||||
flake.nix
|
.
|
||||||
hosts/
|
├── flake.nix
|
||||||
vps[X]/
|
├── lib/
|
||||||
configuration.nix
|
├── modules/
|
||||||
disko.nix
|
│ ├── base/
|
||||||
hardware-configuration.nix
|
│ ├── dev/
|
||||||
modules/
|
│ ├── optional/
|
||||||
home.nix
|
│ ├── roles/
|
||||||
helpers/
|
│ ├── secrets/
|
||||||
home.nix
|
│ └── services/
|
||||||
nixos/
|
├── profiles/
|
||||||
common.nix
|
├── shells/
|
||||||
tailscale-init.nix
|
├── systems/
|
||||||
pkgs/
|
└── templates/
|
||||||
helpers/
|
|
||||||
cli.py
|
|
||||||
templates/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Recommended Workflow
|
## Flake Interface
|
||||||
|
|
||||||
The supported employee path is the `nodeiwest` CLI.
|
Primary outputs:
|
||||||
|
|
||||||
It is exported from the root flake as `.#nodeiwest-helper` and installed by the shared Home Manager profile. You can also run it ad hoc with:
|
- `homeManagerModules.base.*`: low-level base modules
|
||||||
|
- `homeManagerModules.dev.*`: language and workflow modules
|
||||||
|
- `homeManagerModules.roles.*`: reusable role bundles
|
||||||
|
- `homeManagerModules.profiles.*`: ready-made employee profiles
|
||||||
|
- `homeManagerModules.default`: compatibility shim for the old default home module
|
||||||
|
- `lib.mkSystem`: chooses the Darwin or Linux adapter for a downstream flake
|
||||||
|
- `lib.shells.*`: shell factories for repo-local dev environments
|
||||||
|
- `devShells.<system>.*`: ready-to-use company shells
|
||||||
|
- `templates.user-flake`: starter personal flake
|
||||||
|
|
||||||
```bash
|
## Workstation Consumption
|
||||||
nix run .#nodeiwest-helper -- --help
|
|
||||||
```
|
|
||||||
|
|
||||||
Recommended sequence for a new VPS:
|
Downstream user flakes own the actual machine definitions. They consume profiles from this repo:
|
||||||
|
|
||||||
### 1. Probe The Live Host
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest host probe --ip <ip>
|
|
||||||
```
|
|
||||||
|
|
||||||
This validates SSH reachability and derives the boot mode, root device, primary disk candidate, and swap facts from the live machine.
|
|
||||||
|
|
||||||
### 2. Scaffold The Host Files
|
|
||||||
|
|
||||||
Dry-run first:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest host init --name <name> --ip <ip>
|
|
||||||
```
|
|
||||||
|
|
||||||
Write after reviewing the plan:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest host init --name <name> --ip <ip> --apply
|
|
||||||
```
|
|
||||||
|
|
||||||
This command:
|
|
||||||
|
|
||||||
- probes the host unless you override disk or boot mode
|
|
||||||
- creates or updates `hosts/<name>/configuration.nix`
|
|
||||||
- creates or updates `hosts/<name>/disko.nix`
|
|
||||||
- creates `hosts/<name>/hardware-configuration.nix` as a placeholder if needed
|
|
||||||
- prints the exact `flake.nix` snippets still required for `nixosConfigurations` and `colmena`
|
|
||||||
|
|
||||||
### 3. Create The OpenBao Bootstrap Material
|
|
||||||
|
|
||||||
Dry-run first:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest openbao init-host --name <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
Apply after reviewing the policy and AppRole plan:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest openbao init-host --name <name> --apply
|
|
||||||
```
|
|
||||||
|
|
||||||
This verifies your existing `bao` login, creates the host policy and AppRole, and writes:
|
|
||||||
|
|
||||||
- `bootstrap/var/lib/nodeiwest/openbao-approle-role-id`
|
|
||||||
- `bootstrap/var/lib/nodeiwest/openbao-approle-secret-id`
|
|
||||||
|
|
||||||
### 4. Plan Or Run The Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest install plan --name <name>
|
|
||||||
nodeiwest install run --name <name> --apply
|
|
||||||
```
|
|
||||||
|
|
||||||
`install plan` validates the generated host files and bootstrap files, then prints the exact `nixos-anywhere` command. `install run` re-validates, asks for confirmation, and executes that command.
|
|
||||||
|
|
||||||
### 5. Verify First Boot And Colmena Readiness
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nodeiwest verify host --name <name> --ip <ip>
|
|
||||||
nodeiwest colmena plan --name <name>
|
|
||||||
```
|
|
||||||
|
|
||||||
`verify host` summarizes the first-boot OpenBao and Tailscale services over SSH. `colmena plan` confirms the deploy target or prints the exact missing host stanza.
|
|
||||||
|
|
||||||
## Manual Flow (Fallback / Advanced)
|
|
||||||
|
|
||||||
This is the underlying sequence that `nodeiwest` automates. Keep it as the fallback path for unsupported host layouts or when you intentionally want to run the raw commands yourself.
|
|
||||||
|
|
||||||
### 1. Prepare The Host Entry
|
|
||||||
|
|
||||||
Create a new directory under `hosts/<name>/` with:
|
|
||||||
|
|
||||||
- `configuration.nix`
|
|
||||||
- `disko.nix`
|
|
||||||
- `hardware-configuration.nix`
|
|
||||||
|
|
||||||
`configuration.nix` should import both `disko.nix` and `hardware-configuration.nix`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```nix
|
```nix
|
||||||
{ lib, ... }:
|
|
||||||
{
|
{
|
||||||
imports = [
|
inputs.company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
|
||||||
./disko.nix
|
|
||||||
./hardware-configuration.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = "vps1";
|
outputs = { company, ... }: {
|
||||||
networking.useDHCP = lib.mkDefault true;
|
darwinConfigurations.eric = company.lib.mkSystem {
|
||||||
|
target = "darwin";
|
||||||
time.timeZone = "UTC";
|
system = "aarch64-darwin";
|
||||||
|
username = "eric";
|
||||||
boot.loader.efi.canTouchEfiVariables = true;
|
homeDirectory = "/Users/eric";
|
||||||
boot.loader.grub = {
|
modules = [
|
||||||
enable = true;
|
company.homeManagerModules.profiles.frontend
|
||||||
efiSupport = true;
|
];
|
||||||
device = "nodev";
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
nodeiwest.ssh.userCAPublicKeys = [
|
|
||||||
"ssh-ed25519 AAAA... openbao-user-ca"
|
|
||||||
];
|
|
||||||
|
|
||||||
nodeiwest.tailscale.openbao.enable = true;
|
|
||||||
|
|
||||||
system.stateVersion = "25.05";
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2. Add The Host To `flake.nix`
|
For Linux Home Manager:
|
||||||
|
|
||||||
Add the host to:
|
```nix
|
||||||
|
{
|
||||||
|
inputs.company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
|
||||||
|
|
||||||
- `nixosConfigurations`
|
outputs = { company, ... }: {
|
||||||
- `colmena`
|
homeConfigurations."eric@work" = company.lib.mkSystem {
|
||||||
|
target = "linux";
|
||||||
For `colmena`, set:
|
system = "x86_64-linux";
|
||||||
|
username = "eric";
|
||||||
- `deployment.targetHost`
|
homeDirectory = "/home/eric";
|
||||||
- `deployment.targetUser = "root"`
|
modules = [
|
||||||
- tags as needed
|
company.homeManagerModules.profiles.backend
|
||||||
|
];
|
||||||
## Discover Disk And Boot Facts
|
};
|
||||||
|
};
|
||||||
Before writing `disko.nix`, inspect the current VPS over SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh root@<ip> 'lsblk -o NAME,SIZE,TYPE,MODEL,FSTYPE,PTTYPE,MOUNTPOINTS'
|
|
||||||
ssh root@<ip> 'test -d /sys/firmware/efi && echo UEFI || echo BIOS'
|
|
||||||
ssh root@<ip> 'findmnt -no SOURCE /'
|
|
||||||
ssh root@<ip> 'cat /proc/swaps'
|
|
||||||
```
|
|
||||||
|
|
||||||
Use that output to decide:
|
|
||||||
|
|
||||||
- disk device name: `/dev/sda`, `/dev/vda`, `/dev/nvme0n1`, etc.
|
|
||||||
- boot mode: UEFI or BIOS
|
|
||||||
- partition layout you want `disko` to create
|
|
||||||
|
|
||||||
`hosts/vps1/disko.nix` currently assumes:
|
|
||||||
|
|
||||||
- GPT
|
|
||||||
- `/dev/sda`
|
|
||||||
- UEFI
|
|
||||||
- ext4 root
|
|
||||||
- swap partition
|
|
||||||
|
|
||||||
Do not install blindly if those assumptions are wrong.
|
|
||||||
|
|
||||||
## Generate `hardware-configuration.nix`
|
|
||||||
|
|
||||||
`hardware-configuration.nix` is generated during install with `nixos-anywhere`.
|
|
||||||
|
|
||||||
The repo path is passed directly to the install command:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
--generate-hardware-config nixos-generate-config ./hosts/<name>/hardware-configuration.nix
|
|
||||||
```
|
|
||||||
|
|
||||||
That generated file should remain tracked in Git after install.
|
|
||||||
|
|
||||||
## OpenBao Setup For Tailscale
|
|
||||||
|
|
||||||
Each host gets its own AppRole.
|
|
||||||
|
|
||||||
The host uses:
|
|
||||||
|
|
||||||
- OpenBao address: `https://secrets.api.nodeiwest.se`
|
|
||||||
- namespace: `it`
|
|
||||||
- auth mount: `auth/approle`
|
|
||||||
- secret path: `tailscale`
|
|
||||||
- field: `auth_key`
|
|
||||||
|
|
||||||
The host stores:
|
|
||||||
|
|
||||||
- `/var/lib/nodeiwest/openbao-approle-role-id`
|
|
||||||
- `/var/lib/nodeiwest/openbao-approle-secret-id`
|
|
||||||
|
|
||||||
The rendered Tailscale auth key lives at:
|
|
||||||
|
|
||||||
- `/run/nodeiwest/tailscale-auth-key`
|
|
||||||
|
|
||||||
### Create A Policy
|
|
||||||
|
|
||||||
Create a minimal read-only policy for the Tailscale secret.
|
|
||||||
|
|
||||||
If the secret is accessible as:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
BAO_NAMESPACE=it bao kv get tailscale
|
|
||||||
```
|
|
||||||
|
|
||||||
then create the matching read policy for that mount.
|
|
||||||
|
|
||||||
Example shape for a KV v2 mount named `kv`:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
path "kv/data/tailscale" {
|
|
||||||
capabilities = ["read"]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Write it from your machine:
|
## Project Shell Consumption
|
||||||
|
|
||||||
```bash
|
Project repos should keep their own flake and compose shells from this repo instead of outsourcing project ownership here.
|
||||||
export BAO_ADDR=https://secrets.api.nodeiwest.se
|
|
||||||
export BAO_NAMESPACE=it
|
|
||||||
|
|
||||||
bao policy write tailscale-vps1 ./tailscale-vps1-policy.hcl
|
Use the ready-made shell directly:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{
|
||||||
|
inputs.company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
|
||||||
|
|
||||||
|
outputs = { nixpkgs, company, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs { inherit system; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells.${system}.default = company.lib.shells.node {
|
||||||
|
inherit pkgs;
|
||||||
|
extraPackages = [ pkgs.ffmpeg ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Adjust the path to match your actual OpenBao KV mount.
|
Or extend the published company shell in place:
|
||||||
|
|
||||||
### Create The AppRole
|
```nix
|
||||||
|
devShells.${system}.default = pkgs.mkShell {
|
||||||
Create one AppRole per host.
|
inputsFrom = [ company.devShells.${system}.node ];
|
||||||
|
packages = [ pkgs.ffmpeg ];
|
||||||
Example for `vps1`:
|
};
|
||||||
|
|
||||||
```bash
|
|
||||||
bao write auth/approle/role/tailscale-vps1 \
|
|
||||||
token_policies=tailscale-vps1 \
|
|
||||||
token_ttl=1h \
|
|
||||||
token_max_ttl=24h \
|
|
||||||
token_num_uses=0 \
|
|
||||||
secret_id_num_uses=0
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Generate Bootstrap Credentials
|
## Template
|
||||||
|
|
||||||
Create a temporary bootstrap directory on your machine:
|
Bootstrap a personal flake with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p bootstrap/var/lib/nodeiwest
|
nix flake init -t .#user-flake
|
||||||
```
|
```
|
||||||
|
|
||||||
Write the AppRole credentials into it:
|
That template is intentionally small. Add machine-specific modules in the personal repo, not here.
|
||||||
|
|
||||||
```bash
|
|
||||||
bao read -field=role_id auth/approle/role/tailscale-vps1/role-id \
|
|
||||||
> bootstrap/var/lib/nodeiwest/openbao-approle-role-id
|
|
||||||
|
|
||||||
bao write -f -field=secret_id auth/approle/role/tailscale-vps1/secret-id \
|
|
||||||
> bootstrap/var/lib/nodeiwest/openbao-approle-secret-id
|
|
||||||
|
|
||||||
chmod 0400 bootstrap/var/lib/nodeiwest/openbao-approle-role-id
|
|
||||||
chmod 0400 bootstrap/var/lib/nodeiwest/openbao-approle-secret-id
|
|
||||||
```
|
|
||||||
|
|
||||||
These files are install-time bootstrap material. They are not stored in Git.
|
|
||||||
|
|
||||||
## Install With `nixos-anywhere`
|
|
||||||
|
|
||||||
Install from your machine:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix run github:nix-community/nixos-anywhere -- \
|
|
||||||
--extra-files ./bootstrap \
|
|
||||||
--copy-host-keys \
|
|
||||||
--generate-hardware-config nixos-generate-config ./hosts/vps1/hardware-configuration.nix \
|
|
||||||
--flake .#vps1 \
|
|
||||||
root@100.101.167.118
|
|
||||||
```
|
|
||||||
|
|
||||||
What this does:
|
|
||||||
|
|
||||||
- wipes the target disk according to `hosts/vps1/disko.nix`
|
|
||||||
- installs NixOS with `.#vps1`
|
|
||||||
- copies the AppRole bootstrap files into `/var/lib/nodeiwest`
|
|
||||||
- generates `hosts/vps1/hardware-configuration.nix`
|
|
||||||
|
|
||||||
Important:
|
|
||||||
|
|
||||||
- this destroys the existing OS on the target
|
|
||||||
- take provider snapshots and application backups first
|
|
||||||
- the target SSH host keys may change after install
|
|
||||||
|
|
||||||
## First Boot Behavior
|
|
||||||
|
|
||||||
On first boot:
|
|
||||||
|
|
||||||
1. `vault-agent-tailscale.service` starts using `pkgs.openbao`
|
|
||||||
2. it authenticates to OpenBao with AppRole
|
|
||||||
3. it renders `auth_key` from `it/tailscale` to `/run/nodeiwest/tailscale-auth-key`
|
|
||||||
4. `nodeiwest-tailscale-authkey-ready.service` waits until that file exists
|
|
||||||
5. `tailscaled-autoconnect.service` uses that file and runs `tailscale up --ssh`
|
|
||||||
|
|
||||||
Public SSH remains the recovery path if OpenBao or Tailscale bootstrap fails.
|
|
||||||
|
|
||||||
## Verify After Install
|
|
||||||
|
|
||||||
SSH to the host over the public IP first.
|
|
||||||
|
|
||||||
Check:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
systemctl status vault-agent-tailscale
|
|
||||||
systemctl status nodeiwest-tailscale-authkey-ready
|
|
||||||
systemctl status tailscaled-autoconnect
|
|
||||||
|
|
||||||
ls -l /var/lib/nodeiwest
|
|
||||||
ls -l /run/nodeiwest/tailscale-auth-key
|
|
||||||
|
|
||||||
tailscale status
|
|
||||||
```
|
|
||||||
|
|
||||||
If Tailscale bootstrap fails, inspect logs:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
journalctl -u vault-agent-tailscale -b
|
|
||||||
journalctl -u nodeiwest-tailscale-authkey-ready -b
|
|
||||||
journalctl -u tailscaled-autoconnect -b
|
|
||||||
```
|
|
||||||
|
|
||||||
Typical causes:
|
|
||||||
|
|
||||||
- wrong AppRole credentials
|
|
||||||
- wrong OpenBao policy
|
|
||||||
- wrong secret path
|
|
||||||
- wrong KV mount path
|
|
||||||
- `auth_key` field missing in the secret
|
|
||||||
|
|
||||||
## Deploy Changes After Install
|
|
||||||
|
|
||||||
Once the host is installed and reachable, use Colmena:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
nix run .#colmena -- apply --on vps1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rotating The AppRole SecretID
|
|
||||||
|
|
||||||
To rotate the machine credential:
|
|
||||||
|
|
||||||
1. generate a new `secret_id` from your machine
|
|
||||||
2. replace `/var/lib/nodeiwest/openbao-approle-secret-id` on the host
|
|
||||||
3. restart the agent
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bao write -f -field=secret_id auth/approle/role/tailscale-vps1/secret-id > new-secret-id
|
|
||||||
scp new-secret-id root@100.101.167.118:/var/lib/nodeiwest/openbao-approle-secret-id
|
|
||||||
ssh root@100.101.167.118 'chmod 0400 /var/lib/nodeiwest/openbao-approle-secret-id && systemctl restart vault-agent-tailscale tailscaled-autoconnect'
|
|
||||||
rm -f new-secret-id
|
|
||||||
```
|
|
||||||
|
|
||||||
## Recovery Notes
|
|
||||||
|
|
||||||
- Tailscale is additive. It should not be your only access path.
|
|
||||||
- Public SSH on port `22` must remain available for first access and recovery.
|
|
||||||
- OpenBao SSH CA auth is separate from Tailscale bootstrap.
|
|
||||||
- If a machine fails to join the tailnet, recover via public SSH or provider console.
|
|
||||||
|
|||||||
139
flake.lock
generated
139
flake.lock
generated
@@ -1,78 +1,5 @@
|
|||||||
{
|
{
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"disko": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"nixpkgs"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773506317,
|
|
||||||
"narHash": "sha256-qWKbLUJpavIpvOdX1fhHYm0WGerytFHRoh9lVck6Bh0=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"rev": "878ec37d6a8f52c6c801d0e2a2ad554c75b9353c",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "disko",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"flake-compat": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1650374568,
|
|
||||||
"narHash": "sha256-Z+s0J8/r907g149rllvwhb4pKi8Wam5ij0st8PwAh+E=",
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"rev": "b4a34015c698c7793d592d66adbab377907a2be8",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "edolstra",
|
|
||||||
"repo": "flake-compat",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"home-manager": {
|
"home-manager": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@@ -80,11 +7,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1773681856,
|
"lastModified": 1775781825,
|
||||||
"narHash": "sha256-+bRqxoFCJFO9ZTFhcCkzNXbDT3b8AEk88fyjB7Is6eo=",
|
"narHash": "sha256-L5yKTpR+alrZU2XYYvIxCeCP4LBHU5jhwSj7H1VAavg=",
|
||||||
"owner": "nix-community",
|
"owner": "nix-community",
|
||||||
"repo": "home-manager",
|
"repo": "home-manager",
|
||||||
"rev": "57d5560ee92a424fb71fde800acd6ed2c725dfce",
|
"rev": "e35c39fca04fee829cecdf839a50eb9b54d8a701",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -93,50 +20,33 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nix-github-actions": {
|
"nix-darwin": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
"colmena",
|
|
||||||
"nixpkgs"
|
"nixpkgs"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1729742964,
|
"lastModified": 1775037210,
|
||||||
"narHash": "sha256-B4mzTcQ0FZHdpeWcpDYPERtyjJd/NIuaQ9+BV1h+MpA=",
|
"narHash": "sha256-KM2WYj6EA7M/FVZVCl3rqWY+TFV5QzSyyGE2gQxeODU=",
|
||||||
"owner": "nix-community",
|
"owner": "LnL7",
|
||||||
"repo": "nix-github-actions",
|
"repo": "nix-darwin",
|
||||||
"rev": "e04df33f62cdcf93d73e9a04142464753a16db67",
|
"rev": "06648f4902343228ce2de79f291dd5a58ee12146",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "nix-community",
|
"owner": "LnL7",
|
||||||
"repo": "nix-github-actions",
|
"repo": "nix-darwin",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1750134718,
|
"lastModified": 1775763530,
|
||||||
"narHash": "sha256-v263g4GbxXv87hMXMCpjkIxd/viIF7p3JpJrwgKdNiI=",
|
"narHash": "sha256-BuTK9z1QEwWPOIakQ1gCN4pa4VwVJpfptYCviy2uOGc=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "9e83b64f727c88a7711a2c463a7b16eedb69a84c",
|
"rev": "b0188973b4b2a5b6bdba8b65381d6cd09a533da0",
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "NixOS",
|
|
||||||
"ref": "nixos-unstable",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs_2": {
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1773628058,
|
|
||||||
"narHash": "sha256-hpXH0z3K9xv0fHaje136KY872VT2T5uwxtezlAskQgY=",
|
|
||||||
"owner": "NixOS",
|
|
||||||
"repo": "nixpkgs",
|
|
||||||
"rev": "f8573b9c935cfaa162dd62cc9e75ae2db86f85df",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@@ -148,26 +58,9 @@
|
|||||||
},
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"colmena": "colmena",
|
|
||||||
"disko": "disko",
|
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"nixpkgs": "nixpkgs_2"
|
"nix-darwin": "nix-darwin",
|
||||||
}
|
"nixpkgs": "nixpkgs"
|
||||||
},
|
|
||||||
"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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
189
flake.nix
189
flake.nix
@@ -1,125 +1,134 @@
|
|||||||
{
|
{
|
||||||
description = "NodeiWest company flake";
|
description = "NodeiWest company Nix SDK";
|
||||||
|
|
||||||
inputs = {
|
inputs = {
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
colmena.url = "github:zhaofengli/colmena";
|
|
||||||
disko = {
|
|
||||||
url = "github:nix-community/disko";
|
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
};
|
|
||||||
home-manager = {
|
home-manager = {
|
||||||
url = "github:nix-community/home-manager";
|
url = "github:nix-community/home-manager";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nix-darwin = {
|
||||||
|
url = "github:LnL7/nix-darwin";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
inputs@{
|
inputs@{
|
||||||
self,
|
self,
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
colmena,
|
|
||||||
disko,
|
|
||||||
home-manager,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
lib = nixpkgs.lib;
|
lib = nixpkgs.lib;
|
||||||
supportedSystems = [
|
defaults = import ./lib/defaults.nix { inherit lib; };
|
||||||
"aarch64-darwin"
|
inherit (defaults) forAllSystems supportedSystems;
|
||||||
"x86_64-darwin"
|
|
||||||
"x86_64-linux"
|
|
||||||
];
|
|
||||||
forAllSystems = lib.genAttrs supportedSystems;
|
|
||||||
|
|
||||||
mkPkgs =
|
shellFactories = {
|
||||||
system:
|
default = args: import ./shells/default.nix args;
|
||||||
import nixpkgs {
|
node = args: import ./shells/node.nix (args // { inherit lib; });
|
||||||
inherit system;
|
go = args: import ./shells/go.nix args;
|
||||||
};
|
rust = args: import ./shells/rust.nix (args // { inherit lib; });
|
||||||
|
};
|
||||||
|
|
||||||
mkHost =
|
profileModules = {
|
||||||
name:
|
backend = import ./profiles/backend.nix;
|
||||||
nixpkgs.lib.nixosSystem {
|
frontend = import ./profiles/frontend.nix;
|
||||||
system = "x86_64-linux";
|
minimal = import ./profiles/minimal.nix;
|
||||||
specialArgs = {
|
};
|
||||||
inherit inputs self;
|
|
||||||
};
|
darwinSystem = args: import ./systems/darwin/default.nix ({ inherit inputs; } // args);
|
||||||
modules = [
|
linuxHome = args: import ./systems/linux/default.nix ({ inherit inputs; } // args);
|
||||||
disko.nixosModules.disko
|
|
||||||
home-manager.nixosModules.home-manager
|
|
||||||
self.nixosModules.common
|
|
||||||
./hosts/${name}/configuration.nix
|
|
||||||
];
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
homeManagerModules.default = ./modules/home.nix;
|
lib = {
|
||||||
homeManagerModules.helpers = ./modules/helpers/home.nix;
|
inherit (defaults) companySessionVariables forAllSystems supportedSystems;
|
||||||
nixosModules.common = ./modules/nixos/common.nix;
|
mkProfile = import ./lib/mkProfile.nix;
|
||||||
|
mkSystem = import ./lib/mkSystem.nix {
|
||||||
|
systems = {
|
||||||
|
darwin = darwinSystem;
|
||||||
|
linux = linuxHome;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
shells = shellFactories;
|
||||||
|
systems = {
|
||||||
|
darwin = darwinSystem;
|
||||||
|
linux = linuxHome;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
packages = forAllSystems (
|
homeManagerModules = {
|
||||||
|
base = {
|
||||||
|
nix = import ./modules/base/nix.nix;
|
||||||
|
core = import ./modules/base/core.nix;
|
||||||
|
shell = import ./modules/base/shell.nix;
|
||||||
|
fonts = import ./modules/base/fonts.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
dev = {
|
||||||
|
node = import ./modules/dev/node.nix;
|
||||||
|
go = import ./modules/dev/go.nix;
|
||||||
|
rust = import ./modules/dev/rust.nix;
|
||||||
|
docker = import ./modules/dev/docker.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
roles = {
|
||||||
|
backend = import ./modules/roles/backend.nix;
|
||||||
|
frontend = import ./modules/roles/frontend.nix;
|
||||||
|
infra = import ./modules/roles/infra.nix;
|
||||||
|
minimal = import ./modules/roles/minimal.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
services = {
|
||||||
|
tailscale = import ./modules/services/tailscale.nix;
|
||||||
|
ssh = import ./modules/services/ssh.nix;
|
||||||
|
gpg = import ./modules/services/gpg.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
secrets = {
|
||||||
|
env = import ./modules/secrets/env.nix;
|
||||||
|
openbao = import ./modules/secrets/openbao.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
optional = {
|
||||||
|
homebrew = import ./modules/optional/homebrew.nix;
|
||||||
|
gui = import ./modules/optional/gui.nix;
|
||||||
|
devtools = import ./modules/optional/devtools.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
profiles = profileModules;
|
||||||
|
|
||||||
|
default = import ./modules/home.nix;
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells = forAllSystems (
|
||||||
system:
|
system:
|
||||||
let
|
let
|
||||||
pkgs = mkPkgs system;
|
pkgs = import nixpkgs { inherit system; };
|
||||||
nodeiwestHelper = pkgs.callPackage ./pkgs/helpers { };
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
colmena = colmena.packages.${system}.colmena;
|
default = shellFactories.default { inherit pkgs; };
|
||||||
nodeiwest-helper = nodeiwestHelper;
|
node = shellFactories.node { inherit pkgs; };
|
||||||
default = colmena.packages.${system}.colmena;
|
go = shellFactories.go { inherit pkgs; };
|
||||||
|
rust = shellFactories.rust { inherit pkgs; };
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
apps = forAllSystems (system: {
|
formatter = forAllSystems (
|
||||||
colmena = {
|
system:
|
||||||
type = "app";
|
let
|
||||||
program = "${colmena.packages.${system}.colmena}/bin/colmena";
|
pkgs = import nixpkgs { inherit system; };
|
||||||
};
|
in
|
||||||
nodeiwest-helper = {
|
pkgs.nixfmt
|
||||||
type = "app";
|
);
|
||||||
program = "${self.packages.${system}.nodeiwest-helper}/bin/nodeiwest";
|
|
||||||
};
|
|
||||||
default = self.apps.${system}.colmena;
|
|
||||||
});
|
|
||||||
|
|
||||||
nixosConfigurations = {
|
templates = {
|
||||||
vps1 = mkHost "vps1";
|
user-flake = {
|
||||||
};
|
path = ./templates/user-flake;
|
||||||
|
description = "Starter personal flake wired to NodeiWest profiles.";
|
||||||
colmena = {
|
|
||||||
meta = {
|
|
||||||
nixpkgs = mkPkgs "x86_64-linux";
|
|
||||||
specialArgs = {
|
|
||||||
inherit inputs self;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
defaults =
|
|
||||||
{ name, ... }:
|
|
||||||
{
|
|
||||||
networking.hostName = name;
|
|
||||||
imports = [
|
|
||||||
disko.nixosModules.disko
|
|
||||||
home-manager.nixosModules.home-manager
|
|
||||||
self.nixosModules.common
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
vps1 = {
|
|
||||||
deployment = {
|
|
||||||
targetHost = "100.101.167.118";
|
|
||||||
targetUser = "root";
|
|
||||||
tags = [
|
|
||||||
"company"
|
|
||||||
"edge"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
imports = [ ./hosts/vps1/configuration.nix ];
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
colmenaHive = colmena.lib.makeHive self.outputs.colmena;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
imports = [
|
|
||||||
./disko.nix
|
|
||||||
./hardware-configuration.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = "vps1";
|
|
||||||
networking.useDHCP = lib.mkDefault true;
|
|
||||||
|
|
||||||
time.timeZone = "UTC";
|
|
||||||
|
|
||||||
boot.loader.efi.canTouchEfiVariables = true;
|
|
||||||
boot.loader.grub = {
|
|
||||||
enable = true;
|
|
||||||
efiSupport = true;
|
|
||||||
device = "nodev";
|
|
||||||
};
|
|
||||||
|
|
||||||
nodeiwest.ssh.userCAPublicKeys = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIE6c2oMkM7lLg9qWHVgbrFaFBDrrFyynFlPviiydQdFi openbao-user-ca"
|
|
||||||
];
|
|
||||||
nodeiwest.tailscale.openbao = {
|
|
||||||
enable = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
system.stateVersion = "25.05";
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
# Replace /dev/sda if the VPS exposes a different disk, e.g. /dev/vda or /dev/nvme0n1.
|
|
||||||
disko.devices = {
|
|
||||||
disk.main = {
|
|
||||||
type = "disk";
|
|
||||||
device = lib.mkDefault "/dev/sda";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
ESP = {
|
|
||||||
priority = 1;
|
|
||||||
name = "ESP";
|
|
||||||
start = "1MiB";
|
|
||||||
end = "512MiB";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
mountOptions = [ "umask=0077" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
swap = {
|
|
||||||
size = "4GiB";
|
|
||||||
content = {
|
|
||||||
type = "swap";
|
|
||||||
resumeDevice = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
root = {
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext4";
|
|
||||||
mountpoint = "/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
# Replace this file with the generated hardware config from the target host.
|
|
||||||
fileSystems."/" = lib.mkDefault {
|
|
||||||
device = "/dev/disk/by-label/nixos";
|
|
||||||
fsType = "ext4";
|
|
||||||
};
|
|
||||||
|
|
||||||
swapDevices = [ ];
|
|
||||||
}
|
|
||||||
20
lib/defaults.nix
Normal file
20
lib/defaults.nix
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{ lib }:
|
||||||
|
let
|
||||||
|
supportedSystems = [
|
||||||
|
"aarch64-darwin"
|
||||||
|
"x86_64-darwin"
|
||||||
|
"x86_64-linux"
|
||||||
|
];
|
||||||
|
in
|
||||||
|
{
|
||||||
|
inherit supportedSystems;
|
||||||
|
|
||||||
|
forAllSystems = f: lib.genAttrs supportedSystems f;
|
||||||
|
|
||||||
|
companySessionVariables = {
|
||||||
|
BAO_ADDR = "https://secrets.api.nodeiwest.se";
|
||||||
|
SOME_REGISTRY = "git.dgren.dev";
|
||||||
|
};
|
||||||
|
|
||||||
|
stateVersion = "24.11";
|
||||||
|
}
|
||||||
7
lib/mkProfile.nix
Normal file
7
lib/mkProfile.nix
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
modules ? [ ],
|
||||||
|
extraModules ? [ ],
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
imports = modules ++ extraModules;
|
||||||
|
}
|
||||||
15
lib/mkSystem.nix
Normal file
15
lib/mkSystem.nix
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{ systems }:
|
||||||
|
{
|
||||||
|
target,
|
||||||
|
...
|
||||||
|
}@args:
|
||||||
|
let
|
||||||
|
adapter =
|
||||||
|
if target == "darwin" then
|
||||||
|
systems.darwin
|
||||||
|
else if target == "linux" then
|
||||||
|
systems.linux
|
||||||
|
else
|
||||||
|
throw "Unsupported target `${target}`. Expected `darwin` or `linux`.";
|
||||||
|
in
|
||||||
|
adapter (builtins.removeAttrs args [ "target" ])
|
||||||
19
modules/base/core.nix
Normal file
19
modules/base/core.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
age
|
||||||
|
curl
|
||||||
|
fd
|
||||||
|
git
|
||||||
|
jq
|
||||||
|
just
|
||||||
|
ripgrep
|
||||||
|
sops
|
||||||
|
];
|
||||||
|
|
||||||
|
programs.git = {
|
||||||
|
enable = true;
|
||||||
|
lfs.enable = true;
|
||||||
|
signing.format = lib.mkDefault "openpgp";
|
||||||
|
};
|
||||||
|
}
|
||||||
11
modules/base/fonts.nix
Normal file
11
modules/base/fonts.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
jetbrainsMono = lib.attrByPath [ "nerd-fonts" "jetbrains-mono" ] null pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
fonts.fontconfig.enable = pkgs.stdenv.isLinux;
|
||||||
|
|
||||||
|
home.packages = builtins.filter (pkg: pkg != null) [
|
||||||
|
jetbrainsMono
|
||||||
|
];
|
||||||
|
}
|
||||||
14
modules/base/nix.nix
Normal file
14
modules/base/nix.nix
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
{
|
||||||
|
programs.home-manager.enable = true;
|
||||||
|
|
||||||
|
nix.package = lib.mkDefault pkgs.nix;
|
||||||
|
|
||||||
|
nix.settings = {
|
||||||
|
experimental-features = [
|
||||||
|
"nix-command"
|
||||||
|
"flakes"
|
||||||
|
];
|
||||||
|
warn-dirty = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
20
modules/base/shell.nix
Normal file
20
modules/base/shell.nix
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
programs.bash.enable = true;
|
||||||
|
|
||||||
|
programs.zsh = {
|
||||||
|
enable = true;
|
||||||
|
autocd = true;
|
||||||
|
enableCompletion = true;
|
||||||
|
shellAliases = {
|
||||||
|
l = "ls -CF";
|
||||||
|
la = "ls -A";
|
||||||
|
ll = "ls -alF";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
programs.direnv = {
|
||||||
|
enable = true;
|
||||||
|
nix-direnv.enable = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
16
modules/dev/docker.nix
Normal file
16
modules/dev/docker.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
optionalPackage = path: lib.attrByPath path null pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.packages = builtins.filter (pkg: pkg != null) (
|
||||||
|
[
|
||||||
|
(optionalPackage [ "docker-client" ])
|
||||||
|
(optionalPackage [ "docker-compose" ])
|
||||||
|
(optionalPackage [ "lazydocker" ])
|
||||||
|
]
|
||||||
|
++ lib.optionals pkgs.stdenv.isDarwin [
|
||||||
|
(optionalPackage [ "colima" ])
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
7
modules/dev/go.nix
Normal file
7
modules/dev/go.nix
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
home.packages = with pkgs; [
|
||||||
|
go
|
||||||
|
gopls
|
||||||
|
];
|
||||||
|
}
|
||||||
10
modules/dev/node.nix
Normal file
10
modules/dev/node.nix
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
nodejs = lib.attrByPath [ "nodejs_20" ] pkgs.nodejs pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.packages = [
|
||||||
|
nodejs
|
||||||
|
pkgs.pnpm
|
||||||
|
];
|
||||||
|
}
|
||||||
13
modules/dev/rust.nix
Normal file
13
modules/dev/rust.nix
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
optionalPackage = path: lib.attrByPath path null pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.packages = builtins.filter (pkg: pkg != null) [
|
||||||
|
pkgs.cargo
|
||||||
|
pkgs.rustc
|
||||||
|
(optionalPackage [ "rust-analyzer" ])
|
||||||
|
(optionalPackage [ "rustfmt" ])
|
||||||
|
(optionalPackage [ "clippy" ])
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
{ pkgs, ... }:
|
|
||||||
let
|
|
||||||
nodeiwestHelper = pkgs.callPackage ../../pkgs/helpers { };
|
|
||||||
in
|
|
||||||
{
|
|
||||||
home.packages = [
|
|
||||||
pkgs.python3
|
|
||||||
nodeiwestHelper
|
|
||||||
];
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,9 @@
|
|||||||
{ pkgs, lib, ... }:
|
{ pkgs, ... }:
|
||||||
{
|
{
|
||||||
imports = [ ./helpers/home.nix ];
|
imports = [
|
||||||
|
../profiles/minimal.nix
|
||||||
# Company env vars — available in all shells
|
./secrets/openbao.nix
|
||||||
home.sessionVariables = {
|
|
||||||
BAO_ADDR = "https://secrets.api.nodeiwest.se";
|
|
||||||
SOME_REGISTRY = "git.dgren.dev";
|
|
||||||
# etc.
|
|
||||||
};
|
|
||||||
|
|
||||||
home.packages = with pkgs; [
|
|
||||||
# Tools every dev needs
|
|
||||||
openbao
|
|
||||||
colmena
|
|
||||||
# etc.
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
home.packages = [ pkgs.colmena ];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
self,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.nodeiwest;
|
|
||||||
trustedUserCAKeysPath = "/etc/ssh/trusted-user-ca-keys.pem";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = [ ./tailscale-init.nix ];
|
|
||||||
|
|
||||||
options.nodeiwest = {
|
|
||||||
openbao.address = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "https://secrets.api.nodeiwest.se";
|
|
||||||
description = "Remote OpenBao address that hosts should use as clients.";
|
|
||||||
example = "https://secrets.api.nodeiwest.se";
|
|
||||||
};
|
|
||||||
|
|
||||||
homeManagerUsers = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.str;
|
|
||||||
default = [
|
|
||||||
"root"
|
|
||||||
"deploy"
|
|
||||||
];
|
|
||||||
description = "Users that should receive the shared Home Manager company profile.";
|
|
||||||
example = [
|
|
||||||
"root"
|
|
||||||
"deploy"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
ssh.userCAPublicKeys = lib.mkOption {
|
|
||||||
type = lib.types.listOf lib.types.singleLineStr;
|
|
||||||
default = [ ];
|
|
||||||
description = "OpenBao SSH user CA public keys trusted by sshd for user certificate authentication.";
|
|
||||||
example = [
|
|
||||||
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBExampleOpenBaoUserCA openbao-user-ca"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
networking.firewall.allowedTCPPorts = [
|
|
||||||
22
|
|
||||||
80
|
|
||||||
443
|
|
||||||
];
|
|
||||||
|
|
||||||
services.openssh = {
|
|
||||||
enable = true;
|
|
||||||
settings = {
|
|
||||||
PasswordAuthentication = false;
|
|
||||||
KbdInteractiveAuthentication = false;
|
|
||||||
PubkeyAuthentication = true;
|
|
||||||
PermitRootLogin = "prohibit-password";
|
|
||||||
}
|
|
||||||
// lib.optionalAttrs (cfg.ssh.userCAPublicKeys != [ ]) {
|
|
||||||
TrustedUserCAKeys = trustedUserCAKeysPath;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
users.groups.deploy = { };
|
|
||||||
users.users.deploy = {
|
|
||||||
isNormalUser = true;
|
|
||||||
group = "deploy";
|
|
||||||
createHome = true;
|
|
||||||
extraGroups = [ "wheel" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
services.traefik = {
|
|
||||||
enable = true;
|
|
||||||
staticConfigOptions = {
|
|
||||||
api.dashboard = true;
|
|
||||||
entryPoints.web.address = ":80";
|
|
||||||
entryPoints.websecure.address = ":443";
|
|
||||||
ping = { };
|
|
||||||
};
|
|
||||||
dynamicConfigOptions = lib.mkMerge [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
home-manager = {
|
|
||||||
useGlobalPkgs = true;
|
|
||||||
useUserPackages = true;
|
|
||||||
users = lib.genAttrs cfg.homeManagerUsers (_: {
|
|
||||||
imports = [ self.homeManagerModules.default ];
|
|
||||||
home.stateVersion = config.system.stateVersion;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.etc = lib.mkIf (cfg.ssh.userCAPublicKeys != [ ]) {
|
|
||||||
"ssh/trusted-user-ca-keys.pem".text = lib.concatStringsSep "\n" cfg.ssh.userCAPublicKeys + "\n";
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.variables = {
|
|
||||||
BAO_ADDR = cfg.openbao.address;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
cfg = config.nodeiwest;
|
|
||||||
tailscaleOpenbaoCfg = cfg.tailscale.openbao;
|
|
||||||
in
|
|
||||||
{
|
|
||||||
options.nodeiwest.tailscale.openbao = {
|
|
||||||
enable = lib.mkEnableOption "fetching the Tailscale auth key from OpenBao";
|
|
||||||
|
|
||||||
namespace = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "it";
|
|
||||||
description = "OpenBao namespace used when fetching the Tailscale auth key.";
|
|
||||||
};
|
|
||||||
|
|
||||||
authPath = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "auth/approle";
|
|
||||||
description = "OpenBao auth mount path used by the AppRole login.";
|
|
||||||
};
|
|
||||||
|
|
||||||
secretPath = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "tailscale";
|
|
||||||
description = "OpenBao secret path containing the Tailscale auth key.";
|
|
||||||
};
|
|
||||||
|
|
||||||
field = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "auth_key";
|
|
||||||
description = "Field in the OpenBao secret that contains the Tailscale auth key.";
|
|
||||||
};
|
|
||||||
|
|
||||||
renderedAuthKeyFile = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/run/nodeiwest/tailscale-auth-key";
|
|
||||||
description = "Runtime file rendered by OpenBao Agent and consumed by Tailscale autoconnect.";
|
|
||||||
};
|
|
||||||
|
|
||||||
approle = {
|
|
||||||
roleIdFile = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/nodeiwest/openbao-approle-role-id";
|
|
||||||
description = "Root-only file containing the OpenBao AppRole role_id.";
|
|
||||||
};
|
|
||||||
|
|
||||||
secretIdFile = lib.mkOption {
|
|
||||||
type = lib.types.str;
|
|
||||||
default = "/var/lib/nodeiwest/openbao-approle-secret-id";
|
|
||||||
description = "Root-only file containing the OpenBao AppRole secret_id.";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
config = {
|
|
||||||
systemd.tmpfiles.rules = [
|
|
||||||
"d /var/lib/nodeiwest 0700 root root - -"
|
|
||||||
"d /run/nodeiwest 0700 root root - -"
|
|
||||||
];
|
|
||||||
|
|
||||||
services.tailscale = {
|
|
||||||
enable = true;
|
|
||||||
openFirewall = true;
|
|
||||||
extraUpFlags = lib.optionals tailscaleOpenbaoCfg.enable [ "--ssh" ];
|
|
||||||
authKeyFile = if tailscaleOpenbaoCfg.enable then tailscaleOpenbaoCfg.renderedAuthKeyFile else null;
|
|
||||||
};
|
|
||||||
|
|
||||||
services.vault-agent.instances.tailscale = lib.mkIf tailscaleOpenbaoCfg.enable {
|
|
||||||
package = pkgs.openbao;
|
|
||||||
settings = {
|
|
||||||
vault.address = cfg.openbao.address;
|
|
||||||
auto_auth = {
|
|
||||||
method = [
|
|
||||||
{
|
|
||||||
type = "approle";
|
|
||||||
mount_path = tailscaleOpenbaoCfg.authPath;
|
|
||||||
namespace = tailscaleOpenbaoCfg.namespace;
|
|
||||||
config = {
|
|
||||||
role_id_file_path = tailscaleOpenbaoCfg.approle.roleIdFile;
|
|
||||||
secret_id_file_path = tailscaleOpenbaoCfg.approle.secretIdFile;
|
|
||||||
remove_secret_id_file_after_reading = false;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
template = [
|
|
||||||
{
|
|
||||||
contents = ''{{- with secret "${tailscaleOpenbaoCfg.secretPath}" -}}{{- if .Data.data -}}{{ index .Data.data "${tailscaleOpenbaoCfg.field}" }}{{- else -}}{{ index .Data "${tailscaleOpenbaoCfg.field}" }}{{- end -}}{{- end -}}'';
|
|
||||||
destination = tailscaleOpenbaoCfg.renderedAuthKeyFile;
|
|
||||||
perms = "0400";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.vault-agent-tailscale = lib.mkIf tailscaleOpenbaoCfg.enable {
|
|
||||||
wants = [ "network-online.target" ];
|
|
||||||
after = [ "network-online.target" ];
|
|
||||||
serviceConfig.Environment = [ "BAO_NAMESPACE=${tailscaleOpenbaoCfg.namespace}" ];
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.nodeiwest-tailscale-authkey-ready = lib.mkIf tailscaleOpenbaoCfg.enable {
|
|
||||||
description = "Wait for the Tailscale auth key rendered by OpenBao Agent";
|
|
||||||
after = [ "vault-agent-tailscale.service" ];
|
|
||||||
requires = [ "vault-agent-tailscale.service" ];
|
|
||||||
before = [ "tailscaled-autoconnect.service" ];
|
|
||||||
requiredBy = [ "tailscaled-autoconnect.service" ];
|
|
||||||
path = [ pkgs.coreutils ];
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
};
|
|
||||||
script = ''
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
for _ in $(seq 1 60); do
|
|
||||||
if [ -s ${lib.escapeShellArg tailscaleOpenbaoCfg.renderedAuthKeyFile} ]; then
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Timed out waiting for rendered Tailscale auth key at ${tailscaleOpenbaoCfg.renderedAuthKeyFile}" >&2
|
|
||||||
exit 1
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.services.tailscaled-autoconnect = lib.mkIf tailscaleOpenbaoCfg.enable {
|
|
||||||
after = [
|
|
||||||
"vault-agent-tailscale.service"
|
|
||||||
"nodeiwest-tailscale-authkey-ready.service"
|
|
||||||
];
|
|
||||||
requires = [
|
|
||||||
"vault-agent-tailscale.service"
|
|
||||||
"nodeiwest-tailscale-authkey-ready.service"
|
|
||||||
];
|
|
||||||
serviceConfig.ExecStartPre = [
|
|
||||||
"${lib.getExe' pkgs.coreutils "test"} -s ${tailscaleOpenbaoCfg.renderedAuthKeyFile}"
|
|
||||||
];
|
|
||||||
};
|
|
||||||
|
|
||||||
assertions = [
|
|
||||||
{
|
|
||||||
assertion =
|
|
||||||
(!tailscaleOpenbaoCfg.enable)
|
|
||||||
|| (
|
|
||||||
tailscaleOpenbaoCfg.approle.roleIdFile != ""
|
|
||||||
&& tailscaleOpenbaoCfg.approle.secretIdFile != ""
|
|
||||||
);
|
|
||||||
message = "AppRole roleIdFile and secretIdFile must be set when OpenBao-backed Tailscale enrollment is enabled.";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
11
modules/optional/devtools.nix
Normal file
11
modules/optional/devtools.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
optionalPackage = path: lib.attrByPath path null pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.packages = builtins.filter (pkg: pkg != null) [
|
||||||
|
(optionalPackage [ "nil" ])
|
||||||
|
(optionalPackage [ "nixd" ])
|
||||||
|
(optionalPackage [ "nixfmt" ])
|
||||||
|
];
|
||||||
|
}
|
||||||
9
modules/optional/gui.nix
Normal file
9
modules/optional/gui.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
wezterm = lib.attrByPath [ "wezterm" ] null pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.packages = builtins.filter (pkg: pkg != null) [
|
||||||
|
wezterm
|
||||||
|
];
|
||||||
|
}
|
||||||
6
modules/optional/homebrew.nix
Normal file
6
modules/optional/homebrew.nix
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
# Intentionally empty. This is the seam downstream workstations can use to
|
||||||
|
# compose nix-homebrew or Homebrew-specific activation without coupling it
|
||||||
|
# into the shared base roles.
|
||||||
|
}
|
||||||
13
modules/roles/backend.nix
Normal file
13
modules/roles/backend.nix
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./minimal.nix
|
||||||
|
../dev/node.nix
|
||||||
|
../dev/go.nix
|
||||||
|
../dev/docker.nix
|
||||||
|
../services/ssh.nix
|
||||||
|
../services/gpg.nix
|
||||||
|
../secrets/openbao.nix
|
||||||
|
../optional/devtools.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
11
modules/roles/frontend.nix
Normal file
11
modules/roles/frontend.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./minimal.nix
|
||||||
|
../base/fonts.nix
|
||||||
|
../dev/node.nix
|
||||||
|
../services/gpg.nix
|
||||||
|
../optional/devtools.nix
|
||||||
|
../optional/gui.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
15
modules/roles/infra.nix
Normal file
15
modules/roles/infra.nix
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
./minimal.nix
|
||||||
|
../dev/go.nix
|
||||||
|
../dev/docker.nix
|
||||||
|
../services/ssh.nix
|
||||||
|
../services/tailscale.nix
|
||||||
|
../services/gpg.nix
|
||||||
|
../secrets/openbao.nix
|
||||||
|
../optional/devtools.nix
|
||||||
|
];
|
||||||
|
|
||||||
|
home.packages = [ pkgs.colmena ];
|
||||||
|
}
|
||||||
9
modules/roles/minimal.nix
Normal file
9
modules/roles/minimal.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{ ... }:
|
||||||
|
{
|
||||||
|
imports = [
|
||||||
|
../base/nix.nix
|
||||||
|
../base/core.nix
|
||||||
|
../base/shell.nix
|
||||||
|
../secrets/env.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
7
modules/secrets/env.nix
Normal file
7
modules/secrets/env.nix
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
let
|
||||||
|
defaults = import ../../lib/defaults.nix { inherit lib; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.sessionVariables = defaults.companySessionVariables;
|
||||||
|
}
|
||||||
6
modules/secrets/openbao.nix
Normal file
6
modules/secrets/openbao.nix
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
imports = [ ./env.nix ];
|
||||||
|
|
||||||
|
home.packages = [ pkgs.openbao ];
|
||||||
|
}
|
||||||
11
modules/services/gpg.nix
Normal file
11
modules/services/gpg.nix
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
programs.gpg.enable = true;
|
||||||
|
|
||||||
|
services.gpg-agent = {
|
||||||
|
enable = true;
|
||||||
|
enableBashIntegration = true;
|
||||||
|
enableZshIntegration = true;
|
||||||
|
pinentry.package = pkgs.pinentry-curses;
|
||||||
|
};
|
||||||
|
}
|
||||||
22
modules/services/ssh.nix
Normal file
22
modules/services/ssh.nix
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{ lib, ... }:
|
||||||
|
{
|
||||||
|
programs.ssh = {
|
||||||
|
enable = true;
|
||||||
|
enableDefaultConfig = false;
|
||||||
|
|
||||||
|
matchBlocks."*" = {
|
||||||
|
addKeysToAgent = lib.mkDefault "yes";
|
||||||
|
compression = lib.mkDefault false;
|
||||||
|
controlMaster = lib.mkDefault "no";
|
||||||
|
controlPath = lib.mkDefault "~/.ssh/master-%r@%n:%p";
|
||||||
|
controlPersist = lib.mkDefault "no";
|
||||||
|
forwardAgent = lib.mkDefault true;
|
||||||
|
hashKnownHosts = lib.mkDefault false;
|
||||||
|
serverAliveCountMax = lib.mkDefault 3;
|
||||||
|
serverAliveInterval = lib.mkDefault 0;
|
||||||
|
userKnownHostsFile = lib.mkDefault "~/.ssh/known_hosts";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
services.ssh-agent.enable = true;
|
||||||
|
}
|
||||||
9
modules/services/tailscale.nix
Normal file
9
modules/services/tailscale.nix
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{ lib, pkgs, ... }:
|
||||||
|
let
|
||||||
|
tailscale = lib.attrByPath [ "tailscale" ] null pkgs;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
home.packages = builtins.filter (pkg: pkg != null) [
|
||||||
|
tailscale
|
||||||
|
];
|
||||||
|
}
|
||||||
1350
pkgs/helpers/cli.py
1350
pkgs/helpers/cli.py
File diff suppressed because it is too large
Load Diff
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
writeShellApplication,
|
|
||||||
python3,
|
|
||||||
openbao,
|
|
||||||
openssh,
|
|
||||||
gitMinimal,
|
|
||||||
nix,
|
|
||||||
}:
|
|
||||||
writeShellApplication {
|
|
||||||
name = "nodeiwest";
|
|
||||||
|
|
||||||
runtimeInputs = [
|
|
||||||
python3
|
|
||||||
openbao
|
|
||||||
openssh
|
|
||||||
gitMinimal
|
|
||||||
nix
|
|
||||||
];
|
|
||||||
|
|
||||||
text = ''
|
|
||||||
export NODEIWEST_HELPER_TEMPLATES=${./templates}
|
|
||||||
exec ${python3}/bin/python ${./cli.py} "$@"
|
|
||||||
'';
|
|
||||||
|
|
||||||
meta = with lib; {
|
|
||||||
description = "Safe VPS provisioning helper for the NodeiWest NixOS flake";
|
|
||||||
license = licenses.mit;
|
|
||||||
mainProgram = "nodeiwest";
|
|
||||||
platforms = platforms.unix;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
{ lib, ... }:
|
|
||||||
{
|
|
||||||
# Generated by nodeiwest host init.
|
|
||||||
imports = [
|
|
||||||
./disko.nix
|
|
||||||
./hardware-configuration.nix
|
|
||||||
];
|
|
||||||
|
|
||||||
networking.hostName = "@@HOST_NAME@@";
|
|
||||||
networking.useDHCP = lib.mkDefault true;
|
|
||||||
|
|
||||||
time.timeZone = "@@TIMEZONE@@";
|
|
||||||
|
|
||||||
@@BOOT_LOADER_BLOCK@@
|
|
||||||
|
|
||||||
nodeiwest.ssh.userCAPublicKeys = @@SSH_CA_KEYS@@;
|
|
||||||
|
|
||||||
nodeiwest.tailscale.openbao = {
|
|
||||||
enable = @@TAILSCALE_OPENBAO_ENABLE@@;
|
|
||||||
};
|
|
||||||
|
|
||||||
system.stateVersion = "@@STATE_VERSION@@";
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
# Generated by nodeiwest host init.
|
|
||||||
# Replace the disk only if the provider exposes a different primary device.
|
|
||||||
disko.devices = {
|
|
||||||
disk.main = {
|
|
||||||
type = "disk";
|
|
||||||
device = lib.mkDefault "@@DISK_DEVICE@@";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
BIOS = {
|
|
||||||
priority = 1;
|
|
||||||
name = "BIOS";
|
|
||||||
start = "1MiB";
|
|
||||||
end = "2MiB";
|
|
||||||
type = "EF02";
|
|
||||||
};
|
|
||||||
swap = {
|
|
||||||
size = "@@SWAP_SIZE@@";
|
|
||||||
content = {
|
|
||||||
type = "swap";
|
|
||||||
resumeDevice = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
root = {
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext4";
|
|
||||||
mountpoint = "/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
lib,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
{
|
|
||||||
# Generated by nodeiwest host init.
|
|
||||||
# Replace the disk only if the provider exposes a different primary device.
|
|
||||||
disko.devices = {
|
|
||||||
disk.main = {
|
|
||||||
type = "disk";
|
|
||||||
device = lib.mkDefault "@@DISK_DEVICE@@";
|
|
||||||
content = {
|
|
||||||
type = "gpt";
|
|
||||||
partitions = {
|
|
||||||
ESP = {
|
|
||||||
priority = 1;
|
|
||||||
name = "ESP";
|
|
||||||
start = "1MiB";
|
|
||||||
end = "512MiB";
|
|
||||||
type = "EF00";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "vfat";
|
|
||||||
mountpoint = "/boot";
|
|
||||||
mountOptions = [ "umask=0077" ];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
swap = {
|
|
||||||
size = "@@SWAP_SIZE@@";
|
|
||||||
content = {
|
|
||||||
type = "swap";
|
|
||||||
resumeDevice = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
root = {
|
|
||||||
size = "100%";
|
|
||||||
content = {
|
|
||||||
type = "filesystem";
|
|
||||||
format = "ext4";
|
|
||||||
mountpoint = "/";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
{ ... }:
|
|
||||||
{
|
|
||||||
# Placeholder generated by nodeiwest host init.
|
|
||||||
# nixos-anywhere will replace this with the generated hardware config.
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
path "@@POLICY_PATH@@" {
|
|
||||||
capabilities = ["read"]
|
|
||||||
}
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import importlib.util
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
|
|
||||||
REPO_ROOT = Path(__file__).resolve().parents[3]
|
|
||||||
CLI_PATH = REPO_ROOT / "pkgs" / "helpers" / "cli.py"
|
|
||||||
|
|
||||||
spec = importlib.util.spec_from_file_location("nodeiwest_cli", CLI_PATH)
|
|
||||||
cli = importlib.util.module_from_spec(spec)
|
|
||||||
assert spec.loader is not None
|
|
||||||
sys.modules[spec.name] = cli
|
|
||||||
spec.loader.exec_module(cli)
|
|
||||||
|
|
||||||
|
|
||||||
class HelperCliTests(unittest.TestCase):
|
|
||||||
def test_disk_from_device_supports_sd_and_nvme(self) -> None:
|
|
||||||
self.assertEqual(cli.disk_from_device("/dev/sda2"), "/dev/sda")
|
|
||||||
self.assertEqual(cli.disk_from_device("/dev/nvme0n1p2"), "/dev/nvme0n1")
|
|
||||||
|
|
||||||
def test_lookup_colmena_target_host_reads_existing_inventory(self) -> None:
|
|
||||||
flake_text = (REPO_ROOT / "flake.nix").read_text()
|
|
||||||
self.assertEqual(cli.lookup_colmena_target_host(flake_text, "vps1"), "100.101.167.118")
|
|
||||||
|
|
||||||
def test_parse_existing_vps1_configuration(self) -> None:
|
|
||||||
configuration = cli.parse_existing_configuration(REPO_ROOT / "hosts" / "vps1" / "configuration.nix")
|
|
||||||
self.assertEqual(configuration.host_name, "vps1")
|
|
||||||
self.assertEqual(configuration.boot_mode, "uefi")
|
|
||||||
self.assertTrue(configuration.tailscale_openbao)
|
|
||||||
self.assertEqual(configuration.state_version, "25.05")
|
|
||||||
self.assertTrue(configuration.user_ca_public_keys)
|
|
||||||
|
|
||||||
def test_parse_existing_vps1_disko(self) -> None:
|
|
||||||
disko = cli.parse_existing_disko(REPO_ROOT / "hosts" / "vps1" / "disko.nix")
|
|
||||||
self.assertEqual(disko.disk_device, "/dev/sda")
|
|
||||||
self.assertEqual(disko.boot_mode, "uefi")
|
|
||||||
self.assertEqual(disko.swap_size, "4GiB")
|
|
||||||
|
|
||||||
def test_render_bios_disko_uses_bios_partition(self) -> None:
|
|
||||||
rendered = cli.render_disko(boot_mode="bios", disk_device="/dev/vda", swap_size="8GiB")
|
|
||||||
self.assertIn('type = "EF02";', rendered)
|
|
||||||
self.assertIn('device = lib.mkDefault "/dev/vda";', rendered)
|
|
||||||
self.assertIn('size = "8GiB";', rendered)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
||||||
8
profiles/backend.nix
Normal file
8
profiles/backend.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
let
|
||||||
|
mkProfile = import ../lib/mkProfile.nix;
|
||||||
|
in
|
||||||
|
mkProfile {
|
||||||
|
modules = [
|
||||||
|
../modules/roles/backend.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
8
profiles/frontend.nix
Normal file
8
profiles/frontend.nix
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
let
|
||||||
|
mkProfile = import ../lib/mkProfile.nix;
|
||||||
|
in
|
||||||
|
mkProfile {
|
||||||
|
modules = [
|
||||||
|
../modules/roles/frontend.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
10
profiles/minimal.nix
Normal file
10
profiles/minimal.nix
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
let
|
||||||
|
mkProfile = import ../lib/mkProfile.nix;
|
||||||
|
in
|
||||||
|
mkProfile {
|
||||||
|
modules = [
|
||||||
|
../modules/roles/minimal.nix
|
||||||
|
../modules/services/ssh.nix
|
||||||
|
../modules/optional/devtools.nix
|
||||||
|
];
|
||||||
|
}
|
||||||
20
shells/default.nix
Normal file
20
shells/default.nix
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
extraPackages ? [ ],
|
||||||
|
inputsFrom ? [ ],
|
||||||
|
shellHook ? "",
|
||||||
|
}:
|
||||||
|
pkgs.mkShell {
|
||||||
|
inherit inputsFrom shellHook;
|
||||||
|
|
||||||
|
packages =
|
||||||
|
(with pkgs; [
|
||||||
|
git
|
||||||
|
jq
|
||||||
|
just
|
||||||
|
nil
|
||||||
|
nixd
|
||||||
|
nixfmt
|
||||||
|
])
|
||||||
|
++ extraPackages;
|
||||||
|
}
|
||||||
16
shells/go.nix
Normal file
16
shells/go.nix
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
pkgs,
|
||||||
|
extraPackages ? [ ],
|
||||||
|
inputsFrom ? [ ],
|
||||||
|
shellHook ? "",
|
||||||
|
}:
|
||||||
|
pkgs.mkShell {
|
||||||
|
inherit inputsFrom shellHook;
|
||||||
|
|
||||||
|
packages =
|
||||||
|
(with pkgs; [
|
||||||
|
go
|
||||||
|
gopls
|
||||||
|
])
|
||||||
|
++ extraPackages;
|
||||||
|
}
|
||||||
19
shells/node.nix
Normal file
19
shells/node.nix
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
extraPackages ? [ ],
|
||||||
|
inputsFrom ? [ ],
|
||||||
|
shellHook ? "",
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
nodejs = lib.attrByPath [ "nodejs_20" ] pkgs.nodejs pkgs;
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
inherit inputsFrom shellHook;
|
||||||
|
|
||||||
|
packages = [
|
||||||
|
nodejs
|
||||||
|
pkgs.pnpm
|
||||||
|
]
|
||||||
|
++ extraPackages;
|
||||||
|
}
|
||||||
24
shells/rust.nix
Normal file
24
shells/rust.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
extraPackages ? [ ],
|
||||||
|
inputsFrom ? [ ],
|
||||||
|
shellHook ? "",
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
optionalPackage = path: lib.attrByPath path null pkgs;
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
inherit inputsFrom shellHook;
|
||||||
|
|
||||||
|
packages = builtins.filter (pkg: pkg != null) (
|
||||||
|
[
|
||||||
|
pkgs.cargo
|
||||||
|
pkgs.rustc
|
||||||
|
(optionalPackage [ "rust-analyzer" ])
|
||||||
|
(optionalPackage [ "rustfmt" ])
|
||||||
|
(optionalPackage [ "clippy" ])
|
||||||
|
]
|
||||||
|
++ extraPackages
|
||||||
|
);
|
||||||
|
}
|
||||||
42
systems/darwin/default.nix
Normal file
42
systems/darwin/default.nix
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
username,
|
||||||
|
homeDirectory,
|
||||||
|
modules ? [ ],
|
||||||
|
darwinStateVersion ? 6,
|
||||||
|
stateVersion ? "24.11",
|
||||||
|
extraSpecialArgs ? { },
|
||||||
|
}:
|
||||||
|
inputs.nix-darwin.lib.darwinSystem {
|
||||||
|
inherit system;
|
||||||
|
|
||||||
|
specialArgs = extraSpecialArgs // {
|
||||||
|
inherit inputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
inputs.home-manager.darwinModules.home-manager
|
||||||
|
{
|
||||||
|
system.stateVersion = darwinStateVersion;
|
||||||
|
|
||||||
|
users.users = {
|
||||||
|
${username}.home = homeDirectory;
|
||||||
|
};
|
||||||
|
|
||||||
|
home-manager.useGlobalPkgs = true;
|
||||||
|
home-manager.useUserPackages = true;
|
||||||
|
home-manager.extraSpecialArgs = extraSpecialArgs // {
|
||||||
|
inherit inputs;
|
||||||
|
};
|
||||||
|
home-manager.users = {
|
||||||
|
${username} = {
|
||||||
|
imports = modules;
|
||||||
|
home = {
|
||||||
|
inherit stateVersion username;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
}
|
||||||
24
systems/linux/default.nix
Normal file
24
systems/linux/default.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
inputs,
|
||||||
|
system,
|
||||||
|
username,
|
||||||
|
homeDirectory,
|
||||||
|
modules ? [ ],
|
||||||
|
stateVersion ? "24.11",
|
||||||
|
extraSpecialArgs ? { },
|
||||||
|
}:
|
||||||
|
inputs.home-manager.lib.homeManagerConfiguration {
|
||||||
|
pkgs = import inputs.nixpkgs { inherit system; };
|
||||||
|
extraSpecialArgs = extraSpecialArgs // {
|
||||||
|
inherit inputs;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = [
|
||||||
|
{
|
||||||
|
home = {
|
||||||
|
inherit homeDirectory stateVersion username;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
]
|
||||||
|
++ modules;
|
||||||
|
}
|
||||||
36
templates/user-flake/flake.nix
Normal file
36
templates/user-flake/flake.nix
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
description = "Starter personal flake for NodeiWest employees";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
# Adjust the repo path if you publish this under a different name.
|
||||||
|
company.url = "git+ssh://git@git.dgren.dev/employees/company-nix.git";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ company, ... }:
|
||||||
|
let
|
||||||
|
username = "your-name";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Remove whichever target you do not need.
|
||||||
|
homeConfigurations."${username}@linux" = company.lib.mkSystem {
|
||||||
|
target = "linux";
|
||||||
|
system = "x86_64-linux";
|
||||||
|
inherit username;
|
||||||
|
homeDirectory = "/home/${username}";
|
||||||
|
modules = [
|
||||||
|
company.homeManagerModules.profiles.backend
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
darwinConfigurations."${username}-mac" = company.lib.mkSystem {
|
||||||
|
target = "darwin";
|
||||||
|
system = "aarch64-darwin";
|
||||||
|
inherit username;
|
||||||
|
homeDirectory = "/Users/${username}";
|
||||||
|
modules = [
|
||||||
|
company.homeManagerModules.profiles.frontend
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user