Compare commits

..

No commits in common. "master" and "feat/mcentire-crowdsec" have entirely different histories.

82 changed files with 927 additions and 2994 deletions

74
.gitignore vendored
View file

@ -1,74 +0,0 @@
### Linux gitignore ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# Metadata left by Dolphin file manager, which comes with KDE Plasma
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
# Log files created by default by the nohup command
nohup.out
### MacOS gitignore ###
# General
.DS_Store
__MACOSX/
.AppleDouble
.LSOverride
Icon[]
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Nix gitignore ###
# Ignore build outputs from performing a nix-build or `nix build` command
result
result-*
# Ignore automatically generated direnv output
.direnv
### Vim gitignore ###
# Swap
[._]*.s[a-v][a-z]
# comment out the next line if you don't need vector files
!*.svg
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
# Session
Session.vim
Sessionx.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~

157
README.md
View file

@ -1,157 +0,0 @@
# nix-dotfiles
System and home configurations for my machines.
| Machine | Role | OS | Arch | System config tool | Home config tool |
| -------- | --------------- | ------ | ------- | --------------------------- | ---------------- |
| anderson | server | linux | x86_64 | dpkg/Docker (not this repo) | home-manager |
| bosephus | server | linux | x86_64 | NixOS | home-manager |
| mcentire | server | linux | x86_64 | NixOS | home-manager |
| corianne | MacBook | darwin | aarch64 | nix-darwin | home-manager |
| odyssey | workstation | linux | x86_64 | Ansible | home-manager |
## Quickstart
### Home dotfiles
> ![WARNING]
> Fedora systems will set this up automagically via Ansible. Follow the
[Fedora quickstart] instructions.
Ensure Nix is installed, with the `nix` command and flakes enabled. I try to use
the [Determinate Nix installer] (with upstream Nix) to install Nix with these
options turned on by default.
```bash
curl -fsSL https://install.determinate.systems/nix | sh -s -- install
```
Once Nix is installed, clone the repository to `~/.config/home-manager` and
initiate home-manager.
```bash
git clone https://code.millironx.com/millironx/nix-dotfiles.git ~/.config/home-manager
nix run home-manager -- switch --flake ~/.config/home-manager#$USER@$(hostname -s)
```
In the case that the host has not been assigned a configuration within this repo
yet, pick a hostname with the same system OS, arch, and role as the target
system to get temporary dotfiles up and running.
```bash
nix run home-manager -- switch --flake ~/.config/home-manager#millironx@anderson
```
Once an SSH (with or without GPG) key has been setup and added to the authorized
keys of the git server, switch the upstream to track an authorized (i.e.
read/write) version of the repo.
```bash
cd ~/.config/home-manager
git remote set-url origin git@code.millironx.com:millironx/nix-dotfiles.git
cd -
```
### NixOS
Switching to a flake-based config requires running as root. All of the following
commands are assumed to be running as root.
Ensure that the `nix` command and flakes are enabled.
```bash
sed -i '/^}/i nix.settings.experimental-features = [ "nix-command" "flakes" ];' /etc/nixos/configuration.nix
nixos-rebuild switch
```
> ![NOTE]
> To allow secret decryption in the system, the *machine*-specific SSH key must
> be added to the publicKeys attribute of all applicable secrets, and the
> `hardware-configuration.nix` file must be added to git. Copying arbitrary
> strings like SSH keys or disk UUIDs between systems can be painful, so it might
> be worth setting up the [home dotfiles] immediately after enabling flakes,
> then running these steps on the same machine to avoid typos. Alternatively, I
> might someday be smart enough to create an installer CD that automagically
> sets this up for me.
Get the machine-specific public SSH key.
```bash
cat /etc/ssh/ssh_host_ed25519_key.pub
```
On a separate machine, add the machine's SSH key to `./secrets.nix` and assign
it to any secrets it would need.
```nix
let
bosephus-host =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIxTfeg+GZsfmG8TuEV1xW1gXknAIKzZ3UjZ3guRY+EW root@nixos";
bosephus-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKaDPqRJHoqgY2pseh/mnhjaGWXprHk2s5I52LhHpHcF millironx@bosephus";
odyssey-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN9Aj7BtQp1Roa0tgopDrUo7g2am5WJ43lO1d1fDUz45 millironx@odyssey";
system-administrators = [
bosephus-millironx
odyssey-millironx
];
in {
"secrets/network-information.age".publicKeys = system-administrators
++ [ bosephus-host ];
}
```
Rekey the secrets, and push the updated secrets to the upstream repo.
```bash
nix run github:ryantm/agenix -- --rekey
git add secrets.nix secrets/*
git commit -m "added $NEW_HOST to secrets"
git push
```
Copy the target machine's `hardware-configuration.nix` file to this repo's
`./systems/linux/hardware-configuration/$NEW_HOST.nix`, and be sure to update
the configuration to import its own `hardware-configuration`.
```bash
cp /etc/nixos/hardware-configuration.nix ./systems/linux/hardware-configuration/$NEW_HOST.nix
```
```nix
{ config, pkgs, ... }: {
imports = [
./hardware-configuration/bosephus.nix
];
}
```
Commit and push the hardware configuration to the upstream repo.
```bash
git add systems/linux/*
git commit -m "added $NEW_HOST hardware configuration"
git push
```
Now switch to the flake by pulling and switching in one step.
```bash
nixos-rebuild switch --flake git+https://code.millironx.com/millironx/nix-dotfiles#$(hostname -s)
```
### Fedora
Fedora systems are managed using Ansible.
TODO
## Home settings
TODO
[determinate nix installer]: https://github.com/DeterminateSystems/nix-installer
[fedora quickstart]: #fedora
[home dotfiles]: #home-dotfiles

View file

@ -1,32 +0,0 @@
#!/usr/bin/env julia
using TOML: TOML
@show ENV["PATH"]
function remove_podman_secrets()
run(`podman secret rm --all`)
return nothing
end #function
function create_podman_secret(name, secret)
run(
addenv(
`podman secret create --env=true --replace $name SECRET`,
Dict("SECRET" => secret),
),
)
return nothing
end #function
function parse_toml_secrets(file)
foreach(f -> create_podman_secret(f[1], f[2]), TOML.parsefile(file))
return nothing
end #function
function main()
remove_podman_secrets()
foreach(parse_toml_secrets, ARGS)
return 0
end #function
main()

View file

@ -1,157 +0,0 @@
# Translated from <https://github.com/Chocobozzz/PeerTube/blob/v8.1.4/support/nginx/peertube>
@api_resumable <<CEL
path_regexp('^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$')
|| path_regexp('^/api/v1/users/[^/]+/imports/import-resumable$')
CEL
handle @api_resumable {
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
header_up X-Real-IP {remote_host}
flush_interval -1
}
}
@api_video_upload_wrong_method <<CEL
path_regexp('^/api/v1/videos/(upload|([^/]+/studio/edit))$')
&& !method('POST', 'HEAD')
CEL
handle @api_video_upload_wrong_method {
header Allow "POST,HEAD"
respond 405
}
@api_video_upload <<CEL
path_regexp('^/api/v1/videos/(upload|([^/]+/studio/edit))$')
&& method('POST', 'HEAD')
CEL
handle @api_video_upload {
request_body {
max_size 12G
}
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
flush_interval -1
header_up X-File-Maximum-Size 8G
}
}
@api_jobs path_regexp ^/api/v1/runners/jobs/[^/]+/(update|success)$
handle @api_jobs {
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
flush_interval -1
}
}
@api_videos path_regexp ^/api/v1/(videos|video-playlists|video-channels|users/me)
handle @api_videos {
request_body {
max_size 12M
}
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
header_up X-File-Maximum-Size 8M
}
}
@websocket_socket_plugins <<CEL
path('/socket.io')
|| path_regexp('^/plugins/[^/]+(/[^/]+)?/ws/')
CEL
handle @websocket_socket_plugins {
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
header_up X-Real-IP {remote_host}
}
}
handle /tracker/socket {
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
header_up X-Real-IP {remote_host}
transport http {
read_timeout 15m
}
}
}
@client_overridable_assets {
path_regexp asset_path ^/client/(assets/images/(default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$
}
handle @client_overridable_assets {
root * {$MILLIRONX_PEERTUBE_ASSETS_DIR}
header Cache-Control "public, max-age=31536000, immutable"
@try_override file {
try_files storage/client-overrides/{re.asset_path.1}
}
handle @try_override {
rewrite * storage/client-overrides/{re.asset_path.1}
file_server
}
@try_dist file {
try_files {re.asset_path.1}
}
handle @try_dist {
rewrite * {re.asset_path.1}
file_server
}
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT}
}
@client_static {
path_regexp asset_path ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$
}
handle @client_static {
root * {$MILLIRONX_PEERTUBE_ASSETS_DIR}
header Cache-Control "public, max-age=31536000, immutable"
rewrite * {re.asset_path.1}
file_server
}
@static_downloads {
path_regexp dl_path ^/static/(webseed|web-videos|redundancy|streaming-playlists)/
}
handle @static_downloads {
@options method OPTIONS
handle @options {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET,OPTIONS"
header Access-Control-Allow-Headers "Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"
header Access-Control-Max-Age 1728000
header Content-Type 'text/plain; charset=UTF-8'
header Content-Length 0
respond 204
}
@get method GET
handle @get {
header Access-Control-Allow-Origin "*"
header Access-Control-Allow-Methods "GET,OPTIONS"
header Access-Control-Allow-Headers "Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type"
uri path_regexp ^/static/webseed/(.*)$ /web-videos/$1
uri path_regexp ^/static/(.*)$ /$1
root * {$MILLIRONX_PEERTUBE_STORAGE_DIR}
@file_exists file {
try_files {path}
}
handle @file_exists {
file_server
}
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
header_up X-Real-IP {remote_host}
}
}
}
request_body {
max_size 100KB
}
reverse_proxy http://127.0.0.1:{$MILLIRONX_PEERTUBE_PORT} {
header_up X-Real-IP {remote_host}
}

24
dotfiles/dolphinrc Normal file
View file

@ -0,0 +1,24 @@
MenuBar=Disabled
[DetailsMode]
PreviewSize=16
[General]
BrowseThroughArchives=true
EditableUrl=true
GlobalViewProps=false
ShowFullPath=true
ShowStatusBar=FullWidth
ShowZoomSlider=true
Version=202
[KFileDialog Settings]
Places Icons Auto-resize=false
Places Icons Static Size=22
[MainWindow]
MenuBar=Disabled
[PreviewSettings]
Plugins=audiothumbnail,avif,blenderthumbnail,comicbookthumbnail,cursorthumbnail,djvuthumbnail,ebookthumbnail,exrthumbnail,directorythumbnail,fontthumbnail,imagethumbnail,jpegthumbnail,jxl,kraorathumbnail,windowsexethumbnail,windowsimagethumbnail,mobithumbnail,opendocumentthumbnail,gsthumbnail,rawthumbnail,svgthumbnail,gdk-pixbuf-thumbnailer,ffmpegthumbs,gsf-office

9
dotfiles/konsolerc Normal file
View file

@ -0,0 +1,9 @@
[Desktop Entry]
DefaultProfile=My Default.profile
[MainWindow]
StatusBar=Disabled
ToolBarsMovable=Disabled
[UiSettings]
ColorScheme=Default

143
flake.lock generated
View file

