Mochi init

This commit is contained in:
Joseph Hanson 2024-06-20 08:59:56 -05:00
commit cc530d3d5f
Signed by: jahanson
SSH key fingerprint: SHA256:vy6dKBECV522aPAwklFM3ReKAVB086rT3oWwiuiFG7o
90 changed files with 3276 additions and 0 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use nix

View file

@ -0,0 +1,50 @@
name: "Build"
on:
pull_request:
jobs:
nix-build:
if: github.event.pull_request.draft == false
strategy:
fail-fast: false
matrix:
include:
- system: varda
os: native-aarch64
- system: durincore
os: native-x86_64
runs-on: ${{ matrix.os }}
env:
PATH: ${{ format('{0}:{1}', '/run/current-system/sw/bin', env.PATH) }}
steps:
- name: Checkout repository
uses: https://github.com/actions/checkout@v4
with:
fetch-depth: 0
- uses: https://github.com/cachix/cachix-action@v15
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
name: hsndev
# If you chose API tokens for write access OR if you have a private cache
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
# env:
# USER: 'root'
- name: Garbage collect build dependencies
run: nix-collect-garbage
- name: Build new ${{ matrix.system }} system
shell: bash
run: |
set -o pipefail
nix build \
".#top.${{ matrix.system }}" \
--profile ./profile \
--fallback \
-v \
--log-format raw \
> >(tee stdout.log) 2> >(tee /tmp/nix-build-err.log >&2)
- name: Push to Cachix
if: success()
env:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
run: nix build ".#top.${{ matrix.system }}" --json | jq -r .[].drvPath | cachix push hsndev

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
**/*.tmp.sops.yaml
**/*.sops.tmp.yaml
age.key
result*
.direnv
.kube

38
.pre-commit-config.yaml Normal file
View file

@ -0,0 +1,38 @@
---
fail_fast: false
repos:
- repo: https://github.com/adrienverge/yamllint
rev: v1.35.1
hooks:
- args:
- --config-file
- .yamllint.yaml
id: yamllint
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: fix-byte-order-marker
- id: mixed-line-ending
- id: check-added-large-files
args: [--maxkb=2048]
- id: check-merge-conflict
- id: check-executables-have-shebangs
- repo: https://github.com/Lucas-C/pre-commit-hooks
rev: v1.5.5
hooks:
- id: remove-crlf
- id: remove-tabs
exclude: (Makefile)
- repo: https://github.com/zricethezav/gitleaks
rev: v8.18.2
hooks:
- id: gitleaks
- repo: https://github.com/yuvipanda/pre-commit-hook-ensure-sops
rev: v1.1
hooks:
- id: sops-encryption
# Uncomment to exclude all markdown files from encryption
# exclude: *.\.md

29
.sops.yaml Normal file
View file

@ -0,0 +1,29 @@
---
# config files for sops & used for encrypting keys that sops-nix decrypts.
# each machine key is derieved from its generated `ssh_hosts_ed` file
# via ssh-to-age
# sops encrypts the secrets ready to decrypt with the private key of any of the below machines
# OR my 'main' key thats kept outside this repo securely.
# key-per-machine is a little more secure and a little more work than
# copying one key to each machine
keys:
- users:
- &jahanson age18kj3xhlvgjeg2awwku3r8d95w360uysu0w5ejghnp4kh8qmtge5qwa2vjp
- hosts:
- &durincore age1d9p83j52m2xg0vh9k7q0uwlxwhs3y6tlv68yg9s2h9mdw2fmmsqshddz5m
- &gandalf age1nuj9sk2k8ede06f8gk5twdlc593uuc7lll2dvuy20nxw9zn97u5swrcjpj
- &telperion age1z3vjvkead2h934n3w4m5m7tg4tj5qlzagsq6ly84h3tcu7x4ldsqd3s5fg
- &varda age1a8z3p24v32l9yxm5z2l8h7rpc3nhacyfv4jvetk2lenrvsdstd3sdu2kaf
creation_rules:
- path_regex: .*\.sops\.yaml$
key_groups:
- age:
- *durincore
- *gandalf
- *jahanson
- *telperion
- *varda

View file

@ -0,0 +1,23 @@
---
# yaml-language-server: $schema=https://taskfile.dev/schema.json
version: "3"
vars:
host: $HOSTNAME
tasks:
init:
desc: Initialize pre-commit hooks
cmds:
- pre-commit install --install-hooks
update:
desc: Update pre-commit dependencies
cmds:
- pre-commit autoupdate
run:
desc: Run pre-commit
cmds:
- pre-commit run --all-files

View file

@ -0,0 +1,18 @@
---
# yaml-language-server: $schema=https://taskfile.dev/schema.json
version: "3"
tasks:
re-encrypt:
desc: Decrypt and re-encrypt all sops secrets
silent: true
dir: "{{.USER_WORKING_DIR}}"
vars:
SECRET_FILES:
sh: find . -type f -name '*.sops.yaml' ! -name ".sops.yaml"
cmds:
- for: { var: SECRET_FILES }
cmd: |
echo "Re-encrypting {{ .ITEM }}"
sops --decrypt --in-place "{{ .ITEM }}"
sops --encrypt --in-place "{{ .ITEM }}"

11
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"recommendations": [
"jnoortheen.nix-ide",
"mikestead.dotenv",
"redhat.ansible",
"redhat.vscode-yaml",
"signageos.signageos-vscode-sops",
"pkief.material-icon-theme",
"ms-vscode-remote.remote-ssh"
]
}

32
.vscode/module.code-snippets vendored Normal file
View file

@ -0,0 +1,32 @@
{
"nix-module": {
"prefix": "nm",
"body": [
"{ lib",
", config",
", pkgs",
", ...",
"}:",
"with lib;",
"let",
" cfg = config.mySystem.${1}.${2};",
" app = \"${3}\"",
" appFolder = \"apps/${app}\";",
" persistentFolder = \"${config.mySystem.persistentFolder}/${appFolder}\";",
" user = app;",
" group = app;",
"in",
"{",
" options.mySystem.${1}.${2}.enable = mkEnableOption \"${4}\";",
"",
" config = mkIf cfg.enable {",
"",
" $5",
"",
" };",
"}",
""
],
"description": "nix-module"
}
}

27
.yamllint.yaml Normal file
View file

@ -0,0 +1,27 @@
---
ignore: |
.direnv/
.private/
.vscode/
*.sops.*
extends: default
rules:
truthy:
allowed-values: ["true", "false", "on"]
comments:
min-spaces-from-content: 1
line-length: disable
braces:
min-spaces-inside: 0
max-spaces-inside: 1
brackets:
min-spaces-inside: 0
max-spaces-inside: 0
indentation: enable

23
README.md Normal file
View file

@ -0,0 +1,23 @@
# jahanson's homelab
## Goals
- [ ] Learn nix
- [ ] Services I want to separate from my kubernetes cluster I will use Nix.
- [ ] Approval-based update automation for flakes.
- [ ] Expand usage to other shell environments such as WSL, etc
- [ ] keep it simple, use trusted boring tools
## TODO
- [ x ] Forgejo Actions
- [ ] Bring over hosts
- [ x ] Varda (forgejo)
- [ ] Telperion (network services)
- [ ] Gandalf (NixNAS)
- [ x ] Thinkpad T470
## Links & References
- [truxnell/dotfiles](https://github.com//truxnell/nix-config/)
- [billimek/dotfiles](https://github.com/billimek/dotfiles/)

33
Taskfile.yaml Normal file
View file

@ -0,0 +1,33 @@
---
# go-task runner file - rest of config in .taskfiles/**.*.yaml
version: "3"
includes:
sops:
taskfile: ".taskfiles/sops"
dir: .taskfiles/sops
pre:
taskfile: ".taskfiles/pre-commit"
dir: "{{.ROOT_DOR}}"
tasks:
default:
silent: true
cmds:
- task -l
lint:
desc: Run statix lint
cmds:
- statix check .
check:
desc: Check project files
cmds:
- task: lint
- task: pc-run
format:
desc: Check project files
cmds:
- nixpkgs-fmt {{.ROOT_DIR}}

0
flake.nix Normal file
View file

View file

@ -0,0 +1,93 @@
{ pkgs, config, ... }:
with config;
{
imports = [
../modules
];
config = {
myHome.username = "jahanson";
myHome.homeDirectory = "/home/jahanson/";
systemd.user.sessionVariables = {
EDITOR = "vim";
};
home = {
# Install these packages for my user
packages = with pkgs; [
# misc
file
which
tree
gnused
gnutar
gawk
zstd
gnupg
# archives
zip
xz
unzip
p7zip
# cli
_1password
bat
dbus
direnv
git
nix-index
python3
fzf
ripgrep
vim
lsd
# terminal file managers
nnn
ranger
yazi
# networking tools
iperf3
dnsutils # `dig` + `nslookup`
ldns # replacement of `dig`, it provide the command `drill`
aria2 # A lightweight multi-protocol & multi-source command-line download utility
socat # replacement of openbsd-netcat
nmap # A utility for network discovery and security auditing
ipcalc # it is a calculator for the IPv4/v6 addresses
# system tools
sysstat
lm_sensors # for `sensors` command
ethtool
pciutils # lspci
usbutils # lsusb
# system call monitoring
strace # system call monitoring
ltrace # library call monitoring
lsof # list open files
btop # replacement of htop/nmon
iotop # io monitoring
iftop # network monitoring
# dev utils
direnv # shell environment management
envsubst
# nix tools
nvd
];
sessionVariables = {
EDITOR = "vim";
};
};
};
}

View file

@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./global.nix
];
}

View file

@ -0,0 +1,42 @@
{ pkgs, config, ... }:
with config;
{
imports = [
./global.nix
];
myHome.programs.firefox.enable = true;
myHome.shell = {
starship.enable = true;
fish.enable = true;
git = {
enable = true;
username = "Joseph Hanson";
email = "joe@veri.dev";
signingKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIDSAmssproxG+KsVn2DfuteBAemHrmmAFzCtldpKl4J";
};
};
home = {
# Install these packages for my user
packages = with pkgs;
[
#apps
_1password-gui
discord
flameshot
vlc
# cli
brightnessctl
# dev utils
pre-commit # Pre-commit tasks for git
minio-client # S3 management
shellcheck # shell script linting
];
};
}

View file

@ -0,0 +1,36 @@
{ lib, ... }: {
imports = [
./shell
./programs
./security
];
options.myHome.username = lib.mkOption {
type = lib.types.str;
description = "users username";
default = "jahanson";
};
options.myHome.homeDirectory = lib.mkOption {
type = lib.types.str;
description = "users homedir";
default = "jahanson";
};
# Home-manager defaults
config = {
home.stateVersion = "24.11";
programs = {
home-manager.enable = true;
git.enable = true;
};
xdg.enable = true;
nixpkgs.config = {
allowUnfree = true;
};
};
}

View file

@ -0,0 +1,5 @@
{ ... }: {
imports = [
./firefox
];
}

View file

@ -0,0 +1,32 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.myHome.programs.firefox;
in
{
options.myHome.programs.firefox.enable = mkEnableOption "Firefox";
config = mkIf cfg.enable
{
programs.firefox = {
enable = true;
package = pkgs.firefox.override
{
extraPolicies = {
DontCheckDefaultBrowser = true;
DisablePocket = true;
# See nixpkgs' firefox/wrapper.nix to check which options you can use
nativeMessagingHosts = [
# Gnome shell native connector
pkgs.gnome-browser-connector
# plasma connector
# plasma5Packages.plasma-browser-integration
];
};
};
policies = import ./policies.nix;
profiles.default = import ./profile-default.nix { inherit pkgs; };
};
};
}

View file

@ -0,0 +1,20 @@
{
DisableTelemetry = true;
DisableFirefoxStudies = true;
EnableTrackingProtection = {
Value = true;
Locked = true;
Cryptomining = true;
Fingerprinting = true;
};
DisablePocket = true;
# DisableFirefoxAccounts = true;
# DisableAccounts = true;
# DisableFirefoxScreenshots = true;
# OverrideFirstRunPage = "";
OverridePostUpdatePage = "";
DontCheckDefaultBrowser = true;
DisplayBookmarksToolbar = "never"; # alternatives: "always" or "newtab"
DisplayMenuBar = "default-off"; # alternatives: "always", "never" or "default-on"
SearchBar = "unified"; # alternative: "separate"
}

View file

@ -0,0 +1,32 @@
{ pkgs }:
{
id = 0;
name = "default";
isDefault = true;
settings = {
"browser.startup.homepage" = "https://status.hsn.dev";
"browser.search.suggest.enabled.private" = false;
# 0 => blank page
# 1 => your home page(s) {default}
# 2 => the last page viewed in Firefox
# 3 => previous session windows and tabs
"browser.startup.page" = "3";
"browser.send_pings" = false;
# Do not track
"privacy.donottrackheader.enabled" = "true";
"privacy.donottrackheader.value" = 1;
"browser.display.use_system_colors" = "true";
"browser.display.use_document_colors" = "false";
"devtools.theme" = "dark";
"extensions.pocket.enabled" = false;
};
extensions = with pkgs.nur.repos.rycee.firefox-addons; [
ublock-origin
privacy-badger
link-cleaner
refined-github
];
}

View file

@ -0,0 +1,5 @@
{ ... }: {
imports = [
./gnome
];
}

View file

