feat: declare locked NixOS development host
Declare dev, scoped sudo/SSH, workspace ownership, Git and Neovim. Lock Nixpkgs, Home Manager and the unmodified dotfiles. Deploy user files declaratively and preserve a writable Lazy lockfile. Nix formatting and pure flake evaluation pass. Full build, empty-home deployment tests and activation of this reproducibility correction are still pending; the previous account baseline is live.
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
# Nix build results and local environment state.
|
||||
/result
|
||||
/result-*
|
||||
/.direnv/
|
||||
|
||||
# Local secrets must never be imported into Nix or committed.
|
||||
/.env
|
||||
/.env.*
|
||||
!/.env.example
|
||||
/secrets/
|
||||
|
||||
# Editor temporary files.
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
@@ -0,0 +1,76 @@
|
||||
# Development host
|
||||
|
||||
Flat, explicit NixOS modules, with locked inputs. Required machine setup belongs in these configs—not a list of manual installs or dotfile copies.
|
||||
|
||||
| File | Owns |
|
||||
| --- | --- |
|
||||
| `flake.nix`, `flake.lock` | Host entry point and exact Nixpkgs/Home Manager/dotfile revisions and content hashes |
|
||||
| `configuration.nix` | EC2 base, platform, Nix features, compatibility version, imports |
|
||||
| `users.nix` | `dev`, SSH/sudo, Home Manager integration, workspace/repo ownership |
|
||||
| `dev-authorized-keys` | Public SSH keys for `dev`—never private keys |
|
||||
| `tools.nix` | Git, declarative user Git policy, official Nix formatter |
|
||||
| `neovim.nix` | Editor and deployment of the unchanged upstream dotfiles |
|
||||
|
||||
## Account
|
||||
|
||||
- Daily user: `dev`, UID 1001, home `/home/dev`, workspace `~/projects`.
|
||||
- Passwordless sudo is scoped to `dev`; existing SSM-user administration and root SSH recovery remain intact.
|
||||
- `dev` is authorized by the public key in this repo. No private key or password is embedded.
|
||||
- `/etc/nixos` is writable by `dev` through a native tmpfiles ownership rule, which does not follow store symlinks.
|
||||
- Nix daemon access stays untrusted for ordinary use. Sudo is a separate, explicit administrative capability.
|
||||
- Bash is the temporary default; further shell/workflow preferences remain the user's choice.
|
||||
|
||||
Enter from an administrator session with `sudo -iu dev`.
|
||||
|
||||
Git author identity and remote destination are deliberately unset. `user.useConfigOnly` is now a Home Manager setting in `tools.nix`, not a required manual `git config` step.
|
||||
|
||||
## Build and apply
|
||||
|
||||
As `dev`, from `/etc/nixos`:
|
||||
|
||||
```sh
|
||||
nixfmt --check ./*.nix
|
||||
nix flake check --no-update-lock-file
|
||||
nix build .#nixosConfigurations.dev.config.system.build.toplevel --no-update-lock-file
|
||||
```
|
||||
|
||||
New source files must be added to Git for flakes to see them. Keep `flake.lock` in version control. A build does **not** activate changes.
|
||||
|
||||
Review and activate exactly the built closure:
|
||||
|
||||
```sh
|
||||
sudo nixos-rebuild dry-activate --store-path "$(readlink -f result)"
|
||||
sudo nixos-rebuild switch --store-path "$(readlink -f result)"
|
||||
```
|
||||
|
||||
For initial deployment on a compatible NixOS EC2 base where flakes are not enabled yet, check out this repo and run the build as an existing administrator with the temporary CLI flag:
|
||||
|
||||
```sh
|
||||
nix --extra-experimental-features 'nix-command flakes' build \
|
||||
.#nixosConfigurations.dev.config.system.build.toplevel --no-update-lock-file
|
||||
```
|
||||
|
||||
Then review/apply the resulting closure as above. The configuration creates `dev`, sets permissions and deploys its files. **No separate Neovim clone, copy, useradd, chown or global Git-config recipe is required.**
|
||||
|
||||
This host build does not use a mutable channel. NixOS's native flake integration also pins the `nixpkgs` registry entry and `<nixpkgs>` lookup to the system input. Dev-environment templates/composition remain deferred; there is no flake framework here.
|
||||
|
||||
## Neovim: import, do not rewrite
|
||||
|
||||
The input is [the existing neovim-dots repository](https://git.cyber.ayyalasomayajula.net/marsultor/neovim-dots), initially pinned to `380eb86778a7c53a0f1c18e84f14037456155347`.
|
||||
|
||||
Home Manager deploys its files under `~/.config/nvim`, with the **Lua, AstroNvim, Lazy, Mason, plugins and keymaps unchanged**. `programs.neovim.configure` stays empty so Neovim discovers `init.lua` normally. No Nixvim or custom Lua loader.
|
||||
|
||||
- Configuration files are linked from the pinned source and managed by Nix. Change the upstream repo and its input revision rather than editing generated links.
|
||||
- Lazy's `lazy-lock.json` must remain writable. The config seeds a copy at `~/.local/state/nvim/locks/<dotfile-revision>.json` and links to it. A new dotfile revision gets its own original lock; repeated activation preserves runtime changes to an existing lock.
|
||||
- A declared migration preserves the earlier manual checkout intact at `~/projects/neovim-dots-before-nix`. It refuses to overwrite an existing backup. On a clean home this migration does nothing.
|
||||
|
||||
**Reproducibility boundary:** Nix locks the host inputs and dotfile source, and reproduces their deployment. The existing Lua still bootstraps Lazy and manages plugin/Mason downloads at runtime. The supplied Lazy lock records plugin revisions, but it is writable and Mason's tool versions are not pinned by this Nix config. This is not a claim that every runtime download/cache is a Nix-reproducible build. Changing that policy requires a separate agreement; do not silently replace the user's plugin managers.
|
||||
|
||||
## Updates and safety
|
||||
|
||||
- To change a pinned source, update its revision in `flake.nix`, regenerate `flake.lock` with `nix flake lock`, review the diff, then check/build before switching. No unattended updates.
|
||||
- Keep the EC2 module, sandboxing, signature verification and recovery access intact.
|
||||
- `system.stateVersion` and `home.stateVersion` are both `26.05`; these preserve compatibility, not package versions.
|
||||
- `test` activates changes too; it is not a dry run. Keep the original system generation.
|
||||
- Rollbacks do not restore mutable user/application data, lockfile updates, backups, or this Git working tree.
|
||||
- No private keys, plaintext secrets, build outputs or agent notes in this repo.
|
||||
@@ -0,0 +1,26 @@
|
||||
{ inputs, modulesPath, ... }:
|
||||
|
||||
{
|
||||
imports = [
|
||||
# Keep the image's EC2 boot, storage, metadata, SSH and SSM integration.
|
||||
"${modulesPath}/virtualisation/amazon-image.nix"
|
||||
./users.nix
|
||||
./tools.nix
|
||||
./neovim.nix
|
||||
];
|
||||
|
||||
nixpkgs.hostPlatform = "x86_64-linux";
|
||||
|
||||
nix = {
|
||||
channel.enable = false;
|
||||
settings.experimental-features = [
|
||||
"nix-command"
|
||||
"flakes"
|
||||
];
|
||||
};
|
||||
|
||||
system.configurationRevision = inputs.self.rev or inputs.self.dirtyRev or null;
|
||||
|
||||
# Initial data/default compatibility, not the desired package release.
|
||||
system.stateVersion = "26.05";
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPQKGcukJjCK6wu4iMoHlEgaCN2gnR4pEKFjpMgn78yI krishna pub
|
||||
Generated
+68
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"nodes": {
|
||||
"home-manager": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1787377438,
|
||||
"narHash": "sha256-Sxu1NLTD/Ern6hFGLlZmtKCSct3YQXZI/lls8RE1XeM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "65258d5c65a250189fde2e35f490d15e064c4c62",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "65258d5c65a250189fde2e35f490d15e064c4c62",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"neovim-dots": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1780499261,
|
||||
"narHash": "sha256-ScMf9MqK4EyEU0/Ki134efrPa8azNxgALnTfgiaLA3w=",
|
||||
"ref": "main",
|
||||
"rev": "380eb86778a7c53a0f1c18e84f14037456155347",
|
||||
"revCount": 17,
|
||||
"type": "git",
|
||||
"url": "https://git.cyber.ayyalasomayajula.net/marsultor/neovim-dots"
|
||||
},
|
||||
"original": {
|
||||
"ref": "main",
|
||||
"rev": "380eb86778a7c53a0f1c18e84f14037456155347",
|
||||
"type": "git",
|
||||
"url": "https://git.cyber.ayyalasomayajula.net/marsultor/neovim-dots"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1787848966,
|
||||
"narHash": "sha256-7gDpu5hpq0rOYnYMcOWqSzquK4HA/xU8Xf3CjUBOOJA=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d57af924f160a5084293c71c2043f058bd1cdb60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "d57af924f160a5084293c71c2043f058bd1cdb60",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"home-manager": "home-manager",
|
||||
"neovim-dots": "neovim-dots",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
description = "Development host";
|
||||
|
||||
inputs = {
|
||||
# Keep the machine's existing Nixpkgs revision; this is not an upgrade.
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/d57af924f160a5084293c71c2043f058bd1cdb60";
|
||||
|
||||
home-manager = {
|
||||
url = "github:nix-community/home-manager/65258d5c65a250189fde2e35f490d15e064c4c62";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
neovim-dots = {
|
||||
url = "git+https://git.cyber.ayyalasomayajula.net/marsultor/neovim-dots?ref=main&rev=380eb86778a7c53a0f1c18e84f14037456155347";
|
||||
flake = false;
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{ nixpkgs, home-manager, ... }:
|
||||
{
|
||||
nixosConfigurations.dev = nixpkgs.lib.nixosSystem {
|
||||
specialArgs = { inherit inputs; };
|
||||
modules = [
|
||||
./configuration.nix
|
||||
home-manager.nixosModules.home-manager
|
||||
];
|
||||
};
|
||||
|
||||
formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
|
||||
};
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
{ inputs, ... }:
|
||||
|
||||
{
|
||||
programs.neovim = {
|
||||
enable = true;
|
||||
defaultEditor = true;
|
||||
# Leave configure empty: load the user's init.lua normally.
|
||||
};
|
||||
|
||||
home-manager.users.dev =
|
||||
{ config, lib, ... }:
|
||||
let
|
||||
dots = inputs.neovim-dots;
|
||||
configDir = "${config.xdg.configHome}/nvim";
|
||||
lockFile = "${config.xdg.stateHome}/nvim/locks/${dots.rev}.json";
|
||||
backup = "${config.home.homeDirectory}/projects/neovim-dots-before-nix";
|
||||
in
|
||||
{
|
||||
# Import the original repo, without translating or patching its Lua.
|
||||
xdg.configFile."nvim" = {
|
||||
source = lib.cleanSourceWith {
|
||||
src = dots;
|
||||
filter = path: _: baseNameOf path != "lazy-lock.json";
|
||||
};
|
||||
recursive = true;
|
||||
};
|
||||
|
||||
# Lazy writes this file. Seed a writable copy per pinned config revision.
|
||||
xdg.configFile."nvim/lazy-lock.json".source = config.lib.file.mkOutOfStoreSymlink lockFile;
|
||||
home.activation.neovimLock = lib.hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ] ''
|
||||
if [[ ! -e "${lockFile}" ]]; then
|
||||
run install -D -m 0600 "${dots}/lazy-lock.json" "${lockFile}"
|
||||
fi
|
||||
'';
|
||||
|
||||
# Preserve the earlier manual checkout intact; never overwrite a backup.
|
||||
# On a clean machine this is a no-op, not a deployment prerequisite.
|
||||
home.activation.neovimCheckoutBackup =
|
||||
lib.hm.dag.entryBetween [ "linkGeneration" ] [ "writeBoundary" ]
|
||||
''
|
||||
if [[ -d "${configDir}/.git" ]]; then
|
||||
if [[ -e "${backup}" || -L "${backup}" ]]; then
|
||||
echo "Refusing to overwrite Neovim checkout backup: ${backup}" >&2
|
||||
exit 1
|
||||
fi
|
||||
run mkdir -p "$(dirname "${backup}")"
|
||||
run mv -T "${configDir}" "${backup}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{ pkgs, ... }:
|
||||
|
||||
{
|
||||
programs.git.enable = true;
|
||||
home-manager.users.dev.programs.git = {
|
||||
enable = true;
|
||||
package = null; # The system module supplies Git.
|
||||
settings.user.useConfigOnly = true;
|
||||
};
|
||||
|
||||
environment.systemPackages = [
|
||||
pkgs.nixfmt
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
users.users.dev = {
|
||||
isNormalUser = true;
|
||||
uid = 1001;
|
||||
description = "Development user";
|
||||
extraGroups = [ "wheel" ];
|
||||
openssh.authorizedKeys.keyFiles = [ ./dev-authorized-keys ];
|
||||
};
|
||||
|
||||
security.sudo.extraRules = [
|
||||
{
|
||||
users = [ "dev" ];
|
||||
commands = [
|
||||
{
|
||||
command = "ALL";
|
||||
options = [ "NOPASSWD" ];
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
|
||||
home-manager = {
|
||||
useGlobalPkgs = true;
|
||||
useUserPackages = true;
|
||||
backupFileExtension = "before-nix";
|
||||
users.dev.home.stateVersion = "26.05";
|
||||
};
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /home/dev/.config 0755 dev users -"
|
||||
"d /home/dev/projects 0755 dev users -"
|
||||
# Keep the working repo editable by dev. Z does not follow store symlinks.
|
||||
"Z /etc/nixos - dev users -"
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user