Compare commits

75 Commits

Author SHA1 Message Date
aner b3380c6ce4 Bugfix 2026-10-11 23:15:45 +03:00
aner 97fdb926ac Auto-notify on long Bash commands; homey-alert local fallback; nextcloud mount fix
- homey-alert: add a global PreToolUse hook that transparently times every
  Bash command Claude runs and fires a notification once a command crosses
  the configured threshold (default 5 min), without needing the skill to
  be invoked manually. Also makes homey-alert always fire a local
  notify-send alongside the ntfy push, since the ntfy server isn't always
  reachable.
- nextcloud: fix sleep crash-loop under the restricted systemd PATH
  (bin/sleep -> coreutils store path).
- azos-core bump (adds ncdu to the base suite).
2026-10-11 23:14:15 +03:00
aner 04427ddcda Nextcloud sync, also zapzap 2026-10-11 09:22:42 +03:00
aner 0ea6d168fd azos-core bump 2026-10-11 00:23:30 +03:00
aner b6f3eee1b1 pcap mode and auto load hook azos-core bump 2026-10-10 23:53:14 +03:00
aner 1d661943cf Add YubiKey U2F login/sudo auth; disable unused features by default
pam_u2f is set up as a sufficient factor ahead of password for sudo and
login (which greetd/tuigreet substacks), so touching the key logs in
without typing a password while leaving password as fallback.
Fingerprint was investigated too but lauretta's reader is an unsupported
match-on-chip sensor, documented in the feature instead of wired up.

Also flip kubectl, qutebrowser-config, and snx-rs to disabled by default
since they're unused.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-09 14:08:33 +03:00
aner d9fbf95ead Mail facelift 2026-10-09 00:01:24 +03:00
aner 0a3a1746cb New mail notifications 2026-10-08 13:21:32 +03:00
aner f079a39cbc Make elfeed-protocol Nextcloud password fetch retriable
A failed pass/gpg fetch (e.g. YubiKey not plugged in yet) used to pass
process output straight to string-trim, which signals instead of
returning nil on empty output, aborting the sentinel before
elfeed-feeds or elfeed-protocol-enable ran. That left elfeed-feeds nil
for the rest of the session with no visible error beyond a *Messages*
line, and every later elfeed-update silently had nothing to fetch.

Wrap the fetch in azos/elfeed-protocol-init-feeds so a failed attempt
can be retried interactively instead of requiring a full reload of the
elfeed-protocol feature.
2026-10-08 13:14:14 +03:00
aner edd18bafdd Remove unused lieer, add silent periodic mbsync timer; bump azos-core
lieer was declared (programs/services.lieer.enable) but had zero real
footprint — mu4e actually fetches via mbsync (mu4e-get-mail-command is
literally "mbsync -a") and sends via msmtp; lieer never had an auth/sync
state file anywhere. Drop it to stop the config suggesting a sync path
that isn't actually in use.

Add a systemd user timer that runs mbsync every 5 minutes. mbsync's
PassCmd shells out to `pass`, which needs the GPG smartcard; on a blind
timer a missing card would otherwise surface as a pinentry "please
insert card" prompt every run. The wrapper script gates the sync behind
`gpg --card-status`, which only queries the reader without invoking
pinentry either way, so a missing card is now a silent no-op instead of
a nag.

Bump azos-core for EDNC popup notifications.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-08 13:14:08 +03:00
aner c1cb2490ed Bump azos-core: pre-approve org-roam search_nodes/create_node MCP tools
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 10:29:17 +03:00
aner 2f4f4257a3 Fix GPG smartcard pinentry routing to Emacs; bump azos-core
The gpg-agent systemd Environment=INSIDE_EMACS=1 hack never worked:
gpg-agent calls gnupg_unsetenv("INSIDE_EMACS") on itself at startup by
design, expecting the variable per-connection from the client instead.
Move it to home.sessionVariables so git/gpg invocations themselves carry
it and forward it to gpg-agent via "OPTION putenv=...", which is what
actually routes a pinentry request to Emacs instead of failing on
curses with no tty.