@ -0,0 +1,36 @@
# Adjusted manually from generated output of dconf2nix
# https://github.com/gvolpe/dconf2nix
{ lib, pkgs, osConfig, ... }:
with lib.hm.gvariant; {
config = lib.mkIf osConfig.mySystem.de.gnome.enable {
# add user packages
home.packages = with pkgs; [
dconf2nix
];
# worked out from dconf2nix
# dconf dump / | dconf2nix > dconf.nix
# can also dconf watch
dconf.settings = {
"org/gnome/mutter" = {
edge-tiling = true;
workspaces-only-on-primary = false;
};
"org/gnome/desktop/wm/preferences" = {
workspace-names = [ "sys" "talk" "web" "edit" "run" ];
};
"org/gnome/shell" = {
disabled-extensions = [ "apps-menu@gnome-shell-extensions.gcampax.github.com" "light-style@gnome-shell-extensions.gcampax.github.com" "places-menu@gnome-shell-extensions.gcampax.github.com" "drive-menu@gnome-shell-extensions.gcampax.github.com" "window-list@gnome-shell-extensions.gcampax.github.com" "workspace-indicator@gnome-shell-extensions.gcampax.github.com" ];
enabled-extensions = [ "appindicatorsupport@rgcjonas.gmail.com" "caffeine@patapon.info" "dash-to-dock@micxgx.gmail.com" "gsconnect@andyholmes.github.io" "Vitals@CoreCoding.com" "sp-tray@sp-tray.esenliyim.github.com" ];
favorite-apps = [ "org.gnome.Nautilus.desktop" "firefox.desktop" "org.wezfurlong.wezterm.desktop" "PrusaGcodeviewer.desktop" "spotify.desktop" "org.gnome.Console.desktop" "codium.desktop" "discord.desktop" ];
};
"org/gnome/nautilus/preferences" = {
default-folder-viewer = "list-view";
};
"org/gnome/nautilus/icon-view" = {
default-zoom-level = "small";
};
};
};
}

View file

@ -0,0 +1,6 @@
{ ... }: {
imports = [
./browsers
./de
];
}

View file

@ -0,0 +1,5 @@
{ ... }: {
imports = [
./ssh
];
}

View file

@ -0,0 +1,21 @@
{ config, lib, ... }:
with lib; let
cfg = config.myHome.security.ssh;
in
{
options.myHome.security.ssh = {
enable = mkEnableOption "ssh";
matchBlocks = mkOption {
type = types.attrs;
default = { };
};
};
config = mkIf cfg.enable {
programs.ssh = {
inherit (cfg) matchBlocks;
enable = true;
# addKeysToAgent = "yes";
};
};
}

View file

@ -0,0 +1,8 @@
{ ... }: {
imports = [
./fish
./starship
./wezterm
./git
];
}

View file

@ -0,0 +1,75 @@
{ config, pkgs, lib, ... }:
with lib; let
inherit (config.myHome) username homeDirectory;
cfg = config.myHome.shell.fish;
in
{
options.myHome.shell.fish = {
enable = mkEnableOption "fish";
};
config = mkMerge [
(mkIf cfg.enable {
programs.fish = {
enable = true;
shellAliases = {
m = "less";
ls = "${pkgs.lsd}/bin/lsd";
ll = "${pkgs.lsd}/bin/lsd -l";
la = "${pkgs.lsd}/bin/lsd -a";
lt = "${pkgs.lsd}/bin/lsd --tree";
lla = "${pkgs.lsd}/bin/lsd -la";
tm = "tmux attach -t (basename $PWD) || tmux new -s (basename $PWD)";
x = "exit";
};
shellAbbrs = {
nrs = "sudo nixos-rebuild switch --flake .";
nvdiff = "nvd diff /run/current-system result";
};
interactiveShellInit = ''
# Erase fish_mode_prompt function
functions -e fish_mode_prompt
function remove_path
if set -l index (contains -i $argv[1] $PATH)
set --erase --universal fish_user_paths[$index]
end
end
function update_path
if test -d $argv[1]
fish_add_path -m $argv[1]
else
remove_path $argv[1]
end
end
# Paths are in reverse priority order
update_path /opt/homebrew/opt/postgresql@16/bin
update_path /opt/homebrew/bin
update_path ${homeDirectory}/.krew/bin
update_path /nix/var/nix/profiles/default/bin
update_path /run/current-system/sw/bin
update_path /etc/profiles/per-user/${username}/bin
update_path /run/wrappers/bin
update_path ${homeDirectory}/.nix-profile/bin
update_path ${homeDirectory}/go/bin
update_path ${homeDirectory}/.cargo/bin
update_path ${homeDirectory}/.local/bin
set -gx EDITOR "vim"
set -gx LSCOLORS "Gxfxcxdxbxegedabagacad"
set -gx LS_COLORS 'di=01;34:ln=01;36:pi=33:so=01;35:bd=01;33:cd=33:or=31:ex=01;32:*.7z=01;31:*.bz2=01;31:*.gz=01;31:*.lz=01;31:*.lzma=01;31:*.lzo=01;31:*.rar=01;31:*.tar=01;31:*.tbz=01;31:*.tgz=01;31:*.xz=01;31:*.zip=01;31:*.zst=01;31:*.zstd=01;31:*.bmp=01;35:*.tiff=01;35:*.tif=01;35:*.TIFF=01;35:*.gif=01;35:*.jpeg=01;35:*.jpg=01;35:*.png=01;35:*.webp=01;35:*.pot=01;35:*.pcb=01;35:*.gbr=01;35:*.scm=01;35:*.xcf=01;35:*.spl=01;35:*.stl=01;35:*.dwg=01;35:*.ply=01;35:*.apk=01;31:*.deb=01;31:*.rpm=01;31:*.jad=01;31:*.jar=01;31:*.crx=01;31:*.xpi=01;31:*.avi=01;35:*.divx=01;35:*.m2v=01;35:*.m4v=01;35:*.mkv=01;35:*.MOV=01;35:*.mov=01;35:*.mp4=01;35:*.mpeg=01;35:*.mpg=01;35:*.sample=01;35:*.wmv=01;35:*.3g2=01;35:*.3gp=01;35:*.gp3=01;35:*.webm=01;35:*.flv=01;35:*.ogv=01;35:*.f4v=01;35:*.3ga=01;35:*.aac=01;35:*.m4a=01;35:*.mp3=01;35:*.mp4a=01;35:*.oga=01;35:*.ogg=01;35:*.opus=01;35:*.s3m=01;35:*.sid=01;35:*.wma=01;35:*.flac=01;35:*.alac=01;35:*.mid=01;35:*.midi=01;35:*.pcm=01;35:*.wav=01;35:*.ass=01;33:*.srt=01;33:*.ssa=01;33:*.sub=01;33:*.git=01;33:*.ass=01;33:*README=33:*README.rst=33:*README.md=33:*LICENSE=33:*COPYING=33:*INSTALL=33:*COPYRIGHT=33:*AUTHORS=33:*HISTORY=33:*CONTRIBUTOS=33:*PATENTS=33:*VERSION=33:*NOTICE=33:*CHANGES=33:*CHANGELOG=33:*log=33:*.txt=33:*.md=33:*.markdown=33:*.nfo=33:*.org=33:*.pod=33:*.rst=33:*.tex=33:*.texttile=33:*.bib=35:*.json=35:*.jsonl=35:*.jsonnet=35:*.libsonnet=35:*.rss=35:*.xml=35:*.fxml=35:*.toml=35:*.yaml=35:*.yml=35:*.dtd=35:*.cbr=35:*.cbz=35:*.chm=35:*.pdf=35:*.PDF=35:*.epub=35:*.awk=35:*.bash=35:*.bat=35:*.BAT=35:*.sed=35:*.sh=35:*.zsh=35:*.vim=35:*.py=35:*.ipynb=35:*.rb=35:*.gemspec=35:*.pl=35:*.PL=35:*.t=35:*.msql=35:*.mysql=35:*.pgsql=35:*.sql=35:*.r=35:*.R=35:*.cljw=35:*.scala=35:*.sc=35:*.dart=35:*.asm=35:*.cl=35:*.lisp=35:*.rkt=35:*.el=35:*.elc=35:*.eln=35:*.lua=35:*.c=35:*.C=35:*.h=35:*.H=35:*.tcc=35:*.c++=35:*.h++=35:*.hpp=35:*.hxx=35:*ii.=35:*.m=35:*.M=35:*.cc=35:*.cs=35:*.cp=35:*.cpp=35:*.cxx=35:*.go=35:*.f=35:*.F=35:*.nim=35:*.nimble=35:*.s=35:*.S=35:*.rs=35:*.scpt=35:*.swift=35:*.vala=35:*.vapi=35:*.hs=35:*.lhs=35:*.zig=35:*.v=35:*.pyc=35:*.tf=35:*.tfstate=35:*.tfvars=35:*.css=35:*.less=35:*.sass=35:*.scss=35:*.htm=35:*.html=35:*.jhtm=35:*.mht=35:*.eml=35:*.coffee=35:*.java=35:*.js=35:*.mjs=35:*.jsm=35:*.jsp=35:*.rasi=35:*.php=35:*.twig=35:*.vb=35:*.vba=35:*.vbs=35:*.Dockerfile=35:*.dockerignore=35:*.Makefile=35:*.MANIFEST=35:*.am=35:*.in=35:*.hin=35:*.scan=35:*.m4=35:*.old=35:*.out=35:*.SKIP=35:*.diff=35:*.patch=35:*.tmpl=35:*.j2=35:*PKGBUILD=35:*config=35:*.conf=35:*.service=31:*.@.service=31:*.socket=31:*.swap=31:*.device=31:*.mount=31:*.automount=31:*.target=31:*.path=31:*.timer=31:*.snapshot=31:*.allow=31:*.swp=31:*.swo=31:*.tmp=31:*.pid=31:*.state=31:*.lock=31:*.lockfile=31:*.pacnew=31:*.un=31:*.orig=31:'
'';
};
home.sessionVariables.fish_greeting = "";
programs.nix-index.enable = true;
})
];
}

View file

@ -0,0 +1,69 @@
{ pkgs, config, lib, ... }:
let
cfg = config.myHome.shell.git;
in
{
options.myHome.shell.git = {
enable = lib.mkEnableOption "git";
username = lib.mkOption {
type = lib.types.str;
};
email = lib.mkOption {
type = lib.types.str;
};
signingKey = lib.mkOption {
type = lib.types.str;
};
};
config = lib.mkMerge [
(lib.mkIf cfg.enable {
programs.gh.enable = true;
programs.gpg.enable = true;
programs.git = {
enable = true;
userName = cfg.username;
userEmail = cfg.email;
extraConfig = {
core.autocrlf = "input";
init.defaultBranch = "main";
pull.rebase = true;
rebase.autoStash = true;
# public key for signing commits
user.signingKey = cfg.signingKey;
# ssh instead of gpg
gpg.format = "ssh";
# 1password signing gui git signing
gpg.ssh.program = "${pkgs._1password-gui}/bin/op-ssh-sign";
# Auto sign commits without -S
commit.gpgsign = true;
};
aliases = {
co = "checkout";
};
ignores = [
# Mac OS X hidden files
".DS_Store"
# Windows files
"Thumbs.db"
# asdf
".tool-versions"
# Sops
".decrypted~*"
"*.decrypted.*"
# Python virtualenvs
".venv"
];
};
home.packages = [
pkgs.git-filter-repo
pkgs.tig
pkgs.lazygit
];
})
];
}

View file

@ -0,0 +1,115 @@
{ lib
, config
, ...
}:
with lib; let
cfg = config.myHome.shell.starship;
in
{
options.myHome.shell.starship = { enable = mkEnableOption "starship"; };
config = mkIf cfg.enable {
programs.starship = {
enable = true;
settings = {
add_newline = false;
command_timeout = 1000;
format = lib.concatStrings [
"$username"
"$hostname"
"$directory"
"$git_branch"
"$git_status"
"$\{custom.direnv\}"
"$fill"
"$python"
"$status"
"$cmd_duration"
"$line_break"
"$character"
];
username = {
style_user = "yellow";
style_root = "red";
format = "[$user]($style)";
show_always = false;
};
hostname = {
ssh_only = true;
format = "[@$hostname]($style) in ";
style = "green";
};
directory = {
truncation_length = 3;
format = "[$path]($style)[$read_only]($read_only_style) ";
style = "blue";
read_only = " ";
truncation_symbol = "../";
truncate_to_repo = true;
fish_style_pwd_dir_length = 1;
};
git_branch = {
format = "on [$symbol$branch]($style) ";
style = "purple";
symbol = " ";
};
git_status = {
format = "([$all_status$ahead_behind]($style) )";
style = "purple";
conflicted = " ";
ahead = " ";
behind = " ";
diverged = "󰆗 ";
up_to_date = " ";
untracked = " ";
stashed = " ";
modified = " ";
staged = " ";
renamed = " ";
deleted = " ";
};
fill = {
symbol = " ";
};
python = {
format = "[\${symbol}\${pyenv_prefix}(\${version} )(\($virtualenv\) )]($style)";
symbol = "🐍 ";
};
status = {
disabled = false;
format = "[$symbol]($style) ";
symbol = " ";
success_symbol = " ";
style = "red";
};
cmd_duration = {
min_time = 2000;
format = "took [$duration]($style) ";
style = "yellow";
};
character = {
success_symbol = "[](green)";
error_symbol = "[](green)";
vicmd_symbol = "[](purple)";
};
custom.direnv = {
format = "[$symbol]($style)";
symbol = " ";
style = "blue";
when = "env | grep -E '^DIRENV_FILE='";
};
};
};
};
}

View file

@ -0,0 +1,43 @@
{ config
, pkgs
, lib
, ...
}:
with lib; let
cfg = config.myHome.shell.wezterm;
in
{
options.myHome.shell.wezterm = {
enable = mkEnableOption "wezterm";
configPath = mkOption {
type = types.str;
};
};
config = mkIf cfg.enable {
# xdg.configFile."wezterm/wezterm.lua".source = config.lib.file.mkOutOfStoreSymlink cfg.configPath;
programs.wezterm.package = pkgs.unstable.wezterm;
programs.wezterm = {
enable = true;
extraConfig = ''
local wez = require('wezterm')
return {
-- issue relating to nvidia drivers
-- https://github.com/wez/wezterm/issues/2011
-- had to build out 550.67 manually to 'fix'
enable_wayland = true,
color_scheme = "Dracula (Official)",
check_for_updates = false,
window_background_opacity = .90,
window_padding = {
left = '2cell',
right = '2cell',
top = '1cell',
bottom = '0cell',
},
}
'';
};
};
}