@ -14,11 +14,11 @@
"systems": "systems"
},
"locked": {
"lastModified": 1770165109,
"narHash": "sha256-9VnK6Oqai65puVJ4WYtCTvlJeXxMzAp/69HhQuTdl/I=",
"lastModified": 1754433428,
"narHash": "sha256-NA/FT2hVhKDftbHSwVnoRTFhes62+7dxZbxj5Gxvghs=",
"owner": "ryantm",
"repo": "agenix",
"rev": "b027ee29d959fda4b60b57566d64c98a202e0feb",
"rev": "9edb1787864c4f59ae5074ad498b6272b3ec308d",
"type": "github"
},
"original": {
@ -27,6 +27,27 @@
"type": "github"
}
},
"crowdsec": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1752497357,
"narHash": "sha256-9epXn1+T6U4Kfyw8B9zMzbERxDB3VfaPXhVebtai6CE=",
"ref": "refs/heads/main",
"rev": "84db7dcea77f7f477d79e69e35fb0bb560232667",
"revCount": 42,
"type": "git",
"url": "https://codeberg.org/kampka/nix-flake-crowdsec.git"
},
"original": {
"type": "git",
"url": "https://codeberg.org/kampka/nix-flake-crowdsec.git"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
@ -48,6 +69,23 @@
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": "systems_2"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"id": "flake-utils",
"type": "indirect"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
@ -55,16 +93,16 @@
]
},
"locked": {
"lastModified": 1772985280,
"narHash": "sha256-FdrNykOoY9VStevU4zjSUdvsL9SzJTcXt4omdEDZDLk=",
"lastModified": 1756679287,
"narHash": "sha256-Xd1vOeY9ccDf5VtVK12yM0FS6qqvfUop8UQlxEB+gTQ=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "8f736f007139d7f70752657dff6a401a585d6cbc",
"rev": "07fc025fe10487dd80f2ec694f1cd790e752d0e8",
"type": "github"
},
"original": {
"owner": "nix-community",
"ref": "release-25.11",
"ref": "release-25.05",
"repo": "home-manager",
"type": "github"
}
@ -76,79 +114,59 @@
]
},
"locked": {
"lastModified": 1772129556,
"narHash": "sha256-Utk0zd8STPsUJPyjabhzPc5BpPodLTXrwkpXBHYnpeg=",
"lastModified": 1757432263,
"narHash": "sha256-qHn+/0+IOz5cG68BZUwL9BV3EO/e9eNKCjH3+N7wMdI=",
"owner": "LnL7",
"repo": "nix-darwin",
"rev": "ebec37af18215214173c98cf6356d0aca24a2585",
"rev": "1fef4404de4d1596aa5ab2bd68078370e1b9dcdb",
"type": "github"
},
"original": {
"owner": "LnL7",
"ref": "nix-darwin-25.11",
"ref": "nix-darwin-25.05",
"repo": "nix-darwin",
"type": "github"
}
},
"nix-rosetta-builder": {
"inputs": {
"nixpkgs": [
"nixpkgs-darwin"
]
},
"locked": {
"lastModified": 1770491098,
"narHash": "sha256-ZfhynJqgV3A9hEivcgOEZa+TZnJPc26lIUjzKsSchgI=",
"owner": "cpick",
"repo": "nix-rosetta-builder",
"rev": "50e6070082e0b4fbaf67dd8f346892a1a9ed685c",
"type": "github"
},
"original": {
"owner": "cpick",
"repo": "nix-rosetta-builder",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1772822230,
"narHash": "sha256-yf3iYLGbGVlIthlQIk5/4/EQDZNNEmuqKZkQssMljuw=",
"lastModified": 1757545623,
"narHash": "sha256-mCxPABZ6jRjUQx3bPP4vjA68ETbPLNz9V2pk9tO7pRQ=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "71caefce12ba78d84fe618cf61644dce01cf3a96",
"rev": "8cd5ce828d5d1d16feff37340171a98fc3bf6526",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-25.11",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-darwin": {
"locked": {
"lastModified": 1766129819,
"narHash": "sha256-crNRwvsbH2XSV8IwBjX6Tm+uWmYwhYyRuNVJ9/ZwlmA=",
"lastModified": 1757590060,
"narHash": "sha256-EWwwdKLMZALkgHFyKW7rmyhxECO74+N+ZO5xTDnY/5c=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "eedcb27bf99430e51f83d896cd1149b828290d20",
"rev": "0ef228213045d2cdb5a169a95d63ded38670b293",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-25.05-darwin",
"repo": "nixpkgs",
"rev": "eedcb27bf99430e51f83d896cd1149b828290d20",
"type": "github"
}
},
"nixpkgs-unstable": {
"locked": {
"lastModified": 1772771118,
"narHash": "sha256-xWzaTvmmACR/SRWtABgI/Z97lcqwJAeoSd5QW1KdK1s=",
"lastModified": 1757034884,
"narHash": "sha256-PgLSZDBEWUHpfTRfFyklmiiLBE1i1aGCtz4eRA3POao=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "e38213b91d3786389a446dfce4ff5a8aaf6012f2",
"rev": "ca77296380960cd497a765102eeb1356eb80fed0",
"type": "github"
},
"original": {
@ -166,11 +184,11 @@
]
},
"locked": {
"lastModified": 1773029295,
"narHash": "sha256-xmHhVHbaA5hR3dCEoGwqAgL6HTTJ0KEMRUTLdJuVtGM=",
"lastModified": 1757647720,
"narHash": "sha256-qf/utP3d1qBDl5R4yWUCt7E7CHTkw2NY8BEsS7lJ0dc=",
"owner": "nix-community",
"repo": "NUR",
"rev": "bf45b24de2134f1488f7a6c135f4b0420ccec6fe",
"rev": "ef767aa25f9f917fe25d3848051f0e54ae42349f",
"type": "github"
},
"original": {
@ -189,11 +207,11 @@
]
},
"locked": {
"lastModified": 1772361940,
"narHash": "sha256-B1Cz+ydL1iaOnGlwOFld/C8lBECPtzhiy/pP93/CuyY=",
"lastModified": 1756632588,
"narHash": "sha256-ydam6eggXf3ZwRutyCABwSbMAlX+5lW6w1SVZQ+kfSo=",
"owner": "nix-community",
"repo": "plasma-manager",
"rev": "a4b33606111c9c5dcd10009042bb710307174f51",
"rev": "d47428e5390d6a5a8f764808a4db15929347cd77",
"type": "github"
},
"original": {
@ -204,11 +222,11 @@
},
"quadlet-nix": {
"locked": {
"lastModified": 1770606362,
"narHash": "sha256-6pOOPOQr4rtgShBtkLkSDTql5rRqcUgTRz8O+axK2eM=",
"lastModified": 1754008153,
"narHash": "sha256-MYT1mDtSkiVg343agxgBFsnuNU3xS8vRy399JXX1Vw0=",
"owner": "SEIAROTg",
"repo": "quadlet-nix",
"rev": "f4ae60350ea6015b6560cbd0e1f11f7e195c993d",
"rev": "1b2d27d460d8c7e4da5ba44ede463b427160b5c4",
"type": "github"
},
"original": {
@ -220,9 +238,9 @@
"root": {
"inputs": {
"agenix": "agenix",
"crowdsec": "crowdsec",
"home-manager": "home-manager",
"nix-darwin": "nix-darwin",
"nix-rosetta-builder": "nix-rosetta-builder",
"nixpkgs": "nixpkgs",
"nixpkgs-darwin": "nixpkgs-darwin",
"nixpkgs-unstable": "nixpkgs-unstable",
@ -240,11 +258,11 @@
},
"locked": {
"dir": "pkgs/firefox-addons",
"lastModified": 1773028978,
"narHash": "sha256-4BjOTYhHP8ljHShQyZ1gUIdwgSLjvaGN2ueKfqp6CQk=",
"lastModified": 1757591399,
"narHash": "sha256-OlvNzfsqDok0y5PDY+2dK5T53GsxAdm1YGdYHjxAiHM=",
"owner": "rycee",
"repo": "nur-expressions",
"rev": "a6ed037ffc0b50a9bd0c92e20e31f270a03ca1e3",
"rev": "b7d4f61ce9db44ba82859e15f6e1c175959948e3",
"type": "gitlab"
},
"original": {
@ -268,6 +286,21 @@
"repo": "default",
"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",

View file

@ -3,10 +3,8 @@
inputs = {
# Specify the source of Home Manager and Nixpkgs.
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.11";
# Revert to a cached version of Julia for aarch64-darwin
nixpkgs-darwin.url =
"github:nixos/nixpkgs/eedcb27bf99430e51f83d896cd1149b828290d20";
nixpkgs.url = "github:nixos/nixpkgs/nixos-25.05";
nixpkgs-darwin.url = "github:nixos/nixpkgs/nixpkgs-25.05-darwin";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# Inputs for both darwin and linux systems
@ -19,7 +17,7 @@
};
};
home-manager = {
url = "github:nix-community/home-manager/release-25.11";
url = "github:nix-community/home-manager/release-25.05";
inputs.nixpkgs.follows = "nixpkgs";
};
nur = {
@ -32,6 +30,10 @@
};
# Linux-specific inputs
crowdsec = {
url = "git+https://codeberg.org/kampka/nix-flake-crowdsec.git";
inputs.nixpkgs.follows = "nixpkgs";
};
plasma-manager = {
url = "github:nix-community/plasma-manager";
inputs = {
@ -43,28 +45,23 @@
# Darwin-specific inputs
nix-darwin = {
url = "github:LnL7/nix-darwin/nix-darwin-25.11";
inputs.nixpkgs.follows = "nixpkgs-darwin";
};
nix-rosetta-builder = {
url = "github:cpick/nix-rosetta-builder";
url = "github:LnL7/nix-darwin/nix-darwin-25.05";
inputs.nixpkgs.follows = "nixpkgs-darwin";
};
};
outputs = { self, nix-darwin, nixpkgs, nixpkgs-darwin, nixpkgs-unstable
, home-manager, agenix, rycee-nurpkgs, nur, plasma-manager, quadlet-nix
, nix-rosetta-builder, ... }:
, home-manager, agenix, rycee-nurpkgs, nur, crowdsec, plasma-manager
, quadlet-nix, ... }:
let
mkHomeConfiguration = { hostname, arch ? "x86_64", os ? "linux"
, desktop ? false, extraModules ? [ ] }:
let
system = "${arch}-${os}";
syspkg = if os == "darwin" then nixpkgs-darwin else nixpkgs;
pkgs = import syspkg {
pkgs = import nixpkgs {
inherit system;
config.allowUnfree = true;
overlays = [ nur.overlays.default agenix.overlays.default ];
overlays = [ nur.overlays.default ];
};
pkgs-unstable = import nixpkgs-unstable {
inherit system;
@ -83,8 +80,7 @@
] ++ (if desktop then [ ./homes/desktop.nix ] else [ ])
++ (if (desktop && os == "linux") then [
./homes/linux-desktop.nix
plasma-manager.homeModules.plasma-manager
quadlet-nix.homeManagerModules.quadlet
plasma-manager.homeManagerModules.plasma-manager
] else
[ ]) ++ extraModules;
extraSpecialArgs = {
@ -92,7 +88,6 @@
inherit firefox-addons;
inherit buildFirefoxXpiAddon;
inherit custom-pkgs;
inherit hostname;
};
};
in {
@ -106,12 +101,16 @@
"millironx@anderson" = mkHomeConfiguration { hostname = "anderson"; };
"millironx@mcentire" = mkHomeConfiguration { hostname = "mcentire"; };
"millironx@bosephus" = mkHomeConfiguration { hostname = "bosephus"; };
"tchristensen@beocat" = mkHomeConfiguration { hostname = "beocat"; };
"millironx@harmony" = mkHomeConfiguration {
hostname = "harmony";
arch = "aarch64";
desktop = true;
};
"millironx@odyssey" = mkHomeConfiguration {
hostname = "odyssey";
desktop = true;
@ -127,11 +126,8 @@
};
agenix = agenix;
};
modules = [
./systems/darwin/corianne.nix
agenix.darwinModules.default
nix-rosetta-builder.darwinModules.default
];
modules =
[ ./systems/darwin/corianne.nix agenix.darwinModules.default ];
};
nixosConfigurations = {
@ -146,14 +142,13 @@
"mcentire" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = {
home-manager-quadlet-nix = quadlet-nix.homeManagerModules.quadlet;
};
modules = [
./systems/linux/mcentire.nix
agenix.nixosModules.default
home-manager.nixosModules.home-manager
quadlet-nix.nixosModules.quadlet
crowdsec.nixosModules.crowdsec
crowdsec.nixosModules.crowdsec-firewall-bouncer
{ nixpkgs.overlays = [ crowdsec.overlays.default ]; }
];
};
};

View file

@ -1,4 +1,13 @@
{ config, pkgs, ... }: {
{ config, lib, pkgs, pkgs-unstable, ... }:
let
runic_version = "1.5.0";
runic = pkgs.fetchFromGitHub {
owner = "fredrikekre";
repo = "Runic.jl";
rev = "v${runic_version}";
hash = "sha256-y+kiBA94vUMHH0fEEBg7+c9PEgzjGqh6nCuSRnawhQI=";
};
in {
imports = [
./../programs/shells.nix
./../programs/bat.nix
@ -6,15 +15,22 @@
./../programs/git.nix
./../programs/lsd.nix
./../programs/neovim.nix
./../programs/ssh.nix
./../programs/starship.nix
./../programs/tmux.nix
./../programs/yt-dlp.nix
];
home = {
stateVersion = "23.11";
file = {
".local/bin/runic" = {
source = runic + "/bin/runic";
executable = true;
};
".local/bin/git-runic" = {
source = runic + "/bin/git-runic";
executable = true;
};
};
packages = with pkgs; [
agenix
btop
cowsay
figlet
@ -28,12 +44,14 @@
jq
julia-bin
lynx
mamba-cpp
micromamba
most
nextflow
p7zip
pdfgrep
pipx
python3
zulu17
];
sessionVariables = {
PAGER = "most";
@ -65,10 +83,24 @@
"tailscale set --exit-node=$(tailscale exit-node suggest | awk '{print $4}' | head -n1)";
# tsed - TailScale Exit node Disconnect
tsed = "tailscale set --exit-node=";
micromamba = "mamba";
conda = "mamba";
};
sessionPath = [ "$HOME/.local/bin" ];
activation = {
recordHmGitHash = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
cd "$HOME/.config/home-manager" || exit 1
if [ -z "$(${pkgs.git}/bin/git status --porcelain --untracked-files=no)" ]; then
run echo "$(${pkgs.git}/bin/git rev-parse HEAD)" | tee $HOME/.cache/hm-git-hash
else
run echo '*' | tee $HOME/.cache/hm-git-hash
fi
'';
installRunic = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run ${pkgs.julia-bin}/bin/julia --project=@runic --startup-file=no -e 'using Pkg; Pkg.add(name="Runic", version="${runic_version}")'
'';
installJuliaFormatter = lib.hm.dag.entryAfter [ "writeBoundary" ] ''
run ${pkgs.julia-bin}/bin/julia --project=@JuliaFormatter --startup-file=no -e 'using Pkg; Pkg.add(name="JuliaFormatter", version="2.1.6")'
'';
};
};
programs = {
home-manager.enable = true;

View file

@ -7,7 +7,7 @@
];
home = {
packages = with pkgs; [
macpm
asitop
pinentry_mac
(pkgs.writeShellScriptBin "uq" ''
xattr -rdv com.apple.quarantine "/Applications/$1.app"
@ -31,6 +31,114 @@
tailscale = "/Applications/Tailscale.app/Contents/MacOS/Tailscale";
};
};
launchd = {
enable = true;
agents = {
ollama = {
enable = true;
config = {
Label = "local.home-manager.ollama";
ProgramArguments = [ "${pkgs.ollama}/bin/ollama" "serve" ];
RunAtLoad = true;
KeepAlive = true;
StandardOutPath =
"${config.home.homeDirectory}/Library/Logs/ollama.log";
StandardErrorPath =
"${config.home.homeDirectory}/Library/Logs/ollama-error.log";
EnvironmentVariables = {
PATH = "${lib.makeBinPath [ pkgs.ollama ]}:$PATH";
};
};
};
freetube-sync = {
enable = true;
config = {
Label = "local.home-manager.freetube-sync";
ProgramArguments = [
"/bin/sh"
"-c"
''
FREETUBE_CONFIG_DIR="$HOME/Library/Application Support/FreeTube"
NEXTCLOUD_DIR="$HOME/Nextcloud/Settings/FreeTube"
# Create directories if they don't exist
mkdir -p "$FREETUBE_CONFIG_DIR"
mkdir -p "$NEXTCLOUD_DIR"
# List of database files to sync
DB_FILES=("settings.db" "history.db" "playlists.db" "subscriptions.db" "profiles.db")
# Initial sync if Nextcloud has db files but FreeTube doesn't
if [ -n "$(ls $NEXTCLOUD_DIR/*.db 2>/dev/null)" ] && [ ! -n "$(ls $FREETUBE_CONFIG_DIR/*.db 2>/dev/null)" ]; then
echo "Performing initial sync from Nextcloud to FreeTube"
for DB_FILE in "''${DB_FILES[@]}"; do
if [ -f "$NEXTCLOUD_DIR/$DB_FILE" ]; then
rsync -av --checksum "$NEXTCLOUD_DIR/$DB_FILE" "$FREETUBE_CONFIG_DIR/$DB_FILE"
fi
done
# If Nextcloud is empty but FreeTube has db files, copy to Nextcloud
elif [ ! -n "$(ls $NEXTCLOUD_DIR/*.db 2>/dev/null)" ] && [ -n "$(ls $FREETUBE_CONFIG_DIR/*.db 2>/dev/null)" ]; then
echo "Performing initial sync from FreeTube to Nextcloud"
for DB_FILE in "''${DB_FILES[@]}"; do
if [ -f "$FREETUBE_CONFIG_DIR/$DB_FILE" ]; then
rsync -av --checksum "$FREETUBE_CONFIG_DIR/$DB_FILE" "$NEXTCLOUD_DIR/$DB_FILE"
fi
done
else
# Process each database file individually
for DB_FILE in "''${DB_FILES[@]}"; do
FT_PATH="$FREETUBE_CONFIG_DIR/$DB_FILE"
NC_PATH="$NEXTCLOUD_DIR/$DB_FILE"
# Skip if neither file exists
if [ ! -f "$FT_PATH" ] && [ ! -f "$NC_PATH" ]; then
continue
fi
# If only one exists, copy it
if [ -f "$FT_PATH" ] && [ ! -f "$NC_PATH" ]; then
echo "Copying $DB_FILE from FreeTube to Nextcloud"
rsync -av "$FT_PATH" "$NC_PATH"
continue
fi
if [ ! -f "$FT_PATH" ] && [ -f "$NC_PATH" ]; then
echo "Copying $DB_FILE from Nextcloud to FreeTube"
rsync -av "$NC_PATH" "$FT_PATH"
continue
fi
# Both files exist, check which is newer
if [ "$FT_PATH" -nt "$NC_PATH" ]; then
echo "Syncing newer $DB_FILE from FreeTube to Nextcloud"
rsync -av --update "$FT_PATH" "$NC_PATH"
elif [ "$NC_PATH" -nt "$FT_PATH" ]; then
echo "Syncing newer $DB_FILE from Nextcloud to FreeTube"
rsync -av --update "$NC_PATH" "$FT_PATH"
else
# Same modification time, compare checksums
echo "Verifying $DB_FILE with checksum comparison"
rsync -av --checksum "$NC_PATH" "$FT_PATH"
rsync -av --checksum "$FT_PATH" "$NC_PATH"
fi
done
fi
''
];
RunAtLoad = true;
StartInterval = 300; # Run every 5 minutes (300 seconds)
StandardOutPath =
"${config.home.homeDirectory}/Library/Logs/freetube-sync.log";
StandardErrorPath =
"${config.home.homeDirectory}/Library/Logs/freetube-sync-error.log";
EnvironmentVariables = {
PATH = "${lib.makeBinPath [ pkgs.rsync pkgs.findutils ]}:$PATH";
};
};
};
};
};
programs = {
bash = {
profileExtra = ''

View file

@ -2,9 +2,9 @@
imports = [
./../programs/firefox.nix
./../programs/ghostty.nix
./../programs/zed.nix
./../services/gpg-agent.nix
./../services/syncthing.nix
];
home = {
@ -23,7 +23,7 @@
nil
nixd
nixfmt-classic
nixos-rebuild
ollama
quarto
roboto-slab
shellcheck
@ -31,8 +31,6 @@
woodpecker-cli
(texlive.combine { inherit (texlive) scheme-basic latex-bin latexmk; })
custom-pkgs.ark
custom-pkgs.jlfmt
custom-pkgs.runic
];
shellAliases = {
code = "codium";

95
homes/harmony.nix Normal file
View file

@ -0,0 +1,95 @@
{ config, lib, pkgs, pkgs-unstable, ... }: {
# harmony is an Asahi Fedora box
# I don't use NixOS, so there are some programs that don't interact well with
# the base system (or won't even install) when installed from Nix.
# There is no uniform way to trigger dnf package installs from Nix, so I'm
# just going to list my packages here. I hope to create a custom script that
# mimics the ideas of a Brewfile someday
# TODO: Create a Brewfile equivalent for dnf
# dnf repos:
# https://github.com/terrapkg/packages?tab=readme-ov-file
# https://pkgs.tailscale.com/stable/fedora/tailscale.repo
# https://packagecloud.io/filips/FirefoxPWA
# copr repos:
# iucar/rstudio
# dnf packages:
# apptainer
# chromium
# firefoxpwa - The nix version installs an "immutable" runtime, which simply launches extra browser windows on non-NixOS
# inkscape
# kate
# kdiff3
# krita
# lutris
# musescore
# nextcloud-client
# nextcloud-client-dolphin
# obs-studio
# podman-compose
# podman-docker
# qownnotes
# qt
# rssguard
# rstudio-desktop
# steam
# supertuxkart
# tailscale
# thunderbird
# vlc
# vorta - The vorta package is aarch64 compatible, but you cannot see any icons, and it cannot access local ssh keys, so we have to use the dnf package instead
# yakuake
# zed
# zsh
# R
# https://downloads.sourceforge.net/project/mscorefonts2/rpms/msttcore-fonts-installer-2.6-1.noarch.rpm
home = {
username = "millironx";
homeDirectory = "/home/millironx";
# Signal desktop is not available in any other package repository for aarch64 linux
# Similarly, Bitwarden is non-functional in all other forms using a 16k page size
packages = with pkgs; [
trayscale
veracrypt
pkgs-unstable.signal-desktop
pkgs.bitwarden-desktop
];
};
programs = {
git = {
signing = {
key = "0x37A3041D1C8C4524!";
signByDefault = true;
};
};
};
services = {
gpg-agent = { sshKeys = [ "207D13371E19752A67AA2686C16354D9963821DB" ]; };
};
xdg = {
configFile = {
"nextflow.config".text = ''
params {
config_profile_description = 'harmony Asahi Linux local profile'
config_profile_contact = 'Thomas A. Christensen II <25492070+MillironX@users.noreply.github.com>'
config_profile_url = null
max_memory = 12.GB
max_cpus = 12
max_time = 7.d
}
apptainer {
enabled = true
autoMounts = true
}
process {
executor = 'local'
}
'';
};
};
}

View file

@ -18,16 +18,115 @@ in {
'';
};
};
systemd.user = {
services = {
freetube-sync = {
Unit = {
Description = "Sync FreeTube settings with Nextcloud";
After = [ "network.target" ];
};
Service = {
Type = "oneshot";
ExecStart = "${pkgs.writeShellScript "freetube-sync.sh" ''
FREETUBE_CONFIG_DIR="$HOME/.var/app/io.freetubeapp.FreeTube/config/FreeTube"
NEXTCLOUD_DIR="$HOME/Nextcloud/Settings/FreeTube"
# Create directories if they don't exist
mkdir -p "$FREETUBE_CONFIG_DIR"
mkdir -p "$NEXTCLOUD_DIR"
# List of database files to sync
DB_FILES=("settings.db" "history.db" "playlists.db" "subscriptions.db" "profiles.db")
# Initial sync if Nextcloud has db files but FreeTube doesn't
if [ -n "$(ls -A $NEXTCLOUD_DIR/*.db 2>/dev/null)" ] && [ ! -n "$(ls -A $FREETUBE_CONFIG_DIR/*.db 2>/dev/null)" ]; then
echo "Performing initial sync from Nextcloud to FreeTube"
for DB_FILE in "''${DB_FILES[@]}"; do
if [ -f "$NEXTCLOUD_DIR/$DB_FILE" ]; then
rsync -av --checksum "$NEXTCLOUD_DIR/$DB_FILE" "$FREETUBE_CONFIG_DIR/$DB_FILE"
fi
done
# If Nextcloud is empty but FreeTube has db files, copy to Nextcloud
elif [ ! -n "$(ls -A $NEXTCLOUD_DIR/*.db 2>/dev/null)" ] && [ -n "$(ls -A $FREETUBE_CONFIG_DIR/*.db 2>/dev/null)" ]; then
echo "Performing initial sync from FreeTube to Nextcloud"
for DB_FILE in "''${DB_FILES[@]}"; do
if [ -f "$FREETUBE_CONFIG_DIR/$DB_FILE" ]; then
rsync -av --checksum "$FREETUBE_CONFIG_DIR/$DB_FILE" "$NEXTCLOUD_DIR/$DB_FILE"
fi
done
else
# Process each database file individually
for DB_FILE in "''${DB_FILES[@]}"; do
FT_PATH="$FREETUBE_CONFIG_DIR/$DB_FILE"
NC_PATH="$NEXTCLOUD_DIR/$DB_FILE"
# Skip if neither file exists
if [ ! -f "$FT_PATH" ] && [ ! -f "$NC_PATH" ]; then
continue
fi
# If only one exists, copy it
if [ -f "$FT_PATH" ] && [ ! -f "$NC_PATH" ]; then
echo "Copying $DB_FILE from FreeTube to Nextcloud"
rsync -av "$FT_PATH" "$NC_PATH"
continue
fi
if [ ! -f "$FT_PATH" ] && [ -f "$NC_PATH" ]; then
echo "Copying $DB_FILE from Nextcloud to FreeTube"
rsync -av "$NC_PATH" "$FT_PATH"
continue
fi
# Both files exist, check which is newer
if [ "$FT_PATH" -nt "$NC_PATH" ]; then
echo "Syncing newer $DB_FILE from FreeTube to Nextcloud"
rsync -av --update "$FT_PATH" "$NC_PATH"
elif [ "$NC_PATH" -nt "$FT_PATH" ]; then
echo "Syncing newer $DB_FILE from Nextcloud to FreeTube"
rsync -av --update "$NC_PATH" "$FT_PATH"
else
# Same modification time, compare checksums
echo "Verifying $DB_FILE with checksum comparison"
rsync -av --checksum "$NC_PATH" "$FT_PATH"
rsync -av --checksum "$FT_PATH" "$NC_PATH"
fi
done
fi
''}";
};
};
};
timers = {
freetube-sync = {
Unit = { Description = "Timer for FreeTube settings sync"; };
Timer = {
OnBootSec = "1m";
OnUnitActiveSec = "5m";
};
Install = { WantedBy = [ "timers.target" ]; };
};
};
};
xdg = {
configFile = {
"plasma-workspace/env/ZED_WINDOW_DECORATIONS.sh".text =
"export ZED_WINDOW_DECORATIONS=server";
"dolphinrc".source =
mkOutOfStoreSymlink "${home-manager-repo}/dotfiles/dolphinrc";
"konsolerc".source =
mkOutOfStoreSymlink "${home-manager-repo}/dotfiles/konsolerc";
"onedrive/config".text = ''
force_session_upload = "true"
delay_inotify_processing = "true"
'';
"yakuakerc".source =
mkOutOfStoreSymlink "${home-manager-repo}/dotfiles/yakuakerc";
};
dataFile = {
"konsole/My Default.profile".source =
mkOutOfStoreSymlink "${home-manager-repo}/dotfiles/MyDefault.profile";
"kio/servicemenus/kate.desktop".source = ./../dotfiles/kate.desktop;
"kio/servicemenus/vlc.desktop".source = ./../dotfiles/vlc.desktop;
"kio/servicemenus/word-to-pdf.desktop".source =

View file

@ -1,8 +0,0 @@
{ ... }: {
home = {
username = "millironx";
homeDirectory = "/home/millironx";
};
programs = { };
services = { };
}

View file

@ -14,23 +14,6 @@
services = {
gpg-agent = { sshKeys = [ "F72C07DBA3DC0903C3ABB55E8B460803FEC22640" ]; };
};
virtualisation.quadlet = {
containers = {
anythingllm = {
autoStart = true;
containerConfig = {
image = "docker.io/mintplexlabs/anythingllm:latest";
addHosts = [ "ollama.millironx.local:host-gateway" ];
publishPorts = [ "3001:3001" ];
volumes =
[ "${config.xdg.dataHome}/anythingllm:/app/server/storage:Z" ];
environments = { STORAGE_DIR = "/app/server/storage"; };
};
};
};
autoUpdate.enable = true;
};
xdg = {
configFile = {
"nextflow.config".text = ''

View file

@ -3,13 +3,20 @@ ungrouped:
hosts:
localhost:
ansible_connection: local
harmony:
ansible_connection: local
odyssey:
ansible_connection: local
asahi:
hosts:
harmony:
amd64:
hosts:
odyssey:
fedora:
hosts:
harmony:
odyssey:

View file

@ -1,167 +0,0 @@
# Warning to my future self: This module was "vibe coded" by Claude Sonnet 4.5.
# I originally had a hand-written module in here that did the secrets
# translation as the root user. I knew that I would want to support multiple
# secrets files and multiple users, and thought I should be able to create an
# arbitrary number of them, similar to how you can have an arbitrary number
# of `programs.firefox.profiles`. Unfortunately, even after looking at lots of
# example modules, I could not figure out the syntax (Nix has a serious lack
# of minimum working examples), so I broke down and asked Claude to rewrite it
# for me. Based on everything that I read, this seems to be exactly what I asked
# for.
#
# Here is the prompt I used to get here:
# [@Per service isolation on NixOS with Traefik](zed:///agent/thread/94cb8a22-ff0c-4772-a1a1-018b0107f334?name=Per+service+isolation+on+NixOS+with+Traefik)
#
# [@podman-secrets.nix](file:///home/millironx/.config/home-manager/modules/podman-secrets.nix)
#
# I originally wrote this module assuming that I would use Podman as root for
# all containers. I would like to fix the module to have as many secrets files
# processed as needed with services setup for each secret that is run as the
# appropriate user. Ideally, the syntax for working with secrets to be
# translated would be
#
# ```nix
# { config, ... }: {
# age.secrets = {
# "caddy.toml" = {
# file = ./../secrets/caddy.toml.age;
# owner = "caddy";
# group = "caddy";
# };
#
# "authentik.toml" = {
# file = ./../secrets/authentik.toml.age;
# owner = "authentik";
# group = "authentik";
# };
#
# "freshrss.toml" = {
# file = ./../secrets/freshrss.toml.age;
# owner = "freshrss";
# group = "freshrss";
# };
# };
#
# millironx.podman-secrets = with config; {
# caddy = {
# user = "caddy";
# secrets-files = [
# ./../not-really-secret.toml
# age.secrets."caddy.toml".path
# ];
# };
# authentik = {
# user = "authentik";
# secrets-files = [
# age.secrets."authentik.toml".path
# ];
# };
# freshrss = {
# user = "freshrss";
# secrets-files = [
# age.secrets."freshrss.toml".path
# ];
# };
# };
# }
# ```
#
# Can you help me rewrite the module to accomplish this?
#
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.millironx.podman-secrets;
secret-translator = pkgs.writeScriptBin "secret-translator"
(builtins.readFile ./../bin/secret-translator.jl);
# Submodule type for each service's secrets configuration
serviceSecretsType = types.submodule ({ config, name, ... }: {
options = {
user = mkOption {
type = types.str;
description = "User account to run the secrets translation service as";
example = "caddy";
};
secrets-files = mkOption {
type = types.listOf (types.either types.path types.string);
description =
"List of TOML files containing secrets to translate to Podman secrets";
example = literalExpression ''
[
"/run/agenix/caddy.toml"
./../not-really-secret.toml
]
'';
default = [ ];
};
ref = mkOption {
type = types.str;
description =
"Reference to the systemd service name for declaring dependencies";
readOnly = true;
default = "podman-secrets-${name}.service";
example = "podman-secrets-caddy.service";
};
};
});
# Generate a systemd user service for each configured service
mkSecretsService = name: serviceCfg:
nameValuePair "podman-secrets-${name}" {
description = "Podman secrets converter service for ${name}";
wantedBy = [ "default.target" ];
unitConfig.ConditionUser = "${serviceCfg.user}";
serviceConfig = {
Type = "oneshot";
ProtectProc = "invisible";
ExecStart = "${secret-translator}/bin/secret-translator ${
lib.concatStringsSep " " serviceCfg.secrets-files
}";
Environment = "PATH=/run/wrappers/bin:${
lib.makeBinPath (with pkgs; [ shadow podman julia-lts-bin ])
}";
};
};
# Filter out services with no secrets-files
enabledServices =
lib.filterAttrs (name: serviceCfg: serviceCfg.secrets-files != [ ]) cfg;
in {
options.millironx.podman-secrets = mkOption {
type = types.attrsOf serviceSecretsType;
description = ''
Per-service Podman secrets configuration.
Each attribute creates a separate systemd user service that translates TOML secrets
files into Podman secrets.
'';
example = literalExpression ''
{
caddy = {
user = "caddy";
secrets-files = [
./../not-really-secret.toml
config.age.secrets."caddy.toml".path
];
};
authentik = {
user = "authentik";
secrets-files = [
config.age.secrets."authentik.toml".path
];
};
}
'';
default = { };
};
config = mkIf (enabledServices != { }) {
systemd.user.services = lib.mapAttrs' mkSecretsService enabledServices;
};
}

View file

@ -1,7 +1,6 @@
{ pkgs, ... }:
with pkgs; {
ark = callPackage ./ark.nix { };
jlfmt = callPackage ./jlfmt.nix { };
runic = callPackage ./runic.nix { };
sc4pac = callPackage ./sc4pac.nix { };
{
ark = pkgs.callPackage ./ark.nix { };
sc4pac = pkgs.callPackage ./sc4pac.nix { };
}

View file

@ -1,12 +0,0 @@
{ pkgs, ... }:
with pkgs;
let
juliaWithPkgs = julia-bin.withPackages.override { setDefaultDepot = false; }
[ "JuliaFormatter" ];
depotWithPkgs = runCommand "getDepot" { } ''
${juliaWithPkgs}/bin/julia -e 'println(first(DEPOT_PATH))' | tee $out
'';
in writeShellScriptBin "jlfmt" ''
export JULIA_DEPOT_PATH=$(< ${depotWithPkgs})
exec ${juliaWithPkgs}/bin/julia --startup-file=no -e 'using JuliaFormatter; print(format_text(String(read(stdin))));' -- "$@"
''

View file

@ -1,12 +0,0 @@
{ pkgs, ... }:
with pkgs;
let
juliaWithRunic =
julia-bin.withPackages.override { setDefaultDepot = false; } [ "Runic" ];
depotWithRunic = runCommand "getRunicDepot" { } ''
${juliaWithRunic}/bin/julia -e 'println(first(DEPOT_PATH))' | tee $out
'';
in writeShellScriptBin "runic" ''
export JULIA_DEPOT_PATH=$(< ${depotWithRunic})
exec ${juliaWithRunic}/bin/julia --startup-file=no -e 'using Runic; exit(Runic.main(ARGS))' -- "$@"
''

View file

@ -11,7 +11,7 @@
mode: "755"
- name: Create Firefox DNS policy
ansible.builtin.template:
src: "policies.json"
src: "{{ playbook_dir }}/../templates/policies.json"
dest: /etc/firefox/policies/policies.json
mode: "644"

View file

@ -45,8 +45,7 @@
register: home_manager_exists
- name: Init home-manager
ansible.builtin.shell: |
/nix/var/nix/profiles/default/bin/nix run home-manager -- switch \
--flake git+https://code.millironx.com/millironx/nix-dotfiles#{{ ansible_user_id }}@{{ ansible_hostname }}
/nix/var/nix/profiles/default/bin/nix run home-manager -- switch --flake git+https://code.millironx.com/millironx/nix-dotfiles#{{ ansible_user_id }}@{{ ansible_hostname }}
when: not home_manager_exists.stat.exists
register: home_manager_init
changed_when: home_manager_init.rc == 0

View file

@ -1,33 +1,78 @@
---
- name: Configure dnf packages
# These are repos and packages that are useless or unavailable on Asahi Linux,
# or have completely separate install procedures.
- name: Configure amd64-specific dnf packages
hosts: amd64
become: true
tasks:
- name: Install x86-specific dnf packages
ansible.builtin.dnf:
name:
- libdvdcss
- mkvtoolnix
- mpv
- protontricks
- x264
- x264-libs
state: present
- name: Install VeraCrypt
ansible.builtin.dnf:
name: https://launchpad.net/veracrypt/trunk/1.26.20/+download/veracrypt-1.26.20-Fedora-40-x86_64.rpm
state: present
disable_gpg_check: true
- name: Configure amd64-specific Flatpaks
hosts: amd64
become: false
tasks:
- name: Install x86-specific Flatpaks
community.general.flatpak:
name:
- com.bitwarden.desktop
- com.slack.Slack
- dev.deedles.Trayscale
- org.signal.Signal
state: latest
method: user
remote: flathub
- name: Configure Asahi Linux-specific dnf packages
hosts: asahi
become: true
tasks:
- name: Install aarch64-specific dnf packages
ansible.builtin.dnf:
name:
- veracrypt
- name: Configure common (all arch) dnf packages
hosts: fedora
become: true
tasks:
- name: Install dnf packages
- name: Install common (all arch) dnf packages
ansible.builtin.dnf:
name:
- chromium
- firefoxpwa
- fontconfig-devel
- freetype-devel
- fribidi-devel
- ghostty
- inkscape
- jq
- kate
- kdenlive
- kdiff3
- krita
- libdvdcss
- libjpeg-devel
- libpng-devel
- libtiff-devel
- libwebp-devel
- mkvtoolnix
- mpv
- musescore
- nextcloud-client
- nextcloud-client-dolphin
- obs-studio
- onedrive
- protontricks
- qownnotes
- qt
- rssguard
@ -37,8 +82,6 @@
- thunderbird
- vlc
- vorta
- x264
- x264-libs
- yakuake
- zed
- zsh
@ -49,11 +92,6 @@
name: https://downloads.sourceforge.net/project/mscorefonts2/rpms/msttcore-fonts-installer-2.6-1.noarch.rpm
state: present
disable_gpg_check: true
- name: Install VeraCrypt
ansible.builtin.dnf:
name: https://launchpad.net/veracrypt/trunk/1.26.20/+download/veracrypt-1.26.20-Fedora-40-x86_64.rpm
state: present
disable_gpg_check: true
- name: Install rig (R installation manager)
ansible.builtin.dnf:
name: https://github.com/r-lib/rig/releases/download/latest/r-rig-latest-1.{{ ansible_architecture }}.rpm
@ -71,23 +109,21 @@
name: "*"
state: latest # noqa: package-latest
- name: Configure Flatpaks
- name: Configure common (all arch) Flatpaks
hosts: fedora
become: false
tasks:
- name: Install Flatpaks
- name: Install common (all arch) Flatpaks
community.general.flatpak:
name:
- com.bitwarden.desktop
- com.github.tchx84.Flatseal
- com.logseq.Logseq
- com.slack.Slack
- dev.deedles.Trayscale
- io.freetubeapp.FreeTube
- io.github.alainm23.planify
- io.github.dweymouth.supersonic
- io.openrct2.OpenRCT2
- org.signal.Signal
- org.zulip.Zulip
- net.ankiweb.Anki
state: latest
method: user
remote: flathub

View file

@ -1,6 +1,6 @@
---
- name: Configure dnf package repositories
hosts: fedora
- name: Configure amd64-specific package repositories
hosts: amd64
become: true
tasks:
- name: Install RPM Fusion free repository
@ -20,6 +20,31 @@
- name: Install Zotero COPR repository
community.general.copr:
name: "mozes/zotero7"
# Asahi Linux comes with its own strange version of RPMFusion installed, so
# RPMFusion is installed only on amd64 systems. In addition, VeraCrypt and
# Zotero *are* available via COPR, but from different repos than their amd64
# counterparts.
# Also, Asahi has its own version string, so we have to manually specify the
# chroot for COPR repos added via Ansible. This is handled automatically when
# using `dnf copr enable ...`, but not via Ansible.
- name: Configure Asahi Linux-specific package repositories
hosts: asahi
become: true
tasks:
- name: Install Zotero COPR repository
community.general.copr:
name: "isaksamsten/Zotero"
chroot: "fedora-{{ ansible_distribution_major_version }}-aarch64"
- name: Install VeraCrypt COPR repository
community.general.copr:
name: "architektapx/veracrypt"
chroot: "fedora-{{ ansible_distribution_major_version }}-aarch64"
- name: Configure common (all arch) package repositories
hosts: fedora
become: true
tasks:
- name: Install Tailscale repo
ansible.builtin.yum_repository:
name: tailscale-stable
@ -28,6 +53,14 @@
enabled: true
gpgcheck: true
gpgkey: https://pkgs.tailscale.com/stable/fedora/repo.gpg
- name: Install FirefoxPWA repository
ansible.builtin.yum_repository:
name: firefoxpwa
description: FirefoxPWA repository
baseurl: https://packagecloud.io/filips/FirefoxPWA/fedora/$releasever/$basearch
gpgcheck: true
gpgkey: https://packagecloud.io/filips/FirefoxPWA/gpgkey
enabled: true
# Note that I still have to specify the chroot for COPR repos b/c of Asahi
- name: Install RStudio copr repository
community.general.copr:
@ -77,7 +110,7 @@
register: terra_priority
changed_when: terra_priority.rc != 0
- name: Configure Flatpack remotes
- name: Configure Flathub remote
hosts: fedora
become: false
tasks:

View file

@ -1,7 +1,7 @@
{ ... }: {
defaults."AltTab" = {
appearanceStyle = 0;
nextWindowGesture = 3;
nextWindowGesture = 1;
screenRecordingPermissionSkipped = true;
showFullscreenWindows = 0;
showHiddenWindows = 1;

View file

@ -1,4 +1,4 @@
{ pkgs, firefox-addons, buildFirefoxXpiAddon, lib, ... }: {
{ firefox-addons, buildFirefoxXpiAddon, lib, ... }: {
programs.firefox = {
enable = true;
package =
@ -31,48 +31,49 @@
};
};
containersForce = true;
extensions.packages = with firefox-addons;
[
bitwarden
multi-account-containers
old-reddit-redirect
ublock-origin
user-agent-string-switcher
zotero-connector
(buildFirefoxXpiAddon rec {
pname = "always_in_container";
version = "1.0.7";
addonId = "{a1e9543e-5f73-4763-b376-04e53fd12cbd}";
url =
"https://addons.mozilla.org/firefox/downloads/file/4032840/${pname}-${version}.xpi";
sha256 = "sha256-bLxjL2P6Sd06q98MSHYRTNigtcjGwn/C2r4ANWCqKrw=";
meta = with lib; {
homepage = "https://github.com/tiansh/always-in-container";
description =
"Chose a container every time you try to open a page out of a container";
license = licenses.mpl20;
platforms = platforms.all;
};
})
(buildFirefoxXpiAddon rec {
pname = "open_with";
version = "7.2.6";
addonId = "openwith@darktrojan.net";
url =
"https://addons.mozilla.org/firefox/downloads/file/3831723/${pname}-${version}.xpi";
sha256 = "sha256-f9eGhLxg4UyVn4o5e4DRkraLWzj11SGto/GOwsJa9kg=";
meta = with lib; {
homepage = "https://darktrojan.github.io/openwith/";
description =
"Quickly test out your web pages in Chrome, Edge, Safari, or Opera. Open With opens the current page in your other browsers with just two clicks.";
license = licenses.mpl20;
platforms = platforms.all;
};
})
] ++ (if pkgs.stdenv.hostPlatform.isDarwin then
[ ]
else
[ plasma-integration ]);
extensions.packages = with firefox-addons; [
bitwarden
multi-account-containers
floccus
libredirect
old-reddit-redirect
plasma-integration
pwas-for-firefox
ublock-origin
user-agent-string-switcher
web-archives
zotero-connector
(buildFirefoxXpiAddon rec {
pname = "always_in_container";
version = "1.0.7";
addonId = "{a1e9543e-5f73-4763-b376-04e53fd12cbd}";
url =
"https://addons.mozilla.org/firefox/downloads/file/4032840/${pname}-${version}.xpi";
sha256 = "sha256-bLxjL2P6Sd06q98MSHYRTNigtcjGwn/C2r4ANWCqKrw=";
meta = with lib; {
homepage = "https://github.com/tiansh/always-in-container";
description =
"Chose a container every time you try to open a page out of a container";
license = licenses.mpl20;
platforms = platforms.all;
};
})
(buildFirefoxXpiAddon rec {
pname = "open_with";
version = "7.2.6";
addonId = "openwith@darktrojan.net";
url =
"https://addons.mozilla.org/firefox/downloads/file/3831723/${pname}-${version}.xpi";
sha256 = "sha256-f9eGhLxg4UyVn4o5e4DRkraLWzj11SGto/GOwsJa9kg=";
meta = with lib; {
homepage = "https://darktrojan.github.io/openwith/";
description =
"Quickly test out your web pages in Chrome, Edge, Safari, or Opera. Open With opens the current page in your other browsers with just two clicks.";
license = licenses.mpl20;
platforms = platforms.all;
};
})
];
search = {
default = "Kagi";
privateDefault = "Milliron X Search";
@ -245,6 +246,7 @@
"floccus_handmadeideas_org-browser-action"
"7esoorv3_alefvanoon_anonaddy_me-browser-action"
"plasma-browser-integration_kde_org-browser-action"
"firefoxpwa_filips_si-browser-action"
"_d07ccf11-c0cd-4938-a265-2a4d6ad01189_-browser-action" # Web Archives
"openwith_darktrojan_net-browser-action"
"zotero_chnm_gmu_edu-browser-action"
@ -280,6 +282,7 @@
"floccus_handmadeideas_org-browser-action"
"7esoorv3_alefvanoon_anonaddy_me-browser-action"
"plasma-browser-integration_kde_org-browser-action"
"firefoxpwa_filips_si-browser-action"
"ublock0_raymondhill_net-browser-action"
"_d07ccf11-c0cd-4938-a265-2a4d6ad01189_-browser-action"
"zotero_chnm_gmu_edu-browser-action"

19
programs/ghostty.nix Normal file
View file

@ -0,0 +1,19 @@
{ pkgs, ... }: {
programs.ghostty =
let modifierKey = if pkgs.stdenv.isDarwin then "cmd" else "ctrl";
in {
enable = true;
package = null;
enableBashIntegration = true;
enableZshIntegration = true;
settings = {
quick-terminal-position = "top";
quick-terminal-screen = "main";
quick-terminal-autohide = true;
quick-terminal-size = "50%,50%";
keybind = "global:${modifierKey}+backquote=toggle_quick_terminal";
macos-hidden = "always";
linux-cgroup = "always";
};
};
}

View file

@ -1,16 +1,10 @@
{ ... }: {
programs.git = {
enable = true;
settings = {
user = {
name = "Thomas A. Christensen II";
email = "25492070+MillironX@users.noreply.github.com";
};
core = {
editor = "nvim";
# git push/pull via ssh over ipv6 is slow, so force ipv4 instead
sshCommand = "ssh -4";
};
userName = "Thomas A. Christensen II";
userEmail = "25492070+MillironX@users.noreply.github.com";
extraConfig = {
core = { editor = "nvim"; };
credential = { helper = "store"; };
color = { ui = "auto"; };
init = { defaultBranch = "master"; };
@ -46,11 +40,6 @@
};
merge = { conflictstyle = "zdiff3"; };
pull = { rebase = true; };
"url \"ssh://git@github.com/\"".insteadOf = "https://github.com/";
"url \"ssh://git@gitlab.com/\"".insteadOf = "https://gitlab.com/";
"url \"ssh://git@codeberg.com/\"".insteadOf = "https://codeberg.com/";
"url \"ssh://git@code.millironx.com/\"".insteadOf =
"https://code.millironx.com/";
};
};
}

View file

@ -1,23 +0,0 @@
{ ... }: {
programs.konsole = {
enable = true;
defaultProfile = "millironx";
profiles.millironx = {
colorScheme = "Breeze";
font = {
name = "MesloLGS NF";
size = 10;
};
extraConfig = {
"Cursor Options".CursorShape = 1;
General.RemoteTabTitleFormat = "[SSH] %H";
"Interaction Options" = {
CopyTextAsHTML = false;
MouseWheelZoomEnabled = false;
UnderlineFilesEnabled = true;
};
"Terminal Features".BlinkingCursorEnabled = true;
};
};
};
}

View file

@ -1,178 +1,7 @@
{ config, ... }: {
imports = [ ./konsole.nix ./yakuake.nix ];
programs.plasma = {
enable = true;
overrideConfig = true;
shortcuts = { yakuake.toggle-window-state = "Ctrl+`"; };
configFile = {
dolphinrc = {
DetailsMode.PreviewSize = 16;
General = {
BrowseThroughArchives = true;
EditableUrl = true;
GlobalViewProps = false;
ShowFullPath = true;
ShowStatusBar = "FullWidth";
ShowZoomSlider = true;
ViewMode = 1;
};
"KFileDialog Settings" = {
"Places Icons Auto-resize" = false;
"Places Icons Static Size" = 22;
};
PreviewSettings.Plugins =
"audiothumbnail,avif,blenderthumbnail,comicbookthumbnail,cursorthumbnail,djvuthumbnail,ebookthumbnail,exrthumbnail,directorythumbnail,fontthumbnail,imagethumbnail,jpegthumbnail,jxl,kraorathumbnail,windowsexethumbnail,windowsimagethumbnail,mobithumbnail,opendocumentthumbnail,gsthumbnail,rawthumbnail,svgthumbnail,gdk-pixbuf-thumbnailer,ffmpegthumbs,gsf-office";
};
};
input.mice = [{
enable = true;
name = "Logitech M705";
vendorId = "046d";
productId = "406d";
naturalScroll = true;
}];
kwin = {
cornerBarrier = false;
titlebarButtons = {
left = [ "close" "minimize" "maximize" ];
right = [ "help" ];
};
virtualDesktops = {
number = 2;
rows = 1;
};
};
panels = [
### Screen 0 panels ###
##### Top: Fedora menu | App switcher | Menu bar | CPU monitor | Memory monitor | Network monitor | System tray | clock #####
{
location = "top";
floating = true;
height = 27;
lengthMode = "fill";
opacity = "adaptive";
hiding = "normalpanel";
screen = 0;
widgets = [
{ kickoff = { icon = "fedora-logo-icon"; }; }
"org.kde.plasma.marginsseparator"
"org.kde.plasma.windowlist"
"org.kde.plasma.appmenu"
"org.kde.plasma.panelspacer"
{
name = "org.kde.plasma.systemmonitor.cpu";
config = {
Appearance.chartFace = "org.kde.ksysguard.barchart";
Sensors.highPrioritySensorIds = "[${
builtins.concatStringsSep "," (builtins.genList
(i: ''"cpu/cpu${builtins.toString i}/usage"'') 32)
}]";
};
}
{
name = "org.kde.plasma.systemmonitor.memory";
config.Appearance.chartFace = "org.kde.ksysguard.horizontalbars";
}
{
name = "org.kde.plasma.systemmonitor.net";
config = {
Appearance.chartFace = "org.kde.ksysguard.horizontalbars";
Sensors.highPrioritySensorIds =
''[ "network/all/download","network/all/upload" ]'';
# These are the values needed to make the network indicator
# actually useful, but it appears that plasma-manager doesn't
# support nesting this deep yet. Disable for now.
# "org.kde.ksysguard.horizontalbars".General = {
# rangeAuto = false;
# rangeFromMultiplier = 1048576;
# rangeFromUnit = 202;
# rangeToMultiplier = 1048576;
# rangeToUnit = 202;
# };
};
}
{ systemTray = { }; }
{ digitalClock = { }; }
];
}
##### Bottom: Virtual desktop pager | Full-name taskbar w/ pins | Downloads folder | Trash folder
{
location = "bottom";
floating = true;
height = 44;
lengthMode = "fill";
opacity = "adaptive";
hiding = "normalpanel";
screen = 0;
widgets = [
"org.kde.plasma.pager"
{
iconTasks = {
iconsOnly = false;
behavior.showTasks.onlyInCurrentScreen = true;
launchers = [
"applications:systemsettings.desktop"
"applications:org.kde.discover.desktop"
"preferred://filemanager"
"preferred://browser"
"applications:net.thunderbird.Thunderbird.desktop"
"applications:io.github.alainm23.planify.desktop"
"applications:dev.zed.Zed.desktop"
"applications:com.logseq.Logseq.desktop"
"applications:net.lutris.Lutris.desktop"
];
};
}
"org.kde.plasma.panelspacer"
"org.kde.plasma.marginsseparator"
{
name = "org.kde.plasma.folder";
config = {
General.url = "file://${config.home.homeDirectory}/Downloads";
};
}
"org.kde.plasma.trash"
];
}
### Screen 1 panels ###
##### Top: App switcher | Menu bar #####
{
location = "top";
floating = true;
height = 27;
lengthMode = "fill";
opacity = "adaptive";
hiding = "normalpanel";
screen = 1;
widgets = [ "org.kde.plasma.windowlist" "org.kde.plasma.appmenu" ];
}
##### Bottom: Virtual desktop pager | Full-name taskbar w/o pins #####
{
location = "bottom";
floating = true;
height = 44;
lengthMode = "fill";
opacity = "adaptive";
hiding = "normalpanel";
screen = 1;
widgets = [
"org.kde.plasma.pager"
{
iconTasks = {
iconsOnly = false;
behavior.showTasks.onlyInCurrentScreen = true;
launchers = [ ];
};
}
];
}
];
powerdevil.AC.autoSuspend.action = "nothing";
workspace = {
lookAndFeel = "org.kde.breezedark.desktop";
wallpaperFillMode = "preserveAspectCrop";
wallpaperSlideShow = {
interval = 86400;

View file

@ -1,7 +1,7 @@
{ pkgs, ... }:
let
conda_init = shell: ''
eval "$(${pkgs.mamba-cpp}/bin/mamba shell hook --shell ${shell})"
eval "$(${pkgs.micromamba}/bin/micromamba shell hook --shell ${shell})"
'';
nd_bash_function = ''
@ -10,53 +10,11 @@ let
}
'';
sourceCodeDirectory =
if pkgs.stdenv.hostPlatform.isDarwin then "~/Developer" else "~/src";
clone_bash_function = ''
unalias gc
function gc() {
REPO_ID="$(echo "''${1}" | \
awk '{
sub(/^git@/, "");
sub(/^https:\/\//, "");
sub (/:/, "/");
sub(/\.git$/, "");
print
}')"
git clone "https://''${REPO_ID}.git" ${sourceCodeDirectory}/''${REPO_ID}
cd ${sourceCodeDirectory}/''${REPO_ID}
}
'';
repo_init_function = ''
function rinit() {
REPO_ID="$(echo "''${1}" | \
awk '{
sub(/^git@/, "");
sub(/^https:\/\//, "");
sub (/:/, "/");
sub(/\.git$/, "");
print
}')"
mkdir -p ${sourceCodeDirectory}/''${REPO_ID}
cd ${sourceCodeDirectory}/''${REPO_ID}
git init
git remote add origin git@''${REPO_ID/\//:}.git
}
'';
shell_functions = shell:
(conda_init shell) + nd_bash_function + clone_bash_function
+ repo_init_function;
in {
programs = {
bash = {
enable = true;
initExtra = shell_functions "bash" + ''
initExtra = conda_init "bash" + nd_bash_function + ''
export PS1="[\[\e[32m\]\u\[\e[m\]@\[\e[33m\]\h\[\e[m\] \[\e[34m\]\W\[\e[m\]] \\$ "
'';
};
@ -79,7 +37,7 @@ in {
"zsh-users/zsh-completions"
];
};
initContent = shell_functions "zsh";
initContent = conda_init "zsh" + nd_bash_function;
};
};
}

View file

@ -1,73 +0,0 @@
{ pkgs, lib, config, hostname, ... }:
let
sshIdPath = host: "~/.ssh/id_ed25519__${host}";
tailnetConfig = host: { identityFile = sshIdPath host; };
gitConfig = host: tailnetConfig host // { user = "git"; };
tailnetHosts = [ "anderson" "mcentire" "bosephus" ];
gitHosts = [ "github.com" "gitlab.com" "codeberg.org" "code.millironx.com" ];
tailnetMatchBlocks =
lib.genAttrs (lib.lists.remove hostname tailnetHosts) tailnetConfig;
gitMatchBlocks = lib.genAttrs gitHosts gitConfig // {
"code.millironx.com" = (gitConfig "code.millironx.com") // {
proxyCommand = "ssh anderson -W localhost:2222";
hostname = "code.millironx.com";
};
};
in {
programs.ssh = {
enable = true;
enableDefaultConfig = false;
matchBlocks = {
"*" = { identitiesOnly = true; };
"aahz" = {
hostname = "nistac-108-37.dhcp.ksu.edu";
user = "tchristensen";
identityFile = sshIdPath "aahz";
};
"skeeve" = {
hostname = "129.130.108.157";
user = "tchristensen";
identityFile = sshIdPath "skeeve";
};
"ceres" = {
hostname = "ceres.scinet.usda.gov";
user = "thomas.christensen";
identitiesOnly = false;
serverAliveInterval = 20;
serverAliveCountMax = 30;
extraOptions = { TCPKeepAlive = "yes"; };
};
"atlas" = {
hostname = "Atlas-login-1.hpc.msstate.edu";
user = "thomas.christensen";
identitiesOnly = false;
serverAliveInterval = 20;
serverAliveCountMax = 30;
extraOptions = { TCPKeepAlive = "yes"; };
};
"atlas-dtn" = {
hostname = "Atlas-dtn.hpc.msstate.edu";
user = "thomas.christensen";
identitiesOnly = false;
};
"code.millironx.com" = {
proxyCommand = "ssh anderson -W localhost:2222";
};
} // tailnetMatchBlocks // gitMatchBlocks;
};
home.packages = let
# Answer no to overwrite questions
keygen = host: ''
yes "n" | \
ssh-keygen \
-t ed25519 \
-f ~/.ssh/id_ed25519__${host} \
-C "millironx@${hostname}" \
-N ""
'';
in [
(pkgs.writeShellScriptBin "ssh-bootstrap-keys"
(builtins.concatStringsSep "\n" (map keygen (tailnetHosts ++ gitHosts))))
];
}

View file

@ -25,10 +25,6 @@
bundleIdentifier = "org.mozilla.thunderbird";
action = "launchOrActivateApp";
}
{
bundleIdentifier = "com.microsoft.Outlook";
action = "launchOrActivateApp";
}
{
bundleIdentifier = "dev.zed.Zed";
action = "launchOrActivateApp";
@ -38,13 +34,11 @@
action = "launchOrActivateApp";
}
{
# Instinct dashboard
bundleIdentifier =
"com.apple.Safari.WebApp.2F51A6D0-087A-438F-92D3-A73FE09CB4CC";
action = "launchOrActivateApp";
}
{
# Carestream
bundleIdentifier =
"com.apple.Safari.WebApp.5EC6478E-03A6-4147-8A4D-6EF3DE3F06D3";
action = "launchOrActivateApp";

View file

@ -1,7 +0,0 @@
{ pkgs, ... }: {
programs.tmux = {
enable = true;
terminal = "tmux-256color";
plugins = with pkgs.tmuxPlugins; [ tmux-powerline ];
};
}

View file

@ -1,19 +0,0 @@
# Note: this file uses the lower-level `programs.plasma.configFile` syntax
# since plasma-manager does not yet support a high-level module for Yakuake
{ ... }: {
programs.plasma.configFile.yakuakerc = {
"Desktop Entry".DefaultProfile = "millironx.profile";
Shortcuts = {
next-session = "Shift+Right; Ctrl+Shift+Tab";
previous-terminal = "none";
};
Window = {
DynamicTabTitles = true;
Height = 60;
Screen = 1;
ShowSystrayIcon = false;
Width = 60;
};
Dialogs.FirstRun = false;
};
}

View file

@ -1,11 +1,9 @@
{ pkgs, hostname, ... }: {
{ ... }: {
programs.zed-editor = {
enable = true;
package = null;
extensions = [
"basher"
"clean-vscode-icons"
"caddyfile"
"clojure"
"dockerfile"
"earthfile"
@ -25,74 +23,42 @@
use_modifier_to_send = true;
default_model = {
provider = "zed.dev";
model = "claude-sonnet-4-5";
model = "claude-3-7-sonnet";
};
default_profile = "minimal";
};
buffer_font_family = "FiraCode Nerd Font";
buffer_font_size = 11;
features = { edit_prediction_provider = "zed"; };
git_hosting_providers = [
{
provider = "forgejo";
base_url = "https://code.millironx.com";
name = "Milliron X Code";
}
{
provider = "forgejo";
base_url = "https://codeberg.org";
name = "Codeberg";
}
];
languages = {
Caddyfile = {
tab_size = 2;
formatter.external = {
command = "${pkgs.caddy}/bin/caddy";
arguments = [ "fmt" "-c" "-" ];
Julia = {
formatter = {
external = {
command = "julia";
arguments = [
"--project=@JuliaFormatter"
"--startup-file=no"
"-e"
"using JuliaFormatter; print(format_text(String(read(stdin))));"
];
};
};
};
Julia = { formatter = { external = { command = "jlfmt"; }; }; };
LaTeX = {
formatter = {
external = {
command = let
latexindent = (pkgs.texlive.combine {
inherit (pkgs.texlive) scheme-minimal latexindent;
});
in "${latexindent}/bin/latexindent";
arguments = [ "-m" "-tt" "-l" "-" ];
command = "tex-fmt";
arguments = [ "--stdin" ];
};
};
};
Nix = {
formatter.external.command = "${pkgs.nixfmt-classic}/bin/nixfmt";
};
};
lsp = {
nil = { settings.nix.flake.autoArchive = true; };
nixd = {
settings.options.home-manager.expr = ''
(builtins.getFlake (builtins.toString ~/.config/home-manager)).homeConfigurations."millironx@${hostname}".options'';
};
texlab = {
settings.texlab = {
build = {
onSave = true;
forwardSearchAfter = true;
};
forwardSearch = if pkgs.stdenv.hostPlatform.isDarwin then {
executable =
"/Applications/Skim.app/Contents/SharedSupport/displayline";
args = [ "-r" "-g" "%l" "%p" "%f" ];
} else {
executable = "/usr/bin/okular";
args = [ "--unique" "file:%p#src:%l%f" ];
};
};
nil = {
initialization_options.formatting.command = [ "nixfmt" ];
settings.nix.flake.autoArchive = true;
};
tinymist = {
initialization_options = { preview.background.enabled = true; };
settings = {
exportPdf = "onSave";
outputPath = "$root/$name";
@ -115,28 +81,5 @@
ui_font_size = 16;
wrap_guides = [ 80 92 120 ];
};
userTasks = [
{
label = "latexmk (project)";
command = "latexmk";
args = [ "-synctex=1" "-pdf" "-recorder" ];
cwd = "$ZED_DIRNAME";
tags = [ "latex-build" ];
}
{
label = "Open Typst preview";
command = "${
if pkgs.stdenv.hostPlatform.isDarwin then "open" else "xdg-open"
} http://127.0.0.1:23635/";
use_new_terminal = true;
allow_concurrent_runs = false;
reveal = "never";
reveal_target = "dock";
hide = "always";
shell = "system";
show_summary = true;
show_command = true;
}
];
};
}

View file

@ -6,58 +6,25 @@ let
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIxTfeg+GZsfmG8TuEV1xW1gXknAIKzZ3UjZ3guRY+EW root@nixos";
bosephus-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKaDPqRJHoqgY2pseh/mnhjaGWXprHk2s5I52LhHpHcF millironx@bosephus";
corianne-host =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHKKkucebeb1GcerOZAAs5GQsgTS8kXw5W41b9Fy9+hp root@corianne.local";
odyssey-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN9Aj7BtQp1Roa0tgopDrUo7g2am5WJ43lO1d1fDUz45 millironx@odyssey";
corianne-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOgL2lO9RJBdQYANoxGyWXcNKi5/NZkRHHo/rNqaYMc/ millironx@corianne";
mcentire-host =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINT51tQgsKzTIQc9WSQj01h/gPRvAD3k9jRhXppY7xmd root@nixos";
mcentire-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOdC6eNx2nBi3PWK/n4GJMbVf+NlQJv13aUqxse/h1kL millironx@mcentire";
odyssey-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKM5Q2zl3b91j+foqcVeQT+wb5DFEp+MbgotTTaKqZZi millironx@odyssey";
harmony-millironx =
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFBYxsCkw+ObDzIvU8z/rSlYcQx0JIt1bCVxKcDxeNNZ millironx@harmony";
system-administrators = [
anderson-millironx
bosephus-millironx
corianne-millironx
mcentire-millironx
odyssey-millironx
corianne-millironx
harmony-millironx
];
in {
"secrets/ansible-vault-password.age".publicKeys = system-administrators;
"secrets/authentik.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/borgmatic-passphrase.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/borgmatic-ssh-config.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/darwin-policies-json.age".publicKeys = system-administrators
++ [ corianne-host ];
"secrets/fireflyiii.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/freshrss.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/immich.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/millironx-books-s3.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/millironx-music-s3.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/millironx-photos-s3.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/navidrome.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/network-information.age".publicKeys = system-administrators
++ [ bosephus-host ];
"secrets/peertube.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/redis-immich-password.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/redis-password.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/redis-peertube-password.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/vaultwarden.toml.age".publicKeys = system-administrators
++ [ mcentire-host ];
"secrets/pihole.age".publicKeys = system-administrators ++ [ bosephus-host ];
"secrets/ansible-vault-password.age".publicKeys = system-administrators;
"secrets/darwin-policies-json.age".publicKeys = system-administrators;
}

View file

@ -1,13 +1,13 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ SoqTRahd/xUVe/pDmQI3jT5X0lTxOwhy8hct20fwil4
UuYpQa5GpDALKQEMbSLnH2rp3kgPL+4zJZbJirkB5Q0
-> ssh-ed25519 1g/xww gYtrdwJlp1pxcZ7l+RvawRScFOh/ami3yJobbKPyLiM
oQ8dwIUIakV8WVvuknn87BYcmEBBT+UI3BxImd71/jE
-> ssh-ed25519 dbKeHw KVdpRmBWRYd5NC3UK3e8Em3XefnVxxc9KfXzbksvTC0
fwyK7ORnCadX8Czs0WKW3ZDa1jeu7Wjba7Vm9nKqCBs
-> ssh-ed25519 3qPtug nEHt7lMjUrcehOGjtznFd+u60OdfG1dPJr/+aFP4OXY
DRtfbYQZJhMIfj1yHgpTdSU15z0Ld5/2wl0ATPN0W6w
-> ssh-ed25519 FRQvIA a4zYtpYFVGSum3wD850lM/wEcokckt4mlDRsrb6QrHY
QF75fGiiKZc1oHSOdy+QvPnQ1hhaLRebaE7i0keZxHw
--- 8Y5Q9SlGWEzp9jeVcoHFrv3PR+eDyhOPFXIFRxZ1Koo
ÜJáżŢóóDlv\é÷÷lĹu$­0 ëë±{<7B>Ďri·śP7ĎĄPÚ锓…Äkr(ÇPˇĹHWË]r ĚĆŮr¸
-> ssh-ed25519 il3lzQ Ni2CHjeijXHfF62cUqVTm8MAOn6rRg8UrhqN6xvdkyk
DsT0Ysx88FlCLeRzoOGctX7KqatX9/UCr5WdtdLJAf4
-> ssh-ed25519 1g/xww jRn91F29sISMyi41anAlzVCzt1t1DnUqxtryqkTQPlM
cysgZLQR0YhiJYXBl59DjKbm+N8FnjA46wkQtnAzBFA
-> ssh-ed25519 +kBihw t6wlSnDKGgSzGhNJnryXVbDR40DATaV3fHovtI/u7zo
zOyCZtzfLKeer9K6SMpfTxn6El4HB7gQFQqLOxIYB5U
-> ssh-ed25519 dbKeHw cn+8WTwis58bYm2pfEra6LeLvzEZ8GhZrOEeN+kkhCM
fnlUAj8JtG8+r7Cj8xYUgF+JM6Pwqawn4sGI1LOeN78
-> ssh-ed25519 Svnssw zmDBR8TdRZ9NzNhwPYRN6c8naTxAkULyUZpKgk7Gshk
0XCwpegEIlGXhnzLLUtmciKQiYiZRgnSOSvCcYeXXk8
--- D/lZ36n5sVste2NWfdOx8/klPh0CTmMjVQN74KIqDRY
]%得ヌC}<7D>鶲ネモ"vホネ#<23>アェ釋「tュ、ワ_Q;^*!サ+<+ア瑁詞ネdBラ/Kメ<4B>`

Binary file not shown.

Binary file not shown.

View file

@ -1,16 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ 6815+wa1JjdVUu8U8vaRah8kVsjR3Fsot99HKWl4SSA
hPYVZuN1yTKQYFIQvIRdmr400aijXleA22Bxh1nXKdc
-> ssh-ed25519 1g/xww BPxSszlrjPTVJC+YOvo1N6ICUUdL7WKZX3DeambLjBY
JkJQFPGtxDtqeGj8Z1YtVq/pm6hgYKwzMW9MbtkvOwY
-> ssh-ed25519 dbKeHw PU5cZrKCkAvUjI/opkboDQKSWJ8osAOQiI62f9lbfRY
ef1z6ZcBASkhC4fdcN+RRE7q28rB/DN8CVdoNVkC81I
-> ssh-ed25519 3qPtug RWCMZiLQlS+bOOIBRGmoECOfqEI2uoaWCq/4ZuI86B4
VOaLaH1VYnGoQzjb8seU6wVviB94W3qCdYW9Wf4PGIc
-> ssh-ed25519 FRQvIA LCDwNMNVtTcI3GHPR2mcgtR16yAYYwLplI2nKUwNxDQ
vwXBIDlK7FBOsKt48Zv8mp0WFA/TKH2UJxtUxZoxzMI
-> ssh-ed25519 +C0WRg a/R13LfNMjVjJxt2TtTHaqZOhsYJLY+HUGMHEDnkZWo
T2EfeE/a4W4bFvauynfh+4aJL1jhCpO8iBG2aTmKtyM
--- o7tyupfhbxh3dwSrBLsYX0V61va0USlqxheNsdBjVuQ
A ¤q,:_™aɪ”)Àžj“¹<E2809C>1ß2 #¼¾²t,G®£*«
X¿TÛ³(óŽòœ5øtúñ6‡¡ÕB“çš©èã8à0Í Ú*Ïâìèi׆Ðì^ÇÓߨ@ÔaóB@̃Ž+oF¯…<C2AF>ü÷w‰÷²¼åPciˆ2Ü7Y¨SXd²“<C2B2> ÚØg±É̲ñJ

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

15
secrets/pihole.age Normal file
View file

@ -0,0 +1,15 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ Q+/uqZhUWs5pb5T1ocD+/qTSo4DJbd/W1exruQ34zAE
8HFRvEblGVrkoVaqAl/Af6wrDn6A+3unZIMBipEkwgA
-> ssh-ed25519 1g/xww PqXxTvLaF6ZlcVov81VrVH130jFh2iGmHPRtBYV4ME4
1VBknQzaNZyoz2wvgKX+IZGaOEnJ1xGvxPYxq10ar/U
-> ssh-ed25519 +kBihw QXNxY9OQeIM98OqmHoa/S2kMZqSX+ndgxGyCJpHJ+gg
b3DmfUswyPQ09sp57v3QMNEF/Ka3w9Qj2s1kGUSinmQ
-> ssh-ed25519 dbKeHw 5GzjKgjUX5e6Net7voWBykC17zRcdSFDFbDsSwp5FAU
GwTEg3YR9HdcQHPg+XjP2Lg1BpcWA4VunbZSBdxVaYU
-> ssh-ed25519 Svnssw imRjD5CJu/jOac3t/APHbYBnsyJVQdebR6K52A6GdwM
n+Q9kEEkYRBuEzWlSwbjJNsjF8uKloeUEWYxHa29B4U
-> ssh-ed25519 jb0ALQ 4qbGIofHcyhJVfL24peGqqzg0tFdxbWBHJFenwehIAI
Ta3ye4quyHvvE+2CGZwYvQMwWfdrLIdqADLvJYhllPY
--- 3hbht7PYqFafVmcQWQwv3q2gUXM8HXajtmAaMnrh59s
 +óX9ð¾84R2ƒª,òôˆ±¨(#42ì#*, bùôŒç¦½Hã_z…œ…¨Í }ß… ®ëxñ7Aœ!<21>)ê)vÊž¨¨•W-÷¿eX-G‡<´¸@~â¢èEÜk¬?kGlQK¬4c&*JöÂå9V_Ä0Õö µzÓ+ɰ¯CÞ ÑžÀQ°ò«ÃGô§XÁ˜k¡g*£æð -Àjƒö¼l½ú3¿‰1ÏJš´üM·OE†[=S

View file

@ -1,16 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ Lqt1JIJtjTwggbhuJj/vG65IbDyr4EK5pOOKtVqdN3s
OBvcpByhzHT+0fmvLpgOCQaTbudVEdNmNDCJCf+9Tzs
-> ssh-ed25519 1g/xww b1Gu6eFxAJwd+8P38D8HoEzApocQDwRpi7GF4tQH+zE
Js9XmUUcV4gGHvE37j6I3J1vYf7gkxOLbfPC2al0EsU
-> ssh-ed25519 dbKeHw nNL9fDDfudbcHOshRKuxtcaZzPNbTEY2z4jAQMNYQTE
Xrz8nKSxzaEnAYeZTust6eZNhILs4dOutAaIfZ4wcGM
-> ssh-ed25519 3qPtug JUTgaW4s68Gtr/kl+L4FLRldW97kk6vaALLSN3IgY24
cyTIvPurE28TsE8Axl4x05OVcgEX7qA9X00B6u22/LI
-> ssh-ed25519 FRQvIA CYliqiKWpSXwHWteHCyPxDjaVgx3tYH3+OXYtH+HAEM
iSmpVhv01x+g/bN0TpbeN5210YsuAKTWdEJy34lulw4
-> ssh-ed25519 +C0WRg 5O+LJgQPccaXNPvB/eRANbmq+4A5wNTGSwC6cCAecw4
EkLahciiQbR8MmgJptjPVi2R/lMbXkZJYIFvOk4v5Vk
--- EcIUdYgRgZ1xHmTX+O1ULpDJKloyFquwZqFLHj1Lf4E
ô,y-]Ð@Ã5
ôHÄÌíÎÚ€>á\ØÄ= O=,%õö»ZöÙ)q6É3©1ÔV¡l]µâ8wVè†s

View file

@ -1,15 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ 9+/0+lCYWH9ibqY7J0OWz4fyTJYNvjwVEQQgByg8eXo
DmqfjZYNCggmNwez+H0lNUmTuBUmlfOy9BMdo/mP1Ro
-> ssh-ed25519 1g/xww 3v7NGaDyYM/Na5DTgbpv7DtVg6mqsgqd0xU7NmcDlTk
7Z8yymlYYx3fO8CNMcosa6hLAZ7rlkDA6qPat2IWsS0
-> ssh-ed25519 dbKeHw WlHTnJR3TyAuYm5NG+8WMXmeB3YUydDJ61SrWKMcByg
k5dwVAuUKNjFeCY7/L28Kx0ZHBiPUxndEQp1UyOPxIY
-> ssh-ed25519 3qPtug YU9PSuEkJElN4EOFQTrXBrRB/3g/MzSbLEU5mWIl6CA
HBFN2uTQo0tr8cnJ2okFibZXVouaCy6WCq0+YK2jSYI
-> ssh-ed25519 FRQvIA ufWjt9KuGr9Wx0kRbMK3WapeyOM9dnuy8nRpa9LcElY
WGB7xIuZOEGNlycHAlg3sE1W766/rMZGIb17VL54uYs
-> ssh-ed25519 +C0WRg 9jXj4AKdCK5RdQOOmDFu9/RapWvQQ6MieaHO7m/wKSc
vkdJWxmBtjmLQXq1tvM+sAXAOKbJmTdM7klyiW1xW2g
--- lEbO55yAuhIUejggLmlQ9UAZdw8DQVC+2EZ6Qs0Vn9Y
8ÿ¸È/ÚW"F™ÓJ[DÉ<(O²9ª;lj³ô¯špM^À“Ò·•/Iù|9*…Øòx@ɪ±˜¦ÁEa¦:™Ký¥*<2A>íÌ~

View file

@ -1,15 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ BiSY+7dxt1jVDqysxYAqAGAXQ0SurqZXGgGqvwaQEXM
gzdbiaAo0nzVOIFEjAoKXSHfs+JQAbJWDJyrO4tt6gA
-> ssh-ed25519 1g/xww 3MK5439qn8MkP5HrLLkY6a+IRGrjVIIe/OhaZ20THU0
dKe0EkynB0G1zAZZ+VA/Of1x4Q6j+R4al7ohb6V6YWM
-> ssh-ed25519 dbKeHw GfX7az0oBf1Nh6YQFqZuDrCZyK1Mm6w+SGAhQSGeJww
kgLWmzfZjBnds1fjAVme6M7fKD4NrJZ3X6vJU7055ac
-> ssh-ed25519 3qPtug zUXACZya4MCbMyPLR59VN4GRQSOokgORvDhL/ByWbHM
dSfQVAGj8u+7L7GX53dWbs5rHo9H5TaVRwEFZUq3lJ8
-> ssh-ed25519 FRQvIA z2qGPHCG5wGlmmngy2d4L8kHz7pO/F4YPAWwsNjWQEI
HIVGXAxOfD7O8yP2piOfMLTM9EzWkHslCRFIf+XrXXY
-> ssh-ed25519 +C0WRg BhTldebU1YLAxlQ5dYG/RuExT9czS//LUXwKg+Q7tT8
mEiv5g1PWgdwZHrlqrv5wEVWvubsppUpryEwtdSNDXM
--- 2+fbTKQ8yZPOKxLvYhFO2laMF/gq2cWs7byzYOcuohM
™hçž ÕD VšÛ/0ØÀõ;î»<C3AE><C2BB> îO§Ísý7ŽwrVáze¹½üÅÓ”WgŠ­•°Ë<C2B0>‡ëüvl}í

View file

@ -1,17 +0,0 @@
age-encryption.org/v1
-> ssh-ed25519 il3lzQ 8JViVLQl/Y6buE2yIo/k9QhictchYRpvx3U+TtV88RY
bWMzX1yYCP11aGg85fCMEf3o00Ej6VeLrFW2fHyv9zo
-> ssh-ed25519 1g/xww eDRfglbO+tyPhpXhl8RwI1f5CCocjtwpT5QqmpYab1I
s45uOqmgA1TnCthPASC5cA2pEBzriCt69noN3aHZTvg
-> ssh-ed25519 dbKeHw PPnqWUiu/mwpTxRkXvNwELP2F4cETQdVgXL/SgyTMAE
mYQ1s393jTPVE5euXnq+a5Hs+nGG1VC3iaOb/6Mg6sQ
-> ssh-ed25519 3qPtug z3Ho5K7RQD8mbFRq+rSC7mRDs5M/Kkb/O7z/ssdFLgg
6qmYTtOz7l9nRl8yEReS2iI52H+bYxjMJfCFwYWOqn8
-> ssh-ed25519 FRQvIA HZxjrRSKr+ofIai2x2tskTaBIrRfI+uJYISU0Wj0B1Y
tKbposXaqYHF6H7dvoG1iYGT8FOIkX8EF0viCHUvjdQ
-> ssh-ed25519 +C0WRg 9GCzNbR9ZbTypzrkXWVBAC3LiIwl8ypxpG+DcWy1QU4
BlQrkO8GchHE5e275o4Hk5fmTv2RIUtW71tOS+sCsZA
--- Fh46aD+7L1fe5O2BxitKMtJV+QgdbXhJ4O3a+0IhjP4
"nmQ'*°y¸ç·Ë}TÅ¿]uk ^@Yʪqjñ×ß8H#$Jþ¤NÕŒ…·OSÐÄ4<C384>½Lš@'%C<ÞŒ\ømƒ‰¨
ªjI<>‰0^"e'èû…:Ó;Ã6! våeâœÈ ~˜hã@@†¥¿4ÈåêÒƒaçæõËìMÔ Mçœ0÷œ¶yðq³î3˜\žØ£ô¨¾Ž Ì2ÒÆ¿ºÎ¾0_¢ð€]ÌÛ¸<C39B>]nbR?ˆ@ƒD½?™RaEç©Ìòz-ŸÇÓimîÍq^SYå»)“Síðwqf=acKB¤Ng9¿®<C2BF>)Q9ØNÓ ×aäëk Hi+ŸúÕ~4&{l5Þ 9Çùb̀¬3(žß(¼‘òy0 ¯,çÿV-»E:Ó}Ê¡È,´IBP ì¦<C3AC>&”jÕl¶C}ð˜àYy•QRûä<C3BB>PäÞ©Ñ„,uC§µ ž}‰UP<55>½”.Mø-D<>ïÀÑÀÒÈóLúùÆÖLywbÚ <0C>È2<C388>k$˜Å.Ë,c#ÎÇæÀ“±\ÅïbAfp*€K¸`-T6ØN³-ºhdŸoþ¡Î\1EÜãäd:¾«.‰á“?`³¿jt->ó17ïÍ:÷ˆa]aŽˆ6V8<56>:íô~[×QùfõHÏ€{_ æ§Ú.Ž=ü_¬gÄm<îŒÏ<C592>³%«Ž‹'ÝS;{ ch!Ið'äB)ÈóãÅ\®sÀacȲ â‚Ê:{Í2ÐH.+5Ã$†0÷”xO¦ÈëîÃ>ͲfÒ«~¼±JF HÂ3=*/~ý,§™nJ'¼ÛË45<07>G™ÒõO]ÿ94)y
Å´.8<EFBFBD>$õ(•³¥9<C2A5>ú”¦ìÿÞûšRMyÆ•#âX©<30>Š]“ÿûÜD±Èl²(¾ì1ˆM_Ëâ+!8ÛuŠtW‡ÊÚ=­O«ÏÇWôÙí¹Ùð2*¿cåÜ4Â\tSÆTÉ_´žJÎP¤³”ú¯1VçLÿdŒ`“0ÊgF¶Êƒ÷&ÙYtÒ\kgþëE1BQ —D·é€ƒDõGMO¾4û!Jt<4A>C6>óÙd[êá1<>ú7<䙋’˜.I¨ô8Þnood¼¹Í ù}΢eBR» ¯Ç2A°$^ÄOœˆÇ•bÎ>cˆ 3-%â­”z|€¹è;ar <§S.=„nÆ;

View file

@ -1,8 +1,10 @@
$ANSIBLE_VAULT;1.1;AES256
33613635643765623937663135313833396162343134383466343966333964386364356134663264
3137633339396462633431316634623834646437646162360a626564313831373761636161656232
35316566336232666336646231356665366633303530623961666465366163306166623336656364
3835353035333031620a633332376237336530343134623832363534383761616564616138363766
30306361383462353361636161636335313461313835663362393839623735313738316465656537
66396635323432376530346532353238346139376261366237343763373535623364633731323830
333730373965613131336166626230333263
65366137313461383534313965646333656565353061336361363661613033393264353661346337
3838653162383134393463323631613439373663396363380a633339396236363962313333343465
31623961393532666136616438633734366261353866383264323730383432326635626637343739
3235313062623637380a386235316437396534353261383832643165316565386263396664363962
62393364333335373631356161373263313930343565626433383539373030363662353630633933
63336333613965653635313637336437653139616564313861336332323739653865383531356233
31373530343766343131346663656566363038643230343462336332323135323337353539303763
33366638393064323431323636346161343936643062323861313766613264336465326132333631
33306666383561653965303539313366653030663330393363363565333439383133

6
secrets_harmony.enc Normal file
View file

@ -0,0 +1,6 @@
$ANSIBLE_VAULT;1.1;AES256
38383539613238613864336630316433666436623334313334393762396536663530336264306661
3338616565316138616666343862366638643134343931320a633366363539326461346636373738
66393138653463663536313065623332383166386332303564323939336630333163623637386434
6538393966633731660a616437356233643234363562366433663437383439326161353330356331
63346432663036353332303266343361346266396437396131376531303265356233

View file

@ -1,9 +1,6 @@
$ANSIBLE_VAULT;1.1;AES256
61363033383536303833366237323662663236313163663033306138383162383062643830616466
6531636430613462646161343939343363663533373737340a613433363666353432383463356439
33656266633131336565613433653062656563656637656464346232656238646339303961373265
6639643637303433380a393163366331373964353261383662656664643031626432366231346332
34303964346137616233343930333331306363326332383465653163386539306430303965316437
30343333373565623431653436653832356366343937653136346535316166383262623730343831
62376532346237323465653261316339353034323633623632313630666531373839633665333637
34356162356565396564
30343638643335363463653231623566623961613534323261393639623865633964653634333562
3838613035393661656362383736313561366466396439390a383162366362643364636335613664
39646137666437353762363764373562393736626530333336626261366232383063633732623238
6531633638366335640a363461383535646663316533386137323966326237373836363561323462
66646635383137333834363165666365366235333734646364616637383363666239

View file

@ -1,81 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "audiobookshelf";
port = "28346";
stateDirectory = "/var/lib/${user}";
in {
age.secrets = {
millironx-books-s3-token.file = ./../secrets/millironx-books-s3.age;
};
environment.systemPackages = [ pkgs.s3fs ];
fileSystems."millironx-books" = {
device = "millironx-books";
mountPoint = "/mount/s3/millironx-books";
fsType = "fuse./run/current-system/sw/bin/s3fs";
noCheck = true;
options = [
"_netdev"
"allow_other"
"use_path_request_style"
"url=https://us-east-1.linodeobjects.com/"
"passwd_file=${config.age.secrets.millironx-books-s3-token.path}"
"uid=${user}"
"gid=${user}"
"umask=0022"
];
};
systemd.tmpfiles.rules =
map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -") [
""
"config"
"metadata"
];
services.borgmatic.configurations."${config.networking.hostName}" = {
source_directories =
map (d: "${stateDirectory}/${d}") [ "config" "metadata" ];
};
services.caddy.virtualHosts."books.millironx.com".extraConfig = ''
reverse_proxy http://127.0.0.1:${port}
'';
users.users."${user}" = {
group = "${user}";
isNormalUser = true;
home = stateDirectory;
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = {
autoUpdate.enable = true;
containers.audiobookshelf = {
autoStart = true;
containerConfig = {
image = "ghcr.io/advplyr/audiobookshelf:latest";
environments = { TZ = "America/New_York"; };
volumes = [
"/mount/s3/millironx-books/audiobooks:/audiobooks:U"
"/mount/s3/millironx-books/podcasts:/podcasts:U"
"${stateDirectory}/config:/config:U"
"${stateDirectory}/metadata:/metadata:U"
];
publishPorts = [ "127.0.0.1:${port}:80" ];
addHosts = [ "auth.millironx.com:host-gateway" ];
};
};
};
};
}

View file

@ -1,233 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "authentik";
state-directory = "/var/lib/authentik";
port = "9000";
in {
# Secrets are translated in the system NixOS scope, but performed by the user,
# via a user systemd unit, so are available in the user's Podman secrets
age.secrets = {
"authentik.toml" = {
file = ./../secrets/authentik.toml.age;
owner = "${user}";
};
};
millironx.podman-secrets.authentik = {
user = "${user}";
secrets-files = [ config.age.secrets."authentik.toml".path ];
};
# Podman, unlike Docker apparently, does not automatically create mount points
# within folders, so every mounted folder needs to be specified here.
systemd.tmpfiles.rules = [
"d ${state-directory} 1775 ${user} ${user} -"
"d ${state-directory}/database 1775 ${user} ${user} -"
"d ${state-directory}/data 1775 ${user} ${user} -"
"d ${state-directory}/media 1775 ${user} ${user} -"
"d ${state-directory}/certs 1775 ${user} ${user} -"
"d ${state-directory}/custom-templates 1775 ${user} ${user} -"
];
services.caddy.virtualHosts."auth.millironx.com".extraConfig = ''
reverse_proxy http://127.0.0.1:${port}
'';
services.borgmatic.configurations."${config.networking.hostName}" = {
source_directories = [
"${state-directory}/data"
"${state-directory}/media"
"${state-directory}/certs"
"${state-directory}/custom-templates"
];
postgresql_databases = [{
name = "authentik";
psql_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec authentik-db psql --username=${user}";
pg_dump_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec authentik-db pg_dump --username=${user}";
pg_restore_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec authentik-db pg_restore --username=${user}";
}];
};
# Create a dedicated user for this service
users.users."${user}" = {
# Group is technically not mandatory, but NixOS complains that an unset
# group is "unsafe." The group has to be declared below
group = "${user}";
# System users don't have a shell. For security purposes, that *would* be
# superior, but that means that, e.g. borgmatic can't `sudo` into the
# account to access Podman commands
isNormalUser = true;
# A home directory is mandatory in order to allow systemd user units to be
# created and run
home = "${state-directory}";
createHome = true;
# Settings for running containers while a login shell is not active
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
services.crowdsec = {
localConfig.acquisitions = [{
source = "journalctl";
journalctl_filter = [ "_SYSTEMD_USER_UNIT=${user}.service" ];
labels.type = "authentik";
}];
hub.collections = [ "firix/authentik" ];
};
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = {
containers = {
authentik-db = {
autoStart = true;
containerConfig = {
image = "docker.io/library/postgres:16";
environments = {
POSTGRES_DB = "${user}";
POSTGRES_USER = "${user}";
};
secrets = [
# POSTGRES_PASSWORD is used by the container to setup the
# database, PGPASSWORD is used by pg_dump to authenticate for
# backup purposes
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env,target=POSTGRES_PASSWORD"
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env,target=PGPASSWORD"
];
# Double dollarsigns are required for use of *container* environment
# variables, Nix escaping creates the weird $\${} syntax
healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}";
healthInterval = "30s";
healthRetries = 5;
healthStartPeriod = "20s";
# Volumes have to be bound with the :U label to allow for username
# remapping in rootless containers. :Z/:z is not needed b/c NixOS
# does not support SELinux
volumes =
[ "${state-directory}/database:/var/lib/postgresql/data:U" ];
# A network is actually required for these containers to talk to one
# another. I suppose the more idiomatic way would be for these
# related containers to be in a "pod," but I'm still struggling
# learning the idiosyncrasies of quadlet/rootless/podman, so we'll
# stick with this for now
networks =
[ config.virtualisation.quadlet.networks.authentik-net.ref ];
};
# Allowing this container to start before the secrets are translated
# will lead to errors. Use osConfig b/c secrets are declared in the
# system NixOS scope, even though it is a user process.
unitConfig.Requires =
[ osConfig.millironx.podman-secrets.authentik.ref ];
unitConfig.After =
[ osConfig.millironx.podman-secrets.authentik.ref ];
};
authentik-worker = {
autoStart = true;
containerConfig = {
image = "ghcr.io/goauthentik/server:2026.2";
environments = {
AUTHENTIK_POSTGRESQL__HOST = "authentik-db";
AUTHENTIK_POSTGRESQL__NAME = "${user}";
AUTHENTIK_POSTGRESQL__USER = "${user}";
AUTHENTIK_STORAGE__BACKEND = "s3";
};
exec = "worker";
secrets = [
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env"
"AUTHENTIK_SECRET_KEY,type=env"
"AUTHENTIK_STORAGE__S3__ACCESS_KEY,type=env"
"AUTHENTIK_STORAGE__S3__SECRET_KEY,type=env"
"AUTHENTIK_STORAGE__S3__BUCKET_NAME,type=env"
"AUTHENTIK_STORAGE__S3__REGION,type=env"
"AUTHENTIK_STORAGE__S3__ENDPOINT,type=env"
"AUTHENTIK_STORAGE__S3__CUSTOM_DOMAIN,type=env"
];
volumes = [
# Remount media folder into new location based on
# <https://docs.goauthentik.io/releases/2025.12/#storage-improvements>
"${state-directory}/data:/data:U"
"${state-directory}/media:/data/media:U"
"${state-directory}/custom-templates:/templates:U"
"${state-directory}/certs:/certs:U"
];
networks =
[ config.virtualisation.quadlet.networks.authentik-net.ref ];
};
unitConfig.Requires =
[ config.virtualisation.quadlet.containers.authentik-db.ref ];
unitConfig.After =
[ config.virtualisation.quadlet.containers.authentik-db.ref ];
};
authentik = {
autoStart = true;
containerConfig = {
image = "ghcr.io/goauthentik/server:2026.2";
environments = {
AUTHENTIK_POSTGRESQL__HOST = "authentik-db";
AUTHENTIK_POSTGRESQL__NAME = "${user}";
AUTHENTIK_POSTGRESQL__USER = "${user}";
AUTHENTIK_STORAGE__BACKEND = "s3";
};
exec = "server";
secrets = [
"AUTHENTIK_POSTGRESQL__PASSWORD,type=env"
"AUTHENTIK_SECRET_KEY,type=env"
"AUTHENTIK_EMAIL__HOST,type=env"
"AUTHENTIK_EMAIL__PORT,type=env"
"AUTHENTIK_EMAIL__USERNAME,type=env"
"AUTHENTIK_EMAIL__PASSWORD,type=env"
"AUTHENTIK_EMAIL__USE_SSL,type=env"
"AUTHENTIK_EMAIL__FROM,type=env"
"AUTHENTIK_STORAGE__S3__ACCESS_KEY,type=env"
"AUTHENTIK_STORAGE__S3__SECRET_KEY,type=env"
"AUTHENTIK_STORAGE__S3__BUCKET_NAME,type=env"
"AUTHENTIK_STORAGE__S3__REGION,type=env"
"AUTHENTIK_STORAGE__S3__ENDPOINT,type=env"
"AUTHENTIK_STORAGE__S3__CUSTOM_DOMAIN,type=env"
];
# Change from Traefik: publish ports to localhost only via 127.0.0.1
# and then reverse proxy to that port. Authentik does not appear to
# have a way to configure the port, so we will use the default of
# 9000 for non-secured traffic.
publishPorts = [ "127.0.0.1:${port}:${port}" ];
volumes = [
"${state-directory}/media:/media:U"
"${state-directory}/custom-templates:/templates:U"
];
networks =
[ config.virtualisation.quadlet.networks.authentik-net.ref ];
};
unitConfig.Requires =
[ config.virtualisation.quadlet.containers.authentik-db.ref ];
unitConfig.After =
[ config.virtualisation.quadlet.containers.authentik-db.ref ];
};
};
networks.authentik-net = { };
# One of the main advantages of using Quadlet
autoUpdate.enable = true;
};
};
}

View file

@ -1,35 +0,0 @@
{ pkgs, config, ... }: {
# We don't want to expose the location where borg backups are going, so we
# will setup an encrypted ssh config that references the host/username
# combo as simply 'borgserver'
age.secrets = {
borgmatic-ssh-config = { file = ./../secrets/borgmatic-ssh-config.age; };
borgmatic-passphrase = { file = ./../secrets/borgmatic-passphrase.age; };
};
services.borgmatic = {
enable = true;
# This is the bare-bones way to get Borgmatic up and running. Other services
# are expected to declare their stateful directories by adding to
# `services.borgmatic.configurations."${config.networking.hostName}".source_directories`
# and to add their databases to
# `services.borgmatic.configurations."${config.networking.hostName}".[mariadb|postgresql|etc]_databases`
configurations."${config.networking.hostName}" = {
source_directories = [ "/home" "/root" ];
repositories = [{
label = "${config.networking.hostName}-default";
path = "ssh://borgserver/./repo";
}];
ssh_command =
"${pkgs.openssh}/bin/ssh -F ${config.age.secrets.borgmatic-ssh-config.path}";
encryption_passcommand =
"${pkgs.coreutils}/bin/cat ${config.age.secrets.borgmatic-passphrase.path}";
keep_daily = 7;
keep_weekly = 4;
keep_monthly = 6;
};
};
}

View file

@ -1,15 +0,0 @@
{ config, ... }: {
services.caddy = {
enable = true;
logFormat = "level INFO";
};
services.crowdsec = {
localConfig.acquisitions = [{
filenames = [ "${config.services.caddy.logDir}/*.log" ];
labels.type = "caddy";
}];
hub.parsers = [ "crowdsecurity/caddy-logs" ];
};
}

View file

@ -1,51 +1,90 @@
{ pkgs, config, ... }: {
{ pkgs, config, ... }:
let
crowdsec-url = "127.0.0.1:2763";
firewall-bouncer-name = "fw-bouncer";
# Although this key can be reproduced by anyone who actually cares to, the
# Crowdsec API will not be exposed to the outside world, so keeping this key
# super secret really isn't that important to me. Still make it look random
# so that hungry botnets can't just slurp up the password in plaintext.
firewall-bouncer-key = builtins.hashString "sha256"
"${config.networking.hostName}-crowdsec-bouncer-salt";
toMultiYAML = items:
pkgs.lib.concatMapStrings (item:
''
---
'' + (pkgs.lib.generators.toYAML { } item) + "\n") items;
in {
services = {
crowdsec = {
enable = true;
localConfig = {
acquisitions = [
{
source = "journalctl";
journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ];
labels.type = "syslog";
}
{
filenames = [ "/var/log/auth.log" ];
labels.type = "syslog";
}
{
filenames = [ "/var/log/syslog" "/var/log/kern.log" ];
labels.type = "syslog";
}
];
};
hub = {
collections = [
"crowdsecurity/base-http-scenarios"
"crowdsecurity/http-cve"
"crowdsecurity/http-dos"
"crowdsecurity/iptables"
"crowdsecurity/linux"
"crowdsecurity/sshd"
"crowdsecurity/whitelist-good-actors"
];
};
settings = {
general = { api.server.enable = true; };
# See https://github.com/NixOS/nixpkgs/issues/445342
lapi.credentialsFile = "/var/lib/crowdsec/lapi-credentials.yaml";
api.server = { listen_uri = crowdsec-url; };
allowLocalJournalAccess = true;
crowdsec_service.acquisition_path = pkgs.writeText "acquisitions.yaml"
(toMultiYAML [
{
source = "journalctl";
journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ];
labels.type = "syslog";
}
{
filenames = [ "/var/log/auth.log" ];
labels.type = "syslog";
}
{
filenames = [ "/var/log/syslog" "/var/log/kern.log" ];
labels.type = "syslog";
}
]);
};
autoUpdateService = true;
};
crowdsec-firewall-bouncer = {
enable = true;
registerBouncer.enable = true;
settings = {
api_url = firewall-bouncer-name;
api_key = firewall-bouncer-key;
};
};
};
users.users."${config.services.crowdsec.user}".extraGroups = [ "adm" ];
systemd.services.crowdsec.serviceConfig = {
ExecStartPre = let
bouncer-script = pkgs.writeScriptBin "register-bouncer" ''
#!${pkgs.runtimeShell}
set -eu
set -o pipefail
systemd.tmpfiles.rules = let cfg = config.services.crowdsec;
in [ "d /var/lib/crowdsec 0755 ${cfg.user} ${cfg.group}" ];
if ! cscli bouncers list | grep -q "${firewall-bouncer-name}"; then
cscli bouncers add "${firewall-bouncer-name}" --key "${firewall-bouncer-key}"
fi
'';
collection-check = collection: ''
if ! cscli collections list | grep -q "${collection}"; then
cscli collections install "${collection}"
fi
'';
collections = [
"crowdsecurity/base-http-scenarios"
"crowdsecurity/http-cve"
"crowdsecurity/http-dos"
"crowdsecurity/iptables"
"crowdsecurity/linux"
"crowdsecurity/sshd"
"crowdsecurity/whitelist-good-actors"
];
collection-script = pkgs.writeScriptBin "install-collections" ''
#!${pkgs.runtimeShell}
set -eu
set -o pipefail
${pkgs.lib.concatMapStrings collection-check collections}
'';
in [
"${bouncer-script}/bin/register-bouncer"
"${collection-script}/bin/install-collections"
];
};
}

View file

@ -1,164 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "fireflyiii";
port = "34733";
containerPort = "8080";
authentikPort = "9000";
stateDirectory = "/var/lib/${user}";
servicePaths = [ "upload" ];
databasePaths = [ "database" ];
in {
age.secrets."fireflyiii.toml" = {
file = ./../secrets/fireflyiii.toml.age;
owner = user;
};
millironx.podman-secrets.fireflyiii = {
inherit user;
secrets-files = [ config.age.secrets."fireflyiii.toml".path ];
};
systemd.tmpfiles.rules =
map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -")
([ "" ] ++ servicePaths ++ databasePaths);
services.borgmatic.configurations."${config.networking.hostName}" = {
source_directories = map (d: "${stateDirectory}/${d}") servicePaths;
postgresql_databases = [{
name = user;
psql_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db psql --username=${user}";
pg_dump_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_dump --username=${user}";
pg_restore_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_restore --username=${user}";
}];
};
services.caddy.virtualHosts."money.millironx.com".extraConfig = ''
reverse_proxy /outpost.goauthentik.io/* http://127.0.0.1:${authentikPort}
@protected not path /oauth/* /api/*
forward_auth @protected http://127.0.0.1:${authentikPort} {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username X-Authentik-Groups X-Authentik-Entitlements X-Authentik-Email X-Authentik-Name X-Authentik-Uid X-Authentik-Jwt X-Authentik-Meta-Jwks X-Authentik-Meta-Outpost X-Authentik-Meta-Provider X-Authentik-Meta-App X-Authentik-Meta-Version
}
reverse_proxy http://127.0.0.1:${port}
'';
users.users."${user}" = {
group = user;
isNormalUser = true;
home = stateDirectory;
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
systemd.user = let inherit (config.virtualisation.quadlet) containers;
in {
services."${user}-cron" = {
Unit = {
Description = "Firefly III cron";
Requires = [ containers."${user}".ref ];
After = [ containers."${user}".ref ];
};
Service.ExecStart =
"${pkgs.podman}/bin/podman exec ${user} /usr/local/bin/php /var/www/html/artisan firefly-iii:cron";
};
timers."${user}-cron" = {
Unit.Description = "Firefly III cron";
Timer.OnCalendar = "daily";
};
};
virtualisation.quadlet = let
inherit (config.virtualisation.quadlet) containers;
inherit (config.virtualisation.quadlet) networks;
secrets = osConfig.millironx.podman-secrets.fireflyiii;
in {
autoUpdate.enable = true;
autoEscape = true;
networks."${user}" = { };
containers = {
"${user}-db" = {
autoStart = true;
containerConfig = {
image = "docker.io/library/postgres:16";
environments = {
POSTGRES_DB = user;
POSTGRES_USER = user;
};
secrets = [
"POSTGRES_PASSWORD,type=env"
"POSTGRES_PASSWORD,type=env,target=PGPASSWORD"
];
healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}";
healthInterval = "30s";
healthRetries = 5;
healthStartPeriod = "20s";
volumes =
[ "${stateDirectory}/database:/var/lib/postgresql/data:U" ];
networks = [ networks."${user}".ref ];
};
unitConfig.Requires = [ secrets.ref ];
unitConfig.After = [ secrets.ref ];
};
"${user}" = {
autoStart = true;
containerConfig = {
image = "docker.io/fireflyiii/core:version-6";
environments = {
APP_ENV = "local";
DEFAULT_LANGUAGE = "en_US";
TZ = "America/New_York";
TRUSTED_PROXIES = "*";
DB_CONNECTION = "pgsql";
DB_HOST = "${user}-db";
DB_PORT = "5432";
DB_DATABASE = user;
DB_USERNAME = user;
CACHE_DRIVER = "redis";
SESSION_DRIVER = "redis";
REDIS_SCHEME = "tcp";
REDIS_HOST = "host.docker.internal";
REDIS_PORT = "6379";
REDIS_DB = "0";
REDIS_CACHE_DB = "1";
MAIL_MAILER = "smtp";
APP_URL = "https://money.millironx.com";
AUTHENTICATION_GUARD = "remote_user_guard";
AUTHENTICATION_GUARD_HEADER = "HTTP_X_AUTHENTIK_USERNAME";
AUTHENTICATION_GUARD_EMAIL = "HTTP_X_AUTHENTIK_EMAIL";
};
secrets = map (s: "${s},type=env") [
"SITE_OWNER"
"APP_KEY"
"DB_PASSWORD"
"MAIL_HOST"
"MAIL_PORT"
"MAIL_FROM"
"MAIL_USERNAME"
"MAIL_PASSWORD"
"MAIL_ENCRYPTION"
"REDIS_PASSWORD"
];
volumes = [ "${stateDirectory}/upload:/var/html/storage/upload:U" ];
networks = [ networks."${user}".ref ];
publishPorts = [ "127.0.0.1:${port}:${containerPort}" ];
};
unitConfig.Requires = [ secrets.ref containers."${user}-db".ref ];
unitConfig.After = [ secrets.ref containers."${user}-db".ref ];
};
};
};
};
}

View file

@ -1,146 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "freshrss";
port = "37374";
stateDirectory = "/var/lib/freshrss";
serviceContainer = "freshrss";
stateSubDir = subDir: "${stateDirectory}/${subDir}";
createTmpfilesRule = subDir: "d ${stateSubDir subDir} 1755 ${user} ${user}";
dbDirectories = [ "database" ];
serviceDirectories = [ "data" "extensions" ];
in {
age.secrets = {
"freshrss.toml" = {
file = ./../secrets/freshrss.toml.age;
owner = "${user}";
};
};
millironx.podman-secrets.freshrss = {
user = "${user}";
secrets-files = [ config.age.secrets."freshrss.toml".path ];
};
services.caddy.virtualHosts."feeds.millironx.com".extraConfig = ''
reverse_proxy http://127.0.0.1:${port} {
header_up X-Forwarded-Port 443
}
'';
systemd.tmpfiles.rules = builtins.map createTmpfilesRule
([ stateDirectory ] ++ dbDirectories ++ serviceDirectories);
services.borgmatic.configurations."${config.networking.hostName}" = {
source_directories = builtins.map stateSubDir dbDirectories;
postgresql_databases = [{
name = serviceContainer;
psql_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${serviceContainer}-db psql --username=${user}";
pg_dump_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${serviceContainer}-db pg_dump --username=${user}";
pg_restore_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${serviceContainer}-db pg_restore --username=${user}";
}];
};
users.users."${user}" = {
group = "${user}";
isNormalUser = true;
home = "${stateDirectory}";
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = let
inherit (config.virtualisation.quadlet) containers;
inherit (config.virtualisation.quadlet) networks;
secrets = osConfig.millironx.podman-secrets.freshrss;
in {
containers = {
"${serviceContainer}-db" = {
autoStart = true;
containerConfig = {
image = "docker.io/library/postgres:16";
environments = {
POSTGRES_DB = "${user}";
POSTGRES_USER = "${user}";
};
secrets = [
"POSTGRES_PASSWORD,type=env"
"POSTGRES_PASSWORD,type=env,target=PGPASSWORD"
];
healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}";
healthInterval = "30s";
healthRetries = 5;
healthStartPeriod = "20s";
volumes =
[ "${stateDirectory}/database:/var/lib/postgresql/data:U" ];
networks = [ networks."${serviceContainer}".ref ];
};
unitConfig.Requires = [ secrets.ref ];
unitConfig.After = [ secrets.ref ];
};
"${serviceContainer}" = {
autoStart = true;
containerConfig = {
image = "docker.io/freshrss/freshrss:1";
# Required to allow the container to talk to the host ports, in
# other words, to resolve Authentik correctly
addHosts = [ "auth.millironx.com:host-gateway" ];
environments = {
TZ = osConfig.time.timeZone;
CRON_MIN = "2,32";
LISTEN = "0.0.0.0:${port}";
TRUSTED_PROXY = "172.16.0.1/12 192.168.0.1/16";
OIDC_ENABLED = "1";
OIDC_PROVIDER_METADATA_URL =
"https://auth.millironx.com/application/o/freshrss/.well-known/openid-configuration";
OIDC_REMOTE_USER_CLAIM = "preferred_username";
OIDC_SCOPES = "openid email profile";
OIDC_X_FORWARDED_HEADERS =
"X-Forwarded-Host X-Forwarded-Port X-Forwarded-Proto";
};
secrets = [
"OIDC_CLIENT_ID,type=env"
"OIDC_CLIENT_SECRET,type=env"
"OIDC_CLIENT_CRYPTO_KEY,type=env"
];
healthCmd = "cli/health.php";
healthTimeout = "10s";
healthStartPeriod = "60s";
healthStartupInterval = "11s";
healthInterval = "75s";
healthRetries = 3;
networks = [ networks."${serviceContainer}".ref ];
publishPorts = [ "127.0.0.1:${port}:${port}" ];
volumes = [
"${stateDirectory}/data:/var/www/FreshRSS/data:U"
"${stateDirectory}/extensions:/var/www/FreshRSS/extensions:U"
];
};
unitConfig.Requires =
[ secrets.ref containers."${serviceContainer}-db".ref ];
unitConfig.After =
[ secrets.ref containers."${serviceContainer}-db".ref ];
};
};
networks."${serviceContainer}" = { };
autoUpdate.enable = true;
autoEscape = true;
};
};
}

View file

@ -2,7 +2,7 @@
services.gpg-agent = {
enable = true;
enableBashIntegration = true;
enableSshSupport = false;
enableSshSupport = true;
enableZshIntegration = true;
defaultCacheTtl = 604800;
maxCacheTtl = 604800;

View file

@ -1,182 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "immich";
port = "46642";
containerPort = "2283";
redisPort = 64664;
stateDirectory = "/var/lib/${user}";
servicePaths = [ ];
databasePaths = [ "database" ];
s3BucketName = "millironx-photos";
s3MountDirectory = "/mount/s3/${s3BucketName}";
immich-version = "v2";
in {
age.secrets = {
millironx-photos-s3-token.file = ./../secrets/millironx-photos-s3.age;
redis-immich-password.file = ./../secrets/redis-immich-password.age;
"immich.toml" = {
file = ./../secrets/immich.toml.age;
owner = user;
};
};
millironx.podman-secrets.immich = {
inherit user;
secrets-files = [ config.age.secrets."immich.toml".path ];
};
systemd.tmpfiles.rules =
map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -")
([ "" ] ++ servicePaths ++ databasePaths);
environment.systemPackages = [ pkgs.s3fs ];
fileSystems."${s3BucketName}" = {
device = s3BucketName;
mountPoint = s3MountDirectory;
fsType = "fuse./run/current-system/sw/bin/s3fs";
noCheck = true;
options = [
"_netdev"
"allow_other"
"use_path_request_style"
"url=https://us-east-1.linodeobjects.com/"
"passwd_file=${config.age.secrets.millironx-photos-s3-token.path}"
"uid=${user}"
"gid=${user}"
"umask=0022"
];
};
services = {
borgmatic.configurations."${config.networking.hostName}" = {
source_directories = map (d: "${stateDirectory}/${d}") servicePaths;
postgresql_databases = [{
name = user;
psql_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db psql --username=${user}";
pg_dump_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_dump --username=${user}";
pg_restore_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_restore --username=${user}";
}];
};
caddy.virtualHosts."photos.millironx.com".extraConfig = ''
reverse_proxy http://127.0.0.1:${port}
'';
redis.servers."${user}" = {
enable = true;
port = redisPort;
bind = "0.0.0.0";
requirePassFile = config.age.secrets.redis-immich-password.path;
};
};
users.users."${user}" = {
group = user;
isNormalUser = true;
home = stateDirectory;
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = let
inherit (config.virtualisation.quadlet) containers;
inherit (config.virtualisation.quadlet) networks;
inherit (config.virtualisation.quadlet) volumes;
secrets = osConfig.millironx.podman-secrets.immich;
db-user = "postgres";
in {
autoUpdate.enable = true;
autoEscape = true;
networks."${user}" = { };
volumes.model-cache.volumeConfig = { };
containers = {
"${user}-db" = {
autoStart = true;
containerConfig = {
# For some reason, the -rootless variant seems to hang, so go with
# the rootful one (even though this user has no root access)
image = "docker.io/tensorchord/pgvecto-rs:pg16-v0.3.0";
environments = {
POSTGRES_DB = user;
POSTGRES_USER = db-user;
POSTGRES_INITDB_ARGS = "--data-checksums";
};
secrets = [
"POSTGRES_PASSWORD,type=env"
"POSTGRES_PASSWORD,type=env,target=PGPASSWORD"
];
healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}";
healthInterval = "30s";
healthRetries = 5;
healthStartPeriod = "20s";
volumes =
[ "${stateDirectory}/database:/var/lib/postgresql/data:U" ];
networks = [ networks."${user}".ref ];
};
unitConfig.Requires = [ secrets.ref ];
unitConfig.After = [ secrets.ref ];
};
"${user}-ml" = {
autoStart = true;
containerConfig = {
image =
"ghcr.io/immich-app/immich-machine-learning:${immich-version}";
networks = [ networks."${user}".ref ];
volumes = [ "${volumes.model-cache.ref}:/cache" ];
};
};
"${user}" = {
autoStart = true;
containerConfig = {
image = "ghcr.io/immich-app/immich-server:${immich-version}";
environments = {
DB_HOSTNAME = "${user}-db";
DB_USERNAME = db-user;
DB_DATABASE_NAME = user;
REDIS_HOSTNAME = "host.docker.internal";
REDIS_PORT = builtins.toString redisPort;
};
secrets =
map (s: "${s},type=env") [ "DB_PASSWORD" "REDIS_PASSWORD" ];
volumes = [
# Generally, mounts need the :U directive, but in the case of
# mounting the root of a bucket, that hangs. Uploads are verified
# to work without that, so everything should be fine
"${s3MountDirectory}:/usr/src/app/upload"
"/etc/localtime:/etc/localtime:ro"
];
networks = [ networks."${user}".ref ];
publishPorts = [ "127.0.0.1:${port}:${containerPort}" ];
addHosts = [ "auth.millironx.com:host-gateway" ];
};
unitConfig.Requires = [
secrets.ref
containers."${user}-db".ref
containers."${user}-ml".ref
];
unitConfig.After = [
secrets.ref
containers."${user}-db".ref
containers."${user}-ml".ref
];
};
};
};
};
}

View file

@ -1,132 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "navidrome";
port = "4533";
authentikPort = "9000";
stateDirectory = "/var/lib/${user}";
s3BucketName = "millironx-music";
s3MountDirectory = "/mount/s3/${s3BucketName}";
in {
age.secrets = {
millironx-music-s3-token.file = ./../secrets/millironx-music-s3.age;
"navidrome.toml" = {
file = ./../secrets/navidrome.toml.age;
owner = user;
};
};
millironx.podman-secrets.navidrome = {
inherit user;
secrets-files = [ config.age.secrets."navidrome.toml".path ];
};
environment.systemPackages = [ pkgs.s3fs ];
fileSystems."${s3BucketName}" = {
device = s3BucketName;
mountPoint = s3MountDirectory;
fsType = "fuse./run/current-system/sw/bin/s3fs";
noCheck = true;
options = [
"_netdev"
"allow_other"
"use_path_request_style"
"url=https://us-east-1.linodeobjects.com/"
"passwd_file=${config.age.secrets.millironx-music-s3-token.path}"
"uid=${user}"
"gid=${user}"
"umask=0022"
];
};
systemd.tmpfiles.rules =
map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -") [ "" "data" ];
services.borgmatic.configurations."${config.networking.hostName}" = {
source_directories = map (d: "${stateDirectory}/${d}") [ "data" ];
};
# Modified from
# - <https://www.navidrome.org/docs/getting-started/extauth-quickstart/#example-caddy-with-authentik>
# - <https://www.navidrome.org/docs/usage/integration/authentication/#caddy-with-forward_auth>
# Modifications are exclusively changes from Docker hostnames to 127.0.0.1 and
# port numbers
services.caddy.virtualHosts."music.millironx.com".extraConfig = ''
# Authentik output endpoint
reverse_proxy /outpost.goauthentik.io/* http://127.0.0.1:${authentikPort}
# Protect everything except share and subsonic endpoints
@protected not path /share/* /rest/*
forward_auth @protected http://127.0.0.1:${authentikPort} {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username>Remote-User
}
# Authentik uses the Authorization header if present, so should be able to
# authenticate subsonic clients that support BasicAuth. Requests from the
# Navidrome Web App will be authenticated via the existing session cookie.
# If you want to have Navidrome authenticate subsonic requests, remove this
# forward_auth block.
@subsonic path /rest/*
forward_auth @subsonic http://127.0.0.1:${authentikPort} {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-Authentik-Username>Remote-User
# Some clients that claim to support basicauth still expect a subsonic
# response in case of authentication failure instead of a proper basicauth
# response.
@error status 1xx 3xx 4xx 5xx
handle_response @error {
respond <<SUBSONICERR
<subsonic-response xmlns="http://subsonic.org/restapi" status="failed" version="1.16.1" type="proxy-auth" serverVersion="n/a" openSubsonic="true">
<error code="40" message="Invalid credentials or unsupported client"></error>
</subsonic-response>
SUBSONICERR 200
}
}
# Forward everything to Navidrome
reverse_proxy http://127.0.0.1:${port}
'';
users.users."${user}" = {
group = "${user}";
isNormalUser = true;
home = stateDirectory;
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = {
autoUpdate.enable = true;
containers.navidrome = {
autoStart = true;
containerConfig = {
image = "docker.io/deluan/navidrome:latest";
environments = {
ND_BASEURL = "https://music.millironx.com";
# pasta appears to use the static host IP so trust that
ND_EXTAUTH_TRUSTEDSOURCES = "23.239.13.247/24";
};
secrets =
map (s: "${s},type=env") [ "ND_LASTFM_APIKEY" "ND_LASTFM_SECRET" ];
volumes = [
"${s3MountDirectory}:/music:ro"
"${stateDirectory}/data:/data:U"
];
publishPorts = [ "127.0.0.1:${port}:${port}" ];
};
unitConfig.Requires =
[ osConfig.millironx.podman-secrets.navidrome.ref ];
unitConfig.After = [ osConfig.millironx.podman-secrets.navidrome.ref ];
};
};
};
}

50
services/nixos-update.nix Normal file
View file

@ -0,0 +1,50 @@
# This file is designed to be used in the imports
{ pkgs, config, ... }:
{
environment.systemPackages = [
(pkgs.writeScriptBin "update-nixos" ''
#!${pkgs.bash}/bin/bash
echo "Requesting system update..."
${pkgs.systemd}/bin/systemctl start nixos-update.service
'')
];
# Service to update NixOS configuration from git repo
systemd.services."nixos-update" = {
description = "Update NixOS configuration from git repository";
path = with pkgs; [ coreutils ];
script = ''
# Rebuild the system directly from the remote flake
${pkgs.nixos-rebuild}/bin/nixos-rebuild switch --flake git+https://code.millironx.com/millironx/nix-dotfiles.git#${config.networking.hostName}
'';
serviceConfig = {
Type = "oneshot";
User = "root";
};
};
# Timer to run the update service daily at 3am
systemd.timers."nixos-update" = {
wantedBy = [ "timers.target" ];
description = "Run NixOS update daily at 3am";
timerConfig = {
OnCalendar = "3:00";
Persistent = true;
Unit = "nixos-update.service";
};
};
# Polkit rule to allow non-root users to trigger the update
security.polkit.extraConfig = ''
polkit.addRule(function(action, subject) {
if (action.id == "org.freedesktop.systemd1.manage-units" &&
action.lookup("unit") == "nixos-update.service" &&
action.lookup("verb") == "start" &&
subject.isInGroup("wheel")) {
return polkit.Result.YES;
}
});
'';
}

View file

@ -1,9 +0,0 @@
{ ... }: {
services.openssh = {
enable = true;
settings = {
PermitRootLogin = "no";
PasswordAuthentication = false;
};
};
}

View file

@ -1,212 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
domain = "video.millironx.com";
user = "peertube";
port = "33788";
containerPort = "9000";
rtmpHostPort = "41936";
rtmpContainerPort = "1935";
redisPort = 63378;
stateDirectory = "/var/lib/${user}";
servicePaths = [ "data" "config" "assets" ];
databasePaths = [ "database" ];
peertubeVersion = "v8.1.4";
peertubeAssetsDir = "${pkgs.peertube}/client/dist";
in {
age.secrets = {
"redis-${user}-password".file = ./../secrets/redis-${user}-password.age;
"${user}.toml" = {
file = ./../secrets/${user}.toml.age;
owner = user;
};
};
millironx.podman-secrets.${user} = {
inherit user;
secrets-files = [ config.age.secrets."${user}.toml".path ];
};
systemd.tmpfiles.rules =
map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -")
([ "" ] ++ servicePaths ++ databasePaths);
services = {
borgmatic.configurations."${config.networking.hostName}" = {
source_directories = map (d: "${stateDirectory}/${d}") servicePaths;
postgresql_databases = [{
name = user;
psql_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db psql --username=${user}";
pg_dump_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_dump --username=${user}";
pg_restore_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_restore --username=${user}";
}];
};
caddy.virtualHosts.${domain}.extraConfig =
builtins.readFile ./../conf/peertube.caddyfile;
redis.servers.${user} = {
enable = true;
port = redisPort;
bind = "0.0.0.0";
requirePassFile = config.age.secrets."redis-${user}-password".path;
};
};
# This is a hack - I'm deliberately hijacking the systemd service that is
# set up by `services.caddy` in order to sync the `let` variables with the
# external Caddyfile via environment variables
# This is safe for NixOS 25.11 - see
# <https://github.com/NixOS/nixpkgs/blob/nixos-25.11/nixos/modules/services/web-servers/caddy/default.nix#L412>
systemd.services.caddy.environment = {
MILLIRONX_PEERTUBE_PORT = port;
MILLIRONX_PEERTUBE_ASSETS_DIR = peertubeAssetsDir;
MILLIRONX_PEERTUBE_DATA_DIR = "${stateDirectory}/data";
};
# Another hack - allows the Caddy user to be able to read files that
# PeerTube writes into its dist/ folders
users.users.${config.services.caddy.user}.extraGroups = [ user ];
# Forward RTMP (privileged) port to container-accessible (non-privileged) port
systemd = {
sockets."peertube-rtmp" = {
description = "PeerTube RTMP Socket";
wantedBy = [ "sockets.target" ];
socketConfig = {
ListenStream = "0.0.0.0:${rtmpContainerPort}";
Accept = false;
Service = "peertube-rtmp-forward.service";
};
};
services."peertube-rtmp-forward" = {
description = "PeerTube RTMP Port Forwarder";
requires = [ "peertube-rtmp.socket" ];
after = [ "network.target" ];
serviceConfig = {
Type = "notify";
ExecStart =
"${pkgs.systemd}/lib/systemd/systemd-socket-proxyd 127.0.0.1:${rtmpHostPort}";
PrivateTmp = true;
};
};
};
networking.firewall.allowedTCPPorts = [ 1935 ];
users.users.${user} = {
group = user;
isNormalUser = true;
home = stateDirectory;
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups.${user} = { };
home-manager.users.${user} = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = let
inherit (config.virtualisation.quadlet) containers;
inherit (config.virtualisation.quadlet) networks;
secrets = osConfig.millironx.podman-secrets.${user};
in {
autoUpdate.enable = true;
autoEscape = true;
networks.${user} = { };
containers = {
"${user}-db" = {
autoStart = true;
containerConfig = {
image = "docker.io/library/postgres:16";
environments = {
POSTGRES_DB = user;
POSTGRES_USER = user;
};
secrets = [
"POSTGRES_PASSWORD,type=env"
"POSTGRES_PASSWORD,type=env,target=PGPASSWORD"
];
healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}";
healthInterval = "30s";
healthRetries = 5;
healthStartPeriod = "20s";
volumes =
[ "${stateDirectory}/database:/var/lib/postgresql/data:U" ];
networks = [ networks."${user}".ref ];
};
unitConfig.Requires = [ secrets.ref ];
unitConfig.After = [ secrets.ref ];
};
"${user}" = {
# TODO: Once data is migrated from anderson, turn this server to
# autostart true
autoStart = false;
containerConfig = {
image = "docker.io/chocobozzz/peertube:${peertubeVersion}";
environments = {
PEERTUBE_DB_USERNAME = user;
PEERTUBE_DB_SSL = "false";
PEERTUBE_DB_HOSTNAME = "${user}-db";
PEERTUBE_WEBSERVER_HOSTNAME = domain;
PEERTUBE_TRUST_PROXY =
''["127.0.0.1","loopback","172.18.0.0/16"]'';
PEERTUBE_REDIS_HOSTNAME = "host.docker.internal";
PEERTUBE_REDIS_PORT = builtins.toString redisPort;
PEERTUBE_USER_VIDEO_QUOTA = "0";
PEERTUBE_SIGNUP_ENABLED = "false";
PEERTUBE_CONTACT_FORM_ENABLED = "false";
PEERTUBE_TRANSCODING_ENABLED = "true";
PEERTUBE_TRANSCODING_THREADS = "2";
PEERTUBE_TRANSCODING_144P = "true";
PEERTUBE_TRANSCODING_360P = "true";
PEERTUBE_TRANSCODING_480P = "true";
PEERTUBE_TRANSCODING_720P = "true";
PEERTUBE_TRANSCODING_1080P = "true";
PEERTUBE_TRANSCODING_HLS_ENABLED = "true";
PEERTUBE_OBJECT_STORAGE_ENABLED = "true";
PEERTUBE_OBJECT_STORAGE_WEB_VIDEOS_PREFIX = "videos";
PEERTUBE_OBJECT_STORAGE_UPLOAD_ACL_PUBLIC = "public-read";
PEERTUBE_OBJECT_STORAGE_STREAMING_PLAYLISTS_PREFIX = "playlists";
};
secrets = map (s: "${s},type=env") [
"PEERTUBE_DB_PASSWORD"
"PEERTUBE_SECRET"
"PEERTUBE_SMTP_USERNAME"
"PEERTUBE_SMTP_PASSWORD"
"PEERTUBE_SMTP_HOSTNAME"
"PEERTUBE_SMTP_PORT"
"PEERTUBE_SMTP_FROM"
"PEERTUBE_ADMIN_EMAIL"
"PEERTUBE_REDIS_AUTH"
"PEERTUBE_OBJECT_STORAGE_ENDPOINT"
"PEERTUBE_OBJECT_STORAGE_REGION"
"PEERTUBE_OBJECT_STORAGE_CREDENTIALS_ACCESS_KEY_ID"
"PEERTUBE_OBJECT_STORAGE_CREDENTIALS_SECRET_ACCESS_KEY"
"PEERTUBE_OBJECT_STORAGE_STREAMING_PLAYLISTS_BUCKET_NAME"
"PEERTUBE_OBJECT_STORAGE_WEB_VIDEOS_BUCKET_NAME"
];
networks = [ networks."${user}".ref ];
publishPorts = [ "127.0.0.1:${port}:${containerPort}" ];
addHosts = [ "auth.millironx.com:host-gateway" ];
volumes = [
"${stateDirectory}/data:/data"
"${stateDirectory}/config:/config"
"${peertubeAssetsDir}:/app/client/dist:ro"
];
};
unitConfig.Requires = [ secrets.ref containers."${user}-db".ref ];
unitConfig.After = [ secrets.ref containers."${user}-db".ref ];
};
};
};
};
}

27
services/pihole.nix Normal file
View file

@ -0,0 +1,27 @@
{ config, ... }:
{
age.secrets = {
pihole-credentials = {
file = ./../secrets/pihole.age;
owner = "root";
group = "root";
};
};
virtualisation = {
quadlet = {
containers = {
pihole = {
containerConfig = {
image = "docker.io/pihole/pihole:2025.06.2";
publishPorts =
[ "53:53/tcp" "53:53/udp" "80:80/tcp" "443:443/tcp" ];
environmentFiles = [ config.age.secrets.pihole-credentials.path ];
networks = [ "bridge" ];
dns = [ "127.0.0.1" "194.242.2.9" "9.9.9.9" ];
};
};
};
};
};
}

View file

@ -2,6 +2,7 @@
services.samba = {
enable = true;
package = pkgs.sambaFull;
securityType = "user";
openFirewall = true;
settings = {
global = {

View file

@ -1,49 +0,0 @@
{ ... }:
let port = "7327";
in {
services.searx = {
enable = true;
configureUwsgi = true;
uwsgiConfig = {
disable-logging = true;
http = ":${port}";
};
redisCreateLocally = true;
settings = {
general = {
instance_name = "Milliron X Search";
enable_metrics = false;
};
search = { autocomplete = "duckduckgo"; };
server = {
base_url = "https://search.millironx.com/";
limiter = true;
public_instance = true;
image_proxy = true;
method = "GET";
secret_key = "rC35eF8DRpJDqa";
};
ui = { query_in_title = false; };
hostnames = {
replace = { "(www.)?reddit.com$" = "old.reddit.com"; };
low_priority = [
"(.*.)?facebooks.com$"
"(.*.)?youtube.com$"
"(.*.)?youtu.be$"
"(.*.)?reddit.com$"
"(.*.)?redd.it$"
"(www.)?twitter.com$"
"(www.)?x.com$"
];
high_priority = [ "(.*.)?wikipedia.org$" ];
};
};
};
services.caddy.virtualHosts."search.millironx.com".extraConfig = ''
reverse_proxy http://127.0.0.1:${port}
'';
}

View file

@ -1,49 +0,0 @@
{ hostname, ... }: {
services.syncthing = let
devices = {
bracket.id =
"6I5AHYC-IWSO3SZ-TZY4SSR-Z7MGB2V-QMCMJXE-QW5JBQV-DBDU5YV-4LRLKQW";
boozer.id =
"JKLIUHR-SBAVQCX-43ETUQR-M4ZZA75-JXK7EOF-F5RJBG7-PT363R6-MJ6WLQ4";
} // (if hostname != "odyssey" then {
odyssey.id =
"YC6NDSU-2JRS4MY-BSM4B5V-FWPXKSJ-S573II2-HDOSWSN-DVIDORQ-UUHTKQB";
} else
{ }) // (if hostname != "corianne" then {
corianne.id =
"EN5KDDZ-F6DYDSR-KK35M2M-BVGBU4W-MVC4ENT-5EPWA6M-BBJPIBU-EQTPRQX";
} else
{ });
in {
enable = true;
settings = {
inherit devices;
folders = let deviceNames = builtins.attrNames devices;
in {
Logseq = {
label = "Logseq";
id = "kkqs5-4upcf";
path = "~/Logseq";
type = "sendreceive";
versioning = {
type = "trashcan";
params.cleanoutDays = 14;
};
devices = deviceNames;
};
SyncBucket = {
label = "SyncBucket";
id = "9l6gb-rkyou";
path = "~/SyncBucket";
type = "sendreceive";
versioning = {
type = "trashcan";
params.cleanoutDays = 14;
};
devices = deviceNames;
};
};
options = { urAccepted = -1; };
};
};
}

View file

@ -1,6 +0,0 @@
{ ... }: {
services.tailscale = {
enable = true;
useRoutingFeatures = "server";
};
}

View file

@ -1,170 +0,0 @@
{ config, pkgs, home-manager-quadlet-nix, ... }:
let
user = "vaultwarden";
port = "9285";
containerPort = port;
authentikPort = "9000";
stateDirectory = "/var/lib/${user}";
servicePaths = [ "data" ];
databasePaths = [ "database" ];
in {
age.secrets."vaultwarden.toml" = {
file = ./../secrets/vaultwarden.toml.age;
owner = user;
};
millironx.podman-secrets.vaultwarden = {
inherit user;
secrets-files = [ config.age.secrets."vaultwarden.toml".path ];
};
systemd.tmpfiles.rules =
map (d: "d ${stateDirectory}/${d} 1775 ${user} ${user} -")
([ "" ] ++ servicePaths ++ databasePaths);
services.borgmatic.configurations."${config.networking.hostName}" = {
source_directories = map (d: "${stateDirectory}/${d}") servicePaths;
postgresql_databases = [{
name = user;
psql_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db psql --username=${user}";
pg_dump_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_dump --username=${user}";
pg_restore_command =
"/run/wrappers/bin/sudo -iu ${user} ${pkgs.podman}/bin/podman exec ${user}-db pg_restore --username=${user}";
}];
};
services.caddy.virtualHosts."vault.millironx.com".extraConfig = ''
# See <https://github.com/dani-garcia/vaultwarden/wiki/Proxy-examples>
encode zstd gzip
header / {
Strict-Transport-Security "max-age=31536000;"
X-XSS-Protection "0"
X-Frame-Options "DENY"
X-Robots-Tag "noindex, nofollow"
X-Content-Type-Options "nosniff"
-Server
-X-Powered-By
-Last-Modified
}
@admin {
path /admin*
not remote_ip private_ranges 100.64.0.0/10
}
respond @admin "Access denied to remote clients. Use localhost or VPN." 403
reverse_proxy http://127.0.0.1:${port} {
header_up X-Real-IP {remote_host}
}
'';
services.crowdsec = {
localConfig.acquisitions = [{
source = "journalctl";
journalctl_filter = [ "_SYSTEMD_USER_UNIT=${user}.service" ];
labels.type = "bitwarden";
}];
hub.collections = [ "MariuszKociubinski/bitwarden" ];
};
users.users."${user}" = {
group = user;
isNormalUser = true;
home = stateDirectory;
createHome = true;
linger = true;
autoSubUidGidRange = true;
};
users.groups."${user}" = { };
home-manager.users."${user}" = { config, osConfig, ... }: {
imports = [ home-manager-quadlet-nix ];
home.stateVersion = "25.05";
virtualisation.quadlet = let
inherit (config.virtualisation.quadlet) containers;
inherit (config.virtualisation.quadlet) networks;
secrets = osConfig.millironx.podman-secrets.vaultwarden;
in {
autoUpdate.enable = true;
autoEscape = true;
networks."${user}" = { };
containers = {
"${user}-db" = {
autoStart = true;
containerConfig = {
image = "docker.io/library/postgres:16";
environments = {
POSTGRES_DB = user;
POSTGRES_USER = user;
};
secrets = [
"POSTGRES_PASSWORD,type=env"
"POSTGRES_PASSWORD,type=env,target=PGPASSWORD"
];
healthCmd = "pg_isready -d $\${POSTGRES_DB} -U $\${POSTGRES_USER}";
healthInterval = "30s";
healthRetries = 5;
healthStartPeriod = "20s";
volumes =
[ "${stateDirectory}/database:/var/lib/postgresql/data:U" ];
networks = [ networks."${user}".ref ];
};
unitConfig.Requires = [ secrets.ref ];
unitConfig.After = [ secrets.ref ];
};
"${user}" = {
autoStart = true;
containerConfig = {
image = "ghcr.io/dani-garcia/vaultwarden:latest";
addHosts = [ "auth.millironx.com:host-gateway" ];
environments = {
DOMAIN = "https://vault.millironx.com";
ROCKET_PORT = port;
PUSH_ENABLED = "true";
SIGNUPS_ALLOWED = "false";
SMTP_FROM_NAME = "Milliron X Vault";
SMTP_SECURITY = "force_tls";
SSO_ENABLED = "true";
SSO_ONLY = "true";
SSO_AUTHORITY =
"https://auth.millironx.com/application/o/vaultwarden/";
SSO_SCOPES = "openid profile email offline_access";
# Needed to keep token expiration errors from happening
# See <https://github.com/dani-garcia/vaultwarden/issues/6311#issuecomment-3929409097>
SSO_AUTH_ONLY_NOT_SESSION = "true";
};
secrets = map (s: "${s},type=env") [
"ADMIN_TOKEN"
"DATABASE_URL"
"PUSH_INSTALLATION_ID"
"PUSH_INSTALLATION_KEY"
"SMTP_FROM"
"SMTP_HOST"
"SMTP_PORT"
"SMTP_PASSWORD"
"SMTP_USERNAME"
"SSO_CLIENT_ID"
"SSO_CLIENT_SECRET"
"YUBICO_CLIENT_ID"
"YUBICO_SECRET_KEY"
];
volumes = [ "${stateDirectory}/data:/data:U" ];
networks = [ networks."${user}".ref ];
publishPorts = [ "127.0.0.1:${port}:${containerPort}" ];
};
unitConfig.Requires = [ secrets.ref containers."${user}-db".ref ];
unitConfig.After = [ secrets.ref containers."${user}-db".ref ];
};
};
};
};
}

View file

@ -15,8 +15,6 @@ in {
rig-install
];
age.secrets.firefox-policy.file = ./../../secrets/darwin-policies-json.age;
# Use a custom configuration.nix location.
# $ darwin-rebuild switch -I darwin-config=$HOME/.config/nixpkgs/darwin/configuration.nix
environment.darwinConfig = "$HOME/.config/home-manager/configuration.nix";
@ -26,35 +24,9 @@ in {
};
# Auto upgrade nix package and the daemon service.
nix = {
enable = true;
gc = {
automatic = true;
interval = { Weekday = 1; };
options = ''
--delete-older-than 90d
'';
};
settings = {
substituters =
[ "https://nix-community.cachix.org" "https://cache.nixos.org/" ];
trusted-public-keys = [
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
];
};
# Needed for rosetta-builder, see
# <https://github.com/cpick/nix-rosetta-builder/issues/40#issuecomment-3368602687>
# <https://github.com/cpick/nix-rosetta-builder/issues/37>
linux-builder = {
enable = true;
ephemeral = true;
};
extraOptions = ''
extra-platforms = x86_64-darwin
'';
};
nix-rosetta-builder.onDemand = true;
nix.enable = true;
#services.nix-daemon.tempDir = "/nix/tmp";
nix.package = pkgs.nix;
# Create /etc/zshrc that loads the nix-darwin environment.
programs.zsh.enable = true; # default shell on catalina
@ -99,12 +71,11 @@ in {
in [
(sysApp "Firefox")
(sysApp "Thunderbird")
(sysApp "Microsoft Outlook")
(sysApp "Zed")
(sysApp "Logseq")
(sysApp "Zed")
(sysApp "Steam")
(localApp "Instinct Dashboard")
(localApp "Carestream")
(chromeApp "Instinct Dashboard")
(chromeApp "Carestream")
];
show-process-indicators = true;
show-recents = false;
@ -159,11 +130,6 @@ in {
--user=${config.system.primaryUser} \
--set-home \
_rig-install ${r-version}
echo "Applying custom defaults..."
/usr/bin/defaults import \
/Library/Preferences/org.mozilla.firefox \
${config.age.secrets.firefox-policy.path}
'';
nix.settings.experimental-features = [ "nix-command" "flakes" ];
@ -192,7 +158,15 @@ in {
no_quarantine = true;
};
taps = [ "r-lib/rig" ];
taps = [
"homebrew/services"
{
name = "millironx/millironx";
clone_target =
"https://code.millironx.com/millironx/homebrew-millironx.git";
}
"r-lib/rig"
];
brews = [
"borgbackup/tap/borgbackup-fuse"
"buildkit"
@ -203,17 +177,23 @@ in {
"docker"
"docker-buildx"
"docker-credential-helper"
"firefoxpwa"
"mpv"
];
casks = [
"alt-tab"
"dash"
"anki"
"db-browser-for-sqlite"
"dolphin"
"firefox"
"freetube"
"ghostty"
"inkscape"
"iterm2"
"logi-options+"
"logseq"
"macfuse"
"musescore"
"nextcloud"
"openrct2"
"qownnotes"
@ -221,7 +201,6 @@ in {
"rig"
"rstudio"
"signal"
"skim"
"slack"
"stats"
"steam"
@ -231,7 +210,6 @@ in {
"ungoogled-chromium"
"veracrypt"
"vlc"
"vienna"
"vorta"
"zed"
"zotero"

View file

@ -8,7 +8,9 @@
imports = [
./hardware-configuration/bosephus.nix
./hardware-configuration/bosephus-external-drives.nix
./../../services/nixos-update.nix
./../../services/samba.nix
./../../services/pihole.nix
];
# Bootloader.
@ -16,8 +18,8 @@
boot.loader.efi.canTouchEfiVariables = true;
# Ignore lid - so I can close without having the system go into sleep mode
services.logind.settings.Login.HandleLidSwitch = "ignore";
services.logind.settings.Login.HandleLidSwitchDocked = "ignore";
services.logind.lidSwitch = "ignore";
services.logind.lidSwitchDocked = "ignore";
# Secrets
age.secrets = {

View file

@ -3,21 +3,8 @@
{
imports = [ # Include the results of the hardware scan.
./hardware-configuration/mcentire.nix
./../../modules/podman-secrets.nix
./../../services/borgmatic.nix
./../../services/caddy.nix
./../../services/nixos-update.nix
./../../services/crowdsec.nix
./../../services/authentik.nix
./../../services/audiobookshelf.nix
./../../services/fireflyiii.nix
./../../services/freshrss.nix
./../../services/immich.nix
./../../services/navidrome.nix
./../../services/openssh.nix
./../../services/peertube.nix
./../../services/searxng.nix
./../../services/tailscale.nix
./../../services/vaultwarden.nix
];
# Use the GRUB 2 boot loader.
@ -28,7 +15,6 @@
useDHCP = false;
interfaces.eth0.useDHCP = true;
hostName = "mcentire"; # Define your hostname.
firewall.allowedTCPPorts = [ 80 443 ];
};
# Set your time zone.
@ -54,7 +40,7 @@
millironx = {
isNormalUser = true;
description = "Thomas A. Christensen II";
extraGroups = [ "adm" "wheel" ];
extraGroups = [ "wheel" ];
};
};
@ -65,30 +51,11 @@
environment.systemPackages = with pkgs; [ neovim inetutils mtr sysstat git ];
age.secrets.redis-password = {
file = ./../../secrets/redis-password.age;
owner = config.services.redis.servers.redis.user;
};
services = {
# Do not "enable" database services, but include the package configuration
# so that borgmatic does not freak out about unset variables
postgresql.package = pkgs.postgresql_17;
redis.servers.redis = {
enable = true;
# Apparently, a port is actually required, see
# <https://discourse.nixos.org/t/how-to-setup-redis-server-configuration-nix/21878/7>
# Evaluating this in the Nix repl confirms that the port is set
# incorrectly in the final config when left out
port = 6379;
bind = "0.0.0.0";
requirePassFile = config.age.secrets.redis-password.path;
};
openssh.enable = true;
tailscale.enable = true;
};
virtualisation.quadlet.enable = true;
system.stateVersion = "25.05"; # Did you read the comment?
nix = { extraOptions = "experimental-features = nix-command flakes"; };
}