Bump azos-core for the project-brain-aligned todo/workplate/work-history
skills and the global org-roam Read/Edit permission rules.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-06 10:26:29 +03:00
aner cdb8e15060 Whoops 2026-10-06 09:41:36 +03:00
aner 3b389a5f2a pinentry fixees, exwm gated 2026-10-06 09:40:41 +03:00
aner b20a67fdc6 Bump azos-core: resolve node paths via search_nodes, not content calls 2026-10-06 09:22:02 +03:00
aner 858f68f9c6 Lazily retry lichess token fetch; demand-load lichess package
gpg-agent may not have a cached passphrase yet when the top-level setq
runs at Emacs startup, so a cold agent makes 'pass' fail silently
(stderr redirected) and leaves lichess-token permanently empty for the
session. Retry the fetch right before each use instead of only once
at startup.
2026-10-06 00:30:30 +03:00
aner 8578e23114 Bump azos-core: org-roam-resync hook, no-Emacs-dependency 2026-10-06 00:30:00 +03:00
aner 98b2f461cd Leetcode and chess overhaul 2026-10-05 11:31:04 +03:00
aner f80ed5863e Yay fixes and stuff 2026-10-05 09:33:01 +03:00
aner 1d9784877b Better chess, cuda maintainers fix, elfeed update 2026-09-29 10:45:24 +03:00
aner e5455f35f9 Bugfix 2026-09-09 10:06:13 +03:00
aner fc41d9aef3 agent-shell-math-render update 2026-09-09 09:35:18 +03:00
aner d6916a3fa3 Maildir shortcuts, maildir fix 2026-09-07 07:04:05 +03:00
aner a9b5cd2847 Bump 2026-09-06 17:26:12 +03:00
aner 177963b64e Fixes! 2026-09-06 14:40:40 +03:00
aner e5cc25eb49 Fixy 2026-09-06 13:54:15 +03:00
aner 8fde1cd5fe FIx 2026-09-06 13:47:09 +03:00
aner 21be2b0c87 Version bump 2026-09-06 13:29:20 +03:00
aner e27b7cb777 Fixy fix 2026-09-06 13:24:45 +03:00
aner f4923cbcad Experimental features bump 2026-09-06 11:59:39 +03:00
aner 630054fb8d azos-core bump 2026-09-06 11:57:29 +03:00
aner 9909fb411b Pinentry update 2026-09-06 11:55:47 +03:00
aner 9c64e1513e Remote run skill, mu4e change 2026-09-06 11:49:54 +03:00
aner 943498ce52 Homey alert! 2026-09-03 11:37:12 +03:00
aner df97c76527 Added 8bitdo 2026-08-26 20:25:27 +03:00
aner 8be92183bb Headphone mechanism change 2026-08-20 10:18:57 +03:00
aner 4ea2421734 Beacon updated 2026-07-26 10:51:18 +03:00
aner fa2c62e2f4 azos-core bump 2026-07-26 01:20:47 +03:00
aner a6ff616c2d azos-core version bump 2026-07-26 00:53:03 +03:00
aner 6e0f938e65 New beacon config, redone passwords 2026-07-25 11:45:17 +03:00
aner 59c05832b1 New encryption key 2026-07-25 00:00:39 +03:00
aner b8eb351e6c Interim fix, no gpg key 2026-07-15 20:47:04 +03:00
aner 78afc9e5bf azos-core bump 2026-06-18 22:26:02 +03:00
aner ebdee10fba Bump azos-core, configuration limit. 2026-06-18 21:05:46 +03:00
aner 890f39079b Updated skills and lock and lichess 2026-06-17 07:36:21 +03:00
aner e0a5891cd8 Bump azos-core 2026-06-08 22:01:51 +03:00
aner 7f27339312 Bump azos-core: org-roam-mcp direct SQLite writes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 22:55:40 +03:00
aner bd64633454 Bump azos-core: org-roam-mcp fork with db-sync fix
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 22:47:49 +03:00
aner 7c187d1c3d Updated azos-core and org roam stuff. 2026-06-07 08:01:40 +03:00
aner 1adc0439ea Updated roam backup 2026-06-07 01:27:09 +03:00
aner 2254fd419d Claude as preffered agent, azos-core bump. 2026-06-07 01:18:12 +03:00
aner cf976ae14a Claude memory, second brain, skills 2026-06-07 00:48:20 +03:00
aner c928f5f748 Update lock, more azos-core 2026-06-03 23:49:41 +03:00
aner 8d0f541fbe WTTR 2026-06-02 10:49:52 +03:00
aner c0f72be869 Beacon config, beacon integration into emacs 2026-06-01 11:43:42 +03:00
aner e271872cae Qutebrowser fix, attic setup, beacon usb live setup 2026-05-30 13:22:18 +03:00
aner 27fafdc111 Better suite dependencies 2026-05-28 22:59:22 +03:00
aner fcb7e0b884 Refractor to dendritic approach. 2026-05-24 19:24:38 +03:00
aner d4dee59413 Package lint inclusion 2026-05-24 17:22:54 +03:00
aner cf57b3eb4a Bugfix for dashboard 2026-05-19 22:44:11 +03:00
aner e5bf9c0a41 Updated 2026-05-17 11:48:46 +03:00
aner 00f62d61eb Fix 2026-05-17 11:23:27 +03:00
aner c4066da976 New azos-core 2026-05-12 23:33:55 +03:00
aner 6751554982 Better exfat support 2026-05-12 10:28:18 +03:00
aner 83c279ded4 Moved autmounting to core 2026-05-12 09:01:11 +03:00
aner f89e04d898 Fix even more things 2026-05-11 15:02:51 +03:00
aner a89f5ca015 Added hl-line externally plus automount 2026-05-11 14:50:45 +03:00
aner 9780d69f25 Updated azos core for safety. 2026-05-08 23:20:11 +03:00
aner 4cd4114f7c Send us back a little bit - I just exernalized evil hl line. 2026-05-08 23:15:15 +03:00
aner ea550d2e8c Updated azos-core, nextcloud client, better zoom for qutebrowser 2026-05-04 14:52:08 +03:00
aner 29a351845b Updated azos-core, cladav sync, arch64 emulation 2026-04-29 20:21:48 +03:00
aner 2d72c3e7df Fixed flake lock submodule things 2026-04-26 00:13:20 +03:00
aner 315e73c63c Fixed git config. 2026-04-25 21:59:10 +03:00
aner 8a8e63b095 Fixed shell handling 2026-04-25 21:40:20 +03:00
aner b68932f516 Fixed submodule handling 2026-04-25 21:39:54 +03:00
93 changed files with 2719 additions and 1199 deletions
+120 -56
View File
@@ -22,9 +22,9 @@ nix build .#formatter.x86_64-linux
### Deploying
```bash
# Rebuild the azos environment for lauretta (this machine) — use this when asked to "rebuild"
sudo nixos-rebuild switch --flake '.?submodules=1#lauretta'
nix flake update # Update all inputs
nix flake lock --update-input X # Update specific input
azos-lauretta-update
nix flake update --flake '.?submodules=1' # Update all inputs
nix flake lock --flake '.?submodules=1' --update-input X # Update specific input
```
> **Note**: When the user asks to "rebuild", they mean rebuilding the lauretta environment with the command above — NOT building the VM.
@@ -70,76 +70,111 @@ emacsclient -c -e '(switch-to-buffer (get-buffer-create "test"))'
emacsclient -e '(message "hello")'
```
## Code Style Guidelines
## Repository Structure
### File Organization
- **Home-manager modules**: `modules/home-manager/<name>.nix`
- **NixOS modules**: `modules/nixos/<name>.nix`
- **Imports**: All modules in `modules/home-manager/default.nix` and `modules/nixos/default.nix` (alphabetical order)
- **Home config**: `home-manager/home.nix` - main home-manager user config
- **Custom packages**: `pkgs/` - custom package definitions
- **Overlays**: `overlays/` - package overlays (addpkgs, modifications, unstable-packages)
```
azos/
├── flake.nix # Main flake
├── home-manager/home.nix # Home-manager entry point; manually imports all modules
├── _machines/ # Per-machine NixOS configs (pass suiteModules as specialArgs)
├── nixos/ # NixOS system configs (configuration.nix, configuration-vm.nix, etc.)
├── features/ # Machine-specific home-manager features (auto-discovered)
├── overlays/ # Package overlays
├── shells/ # Dev shells
└── azos-core/ # Shared feature library (git submodule)
├── flake.nix
├── features/ # Shared/reusable features (auto-discovered)
├── overlays/ # Core overlays
└── _lib/ # Module schema helpers
```
Features are auto-discovered by `import-tree` — no central imports file. Each feature's
`default.nix` registers itself via `config.flake.modules.homeManager.<name>`. Modules are
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`
- Machine-specific → `azos/features/<name>/default.nix`
## Code Style Guidelines
### Nix Module Template
All features follow this flake-parts registration pattern:
```nix
{
{...}: {
config.flake.modules.homeManager.<name> = {
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.<module-name>.enable;
in {
options.azos.<module-name>.enable = lib.mkOption {
default = true;
}: {
options.azos.<name>.enable = lib.mkOption {
default = false;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [ pkg1 pkg2 ];
config = lib.mkIf config.azos.<name>.enable {
home.packages = with pkgs; [pkg1 pkg2];
};
};
}
```
### Naming Conventions
- Option paths: `config.azos.<module-name>.<option>`
- Enable option: `enable` (bool), default `true`
- Module names: kebab-case (`libreoffice.nix`, `git-config.nix`)
- Option names: may differ from filename (e.g., `git.nix` uses `azos.git-config`)
- Variables: kebab-case (`isEnabled`)
- Option paths: `azos.<module-name>.<option>`
- Module names: kebab-case (`git-config`, `claude-memory`)
- Feature directories: kebab-case, one `default.nix` per feature
- Variables: kebab-case
### Formatting
- Indentation: 2 spaces
- Let bindings: multi-line with `isEnabled` on its own line
- Packages: Use `with pkgs; [ ... ]` syntax
- Packages: `with pkgs; [pkg1 pkg2]` (no trailing semicolon inside list)
### Imports
- Always include `{...}` for module args
- Standard args: `lib, config, pkgs, ...`
- NixOS config args: `inputs, outputs, lib, config, pkgs, ...`
- Use `./relative/path.nix` for local imports
- Always include `{...}` for the outer module args
- Inner module args: `lib, config, pkgs, ...`
### Option Patterns
- Boolean enable: `lib.mkOption { default = true; example = true; type = lib.types.bool; }`
- Conditional config: `lib.mkIf isEnabled { ... }`
- User-overridable: `lib.mkDefault`
- Boolean enable: `lib.mkOption { default = false; example = true; type = lib.types.bool; }`
- Conditional config: `lib.mkIf config.azos.<name>.enable { ... }`
- Auto-enable a dependency: `azos.<dep>.enable = lib.mkDefault true;`
- Force value: `lib.mkForce` (sparingly)
### Extensible Options
For options that multiple modules should be able to contribute to, use attrset options:
```nix
options.azos.<name>.things = lib.mkOption {
default = {};
type = lib.types.attrsOf lib.types.path; # or lines, str, etc.
};
```
Any module can then add entries without modifying the owning feature. Examples in use:
- `azos.claude.globalSkills` — attrset of skill name → markdown file path
- `azos.claude.globalMdSections` — attrset of key → markdown string (merged into `~/.claude/CLAUDE.md`)
### Home-Manager vs NixOS
- Home-manager: `home.packages`, `home.file`, `programs.<program>`
- NixOS: `config.services`, `environment.systemPackages`
### Deploying Files
- Static file: `home.file."dest".source = ./file;`
- Generated text: `home.file."dest".text = "...";`
- Runtime script (e.g. merging JSON at activation): `home.activation.<name> = lib.hm.dag.entryAfter ["writeBoundary"] ''...'';`
### Suites System
Modules are organized into suites (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
- Enable in `home-manager/home.nix` with `azos.suites.<name>.enable = true;`
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
The machine suite (`azos.suites.lauretta`) enables the relevant suites for this machine.
### Specializations
NixOS supports specializations for alternative configurations:
@@ -158,33 +193,62 @@ specialisation = {
## Adding New Modules
1. Create file in `modules/home-manager/` or `modules/nixos/`
2. Add import to `modules/<type>/default.nix` (alphabetical)
3. Use module template above
1. Create `features/<name>/default.nix` (machine-specific) or `azos-core/features/<name>/default.nix` (shared)
2. Use the module template above
3. Add `suiteModules.homeManager.<name>` to the imports list in `home-manager/home.nix`
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>/
> ```
## Claude Integration
Claude Code is integrated with this environment via several azos-core features:
| 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 |
### Adding Claude skills or standing instructions
From any module — no need to touch azos-core:
```nix
azos.claude.globalSkills.my-skill = ./skills/my-skill.md;
azos.claude.globalMdSections.my-rule = ''
# My Rule
Always do X when Y.
'';
```
See `azos-core/features/claude-skills/README.md` for full documentation.
## Key Files
| File | Purpose |
|------|---------|
| `flake.nix` | Main flake, defines systems, overlays |
| `modules/home-manager/default.nix` | Home-manager imports |
| `modules/nixos/default.nix` | NixOS imports |
| `home-manager/home.nix` | User home-manager config, enables suites |
| `nixos/configuration.nix` | Lauretta laptop config |
| `flake.nix` | Main flake, defines systems and overlays |
| `home-manager/home.nix` | Home-manager entry point, imports all modules |
| `_machines/lauretta.nix` | Lauretta machine config, passes `suiteModules` as specialArgs |
| `nixos/configuration.nix` | Lauretta NixOS system config |
| `nixos/configuration-vm.nix` | Test VM config |
| `overlays/` | Custom package overlays |
| `pkgs/` | Custom packages |
| `azos-core/` | Shared modules (submodule) |
| `overlays/` | Package overlays |
| `azos-core/` | Shared feature library (git submodule) |
| `azos-core/features/` | Shared 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 |
## Common Tasks
**Add system package**: Edit NixOS module, add to `environment.systemPackages = with pkgs; [ pkg ]`
**Add system package**: Edit a NixOS feature, add to `environment.systemPackages = with pkgs; [ pkg ]`
**Add home-manager package**: Edit module, add to `home.packages = with pkgs; [ pkg ]`
**Add home-manager package**: Edit a feature's config block, add to `home.packages = with pkgs; [ pkg ]`
**Add system service**: Edit NixOS module, add under `services.<service>`
**Add system service**: Edit a NixOS feature, add under `services.<service>`
**Add custom package**: Create file in `pkgs/`, add to `pkgs/default.nix`, use via overlay
**Add custom package**: Add to `azos-core/overlays/` via `config.flake.overlayPkgs.<name>`, then use as `pkgs.<name>`
**Access unstable packages**: Use `pkgs.unstable.<package>` (via unstable-packages overlay)
Symlink
+1
View File
@@ -0,0 +1 @@
AGENTS.md
+17
View File
@@ -0,0 +1,17 @@
{
config,
inputs,
...
}: {
config.flake.nixosConfigurations.beacon-wsl = inputs.nixpkgs.lib.nixosSystem {
specialArgs = {
inherit inputs;
outputs = config.flake;
suiteModules = config.flake.modules;
};
modules = [../nixos/configuration-beacon-wsl.nix];
};
config.flake.packages.x86_64-linux.beacon-wsl-tarball =
config.flake.nixosConfigurations.beacon-wsl.config.system.build.tarballBuilder;
}
+17
View File
@@ -0,0 +1,17 @@
{
config,
inputs,
...
}: {
config.flake.nixosConfigurations.beacon = inputs.nixpkgs.lib.nixosSystem {
specialArgs = {
inherit inputs;
outputs = config.flake;
suiteModules = config.flake.modules;
};
modules = [../nixos/configuration-beacon.nix];
};
config.flake.packages.x86_64-linux.beacon-image =
config.flake.nixosConfigurations.beacon.config.system.build.image;
}
+14
View File
@@ -0,0 +1,14 @@
{
config,
inputs,
...
}: {
config.flake.nixosConfigurations.lauretta = inputs.nixpkgs.lib.nixosSystem {
specialArgs = {
inherit inputs;
outputs = config.flake;
suiteModules = config.flake.modules;
};
modules = [../nixos/configuration.nix];
};
}
+14
View File
@@ -0,0 +1,14 @@
{
config,
inputs,
...
}: {
config.flake.nixosConfigurations.vm = inputs.nixpkgs.lib.nixosSystem {
specialArgs = {
inherit inputs;
outputs = config.flake;
suiteModules = config.flake.modules;
};
modules = [../nixos/configuration-vm.nix];
};
}
+33
View File
@@ -0,0 +1,33 @@
{...}: {
config.flake.modules.nixos.attic = {
lib,
config,
pkgs,
...
}: {
options.azos.attic.enable = lib.mkOption {
default = false;
example = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.attic.enable {
environment.systemPackages = [pkgs.attic-client];
nix.settings = {
extra-substituters = ["https://attic.zakobar.com/main"];
extra-trusted-public-keys = ["main:9SZt/6plBU7jjQzz90J7O011I13hmJvOMYouxNqExNQ="];
netrc-file = "/etc/nix/attic-netrc";
};
environment.etc."nix/attic-netrc" = {
mode = "0600";
text = ''
machine attic.zakobar.com
login token
password eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjIwOTU3MDk0NDEsIm5iZiI6MTc4MDEzMzQ0MSwic3ViIjoibml4b3MtY2xpZW50IiwiaHR0cHM6Ly9qd3QuYXR0aWMucnMvdjEiOnsiY2FjaGVzIjp7IioiOnsiciI6MX19fX0.lqT_m2otoJQtA_AeJu62NT87u8cMWxgN-JhqtEtZ88s
'';
};
};
};
}
+41
View File
@@ -0,0 +1,41 @@
{...}: {
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;
};
};
}
+15
View File
@@ -0,0 +1,15 @@
{...}: {
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"];
};
};
}
+22
View File
@@ -0,0 +1,22 @@
{...}: {
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
'';
};
};
};
}
+39
View File
@@ -0,0 +1,39 @@
{...}: {
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"];
};
};
}
+14
View File
@@ -0,0 +1,14 @@
{
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
];
}
+30
View File
@@ -0,0 +1,30 @@
#+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
+3
View File
@@ -0,0 +1,3 @@
{...}: {
config.flake.overlayPkgs.azos-emacs-chess = pkgs: pkgs.localEmacsPkg ./_pkg.nix;
}
+18
View File
@@ -0,0 +1,18 @@
{...}: {
config.flake.modules.homeManager.claude = {
lib,
config,
pkgs,
...
}: {
options.azos.claude.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
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;
};
};
}
+80
View File
@@ -0,0 +1,80 @@
{...}: {
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 needs INSIDE_EMACS set, but NOT on gpg-agent itself:
# gpg-agent calls gnupg_unsetenv("INSIDE_EMACS") on its own process at
# startup (agent/gpg-agent.c, both supervised and daemon mode) -- by
# design, it expects this to arrive per-connection from the CLIENT,
# not statically from its own daemon environment. Each gpg/git
# invocation reads INSIDE_EMACS from its own env and forwards it to
# gpg-agent via "OPTION putenv=INSIDE_EMACS=..." (common/asshelp.c:
# send_pinentry_environment); gpg-agent stores that per-session and
# copies it into the forked pinentry's actual env for that one
# request (agent/call-pinentry.c:atfork_core). So the var has to be
# set on whatever shell/process runs git or gpg -- a prior attempt
# at this set it on gpg-agent.service's systemd Environment instead,
# which gpg-agent silently discards before it can ever be read.
#
# Only safe to set globally 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).
home.sessionVariables = lib.mkIf config.azos.suites.exwm.enable {
INSIDE_EMACS = "t";
};
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
View File
@@ -0,0 +1 @@
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
View File
@@ -0,0 +1 @@
37710BAB5F6DC9878A450CDA8A4661D932C04ACB
+32
View File
@@ -0,0 +1,32 @@
{...}: {
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;
};
};
}
+2
View File
@@ -0,0 +1,2 @@
[*git.zakobar.com*]
target=zakobar.com
+52
View File
@@ -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;
};
};
};
}
+56
View File
@@ -0,0 +1,56 @@
{...}: {
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
'')
];
};
};
}
+16
View File
@@ -0,0 +1,16 @@
{...}: {
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];
};
};
}
+191
View File
@@ -0,0 +1,191 @@
{...}: {
config.flake.modules.homeManager.homey-alert = {
lib,
config,
pkgs,
...
}: {
options.azos.homey-alert.enable = lib.mkOption {
default = false;
example = true;
type = lib.types.bool;
};
options.azos.homey-alert.longCommandThresholdSeconds = lib.mkOption {
default = 300;
example = 600;
type = lib.types.int;
description = ''
Installs a global PreToolUse hook (matcher "Bash") in
~/.claude/settings.json that transparently wraps every Bash command
Claude Code runs: it times the command, and if it ran at or past
this threshold, fires homey-alert with the elapsed time and exit
code once it finishes. Commands that finish faster are unaffected.
This exists so long-running commands (big rclone transfers, builds)
notify automatically even when Claude never thinks to chain
homey-alert itself, and even if the harness moves the command to
the background partway through.
The hook only adds timing + notification around the command as
given -- it does not change permissions, so normal confirmation
prompts for risky commands still happen exactly as before.
'';
};
config = lib.mkIf config.azos.homey-alert.enable (let
threshold = toString config.azos.homey-alert.longCommandThresholdSeconds;
# Rewrites every Bash tool command to time itself and fire
# homey-alert if it ran at or past the threshold. The original
# command is spliced in verbatim (not eval'd from an encoded blob)
# so it stays human-readable in any permission prompt, and it's run
# in a subshell so the wrapper's own timing/notify code still runs
# even if the original command uses `set -e` or calls `exit`.
# permissionDecision is deliberately omitted from the hook's JSON
# output: supplying it (even as "allow") would bypass the normal
# confirmation flow, which must stay untouched here.
bashNotifyHook = pkgs.writeShellApplication {
name = "azos-bash-notify-hook";
runtimeInputs = [pkgs.jq];
text = ''
input=$(cat)
command=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null) || command=""
if [ -z "$command" ]; then
echo '{}'
exit 0
fi
wrapped="__azos_notify_start=\$(date +%s)
(
$command
)
__azos_notify_rc=\$?
__azos_notify_dur=\$(( \$(date +%s) - __azos_notify_start ))
if [ \"\$__azos_notify_dur\" -ge ${threshold} ]; then
__azos_notify_tag=\$([ \"\$__azos_notify_rc\" -eq 0 ] && echo white_check_mark || echo x)
homey-alert -t \"Long command\" -T \"\$__azos_notify_tag\" \"Finished in \$__azos_notify_dur s (exit \$__azos_notify_rc)\" >/dev/null 2>&1 || true
fi
exit \"\$__azos_notify_rc\""
jq -n --arg cmd "$wrapped" \
'{hookSpecificOutput: {hookEventName: "PreToolUse", updatedInput: {command: $cmd}}}' 2>/dev/null || echo '{}'
'';
};
in {
azos.claude.globalSkills.notify = ./notify-skill.md;
home.activation.configureBashNotifyHook = lib.hm.dag.entryAfter ["writeBoundary"] ''
SETTINGS_JSON="$HOME/.claude/settings.json"
HOOK_ENTRY=$(${pkgs.jq}/bin/jq -n \
--arg cmd "${bashNotifyHook}/bin/azos-bash-notify-hook" \
'{
matcher: "Bash",
hooks: [{type: "command", command: $cmd}]
}')
if [ -f "$SETTINGS_JSON" ]; then
tmp=$(mktemp)
${pkgs.jq}/bin/jq --argjson entry "$HOOK_ENTRY" \
'.hooks.PreToolUse = ((.hooks.PreToolUse // []) | map(select(.matcher != $entry.matcher))) + [$entry]' \
"$SETTINGS_JSON" > "$tmp" && mv "$tmp" "$SETTINGS_JSON"
else
${pkgs.jq}/bin/jq -n --argjson entry "$HOOK_ENTRY" \
'{hooks: {PreToolUse: [$entry]}}' > "$SETTINGS_JSON"
fi
'';
home.packages = [
bashNotifyHook
(pkgs.writeShellApplication {
name = "homey-alert";
runtimeInputs = with pkgs; [curl pass libnotify];
text = ''
usage() {
cat <<EOF
Usage: homey-alert [OPTIONS] [MESSAGE]
Send a notification to the homey ntfy server (ntfy.zakobar.com/alerts),
and (when run locally, not with --print-remote) also via notify-send --
the ntfy server isn't always reachable, so the local desktop notification
always fires too, independent of whether the ntfy push succeeds.
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[@]}" || true
# Local desktop notification, independent of the ntfy push above --
# the server isn't always up, so this always fires as a fallback.
URGENCY="normal"
case "$PRIORITY" in
min|low) URGENCY="low" ;;
high|urgent) URGENCY="critical" ;;
esac
notify-send -u "$URGENCY" "''${TITLE:-homey-alert}" "$MESSAGE" || true
fi
'';
})
];
});
};
}
+65
View File
@@ -0,0 +1,65 @@
# 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.
## Background / async tasks (Claude Code specifically)
If you (the agent) are running a command via Bash that could take longer than
~10 minutes — large rclone/rsync transfers, builds, training runs — and it
might run in the background (either because you set `run_in_background`, or
because the harness auto-backgrounds it after its timeout), chain the
`homey-alert` call **into that same command string from the start**. Don't
wait until you notice it's taking a while and then think about notifying —
by then the command is already running without it, and the harness's own
"task finished" signal only reaches this session, not Aner's phone. Chaining
upfront is the only way to get a real ntfy push the moment it actually
finishes:
```bash
rclone copy ... ; homey-alert -t "Backup" -p default -T "$([ $? -eq 0 ] && echo white_check_mark || echo x)" "rclone copy finished (exit $?)"
```
## 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`
+38
View File
@@ -0,0 +1,38 @@
{...}: {
config.flake.modules.homeManager.hyprland = {
lib,
config,
pkgs,
...
}: {
options.azos.suites.hyprland.enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
config = lib.mkIf config.azos.suites.hyprland.enable {
azos.suites.base.enable = lib.mkDefault true;
home.packages = with pkgs; [
hyprland
waybar
wofi
kitty
grim
slurp
swappy
networkmanagerapplet
xwayland
];
azos.emacs.enabledSuites = ["azos-emacs-hyprland"];
azos.emacs.pkgs = [pkgs.azos-emacs-hyprland];
azos.emacs.emacspkg = pkgs.emacs-pgtk;
home.file.".login.sh" = {
text = ''
#!/usr/bin/env bash
${pkgs.hyprland}/bin/Hyprland
'';
executable = true;
};
};
};
}
@@ -6,7 +6,7 @@
orgTrivialBuild {
pname = "azos-emacs-hyprland";
version = "0.1.6";
src = ./elisp/azos-emacs-hyprland.org;
src = ./config.org;
packageRequires = with epkgs; [
pkgs.azos-emacs-base
];
+3
View File
@@ -0,0 +1,3 @@
{...}: {
config.flake.overlayPkgs.azos-emacs-hyprland = pkgs: pkgs.localEmacsPkg ./_pkg.nix;
}
+16
View File
@@ -0,0 +1,16 @@
{...}: {
config.flake.modules.homeManager.kubernetes = {
lib,
config,
pkgs,
...
}: {
options.azos.kubectl.enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
config = lib.mkIf config.azos.kubectl.enable {
home.packages = with pkgs; [kubectl kubernetes-helm velero];
};
};
}
+22
View File
@@ -0,0 +1,22 @@
{...}: {
config.flake.modules.homeManager.lauretta = {
lib,
config,
pkgs,
...
}: {
options.azos.suites.lauretta.enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
config = lib.mkIf (config.azos.suites.lauretta.enable) {
azos.suites.exwm.enable = lib.mkDefault true;
azos.suites.editor.enable = lib.mkDefault true;
azos.suites.dev.enable = lib.mkDefault true;
azos.emacs.enabledSuites = ["azos-emacs-lauretta"];
azos.emacs.pkgs = [pkgs.azos-emacs-lauretta];
home.packages = with pkgs; [nix-search-cli];
};
};
}
@@ -6,7 +6,7 @@
orgTrivialBuild {
pname = "azos-emacs-lauretta";
version = "0.1.6";
src = ./elisp/azos-emacs-lauretta.org;
src = ./config.org;
packageRequires = with epkgs; [
pkgs.azos-emacs-base
pkgs.azos-emacs-dev
@@ -15,5 +15,8 @@ orgTrivialBuild {
pkgs.azos-emacs-station
agent-shell
elfeed-protocol
org-caldav
pinentry
];
}
+556
View File
@@ -0,0 +1,556 @@
#+title: Aner's Emacs Lauretta Configuration
#+property: header-args :results silent
* Base setup
** Require
#+begin_src emacs-lisp
(require 'azos-emacs-base)
(require 'azos-emacs-dev)
(require 'azos-emacs-editor)
(require 'azos-emacs-exwm)
(require 'azos-emacs-station)
#+end_src
* Lauretta specific
** Weather
#+begin_src emacs-lisp
(setq wttrin-default-cities '("Meitar, Israel"))
#+end_src
** LLM
#+begin_src emacs-lisp
(setq
gptel-model 'openai/gpt-oss-120b
gptel-backend
(gptel-make-openai "Groq"
:host "api.groq.com"
:endpoint "/openai/v1/chat/completions"
:stream t
:key "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"
:models '(openai/gpt-oss-120b)))
#+end_src
** GPG Pinentry
gpg-agent is configured with =pinentry.package = pkgs.pinentry-emacs=, which
needs a running Emacs-side pinentry service to hand it prompts over a
loopback socket. Without =pinentry-start=, the pinentry-emacs binary can't
reach Emacs and falls back to the curses pinentry (visible as a TUI prompt
when committing from vterm) or hangs entirely (as seen from Magit).
#+begin_src emacs-lisp
(require 'pinentry)
(pinentry-start)
#+end_src
** Agent Shell
#+begin_src emacs-lisp
(setq agent-shell-preferred-agent-config 'claude-code)
#+end_src
** Headphones
#+begin_src emacs-lisp
(defun azos/connect-headphones (&optional arg)
"Connect to headphones via bluetoothctl.
Without prefix ARG, connects to the first device in the list.
With prefix ARG, prompts to choose from all configured devices."
(interactive "P")
(if (not arg)
(start-process-shell-command "connect-headphones" nil "azos-connect-headphones")
(let* ((raw (shell-command-to-string "azos-connect-headphones --list"))
(lines (split-string (string-trim raw) "\n" t))
(choices (mapcar (lambda (line)
(let ((space (string-match " " line)))
(cons (substring line (1+ space))
(substring line 0 space))))
lines))
(name (completing-read "Connect headphones: " (mapcar #'car choices) nil t))
(mac (cdr (assoc name choices))))
(start-process-shell-command "connect-headphones" nil
(concat "azos-connect-headphones --mac " mac)))))
(define-key azos/global-minor-mode/open-keymap
(kbd "h") 'azos/connect-headphones)
#+end_src
** Tab bar setup
#+begin_src emacs-lisp
(when (fboundp #'azos/bat/enable-tab-display)
(azos/bat/enable-tab-display))
(when (fboundp #'azos/network/enable-tab-display)
(azos/network/enable-tab-display))
(when (fboundp #'azos/audio/enable-tab-display)
(azos/audio/enable-tab-display))
#+end_src
** Nixpkgs Search
#+begin_src emacs-lisp
(defun azos/nixpkgs-parse-json-lines (str)
"Parse JSON Lines format (one JSON object per line) from STR."
(mapcar (lambda (line)
(and (not (string-empty-p line))
(string-match "^{" line)
(condition-case nil
(json-parse-string line)
(error nil))))
(split-string (string-trim str) "\n" t)))
(defun azos/nixpkgs-g (key hash)
"Get KEY from HASH table."
(when (hash-table-p hash)
(gethash key hash)))
(defun azos/nixpkgs-search ()
"Search for a nixpkgs package and show its info."
(interactive)
(let* ((pattern (read-string "Search pattern: "))
(json-output (shell-command-to-string
(format "nix-search --json --max-results 50 '%s'" pattern)))
(results (and json-output
(not (string-empty-p json-output))
(azos/nixpkgs-parse-json-lines json-output)))
(packages (delq nil (mapcar (lambda (item)
(let ((pname (azos/nixpkgs-g "package_pname" item)))
(when pname
(cons pname item))))
results))))
(if packages
(let* ((selected (completing-read "Package: " (mapcar #'car packages) nil t))
(package-data (cdr (assoc-string selected packages))))
(let ((buf (get-buffer-create "*nixpkgs-package-info*")))
(with-current-buffer buf
(erase-buffer)
(if package-data
(progn
(insert (format "Package: %s\n\n" selected))
(insert (format "Version: %s\n" (or (azos/nixpkgs-g "package_pversion" package-data) "unknown")))
(insert (format "Attr: %s\n" (or (azos/nixpkgs-g "package_attr_name" package-data) "unknown")))
(insert (format "\nDescription: %s\n"
(or (azos/nixpkgs-g "package_description" package-data) "none")))
(let ((programs (azos/nixpkgs-g "package_programs" package-data)))
(when (vectorp programs)
(insert (format "\nPrograms: %s\n"
(mapconcat #'identity (append programs nil) " ")))))
(let ((homepage (let ((h (azos/nixpkgs-g "package_homepage" package-data)))
(when (vectorp h) (aref h 0)))))
(when homepage
(insert (format "\nHomepage: %s\n" homepage))))
(let ((licenses (azos/nixpkgs-g "package_license" package-data)))
(when (vectorp licenses)
(insert (format "\nLicense: %s\n"
(mapconcat (lambda (l)
(or (azos/nixpkgs-g "fullName" l) ""))
(append licenses nil) ", ")))))
(insert (format "Package: %s\n\n" selected)
"\nNo additional info available.")))
(goto-char (point-min)))
(display-buffer buf)))
(message "No packages found matching '%s'" pattern))))
(define-key azos/global-minor-mode/open-keymap
(kbd "n") 'azos/nixpkgs-search)
#+end_src
** CalDAV / Org Sync
#+begin_src emacs-lisp
(require 'org-caldav)
(defvar azos/lauretta/nextcloud-user "aner"
"Nextcloud username for CalDAV sync.")
(setq org-caldav-url "https://nextcloud.zakobar.com/remote.php/dav/calendars/1ddd03a6-4c2d-103c-9f7b-27b20313341d"
org-caldav-calendar-id "personal"
org-caldav-inbox "~/org/caldav-inbox.org"
org-caldav-files '("~/org/todo.org")
org-icalendar-timezone "Asia/Jerusalem")
(defun azos/caldav-sync ()
"Sync org-caldav with Nextcloud calendar, reading password from pass."
(interactive)
(let* ((password (string-trim
(shell-command-to-string
"pass zakobar.com | head -1")))
(url-http-real-basic-auth-storage
(list (list "nextcloud.zakobar.com:443"
(cons azos/lauretta/nextcloud-user password)))))
(org-caldav-sync)))
(define-key azos/global-minor-mode/open-keymap
(kbd "C") 'azos/caldav-sync)
#+end_src
** Elfeed Nextcloud News Sync
[[https://github.com/fasheng/elfeed-protocol][elfeed-protocol]] adds an =owncloud+= protocol that speaks the
ownCloud News API, which the Nextcloud News app also implements, so
it syncs elfeed's feed list and read/unread state with the Nextcloud
News app instead of just reading a local feed list one-way.
Reuses the same =zakobar.com= pass entry as the CalDAV sync above,
since it's the same Nextcloud account.
As of elfeed-protocol 1.0.0 (paired with elfeed >= 4.0.0), the
credentials go directly on =elfeed-feeds= — the older
=elfeed-protocol-feeds= variable is dead: setting it only logs a
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.
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
with =make-process= instead keeps the command loop alive, so
=pinentry-emacs= can prompt normally; =elfeed-feeds= and
=elfeed-protocol-enable= are only set once the process sentinel
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
(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")
:noquery t
:filter (lambda (proc chunk)
(process-put proc 'output (concat (or (process-get proc 'output) "") chunk)))
:sentinel (lambda (proc _event)
(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
Many feeds only publish a short excerpt. elfeed >= 4.0 has built-in
commands for this — =f= (=elfeed-show-fetch-link=) fetches the
linked page and appends it to the show buffer, =R=
(=elfeed-show-readable=) strips the fetched page down to
article text via eww's readability scorer — and both can run
automatically via =elfeed-show-update-hook=, gated on a feed's
=:fetch-link=/=:readable= metadata.
That gating keys off an exact match on the feed's URL/id in
=elfeed-feeds=, which breaks for ownCloud/Nextcloud subfeeds:
elfeed-protocol synthesizes a per-subfeed id of the form
=<host-url>::<subfeed-url>= that never appears in =elfeed-feeds=
(only the bare host URL does), so the metadata lookup always misses
and the stock hooks never fire. Replacing them with unconditional
versions makes full-text + readable-mode the default for every
entry instead.
#+begin_src emacs-lisp
(defun azos/elfeed-show-auto-readable ()
"Like `elfeed-show-auto-readable', but unconditional."
(unless (local-variable-p 'elfeed-transform-html-functions)
(let ((inhibit-message t))
(elfeed-show-readable))))
(defun azos/elfeed-show-auto-fetch-link ()
"Like `elfeed-show-auto-fetch-link', but unconditional."
(elfeed-show-fetch-link))
(setq elfeed-show-update-hook
(list #'azos/elfeed-show-auto-readable #'azos/elfeed-show-auto-fetch-link))
#+end_src
** Lichess
=lichess.org= in pass already holds the website login password, and
lichess.el's =auth-source-pass= lookup resolves by bare hostname, so
the API token can't live under that same entry without colliding.
Store it separately under =lichess.org/token= (=pass insert
lichess.org/token=) and set the =lichess-token= variable directly —
=lichess-token= (the function) checks this variable before falling
back to =auth-source=, so this takes precedence without disturbing
the existing =lichess.org= entry or its consumers (e.g. browserpass).
=shell-command-to-string= merges stderr into its return value, so any
gpg/pass warning on stderr (e.g. an unsafe-permissions notice) would
otherwise get concatenated onto the token and break auth silently.
Redirect stderr away so only the token itself is ever captured.
#+begin_src emacs-lisp
(setq lichess-token
(string-trim (shell-command-to-string "pass lichess.org/token 2>/dev/null | head -1")))
#+end_src
=gpg-agent= may not have a cached passphrase yet when the =setq= above
runs at Emacs startup, in which case =pass= fails silently (stderr is
redirected away) and =lichess-token= is left as =""=. Retry the fetch
lazily right before each actual use, so a cold agent at startup
doesn't permanently wedge the token for the rest of the session.
#+begin_src emacs-lisp
(with-eval-after-load 'lichess
(advice-add 'lichess-token :before
(lambda ()
(when (string-empty-p lichess-token)
(setq lichess-token
(string-trim (shell-command-to-string "pass lichess.org/token 2>/dev/null | head -1")))))))
#+end_src
** Beacon Remote Jobs
#+begin_src emacs-lisp
(defvar azos/beacon/host "aner@192.168.1.200"
"SSH connection for beacon machine.")
(defvar azos/beacon/max-logs 5
"Number of most-recent logs to keep per project on beacon.")
(defvar azos/beacon/keymap (make-sparse-keymap)
"Keymap for beacon remote job commands (M-o b prefix).")
(define-key azos/global-minor-mode/open-keymap (kbd "b") azos/beacon/keymap)
(defun azos/beacon--project-root ()
(or (and (fboundp 'projectile-project-root) (projectile-project-root))
(error "Not in a projectile project")))
(defun azos/beacon--project-name (root)
(file-name-nondirectory (directory-file-name root)))
(defun azos/beacon--list-python-files (root)
(split-string
(shell-command-to-string
(format "cd %s && git ls-files | grep '\\.py$'" (shell-quote-argument root)))
"\n" t))
(defun azos/beacon--list-executables (root)
(split-string
(shell-command-to-string
(format "cd %s && git ls-files | while IFS= read -r f; do [ -x \"$f\" ] && echo \"$f\"; done"
(shell-quote-argument root)))
"\n" t))
(defun azos/beacon--ssh-run (script)
"Pipe SCRIPT as bash to beacon over SSH stdin. Error on non-zero exit."
(with-temp-buffer
(insert script)
(let ((ret (call-process-region (point-min) (point-max) "ssh" nil t nil
azos/beacon/host "bash")))
(unless (zerop ret)
(error "Beacon SSH error (exit %d): %s" ret (string-trim (buffer-string)))))))
(defun azos/beacon--ssh-query (cmd &optional allow-exit-one)
"Run CMD on beacon, return output string. Error on failure.
With ALLOW-EXIT-ONE, exit code 1 is also treated as success (for tmux ls)."
(with-temp-buffer
(let ((ret (call-process "ssh" nil t nil azos/beacon/host cmd)))
(unless (or (zerop ret) (and allow-exit-one (= ret 1)))
(error "Beacon SSH error (exit %d): %s" ret (string-trim (buffer-string)))))
(buffer-string)))
(defun azos/beacon--dispatch (root cmd label)
"Sync ROOT to beacon and run CMD in project dir under direnv.
LABEL names the tmux session and log file."
(let* ((project-name (azos/beacon--project-name root))
(timestamp (format-time-string "%Y%m%d-%H%M%S"))
(session (format "%s-%s-%s" project-name label timestamp))
(log-name (format "%s-%s.log" label timestamp)))
(message "Syncing %s to beacon..." project-name)
(azos/beacon--ssh-run (format "mkdir -p ~/beacon-projects/%s\n" project-name))
(with-temp-buffer
(let ((ret (call-process-shell-command
(format "bash -c %s"
(shell-quote-argument
(format "cd %s && git ls-files | rsync -avz --files-from=- . %s:~/beacon-projects/%s/"
root azos/beacon/host project-name)))
nil t nil)))
(unless (zerop ret)
(error "Beacon rsync failed (exit %d): %s" ret (string-trim (buffer-string))))))
(azos/beacon--ssh-run
(format "RDIR=~/beacon-projects/%s
LDIR=~/beacon-logs/%s
mkdir -p \"$LDIR\"
direnv allow \"$RDIR\"
tmux new-session -d -s %s -- bash -c \"cd $RDIR && direnv exec . %s 2>&1 | tee $LDIR/%s\"
"
project-name project-name
(shell-quote-argument session)
cmd log-name))
(azos/beacon--ssh-run
(format "find ~/beacon-logs/%s -name '*.log' -printf '%%T@ %%p\\n' 2>/dev/null | sort -rn | tail -n +%d | cut -d' ' -f2- | xargs -r rm -f\n"
project-name (1+ azos/beacon/max-logs)))
(message "Beacon job started: %s" session)))
(defun azos/beacon/run-python ()
"Sync current project to beacon and run a selected Python file."
(interactive)
(let* ((root (azos/beacon--project-root))
(files (azos/beacon--list-python-files root))
(file (completing-read "Python file: " files nil t))
(label (file-name-sans-extension (file-name-nondirectory file))))
(azos/beacon--dispatch root (format "python3 %s" file) label)))
(defun azos/beacon/run-exec ()
"Sync current project to beacon and run a selected executable."
(interactive)
(let* ((root (azos/beacon--project-root))
(files (azos/beacon--list-executables root))
(file (completing-read "Executable: " files nil t))
(label (file-name-sans-extension (file-name-nondirectory file))))
(azos/beacon--dispatch root (format "./%s" file) label)))
(defun azos/beacon/run-command ()
"Sync current project to beacon and run an arbitrary command."
(interactive)
(let* ((root (azos/beacon--project-root))
(cmd (read-string "Command: "))
(label (replace-regexp-in-string
"[^a-zA-Z0-9-]" "-"
(substring cmd 0 (min 20 (length cmd))))))
(azos/beacon--dispatch root cmd label)))
(defun azos/beacon/list-jobs ()
"Show all running beacon tmux sessions."
(interactive)
(async-shell-command
(format "ssh %s 'tmux ls 2>/dev/null || echo \"No jobs running\"'" azos/beacon/host)
"*beacon-jobs*"))
(defun azos/beacon/kill-job ()
"Kill a beacon tmux session selected interactively."
(interactive)
(let* ((output (azos/beacon--ssh-query "tmux ls -F '#S' 2>/dev/null" t))
(sessions (split-string (string-trim output) "\n" t))
(session (completing-read "Kill job: " sessions nil t)))
(azos/beacon--ssh-run (format "tmux kill-session -t %s\n" (shell-quote-argument session)))
(message "Killed beacon job: %s" session)))
(defun azos/beacon--tail-path (path)
(async-shell-command
(format "ssh %s %s" azos/beacon/host
(shell-quote-argument (format "tail -f %s" path)))
(format "*beacon-tail-%s*" (file-name-nondirectory path))))
(defun azos/beacon/tail-log (&optional arg)
"Tail the most recent beacon log.
With prefix ARG, prompt for a pattern then select from matches."
(interactive "P")
(let* ((sorted-output (azos/beacon--ssh-query
"find ~/beacon-logs -name '*.log' -printf '%T@ %P\n' 2>/dev/null | sort -rn | cut -d' ' -f2-"))
(logs (split-string (string-trim sorted-output) "\n" t)))
(when (null logs) (error "No logs found on beacon"))
(if (not arg)
(azos/beacon--tail-path (format "~/beacon-logs/%s" (car logs)))
(let* ((pattern (read-string "Log pattern: "))
(matches (seq-filter (lambda (l) (string-match-p pattern l)) logs))
(log (if (= (length matches) 1)
(car matches)
(completing-read "Tail log: " matches nil t))))
(azos/beacon--tail-path (format "~/beacon-logs/%s" log))))))
(define-key azos/beacon/keymap (kbd "p") #'azos/beacon/run-python)
(define-key azos/beacon/keymap (kbd "e") #'azos/beacon/run-exec)
(define-key azos/beacon/keymap (kbd "c") #'azos/beacon/run-command)
(define-key azos/beacon/keymap (kbd "l") #'azos/beacon/list-jobs)
(define-key azos/beacon/keymap (kbd "k") #'azos/beacon/kill-job)
(define-key azos/beacon/keymap (kbd "t") #'azos/beacon/tail-log)
#+end_src
** Roam Backup
=roam-backup= signs its commit with gpg, which can prompt for the YubiKey PIN
via pinentry. =async-shell-command= spawns the process with =TERM=dumb= and
no real controlling tty, so pinentry's curses fallback fails with
\"Inappropriate ioctl for device\" whenever the PIN cache has expired. Run it
in a =vterm= instead, which gives pinentry a real pty to prompt on.
#+begin_src emacs-lisp
(defun azos/roam/backup ()
"Commit and push ~/roam to the remote git repo."
(interactive)
(let ((vterm-buffer-name "*roam-backup*"))
(with-current-buffer (vterm-other-window)
(vterm-send-string "roam-backup")
(vterm-send-return))))
(define-key azos/roam-keymap (kbd "B") #'azos/roam/backup)
#+end_src
** Printing
#+begin_src emacs-lisp
(defun azos/print-buffer (&optional arg)
"Print the current buffer.
For image/PDF/doc buffers, sends the file path to lp directly.
For text buffers, uses lpr-buffer.
With C-u, interactively prompt for scaling, B&W, and orientation options."
(interactive "P")
(let* ((image-p (derived-mode-p 'image-mode 'pdf-view-mode 'doc-view-mode))
(scale (when arg (y-or-n-p "Fit to page? ")))
(bw (when arg (y-or-n-p "Black and white? ")))
(landscape (when (and arg image-p) (y-or-n-p "Landscape? ")))
(lp-opts (concat
(when scale " -o fit-to-page")
(when bw " -o print-color-mode=monochrome")
(when landscape " -o landscape"))))
(if image-p
(if buffer-file-name
(progn
(shell-command
(concat "lp" lp-opts " " (shell-quote-argument buffer-file-name)))
(message "Sent %s to printer%s%s%s."
(file-name-nondirectory buffer-file-name)
(if scale " [scaled]" "")
(if bw " [B&W]" "")
(if landscape " [landscape]" "")))
(error "Buffer has no associated file"))
(let ((lpr-switches
(append lpr-switches
(when bw '("-o print-color-mode=monochrome")))))
(lpr-buffer)))))
(define-key azos/global-minor-mode/open-keymap (kbd "P") #'azos/print-buffer)
#+end_src
* Provide
#+begin_src emacs-lisp
(provide 'azos-emacs-lauretta)
#+end_src
+3
View File
@@ -0,0 +1,3 @@
{...}: {
config.flake.overlayPkgs.azos-emacs-lauretta = pkgs: pkgs.localEmacsPkg ./_pkg.nix;
}
+19
View File
@@ -0,0 +1,19 @@
{...}: {
config.flake.modules.homeManager.leetcode = {
lib,
config,
pkgs,
...
}: {
options.azos.leetcode.enable = lib.mkOption {
default = false;
example = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.leetcode.enable {
azos.emacs.pkgs = [pkgs.azos-emacs-leetcode];
azos.emacs.enabledSuites = ["azos-emacs-leetcode"];
};
};
}
+14
View File
@@ -0,0 +1,14 @@
{
orgTrivialBuild,
epkgs,
pkgs,
}:
orgTrivialBuild {
pname = "azos-emacs-leetcode";
version = "0.1.0";
src = ./config.org;
packageRequires = with epkgs; [
pkgs.azos-emacs-base
leetcode
];
}
+29
View File
@@ -0,0 +1,29 @@
#+title: Aner's Emacs Leetcode Configuration
#+property: header-args :results silent
* Base setup
** Require
#+begin_src emacs-lisp
(require 'azos-emacs-base)
#+end_src
* Leetcode
#+begin_src emacs-lisp
(use-package leetcode
:init
(defun azos/leetcode/open ()
"Open leetcode in its dedicated \"leetcode\" tab."
(interactive)
(azos/tab-bar/open-singleton-tab "leetcode" #'leetcode))
:bind
(:map azos/global-minor-mode/open-keymap
("L" . azos/leetcode/open)))
#+end_src
* Provide
#+begin_src emacs-lisp
(provide 'azos-emacs-leetcode)
#+end_src
+3
View File
@@ -0,0 +1,3 @@
{...}: {
config.flake.overlayPkgs.azos-emacs-leetcode = pkgs: pkgs.localEmacsPkg ./_pkg.nix;
}
+126
View File
@@ -0,0 +1,126 @@
{...}: {
config.flake.modules.homeManager.mail = {
lib,
config,
pkgs,
...
}: let
default_account_params = {
realName = "Aner Zakobar";
mbsync = {
enable = true;
create = "maildir";
remove = "both";
expunge = "both";
};
msmtp.enable = true;
};
default_gmail_params =
default_account_params
// {
flavor = "gmail.com";
};
default_smtp = {
tls = {
enable = true;
useStartTls = true;
certificatesFile = "/etc/ssl/certs/ca-certificates.crt";
};
};
# mbsync's PassCmd shells out to `pass`, which needs the GPG smartcard to
# decrypt. On a timer, a missing card would otherwise surface as a
# pinentry "please insert card" prompt every run. azos-card-present is a
# silent presence check (see overlays/default.nix): skip the sync
# entirely when the card isn't there, instead of letting mbsync hit that
# prompt.
#
# New mail is detected by counting files under every account's Maildir
# "new" subdirectory before and after the sync -- that's where messages
# the server hasn't marked seen land, across all three accounts at once,
# with no need to touch mu's index. A rise in that count means mail
# actually arrived, so only then does it post a desktop notification via
# notify-send -- intentionally separate from mu4e's own "mbsync -a" (its
# mu4e-get-mail-command), which doesn't go through this script at all.
mbsyncSafe = pkgs.writeShellScript "mbsync-safe" ''
if ! ${pkgs.azos-card-present}/bin/azos-card-present; then
exit 0
fi
before=$(find "$HOME/Maildir" -path '*/new/*' -type f 2>/dev/null | wc -l)
${pkgs.isync}/bin/mbsync -a
after=$(find "$HOME/Maildir" -path '*/new/*' -type f 2>/dev/null | wc -l)
new=$((after - before))
if [ "$new" -gt 0 ]; then
label="new message"
[ "$new" -ne 1 ] && label="new messages"
${pkgs.libnotify}/bin/notify-send "Mail" "$new $label"
fi
'';
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;
};
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
'';
accounts.email.accounts = {
gmail =
default_gmail_params
// {
address = "anerisgreat@gmail.com";
userName = "anerisgreat";
passwordCommand = "pass mbsync/anerisgreat@gmail.com | head -1";
};
bgu =
default_gmail_params
// {
address = "anerz@post.bgu.ac.il";
userName = "anerz@post.bgu.ac.il";
passwordCommand = "pass mbsync/anerz@post.bgu.ac.il | head -1";
};
zakobar =
default_account_params
// {
address = "aner@zakobar.com";
primary = true;
userName = "aner@zakobar.com";
imap = {host = "mail.privateemail.com";};
smtp =
default_smtp
// {
port = 587;
host = "mail.privateemail.com";
};
passwordCommand = "pass privateemail.com | head -1";
};
};
systemd.user.services.mbsync-timer = {
Unit.Description = "Sync mail via mbsync (skips silently if smartcard is absent)";
Service = {
Type = "oneshot";
ExecStart = "${mbsyncSafe}";
};
};
systemd.user.timers.mbsync-timer = {
Unit.Description = "Periodic mbsync mail sync";
Timer = {
OnStartupSec = "2m";
OnUnitActiveSec = "5m";
};
Install.WantedBy = ["timers.target"];
};
};
};
}
+15
View File
@@ -0,0 +1,15 @@
{...}: {
config.flake.modules.homeManager.mpris-proxy = {
lib,
config,
...
}: {
options.azos.mpris-proxy.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.mpris-proxy.enable {
services.mpris-proxy.enable = true;
};
};
}
+77
View File
@@ -0,0 +1,77 @@
{...}: {
config.flake.modules.homeManager.nextcloud = {
lib,
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}"
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 | head -1)
uid=$(${pkgs.pass}/bin/pass nextcloud-username/aner | 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;
};
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=${pkgs.fuse3}/bin:${pkgs.fuse}/bin";
ExecStart = "${nextcloudMountSafe}";
ExecStop = "${pkgs.fuse3}/bin/fusermount3 -u %h/${mountDir}";
Restart = "always";
RestartSec = "10s";
};
Install.WantedBy = ["default.target"];
};
})
];
};
}
+15
View File
@@ -0,0 +1,15 @@
{...}: {
config.flake.modules.homeManager.opencode = {
lib,
config,
...
}: {
options.azos.opencode.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.opencode.enable {
programs.opencode.enable = true;
};
};
}
+55
View File
@@ -0,0 +1,55 @@
{...}: {
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"];
};
};
}
@@ -43,7 +43,7 @@ c.downloads.location.prompt = False
c.editor.command = ['emacsclient', '-e', '(find-file "{}")']
monospace = "10pt 'DejaVu Sans Mono'"
c.fonts.completion.category = f"bold{monospace}"
c.fonts.completion.category = f"bold {monospace}"
c.fonts.completion.entry = monospace
c.fonts.debug_console = monospace
c.fonts.downloads = monospace
@@ -54,3 +54,10 @@ c.fonts.messages.warning = monospace
c.fonts.prompts = monospace
c.fonts.statusbar = monospace
c.fonts.hints = "bold 13px 'LiberationMono'"
#Set highdpi
c.qt.highdpi = True
c.zoom.default = 70
c.qt.args = ['--disable-gpu']
c.qt.environ = {'QT_XCB_GL_INTEGRATION': 'none', 'LIBGL_ALWAYS_SOFTWARE': '1'}
+15
View File
@@ -0,0 +1,15 @@
{...}: {
config.flake.modules.homeManager.qutebrowser = {
lib,
config,
...
}: {
options.azos.qutebrowser-config.enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
config = lib.mkIf config.azos.qutebrowser-config.enable {
home.file.".config/qutebrowser/config.py".source = ./config.py;
};
};
}
+21
View File
@@ -0,0 +1,21 @@
{...}: {
config.flake.modules.homeManager.reaper = {
lib,
config,
pkgs,
...
}: {
options.azos.reaper.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.reaper.enable {
home.packages = with pkgs; [
reaper
helm
lsp-plugins
vital
];
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{...}: {
config.flake.modules.homeManager.roam-backup = {
lib,
config,
pkgs,
...
}: {
options.azos.roam-backup.enable = lib.mkOption {
default = false;
example = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.roam-backup.enable {
home.packages = [
(pkgs.writeShellScriptBin "roam-backup" ''
set -e
cd "$HOME/roam"
git add -A
if git diff --cached --quiet; then
echo "Nothing to commit."
else
git commit -m "backup: $(date '+%Y-%m-%d %H:%M')"
fi
git push --set-upstream origin HEAD
'')
];
};
};
}
+61
View File
@@ -0,0 +1,61 @@
{...}: {
config.flake.modules.homeManager.snx-rs = {
lib,
config,
pkgs,
...
}: let
cfg = config.azos.snx-rs;
in {
options.azos.snx-rs = {
enable = lib.mkOption {
default = false;
type = lib.types.bool;
};
server = lib.mkOption {
default = "vpn.bgu.ac.il";
type = lib.types.str;
description = "VPN server address";
};
username = lib.mkOption {
default = "anerz@vpn";
type = lib.types.str;
description = "VPN username";
};
loginType = lib.mkOption {
default = "vpn";
type = lib.types.str;
description = "Login type";
};
ignoreServerCert = lib.mkOption {
default = true;
type = lib.types.bool;
description = "Ignore server certificate validation";
};
};
config = lib.mkIf cfg.enable {
home.packages = with pkgs; [
snx-rs
(pkgs.writeShellScriptBin "snx-connect" ''
#!/bin/sh
sudo ${pkgs.snx-rs}/bin/snx-rs -m command &
sleep 1
${pkgs.snx-rs}/bin/snxctl connect
'')
(pkgs.writeShellScriptBin "snx-disconnect" ''
#!/bin/sh
${pkgs.snx-rs}/bin/snxctl disconnect
pkill -x snx-rs 2>/dev/null || true
'')
];
home.file.".config/snx-rs/snx-rs.conf" = {
text = ''
server ${cfg.server}
username ${cfg.username}
login-type ${cfg.loginType}
ignore-server-cert ${lib.boolToString cfg.ignoreServerCert}
'';
};
};
};
}
+15
View File
@@ -0,0 +1,15 @@
{...}: {
config.flake.modules.nixos.steam = {
lib,
config,
...
}: {
options.azos.steam.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.steam.enable {
programs.steam.enable = true;
};
};
}
+30
View File
@@ -0,0 +1,30 @@
{...}: {
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];
};
};
}
+16
View File
@@ -0,0 +1,16 @@
{...}: {
config.flake.modules.homeManager.ytdl = {
lib,
config,
pkgs,
...
}: {
options.azos.ytdl.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
config = lib.mkIf config.azos.ytdl.enable {
home.packages = with pkgs; [yt-dlp];
};
};
}
Generated
+221 -96
View File
@@ -3,8 +3,9 @@
"azos-core": {
"inputs": {
"cabata": "cabata",
"nix-search-cli": "nix-search-cli",
"nixpkgs": "nixpkgs_3"
"evil-hl-line": "evil-hl-line",
"flake-parts": "flake-parts",
"import-tree": "import-tree"
},
"locked": {
"path": "./azos-core",
@@ -23,11 +24,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1771368993,
"narHash": "sha256-mWcLaCViNfUMPcqCvQRed4kR20Ifs8aaHcjMlVbF2tQ=",
"lastModified": 1779008956,
"narHash": "sha256-jpJ30XX1pAH+Mpsyj92K8t6812Qs+P4J31wO7RS/e+Y=",
"owner": "anerisgreat",
"repo": "cabata",
"rev": "4fa661795527fa437698409395dc4363fbc0adf0",
"rev": "ff1871152ffb635e18e1a9804750192c6969d1cb",
"type": "github"
},
"original": {
@@ -36,14 +37,33 @@
"type": "github"
}
},
"evil-hl-line": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1778617640,
"narHash": "sha256-bG1hjyLIOiRB+M1rXm8ch0ftHPvV43lRtzQ8qWcus5s=",
"owner": "anerisgreat",
"repo": "evil-hl-line",
"rev": "7cf5a11bdca2abd9516f0ce572e1e03bb541507f",
"type": "github"
},
"original": {
"owner": "anerisgreat",
"repo": "evil-hl-line",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1673956053,
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=",
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
@@ -52,6 +72,42 @@
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1778716662,
"narHash": "sha256-m1Yf0wZ8j1OHjTc2UwHwyQRSnNeSgLJOd7q5Y45hzi4=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "f7c1a2d347e4c52d5fb8d10cb4d94b5884e546fb",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib_2"
},
"locked": {
"lastModified": 1788450739,
"narHash": "sha256-glZLQlzIn1fXH6PazR2iUmTo7kzzyYSshrWhLS9TqCU=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "31729ca8cbdb4fa927b34e5f4353e6a83f39e993",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": [
@@ -75,12 +131,15 @@
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1676283394,
"narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=",
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
@@ -96,11 +155,11 @@
]
},
"locked": {
"lastModified": 1774647770,
"narHash": "sha256-UNNi14XiqRWWjO8ykbFwA5wRwx7EscsC+GItOVpuGjc=",
"lastModified": 1788651960,
"narHash": "sha256-v9wJd32eZ2bvhBzVOd7TIjLQd011P7nwOhjKtWlci5I=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "02371c05a04a2876cf92e2d67a259e8f87399068",
"rev": "2c0350c759688177331b8f5242311fae8877bdb3",
"type": "github"
},
"original": {
@@ -110,16 +169,46 @@
"type": "github"
}
},
"import-tree": {
"locked": {
"lastModified": 1778781969,
"narHash": "sha256-Jjuz5CmSkur8KvLDoGa+vylEp+RkQtv4mt/qcMznpH0=",
"owner": "vic",
"repo": "import-tree",
"rev": "d321337efd0f23a9eb14a42adb7b2c29313ab274",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"import-tree_2": {
"locked": {
"lastModified": 1788467110,
"narHash": "sha256-ljEMTXP/rH0tOvDzc9gzwww6KcHRPRnEHzd9lK48V7s=",
"owner": "vic",
"repo": "import-tree",
"rev": "eb1b52eaecc57f7c136d07ae8a93e724dfecac46",
"type": "github"
},
"original": {
"owner": "vic",
"repo": "import-tree",
"type": "github"
}
},
"musnix": {
"inputs": {
"nixpkgs": "nixpkgs_4"
"nixpkgs": "nixpkgs_3"
},
"locked": {
"lastModified": 1773185476,
"narHash": "sha256-COuJkFg669oalmEN3T61gD3gM9SfUsiGvDS9pCOphZY=",
"lastModified": 1777848538,
"narHash": "sha256-Dj51C0NWsglqRrCpdmMr2nFiYacFOid1Tor4H9yG2HY=",
"owner": "musnix",
"repo": "musnix",
"rev": "739e2a1f94c87d5f4c8b880a12480185cf0d7620",
"rev": "8548782f0d1d0928daa3fffde8a008f72219a3f3",
"type": "github"
},
"original": {
@@ -128,49 +217,16 @@
"type": "github"
}
},
"nix-filter": {
"locked": {
"lastModified": 1710156097,
"narHash": "sha256-1Wvk8UP7PXdf8bCCaEoMnOT1qe5/Duqgj+rL8sRQsSM=",
"owner": "numtide",
"repo": "nix-filter",
"rev": "3342559a24e85fc164b295c3444e8a139924675b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "nix-filter",
"type": "github"
}
},
"nix-search-cli": {
"inputs": {
"flake-compat": "flake-compat",
"flake-utils": "flake-utils_2",
"nix-filter": "nix-filter",
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1762187737,
"narHash": "sha256-NGL9jj4y16+d0Es7aK1oxqAimZn7sdJDAxVkcY3CTcg=",
"owner": "peterldowns",
"repo": "nix-search-cli",
"rev": "ab0d5156c1e3b383c250ff273639cd3dea6de2d9",
"type": "github"
},
"original": {
"owner": "peterldowns",
"repo": "nix-search-cli",
"type": "github"
}
},
"nixos-hardware": {
"inputs": {
"nixpkgs": "nixpkgs_4"
},
"locked": {
"lastModified": 1774567711,
"narHash": "sha256-uVlOHBvt6Vc/iYNJXLPa4c3cLXwMllOCVfAaLAcphIo=",
"lastModified": 1788335262,
"narHash": "sha256-3jBEq8avfzlrsY4UpW4d5TOmm7ocseZfnitEJhqauyc=",
"owner": "NixOS",
"repo": "nixos-hardware",
"rev": "3f6f874dfc34d386d10e434c48ad966c4832243e",
"rev": "44d95795ee2d475b3d687325e26dcf4ca9104557",
"type": "github"
},
"original": {
@@ -180,6 +236,27 @@
"type": "github"
}
},
"nixos-wsl": {
"inputs": {
"flake-compat": "flake-compat",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784642409,
"narHash": "sha256-hcbDqFuySAJawljt5r0sKBCJKYnbtGD0T/ZIozH1Dq0=",
"owner": "nix-community",
"repo": "nixos-wsl",
"rev": "eaeb18da90024448a60eb1ec7132eafa4003404e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixos-wsl",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1769421245,
@@ -194,13 +271,43 @@
"type": "indirect"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1777168982,
"narHash": "sha256-GOkGPcboWE9BmGCRMLX3worL4EMnsnG8MyKmXNeYuhQ=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "f5901329dade4a6ea039af1433fb087bd9c1fe14",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-lib_2": {
"locked": {
"lastModified": 1788057806,
"narHash": "sha256-DTQSMxzDWmT0zhguthvegnVkn7CFqGCv4IHCzk5ZUpM=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "596e2e3940e09b2abbeb03f75fa1828c57fcd72c",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1774386573,
"narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=",
"lastModified": 1788614874,
"narHash": "sha256-7QYjT2vHLuX9Z1pdxHXDKCbh1CR3D/2rywB9Tx0MPRg=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9",
"rev": "c043004d1c6985732bcc1cbc5a9c9aecbbb4e0f0",
"type": "github"
},
"original": {
@@ -212,40 +319,11 @@
},
"nixpkgs_2": {
"locked": {
"lastModified": 1761752004,
"narHash": "sha256-z83uhYagfW6ZnH5Svvaxx6O0a305sBLGCCJZBJnMD9w=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "ddf56acbeb53825b79b82c13a657806774bfd137",
"type": "github"
},
"original": {
"owner": "nixos",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_3": {
"locked": {
"lastModified": 1774273680,
"narHash": "sha256-a++tZ1RQsDb1I0NHrFwdGuRlR5TORvCEUksM459wKUA=",
"lastModified": 1779508470,
"narHash": "sha256-Ap9KJX+5xHIn3bPIpfNgT6MEXdAECECwo4/rmlQD74M=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fdc7b8f7b30fdbedec91b71ed82f36e1637483ed",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1772198003,
"narHash": "sha256-I45esRSssFtJ8p/gLHUZ1OUaaTaVLluNkABkk6arQwE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "dd9b079222d43e1943b6ebd802f04fd959dc8e61",
"rev": "29916453413845e54a65b8a1cf996842300cd299",
"type": "github"
},
"original": {
@@ -255,13 +333,42 @@
"type": "github"
}
},
"nixpkgs_5": {
"nixpkgs_3": {
"locked": {
"lastModified": 1774273680,
"narHash": "sha256-a++tZ1RQsDb1I0NHrFwdGuRlR5TORvCEUksM459wKUA=",
"lastModified": 1777268161,
"narHash": "sha256-bxrdOn8SCOv8tN4JbTF/TXq7kjo9ag4M+C8yzzIRYbE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fdc7b8f7b30fdbedec91b71ed82f36e1637483ed",
"rev": "1c3fe55ad329cbcb28471bb30f05c9827f724c76",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs_4": {
"locked": {
"lastModified": 1767892417,
"narHash": "sha256-8bW3q88CEg2u4hSP66Vf4lpbLonHz7hqDNBMcCY7E9U=",
"rev": "3497aa5c9457a9d88d71fa93a4a8368816fbeeba",
"type": "tarball",
"url": "https://releases.nixos.org/nixos/unstable/nixos-26.05pre924538.3497aa5c9457/nixexprs.tar.xz"
},
"original": {
"type": "tarball",
"url": "https://channels.nixos.org/nixos-unstable/nixexprs.tar.xz"
}
},
"nixpkgs_5": {
"locked": {
"lastModified": 1788549839,
"narHash": "sha256-kOrCcSIA6w9J1hX5DqHy2k9pDTJymExTsbV74U9UtCA=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "17de0b976395537756f30a3e78f2f06e5cec89ed",
"type": "github"
},
"original": {
@@ -274,9 +381,12 @@
"root": {
"inputs": {
"azos-core": "azos-core",
"flake-parts": "flake-parts_2",
"home-manager": "home-manager",
"import-tree": "import-tree_2",
"musnix": "musnix",
"nixos-hardware": "nixos-hardware",
"nixos-wsl": "nixos-wsl",
"nixpkgs": "nixpkgs_5",
"nixpkgs-unstable": "nixpkgs-unstable"
}
@@ -295,6 +405,21 @@
"repo": "x86_64-linux",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
+28 -44
View File
@@ -3,13 +3,8 @@
inputs = {
self.submodules = true;
# Nixpkgs
#Temporarily
# nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
#Need unstable for opencode and agent-shell
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
# Unused unstable
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
home-manager = {
@@ -19,59 +14,48 @@
nixos-hardware.url = "github:NixOS/nixos-hardware/master";
#Music production nix
musnix = {url = "github:musnix/musnix";};
azos-core = {
url = "./azos-core";
flake = true;
};
nixos-wsl = {
url = "github:nix-community/nixos-wsl";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = {
self,
nixpkgs,
home-manager,
...
} @ inputs: let
inherit (self) outputs;
flake-parts.url = "github:hercules-ci/flake-parts";
import-tree.url = "github:vic/import-tree";
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
imports = [
inputs.azos-core.flakeModules.default
./overlays/default.nix
(inputs.import-tree ./features)
./_machines/lauretta.nix
./_machines/vm.nix
./_machines/beacon.nix
./_machines/beacon-wsl.nix
];
systems = [
"aarch64-linux"
"x86_64-linux"
"aarch64-darwin"
"x86_64-darwin"
];
forAllSystems = nixpkgs.lib.genAttrs systems;
in {
formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.alejandra);
overlays = import ./overlays {inherit inputs;};
azos-core.nixosModules = inputs.azos-core.nixosModules.nixosModules;
nixosModules = import ./modules/nixos;
azos-core.homeManagerModules = inputs.azos-core.nixosModules.homeManagerModules;
homeManagerModules = import ./modules/home-manager;
#Systems configured
nixosConfigurations = {
#Lauretta - laptop
lauretta = nixpkgs.lib.nixosSystem {
specialArgs = {inherit inputs outputs;};
modules = [
./nixos/configuration.nix
];
perSystem = {
system,
pkgs,
...
}: {
formatter = pkgs.alejandra;
devShells = (import ./shells) {inherit pkgs;};
};
#Test VM
vm = nixpkgs.lib.nixosSystem {
specialArgs = {inherit inputs outputs;};
modules = [
./nixos/configuration-vm.nix
];
};
};
#TODO this is probably unsupported
#TODO the devshells do not know of the new packages.
devShells = forAllSystems (system: ((import ./shells) {pkgs = nixpkgs.legacyPackages.${system};}));
};
}
+49 -11
View File
@@ -1,36 +1,74 @@
# This is your home-manager configuration file
# Use this to configure your home environment (it replaces ~/.config/nixpkgs/home.nix)
{
inputs,
outputs,
suiteModules,
lib,
config,
pkgs,
...
}: {
imports = [
outputs.homeManagerModules
outputs.azos-core.homeManagerModules
suiteModules.homeManager.base
suiteModules.homeManager.dev
suiteModules.homeManager.editor
suiteModules.homeManager.station
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.mail
suiteModules.homeManager.mpris-proxy
suiteModules.homeManager.nextcloud
suiteModules.homeManager.opencode
suiteModules.homeManager.qutebrowser
suiteModules.homeManager.reaper
suiteModules.homeManager.snx-rs
suiteModules.homeManager.roam-backup
suiteModules.homeManager.ytdl
suiteModules.homeManager.chess
suiteModules.homeManager.homey-alert
suiteModules.homeManager.leetcode
];
programs.home-manager.enable = true;
azos.suites.base.enable = true;
azos.suites.editor.enable = true;
azos.suites.dev.enable = true;
azos.suites.station.enable = true;
azos.suites.exwm.enable = true;
azos.suites.lauretta.enable = true;
azos.chess.enable = true;
azos.leetcode.enable = true;
azos.name = "Aner Zakobar";
azos.headphones.enable = true;
azos.headphones.devices = [
{
mac = "AC:80:0A:AF:E1:C2";
name = "Aner's Headphones";
}
{
mac = "AC:80:0A:A1:5F:53";
name = "Chen's Headphones";
}
{
mac = "E4:17:D8:29:9B:16";
name = "8bitdo";
}
];
azos.roam-backup.enable = true;
azos.homey-alert.enable = true;
home = {
username = "aner";
homeDirectory = "/home/aner";
};
# Nicely reload system units when changing configs
systemd.user.startServices = "sd-switch";
# https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
home.stateVersion = "25.11";
}
-19
View File
@@ -1,19 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.home-audio.enable;
in {
options.azos.home-audio.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
# home.packages = with pkgs; [pavucontrol];
};
}
-19
View File
@@ -1,19 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.claude.enable;
in {
options.azos.claude.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [claude-code claude-agent-acp];
};
}
-22
View File
@@ -1,22 +0,0 @@
{
imports = [
./audio.nix
./claude.nix
./encryption.nix
./git.nix
./hfsprogs.nix
./headphones-whmx4000.nix
./hyprland-suite.nix
./kubernetes.nix
./lauretta-emacs.nix
./mail.nix
./mpris-proxy.nix
./opencode.nix
# ./printing.nix
# ./gnuradio.nix
./qutebrowser-config.nix
./reaper.nix
./snx-rs.nix
./ytdl.nix
];
}
-41
View File
@@ -1,41 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.encryption.enable;
in {
options.azos.encryption.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
programs.password-store = {
enable = true;
settings = {
PASSWORD_STORE_KEY = "076AA297579A0064";
};
};
home.packages = with pkgs; [
yubikey-personalization
pinentry-gtk2
];
programs.gpg = {
enable = true;
};
services.gpg-agent = {
enable = true;
enableSshSupport = true;
pinentry.package = pkgs.pinentry-gtk2;
};
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;
};
}
-40
View File
@@ -1,40 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.git-config.enable;
in {
options.azos.git-config.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [pass-git-helper unzip];
programs.git = {
enable = true;
signing = {
key = "6D17E295C70E2674";
signByDefault = true;
};
settings = {
extraConfig = {
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;
};
}
-29
View File
@@ -1,29 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.gnuradio.enable;
in {
options.azos.gnuradio.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
gnuradio
uhd
gmp
boost
volk
libxcursor
cmake
pkg-config
spdlog
];
};
}
-1
View File
@@ -1 +0,0 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDBFZRqiTsOCAJPMqUyMeLd2MbyjdGoyqDVq5/Inhb6EOaM1NUGG4b6FPmYgFLyJIm5LC9BOo6M7npiaiOs/zMqp+hoGLNQUNwm5/G0uy1bjkEfKdUTdGnJ2+M9rkxrR1c+KXrjkiqECqTbnPE4mJbGyVxBW2MwMeP5w8c0DB5KO528PetvHMPPQuEdXyZzDI4kKtVpMlJoPIrIGlNFX0G/wrgXcM4zU1snOTuYGqZnWW++4kBsgIlRKpf/bLJyUMTp30eLVr0fQ6OMBtj1tzUUBaaowU6VGYQQDU/rIh/NpkA2cEVPXZegM4OohkAqrJBFPIAg90WD9Z/SyQlz0Jn8PpAloP0Cuq2vVRr+QLEwxqGiFq91YQ2VtwksMHwJGVrXRCNegpxTZQijWMEd+o0FD2cEd7Ftw6v2L6g12GJ3QGX/q0d/u0GongLLa9fPXl4VoAu7AL+cUcbX/SS7RCG8kYAR3DwOazVbK0NWEdwvWdoSU4lZ3j2at1xqMGjHjyLiTeUqZBjm+Sl5MJWIYNg+8hnONljvggg4SzDFDAkgVLZtOCaZibsMA1ucGR7VRCM09uoaEI4/ZS5pCBtYcp8X67Bv67Og8s2NFf5sUfYBPPKpdBSs+dEPycNVff6JlmzfNiyzLawacGKIDWYSgkOl43N/5ehtpsL3HMZ+5SVNIw== (none)
@@ -1,23 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.headphones-whmx4000.enable;
in {
options.azos.headphones-whmx4000.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
(pkgs.writeShellScriptBin
"azos-connect-headphones-whmx4000"
"echo \"connect AC:80:0A:AF:E1:C2\" | bluetoothctl")
];
};
}
-20
View File
@@ -1,20 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.hfsprogs.enable;
in {
options.azos.hfsprogs.enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
hfsprogs
];
};
}
-58
View File
@@ -1,58 +0,0 @@
{
lib,
config,
pkgs,
options,
azos-utils,
...
}: let
isEnabled =
config.azos.suites.hyprland.enable;
in {
options.azos.suites.hyprland.enable = lib.mkOption {
default = false;
example = true;
type = lib.types.bool;
};
#TODO shutdown reboot commands
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
hyprland
waybar # status bar
wofi # application launcher
kitty
grim # screenshot utility
slurp # region selector
swappy # screenshot editor
networkmanagerapplet
xwayland
];
azos.emacs.enabledSuites = ["azos-emacs-hyprland"];
azos.emacs.pkgs = [pkgs.azos-emacs-hyprland];
azos.emacs.emacspkg = pkgs.emacs-pgtk;
home.file.".login.sh" = {
text = ''
#!/usr/bin/env bash
${pkgs.hyprland}/bin/Hyprland
'';
#Make executable
executable = true;
};
# systemd.user.services.xwayland = {
# Unit.Description = "XWayland server";
# Install.WantedBy = [ "default.target" ];
# Service = {
# ExecStart = "${pkgs.xwayland}/bin/Xwayland :0 -rootless -terminate -listen tcp";
# Restart = "on-failure";
# RestartSec = "1s";
# StandardInput = "null";
# StandardOutput = "null";
# StandardError = "journal";
# };
# };
};
}
-23
View File
@@ -1,23 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.kubectl.enable;
in {
options.azos.kubectl.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
kubectl
kubernetes-helm
velero
];
};
}
-23
View File
@@ -1,23 +0,0 @@
{
lib,
config,
pkgs,
azos-utils,
...
}: let
isEnabled =
config.azos.emacs.enable && config.azos.suites.lauretta.enable;
emacspkgs = config.azos.emacs.emacspkg.pkgs;
localPkgName = "azos-emacs-lauretta";
in {
#Set config
options.azos.suites.lauretta.enable = azos-utils.mkSuiteEnableOption {};
config = lib.mkIf isEnabled {
azos.emacs.enabledSuites = [localPkgName];
#Base emacs suite definition
azos.emacs.pkgs = [pkgs.azos-emacs-lauretta];
home.packages = with pkgs; [pkgs.nix-search-cli];
};
}
-108
View File
@@ -1,108 +0,0 @@
#https://xeiaso.net/talks/asg-2023-nixos/ example
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.mail.enable;
default_account_params = {
realName = "Aner Zakobar";
notmuch.enable = true;
mbsync = {
enable = true;
create = "maildir";
};
};
default_gmail_params =
default_account_params
// {
# lieer.enable = true;
# lieer.sync.enable = true;
flavor = "gmail.com";
# imap = {
# host = "mail.privateemail.com";
# };
};
default_smtp = {
tls = {
enable = true;
certificatesFile = "/etc/ssl/certs/ca-certificates.crt";
};
};
default_gmail_smtp =
default_smtp
// {
host = "smtp.gmail.com";
};
in {
options.azos.mail.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
programs = {
notmuch = {
enable = true;
hooks = {
preNew = "mbsync -a";
};
};
mbsync = {
enable = true;
};
msmtp = {
enable = true;
};
lieer = {
enable = true;
};
};
services.lieer.enable = true;
accounts.email.accounts = {
gmail =
default_gmail_params
// {
address = "anerisgreat@gmail.com";
userName = "anerisgreat";
# smtp = default_gmail_smtp;
passwordCommand = "pass gmail.com/mbsync-anerisgreat";
};
bgu =
default_gmail_params
// {
address = "anerz@post.bgu.ac.il";
userName = "anerz@post.bgu.ac.il";
# smtp = default_gmail_smtp;
passwordCommand = "pass post.bgu.ac.il/mbsync-anerz";
};
zakobar =
default_account_params
// {
address = "aner@zakobar.com";
msmtp.enable = true;
primary = true;
userName = "aner@zakobar.com";
imap = {
host = "mail.privateemail.com";
};
# mbsync = {
# enable = true;
# create = "maildir";
# };
smtp =
default_smtp
// {
port = 587;
host = "mail.privateemail.com";
};
passwordCommand = "pass zakobar.com/mail/aner";
};
};
};
}
-20
View File
@@ -1,20 +0,0 @@
#https://xeiaso.net/talks/asg-2023-nixos/ example
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.mpris-proxy.enable;
in {
options.azos.mpris-proxy.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
services.mpris-proxy.enable = true;
};
}
-19
View File
@@ -1,19 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.opencode.enable;
in {
options.azos.opencode.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
programs.opencode.enable = true;
};
}
@@ -1,2 +0,0 @@
[*git.zakobar.com*]
target=zakobar.com/users/aner
-22
View File
@@ -1,22 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.printing.enable;
in {
options.azos.printing.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
services.printing.enable = true;
home.packages = with pkgs; [
hplip
];
};
}
@@ -1,19 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.qutebrowser-config.enable;
in {
options.azos.qutebrowser-config.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.file.".config/qutebrowser/config.py".source = ./qutebrowser-config.py;
};
}
-25
View File
@@ -1,25 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.reaper.enable;
in {
options.azos.reaper.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
reaper
helm #TODO this synth had better work
# surge
lsp-plugins
vital
];
};
}
-63
View File
@@ -1,63 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.snx-rs.enable;
cfg = config.azos.snx-rs;
in {
options.azos.snx-rs = {
enable = lib.mkOption {
default = true;
type = lib.types.bool;
};
server = lib.mkOption {
default = "vpn.bgu.ac.il";
type = lib.types.str;
description = "VPN server address";
};
username = lib.mkOption {
default = "anerz@vpn";
type = lib.types.str;
description = "VPN username";
};
loginType = lib.mkOption {
default = "vpn";
type = lib.types.str;
description = "Login type (e.g., vpn, vpn_Microsoft_Authenticator)";
};
ignoreServerCert = lib.mkOption {
default = true;
type = lib.types.bool;
description = "Ignore server certificate validation";
};
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
snx-rs
(pkgs.writeShellScriptBin "snx-connect" ''
#!/bin/sh
sudo ${pkgs.snx-rs}/bin/snx-rs -m command &
sleep 1
${pkgs.snx-rs}/bin/snxctl connect
'')
(pkgs.writeShellScriptBin "snx-disconnect" ''
#!/bin/sh
${pkgs.snx-rs}/bin/snxctl disconnect
pkill -x snx-rs 2>/dev/null || true
'')
];
home.file.".config/snx-rs/snx-rs.conf" = {
text = ''
server ${cfg.server}
username ${cfg.username}
login-type ${cfg.loginType}
ignore-server-cert ${lib.boolToString cfg.ignoreServerCert}
'';
};
};
}
-1
View File
@@ -1 +0,0 @@
61D809B46CEE2A0AF799C4C2FADB0F61A19EEABD
-21
View File
@@ -1,21 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.ytdl.enable;
in {
options.azos.ytdl.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
home.packages = with pkgs; [
yt-dlp
];
};
}
-33
View File
@@ -1,33 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.hardware-audio.enable;
in {
options.azos.hardware-audio.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
services = {
pipewire = {
enable = true;
audio.enable = true;
pulse.enable = true;
alsa = {
enable = true;
support32Bit = true;
};
jack.enable = true;
};
};
# hardware.pulseaudio.enable = true;
environment.systemPackages = with pkgs; [pavucontrol];
musnix.enable = true;
};
}
-19
View File
@@ -1,19 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.binfmt.enable;
in {
options.azos.binfmt.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
boot.binfmt.emulatedSystems = ["aarch64-linux"];
};
}
-20
View File
@@ -1,20 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.bluetooth.enable;
in {
options.azos.bluetooth.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
services.blueman.enable = true;
hardware.bluetooth.enable = true;
};
}
-3
View File
@@ -1,3 +0,0 @@
{
imports = [./audio.nix ./binfmt.nix ./bluetooth.nix ./steam.nix ./virtualization.nix];
}
-19
View File
@@ -1,19 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.steam.enable;
in {
options.azos.steam.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
programs.steam.enable = true;
};
}
-40
View File
@@ -1,40 +0,0 @@
{
lib,
config,
pkgs,
...
}: let
isEnabled =
config.azos.virtualization.enable;
in {
options.azos.virtualization.enable = lib.mkOption {
default = true;
example = true;
type = lib.types.bool;
};
config = lib.mkIf isEnabled {
virtualisation.libvirtd = {
enable = true; # start / run libvirtd as a system service
# optional: expose the default NAT network (virbr0) – libvirtd creates it
# automatically when the daemon is on, but we make sure the bridge is
# allowed through the firewall.
qemu = {
swtpm.enable = true; # (optional) enable software TPM for guests
};
};
environment.systemPackages = with pkgs; [
# QEMU (KVM‑accelerated)
qemu_kvm # same as pkgs.qemu (but with KVM support explicitly enabled)
# CLI utilities
libvirt # provides virsh, virt-install, virt-manager (cli bits)
# GUI front‑end
virt-manager # graphical manager (uses libvirt + spice)
virt-viewer # Spice/VNC client that virt‑manager calls under the hood
];
services.spice-vdagentd.enable = true; # makes copy‑paste & auto‑resize work in Spice windows
networking.firewall.allowedTCPPorts = [5900 5901]; # Spice ports (adjust if you expose elsewhere)
networking.firewall.allowedUDPPorts = [5900 5901];
};
}
+78
View File
@@ -0,0 +1,78 @@
{
lib,
config,
pkgs,
inputs,
suiteModules,
...
}: {
imports = [
inputs.nixos-wsl.nixosModules.default
suiteModules.nixos.attic
];
wsl.enable = true;
wsl.defaultUser = "aner";
nixpkgs.hostPlatform = "x86_64-linux";
nixpkgs.config.allowUnfree = true;
nix.settings = {
experimental-features = ["nix-command" "flakes"];
auto-optimise-store = true;
substituters = [
"https://cache.nixos.org"
"https://cuda-maintainers.cachix.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"cuda-maintainers.cachix.org-1:0dq3bujKpuEPMCX6U4WylrUDZ9JyUG0VpVZa7CNfq5E="
];
};
security.sudo.wheelNeedsPassword = false;
networking.hostName = "beacon";
networking.firewall.allowedTCPPorts = [ 22 ];
programs.nix-ld.enable = true;
time.timeZone = "Asia/Jerusalem";
# GPU is exposed through WSL2 paravirtualization; no Linux NVIDIA KM needed
hardware.graphics.enable = true;
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
};
};
users.users.aner = {
isNormalUser = true;
extraGroups = ["wheel" "video"];
openssh.authorizedKeys.keys = [
"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== cardno:29_109_265"
];
};
environment.systemPackages = with pkgs; [
git
rsync
tmux
vim
wget
rclone
pciutils
nvtopPackages.nvidia
cudaPackages.cudatoolkit
cudaPackages.cudnn
cudaPackages.nccl
python3
direnv
];
azos.attic.enable = true;
system.stateVersion = "25.11";
}
+146
View File
@@ -0,0 +1,146 @@
{
lib,
config,
pkgs,
modulesPath,
suiteModules,
...
}: {
imports = [
"${modulesPath}/virtualisation/disk-image.nix"
suiteModules.nixos.attic
];
image.format = "raw";
boot.initrd.availableKernelModules = [
"xhci_pci"
"usb_storage"
"uas"
"ahci"
"nvme"
"usbhid"
"sd_mod"
];
nixpkgs.hostPlatform = "x86_64-linux";
nixpkgs.config.allowUnfree = true;
nixpkgs.config.cudaSupport = true;
nix.settings = {
experimental-features = ["nix-command" "flakes"];
auto-optimise-store = true;
substituters = [
"https://cache.nixos.org"
"https://cuda-maintainers.cachix.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
"cuda-maintainers.cachix.org-1:0dq3bujKpuEPMCX6U4WylrUDZ9JyUG0VpVZa7CNfq5E="
];
};
security.sudo.wheelNeedsPassword = false;
networking.hostName = "beacon";
time.timeZone = "Asia/Jerusalem";
networking.networkmanager.enable = true;
hardware.enableRedistributableFirmware = true;
networking.useDHCP = false;
environment.etc."NetworkManager/system-connections/Zakobar.nmconnection" = {
mode = "0600";
text = ''
[connection]
id=Zakobar
type=wifi
autoconnect=true
[wifi]
mode=infrastructure
ssid=Zakobar
[wifi-security]
auth-alg=open
key-mgmt=wpa-psk
psk=0502711157
[ipv4]
method=manual
address1=192.168.1.200/24,192.168.1.1
dns=8.8.8.8;1.1.1.1;
ignore-auto-dns=true
[ipv6]
method=disabled
'';
};
# Swap is created on first boot after boot.growPartition expands the root
# partition to the full drive size. Non-blocking: a failure just means no
# swap on this boot (retried next boot once the partition has grown).
systemd.services.beacon-swap = {
description = "Create and activate swapfile";
after = ["systemd-growfs@-.service" "local-fs.target"];
wantedBy = ["multi-user.target"];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
if [ ! -f /swapfile ]; then
dd if=/dev/zero of=/swapfile bs=1M count=16384 status=progress
chmod 600 /swapfile
${pkgs.util-linux}/bin/mkswap /swapfile
fi
${pkgs.util-linux}/bin/swapon /swapfile
'';
};
# NVIDIA RTX 4050 — Ada Lovelace supports open kernel modules
services.xserver.videoDrivers = ["nvidia"];
hardware.nvidia = {
open = true;
modesetting.enable = true;
package = config.boot.kernelPackages.nvidiaPackages.stable;
};
hardware.graphics.enable = true;
services.getty.autologinUser = "aner";
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
};
};
users.users.aner = {
isNormalUser = true;
extraGroups = ["wheel" "video"];
openssh.authorizedKeys.keys = [
"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDfzDDO5juINctECmWlsYtGghEiX/RnTJ1cazLvOWSrPfsTyEd+B1+Ig8kFefNryjkpApfRXqj5KtLPNlpLfdVBrOIfhIveEp2MGqhgOGZFNVxQyXnZgii8Zdh4cqZ2O3pZpMsaAQBaJ9nH6dK0dJjicWT5f6TqwrVcInywRc5SuyizoSxoFmg7ch2rnlVi0j5XMVqdh8XLzHXZ7yWCzXy7+hWl/d7pwpyuzoK8dBw2EU9TauhgRDruom5Q9vWJTLStALC9pAIb0v9UFj9y+1zwx7pXsXp5F1g73EYrE4QR+QQ6z2LebuK280W0t+VA/fSCEB13DnkmofgqZQxX5MSCmrxZ5lTFp1FjW6yJo7As9FheF/GECowYkMRIx4IiQsjjHjZqlLRpLas11yAp6tGoZnw59hFo6Lu0Kva39jGVVmioYHtAeE5rD5w+v5kseJR4jlQ8aKB5yOjYUQOIz2AHQyoidgaeR2jPWqZUeRQbACI+/p3CHO45r3hrjATtGloBg0xF95Qws7Be3mjHVhbBLOoob8MdZ8nYAGnhlWrZphlkvXsHC6OUkuDJW00tmMjWXRlFwhFJ+nqUQCgLVjxVHQJ5rq9GeXBUuNXAeCm5BKBsdq+9qqVlt7D9iGyfr0lcZ7peKz/96KwPCWpG2En1Ur0/cVcbWnXEfG/xWO10tQ== openpgp:0xFA67FAB0"
];
};
environment.systemPackages = with pkgs; [
git
rsync
tmux
vim
wget
rclone
pciutils
nvtopPackages.nvidia
cudaPackages.cudatoolkit
cudaPackages.cudnn
cudaPackages.nccl
python3
direnv
];
azos.attic.enable = true;
system.stateVersion = "25.11";
}
+6 -31
View File
@@ -1,53 +1,42 @@
# This is your system's configuration file.
# Use this to configure your system environment (it replaces /etc/nixos/configuration.nix)
{
inputs,
outputs,
suiteModules,
lib,
config,
pkgs,
...
}: {
# You can import other NixOS modules here
virtualisation.vmVariant = {
virtualisation.resolution = {
x = 1280;
y = 1024;
};
virtualisation.qemu.options = [
# Better display option
"-vga virtio"
"-display gtk,zoom-to-fit=false"
# Enable copy/paste
# https://www.kraxel.org/blog/2021/05/qemu-cut-paste/
"-chardev qemu-vdagent,id=ch1,name=vdagent,clipboard=on"
"-device virtio-serial-pci"
"-device virtserialport,chardev=ch1,id=ch1,name=com.redhat.spice.0"
];
};
imports = [
inputs.home-manager.nixosModules.home-manager
outputs.nixosModules
];
nixpkgs.hostPlatform = "x86_64-linux";
nixpkgs = {
overlays = [
inputs.azos-core.overlays.addpkgs
# outputs.overlays.additions
# outputs.overlays.modifications
# outputs.overlays.unstable-packages
outputs.overlays.addpkgs
];
# Configure your nixpkgs instance
config = {
allowUnfree = true;
};
};
nix.registry = (lib.mapAttrs (_: flake: {inherit flake;})) ((lib.filterAttrs (_: lib.isType "flake")) inputs);
nix.nixPath = ["/etc/nix/path"];
environment.etc =
lib.mapAttrs'
@@ -58,22 +47,18 @@
config.nix.registry;
nix.settings = {
experimental-features = "nix-command flakes";
# Deduplicate and optimize nix store
experimental-features = ["nix-command" "flakes"];
auto-optimise-store = true;
};
# TODO: Set your hostname
networking.hostName = "test-vm";
# TODO: This is just an example, be sure to use whatever bootloader you prefer
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
home-manager = {
extraSpecialArgs = {inherit inputs outputs;};
extraSpecialArgs = {inherit inputs outputs suiteModules pkgs;};
users = {
# Import your home-manager configuration
aner = import ../home-manager/home.nix;
};
};
@@ -82,28 +67,18 @@
aner = {
initialPassword = "password";
isNormalUser = true;
openssh.authorizedKeys.keys = [
# TODO: Add your SSH public key(s) here, if you plan on using SSH to connect
];
# TODO: Be sure to add any other groups you need (such as networkmanager, audio, docker, etc)
openssh.authorizedKeys.keys = [];
extraGroups = ["wheel" "libvirtd"];
};
};
# This setups a SSH server. Very important if you're setting up a headless system.
# Feel free to remove if you don't need it.
services.openssh = {
enable = true;
settings = {
# Forbid root login through SSH.
PermitRootLogin = "no";
# Use keys only. Remove if you want to SSH using password (not recommended)
PasswordAuthentication = true;
};
};
#Graphical environment
# services.xserver.desktopManager.xfce.enable = true;
# https://nixos.wiki/wiki/FAQ/When_do_I_update_stateVersion
system.stateVersion = "24.05";
}
+65 -28
View File
@@ -1,6 +1,7 @@
{
inputs,
outputs,
suiteModules,
lib,
config,
pkgs,
@@ -11,19 +12,30 @@
inputs.nixos-hardware.nixosModules.lenovo-thinkpad-t480
inputs.musnix.nixosModules.musnix
inputs.home-manager.nixosModules.home-manager
outputs.nixosModules
outputs.azos-core.nixosModules
suiteModules.nixos.base
suiteModules.nixos.exwm
suiteModules.nixos.audio
suiteModules.nixos.bluetooth
suiteModules.nixos.steam
suiteModules.nixos.virtualization
suiteModules.nixos.binfmt
suiteModules.nixos.attic
suiteModules.nixos.printing
suiteModules.nixos.hardware-auth
];
# Bootloader.
boot.loader.systemd-boot.enable = true;
boot.loader.grub = {
enable = true;
efiSupport = true;
device = "nodev";
configurationLimit = 5;
};
boot.loader.efi.canTouchEfiVariables = true;
nixpkgs = {
overlays = [
inputs.azos-core.overlays.addpkgs
outputs.overlays.addpkgs
inputs.azos-core.overlays.qutebrowserdrm
outputs.overlays.qutebrowserdrm
outputs.overlays.modifications
outputs.overlays.unstable-packages
];
@@ -33,20 +45,14 @@
};
nix.registry = (lib.mapAttrs (_: flake: {inherit flake;})) ((lib.filterAttrs (_: lib.isType "flake")) inputs);
nix.nixPath = ["/etc/nix/path"];
networking.hostName = "lauretta"; # Define your hostname.
# Enable networking
networking.hostName = "lauretta";
networking.networkmanager.enable = true;
# Set your time zone.
time.timeZone = "Asia/Jerusalem";
# Select internationalisation properties.
i18n.defaultLocale = "en_IL";
i18n.extraLocaleSettings = {
LC_ADDRESS = "he_IL.UTF-8";
LC_IDENTIFICATION = "he_IL.UTF-8";
@@ -59,7 +65,6 @@
LC_TIME = "he_IL.UTF-8";
};
# Configure keymap in X11
services.xserver = {
xkb = {
layout = "us,il";
@@ -68,51 +73,83 @@
xkb.options = "grp:alt_shift_toggle";
};
# Configure console keymap
console.keyMap = "il";
# Define a user account. Don't forget to set a password with ‘passwd’.
users.users.aner = {
isNormalUser = true;
description = "Aner Zakobar";
#Audio group for audio config, video group necessary for backlight.
extraGroups = ["networkmanager" "wheel" "audio" "video"];
extraGroups = ["networkmanager" "wheel" "audio" "video" "seat" "input"];
packages = with pkgs; [];
};
nix.settings = {
experimental-features = "nix-command flakes";
experimental-features = ["nix-command" "flakes"];
auto-optimise-store = true;
substituters = [
"https://cache.nixos.org"
];
trusted-public-keys = [
"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
];
};
azos.suites.exwm.enable = true;
azos.attic.enable = true;
azos.printing.enable = true;
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 pkgs;};
extraSpecialArgs = {inherit inputs outputs suiteModules pkgs;};
users = {
aner = import ../home-manager/home.nix;
};
};
# Specializations
specialisation = {
hyprland = {
steam-big-picture = {
configuration = {
# Override home‑manager options for this specialization
home-manager.users.aner = {pkgs, ...}: {
environment.systemPackages = [pkgs.sway];
environment.etc."sway-steam.conf".text = ''
output * bg #000000 solid_color
default_border none
default_floating_border none
exec ${pkgs.dbus}/bin/dbus-run-session -- ${pkgs.bash}/bin/bash -c "steam -tenfoot; ${pkgs.sway}/bin/swaymsg exit"
'';
services.seatd.enable = true;
services.greetd.settings = lib.mkForce {
terminal.vt = 1;
default_session = {
command = "${pkgs.tuigreet}/bin/tuigreet --time --user-menu --user-menu-min-uid 1000 --user-menu-max-uid 29999 --cmd '/home/aner/.login.sh'";
user = "greeter";
};
initial_session = {
command = "${pkgs.sway}/bin/sway --config /etc/sway-steam.conf";
user = "aner";
};
};
home-manager.users.aner = {lib, ...}: {
azos.suites.exwm.enable = lib.mkForce false;
azos.suites.hyprland.enable = true;
};
};
};
};
# List packages installed in system profile. To search, run:
# $ nix search wget
boot.supportedFilesystems = ["exfat"];
boot.kernelModules = ["exfat"];
environment.systemPackages = with pkgs; [
vim
git
tmux
killall
brightnessctl
exfatprogs
gamescope
];
fonts.enableDefaultPackages = true;
@@ -124,5 +161,5 @@
systemd.targets.hibernate.enable = false;
systemd.targets.hybrid-sleep.enable = false;
system.stateVersion = "25.11"; # Did you read the comment?
system.stateVersion = "25.11";
}
+14 -15
View File
@@ -1,25 +1,24 @@
# This file defines overlays
{inputs, ...}: {
addpkgs = final: _prev: (import ../pkgs {pkgs = final.pkgs;});
#Current overlay is for helm, to disable binary, so no conflict
modifications = final: prev: {
helm = prev.helm.overrideAttrs (oldAttrs: rec {
patches =
oldAttrs.patches
or []
++ [
./helm.patch
];
config.flake.overlays.modifications = _final: prev: {
helm = prev.helm.overrideAttrs (oldAttrs: {
patches = oldAttrs.patches or [] ++ [./helm.patch];
});
};
# When applied, the unstable nixpkgs set (declared in the flake inputs) will
# be accessible through 'pkgs.unstable'
unstable-packages = final: _prev: {
config.flake.overlays.unstable-packages = final: _prev: {
unstable = import inputs.nixpkgs-unstable {
system = final.system;
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
'';
}
-7
View File
@@ -1,7 +0,0 @@
{pkgs} @ args: let
inherit args;
localEmacsPkg = pkgs.localEmacsPkg;
in {
azos-emacs-lauretta = localEmacsPkg ./azos-emacs-lauretta.nix;
azos-emacs-hyprland = localEmacsPkg ./azos-emacs-hyprland.nix;
}
-135
View File
@@ -1,135 +0,0 @@
#+title: Aner's Emacs Lauretta Configuration
#+property: header-args :results silent
* Base setup
** Require
#+begin_src emacs-lisp
(require 'azos-emacs-base)
(require 'azos-emacs-dev)
(require 'azos-emacs-editor)
(require 'azos-emacs-exwm)
(require 'azos-emacs-station)
#+end_src
* Lauretta specific
** LLM
#+begin_src emacs-lisp
(setq
gptel-model 'openai/gpt-oss-120b
gptel-backend
(gptel-make-openai "Groq"
:host "api.groq.com"
:endpoint "/openai/v1/chat/completions"
:stream t
:key "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"
:models '(openai/gpt-oss-120b)))
#+end_src
** Agent Shell
#+begin_src emacs-lisp
(setq agent-shell-opencode-default-model-id "opencode/big-pickle")
;; (setq agent-shell-preferred-agent-config (agent-shell-opencode-make-agent-config))
#+end_src
** Headphones
#+begin_src emacs-lisp
(defun azos/connect-headphones ()
(interactive)
(start-process-shell-command "connect-headphones" nil "azos-connect-headphones-whmx4000"))
(define-key azos/global-minor-mode/open-keymap
(kbd "h") 'azos/connect-headphones)
#+end_src
** Tab bar setup
#+begin_src emacs-lisp
(when (fboundp #'azos/bat/enable-tab-display)
(azos/bat/enable-tab-display))
(when (fboundp #'azos/network/enable-tab-display)
(azos/network/enable-tab-display))
(when (fboundp #'azos/audio/enable-tab-display)
(azos/audio/enable-tab-display))
#+end_src
** Nixpkgs Search
#+begin_src emacs-lisp
(defun azos/nixpkgs-parse-json-lines (str)
"Parse JSON Lines format (one JSON object per line) from STR."
(mapcar (lambda (line)
(and (not (string-empty-p line))
(string-match "^{" line)
(condition-case nil
(json-parse-string line)
(error nil))))
(split-string (string-trim str) "\n" t)))
(defun azos/nixpkgs-g (key hash)
"Get KEY from HASH table."
(when (hash-table-p hash)
(gethash key hash)))
(defun azos/nixpkgs-search ()
"Search for a nixpkgs package and show its info."
(interactive)
(let* ((pattern (read-string "Search pattern: "))
(json-output (shell-command-to-string
(format "nix-search --json --max-results 50 '%s'" pattern)))
(results (and json-output
(not (string-empty-p json-output))
(azos/nixpkgs-parse-json-lines json-output)))
(packages (delq nil (mapcar (lambda (item)
(let ((pname (azos/nixpkgs-g "package_pname" item)))
(when pname
(cons pname item))))
results))))
(if packages
(let* ((selected (completing-read "Package: " (mapcar #'car packages) nil t))
(package-data (cdr (assoc-string selected packages))))
(let ((buf (get-buffer-create "*nixpkgs-package-info*")))
(with-current-buffer buf
(erase-buffer)
(if package-data
(progn
(insert (format "Package: %s\n\n" selected))
(insert (format "Version: %s\n" (or (azos/nixpkgs-g "package_pversion" package-data) "unknown")))
(insert (format "Attr: %s\n" (or (azos/nixpkgs-g "package_attr_name" package-data) "unknown")))
(insert (format "\nDescription: %s\n"
(or (azos/nixpkgs-g "package_description" package-data) "none")))
(let ((programs (azos/nixpkgs-g "package_programs" package-data)))
(when (vectorp programs)
(insert (format "\nPrograms: %s\n"
(mapconcat #'identity (append programs nil) " ")))))
(let ((homepage (let ((h (azos/nixpkgs-g "package_homepage" package-data)))
(when (vectorp h) (aref h 0)))))
(when homepage
(insert (format "\nHomepage: %s\n" homepage))))
(let ((licenses (azos/nixpkgs-g "package_license" package-data)))
(when (vectorp licenses)
(insert (format "\nLicense: %s\n"
(mapconcat (lambda (l)
(or (azos/nixpkgs-g "fullName" l) ""))
(append licenses nil) ", ")))))
(insert (format "Package: %s\n\n" selected)
"\nNo additional info available.")))
(goto-char (point-min)))
(display-buffer buf)))
(message "No packages found matching '%s'" pattern))))
(define-key azos/global-minor-mode/open-keymap
(kbd "n") 'azos/nixpkgs-search)
#+end_src
* Provide
#+begin_src emacs-lisp
(provide 'azos-emacs-lauretta)
#+end_src
-2
View File
@@ -1,5 +1,3 @@
{pkgs} @ args: {
#TODO since I do not pass along packages, this cannot work
# debugTex = import ./debugTex args;
default = import ./defaultShell.nix args;
}
+10 -3
View File
@@ -2,14 +2,21 @@
pkgs.mkShell {
buildInputs = with pkgs; [
alejandra
nh
(pkgs.writeShellScriptBin
"azos-lauretta-update"
"sudo nixos-rebuild switch --flake .#lauretta")
"nh os switch '.?submodules=1#lauretta'")
(pkgs.writeShellScriptBin
"azos-update-azos-core"
"nix flake update azos-core")
"nix flake update --flake '.?submodules=1' azos-core")
(pkgs.writeShellScriptBin
"azos-update"
"nix flake update")
"nix flake update --flake '.?submodules=1'")
(pkgs.writeShellScriptBin
"azos-beacon-build-image"
"nix build '.?submodules=1#packages.x86_64-linux.beacon-image'")
(pkgs.writeShellScriptBin
"azos-beacon-remote-update"
"nixos-rebuild switch --flake '.?submodules=1#beacon' --target-host aner@192.168.1.200 --build-host aner@192.168.1.200 --sudo")
];
}