View file

@ -0,0 +1,33 @@
{ ... }: {
config = {
# hardware-configuration.nix - half of the hardware-configuration.nix file
networking.hostId = "ad4380db";
networking.hostName = "durincore";
fileSystems."/" =
{ device = "rpool/root";
fsType = "zfs";
};
fileSystems."/home" =
{ device = "rpool/home";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/F1B9-CA7C";
fsType = "vfat";
options = [ "fmask=0077" "dmask=0077" ];
};
swapDevices = [ ];
# System settings and services.
mySystem = {
system.motd.networkInterfaces = [ "enp0s31f6" "wlp4s0" ];
};
};
}

View file

@ -0,0 +1,34 @@
{ ... }: {
imports = [ ];
networking.hostId = "cdab8473";
networking.hostName = "varda"; # Define your hostname.
fileSystems."/" = {
device = "rpool/root";
fsType = "zfs";
};
fileSystems."/home" = {
device = "rpool/home";
fsType = "zfs";
};
fileSystems."/boot" =
{ device = "/dev/disk/by-uuid/8091-E7F2";
fsType = "vfat";
};
swapDevices = [ ];
# System settings and services.
mySystem = {
system.motd.networkInterfaces = [ "enp1s0" ];
security.acme.enable = true;
services = {
forgejo.enable = true;
nginx.enable = true;
};
};
}

43
nixos/lib/default.nix Normal file
View file

@ -0,0 +1,43 @@
{ lib, ... }:
with lib;
rec {
firstOrDefault = first: default: if first != null then first else default;
existsOrDefault = x: set: default: if builtins.hasAttr x set then builtins.getAttr x set else default;
# main service builder
mkService = options: (
let
user = existsOrDefault "user" options "568";
group = existsOrDefault "group" options "568";
enableBackups = (lib.attrsets.hasAttrByPath [ "persistence" "folder" ] options)
&& (lib.attrsets.attrByPath [ "persistence" "enable" ] true options);
# Security options for containers
containerExtraOptions = lib.optionals (lib.attrsets.attrByPath [ "container" "caps" "privileged" ] false options) [ "--privileged" ]
++ lib.optionals (lib.attrsets.attrByPath [ "container" "caps" "readOnly" ] false options) [ "--read-only" ]
++ lib.optionals (lib.attrsets.attrByPath [ "container" "caps" "tmpfs" ] false options) [ (map (folders: "--tmpfs=${folders}") tmpfsFolders) ]
++ lib.optionals (lib.attrsets.attrByPath [ "container" "caps" "noNewPrivileges" ] false options) [ "--security-opt=no-new-privileges" ]
++ lib.optionals (lib.attrsets.attrByPath [ "container" "caps" "dropAll" ] false options) [ "--cap-drop=ALL" ]
;
in
{
virtualisation.oci-containers.containers.${options.app} = mkIf options.container.enable {
image = "${options.container.image}";
user = "${user}:${group}";
environment = {
TZ = options.timeZone;
} // options.container.env;
environmentFiles = lib.attrsets.attrByPath [ "container" "envFiles" ] [ ] options;
volumes = [ "/etc/localtime:/etc/localtime:ro" ] ++
lib.optionals (lib.attrsets.hasAttrByPath [ "container" "persistentFolderMount" ] options) [
"${options.persistence.folder}:${options.container.persistentFolderMount}:rw"
] ++ lib.attrsets.attrByPath [ "container" "volumes" ] [ ] options;
extraOptions = containerExtraOptions;
};
systemd.tmpfiles.rules = lib.optionals (lib.attrsets.hasAttrByPath [ "persistence" "folder" ] options) [ "d ${options.persistence.folder} 0750 ${user} ${group} -" ];
}
);
}

0
nixos/modules/.gitkeep Normal file
View file

9
nixos/modules/README.md Normal file
View file

@ -0,0 +1,9 @@
## Modules
A set of 'custom' modules with the aim to enable easy on/off/settings to build up a system modularly to my 'specs'.
The main goal is to build up a `mySystem` options key which is easy to read and toggle functionality on and off.
This option key will largely be manipulated by a profile to build up a host to a base, then toggle specific options from there.
I will _try_ and only do modules for things I want to be able to configure, and just use nixos config directly for some simple static things.

View file

@ -0,0 +1,3 @@
{
mySystem = import ./nixos;
}

View file

@ -0,0 +1,56 @@
{ lib, config, ... }:
with lib;
let
app = "backrest";
image = "garethgeorge/backrest:v1.1.0";
user = "568"; #string
group = "568"; #string
port = 9898; #int
cfg = config.mySystem.services.${app};
appFolder = "/var/lib/${app}";
# persistentFolder = "${config.mySystem.persistentFolder}/var/lib/${appFolder}";
in
{
options.mySystem.services.${app} =
{
enable = mkEnableOption "${app}";
addToHomepage = mkEnableOption "Add ${app} to homepage" // { default = true; };
};
config = mkIf cfg.enable {
# ensure folder exist and has correct owner/group
systemd.tmpfiles.rules = [
"d ${appFolder}/config 0750 ${user} ${group} -"
"d ${appFolder}/data 0750 ${user} ${group} -"
"d ${appFolder}/cache 0750 ${user} ${group} -"
];
virtualisation.oci-containers.containers.${app} = {
image = "${image}";
user = "${user}:${group}";
environment = {
BACKREST_PORT = "9898";
BACKREST_DATA = "/data";
BACKREST_CONFIG = "/config/config.json";
XDG_CACHE_HOME = "/cache";
};
volumes = [
"${appFolder}/nixos/config:/config:rw"
"${appFolder}/nixos/data:/data:rw"
"${appFolder}/nixos/cache:/cache:rw"
"${config.mySystem.nasFolder}/backup/nixos/nixos:/repos:rw"
"/etc/localtime:/etc/localtime:ro"
];
};
services.nginx.virtualHosts."${app}.${config.networking.domain}" = {
useACMEHost = config.networking.domain;
forceSSL = true;
locations."^~ /" = {
proxyPass = "http://${app}:${builtins.toString port}";
extraConfig = "resolver 10.88.0.1;";
};
};
};
}

View file

@ -0,0 +1,5 @@
{
imports = [
./backrest
];
}

View file

@ -0,0 +1,5 @@
{
imports = [
./gnome.nix
];
}

View file

@ -0,0 +1,101 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.mySystem.de.gnome;
in
{
options.mySystem.de.gnome.enable = mkEnableOption "GNOME";
options.mySystem.de.gnome.systrayicons = mkEnableOption "Enable systray icons" // { default = true; };
options.mySystem.de.gnome.gsconnect = mkEnableOption "Enable gsconnect (KDEConnect for GNOME)" // { default = true; };
config = mkIf cfg.enable {
# Ref: https://nixos.wiki/wiki/GNOME
# GNOME plz
services = {
displayManager = {
defaultSession = "gnome";
autoLogin = {
enable = false;
user = "jahanson"; # TODO move to config overlay
};
};
xserver = {
enable = true;
xkb.layout = "us"; # `localctl` will give you
displayManager = {
gdm.enable = true;
};
desktopManager = {
# GNOME
gnome.enable = true;
};
};
udev.packages = optionals cfg.systrayicons [ pkgs.gnome.gnome-settings-daemon ]; # support appindicator
};
# systyray icons
# extra pkgs and extensions
environment = {
systemPackages = with pkgs; [
wl-clipboard # ls ~/Downloads | wl-copy or wl-paste > clipboard.txt
playerctl # gsconnect play/pause command
pamixer # gcsconnect volume control
gnome.gnome-tweaks
gnome.dconf-editor
# This installs the extension packages, but
# dont forget to enable them per-user in dconf settings -> "org/gnome/shell"
gnomeExtensions.vitals
gnomeExtensions.caffeine
gnomeExtensions.dash-to-dock
]
++ optionals cfg.systrayicons [ pkgs.gnomeExtensions.appindicator ];
};
# enable gsconnect
# this method also opens the firewall ports required when enable = true
programs.kdeconnect = mkIf
cfg.gsconnect
{
enable = true;
package = pkgs.gnomeExtensions.gsconnect;
};
# GNOME connection to browsers - requires flag on browser as well
services.gnome.gnome-browser-connector.enable = lib.any
(user: user.programs.firefox.enable)
(lib.attrValues config.home-manager.users);
# And dconf
programs.dconf.enable = true;
# Exclude default GNOME packages that dont interest me.
environment.gnome.excludePackages =
(with pkgs; [
gnome-photos
gnome-tour
gedit # text editor
])
++ (with pkgs.gnome; [
cheese # webcam tool
gnome-music
gnome-terminal
epiphany # web browser
geary # email reader
evince # document viewer
gnome-characters
totem # video player
tali # poker game
iagno # go game
hitori # sudoku game
atomix # puzzle game
]);
};
}

View file

@ -0,0 +1,62 @@
{ lib, config, ... }:
with lib;
{
imports = [
./system
./programs
./services
./de
./hardware
./containers
./lib.nix
./security
];
options.mySystem.persistentFolder = mkOption {
type = types.str;
description = "persistent folder for nixos mutable files";
default = "/persist";
};
options.mySystem.nasFolder = mkOption {
type = types.str;
description = "folder where nas mounts reside";
default = "/mnt/nas";
};
options.mySystem.nasAddress = mkOption {
type = types.str;
description = "NAS Address or name for the backup nas";
default = "10.1.1.13";
};
options.mySystem.domain = mkOption {
type = types.str;
description = "domain for hosted services";
default = "";
};
options.mySystem.internalDomain = mkOption {
type = types.str;
description = "domain for local devices";
default = "";
};
options.mySystem.purpose = mkOption {
type = types.str;
description = "System purpose";
default = "Production";
};
options.mySystem.monitoring.prometheus.scrapeConfigs = mkOption {
type = lib.types.listOf lib.types.attrs;
description = "Prometheus scrape targets";
default = [ ];
};
config = {
systemd.tmpfiles.rules = [
"d ${config.mySystem.persistentFolder} 777 - - -" #The - disables automatic cleanup, so the file wont be removed after a period
];
};
}

View file

@ -0,0 +1,5 @@
{
imports = [
./nvidia
];
}

View file

@ -0,0 +1,79 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.mySystem.hardware.nvidia;
in
{
options.mySystem.hardware.nvidia.enable = mkEnableOption "NVIDIA config";
config = mkIf cfg.enable {
# ref: https://nixos.wiki/wiki/Nvidia
# Enable OpenGL
hardware.opengl = {
enable = true;
driSupport = true;
driSupport32Bit = true;
};
hardware.opengl.extraPackages = with pkgs; [
vaapiVdpau
];
# This is for the benefit of VSCODE running natively in wayland
environment.sessionVariables.NIXOS_OZONE_WL = "1";
hardware.nvidia = {
# Modesetting is required.
modesetting.enable = true;
# Nvidia power management. Experimental, and can cause sleep/suspend to fail.
# Enable this if you have graphical corruption issues or application crashes after waking
# up from sleep. This fixes it by saving the entire VRAM memory to /tmp/ instead
# of just the bare essentials.
powerManagement.enable = false;
# Fine-grained power management. Turns off GPU when not in use.
# Experimental and only works on modern Nvidia GPUs (Turing or newer).
powerManagement.finegrained = false;
# Use the NVidia open source kernel module (not to be confused with the
# independent third-party "nouveau" open source driver).
# Support is limited to the Turing and later architectures. Full list of
# supported GPUs is at:
# https://github.com/NVIDIA/open-gpu-kernel-modules#compatible-gpus
# Only available from driver 515.43.04+
# Currently alpha-quality/buggy, so false is currently the recommended setting.
open = false;
# Enable the Nvidia settings menu,
# accessible via `nvidia-settings`.
nvidiaSettings = true;
# Optionally, you may need to select the appropriate driver version for your specific GPU.
# package = config.boot.kernelPackages.nvidiaPackages.stable;
# manual build nvidia driver, works around some wezterm issues
# https://github.com/wez/wezterm/issues/2011
package =
# let
# rcu_patch = pkgs.fetchpatch {
# url = "https://github.com/gentoo/gentoo/raw/c64caf53/x11-drivers/nvidia-drivers/files/nvidia-drivers-470.223.02-gpl-pfn_valid.patch";
# hash = "sha256-eZiQQp2S/asE7MfGvfe6dA/kdCvek9SYa/FFGp24dVg=";
# };
# in
config.boot.kernelPackages.nvidiaPackages.mkDriver {
version = "550.67";
sha256_64bit = "sha256-mSAaCccc/w/QJh6w8Mva0oLrqB+cOSO1YMz1Se/32uI=";
sha256_aarch64 = "sha256-+UuK0UniAsndN15VDb/xopjkdlc6ZGk5LIm/GNs5ivA=";
openSha256 = "sha256-M/1qAQxTm61bznAtCoNQXICfThh3hLqfd0s1n1BFj2A=";
settingsSha256 = "sha256-FUEwXpeUMH1DYH77/t76wF1UslkcW721x9BHasaRUaM=";
persistencedSha256 = "sha256-ojHbmSAOYl3lOi2X6HOBlokTXhTCK6VNsH6+xfGQsyo=";
# patches = [ rcu_patch ];
};
};
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
};
}

View file

