Compare commits
28 Commits
3b389a5f2a
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
| fd5793e2bd | |||
| a7d90c3b8d | |||
| 63f9e5c1d7 | |||
| b5c7d7d589 | |||
| d0c839494c | |||
| 2cfaddec4b | |||
| e36767b625 | |||
| 2585a0e9d3 | |||
| ac6c15f931 | |||
| b880a194ba | |||
| 8b633f598c | |||
| da5df102cd | |||
| 5ec9f2c528 | |||
| 80420198e8 | |||
| 53976751cd | |||
| b3380c6ce4 | |||
| 97fdb926ac | |||
| 04427ddcda | |||
| 0ea6d168fd | |||
| b6f3eee1b1 | |||
| 1d661943cf | |||
| d9fbf95ead | |||
| 0a3a1746cb | |||
| f079a39cbc | |||
| edd18bafdd | |||
| c1cb2490ed | |||
| 2f4f4257a3 | |||
| cdb8e15060 |
@@ -83,7 +83,7 @@ azos/
|
||||
├── shells/ # Dev shells
|
||||
└── azos-core/ # Shared feature library (git submodule)
|
||||
├── flake.nix
|
||||
├── features/ # Shared/reusable features (auto-discovered)
|
||||
├── suites/ # Suites, each grouping related sub-features (auto-discovered)
|
||||
├── overlays/ # Core overlays
|
||||
└── _lib/ # Module schema helpers
|
||||
```
|
||||
@@ -93,7 +93,10 @@ Features are auto-discovered by `import-tree` — no central imports file. Each
|
||||
then explicitly imported in `home-manager/home.nix` via `suiteModules.homeManager.<name>`.
|
||||
|
||||
**Where to put a new feature:**
|
||||
- Shared / reusable across machines → `azos-core/features/<name>/default.nix`
|
||||
- Shared / reusable across machines → inside an existing or new suite directory,
|
||||
`azos-core/suites/<suite>/<name>/default.nix` (registering
|
||||
`config.flake.modules.homeManager.<suite>`) — never a bare file directly under
|
||||
`azos-core/suites/`; every azos-core feature belongs to a suite.
|
||||
- Machine-specific → `azos/features/<name>/default.nix`
|
||||
|
||||
## Code Style Guidelines
|
||||
@@ -125,7 +128,7 @@ All features follow this flake-parts registration pattern:
|
||||
|
||||
### Naming Conventions
|
||||
- Option paths: `azos.<module-name>.<option>`
|
||||
- Module names: kebab-case (`git-config`, `claude-memory`)
|
||||
- Module names: kebab-case (`git-config`, `desktop`)
|
||||
- Feature directories: kebab-case, one `default.nix` per feature
|
||||
- Variables: kebab-case
|
||||
|
||||
@@ -171,8 +174,13 @@ Top-level suites group related features (defined in `azos-core/`):
|
||||
- `azos.suites.base` — base packages and config
|
||||
- `azos.suites.editor` — editor tools
|
||||
- `azos.suites.dev` — development tools
|
||||
- `azos.suites.station` — desktop applications
|
||||
- `azos.suites.exwm` — EXWM window manager
|
||||
- `azos.suites.desktop` — desktop session plumbing (flameshot, dunst, keyboard layout) plus generally-useful apps (browser, libreoffice, zoom, spotify, gimp, whatsapp). Shared, lives in azos-core — wanted on any GUI machine, work included.
|
||||
- `azos.suites.exwm` — EXWM window manager, depends on `desktop`
|
||||
|
||||
Machine-specific, personal-only apps (steam, discord) are **not** part of any azos-core
|
||||
suite — they live as standalone features directly in `azos/features/`, imported only on
|
||||
machines that want them (e.g. `azos/features/discord`, `azos/features/steam`), since any
|
||||
such machine already uses the `azos` project rather than a different downstream repo.
|
||||
|
||||
The machine suite (`azos.suites.lauretta`) enables the relevant suites for this machine.
|
||||
|
||||
@@ -193,26 +201,38 @@ specialisation = {
|
||||
|
||||
## Adding New Modules
|
||||
|
||||
1. Create `features/<name>/default.nix` (machine-specific) or `azos-core/features/<name>/default.nix` (shared)
|
||||
1. Create `features/<name>/default.nix` (machine-specific) or, for shared features,
|
||||
`azos-core/suites/<suite>/<name>/default.nix` inside an existing or new suite
|
||||
(registering `config.flake.modules.homeManager.<suite>`, not a standalone module name)
|
||||
2. Use the module template above
|
||||
3. Add `suiteModules.homeManager.<name>` to the imports list in `home-manager/home.nix`
|
||||
3. Machine-specific: add `suiteModules.homeManager.<name>` to the imports list in
|
||||
`home-manager/home.nix`. Shared: no change needed if the suite is already imported.
|
||||
4. Run `nix fmt`
|
||||
|
||||
> **Submodule gotcha**: New files in `azos-core/` must be `git add`-ed inside the submodule
|
||||
> before `nix build` will see them — Nix flakes only evaluate git-tracked files.
|
||||
> ```bash
|
||||
> cd azos-core && git add features/<name>/
|
||||
> cd azos-core && git add suites/<suite>/<name>/
|
||||
> ```
|
||||
|
||||
## Claude Integration
|
||||
## Agent Memory/Skills Integration
|
||||
|
||||
Claude Code is integrated with this environment via several azos-core features:
|
||||
Memory (org-roam) and skills (slash commands/CLAUDE.md) are backend-pluggable, not
|
||||
Claude-specific, even though Claude is currently the only skills backend and one of two
|
||||
memory backends:
|
||||
|
||||
| Feature | Option | What it does |
|
||||
|---------|--------|--------------|
|
||||
| `azos-core/features/claude-memory` | `azos.claude-memory.enable` | Registers org-roam-mcp as a global MCP server in `~/.claude.json` |
|
||||
| `azos-core/features/claude-skills` | `azos.claude-skills.enable` | Deploys skills to `~/.claude/commands/` and content to `~/.claude/CLAUDE.md` |
|
||||
| `azos/features/claude` | `azos.claude.enable` | Installs claude-code, auto-enables the above two |
|
||||
| `azos-core/suites/base/agent-memory` | `azos.agent-memory.backends.claude.enable` | Registers org-roam-mcp as a global MCP server in `~/.claude.json`; hooks resync org-roam's index on edit and inject project context at session start |
|
||||
| `azos-core/suites/base/agent-memory` | `azos.agent-memory.backends.agent-shell.enable` | Registers org-roam-mcp via `agent-shell-mcp-servers` and resyncs on `file-write` events — applies regardless of which ACP backend (Claude, Gemini CLI, Codex, ...) a given agent-shell session runs. Also marks Claude sessions agent-shell launches (`AZOS_AGENT_SHELL_SESSION=1`) so the claude backend's own hooks no-op for those sessions instead of double-acting |
|
||||
| `azos-core/suites/base/agent-skills` | `azos.agent-skills.backends.claude.enable` | Deploys skills to `~/.claude/commands/` and content to `~/.claude/CLAUDE.md` |
|
||||
| `azos/features/claude` | `azos.claude.enable` | Installs claude-code, auto-enables the claude backend for both of the above |
|
||||
| `azos-core/suites/editor/emacs` | (bundled, not a separate option) | Installs agent-shell, auto-enables `azos.agent-memory.backends.agent-shell.enable` |
|
||||
|
||||
Both agent-memory and agent-skills register `config.flake.modules.homeManager.base`
|
||||
(they live inside the `base` suite, since that's where agent-shell is introduced) — not
|
||||
standalone module names, so no separate `suiteModules.homeManager.*` import is needed for
|
||||
them in `home-manager/home.nix`.
|
||||
|
||||
### Adding Claude skills or standing instructions
|
||||
From any module — no need to touch azos-core:
|
||||
@@ -223,7 +243,9 @@ azos.claude.globalMdSections.my-rule = ''
|
||||
Always do X when Y.
|
||||
'';
|
||||
```
|
||||
See `azos-core/features/claude-skills/README.md` for full documentation.
|
||||
`globalSkills`/`globalMdSections` stay Claude-specific (`~/.claude/commands/`,
|
||||
`~/.claude/CLAUDE.md`) deliberately — no other backend has a uniform equivalent yet. See
|
||||
`azos-core/suites/base/agent-skills/README.md` for full documentation.
|
||||
|
||||
## Key Files
|
||||
|
||||
@@ -236,10 +258,10 @@ See `azos-core/features/claude-skills/README.md` for full documentation.
|
||||
| `nixos/configuration-vm.nix` | Test VM config |
|
||||
| `overlays/` | Package overlays |
|
||||
| `azos-core/` | Shared feature library (git submodule) |
|
||||
| `azos-core/features/` | Shared features (auto-discovered by import-tree) |
|
||||
| `azos-core/suites/` | Suites, each grouping related shared sub-features (auto-discovered by import-tree) |
|
||||
| `features/` | Machine-specific features (auto-discovered by import-tree) |
|
||||
| `azos-core/features/editor/emacs/config.org` | Literate Emacs config |
|
||||
| `azos-core/features/claude-skills/README.md` | Claude skills extensibility docs |
|
||||
| `azos-core/suites/editor/emacs/config.org` | Literate Emacs config |
|
||||
| `azos-core/suites/base/claude-skills/README.md` | Claude skills extensibility docs |
|
||||
|
||||
## Common Tasks
|
||||
|
||||
|
||||
+1
-1
Submodule azos-core updated: 28185950ad...d6f965b6e7
@@ -1,41 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.audio = {
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.azos.home-audio.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.home-audio.enable {};
|
||||
};
|
||||
|
||||
config.flake.modules.nixos.audio = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.hardware-audio.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.hardware-audio.enable {
|
||||
services = {
|
||||
pipewire = {
|
||||
enable = true;
|
||||
audio.enable = true;
|
||||
pulse.enable = true;
|
||||
alsa = {
|
||||
enable = true;
|
||||
support32Bit = true;
|
||||
};
|
||||
jack.enable = true;
|
||||
};
|
||||
};
|
||||
environment.systemPackages = with pkgs; [pavucontrol];
|
||||
musnix.enable = true;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.nixos.binfmt = {
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.azos.binfmt.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.binfmt.enable {
|
||||
boot.binfmt.emulatedSystems = ["aarch64-linux"];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.nixos.bluetooth = {
|
||||
lib,
|
||||
config,
|
||||
...
|
||||
}: {
|
||||
options.azos.bluetooth.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.bluetooth.enable {
|
||||
services.blueman.enable = true;
|
||||
hardware.bluetooth.enable = true;
|
||||
environment.etc."bluetooth/input.conf" = lib.mkForce {
|
||||
text = ''
|
||||
[General]
|
||||
ClassicBondedOnly=false
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.overlayPkgs.emacs-lichess = pkgs: let
|
||||
epkgs = pkgs.emacs.pkgs;
|
||||
in
|
||||
epkgs.trivialBuild {
|
||||
pname = "lichess";
|
||||
version = "1.0";
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "tmythicator";
|
||||
repo = "lichess.el";
|
||||
rev = "572a6a353e2113c8a95564b9bea1f2896e6d3dab";
|
||||
sha256 = "1d87qjmdpizggkrzq39i9vqgvbpjf5mv97vm7zy28ksbqxmfasqa";
|
||||
};
|
||||
# trivialBuild's installPhase only installs *.el/*.elc; the piece SVGs
|
||||
# under assets/ must be copied alongside so the default
|
||||
# lichess-board-gui-asset-path (relative to the installed .el file) resolves.
|
||||
postInstall = ''
|
||||
cp -r assets $out/share/emacs/site-lisp/
|
||||
'';
|
||||
};
|
||||
|
||||
config.flake.modules.homeManager.chess = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.chess.enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
config = lib.mkIf config.azos.chess.enable {
|
||||
azos.emacs.pkgs = [pkgs.azos-emacs-chess];
|
||||
azos.emacs.enabledSuites = ["azos-emacs-chess"];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
orgTrivialBuild,
|
||||
epkgs,
|
||||
pkgs,
|
||||
}:
|
||||
orgTrivialBuild {
|
||||
pname = "azos-emacs-chess";
|
||||
version = "0.1.0";
|
||||
src = ./config.org;
|
||||
packageRequires = with epkgs; [
|
||||
pkgs.azos-emacs-base
|
||||
pkgs.emacs-lichess
|
||||
];
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#+title: Aner's Emacs Chess Configuration
|
||||
#+property: header-args :results silent
|
||||
|
||||
* Base setup
|
||||
** Require
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(require 'azos-emacs-base)
|
||||
#+end_src
|
||||
|
||||
* Lichess
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package lichess
|
||||
:demand t
|
||||
:init
|
||||
(defun azos/chess/open ()
|
||||
"Open lichess in its dedicated \"chess\" tab."
|
||||
(interactive)
|
||||
(azos/tab-bar/open-singleton-tab "chess" #'lichess))
|
||||
:bind
|
||||
(:map azos/global-minor-mode/open-keymap
|
||||
("k" . azos/chess/open)))
|
||||
#+end_src
|
||||
|
||||
* Provide
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(provide 'azos-emacs-chess)
|
||||
#+end_src
|
||||
@@ -1,3 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.overlayPkgs.azos-emacs-chess = pkgs: pkgs.localEmacsPkg ./_pkg.nix;
|
||||
}
|
||||
@@ -11,8 +11,8 @@
|
||||
};
|
||||
config = lib.mkIf config.azos.claude.enable {
|
||||
home.packages = with pkgs; [claude-code claude-agent-acp];
|
||||
azos.claude-memory.enable = lib.mkDefault true;
|
||||
azos.claude-skills.enable = lib.mkDefault true;
|
||||
azos.agent-memory.backends.claude.enable = lib.mkDefault true;
|
||||
azos.agent-skills.backends.claude.enable = lib.mkDefault true;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.dav = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
config = {
|
||||
azos.dav.enable = true;
|
||||
|
||||
# Same smartcard gate as mail: the password comes from `pass`.
|
||||
azos.dav.presenceCheckCommand = lib.mkIf config.azos.encryption.yubikey.enable "${pkgs.azos-card-present}/bin/azos-card-present";
|
||||
|
||||
accounts.calendar.accounts.nextcloud = {
|
||||
remote = {
|
||||
type = "caldav";
|
||||
# Principal discovery from the DAV root resolves the account's
|
||||
# user ID, which differs from the login name on this server.
|
||||
url = "https://nextcloud.zakobar.com/remote.php/dav/";
|
||||
userName = "aner";
|
||||
passwordCommand = ["sh" "-c" "pass zakobar.com | head -1"];
|
||||
};
|
||||
vdirsyncer = {
|
||||
enable = true;
|
||||
collections = ["from a"];
|
||||
itemTypes = ["VTODO"];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.discord = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.discord.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.discord.enable {
|
||||
home.packages = with pkgs; [discord];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.encryption = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.encryption.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.encryption.enable {
|
||||
programs.password-store = {
|
||||
enable = true;
|
||||
settings = {
|
||||
PASSWORD_STORE_KEY = "26B851F81F8F7C55";
|
||||
};
|
||||
};
|
||||
home.packages = with pkgs; [
|
||||
yubikey-personalization
|
||||
];
|
||||
programs.gpg = {
|
||||
enable = true;
|
||||
};
|
||||
programs.browserpass = {
|
||||
enable = true;
|
||||
browsers = ["chromium"];
|
||||
};
|
||||
services.gpg-agent = {
|
||||
enable = true;
|
||||
enableSshSupport = true;
|
||||
grabKeyboardAndMouse = false;
|
||||
pinentry.package = pkgs.pinentry-emacs;
|
||||
extraConfig = ''
|
||||
allow-emacs-pinentry
|
||||
allow-loopback-pinentry
|
||||
'';
|
||||
};
|
||||
|
||||
# pinentry-emacs only attempts its Emacs-loopback socket when ITS OWN
|
||||
# process env has INSIDE_EMACS set (see pinentry-emacs.c) -- and that
|
||||
# process is gpg-agent's child, so it inherits gpg-agent's daemon env,
|
||||
# not the env of whichever client (git, pass, ...) asked it to sign.
|
||||
# Without this, any signing request from outside Emacs (e.g. a plain
|
||||
# terminal, or an agent shelling out to git) always falls through to
|
||||
# the curses pinentry, which fails outright when there's no real tty.
|
||||
#
|
||||
# Only safe to force on when Emacs is structurally guaranteed to be
|
||||
# up and responsive -- i.e. under EXWM, where Emacs *is* the window
|
||||
# manager. Elsewhere Emacs may not be running (fine: the loopback
|
||||
# socket connect fails instantly, same fast fallback as today) or may
|
||||
# be running but transiently busy (not fine: pinentry then blocks on
|
||||
# the Assuan handshake for up to its 60s timeout before falling back,
|
||||
# which is strictly worse than today's instant failure).
|
||||
systemd.user.services.gpg-agent.Service.Environment =
|
||||
lib.mkIf config.azos.suites.exwm.enable ["INSIDE_EMACS=1"];
|
||||
|
||||
home.file.".ssh/config".source = ./ssh-config;
|
||||
home.file.".ssh/gpg-as-ssh.pub".source = ./gpg-as-ssh.pub;
|
||||
home.file.".gnupg/sshcontrol".source = ./sshcontrol;
|
||||
|
||||
# home-manager creates ~/.gnupg (via the files above) with the default
|
||||
# umask, which can leave it group/other-readable. gpg refuses to trust
|
||||
# a homedir like that and prints an "unsafe permissions" warning on
|
||||
# every invocation, which pollutes anything that captures gpg/pass
|
||||
# output (e.g. shell-command-to-string). Enforce 700 on every switch.
|
||||
home.activation.gnupgPerms = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||
$DRY_RUN_CMD chmod 700 "$HOME/.gnupg"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMdozJewuuqG4VuwGhI5Uw2xDIUrkQwyPGQ2q3tvnPq/VoV+zfdnou42I6XYEfOGd6xfLR0byOGCk1Ia1F/picBsxADEaJFBnTny5MWNA5MxwJIYUPW5+kFN1TmxdNGmj0WJ8qZeKkS3ffixdHlHTVu4Rzrlu8qDdw8TmgcvD7sHpNaBB+hwG9B/s5BAozfXvyXtW51lLLrEZdmUXJxAP51ubVgysZ985PA89o4G7LUE20IKAj3rgylLwq+Lz2PoGmVCptPTknLLzGlxTy2mU/tESJtS4iUvshSYiKak73BSen88xLWL/eZGFzOLW4TNem/qT0ykqK1s1BaGWCcQiDWN+Lpa3XqTbFN054MfhLdEMp+NjrdsCbZORzp9p2ZPDqi5jI1Hy7IN/8VzzB+T13MX6YhW7CBBqUZaivGhVQGsNjrFusPGhCaZrzNJ1l4qssLAlOh9qdaYzqDe+BIHsty55vw4Ph+Axou82babpLoiQzkCol2TWjPr7JSfHFkm6CmoTQ7mK8m7jRCodmxF+BEe4Usr+HrTKDc9fr5P4mdfL0O+ZPrlkPuG1zBTMal9MPxeCk4emzOZf7vBMNzb0aKHbOtSdVk/IcjatlDd2EIo34e5A6anLRMAdxb7901mnCAcjvCn6+ArNV8nr+NBk1+UvumZ+Q59LTrAkgRt3rBw== openpgp:0x13E0C25D
|
||||
@@ -1 +0,0 @@
|
||||
IdentityAgent ${XDG_RUNTIME_DIR}/gnupg/S.gpg-agent.ssh
|
||||
@@ -1 +0,0 @@
|
||||
37710BAB5F6DC9878A450CDA8A4661D932C04ACB
|
||||
@@ -1,32 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.git-config = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.git-config.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.git-config.enable {
|
||||
home.packages = with pkgs; [pass-git-helper unzip];
|
||||
programs.git = {
|
||||
enable = true;
|
||||
signing = {
|
||||
key = "BB141234A3DD953D";
|
||||
signByDefault = true;
|
||||
};
|
||||
settings = {
|
||||
credential.helper = "!pass-git-helper $@";
|
||||
user = {
|
||||
name = "Aner Zakobar";
|
||||
email = "aner@zakobar.com";
|
||||
};
|
||||
};
|
||||
};
|
||||
home.file.".config/pass-git-helper/git-pass-mapping.ini".source =
|
||||
./pass-git-mapping.ini;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
[*git.zakobar.com*]
|
||||
target=zakobar.com
|
||||
@@ -0,0 +1,52 @@
|
||||
{...}: {
|
||||
config.flake.modules.nixos.hardware-auth = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.hardware-auth.enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enables YubiKey (U2F/FIDO2) as a "sufficient" PAM factor ahead of
|
||||
the password, for both sudo and login (which greetd/tuigreet
|
||||
substacks) -- touch the key to auth, or just type your password as
|
||||
normal if it's not plugged in.
|
||||
|
||||
After enabling and rebuilding, enroll each key on the new machine
|
||||
(per-user, not machine-wide state, so this must be redone per
|
||||
machine):
|
||||
|
||||
mkdir -p ~/.config/Yubico
|
||||
pamu2fcfg > ~/.config/Yubico/u2f_keys
|
||||
|
||||
To add a second/backup key without overwriting the first:
|
||||
|
||||
pamu2fcfg -n >> ~/.config/Yubico/u2f_keys
|
||||
|
||||
Test with `sudo true` (touching the key) before trusting it on the
|
||||
greeter -- a login lockout is harder to recover from than a failed
|
||||
sudo in an already-open session.
|
||||
'';
|
||||
};
|
||||
|
||||
config = lib.mkIf config.azos.hardware-auth.enable {
|
||||
# Default NixOS PAM rule ordering already tries u2f before pam_unix
|
||||
# (password), both "sufficient" -- so YubiKey touch wins first,
|
||||
# password always remains as fallback.
|
||||
#
|
||||
# Fingerprint (fprintd) is deliberately not wired up here: lauretta's
|
||||
# reader is a Synaptics Metallica MIS (06cb:009a), a match-on-chip
|
||||
# sensor unsupported by upstream libfprint. The only known fix is an
|
||||
# unofficial python-validity + open-fprintd stack, not packaged in
|
||||
# nixpkgs and fragile enough (suspend/resume, PAM lockout risk) to not
|
||||
# be worth it for now.
|
||||
security.pam.u2f = {
|
||||
enable = true;
|
||||
settings.cue = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.headphones = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.headphones = {
|
||||
enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
devices = lib.mkOption {
|
||||
default = [];
|
||||
type = lib.types.listOf (lib.types.submodule {
|
||||
options = {
|
||||
mac = lib.mkOption {type = lib.types.str;};
|
||||
name = lib.mkOption {type = lib.types.str;};
|
||||
};
|
||||
});
|
||||
description = "Headphone devices; first entry is the default connect target.";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf config.azos.headphones.enable {
|
||||
home.packages = let
|
||||
deviceEntries =
|
||||
lib.concatMapStrings
|
||||
(d: " ${lib.escapeShellArg "${d.mac} ${d.name}"}\n")
|
||||
config.azos.headphones.devices;
|
||||
defaultMac =
|
||||
if config.azos.headphones.devices == []
|
||||
then ""
|
||||
else (lib.head config.azos.headphones.devices).mac;
|
||||
in [
|
||||
(pkgs.writeShellScriptBin "azos-connect-headphones" ''
|
||||
DEVICES=(
|
||||
${deviceEntries})
|
||||
|
||||
case "$1" in
|
||||
--list)
|
||||
printf '%s\n' "''${DEVICES[@]}"
|
||||
;;
|
||||
--mac)
|
||||
echo "connect $2" | bluetoothctl
|
||||
;;
|
||||
*)
|
||||
echo "connect ${defaultMac}" | bluetoothctl
|
||||
;;
|
||||
esac
|
||||
'')
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.hfsprogs = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.hfsprogs.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.hfsprogs.enable {
|
||||
home.packages = with pkgs; [hfsprogs];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,98 +1,28 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.homey-alert = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.homey-alert.enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
config.flake.overlayPkgs.azos-homey-ntfy-alert = pkgs:
|
||||
pkgs.writeShellApplication {
|
||||
name = "azos-homey-ntfy-alert";
|
||||
runtimeInputs = with pkgs; [curl pass];
|
||||
text = ''
|
||||
TOKEN="$(pass zakobar-homecenter/ntfy-token)"
|
||||
|
||||
CMD=(
|
||||
curl -sf
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
-H "Priority: ''${PRIORITY:-default}"
|
||||
)
|
||||
[[ -n "''${TITLE:-}" ]] && CMD+=(-H "Title: $TITLE")
|
||||
[[ -n "''${TAGS:-}" ]] && CMD+=(-H "Tags: $TAGS")
|
||||
CMD+=(-d "''${MESSAGE:-}" https://ntfy.zakobar.com/alerts)
|
||||
|
||||
if [[ -n "''${AZOS_SEND_ALERT_PRINT_REMOTE:-}" ]]; then
|
||||
# Shell-quote each element so the output is safe to embed in SSH commands --
|
||||
# the token is already resolved above, so the remote host only needs curl.
|
||||
printf '%q ' "''${CMD[@]}"
|
||||
printf '\n'
|
||||
else
|
||||
"''${CMD[@]}"
|
||||
fi
|
||||
'';
|
||||
};
|
||||
|
||||
config = lib.mkIf config.azos.homey-alert.enable {
|
||||
azos.claude.globalSkills.notify = ./notify-skill.md;
|
||||
|
||||
home.packages = [
|
||||
(pkgs.writeShellApplication {
|
||||
name = "homey-alert";
|
||||
runtimeInputs = with pkgs; [curl pass];
|
||||
text = ''
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: homey-alert [OPTIONS] [MESSAGE]
|
||||
|
||||
Send a notification to the homey ntfy server (ntfy.zakobar.com/alerts).
|
||||
MESSAGE can be given as a positional argument or piped via stdin.
|
||||
|
||||
Options:
|
||||
-t, --title TITLE notification title
|
||||
-p, --priority PRIO priority: min|low|default|high|urgent (default: default)
|
||||
-T, --tags TAGS comma-separated emoji shortcodes (e.g. warning,rotating_light)
|
||||
-r, --print-remote print the curl command instead of running it
|
||||
(for pasting into SSH commands to remote machines)
|
||||
-h, --help show this help
|
||||
|
||||
Examples:
|
||||
# Local
|
||||
long_cmd && homey-alert -t "Training" -p high -T white_check_mark "done"
|
||||
|
||||
# Remote — expands locally, runs on the remote machine
|
||||
ssh user@host "python train.py && \$(homey-alert -r -t 'Training' 'done')"
|
||||
EOF
|
||||
}
|
||||
|
||||
TITLE=""
|
||||
PRIORITY="default"
|
||||
TAGS=""
|
||||
PRINT_REMOTE=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-t|--title) TITLE="$2"; shift 2 ;;
|
||||
-p|--priority) PRIORITY="$2"; shift 2 ;;
|
||||
-T|--tags) TAGS="$2"; shift 2 ;;
|
||||
-r|--print-remote) PRINT_REMOTE=true; shift ;;
|
||||
-h|--help) usage; exit 0 ;;
|
||||
--) shift; break ;;
|
||||
-*) echo "Unknown option: $1" >&2; usage >&2; exit 1 ;;
|
||||
*) break ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ $# -gt 0 ]]; then
|
||||
MESSAGE="$*"
|
||||
elif [[ ! -t 0 ]]; then
|
||||
MESSAGE="$(cat)"
|
||||
else
|
||||
echo "homey-alert: no message provided" >&2
|
||||
usage >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN="$(pass zakobar-homecenter/ntfy-token)"
|
||||
|
||||
# Build the curl invocation as an array — single source of truth for both modes
|
||||
CMD=(
|
||||
curl -sf
|
||||
-H "Authorization: Bearer $TOKEN"
|
||||
-H "Priority: $PRIORITY"
|
||||
)
|
||||
[[ -n "$TITLE" ]] && CMD+=(-H "Title: $TITLE")
|
||||
[[ -n "$TAGS" ]] && CMD+=(-H "Tags: $TAGS")
|
||||
CMD+=(-d "$MESSAGE" https://ntfy.zakobar.com/alerts)
|
||||
|
||||
if $PRINT_REMOTE; then
|
||||
# Shell-quote each element so the output is safe to embed in SSH commands
|
||||
printf '%q ' "''${CMD[@]}"
|
||||
printf '\n'
|
||||
else
|
||||
"''${CMD[@]}"
|
||||
fi
|
||||
'';
|
||||
})
|
||||
];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
# Homey Alert — sending ntfy notifications when tasks finish
|
||||
|
||||
Use `homey-alert` to push notifications to Aner's homelab ntfy server when long tasks complete.
|
||||
|
||||
## When to use proactively
|
||||
|
||||
When running (or suggesting) a command that will take more than ~30 seconds — training runs,
|
||||
builds, deploys, downloads, test suites — append a notification automatically. Don't wait to
|
||||
be asked.
|
||||
|
||||
## Local commands
|
||||
|
||||
Append after the command so it fires on success only (`&&`) or always (`;`):
|
||||
|
||||
```bash
|
||||
# Notify on success only
|
||||
long_cmd && homey-alert "Task done"
|
||||
|
||||
# Always notify (even on failure) — prefer this for deploys/training
|
||||
long_cmd; homey-alert -t "Training" -p high -T "$([ $? -eq 0 ] && echo white_check_mark || echo x)" "Exited $?"
|
||||
```
|
||||
|
||||
## Remote commands (SSH / training machines)
|
||||
|
||||
Remote machines don't have `homey-alert`. Use `-r`/`--print-remote` to get a self-contained
|
||||
`curl` command with the token already embedded. The `$(...)` expands **locally**; the resulting
|
||||
command string is what the remote machine executes.
|
||||
|
||||
```bash
|
||||
ssh user@host "python train.py && $(homey-alert -r -t 'Training' -p high -T white_check_mark 'Model finished')"
|
||||
```
|
||||
|
||||
The remote machine only needs `curl` (universally available).
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Short | Values / notes |
|
||||
|------|-------|----------------|
|
||||
| `--title TITLE` | `-t` | Notification title |
|
||||
| `--priority PRIO` | `-p` | `min` / `low` / `default` / `high` / `urgent` |
|
||||
| `--tags TAGS` | `-T` | Comma-separated emoji shortcodes: `white_check_mark`, `x`, `warning`, `rotating_light`, `tada` |
|
||||
| `--print-remote` | `-r` | Print curl command instead of running (embed token locally, run remotely) |
|
||||
|
||||
## Choosing priority and tags
|
||||
|
||||
- Routine completion (backup, build): `default` priority, `white_check_mark`
|
||||
- Training / long job: `high` priority, `white_check_mark` on success / `x` on failure
|
||||
- Something broke: `urgent` priority, `rotating_light`
|
||||
@@ -1,16 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.homeManager.kubernetes = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.kubectl.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.kubectl.enable {
|
||||
home.packages = with pkgs; [kubectl kubernetes-helm velero];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -12,7 +12,7 @@ orgTrivialBuild {
|
||||
pkgs.azos-emacs-dev
|
||||
pkgs.azos-emacs-editor
|
||||
pkgs.azos-emacs-exwm
|
||||
pkgs.azos-emacs-station
|
||||
pkgs.azos-emacs-desktop
|
||||
|
||||
agent-shell
|
||||
elfeed-protocol
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
(require 'azos-emacs-dev)
|
||||
(require 'azos-emacs-editor)
|
||||
(require 'azos-emacs-exwm)
|
||||
(require 'azos-emacs-station)
|
||||
(require 'azos-emacs-desktop)
|
||||
#+end_src
|
||||
|
||||
* Lauretta specific
|
||||
@@ -207,12 +207,9 @@ deprecation warning, and =elfeed-feeds= ends up with the bare URL
|
||||
and no =:password=, so requests silently authenticate with an empty
|
||||
password and nothing ever lands in the db.
|
||||
|
||||
This =:config= block runs synchronously the first time =elfeed= is
|
||||
loaded (i.e. the first =M-x elfeed= of the session), with no user
|
||||
keypress behind it the way =azos/caldav-sync= has. Fetching the
|
||||
password with =shell-command-to-string= there used to deadlock
|
||||
Emacs whenever the GPG PIN cache had expired: that call blocks
|
||||
Emacs's single thread on the =pass|gpg= subprocess, but
|
||||
Fetching the password with =shell-command-to-string= used to
|
||||
deadlock Emacs whenever the GPG PIN cache had expired: that call
|
||||
blocks Emacs's single thread on the =pass|gpg= subprocess, but
|
||||
=pinentry-emacs= (see the GPG Pinentry section above) needs Emacs's
|
||||
command loop running to answer its loopback prompt — so Emacs waits
|
||||
on gpg-agent while gpg-agent waits on Emacs. Fetching the password
|
||||
@@ -222,11 +219,27 @@ with =make-process= instead keeps the command loop alive, so
|
||||
fires. Same stderr-redirect reasoning as before, to keep a stray
|
||||
gpg/pass warning off the captured password.
|
||||
|
||||
The fetch is wrapped in a named, interactive function rather than
|
||||
inlined in =:config= so a failed attempt (e.g. =pass= produced no
|
||||
output because the YubiKey wasn't plugged in yet) can be retried
|
||||
with =M-x azos/elfeed-protocol-init-feeds= — re-running =:config=
|
||||
itself would require reloading the =elfeed-protocol= feature
|
||||
entirely. An earlier version passed =(process-get proc 'output)=
|
||||
straight to =string-trim=, which signals (not just returns nil) when
|
||||
no output was ever read, aborting the sentinel before =elfeed-feeds=
|
||||
or =elfeed-protocol-enable= ran — so a failed first attempt left
|
||||
=elfeed-feeds= permanently =nil= for the rest of the session with no
|
||||
visible error beyond a =*Messages*= line, and every subsequent
|
||||
=elfeed-update= silently had nothing to fetch.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package elfeed-protocol
|
||||
:after elfeed
|
||||
:config
|
||||
(setq elfeed-protocol-enabled-protocols '(owncloud))
|
||||
(defun azos/elfeed-protocol-init-feeds ()
|
||||
"Fetch the Nextcloud password and (re)enable elfeed-protocol sync.
|
||||
Unlike the one-shot `use-package' :config form this is called from,
|
||||
safe to call again (e.g. via \\[azos/elfeed-protocol-init-feeds]) if
|
||||
a previous attempt failed, such as the YubiKey not being plugged in
|
||||
yet — it re-spawns the `pass' process and re-prompts for the PIN."
|
||||
(interactive)
|
||||
(make-process
|
||||
:name "elfeed-protocol-pass"
|
||||
:command '("sh" "-c" "pass zakobar.com 2>/dev/null | head -1")
|
||||
@@ -234,10 +247,20 @@ gpg/pass warning off the captured password.
|
||||
:filter (lambda (proc chunk)
|
||||
(process-put proc 'output (concat (or (process-get proc 'output) "") chunk)))
|
||||
:sentinel (lambda (proc _event)
|
||||
(setq elfeed-feeds
|
||||
`(("owncloud+https://aner@nextcloud.zakobar.com"
|
||||
:password ,(string-trim (process-get proc 'output)))))
|
||||
(elfeed-protocol-enable))))
|
||||
(let ((password (string-trim (or (process-get proc 'output) ""))))
|
||||
(if (string-empty-p password)
|
||||
(message "elfeed-protocol: `pass zakobar.com' produced no output; retry with M-x azos/elfeed-protocol-init-feeds")
|
||||
(setq elfeed-feeds
|
||||
`(("owncloud+https://aner@nextcloud.zakobar.com"
|
||||
:password ,password)))
|
||||
(elfeed-protocol-enable)
|
||||
(elfeed-update))))))
|
||||
|
||||
(use-package elfeed-protocol
|
||||
:after elfeed
|
||||
:config
|
||||
(setq elfeed-protocol-enabled-protocols '(owncloud))
|
||||
(azos/elfeed-protocol-init-feeds))
|
||||
#+end_src
|
||||
|
||||
** Elfeed Full Article Fetch
|
||||
|
||||
+10
-21
@@ -10,6 +10,8 @@
|
||||
mbsync = {
|
||||
enable = true;
|
||||
create = "maildir";
|
||||
remove = "both";
|
||||
expunge = "both";
|
||||
};
|
||||
msmtp.enable = true;
|
||||
};
|
||||
@@ -26,27 +28,14 @@
|
||||
};
|
||||
};
|
||||
in {
|
||||
options.azos.mail.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.mail.enable {
|
||||
programs = {
|
||||
mu.enable = true;
|
||||
mbsync.enable = true;
|
||||
msmtp.enable = true;
|
||||
lieer.enable = true;
|
||||
};
|
||||
home.activation.muInit = lib.hm.dag.entryAfter ["writeBoundary"] ''
|
||||
if [ ! -d "$HOME/.cache/mu" ]; then
|
||||
$DRY_RUN_CMD ${pkgs.mu}/bin/mu init \
|
||||
--maildir="$HOME/Maildir" \
|
||||
--my-address=aner@zakobar.com \
|
||||
--my-address=anerisgreat@gmail.com \
|
||||
--my-address=anerz@post.bgu.ac.il
|
||||
fi
|
||||
'';
|
||||
services.lieer.enable = true;
|
||||
config = {
|
||||
azos.mail.enable = true;
|
||||
|
||||
# mbsync's PassCmd shells out to `pass`, which needs the GPG smartcard to
|
||||
# decrypt -- only meaningful to gate on when the smartcard/YubiKey setup
|
||||
# is actually in play.
|
||||
azos.mail.presenceCheckCommand = lib.mkIf config.azos.encryption.yubikey.enable "${pkgs.azos-card-present}/bin/azos-card-present";
|
||||
|
||||
accounts.email.accounts = {
|
||||
gmail =
|
||||
default_gmail_params
|
||||
|
||||
@@ -4,17 +4,74 @@
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
}: let
|
||||
mountDir = "synced-docs";
|
||||
remoteSubfolder = "synced-docs";
|
||||
remoteName = "ncsynceddocs";
|
||||
remoteNameEnv = lib.toUpper remoteName;
|
||||
# rclone's webdav backend needs the account's Nextcloud *user ID*, not
|
||||
# its login name, in the /dav/files/<uid>/ path -- the two differ on
|
||||
# this server. The login name ("aner") still goes in the `user` field
|
||||
# for Basic Auth. Both the uid and password come from `pass` at service
|
||||
# start, same as mbsync in features/mail, so neither lands in the Nix
|
||||
# store.
|
||||
#
|
||||
# Shares the azos-card-present silent presence check (overlays/default.nix)
|
||||
# with the mail sync timer: loop quietly until the YubiKey is plugged in
|
||||
# (no pinentry prompt either way), then exec rclone in the foreground.
|
||||
# Restart=always means unplugging/replugging the key remounts naturally.
|
||||
nextcloudMountSafe = pkgs.writeShellScript "nextcloud-mount-safe" ''
|
||||
set -eu
|
||||
mountpoint="$HOME/${mountDir}"
|
||||
${pkgs.coreutils}/bin/mkdir -p "$mountpoint"
|
||||
while ! ${pkgs.azos-card-present}/bin/azos-card-present; do
|
||||
${pkgs.coreutils}/bin/sleep 10
|
||||
done
|
||||
password=$(${pkgs.pass}/bin/pass zakobar.com | ${pkgs.coreutils}/bin/head -1)
|
||||
uid=$(${pkgs.pass}/bin/pass nextcloud-username/aner | ${pkgs.coreutils}/bin/head -1)
|
||||
export RCLONE_CONFIG_${remoteNameEnv}_TYPE=webdav
|
||||
export RCLONE_CONFIG_${remoteNameEnv}_URL="https://nextcloud.zakobar.com/remote.php/dav/files/$uid/"
|
||||
export RCLONE_CONFIG_${remoteNameEnv}_VENDOR=nextcloud
|
||||
export RCLONE_CONFIG_${remoteNameEnv}_USER=aner
|
||||
export RCLONE_CONFIG_${remoteNameEnv}_PASS="$(${pkgs.rclone}/bin/rclone obscure "$password")"
|
||||
exec ${pkgs.rclone}/bin/rclone mount ${remoteName}:${remoteSubfolder} "$mountpoint" \
|
||||
--vfs-cache-mode writes
|
||||
'';
|
||||
in {
|
||||
options.azos.nextcloud-client.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.nextcloud-client.enable {
|
||||
home.packages = with pkgs; [nextcloud-client];
|
||||
services.nextcloud-client = {
|
||||
enable = true;
|
||||
startInBackground = false;
|
||||
};
|
||||
options.azos.nextcloud-mount.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkMerge [
|
||||
(lib.mkIf config.azos.nextcloud-client.enable {
|
||||
home.packages = with pkgs; [nextcloud-client];
|
||||
services.nextcloud-client = {
|
||||
enable = true;
|
||||
startInBackground = false;
|
||||
};
|
||||
})
|
||||
(lib.mkIf config.azos.nextcloud-mount.enable {
|
||||
home.packages = with pkgs; [rclone];
|
||||
# default.target, not graphical-session.target: the latter never
|
||||
# activates under EXWM in this setup (nothing explicitly starts it),
|
||||
# which is why nextcloud-client itself silently never autostarts.
|
||||
systemd.user.services.nextcloud-mount = {
|
||||
Unit.Description = "Mount Nextcloud ${remoteSubfolder} via rclone (waits silently for YubiKey)";
|
||||
Service = {
|
||||
Type = "simple";
|
||||
Environment = "PATH=/run/wrappers/bin:${pkgs.fuse3}/bin:${pkgs.fuse}/bin";
|
||||
ExecStart = "${nextcloudMountSafe}";
|
||||
ExecStop = "/run/wrappers/bin/fusermount3 -u %h/${mountDir}";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
};
|
||||
Install.WantedBy = ["default.target"];
|
||||
};
|
||||
})
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.nixos.printing = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: let
|
||||
webpFilter = pkgs.writeShellScript "webptopdf" ''
|
||||
if [ $# -ge 6 ]; then
|
||||
INPUT="$6"
|
||||
TMPFILE=""
|
||||
else
|
||||
TMPFILE=$(mktemp /tmp/cups-webp-XXXXXX.webp)
|
||||
cat > "$TMPFILE"
|
||||
INPUT="$TMPFILE"
|
||||
fi
|
||||
TMPOUT=$(mktemp /tmp/cups-webp-XXXXXX.pdf)
|
||||
${pkgs.imagemagick}/bin/magick "$INPUT" "$TMPOUT"
|
||||
cat "$TMPOUT"
|
||||
rm -f "$TMPOUT"
|
||||
[ -n "$TMPFILE" ] && rm -f "$TMPFILE"
|
||||
'';
|
||||
|
||||
webpCupsMime = pkgs.runCommand "webp-cups-mime" {} ''
|
||||
mkdir -p $out/lib/cups/filter $out/share/cups/mime
|
||||
cp ${webpFilter} $out/lib/cups/filter/webptopdf
|
||||
chmod +x $out/lib/cups/filter/webptopdf
|
||||
echo 'image/webp webp string(0,"RIFF") string(8,"WEBP")' > $out/share/cups/mime/webp.types
|
||||
echo 'image/webp application/pdf 0 webptopdf' > $out/share/cups/mime/webp.convs
|
||||
'';
|
||||
in {
|
||||
options.azos.printing.enable = lib.mkOption {
|
||||
default = false;
|
||||
example = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
|
||||
config = lib.mkIf config.azos.printing.enable {
|
||||
services.printing = {
|
||||
enable = true;
|
||||
drivers = [pkgs.hplip webpCupsMime];
|
||||
};
|
||||
|
||||
environment.systemPackages = [pkgs.hplip];
|
||||
|
||||
services.avahi = {
|
||||
enable = true;
|
||||
nssmdns4 = true;
|
||||
openFirewall = true;
|
||||
};
|
||||
|
||||
users.users.aner.extraGroups = ["lp"];
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
...
|
||||
}: {
|
||||
options.azos.qutebrowser-config.enable = lib.mkOption {
|
||||
default = true;
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.qutebrowser-config.enable {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
in {
|
||||
options.azos.snx-rs = {
|
||||
enable = lib.mkOption {
|
||||
default = true;
|
||||
default = false;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
server = lib.mkOption {
|
||||
|
||||
@@ -10,6 +10,14 @@
|
||||
};
|
||||
config = lib.mkIf config.azos.steam.enable {
|
||||
programs.steam.enable = true;
|
||||
|
||||
# HORI STICK A fight stick (0f0d:0040) is missing from Steam's bundled
|
||||
# 60-steam-input.rules, so Steam can't open its hidraw node for Steam
|
||||
# Input and falls back to a generic joystick mapping that drops BTN_C
|
||||
# (Circle). Grant the same uaccess the other HORI devices get.
|
||||
services.udev.extraRules = ''
|
||||
KERNEL=="hidraw*", ATTRS{idVendor}=="0f0d", ATTRS{idProduct}=="0040", MODE="0660", TAG+="uaccess"
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{...}: {
|
||||
config.flake.modules.nixos.virtualization = {
|
||||
lib,
|
||||
config,
|
||||
pkgs,
|
||||
...
|
||||
}: {
|
||||
options.azos.virtualization.enable = lib.mkOption {
|
||||
default = true;
|
||||
type = lib.types.bool;
|
||||
};
|
||||
config = lib.mkIf config.azos.virtualization.enable {
|
||||
virtualisation.libvirtd = {
|
||||
enable = true;
|
||||
qemu = {
|
||||
swtpm.enable = true;
|
||||
};
|
||||
};
|
||||
environment.systemPackages = with pkgs; [
|
||||
qemu_kvm
|
||||
libvirt
|
||||
virt-manager
|
||||
virt-viewer
|
||||
];
|
||||
services.spice-vdagentd.enable = true;
|
||||
networking.firewall.allowedTCPPorts = [5900 5901];
|
||||
networking.firewall.allowedUDPPorts = [5900 5901];
|
||||
};
|
||||
};
|
||||
}
|
||||
Generated
+37
-37
@@ -43,11 +43,11 @@
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1778617640,
|
||||
"narHash": "sha256-bG1hjyLIOiRB+M1rXm8ch0ftHPvV43lRtzQ8qWcus5s=",
|
||||
"lastModified": 1791902047,
|
||||
"narHash": "sha256-Mk/M/b2REkIj418YaOdXWWcoKK+5a1VHS08GnLAP7rE=",
|
||||
"owner": "anerisgreat",
|
||||
"repo": "evil-hl-line",
|
||||
"rev": "7cf5a11bdca2abd9516f0ce572e1e03bb541507f",
|
||||
"rev": "8489f19d6262c6f86e26d8635fa89016d23203c3",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -95,11 +95,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788450739,
|
||||
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
|
||||
"lastModified": 1790898470,
|
||||
"narHash": "sha256-zTwuwezD0w3hpga6a6P8emidzAZFyWqlNeRBlpWOOl8=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
|
||||
"rev": "024633cd702b10285db5cb19b40ad48d2399ba60",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -155,11 +155,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788651960,
|
||||
"narHash": "sha256-v9wJd32eZ2bvhBzVOd7TIjLQd011P7nwOhjKtWlci5I=",
|
||||
"lastModified": 1791642191,
|
||||
"narHash": "sha256-0bK0A59rVUn1SRZzo4cNi4VSqAe7vR1gL2/MZl53Frw=",
|
||||
"owner": "nix-community",
|
||||
"repo": "home-manager",
|
||||
"rev": "2c0350c759688177331b8f5242311fae8877bdb3",
|
||||
"rev": "c58f54c733a7894d84fa2fdff26cc12088c4b78c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -204,11 +204,11 @@
|
||||
"nixpkgs": "nixpkgs_3"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1777848538,
|
||||
"narHash": "sha256-Dj51C0NWsglqRrCpdmMr2nFiYacFOid1Tor4H9yG2HY=",
|
||||
"lastModified": 1791575145,
|
||||
"narHash": "sha256-A4dB6Yv1Q3ygHgYWefX2DLdbELN//mB+PbCxRMdjTeg=",
|
||||
"owner": "musnix",
|
||||
"repo": "musnix",
|
||||
"rev": "8548782f0d1d0928daa3fffde8a008f72219a3f3",
|
||||
"rev": "6e86730c235db14e8cac6605dfc33f300ce9105e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -222,11 +222,11 @@
|
||||
"nixpkgs": "nixpkgs_4"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1788335262,
|
||||
"narHash": "sha256-3jBEq8avfzlrsY4UpW4d5TOmm7ocseZfnitEJhqauyc=",
|
||||
"lastModified": 1791721963,
|
||||
"narHash": "sha256-BYhx89XJltnRyQeuDKHVL5eSqbbfAcOKuDts7QRsrxQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixos-hardware",
|
||||
"rev": "44d95795ee2d475b3d687325e26dcf4ca9104557",
|
||||
"rev": "60a624466c08787ee8303393b7d4752d1826f14f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -244,11 +244,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1784642409,
|
||||
"narHash": "sha256-hcbDqFuySAJawljt5r0sKBCJKYnbtGD0T/ZIozH1Dq0=",
|
||||
"lastModified": 1789164534,
|
||||
"narHash": "sha256-DoYGPM6QpnYBLWj9gGw6ZwAzIX+HrAVov1BoT+8Jixo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixos-wsl",
|
||||
"rev": "eaeb18da90024448a60eb1ec7132eafa4003404e",
|
||||
"rev": "72c92b11bb8289e6651c7fef29cc0a885fd6a255",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -288,11 +288,11 @@
|
||||
},
|
||||
"nixpkgs-lib_2": {
|
||||
"locked": {
|
||||
"lastModified": 1788057806,
|
||||
"narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
|
||||
"lastModified": 1790477367,
|
||||
"narHash": "sha256-oujbxmVKDhci2E4EM3KyAbRUgp5/IsCoiA/LfEF7OOk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
|
||||
"rev": "f7cd230690d9fc982d06129f9189b101fddcfa3b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -303,11 +303,11 @@
|
||||
},
|
||||
"nixpkgs-unstable": {
|
||||
"locked": {
|
||||
"lastModified": 1788614874,
|
||||
"narHash": "sha256-7QYjT2vHLuX9Z1pdxHXDKCbh1CR3D/2rywB9Tx0MPRg=",
|
||||
"lastModified": 1791734076,
|
||||
"narHash": "sha256-i5qk1kLiit98rRbh1AXmWBNT9QXC/y4l5Wk/u5EdpDQ=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c043004d1c6985732bcc1cbc5a9c9aecbbb4e0f0",
|
||||
"rev": "7fd4004d0511991fff3a0524856a9be09e28c5f0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -319,11 +319,11 @@
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1779508470,
|
||||
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
|
||||
"lastModified": 1791734076,
|
||||
"narHash": "sha256-i5qk1kLiit98rRbh1AXmWBNT9QXC/y4l5Wk/u5EdpDQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "29916453413845e54a65b8a1cf996842300cd299",
|
||||
"rev": "7fd4004d0511991fff3a0524856a9be09e28c5f0",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -335,11 +335,11 @@
|
||||
},
|
||||
"nixpkgs_3": {
|
||||
"locked": {
|
||||
"lastModified": 1777268161,
|
||||
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
|
||||
"lastModified": 1791456040,
|
||||
"narHash": "sha256-H83yz/do+hjOO0uujHyH8RgtIQP2v7Mw+cLwcZK/xKc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
|
||||
"rev": "e7439b6b14ad3cc35d05608ebca9bce01a25f5f8",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -351,11 +351,11 @@
|
||||
},
|
||||
"nixpkgs_4": {
|
||||
"locked": {
|
||||
"lastModified": 1767892417,
|
||||
"narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=",
|
||||
"rev": "3497aa5c9457a9d88d71fa93a4a8368816fbeeba",
|
||||
"lastModified": 1789546076,
|
||||
"narHash": "sha256-vWkSk5bbfTqdtMoSgD9FshACO8JCvXTFi+3cqEp0mH0=",
|
||||
"rev": "b1b875982b17dabde9b4a37f3e229e74913e6db3",
|
||||
"type": "tarball",
|
||||
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre924538.3497aa5c9457/nixexprs.tar.xz"
|
||||
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.11pre1074753.b1b875982b17/nixexprs.tar.xz"
|
||||
},
|
||||
"original": {
|
||||
"type": "tarball",
|
||||
@@ -364,11 +364,11 @@
|
||||
},
|
||||
"nixpkgs_5": {
|
||||
"locked": {
|
||||
"lastModified": 1788549839,
|
||||
"narHash": "sha256-kOrCcSIA6w9J1hX5DqHy2k9pDTJymExTsbV74U9UtCA=",
|
||||
"lastModified": 1791487292,
|
||||
"narHash": "sha256-tqM7xDEsaeK1p6VvRygeZ9LkQRpUUkbRT02BHv6hnTM=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "17de0b976395537756f30a3e78f2f06e5cec89ed",
|
||||
"rev": "8edc0c72e3a38faf5434e40d1f19431125fe4b30",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
+23
-12
@@ -11,19 +11,13 @@
|
||||
suiteModules.homeManager.base
|
||||
suiteModules.homeManager.dev
|
||||
suiteModules.homeManager.editor
|
||||
suiteModules.homeManager.station
|
||||
suiteModules.homeManager.desktop
|
||||
suiteModules.homeManager.discord
|
||||
suiteModules.homeManager.exwm
|
||||
suiteModules.homeManager.lauretta
|
||||
suiteModules.homeManager.audio
|
||||
suiteModules.homeManager.claude
|
||||
suiteModules.homeManager.claude-memory
|
||||
suiteModules.homeManager.claude-skills
|
||||
suiteModules.homeManager.encryption
|
||||
suiteModules.homeManager.git-config
|
||||
suiteModules.homeManager.hfsprogs
|
||||
suiteModules.homeManager.headphones
|
||||
suiteModules.homeManager.hyprland
|
||||
suiteModules.homeManager.kubernetes
|
||||
suiteModules.homeManager.dav
|
||||
suiteModules.homeManager.mail
|
||||
suiteModules.homeManager.mpris-proxy
|
||||
suiteModules.homeManager.nextcloud
|
||||
@@ -33,8 +27,6 @@
|
||||
suiteModules.homeManager.snx-rs
|
||||
suiteModules.homeManager.roam-backup
|
||||
suiteModules.homeManager.ytdl
|
||||
suiteModules.homeManager.chess
|
||||
suiteModules.homeManager.homey-alert
|
||||
suiteModules.homeManager.leetcode
|
||||
];
|
||||
|
||||
@@ -61,7 +53,26 @@
|
||||
}
|
||||
];
|
||||
azos.roam-backup.enable = true;
|
||||
azos.homey-alert.enable = true;
|
||||
|
||||
azos.send-alert.enable = true;
|
||||
azos.send-alert.remoteCommand = "${pkgs.azos-homey-ntfy-alert}/bin/azos-homey-ntfy-alert";
|
||||
|
||||
azos.git-config.userName = "Aner Zakobar";
|
||||
azos.git-config.userEmail = "aner@zakobar.com";
|
||||
azos.git-config.signing.enable = true;
|
||||
azos.git-config.signing.key = "BB141234A3DD953D";
|
||||
azos.git-config.passCredentialHelper.enable = true;
|
||||
azos.git-config.passCredentialHelper.mapping = ''
|
||||
[*git.zakobar.com*]
|
||||
target=zakobar.com
|
||||
'';
|
||||
|
||||
azos.encryption.passwordStore.gpgKeyId = "26B851F81F8F7C55";
|
||||
azos.encryption.yubikey.enable = true;
|
||||
azos.encryption.browserpass.enable = true;
|
||||
azos.encryption.gpgSsh.enable = true;
|
||||
azos.encryption.gpgSsh.keygrip = "37710BAB5F6DC9878A450CDA8A4661D932C04ACB";
|
||||
azos.encryption.gpgSsh.publicKey = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDMdozJewuuqG4VuwGhI5Uw2xDIUrkQwyPGQ2q3tvnPq/VoV+zfdnou42I6XYEfOGd6xfLR0byOGCk1Ia1F/picBsxADEaJFBnTny5MWNA5MxwJIYUPW5+kFN1TmxdNGmj0WJ8qZeKkS3ffixdHlHTVu4Rzrlu8qDdw8TmgcvD7sHpNaBB+hwG9B/s5BAozfXvyXtW51lLLrEZdmUXJxAP51ubVgysZ985PA89o4G7LUE20IKAj3rgylLwq+Lz2PoGmVCptPTknLLzGlxTy2mU/tESJtS4iUvshSYiKak73BSen88xLWL/eZGFzOLW4TNem/qT0ykqK1s1BaGWCcQiDWN+Lpa3XqTbFN054MfhLdEMp+NjrdsCbZORzp9p2ZPDqi5jI1Hy7IN/8VzzB+T13MX6YhW7CBBqUZaivGhVQGsNjrFusPGhCaZrzNJ1l4qssLAlOh9qdaYzqDe+BIHsty55vw4Ph+Axou82babpLoiQzkCol2TWjPr7JSfHFkm6CmoTQ7mK8m7jRCodmxF+BEe4Usr+HrTKDc9fr5P4mdfL0O+ZPrlkPuG1zBTMal9MPxeCk4emzOZf7vBMNzb0aKHbOtSdVk/IcjatlDd2EIo34e5A6anLRMAdxb7901mnCAcjvCn6+ArNV8nr+NBk1+UvumZ+Q59LTrAkgRt3rBw== openpgp:0x13E0C25D";
|
||||
|
||||
home = {
|
||||
username = "aner";
|
||||
|
||||
+10
-5
@@ -14,13 +14,11 @@
|
||||
inputs.home-manager.nixosModules.home-manager
|
||||
suiteModules.nixos.base
|
||||
suiteModules.nixos.exwm
|
||||
suiteModules.nixos.audio
|
||||
suiteModules.nixos.bluetooth
|
||||
suiteModules.nixos.desktop
|
||||
suiteModules.nixos.dev
|
||||
suiteModules.nixos.steam
|
||||
suiteModules.nixos.virtualization
|
||||
suiteModules.nixos.binfmt
|
||||
suiteModules.nixos.attic
|
||||
suiteModules.nixos.printing
|
||||
suiteModules.nixos.hardware-auth
|
||||
];
|
||||
|
||||
boot.loader.grub = {
|
||||
@@ -95,6 +93,13 @@
|
||||
azos.suites.exwm.enable = true;
|
||||
azos.attic.enable = true;
|
||||
azos.printing.enable = true;
|
||||
azos.printing.username = "aner";
|
||||
azos.hardware-auth.enable = true;
|
||||
|
||||
# Needed for unprivileged FUSE mounts (e.g. rclone mount) -- without this,
|
||||
# fusermount3 has no setuid wrapper and mounting fails with
|
||||
# "Operation not permitted" even though /dev/fuse itself is world-writable.
|
||||
programs.fuse.enable = true;
|
||||
|
||||
home-manager = {
|
||||
extraSpecialArgs = {inherit inputs outputs suiteModules pkgs;};
|
||||
|
||||
@@ -11,4 +11,14 @@
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Shared silent presence check for the GPG smartcard (the YubiKey's OpenPGP
|
||||
# applet): `gpg --card-status` only queries the reader, never invoking
|
||||
# pinentry, so it's safe to poll without ever producing a "please insert
|
||||
# card" prompt. Used by both the mail sync timer and the Nextcloud mount
|
||||
# service to gate on `pass` being decryptable.
|
||||
config.flake.overlayPkgs.azos-card-present = pkgs:
|
||||
pkgs.writeShellScriptBin "azos-card-present" ''
|
||||
exec ${pkgs.gnupg}/bin/gpg --card-status >/dev/null 2>&1
|
||||
'';
|
||||
}
|
||||
|
||||
+12
-1
@@ -5,7 +5,18 @@ pkgs.mkShell {
|
||||
nh
|
||||
(pkgs.writeShellScriptBin
|
||||
"azos-lauretta-update"
|
||||
"nh os switch '.?submodules=1#lauretta'")
|
||||
''
|
||||
# Authenticate up front and keep the sudo timestamp fresh during the
|
||||
# build, so nh's activation step doesn't prompt (and time out) at the end.
|
||||
sudo -v || exit 1
|
||||
while true; do
|
||||
sleep 60
|
||||
sudo -n -v 2>/dev/null || exit
|
||||
done &
|
||||
keepalive=$!
|
||||
trap 'kill $keepalive 2>/dev/null' EXIT
|
||||
nh os switch '.?submodules=1#lauretta'
|
||||
'')
|
||||
(pkgs.writeShellScriptBin
|
||||
"azos-update-azos-core"
|
||||
"nix flake update --flake '.?submodules=1' azos-core")
|
||||
|
||||
Reference in New Issue
Block a user