@ -0,0 +1,83 @@
{ lib, config, pkgs, ... }:
with lib;
{
# container builder
lib.mySystem.mkContainer = options: (
let
# nix doesnt have an exhausive list of options for oci
# so here i try to get a robust list of security options for containers
# because everyone needs more tinfoild hat right? RIGHT?
containerExtraOptions = lib.optionals (lib.attrsets.attrByPath [ "caps" "privileged" ] false options) [ "--privileged" ]
++ lib.optionals (lib.attrsets.attrByPath [ "caps" "readOnly" ] false options) [ "--read-only" ]
++ lib.optionals (lib.attrsets.attrByPath [ "caps" "tmpfs" ] false options) [ (map (folders: "--tmpfs=${folders}") tmpfsFolders) ]
++ lib.optionals (lib.attrsets.attrByPath [ "caps" "noNewPrivileges" ] false options) [ "--security-opt=no-new-privileges" ]
++ lib.optionals (lib.attrsets.attrByPath [ "caps" "dropAll" ] false options) [ "--cap-drop=ALL" ];
in
{
${options.app} = {
image = "${options.image}";
user = "${options.user}:${options.group}";
environment = {
TZ = config.time.timeZone;
} // lib.attrsets.attrByPath [ "env" ] { } options;
environmentFiles = lib.attrsets.attrByPath [ "envFiles" ] [ ] options;
volumes = [ "/etc/localtime:/etc/localtime:ro" ]
++ lib.attrsets.attrByPath [ "volumes" ] [ ] options;
ports = lib.attrsets.attrByPath [ "ports" ] [ ] options;
extraOptions = containerExtraOptions;
};
}
);
# build a restic restore set for both local and remote
lib.mySystem.mkRestic = options: (
let
excludePath = if builtins.hasAttr "excludePath" options then options.excludePath else [ ];
timerConfig = {
OnCalendar = "02:05";
Persistent = true;
RandomizedDelaySec = "3h";
};
pruneOpts = [
"--keep-daily 7"
"--keep-weekly 5"
"--keep-monthly 12"
];
initialize = true;
backupPrepareCommand = ''
# remove stale locks - this avoids some occasional annoyance
#
${pkgs.restic}/bin/restic unlock --remove-all || true
'';
in
{
# local backup
"${options.app}-local" = {
inherit pruneOpts timerConfig initialize backupPrepareCommand;
# Move the path to the zfs snapshot path
paths = map (x: "${config.mySystem.system.resticBackup.mountPath}/${x}") options.paths;
passwordFile = config.sops.secrets."services/restic/password".path;
exclude = excludePath;
repository = "${config.mySystem.system.resticBackup.local.location}/${options.appFolder}";
# inherit (options) user;
};
# remote backup
"${options.app}-remote" = {
inherit pruneOpts timerConfig initialize backupPrepareCommand;
# Move the path to the zfs snapshot path
paths = map (x: "${config.mySystem.system.resticBackup.mountPath}/${x}") options.paths;
environmentFile = config.sops.secrets."services/restic/env".path;
passwordFile = config.sops.secrets."services/restic/password".path;
repository = "${config.mySystem.system.resticBackup.remote.location}/${options.appFolder}";
exclude = excludePath;
# inherit (options) user;
};
}
);
}

View file

@ -0,0 +1,5 @@
{
imports = [
./shell
];
}

View file

@ -0,0 +1,5 @@
{
imports = [
./fish.nix
];
}

View file

@ -0,0 +1,28 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.shell.fish;
in
{
options.mySystem.shell.fish =
{
enable = mkEnableOption "Fish";
enablePlugins = mkOption
{
type = lib.types.bool;
description = "If we want to add fish plugins";
default = true;
};
};
# Install fish systemwide
config.programs.fish = mkIf cfg.enable {
enable = true;
vendor = {
completions.enable = true;
config.enable = true;
functions.enable = true;
};
};
}

View file

@ -0,0 +1,36 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.security.acme;
in
{
options.mySystem.security.acme.enable = mkEnableOption "acme";
config = mkIf cfg.enable {
sops.secrets = {
"security/acme/env".sopsFile = ./secrets.sops.yaml;
"security/acme/env".restartUnits = [ "lego.service" ];
};
environment.persistence."${config.mySystem.system.impermanence.persistPath}" = lib.mkIf config.mySystem.system.impermanence.enable {
directories = [ "/var/lib/acme" ];
};
security.acme = {
acceptTerms = true;
defaults.email = "admin@${config.networking.domain}";
certs.${config.networking.domain} = {
extraDomainNames = [
"${config.networking.domain}"
"*.${config.networking.domain}"
];
dnsProvider = "cloudflare";
dnsResolver = "1.1.1.1:53";
credentialsFile = config.sops.secrets."security/acme/env".path;
};
};
};
}

View file

@ -0,0 +1,50 @@
security:
acme:
env: ENC[AES256_GCM,data:/exabYG1GvSCxe+TBeECudCN6DQLVBrifa9509skrNRYBsbm1UJRK+WxO0xcrAQkcb1Lse4WE95ueQurSFaY81w=,iv:oD2zcXbO12TlOeH0xLYsaHWw4PVjMHtbVm1LAWp89oo=,tag:dwGBevRXV1B4n/5Sveq5cg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age18kj3xhlvgjeg2awwku3r8d95w360uysu0w5ejghnp4kh8qmtge5qwa2vjp
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGVEVHekhud2xUclpURFZB
bzgrSkh2TWFibndHcTBpb3AySjRnUWliYzJBCmlROE83ZmJNbWpLaTExOUNwTmZw
dElIUThUQVo3MjlobjVrdFMrZUJHM1EKLS0tIGVlcU16Vm12eERKN1o1Ym1yRnJB
UkR2YUlLSnBWT0QrWTJuS0hRMnhBM2MKBaaD1gtd52RwKKPf7Yi7fhVEot6kIhAg
oaS589WtWIiIiEs/Fde8QfOEJ4aydhuyfVFGxsJkb8a5QGkjKP4Faw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z3vjvkead2h934n3w4m5m7tg4tj5qlzagsq6ly84h3tcu7x4ldsqd3s5fg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBiSEwyT21iZHl2cEtKZTRG
RGwwclFtTnNkK2c0U2U0U2NTWTkrckllbHhrCkprUTRxcFJnT3hTS3VuUjZXQUti
MDNWZGMrSmFDL2svaTJDZlEwcFpEeEUKLS0tIEVBMDlxZmVEU0UreS9KZnNodWcy
bUF2WVRySUd6NDJpbFRiYWszVlFYeGMK7Cl76mjrhdMNKy3SXQ4KqpUJl/P9peJc
O+EZ0Q1m0tZrShg1soqdMb1p/00ubvy+Rvxx5Tq0jsIF/Lq0Y3Q3CA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1a8z3p24v32l9yxm5z2l8h7rpc3nhacyfv4jvetk2lenrvsdstd3sdu2kaf
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB5U0cyRlFDaCsvSEZyeE1q
WXpUdU5teVpGT1J6dkJ3VHJudG5UaEIvMlYwCm8xcldxOTFEV3JJbUhYbHNjZjgw
T2tDZERTNnZtdVEvNWo5STZTdzZYbnMKLS0tIHpXQUREODAzOWIzcGVxZFJ1a2Rh
S2U3T2NUdkVKL3VhWGtRTXUrclJ3TnMKE+6waO9P3EWCGeBRmh3OH689ttLU0F0G
hEeBbwYSXfrXtHGSEwwNo++5vP1UVA7KUvLTF2G3mM2nhztJwacWtg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1d9p83j52m2xg0vh9k7q0uwlxwhs3y6tlv68yg9s2h9mdw2fmmsqshddz5m
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsSEREWEtSWTFUN1l3Uktp
YlJCNUZEMnBJL0xQc21VenBkVTgrcVBDQlJFCm51QkhVK2J4ODNQN3AyZ2V4dnQw
SVI2blo4SzdSYVdpeDluSFJ1YjFscXMKLS0tIEFMQXAyMGc2MHdMYjkxeHZOWHJ3
TnJ6dFBkUGpXMTBjRU5MMVJGTWZmd2sKh83VVst9g3e9lbx2v3b46X23HHtQAeS/
//oLEkBDuk5yqnNFtbcSGsWI9DBh9mD87bupw0Hz7GXTC/5LkApTkQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-05-18T16:39:41Z"
mac: ENC[AES256_GCM,data:tiHQmGUiTuM6aH5Vf9abJUIPDlQ73vmXHCNvkoBHanX7k9ScDxpByqgnDaIR2Tue4SusVVgXFpAlSda9fTT/bSgLUA1+0Qxo4YDLRz5I0pp4sCXAGVELHguADKwAbv+AyT7x4idzecLZ532RGqKkLWHljfOzHtAAlA8FBHH3ylk=,iv:iHlFeshdBreirhJGCBYUucUkz0oEbxYLo6dIeab73e0=,tag:ZeUXSWla9hDldeoqFIw+/g==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1

View file

@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./acme
];
}

View file

@ -0,0 +1,22 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.mySystem.services.cockpit;
in
{
options.mySystem.services.cockpit.enable = mkEnableOption "Cockpit";
config.services.cockpit = mkIf cfg.enable {
enable = true;
openFirewall = true;
package = pkgs.cockpit.overrideAttrs (old: {
# remove packagekit and selinux, don't work on NixOS
postBuild = ''
${old.postBuild}
rm -rf \
dist/packagekit \
dist/selinux
'';
});
};
}

View file

@ -0,0 +1,12 @@
{
imports = [
./cockpit
./forgejo
./nginx
./podman
./postgresql
./radicale
./reboot-required-check.nix
./restic
];
}

View file

@ -0,0 +1,92 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.services.forgejo;
http_port = 3000;
serviceUser = "forgejo";
domain = "git.hsn.dev";
in
{
options.mySystem.services.forgejo = {
enable = mkEnableOption "Forgejo";
};
config = mkIf cfg.enable {
services.nginx = {
virtualHosts.${domain} = {
forceSSL = true;
useACMEHost = config.networking.domain;
extraConfig = ''
client_max_body_size 512M;
'';
locations."/".proxyPass = "http://127.0.0.1:${toString http_port}";
};
};
services.forgejo = {
enable = true;
# enable sql db dumps daily
dump.enable = true;
database.type = "postgres";
# Enable support for Git Large File Storage
lfs.enable = true;
settings = {
server = {
DOMAIN = domain;
# You need to specify this to remove the port from URLs in the web UI.
ROOT_URL = "https://${domain}/";
HTTP_PORT = http_port;
# Default landing page on 'explore'
LANDING_PAGE = "explore";
};
# You can temporarily allow registration to create an admin user.
service = {
DISABLE_REGISTRATION = true;
ENABLE_NOTIFY_MAIL = true;
REGISTER_EMAIL_CONFIRM = true;
REQUIRE_SIGNIN_VIEW = false;
};
indexer = {
REPO_INDEXER_ENABLED = true;
REPO_INDEXER_PATH = "indexers/repos.bleve";
MAX_FILE_SIZE = 1048576;
REPO_INDEXER_INCLUDE = "";
REPO_INDEXER_EXCLUDE = "resources/bin/**";
};
picture = {
AVATAR_UPLOAD_PATH = "/var/lib/forgejo/data/avatars";
REPOSITORY_AVATAR_UPLOAD_PATH = "/var/lib/forgejo/data/repo-avatars";
};
# Add support for actions, based on act: https://github.com/nektos/act
actions = {
ENABLED = true;
};
# Sending emails is completely optional
# You can send a test email from the web UI at:
# Profile Picture > Site Administration > Configuration > Mailer Configuration
mailer = {
ENABLED = true;
SMTP_ADDR = "smtp.mailgun.org";
FROM = "git@hsn.dev";
USER = "git@mg.hsn.dev";
SMTP_PORT = 587;
};
session = {
COOKIE_SECURE = true;
COOKIE_NAME = "session";
};
};
mailerPasswordFile = config.sops.secrets."services/forgejo/smtp/password".path;
};
# sops
sops.secrets."services/forgejo/smtp/password" = {
sopsFile = ./secrets.sops.yaml;
owner = serviceUser;
mode = "400";
restartUnits = [ "forgejo.service" ];
};
environment.persistence."${config.mySystem.system.impermanence.persistPath}" = lib.mkIf config.mySystem.system.impermanence.enable {
directories = [ "/var/lib/forgejo" ];
};
};
}

View file

@ -0,0 +1,51 @@
services:
forgejo:
smtp:
password: ENC[AES256_GCM,data:p5iDVW+V0aIlj9RTHpMgko/1ahVkiHXafgm2u2g3TTrYFORBqCJqv+lCSSm4mGIabn4=,iv:GIx/dnn1y6Is6uzKEkmo326AUSS+I5RnLsV2duKHPBo=,tag:7R4222rGEsK+egunnB+llg==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age18kj3xhlvgjeg2awwku3r8d95w360uysu0w5ejghnp4kh8qmtge5qwa2vjp
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBVOU9SMEZvQUxhZWU2dzJ0
Z1ZUODd0R2Rtb2lmaHhweC9Tb0J2anJuRno4Cis5UGZHc3pobkQ2OUJwSy9tV1RF
SmNvUi9Xek9oVFdPdzZQUnlJeWtBSEEKLS0tIFpoWTZOYlFOK2p4bTFaUVhGV1hz
dlpWOWhLT2VtYXdDQjJlWnh4T05TT3MKJtewm9QN1EfT8IQkkHdH0GGNDyg1wH6Z
Ja6lcZB580FPEHwoMC7cayQe5v82vbQsj6s8mOaUW9yRQLMQMkmOfA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z3vjvkead2h934n3w4m5m7tg4tj5qlzagsq6ly84h3tcu7x4ldsqd3s5fg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6bUNpMWJ1YklkV1JPMnFi
S0tiajlKblV5aTU2djJaQjN0M1IwOU9pdTNRCnFycmQ5WHFadmxhZ1R6a1ZHYjlW
TjVCN2dUelc5Qkt5Y0ZtYUtNRHN0WTAKLS0tIDFKSUE2YzFmQzB1TjgrRDFEalJO
MDg0bU9NS3VObVVYNDAvY1JIRU5xZEEKSWzp9smLNYypodRIh49XPBxS536XtFWf
kJWaLiUfULifcN46F8Tyts9dGHXx/bOgM5rCFAqtqTL7EaJbam13pw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1a8z3p24v32l9yxm5z2l8h7rpc3nhacyfv4jvetk2lenrvsdstd3sdu2kaf
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBuQ09PSWwyY2tGZzdyOGxq
KzZuUW95Z2QzVXY4dk5tRGZ3MXMxNW9INXlRCmkra2t2UUMwWnBhM0VoTnpUaWIx
UW13a3VxZWJyL0ZDdk1IVXJNQitpblkKLS0tIGMwZHJqOGt4Y2FCUUUrc2tEQjY0
eWp3WmdOMUlTNVlUT3FMM0JCTnViQ28KE6rpeFodW4BAIDVdjpgPpRs26XgUya3b
Xbg6IuB90DvzNMKkoGB36Dh5cWUGFBsJ3QYopSDQGFbUepyDQa4Wvw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1d9p83j52m2xg0vh9k7q0uwlxwhs3y6tlv68yg9s2h9mdw2fmmsqshddz5m
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB6VG9uejd2YTZ4c1p3akFZ
dUtEbXk3S1UzeEhLeld4VXVLK1dVKytlOEc4ClFwbVVUUG9SbzB2bDZwRHA2cE4y
STFLOWR3S0YrQ1FBbjJDV3o1aW1rNjgKLS0tIDlnQkZhRlpXanY5TzViVzdTNGpo
NDhZZ24zWmwwNVE4VDM0bllyUkdxVncKdyYx4r4ERRy2lz7b2oK4JvzF1RnIJ/mV
neTz0N1LSdmTUAOUtRT7D73tR6HhBZQNxH4LIsYiRllto46mEamx7g==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-05-18T16:39:41Z"
mac: ENC[AES256_GCM,data:TQ019mze1TDfHM3unUvhLzQOdPVhZ0lIw+CbcuSGIqpjITr7nGVRYtPMvHtY9taxaAQgpdJghOaP9MS/YR6KT9II54qYtel944rmOHxOIpB8Oy/mRHBJvx1JhQxHzxPx6hLsLxNagSbgIpyiNGwMiR2gedBLPI/BBNcPGESiMTA=,iv:m4UnG4p0eWzRmspSU/RL4HSeq3Fr5t+9FnNMxgCMOJE=,tag:w2aJxYjVoEHlaQAT+VAa7g==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1

View file

@ -0,0 +1,62 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.services.nginx;
in
{
options.mySystem.services.nginx.enable = mkEnableOption "nginx";
config = mkIf cfg.enable {
services.nginx = {
enable = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedProxySettings = true;
recommendedTlsSettings = true;
recommendedBrotliSettings = true;
proxyResolveWhileRunning = true; # needed to ensure nginx loads even if it cant resolve vhosts
statusPage = true;
enableReload = true;
# Only allow PFS-enabled ciphers with AES256
sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
appendHttpConfig = ''
# Minimize information leaked to other domains
add_header 'Referrer-Policy' 'origin-when-cross-origin';
# Disable embedding as a frame
add_header X-Frame-Options DENY;
# Prevent injection of code in other mime types (XSS Attacks)
add_header X-Content-Type-Options nosniff;
'';
commonHttpConfig = ''
add_header X-Clacks-Overhead "GNU Terry Pratchett";
'';
# provide default host with returning error
# else nginx returns the first server
# in the config file... >:S
virtualHosts = {
"_" = {
default = true;
rejectSSL = true;
extraConfig = "return 444;";
};
};
};
networking.firewall = {
allowedTCPPorts = [ 80 443 ];
allowedUDPPorts = [ 80 443 ];
};
# required for using acme certs
users.users.nginx.extraGroups = [ "acme" ];
};
}

View file

@ -0,0 +1,51 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.mySystem.services.podman;
in
{
options.mySystem.services.podman.enable = mkEnableOption "Podman";
config = mkIf cfg.enable
{
virtualisation.podman = {
enable = true;
dockerCompat = true;
extraPackages = [ pkgs.zfs ];
# regular cleanup
autoPrune.enable = true;
autoPrune.dates = "weekly";
# and add dns
defaultNetwork.settings = {
dns_enabled = true;
};
};
virtualisation.oci-containers = {
backend = "podman";
};
environment.systemPackages = with pkgs; [
podman-tui # status of containers in the terminal
lazydocker
];
programs.fish.shellAliases = {
# lazydocker --> lazypodman
lazypodman="sudo DOCKER_HOST=unix:///run/podman/podman.sock lazydocker";
};
networking.firewall.interfaces.podman0.allowedUDPPorts = [ 53 ];
# extra user for containers
users.users.kah = {
uid = 568;
group = "kah";
};
users.groups.kah = { };
users.users.jahanson.extraGroups = [ "kah" ];
};
}

View file

@ -0,0 +1,71 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.${category}.${app};
app = "postgresql";
category = "services";
in
{
options.mySystem.${category}.${app} =
{
enable = mkEnableOption "${app}";
addToHomepage = mkEnableOption "Add ${app} to homepage" // { default = true; };
prometheus = mkOption
{
type = lib.types.bool;
description = "Enable prometheus scraping";
default = true;
};
backupLocation = mkOption
{
type = lib.types.string;
description = "Location for sql backups to be stored.";
default = "/persist/backup/postgresql";
};
backup = mkOption
{
type = lib.types.bool;
description = "Enable backups";
default = true;
};
};
config = mkIf cfg.enable {
services.postgresql = {
enable = true;
identMap = ''
# ArbitraryMapName systemUser DBUser
superuser_map root postgres
superuser_map postgres postgres
# Let other names login as themselves
superuser_map /^(.*)$ \1
'';
authentication = ''
#type database DBuser auth-method optional_ident_map
local sameuser all peer map=superuser_map
'';
settings = {
max_connections = 200;
random_page_cost = 1.1;
};
};
# enable backups
services.postgresqlBackup = mkIf cfg.backup {
enable = lib.mkForce true;
location = cfg.backupLocation;
};
### firewall config
# networking.firewall = mkIf cfg.openFirewall {
# allowedTCPPorts = [ port ];
# allowedUDPPorts = [ port ];
# };
};
}

View file

@ -0,0 +1,110 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.${category}.${app};
app = "radicale";
category = "services";
user = app; #string
group = app; #string
port = 5232; #int
appFolder = "/var/lib/${app}";
url = "${app}.jahanson.tech";
in
{
options.mySystem.${category}.${app} =
{
enable = mkEnableOption "${app}";
addToHomepage = mkEnableOption "Add ${app} to homepage" // { default = true; };
monitor = mkOption
{
type = lib.types.bool;
description = "Enable gatus monitoring";
default = true;
};
prometheus = mkOption
{
type = lib.types.bool;
description = "Enable prometheus scraping";
default = true;
};
backups = mkOption
{
type = lib.types.bool;
description = "Enable local backups";
default = true;
};
};
config = mkIf cfg.enable {
## Secrets
sops.secrets."${category}/${app}/htpasswd" = {
sopsFile = ./secrets.sops.yaml;
owner = user;
inherit group;
restartUnits = [ "${app}.service" ];
};
users.users.jahanson.extraGroups = [ group ];
environment.persistence."${config.mySystem.system.impermanence.persistPath}" = lib.mkIf config.mySystem.system.impermanence.enable {
hideMounts = true;
directories = [ "/var/lib/radicale/" ];
};
## service
services.radicale = {
enable = true;
settings = {
server.hosts = [ "0.0.0.0:${builtins.toString port}" ];
auth = {
type = "htpasswd";
htpasswd_filename = config.sops.secrets."${category}/${app}/htpasswd".path;
htpasswd_encryption = "plain";
realm = "Radicale - Password Required";
};
storage.filesystem_folder = "/var/lib/radicale/collections";
};
};
### gatus integration
mySystem.services.gatus.monitors = mkIf cfg.monitor [
{
name = app;
group = "${category}";
url = "https://${url}";
interval = "1m";
conditions = [ "[CONNECTED] == true" "[STATUS] == 200" "[RESPONSE_TIME] < 50" ];
}
];
### Ingress
services.nginx.virtualHosts.${host} = {
useACMEHost = config.networking.domain;
forceSSL = true;
locations."/" = {
proxyPass = "http://127.0.0.1:${builtins.toString port}";
};
};
### firewall config
# networking.firewall = mkIf cfg.openFirewall {
# allowedTCPPorts = [ port ];
# allowedUDPPorts = [ port ];
# };
### backups
warnings = [
(mkIf (!cfg.backups && config.mySystem.purpose != "Development")
"WARNING: Backups for ${app} are disabled!")
];
services.restic.backups = mkIf cfg.backups (config.lib.mySystem.mkRestic
{
inherit app user;
paths = [ appFolder ];
inherit appFolder;
});
};
}

View file

@ -0,0 +1,50 @@
services:
radicale:
htpasswd: ENC[AES256_GCM,data:4y6QUrivlOpUVNAGDuPb9w==,iv:AS13cN3EKDEfRCc6USejkYG4SEFS4sPoYrfok8jsfYg=,tag:60ZhYvH/+SQhZKR6M9Zlfw==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age18kj3xhlvgjeg2awwku3r8d95w360uysu0w5ejghnp4kh8qmtge5qwa2vjp
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLc011SkxreTJSZERMemlS
clM0NnhPekF3bFZiUXUreDZEN3dkN3NmN0RNCmM3UEdlV3Azbk4yL2dpS3ZRQVpv
OWRQZ2E0eGU5cVIxRUU1c3RxMnpucEUKLS0tIHhjVXhtZEdVb3lvRnpoVGdJbkNq
WEM1bGRUOFVrOU5jbmhxSmpRK0F4WWsKoV2ABFGgcLb0qMmorOzNaAzPf6AbMfr0
PUdIkaXLJrF3Qi3bts96KhlPLUA7llArSMrUBtkXeCit6xS+87Imuw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z3vjvkead2h934n3w4m5m7tg4tj5qlzagsq6ly84h3tcu7x4ldsqd3s5fg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBka1htN2tsN2ZZMkpDdHph
dk5EY1JkM0hONlRiaXFhdGVIT0xuVDA0b2tBCnlTVExGOHg5RFdiRzU0RWd3dEg3
aXB2UkhQRUF6QnBRN0FnNjRqMWIwRkUKLS0tIHJxOWNmR09PRVFQK1E3Um1laHJj
VmdPdHl0OGtGODBIZHVzelByaVVoRTAKWb7/Th4zdNFo+K+rwi+nHpbftxnnMOlt
BV0hsMpFBaQZXw01n9uw71MZiJZAbZOMA9P+toMKL9UwWSx+isa3gA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1a8z3p24v32l9yxm5z2l8h7rpc3nhacyfv4jvetk2lenrvsdstd3sdu2kaf
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNVTdVN1VnN1JpWkJFWC95
Qmc3dUNQdUtWSjRwWlVSVlJ2aGZ1MXIrMEFJCjNtck5vcGVhNnFQNXk2MjlHSC9P
aDU3WHQwbWdtbTBMcWdCUFE4Ulg5RlkKLS0tIGFDYnpGNE1SY2cvaUk4cWtBVmN6
VGRIaEpkUk1qN1RsakV2YkdCbGJhUGcK8l4O+ysy9MeOq5uqnzt9GdkkH53BPtFf
rmONh4pxPogBB0IsvZBz2NUsKkixnucEK0+SrF2X5x0wveMl/2Nm3Q==
-----END AGE ENCRYPTED FILE-----
- recipient: age1d9p83j52m2xg0vh9k7q0uwlxwhs3y6tlv68yg9s2h9mdw2fmmsqshddz5m
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB3d2FMMStGS2dSYVcxMHNM
QXdPenBQMXJlWFJWcHp0U0h0SkNpWWlNdXgwClBTMk1TQXg4M3ZueVZha1dNN1gv
cTNuS3hBblNPQjBCa3o3N2xyNG9EZWMKLS0tIEo5SVc1b04xZndSNXQ4T3BOTmtr
M1NVZkphS2E1R2c3dWovcHlaTloxTkUKsDiyHq40XddMdcZ/2UmOUnPSu44SWTRZ
ZcDiYbfU4R7Ysgqaxl2iqBun/GOPWf4yeAbcynwXsLMUGG+XnsRWVw==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-05-18T16:39:41Z"
mac: ENC[AES256_GCM,data:9HLkT8t+LOSeVCWbjMhXMi7XduJ0nw4nZz9/dYCeyMW0w0/NscYn2avSUSks/5j14xAt4/NFmuM+KxW6z2dEdXE7DsCo1nWh+WyaTpL1QUId5xSWc6abQaexaKeRu/3QJ1wLNFgotaCuut96iFFTmvDL+t266ozxst8N3iR7zhk=,iv:NQwgSFv6Ogybrs6BMYl9eLeu0kNIJpmHR0MJCYHbCxA=,tag:X3BkmlHDt3E3GFT1iDYJHg==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1

View file

@ -0,0 +1,45 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.services.rebootRequiredCheck;
in
{
options.mySystem.services.rebootRequiredCheck.enable = mkEnableOption "Reboot required check";
config = mkIf cfg.enable {
# Enable timer
systemd.timers."reboot-required-check" = {
wantedBy = [ "timers.target" ];
timerConfig = {
# start at boot
OnBootSec = "0m";
# check every hour
OnUnitActiveSec = "1h";
Unit = "reboot-required-check.service";
};
};
# Below script will check if initrd, kernel, kernel-modules that were booted match the current system
# i.e. if a nixos-rebuild switch has upgraded anything
systemd.services."reboot-required-check" = {
script = ''
#!/usr/bin/env bash
# compare current system with booted sysetm to determine if a reboot is required
if [[ "$(readlink /run/booted-system/{initrd,kernel,kernel-modules})" == "$(readlink /run/current-system/{initrd,kernel,kernel-modules})" ]]; then
# check if the '/var/run/reboot-required' file exists and if it does, remove it
if [[ -f /var/run/reboot-required ]]; then
rm /var/run/reboot-required || { echo "Failed to remove /var/run/reboot-required"; exit 1; }
fi
else
echo "reboot required"
touch /var/run/reboot-required || { echo "Failed to create /var/run/reboot-required"; exit 1; }
fi
'';
serviceConfig = {
Type = "oneshot";
User = "root";
};
};
};
}

View file

@ -0,0 +1,102 @@
{ lib, config, pkgs, ... }:
with lib;
let
cfg = config.mySystem.system.resticBackup;
in
{
options.mySystem.system.resticBackup = {
local = {
enable = mkEnableOption "Local backups" // { default = true; };
location = mkOption
{
type = types.str;
description = "Location for local backups";
default = "";
};
};
remote = {
enable = mkEnableOption "Remote backups" // { default = true; };
location = mkOption
{
type = types.str;
description = "Location for remote backups";
default = "";
};
};
mountPath = mkOption
{
type = types.str;
description = "Location for snapshot mount";
default = "/mnt/nightly_backup";
};
};
config = {
# Warn if backups are disable and machine isnt a dev box
warnings = [
(mkIf (!cfg.local.enable && config.mySystem.purpose != "Development") "WARNING: Local backups are disabled!")
(mkIf (!cfg.remote.enable && config.mySystem.purpose != "Development") "WARNING: Remote backups are disabled!")
];
sops.secrets = mkIf (cfg.local.enable || cfg.remote.enable) {
"services/restic/password" = {
sopsFile = ./secrets.sops.yaml;
owner = "kah";
group = "kah";
};
"services/restic/env" = {
sopsFile = ./secrets.sops.yaml;
owner = "kah";
group = "kah";
};
};
environment.persistence = mkIf (cfg.local.enable || cfg.remote.enable) {
"${config.mySystem.system.impermanence.persistPath}" = {
directories = [ "/var/lib/containers" ];
};
};
# useful commands:
# view snapshots - zfs list -t snapshot
# below takes a snapshot of the zfs persist volume
# ready for restic syncs
# essentially its a nightly rotation of atomic state at 2am.
# this is the safest option, as if you run restic
# on live services/databases/etc, you will have
# a bad day when you try and restore
# (backing up a in-use file can and will cause corruption)
# ref: https://cyounkins.medium.com/correct-backups-require-filesystem-snapshots-23062e2e7a15
systemd = mkIf (cfg.local.enable || cfg.remote.enable) {
timers.restic_nightly_snapshot = {
description = "Nightly ZFS snapshot timer";
wantedBy = [ "timers.target" ];
partOf = [ "restic_nightly_snapshot.service" ];
timerConfig.OnCalendar = "2:00";
timerConfig.Persistent = "true";
};
# recreate snapshot and mount, ready for backup
# I used mkdir -p over a nix tmpfile, as mkdir -p exits cleanly
# if the folder already exists, and tmpfiles complain
# if the folder exists and is already mounted.
services.restic_nightly_snapshot = {
description = "Nightly ZFS snapshot for Restic";
path = with pkgs; [ zfs busybox ];
serviceConfig.Type = "simple";
script = ''
mkdir -p /mnt/nightly_backup/ && \
umount ${cfg.mountPath} || true && \
zfs destroy rpool/safe/persist@restic_nightly_snap || true && \
zfs snapshot rpool/safe/persist@restic_nightly_snap && \
mount -t zfs rpool/safe/persist@restic_nightly_snap ${cfg.mountPath}
'';
};
};
};
}

View file

@ -0,0 +1,52 @@
services:
restic:
password: ENC[AES256_GCM,data:23E=,iv:qK5OxK70KSC6nxsiovOWLDG1+6Mdlf+DNibc/mm3uGA=,tag:GV3V+T/Y8dO/L7pv+OkHzg==,type:str]
repository: ENC[AES256_GCM,data:Sbao4Q==,iv:M9asdh36DY4Ys3RHugTeduECunSkrcL9yjE0j+CaCTk=,tag:Nmy8LNJbwr7cOTQd1p/kqQ==,type:str]
env: ENC[AES256_GCM,data:wNFPsVW6DwZF6Qyyztt0Jw==,iv:PrP6gq+IMTUSKWIKt8zV8o1wbR51vZuZ95Yjwt07ypU=,tag:bBO0tYVR4qrSTZxMoQKgZA==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age18kj3xhlvgjeg2awwku3r8d95w360uysu0w5ejghnp4kh8qmtge5qwa2vjp
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBaMzZ0Y0FRaUdzY0tMeEx5
VUJxZkFRUlY3clI1ejVCT0xPNnN3NTh1OHhZCnh2YkxkemZ0ekwxT1NQKzIvTGlH
ejZISVJqVmM2UE1iM0M1eGpDSTN6VGcKLS0tIHJxSEhobUxoYWNXeE1LbWV6N0t1
dGluUjZCbjFMMktnMFhHSVRscUtUNFEKFAEjmLUA0/9iZLQ4EhpPgyYYYkzQCpUL
bo83xqeLmxY7jU8HGF2YNyBe+I9LHpZvxDTiXzBDiK0Bry9b57hWgg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z3vjvkead2h934n3w4m5m7tg4tj5qlzagsq6ly84h3tcu7x4ldsqd3s5fg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBLS1diU0xaU0M0amJEajM3
YkpDeUh2VlNGa3liTFZMOGZYUDRoRUNGeldzCk5kSTcrOXNsclJTUnRoMEZqT0s5
WTVKcFh4WVlSY0JlYUp0QmF4dWhCWlEKLS0tIGhoeERZWmZCcHR5bEtRZW1JczFy
bEl2Yy9mb1NnbUx6WFFHUGhjUXNqV2cK4M0cAXzjNzG09g2FHCZUw8NQkw6Qw3o9
G3uFAkWw1Dny8PewS4tzOErAZlUQ0iKJDgUVqOrrBrfvctTFtBseAw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1a8z3p24v32l9yxm5z2l8h7rpc3nhacyfv4jvetk2lenrvsdstd3sdu2kaf
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3WE9jVFRMRC9iNFVaTnNw
OEovY3hwYW5kWnlrSTRIbFU5enFJVXlFVTJjCkdueno5bXZFS0VPU2RWdk5PUTRj
Tk53UGJVZXZ3MExER2txVlRsVDIvY1kKLS0tIFI3VlNyYzB1S1RLS2JEbW54MTdW
aHpBN3ZqWENXc1cvZE5Xa2FiaHNlU0UKQ5/xk21o60JLZ6xNcB0cFHkoTXMN99LD
okhdVhlpml06ODoMIsFq/PR3adjeWJRjB7C4/Bz/wzlEUmze3TQMew==
-----END AGE ENCRYPTED FILE-----
- recipient: age1d9p83j52m2xg0vh9k7q0uwlxwhs3y6tlv68yg9s2h9mdw2fmmsqshddz5m
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA3L0h2T3crY2JpaGMreS8z
YkJLOTMvbTRKUFNlWXgzZkpPUVJ5eDFON3pFCnBEeDg1YnpxNXZvdWtPbndRT1VZ
ZUhPRFE3TGFYaGluSDRjbkFHdi81NVEKLS0tIFJYZ3NaSVd6a25qMmxZV1VUNk50
Wmo2cHY4cUZFMkFlUmNKcmg2RjcrUncKmbMWBL8r6R777c3SvFmmRpRY2oRQsf+A
Yqlv0wmrD6A1B61SDRDwkmFggnk0PquznZD/y0JctqRqmcstAscyNQ==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-05-18T16:39:41Z"
mac: ENC[AES256_GCM,data:sMTQ25VnUOrMSN9Ro7GoovFWh9Y/4RmHXm/mRHNuOGpl2utXGGQhAGM0F2lpdQwJfd/cRneiDfEJ1GSV+yaSPfTm2KFVVM+Kzl7AyHQO6W1dEvaMhJgGvxixc0SkF9zuU0WXnNOxW67fKUuuZZ69TwMdcVmwF1u7pV9tsONtgf8=,iv:Heynu00CnyU17zNkv1riz2hpulbXOeoi7ukj7c3Qk1g=,tag:Jqkaqq3XSXvENl/8a1z2QA==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1

View file

@ -0,0 +1,14 @@
{
imports = [
./impermanence.nix
./motd
./nix.nix
./nfs
./openssh.nix
./pushover
./security.nix
./systempackages.nix
./time.nix
./zfs.nix
];
}

View file

@ -0,0 +1,55 @@
{ lib, config, ... }:
let
cfg = config.mySystem.system.impermanence;
in
with lib;
{
options.mySystem.system.impermanence = {
enable = mkEnableOption "system impermanence";
rootBlankSnapshotName = lib.mkOption {
type = lib.types.str;
default = "blank";
};
rootPoolName = lib.mkOption {
type = lib.types.str;
default = "rpool/local/root";
};
persistPath = lib.mkOption {
type = lib.types.str;
default = "/persist";
};
};
config = lib.mkIf cfg.enable {
# move ssh keys
# bind a initrd command to rollback to blank root after boot
boot.initrd.postDeviceCommands = lib.mkAfter ''
zfs rollback -r ${cfg.rootPoolName}@${cfg.rootBlankSnapshotName}
'';
systemd.tmpfiles.rules = mkIf config.services.openssh.enable [
"d /etc/ 0755 root root -" #The - disables automatic cleanup, so the file wont be removed after a period
"d /etc/ssh/ 0755 root root -" #The - disables automatic cleanup, so the file wont be removed after a period
];
environment.persistence."${cfg.persistPath}" = {
hideMounts = true;
directories =
[
"/var/log" # persist logs between reboots for debugging
"/var/lib/cache" # cache files (restic, nginx, contaienrs)
"/var/lib/nixos" # nixos state
];
files = [
"/etc/machine-id"
"/etc/adjtime" # hardware clock adjustment
# ssh keys
"/etc/ssh/ssh_host_ed25519_key"
"/etc/ssh/ssh_host_ed25519_key.pub"
"/etc/ssh/ssh_host_rsa_key"
"/etc/ssh/ssh_host_rsa_key.pub"
];
};
};
}

View file

@ -0,0 +1,98 @@
{ config, lib, pkgs, ... }:
let
motd = pkgs.writeShellScriptBin "motd"
''
#! /usr/bin/env bash
source /etc/os-release
service_status=$(systemctl list-units | grep podman-)
RED="\e[31m"
GREEN="\e[32m"
BOLD="\e[1m"
ENDCOLOR="\e[0m"
LOAD1=`cat /proc/loadavg | awk {'print $1'}`
LOAD5=`cat /proc/loadavg | awk {'print $2'}`
LOAD15=`cat /proc/loadavg | awk {'print $3'}`
MEMORY=`free -m | awk 'NR==2{printf "%s/%sMB (%.2f%%)\n", $3,$2,$3*100 / $2 }'`
# time of day
HOUR=$(date +"%H")
if [ $HOUR -lt 12 -a $HOUR -ge 0 ]
then TIME="morning"
elif [ $HOUR -lt 17 -a $HOUR -ge 12 ]
then TIME="afternoon"
else
TIME="evening"
fi
uptime=`cat /proc/uptime | cut -f1 -d.`
upDays=$((uptime/60/60/24))
upHours=$((uptime/60/60%24))
upMins=$((uptime/60%60))
upSecs=$((uptime%60))
figlet "$(hostname)" | lolcat -f
printf "$BOLD %-20s$ENDCOLOR %s\n" "Role:" "${config.mySystem.purpose}"
printf "\n"
${lib.strings.concatStrings (lib.lists.forEach cfg.networkInterfaces (x: "printf \"$BOLD * %-20s$ENDCOLOR %s\\n\" \"IPv4 ${x}\" \"$(ip -4 addr show ${x} | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}')\"\n"))}
printf "$BOLD * %-20s$ENDCOLOR %s\n" "Release" "$PRETTY_NAME"
printf "$BOLD * %-20s$ENDCOLOR %s\n" "Kernel" "$(uname -rs)"
[ -f /var/run/reboot-required ] && printf "$RED * %-20s$ENDCOLOR %s\n" "A reboot is required"
printf "\n"
printf "$BOLD * %-20s$ENDCOLOR %s\n" "CPU usage" "$LOAD1, $LOAD5, $LOAD15 (1, 5, 15 min)"
printf "$BOLD * %-20s$ENDCOLOR %s\n" "Memory" "$MEMORY"
printf "$BOLD * %-20s$ENDCOLOR %s\n" "System uptime" "$upDays days $upHours hours $upMins minutes $upSecs seconds"
printf "\n"
if ! type "$zpool" &> /dev/null; then
printf "$BOLD Zpool status: $ENDCOLOR\n"
zpool status -x | sed -e 's/^/ /'
fi
if ! type "$zpool" &> /dev/null; then
printf "$BOLD Zpool usage: $ENDCOLOR\n"
zpool list -Ho name,cap,size | awk '{ printf("%-10s%+3s used out of %+5s\n", $1, $2, $3); }' | sed -e 's/^/ /'
fi
printf "\n"
if [[ -n "$service_status" ]]; then
printf "$BOLDService status$ENDCOLOR\n"
while IFS= read -r line; do
if [[ $line =~ ".scope" ]]; then
continue
fi
if echo "$line" | grep -q 'failed'; then
service_name=$(echo $line | awk '{print $2;}' | sed 's/podman-//g')
printf "$RED $ENDCOLOR%-50s $RED[failed]$ENDCOLOR\n" "$service_name"
elif echo "$line" | grep -q 'running'; then
service_name=$(echo $line | awk '{print $1;}' | sed 's/podman-//g')
printf "$GREEN $ENDCOLOR%-50s $GREEN[active]$ENDCOLOR\n" "$service_name"
else
echo "service status unknown"
fi
done <<< "$service_status"
fi
'';
cfg = config.mySystem.system.motd;
in
{
options.mySystem.system.motd = {
enable = lib.mkEnableOption "MOTD";
networkInterfaces = lib.mkOption {
description = "Network interfaces to monitor";
type = lib.types.listOf lib.types.str;
# default = lib.mapAttrsToList (_: val: val.interface)
default = [ ];
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [
motd
pkgs.lolcat
pkgs.figlet
];
programs.fish.interactiveShellInit = lib.mkIf config.programs.fish.enable ''
motd
'';
};
}

View file

@ -0,0 +1,24 @@
{ lib
, config
, ...
}:
let
cfg = config.mySystem.services.nfs;
in
{
options.mySystem.services.nfs = {
enable = lib.mkEnableOption "nfs";
exports = lib.mkOption {
type = lib.types.str;
default = "";
};
};
config = lib.mkIf cfg.enable {
services.nfs.server.enable = true;
services.nfs.server.exports = cfg.exports;
networking.firewall.allowedTCPPorts = [
2049
];
};
}

View file

@ -0,0 +1,38 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.nix;
in
{
options.mySystem.nix = {
autoOptimiseStore = mkOption
{
type = lib.types.bool;
description = "If we want to auto optimise store";
default = true;
};
gc = {
enable = mkEnableOption "automatic garbage collection" // {
default = true;
};
persistent = mkOption
{
type = lib.types.bool;
description = "Persistent timer for gc, runs at startup if timer missed";
default = true;
};
};
};
config.nix = {
optimise.automatic = cfg.autoOptimiseStore;
# automatically garbage collect nix store
gc = mkIf cfg.gc.enable {
# garbage collection
automatic = cfg.gc.enable;
dates = "daily";
options = "--delete-older-than 7d";
inherit (cfg.gc) persistent;
};
};
}

View file

@ -0,0 +1,38 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.services.openssh;
in
{
options.mySystem.services.openssh = {
enable = mkEnableOption "openssh" // { default = true; };
passwordAuthentication = mkOption
{
type = lib.types.bool;
description = "If password can be accepted for ssh (commonly disable for security hardening)";
default = false;
};
permitRootLogin = mkOption
{
type = types.enum [ "yes" "without-password" "prohibit-password" "forced-commands-only" "no" ];
description = "If root can login via ssh (commonly disable for security hardening)";
default = "prohibit-password";
};
};
config = mkIf cfg.enable {
services.openssh = {
enable = true;
openFirewall = true;
settings = {
# Harden
PasswordAuthentication = cfg.passwordAuthentication;
PermitRootLogin = cfg.permitRootLogin;
# Automatically remove stale sockets
StreamLocalBindUnlink = "yes";
# Allow forwarding ports to everywhere
GatewayPorts = "clientspecified";
};
};
};
}

View file

@ -0,0 +1,54 @@
{ lib
, config
, pkgs
, ...
}:
with lib;
let
cfg = config.mySystem.system.systemd.pushover-alerts;
in
{
options.mySystem.system.systemd.pushover-alerts.enable = mkEnableOption "Pushover alerts for systemd failures" // { default = true; };
options.systemd.services = mkOption {
type = with types; attrsOf (
submodule {
config.onFailure = [ "notify-pushover@%n.service" ];
}
);
};
config = {
# Warn if backups are disable and machine isnt a dev box
warnings = [
(mkIf (!cfg.enable && config.mySystem.purpose != "Development") "WARNING: Pushover SystemD notifications are disabled!")
];
systemd.services."notify-pushover@" = mkIf cfg.enable {
enable = true;
onFailure = lib.mkForce [ ]; # cant refer to itself on failure
description = "Notify on failed unit %i";
serviceConfig = {
Type = "oneshot";
EnvironmentFile = config.sops.secrets."services/pushover/env".path;
};
# Script calls pushover with some deets.
# Here im using the systemd specifier %i passed into the script,
# which I can reference with bash $1.
scriptArgs = "%i %H";
script = ''
${pkgs.curl}/bin/curl --fail -s -o /dev/null \
--form-string "token=$PUSHOVER_API_KEY" \
--form-string "user=$PUSHOVER_USER_KEY" \
--form-string "priority=1" \
--form-string "html=1" \
--form-string "timestamp=$(date +%s)" \
--form-string "url=https://$2:9090/system/services#/$1" \
--form-string "url_title=View in Cockpit" \
--form-string "title=Unit failure: '$1' on $2" \
--form-string "message=<b>$1</b> has failed on <b>$2</b><br><u>Journal tail:</u><br><br><i>$(journalctl -u $1 -n 10 -o cat)</i>" \
https://api.pushover.net/1/messages.json 2&>1
'';
};
};
}

View file

@ -0,0 +1,45 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.security;
in
{
options.mySystem.security = {
sshAgentAuth.enable = lib.mkEnableOption "openssh";
wheelNeedsSudoPassword = lib.mkOption {
type = lib.types.bool;
description = "If wheel group users need password for sudo";
default = true;
};
increaseWheelLoginLimits = lib.mkOption {
type = lib.types.bool;
description = "If wheel group users receive increased login limits";
default = true;
};
};
config =
{
security = {
sudo.wheelNeedsPassword = cfg.wheelNeedsSudoPassword;
# Don't bother with the lecture or the need to keep state about who's been lectured
sudo.extraConfig = "Defaults lecture=\"never\"";
pam.sshAgentAuth.enable = cfg.sshAgentAuth.enable;
# Increase open file limit for sudoers
pam.loginLimits = mkIf cfg.increaseWheelLoginLimits [
{
domain = "@wheel";
item = "nofile";
type = "soft";
value = "524288";
}
{
domain = "@wheel";
item = "nofile";
type = "hard";
value = "1048576";
}
];
};
};
}

View file

@ -0,0 +1,20 @@
{ lib, config, ... }:
with lib;
let
cfg = config.mySystem.system;
in
{
options.mySystem.system = {
packages = mkOption
{
type = with types; listOf package;
description = "List of system level package installs";
default = [ ];
};
};
# System packages deployed globally.
# This is NixOS so lets keep this liiight?
# Ideally i'd keep most of it to home-manager user only stuff
# and keep server role as light as possible
config.environment.systemPackages = cfg.packages;
}

View file

@ -0,0 +1,22 @@
{ lib, config, ... }:
let
cfg = config.mySystem.time;
in
{
options.mySystem.time = {
timeZone = lib.mkOption {
type = lib.types.str;
description = "Timezone of system";
default = "America/Chicago";
};
hwClockLocalTime = lib.mkOption {
type = lib.types.bool;
description = "If hardware clock is set to local time (useful for windows dual boot)";
default = false;
};
};
config = {
time.timeZone = cfg.timeZone;
time.hardwareClockInLocalTime = cfg.hwClockLocalTime;
};
}

View file

@ -0,0 +1,40 @@
{ lib, config, pkgs, ... }:
let
cfg = config.mySystem.system.zfs;
in
with lib;
{
options.mySystem.system.zfs = {
enable = lib.mkEnableOption "zfs";
mountPoolsAtBoot = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
};
};
config = lib.mkIf cfg.enable {
# setup boot
boot = {
supportedFilesystems = [
"zfs"
];
zfs = {
forceImportRoot = false; # if stuck on boot, modify grub options , force importing isnt secure
extraPools = cfg.mountPoolsAtBoot;
};
};
services.zfs = {
autoScrub.enable = true;
# Defaults to weekly and is a bit too regular for my NAS
autoScrub.interval = "weekly";
trim.enable = true;
};
# Pushover notifications
environment.systemPackages = with pkgs; [
busybox
];
services.zfs.zed.settings = {
ZED_PUSHOVER_TOKEN = "$(${pkgs.busybox}/bin/cat ${config.sops.secrets.pushover-api-key.path})";
ZED_PUSHOVER_USER = "$(${pkgs.busybox}/bin/cat ${config.sops.secrets.pushover-user-key.path})";
};
};
}

18
nixos/profiles/README.md Normal file
View file

@ -0,0 +1,18 @@
## Profiles
Here are the profiles that each host picks from to build up a system.
Pair a role with a device type and the correct software and settings will be enabled for that configuration.
Where possible use the `mySystem` option list to configure defaults via these profiles, so they _can_ be overridden later.
## Global
Default global settings that will apply to every device. Things like locale, timezone, etc that wont change machine to machine
## Hardware
Hardware settings so I can apply per set of machines as standard- i.e. all Raspi4's may benefit from a specific set of additions/hardware overlays.
## Role
The roles the machine has. Machines may have multiple roles.

44
nixos/profiles/global.nix Normal file
View file

@ -0,0 +1,44 @@
{ config, lib, pkgs, modulesPath, ... }:
with lib;
{
# NOTE
# Some 'global' areas have defaults set in their respective modules.
# These will be applied when the modules are loaded
# Not the global role.
# Not sure at this point a good way to manage globals in one place
# without mono-repo config.
imports =
[
(modulesPath + "/installer/scan/not-detected.nix") # Generated by nixos-config-generate
./global
];
config = {
boot.tmp.cleanOnBoot = true;
mySystem = {
# basics for all devices
time.timeZone = "America/Chicago";
security.increaseWheelLoginLimits = true;
system.packages = [ pkgs.bat ];
domain = "hsn.dev";
shell.fish.enable = true;
# But wont enable plugins globally, leave them for workstations
# TODO: Make per device option
system.resticBackup.remote.location = "s3:https://x.r2.cloudflarestorage.com/nixos-restic";
};
environment.systemPackages = with pkgs; [
curl
wget
dnsutils
jq
yq
];
networking.useDHCP = lib.mkDefault true;
networking.domain = config.mySystem.domain;
};
}

View file

@ -0,0 +1,8 @@
{
imports = [
./nix.nix
./sops.nix
./system.nix
./users.nix
];
}

View file

@ -0,0 +1,47 @@
{ lib, nixpkgs, ... }:
{
## Below is to align shell/system to flake's nixpkgs
## ref: https://nixos-and-flakes.thiscute.world/best-practices/nix-path-and-flake-registry
# Make `nix repl '<nixpkgs>'` use the same nixpkgs as the one used by this flake.
environment.etc."nix/inputs/nixpkgs".source = "${nixpkgs}";
nix = {
# make `nix run nixpkgs#nixpkgs` use the same nixpkgs as the one used by this flake.
registry.nixpkgs.flake = nixpkgs;
channel.enable = false; # remove nix-channel related tools & configs, we use flakes instead.
# but NIX_PATH is still used by many useful tools, so we set it to the same value as the one used by this flake.
# https://github.com/NixOS/nix/issues/9574
settings.nix-path = lib.mkForce "nixpkgs=/etc/nix/inputs/nixpkgs";
settings = {
# Enable flakes
experimental-features = [
"nix-command"
"flakes"
];
# Substitutions
substituters = [
"https://hsndev.cachix.org"
"https://nix-community.cachix.org"
"https://numtide.cachix.org"
];
trusted-public-keys = [
"hsndev.cachix.org-1:vN1/XGBZtMLnTFYDmTLDrullgZHSUYY3Kqt+Yg/C+tE="
"nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
"numtide.cachix.org-1:2ps1kLBUWjxIneOy1Ik6cQjb41X0iXVXeHigGmycPPE="
];
# Fallback quickly if substituters are not available.
connect-timeout = 25;
# Avoid copying unnecessary stuff over SSH
builders-use-substitutes = true;
trusted-users = [ "root" "@wheel" ];
warn-dirty = false;
# The default at 10 is rarely enough.
log-lines = lib.mkDefault 25;
};
};
}

View file

@ -0,0 +1,53 @@
services:
pushover:
env: ENC[AES256_GCM,data:Ug92K492ytxkRMHTw6fmYKgpkz47gBg9aLxqivo+t5GejJbIU1cOewo88/7ASmslceTAiY57NcBTM56eHw7U9+HEq1IjX6RukM/OxZ27R4aMkLCdf1c7DNJ5GOOvX2Zo,iv:uVXUIervTvgLnr60oeWeysB08TR/jy9AjNMWLW75teo=,tag:l2ZGGzMKcVTdnKx5q7XJiA==,type:str]
pushover-user-key: ENC[AES256_GCM,data:EWMIbhjq6+CH958H661xlxcVNLSKWHgxVyjTCU50,iv:Ph6z7Tri64WoPnCbTtrCA2ziprID1VXg8rynx6j6OOg=,tag:MZvq0AE1gp9Ga/73s6MWOw==,type:str]
pushover-api-key: ENC[AES256_GCM,data:1rnQ/dgls1iLtx3efKT37PyrZyArVbRe7BjmKYqZ,iv:pRvkTjRtpf5PhXG555OR+TVmgzwmJnF8+Pb2wfbTKWU=,tag:3DFYTgLxQFQIT4qTP9cQgQ==,type:str]
jahanson-password: ENC[AES256_GCM,data:HPLOlKHCpQIhat5qKKg=,iv:AawKpXj+2ZfEXg/eO2On4qtqHsoxksdRxH6nr+/oTGE=,tag:4wWfOGXybP6B88iGdFMF9A==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age:
- recipient: age18kj3xhlvgjeg2awwku3r8d95w360uysu0w5ejghnp4kh8qmtge5qwa2vjp
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBGSTUzM1RzZEFVZTJBdFAz
UnlYSmllNUdWa3BEa1BWQnBtbXZ1YTBnR25RClVIdy9YTUFyZ2VOdHlyVnNxTS9W
Smw5dDc2S053ZElsdHZpdTRQc2dCWGsKLS0tIGZXUzNMTFlxaVFWNE1GREphanBu
Rkdyd2lXUE5ucUVYT1FOZVlJR0hORGcKC5k9U3D+rfp4yEOx88APxmobjvOnf9jn
POshirAInB/FjEOCb6gwmX8Y/0KJK1A69vwrx3+C4S+8xWcTQwnHqA==
-----END AGE ENCRYPTED FILE-----
- recipient: age1z3vjvkead2h934n3w4m5m7tg4tj5qlzagsq6ly84h3tcu7x4ldsqd3s5fg
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBMeCtkNnllZ3RHSjdVWHpq
N0pXM3B3RVJ2QnNwVXpvN2Q3R09mSFU1QWtnCnRvYnR6eVZCaEJkMjMxeEFzRHZ2
enVodXlHUVFGcEZpTnFHT1VkTGwzbFkKLS0tIFhCTzQ3Ny9TOVBTamZ5SHIzKzFZ
L2NsSjFGWHR4ZDBQR0FFZUdCUXpPRmcKdC6gSCxkHvThXEhp94b3Gx/5Tz4E2B6C
TcwMZVgCC2VkSMASj1Jg08AX+xmJ92gc2xl/v9hQ3MWJbR38KBkaBw==
-----END AGE ENCRYPTED FILE-----
- recipient: age1a8z3p24v32l9yxm5z2l8h7rpc3nhacyfv4jvetk2lenrvsdstd3sdu2kaf
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBvMk1XRjdHQUtYbnNrb1oy
SnlwVjBEUTd3dFdlaWh6UGlFTzR1RmpKYjE4CmhSKzFBOFFJSmtnM3Zwc3pKUy9j
SmJiNFhoVmd0ZzE0V2xDK0dzaGhZRGMKLS0tIHlqbWozZHV1R3JSclQ0ZWNRejlD
b1Q4RjJSWm1uSjRVNWd1WjBld1hUbk0KOB+7u0eLMslPCBE745dk7P8kVgELGp6m
glDmVIqsKmvutZ54AhCtI/pOuL5vlmnstRn8MCXcZgXbm3a80BSNFg==
-----END AGE ENCRYPTED FILE-----
- recipient: age1d9p83j52m2xg0vh9k7q0uwlxwhs3y6tlv68yg9s2h9mdw2fmmsqshddz5m
enc: |
-----BEGIN AGE ENCRYPTED FILE-----
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOcUFiQk5yMGJBUitabTB6
UHFVRXFVVWFoREV2dXZXcVJ0cG11TlhCUEVVCnAxQkJUUkU1VUhSaWNLQStpcUJu
ZzJBQjlUQXhuZk9PZXRJSktnQWFHZmcKLS0tIFI4TUNwREF5cXNXZEd1NUJ3MGFH
OFVrMXovMDVZTU5obE42NXkwektRa0UKmd1VNqD43WmhwICUohG2DLsZ9wYsGujp
IERwiWAiCppqOEULQeZti8ioPHJDs6tjpuAR+vZ1TD/uhdJP4xsfcA==
-----END AGE ENCRYPTED FILE-----
lastmodified: "2024-05-18T16:39:41Z"
mac: ENC[AES256_GCM,data:EXz+9dIdcI21WlJ9r/lewrfx9MjynA1eS82MMnO0BBiE2KV6XcMvrxStMIerXY/0nr2XGoZii8amo072naaHBTCwCsaDlx0Ry3oMyZYZNFwTM2mqIK93CymIkhe/qhMyNxDTmKc3QFEiRtimLnUx8YW27leS1b4mNFHcauXYNHA=,iv:EEmK39VBNXnJayk6z4MfHHsfj7OuwXUeLZQ06q2y2cE=,tag:9PLLS5BbwB1wmA+UV/oIwA==,type:str]
pgp: []
unencrypted_suffix: _unencrypted
version: 3.8.1

View file

@ -0,0 +1,14 @@
{ ... }:
{
sops.age.sshKeyPaths = [ "/etc/ssh/ssh_host_ed25519_key" ];
# Secret for machine-specific pushover
sops.secrets."services/pushover/env" = {
sopsFile = ./secrets.sops.yaml;
};
sops.secrets.pushover-user-key = {
sopsFile = ./secrets.sops.yaml;
};
sops.secrets.pushover-api-key = {
sopsFile = ./secrets.sops.yaml;
};
}

View file

@ -0,0 +1,13 @@
{ lib, pkgs, ... }:
{
system = {
# Enable printing changes on nix build etc with nvd
activationScripts.report-changes = ''
PATH=$PATH:${lib.makeBinPath [ pkgs.nvd pkgs.nix ]}
nvd diff $(ls -dv /nix/var/nix/profiles/system-*-link | tail -2)
'';
# Do not change unless you know what you are doing
stateVersion = "24.11";
};
}

View file

@ -0,0 +1,39 @@
{ pkgs, config, ... }:
let
ifTheyExist = groups: builtins.filter (group: builtins.hasAttr group config.users.groups) groups;
in
{
sops.secrets = {
jahanson-password = {
sopsFile = ./secrets.sops.yaml;
neededForUsers = true;
};
};
users.users.jahanson = {
isNormalUser = true;
shell = pkgs.fish;
hashedPasswordFile = config.sops.secrets.jahanson-password.path;
extraGroups =
[
"wheel"
]
++ ifTheyExist [
"network"
"samba-users"
"docker"
"podman"
"audio" # pulseaudio
"libvirtd"
];
openssh.authorizedKeys.keys = [
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBsUe5YF5z8vGcEYtQX7AAiw2rJygGf2l7xxr8nZZa7w"
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBH3VVFenoJfnu+IFUlD79uxl7L8SFoRup33J2HGny4WEdRgGR41s0MpFKDBmxXZHy4O9Nh8NMMnpy5VhUefnIKI="
"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPHFQ3hDjjrKsecn3jmSWYlRXy4IJCrepgU1HaIV5VcmB3mUFmIZ/pCZnPmIG/Gbuqf1PP2FQDmHMX5t0hTYG9A="
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETR70eQJiXaJuB+qpI1z+jFOPbEZoQNRcq4VXkojWfU"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIATyScd8ZRhV7uZmrQNSAbRTs9N/Dbx+Y8tGEDny30sA"
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJyA/yMPPo+scxBaDFUk7WeEyMAMhXUro5vi4feOKsJT jahanson@durincore"
];
};
}

View file

@ -0,0 +1,27 @@
{ lib, pkgs, ... }:
with lib;
{
mySystem.system.packages = with pkgs; [
ntfs3g
];
boot = {
initrd.availableKernelModules = [ "nvme" "xhci_pci" "ahci" "usbhid" "usb_storage" "sd_mod" ];
kernelModules = [ ];
extraModulePackages = [ ];
# for managing/mounting ntfs
supportedFilesystems = [ "ntfs" ];
loader = {
systemd-boot.enable = true;
efi.canTouchEfiVariables = true;
grub.memtest86.enable = true;
};
};
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
}

View file

@ -0,0 +1,26 @@
{ lib, ... }: {
imports = [ ];
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;
boot.initrd.availableKernelModules = [ "xhci_pci" "virtio_pci" "virtio_scsi" "usbhid" "sr_mod" ];
boot.initrd.kernelModules = [ ];
boot.kernelModules = [ ];
boot.extraModulePackages = [ ];
mySystem = {
services.openssh.enable = true;
security.wheelNeedsSudoPassword = false;
# Restic backups disabled.
# TODO: configure storagebox for hetzner backups
system.resticBackup =
{
local.enable = false;
remote.enable = false;
};
};
networking.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "aarch64-linux";
}

View file

@ -0,0 +1,30 @@
{ config, lib, ... }:
{
boot = {
# Use the systemd-boot EFI boot loader.
loader = {
systemd-boot = {
enable = true;
};
efi = {
canTouchEfiVariables = true;
};
};
# Kernel mods
initrd = {
availableKernelModules = [ "xhci_pci" "nvme" "usb_storage" "sd_mod" ];
kernelModules = [ ];
};
kernelModules = [ "kvm-intel" ];
extraModulePackages = [ ];
};
networking = {
useDHCP = lib.mkDefault true;
};
# networking.interfaces.enp0s31f6.useDHCP = lib.mkDefault true;
# networking.interfaces.wlp4s0.useDHCP = lib.mkDefault true;
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
hardware.cpu.intel.updateMicrocode = lib.mkDefault config.hardware.enableRedistributableFirmware;
}

View file

@ -0,0 +1,36 @@
{ config, lib, pkgs, imports, boot, self, inputs, ... }:
# Role for dev stations
# Could be a workstatio or a headless server.
with config;
{
# git & vim are in global
environment.systemPackages = with pkgs; [
jq
yq
btop
dnsutils
nix
# nix dev
dnscontrol # for updating internal DNS servers with homelab services
# TODO Move
nil
nixpkgs-fmt
statix
# nvd
gh
# bind # for dns utils like named-checkconf
inputs.nix-inspect.packages.${pkgs.system}.default
];
programs.direnv = {
# TODO move to home-manager
enable = true;
nix-direnv.enable = true;
};
}

View file

@ -0,0 +1,47 @@
{ config, lib, pkgs, ... }:
# Role for headless servers
# covers raspi's, sbc, NUC etc, anything
# that is headless and minimal for running services
with lib;
{
config = {
# Enable monitoring for remote scraiping
mySystem.services.promMonitoring.enable = true;
mySystem.services.rebootRequiredCheck.enable = true;
mySystem.security.wheelNeedsSudoPassword = false;
mySystem.services.cockpit.enable = true;
mySystem.system.motd.enable = true;
mySystem.services.gatus.monitors = [{
name = config.networking.hostName;
group = "servers";
url = "icmp://${config.networking.hostName}";
interval = "1m";
conditions = [ "[CONNECTED] == true" ];
}];
nix.settings = {
# TODO factor out into mySystem
# Avoid disk full issues
max-free = lib.mkDefault (1000 * 1000 * 1000);
min-free = lib.mkDefault (128 * 1000 * 1000);
};
services.logrotate.enable = mkDefault true;
environment = {
noXlibs = mkDefault true;
systemPackages = [ pkgs.lazygit ];
};
documentation = {
enable = mkDefault false;
doc.enable = mkDefault false;
info.enable = mkDefault false;
man.enable = mkDefault false;
nixos.enable = mkDefault false;
};
programs.command-not-found.enable = mkDefault false;
sound.enable = false;
hardware.pulseaudio.enable = false;
services.udisks2.enable = mkDefault false;
};
}

View file

@ -0,0 +1,75 @@
{ config, lib, pkgs, ... }:
# Role for workstations
# Covers desktops/laptops, expected to have a GUI and do workloads
# Will have home-manager installs
with config;
{
mySystem = {
de.gnome.enable = true;
# Lets see if fish everywhere is OK on the pi's
# TODO decide if i drop to bash on pis?
shell.fish.enable = true;
# TODO make nfs server configurable
# nfs.nas = {
# enable = true;
# lazy = true;
# };
system.resticBackup.local.enable = false;
system.resticBackup.remote.enable = false;
};
boot = {
binfmt.emulatedSystems = [ "aarch64-linux" ]; # Enabled for raspi4 compilation
plymouth.enable = true; # hide console with splash screen
};
nix.settings = {
# TODO factor out into mySystem
# Avoid disk full issues
max-free = lib.mkDefault (1000 * 1000 * 1000);
min-free = lib.mkDefault (128 * 1000 * 1000);
};
# set xserver videodrivers if used
services.xserver.enable = true;
services = {
fwupd.enable = config.boot.loader.systemd-boot.enable; # fwupd does not work in BIOS mode
thermald.enable = true;
smartd.enable = true;
# required for yubikey
udev.packages = [ pkgs.yubikey-personalization ];
pcscd.enable = true;
};
hardware = {
enableAllFirmware = true;
sensor.hddtemp = {
enable = true;
drives = [ "/dev/disk/by-id/*" ];
};
};
environment.systemPackages = with pkgs; [
# Sensors etc
lm_sensors
cpufrequtils
cpupower-gui
vscode
vivaldi
termius
];
i18n = {
defaultLocale = lib.mkDefault "en_US.UTF-8";
};
programs.mtr.enable = true;
}

13
renovate.json Normal file
View file

@ -0,0 +1,13 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"nix": {
"enabled": true
},
"lockFileMaintenance": {
"enabled": true,
"extends": ["schedule:daily"]
}
}

30
shell.nix Normal file
View file

@ -0,0 +1,30 @@
# Shell for bootstrapping flake-enabled nix and home-manager
{ pkgs ? let
# If pkgs is not defined, instantiate nixpkgs from locked commit
lock = (builtins.fromJSON (builtins.readFile ./flake.lock)).nodes.nixpkgs.locked;
nixpkgs = fetchTarball {
url = "https://github.com/nixos/nixpkgs/archive/${lock.rev}.tar.gz";
sha256 = lock.narHash;
};
system = builtins.currentSystem;
overlays = [ ]; # Explicit blank overlay to avoid interference
in
import nixpkgs { inherit system overlays; }
, ...
}:
pkgs.mkShell {
# Enable experimental features without having to specify the argument
NIX_CONFIG = "experimental-features = nix-command flakes";
nativeBuildInputs = with pkgs; [
nix
home-manager
git
nil
nixpkgs-fmt
go-task
sops
pre-commit
gitleaks
];
}