Compare commits
7 commits
3dd421cb08
...
10bad206e5
Author | SHA1 | Date | |
---|---|---|---|
10bad206e5 | |||
894cc4444c | |||
12ac221b46 | |||
4563b89b6e | |||
6d0942d351 | |||
80f932ba6d | |||
538d0a20a4 |
30 changed files with 877 additions and 1030 deletions
58
flake.lock
generated
58
flake.lock
generated
|
@ -69,46 +69,24 @@
|
||||||
},
|
},
|
||||||
"chaotic": {
|
"chaotic": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"fenix": "fenix",
|
|
||||||
"flake-schemas": "flake-schemas",
|
"flake-schemas": "flake-schemas",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"jovian": "jovian",
|
"jovian": "jovian",
|
||||||
"nixpkgs": "nixpkgs"
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay"
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1748304776,
|
"lastModified": 1750195929,
|
||||||
"narHash": "sha256-Eb+kBcm7ECpJ1HKjMgvZPo9TpGG0CpzfGRUc0FCZKP0=",
|
"narHash": "sha256-5gaf/9wuxtfKqAFnNlX74Vz2VMURa/UzyfuEyYv4tZw=",
|
||||||
"owner": "chaotic-cx",
|
"owner": "chaotic-cx",
|
||||||
"repo": "nyx",
|
"repo": "nyx",
|
||||||
"rev": "5278f55d2c2c568db38ed03370606b5e009e34df",
|
"rev": "419a1cfaf34100008ff5fa97ce9ef3b194472f71",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "chaotic-cx",
|
"owner": "chaotic-cx",
|
||||||
|
"ref": "nyxpkgs-unstable",
|
||||||
"repo": "nyx",
|
"repo": "nyx",
|
||||||
"rev": "5278f55d2c2c568db38ed03370606b5e009e34df",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fenix": {
|
|
||||||
"inputs": {
|
|
||||||
"nixpkgs": [
|
|
||||||
"chaotic",
|
|
||||||
"nixpkgs"
|
|
||||||
],
|
|
||||||
"rust-analyzer-src": "rust-analyzer-src"
|
|
||||||
},
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1747392669,
|
|
||||||
"narHash": "sha256-zky3+lndxKRu98PAwVK8kXPdg+Q1NVAhaI7YGrboKYA=",
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"rev": "c3c27e603b0d9b5aac8a16236586696338856fbb",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "nix-community",
|
|
||||||
"repo": "fenix",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -760,20 +738,24 @@
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"rust-analyzer-src": {
|
"rust-overlay": {
|
||||||
"flake": false,
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"chaotic",
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1747323949,
|
"lastModified": 1750214276,
|
||||||
"narHash": "sha256-G4NwzhODScKnXqt2mEQtDFOnI0wU3L1WxsiHX3cID/0=",
|
"narHash": "sha256-1kniuhH70q4TAC/xIvjFYH46aHiLrbIlcr6fdrRwO1A=",
|
||||||
"owner": "rust-lang",
|
"owner": "oxalica",
|
||||||
"repo": "rust-analyzer",
|
"repo": "rust-overlay",
|
||||||
"rev": "f8e784353bde7cbf9a9046285c1caf41ac484ebe",
|
"rev": "f9b2b2b1327ff6beab4662b8ea41689e0a57b8d4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "rust-lang",
|
"owner": "oxalica",
|
||||||
"ref": "nightly",
|
"repo": "rust-overlay",
|
||||||
"repo": "rust-analyzer",
|
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
|
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
chaotic.url = "github:chaotic-cx/nyx/5278f55d2c2c568db38ed03370606b5e009e34df"; # Bleeding edge packages from Chaotic-AUR
|
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; # Bleeding edge packages from Chaotic-AUR
|
||||||
|
|
||||||
## NixOS ##
|
## NixOS ##
|
||||||
|
|
||||||
|
|
|
@ -4,31 +4,141 @@
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
let
|
||||||
|
primaryMonitor = lib.custom.getPrimaryMonitor (config.monitors or [ ]);
|
||||||
|
WIDTH = primaryMonitor.width or 1980;
|
||||||
|
HEIGHT = primaryMonitor.height or 1080;
|
||||||
|
REFRESH_RATE = primaryMonitor.refreshRate or 60;
|
||||||
|
VRR = primaryMonitor.vrr or false;
|
||||||
|
HDR = primaryMonitor.hdr or false;
|
||||||
|
|
||||||
|
# INFO: Example working commands for running games in steam-session
|
||||||
|
## Rivals ##
|
||||||
|
# SteamDeck=1 LD_PRELOAD="" PROTON_ENABLE_NVAPI=1 PROTON_ENABLE_WAYLAND=1 VKD3D_DISABLE_EXTENSIONS=VK_KHR_present_wait gamemoderun %command% -PSOCompileMode=1 -dx12
|
||||||
|
## Stats Overlay ##
|
||||||
|
# gamemoderun mangohud %command%
|
||||||
|
## gamescope-run ##
|
||||||
|
# GAMESCOPE_EXTRA_OPTS="--force-grab-cursor"; gamescope-run gamemoderun mangohud %command%
|
||||||
|
|
||||||
|
gamescope-base-script = pkgs.writeShellScript "gamescope-base" ''
|
||||||
|
# Session configuration
|
||||||
|
export CAP_SYS_NICE="eip"
|
||||||
|
export DXVK_HDR="1"
|
||||||
|
export ENABLE_GAMESCOPE_WSI="1"
|
||||||
|
export ENABLE_HDR_WSI="1"
|
||||||
|
export AMD_VULKAN_ICD=RADV
|
||||||
|
export RADV_PERFTEST=aco
|
||||||
|
export SDL_VIDEODRIVER="wayland"
|
||||||
|
|
||||||
|
# Steam environment variables for better compatibility
|
||||||
|
export STEAM_FORCE_DESKTOPUI_SCALING=1
|
||||||
|
export STEAM_GAMEPADUI=1
|
||||||
|
export STEAM_GAMESCOPE_CLIENT=1
|
||||||
|
|
||||||
|
# Default resolution settings
|
||||||
|
WIDTH=${toString WIDTH}
|
||||||
|
HEIGHT=${toString HEIGHT}
|
||||||
|
REFRESH_RATE=${toString REFRESH_RATE}
|
||||||
|
|
||||||
|
# Build gamescope options
|
||||||
|
GAMESCOPE_OPTS="--fade-out-duration 200 -w $WIDTH -h $HEIGHT -r $REFRESH_RATE -f"
|
||||||
|
GAMESCOPE_OPTS="$GAMESCOPE_OPTS --expose-wayland --backend sdl --rt --immediate-flips"
|
||||||
|
${lib.optionalString HDR ''GAMESCOPE_OPTS="$GAMESCOPE_OPTS --hdr-enabled --hdr-debug-force-output --hdr-itm-enable"''}
|
||||||
|
${lib.optionalString VRR ''GAMESCOPE_OPTS="$GAMESCOPE_OPTS --adaptive-sync"''}
|
||||||
|
# Allow extra options to be passed
|
||||||
|
GAMESCOPE_OPTS="$GAMESCOPE_OPTS $GAMESCOPE_EXTRA_OPTS"
|
||||||
|
|
||||||
|
# Execute gamescope with provided command
|
||||||
|
exec ${lib.getExe pkgs.gamescope} $GAMESCOPE_OPTS -- "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
gamescope-run = pkgs.writeShellScriptBin "gamescope-run" ''
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
echo "Usage: gamescope-run <command> [args...]"
|
||||||
|
echo "Examples:"
|
||||||
|
echo " gamescope-run heroic"
|
||||||
|
echo " gamescope-run steam"
|
||||||
|
echo " gamescope-run lutris"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec ${gamescope-base-script} "$@"
|
||||||
|
'';
|
||||||
|
|
||||||
|
steam-session-script = pkgs.writeShellScript "steam-session-script" ''
|
||||||
|
# Check launch mode parameter
|
||||||
|
case "$1" in
|
||||||
|
"desktop")
|
||||||
|
# Launch Steam in desktop mode within gamescope
|
||||||
|
exec ${gamescope-base-script} ${lib.getExe pkgs.steam}
|
||||||
|
;;
|
||||||
|
"bigpicture"|*)
|
||||||
|
# Default: Launch Steam in Big Picture mode
|
||||||
|
exec ${gamescope-base-script} ${lib.getExe pkgs.steam} -tenfoot -steamdeck -gamepadui
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
'';
|
||||||
|
in
|
||||||
{
|
{
|
||||||
imports = lib.custom.scanPaths ./.;
|
imports = lib.custom.scanPaths ./.;
|
||||||
|
|
||||||
home.packages = with pkgs; [
|
home.packages = with pkgs; [
|
||||||
prismlauncher
|
prismlauncher
|
||||||
|
steam-run
|
||||||
|
heroic
|
||||||
|
gamescope-run
|
||||||
# modrinth-app
|
# modrinth-app
|
||||||
];
|
];
|
||||||
|
|
||||||
|
programs.mangohud = {
|
||||||
|
enable = true;
|
||||||
|
settings = {
|
||||||
|
position = "top-right";
|
||||||
|
cpu_stats = true;
|
||||||
|
gpu_stats = true;
|
||||||
|
fps = true;
|
||||||
|
font_size = 12;
|
||||||
|
cellpadding_y = -0.070;
|
||||||
|
background_alpha = lib.mkForce 0.5;
|
||||||
|
alpha = lib.mkForce 0.75;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
xdg.desktopEntries = {
|
||||||
|
steam = {
|
||||||
|
name = "Steam Desktop (Gamescope)";
|
||||||
|
comment = "Steam in Gamescope Session";
|
||||||
|
exec = "${steam-session-script} desktop";
|
||||||
|
icon = "steam";
|
||||||
|
type = "Application";
|
||||||
|
terminal = false;
|
||||||
|
categories = [ "Game" ];
|
||||||
|
actions = {
|
||||||
|
desktop = {
|
||||||
|
name = "Steam Big Picture (Gamescope)";
|
||||||
|
exec = "${steam-session-script} bigpicture";
|
||||||
|
};
|
||||||
|
regular = {
|
||||||
|
name = "Steam Desktop (No Gamescope)";
|
||||||
|
exec = "${lib.getExe pkgs.steam}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
"com.heroicgameslauncher.hgl.desktop" = {
|
||||||
|
name = "Heroic Games Launcher (Gamescope)";
|
||||||
|
comment = "Heroic in Gamescope Session";
|
||||||
|
exec = "${lib.getExe gamescope-run} heroic";
|
||||||
|
icon = "com.heroicgameslauncher.hgl";
|
||||||
|
type = "Application";
|
||||||
|
terminal = false;
|
||||||
|
categories = [ "Game" ];
|
||||||
|
actions = {
|
||||||
|
regular = {
|
||||||
|
name = "Heroic (No Gamescope)";
|
||||||
|
exec = "${lib.getExe pkgs.heroic}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
# INFO: Example working commands for running gamescope, excuse the mess lol
|
|
||||||
|
|
||||||
# gamescope --adaptive-sync --backend sdl --expose-wayland --force-grab-cursor --framerate-limit 120 --immediate-flips --output-height 2160 --output-width 3840 --prefer-output DP-3 --rt -- gamemoderun %command%
|
|
||||||
|
|
||||||
# AMD_VULKAN_ICD=RADV RADV_PERFTEST=aco PROTON_USE_D9VK=1 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 120 --output-height 2160 --output-width 3840 --prefer-vk-device 7550:C0 --rt -F fsr -f --sharpness 4 -- gamemoderun %command%
|
|
||||||
|
|
||||||
# CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 144 --output-height 1440 --output-width 2560 --rt -F fsr -f --sharpness 4 -- gamemoderun %command%
|
|
||||||
|
|
||||||
# gamescope --expose-wayland --backend sdl --framerate-limit 144 --output-height 1440 --output-width 2560 --prefer-vk-device 7480:CF --prefer-output DP-2 --fullscreen --rt -F fsr --sharpness 4 -- gamemoderun %command%
|
|
||||||
|
|
||||||
# CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 160 --output-height 1440 --output-width 2560 --prefer-vk-device 73A5:C0 --prefer-output DP-2 --fullscreen --force-windows-fullscreen --rt -F fsr --sharpness 4 -- gamemoderun %command%
|
|
||||||
|
|
||||||
# SDL_VIDEODRIVER=x11 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 160 --output-height 1440 --output-width 2560 --force-windows-fullscreen -f --prefer-vk-device 73A5:C0 --prefer-output DP-2 -- gamemoderun %command% --use-d3d11
|
|
||||||
|
|
||||||
# SteamDeck=1 LD_PRELOAD="" PROTON_ENABLE_NVAPI=1 PROTON_ENABLE_WAYLAND=1 VKD3D_DISABLE_EXTENSIONS=VK_KHR_present_wait gamemoderun %command% -PSOCompileMode=1
|
|
||||||
|
|
||||||
#SDL_VIDEODRIVER=x11 AMD_VULKAN_ICD=RADV RADV_PERFTEST=aco PROTON_USE_D9VK=1 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 120 --output-height 2160 --output-width 3840 --prefer-vk-device 7550:C0 --rt -F fsr -f --sharpness 4 -- gamemoderun %command%
|
|
||||||
|
|
||||||
#SDL_VIDEODRIVER=x11 AMD_VULKAN_ICD=RADV RADV_PERFTEST=aco PROTON_USE_D9VK=1 CAP_SYS_NICE=eip gamescope --expose-wayland --backend sdl --framerate-limit 120 --output-height 2160 --output-width 3840 --prefer-vk-device 7550:C0 --force-windows-fullscreen -f --prefer-vk-device 73A5:C0 --prefer-output DP-2 -- gamemoderun %command% --use-d3d11
|
|
||||||
|
|
|
@ -322,9 +322,12 @@ with lib.hm.gvariant;
|
||||||
vertical-margin-bottom = 8;
|
vertical-margin-bottom = 8;
|
||||||
window-gap = 8;
|
window-gap = 8;
|
||||||
winprops = [
|
winprops = [
|
||||||
''
|
''
|
||||||
{"wm_class":"Code","spaceIndex":0}
|
{"wm_class":"Code","spaceIndex":0}
|
||||||
''
|
''
|
||||||
|
''
|
||||||
|
{"wm_class":"com.jaoushingan.WaydroidHelper","scratch_layer":true}
|
||||||
|
''
|
||||||
''
|
''
|
||||||
{"wm_class":"com.mitchellh.ghostty","scratch_layer":true}
|
{"wm_class":"com.mitchellh.ghostty","scratch_layer":true}
|
||||||
''
|
''
|
||||||
|
@ -337,12 +340,21 @@ with lib.hm.gvariant;
|
||||||
''
|
''
|
||||||
{"wm_class":"gnome-extensions-app","scratch_layer":true}
|
{"wm_class":"gnome-extensions-app","scratch_layer":true}
|
||||||
''
|
''
|
||||||
|
''
|
||||||
|
{"wm_class":"org.gnome.Extensions","scratch_layer":true}
|
||||||
|
''
|
||||||
''
|
''
|
||||||
{"wm_class":"org.gnome.Nautilus","scratch_layer":true}
|
{"wm_class":"org.gnome.Nautilus","scratch_layer":true}
|
||||||
''
|
''
|
||||||
''
|
''
|
||||||
{"wm_class":"TelegramDesktop","spaceIndex":1}
|
{"wm_class":"TelegramDesktop","spaceIndex":1}
|
||||||
''
|
''
|
||||||
|
''
|
||||||
|
{"wm_class":"Waydroid","preferredWidth":"100%","spaceIndex":0,"title":""}
|
||||||
|
''
|
||||||
|
''
|
||||||
|
{"wm_class":".gamescope-wrapped","preferredWidth":"100%","spaceIndex":2}
|
||||||
|
''
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,15 +6,14 @@
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
#TODO: Scripts might need a rework
|
|
||||||
programs.fastfetch =
|
programs.fastfetch =
|
||||||
let
|
let
|
||||||
hostname = hostSpec.hostName;
|
hostname = hostSpec.hostName;
|
||||||
logoFile =
|
logoFile =
|
||||||
let
|
let
|
||||||
hostLogoPath = ./. + "/host/${hostname}.txt";
|
hostLogoPath = ./. + "/host/images/${hostname}.png";
|
||||||
in
|
in
|
||||||
if builtins.pathExists hostLogoPath then hostLogoPath else ./host/nix.txt;
|
if builtins.pathExists hostLogoPath then hostLogoPath else ./host/images/nix.png;
|
||||||
weather = import ./scripts/weather.nix { inherit pkgs; };
|
weather = import ./scripts/weather.nix { inherit pkgs; };
|
||||||
title = import ./scripts/title.nix { inherit pkgs; };
|
title = import ./scripts/title.nix { inherit pkgs; };
|
||||||
in
|
in
|
||||||
|
@ -22,12 +21,14 @@
|
||||||
enable = true;
|
enable = true;
|
||||||
settings = {
|
settings = {
|
||||||
logo = {
|
logo = {
|
||||||
source = builtins.readFile logoFile;
|
type = "kitty";
|
||||||
type = "data";
|
source = logoFile;
|
||||||
position = "left";
|
width = 21; # columns
|
||||||
|
height = 12; # rows
|
||||||
padding = {
|
padding = {
|
||||||
top = 0;
|
top = 1;
|
||||||
right = 0;
|
right = 2;
|
||||||
|
left = 2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
display = {
|
display = {
|
||||||
|
@ -72,7 +73,11 @@
|
||||||
type = "wm";
|
type = "wm";
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
text = "printf '%s%s' (string upper (string sub -l 1 $SHELL)) (string lower (string sub -s 2 $SHELL))";
|
text =
|
||||||
|
let
|
||||||
|
name = lib.getName pkgs.fish;
|
||||||
|
in
|
||||||
|
"printf '%s%s' (string upper (string sub -l 1 ${name})) (string lower (string sub -s 2 ${name}))";
|
||||||
key = "shell » {#keys}";
|
key = "shell » {#keys}";
|
||||||
keyColor = "1;33";
|
keyColor = "1;33";
|
||||||
type = "command";
|
type = "command";
|
||||||
|
|
BIN
home/global/core/fastfetch/host/images/haze.png
Normal file
BIN
home/global/core/fastfetch/host/images/haze.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 123 KiB |
|
@ -21,7 +21,10 @@ for i in (seq 1 $num)
|
||||||
set type2 $types[$j]
|
set type2 $types[$j]
|
||||||
set combination "$type1+$type2"
|
set combination "$type1+$type2"
|
||||||
echo "Creating with type $combination"
|
echo "Creating with type $combination"
|
||||||
nix run nixpkgs#chafa -- -s 24x11 -w 9 --symbols $combination --view-size 24x11 $input_png
|
yay try chafa -- chafa -s 24x11 -w 9 --symbols $combination --view-size 24x11 $input_png
|
||||||
# chafa -s 23x12 -w 9 --stretch --symbols $combination --view-size 23x12 $input_png
|
# yay try chafa -- chafa -s 23x12 -w 9 --stretch --symbols $combination --view-size 23x12 $input_png
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# yay try chafa -- chafa -s 23x12 -w 9 --symbols half --view-size 23x12 ./1989.png
|
||||||
|
# fastfetch --logo-height 12 --logo-width 21 --logo-type kitty --logo-position left --logo-preserve-aspect-ratio --logo ./haze.png --logo-padding 2 --logo-padding-top 1
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
set fish_greeting # Disable greeting
|
function fish_greeting
|
||||||
|
if test "$SIXEL" = true; or string match -q xterm-256color "$TERM"
|
||||||
|
fastfetch --logo-type sixel
|
||||||
|
else
|
||||||
|
fastfetch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
## Aliases and Overrides ##
|
## Aliases and Overrides ##
|
||||||
|
|
||||||
|
@ -108,7 +114,3 @@ function unzipz
|
||||||
return 1
|
return 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
## Fish Prompt ##
|
|
||||||
|
|
||||||
fastfetch
|
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
{
|
{
|
||||||
imports = lib.custom.scanPaths ./.;
|
imports = lib.custom.scanPaths ./.;
|
||||||
|
|
||||||
# home.file.".config/monitors_source" = {
|
# home.file.".config/monitors_source" = {
|
||||||
# source = ./monitors.xml;
|
# source = ./monitors.xml;
|
||||||
# onChange = ''
|
# onChange = ''
|
||||||
# cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
# cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||||
# '';
|
# chmod 755 $HOME/.config/monitors.xml
|
||||||
# };
|
# '';
|
||||||
|
# };
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,10 +30,45 @@
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
monitors = [
|
||||||
|
{
|
||||||
|
name = "DP-2";
|
||||||
|
primary = true;
|
||||||
|
width = 3840;
|
||||||
|
height = 2160;
|
||||||
|
refreshRate = 60;
|
||||||
|
x = 0;
|
||||||
|
y = 0;
|
||||||
|
scale = 1.0;
|
||||||
|
transform = 0;
|
||||||
|
enabled = true;
|
||||||
|
hdr = true;
|
||||||
|
vrr = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "HDMI-1";
|
||||||
|
primary = false;
|
||||||
|
width = 1920;
|
||||||
|
height = 1080;
|
||||||
|
refreshRate = 60;
|
||||||
|
x = 1920; # Positioned to the right of DP-1
|
||||||
|
y = 0;
|
||||||
|
scale = 1.0;
|
||||||
|
transform = 1;
|
||||||
|
enabled = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
## TODO:
|
||||||
|
## I want to automate this monitors.xml
|
||||||
|
## But dk how to do it properly yet.
|
||||||
|
## So for now ill still use this for the gnome config
|
||||||
|
|
||||||
home.file.".config/monitors_source" = {
|
home.file.".config/monitors_source" = {
|
||||||
source = ./monitors.xml;
|
source = ./monitors.xml;
|
||||||
onChange = ''
|
onChange = ''
|
||||||
cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||||
|
chmod 755 $HOME/.config/monitors.xml
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,6 @@
|
||||||
<monitors version="2">
|
<monitors version="2">
|
||||||
<configuration>
|
<configuration>
|
||||||
<layoutmode>physical</layoutmode>
|
<layoutmode>logical</layoutmode>
|
||||||
<logicalmonitor>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<scale>1</scale>
|
|
||||||
<transform>
|
|
||||||
<rotation>right</rotation>
|
|
||||||
<flipped>no</flipped>
|
|
||||||
</transform>
|
|
||||||
<monitor>
|
|
||||||
<monitorspec>
|
|
||||||
<connector>HDMI-1-2</connector>
|
|
||||||
<vendor>DEL</vendor>
|
|
||||||
<product>DELL U2417H</product>
|
|
||||||
<serial>XVNNT67I176L</serial>
|
|
||||||
</monitorspec>
|
|
||||||
<mode>
|
|
||||||
<width>1920</width>
|
|
||||||
<height>1080</height>
|
|
||||||
<rate>60.000</rate>
|
|
||||||
</mode>
|
|
||||||
</monitor>
|
|
||||||
</logicalmonitor>
|
|
||||||
<logicalmonitor>
|
<logicalmonitor>
|
||||||
<x>1080</x>
|
<x>1080</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
|
@ -42,5 +20,27 @@
|
||||||
</mode>
|
</mode>
|
||||||
</monitor>
|
</monitor>
|
||||||
</logicalmonitor>
|
</logicalmonitor>
|
||||||
|
<logicalmonitor>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<scale>1</scale>
|
||||||
|
<transform>
|
||||||
|
<rotation>right</rotation>
|
||||||
|
<flipped>no</flipped>
|
||||||
|
</transform>
|
||||||
|
<monitor>
|
||||||
|
<monitorspec>
|
||||||
|
<connector>HDMI-2</connector>
|
||||||
|
<vendor>DEL</vendor>
|
||||||
|
<product>DELL U2417H</product>
|
||||||
|
<serial>XVNNT67I176L</serial>
|
||||||
|
</monitorspec>
|
||||||
|
<mode>
|
||||||
|
<width>1920</width>
|
||||||
|
<height>1080</height>
|
||||||
|
<rate>60.000</rate>
|
||||||
|
</mode>
|
||||||
|
</monitor>
|
||||||
|
</logicalmonitor>
|
||||||
</configuration>
|
</configuration>
|
||||||
</monitors>
|
</monitors>
|
||||||
|
|
|
@ -2,22 +2,33 @@
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
|
inputs,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
{
|
{
|
||||||
hardware.opengl = {
|
|
||||||
|
hardware.graphics = {
|
||||||
enable = true;
|
enable = true;
|
||||||
driSupport32Bit = true;
|
enable32Bit = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
# AMDgpu tool
|
# AMDgpu tool
|
||||||
environment.systemPackages = with pkgs; [ lact ];
|
environment.systemPackages = with pkgs; [
|
||||||
systemd.packages = with pkgs; [ lact ];
|
lact
|
||||||
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
|
gamescope
|
||||||
|
# gamescope-wsi
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd = {
|
||||||
|
packages = with pkgs; [ lact ];
|
||||||
|
services.lactd.wantedBy = [ "multi-user.target" ];
|
||||||
|
};
|
||||||
|
|
||||||
programs = {
|
programs = {
|
||||||
steam = {
|
steam = {
|
||||||
enable = true;
|
enable = true;
|
||||||
|
remotePlay.openFirewall = true;
|
||||||
|
dedicatedServer.openFirewall = true;
|
||||||
protontricks = {
|
protontricks = {
|
||||||
enable = true;
|
enable = true;
|
||||||
package = pkgs.protontricks;
|
package = pkgs.protontricks;
|
||||||
|
@ -41,7 +52,7 @@
|
||||||
|
|
||||||
inherit (pkgs)
|
inherit (pkgs)
|
||||||
gamemode
|
gamemode
|
||||||
gamescope # !!!: DO NOT ADD GAMESCOPE ANYWHERE ELSE IN CONFIG, IT WILL BREAK!
|
mangohud
|
||||||
gperftools
|
gperftools
|
||||||
keyutils
|
keyutils
|
||||||
libkrb5
|
libkrb5
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
{ pkgs, config, ... }:
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
{
|
{
|
||||||
## DE ##
|
## DE ##
|
||||||
services.xserver = {
|
|
||||||
enable = true;
|
|
||||||
xkb = {
|
|
||||||
layout = "us";
|
|
||||||
variant = "";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.desktopManager.gnome = {
|
services.desktopManager.gnome = {
|
||||||
enable = true;
|
enable = true;
|
||||||
extraGSettingsOverridePackages = [ pkgs.mutter ];
|
extraGSettingsOverridePackages = [ pkgs.mutter ];
|
||||||
|
@ -23,12 +20,25 @@
|
||||||
enable = true;
|
enable = true;
|
||||||
wayland = true;
|
wayland = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Set the custom session as default
|
||||||
|
defaultSession = lib.mkForce "gnome";
|
||||||
|
|
||||||
autoLogin = {
|
autoLogin = {
|
||||||
enable = true;
|
enable = true;
|
||||||
user = config.hostSpec.username;
|
user = config.hostSpec.username;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# Configure keyboard layout for Wayland
|
||||||
|
services.xserver = {
|
||||||
|
enable = false;
|
||||||
|
xkb = {
|
||||||
|
layout = "us";
|
||||||
|
variant = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
#INFO: Fix for autoLogin
|
#INFO: Fix for autoLogin
|
||||||
systemd.services."getty@tty1".enable = false;
|
systemd.services."getty@tty1".enable = false;
|
||||||
systemd.services."autovt@tty1".enable = false;
|
systemd.services."autovt@tty1".enable = false;
|
||||||
|
@ -53,7 +63,6 @@
|
||||||
];
|
];
|
||||||
|
|
||||||
## Exclusions ##
|
## Exclusions ##
|
||||||
services.xserver.excludePackages = [ pkgs.xterm ];
|
|
||||||
environment.gnome.excludePackages = (
|
environment.gnome.excludePackages = (
|
||||||
with pkgs;
|
with pkgs;
|
||||||
[
|
[
|
||||||
|
|
44
hosts/global/common/waydroid.nix
Normal file
44
hosts/global/common/waydroid.nix
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
{ pkgs, ... }:
|
||||||
|
let
|
||||||
|
# TODO: automate with monitors.nix when i fix that up again
|
||||||
|
width = 1064;
|
||||||
|
height = 1904;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
# Enable Waydroid
|
||||||
|
virtualisation.waydroid.enable = true;
|
||||||
|
|
||||||
|
# Required packages
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
waydroid
|
||||||
|
waydroid-helper
|
||||||
|
wl-clipboard # Clipboard sharing
|
||||||
|
|
||||||
|
# Waydroid setup script; run once to initialize
|
||||||
|
(pkgs.writeShellScriptBin "waydroid-setup" ''
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Initializing WayDroid with GApps support..."
|
||||||
|
sudo waydroid init -s GAPPS -f
|
||||||
|
|
||||||
|
echo "Setting default WayDroid properties..."
|
||||||
|
sudo mkdir -p /var/lib/waydroid
|
||||||
|
|
||||||
|
# Create or update waydroid_base.prop
|
||||||
|
echo "persist.waydroid.multi_windows=true" | sudo tee /var/lib/waydroid/waydroid_base.prop
|
||||||
|
echo "persist.waydroid.width=${toString width}" | sudo tee -a /var/lib/waydroid/waydroid_base.prop
|
||||||
|
echo "persist.waydroid.height=${toString height}" | sudo tee -a /var/lib/waydroid/waydroid_base.prop
|
||||||
|
echo "sys.use_memfd=true" | sudo tee -a /var/lib/waydroid/waydroid_base.prop
|
||||||
|
|
||||||
|
echo "Setup complete!"
|
||||||
|
echo ""
|
||||||
|
echo "To start WayDroid:"
|
||||||
|
echo " 1. sudo systemctl start waydroid-container"
|
||||||
|
echo " 2. waydroid session start"
|
||||||
|
echo " 3. waydroid show-full-ui"
|
||||||
|
echo ""
|
||||||
|
echo "To install APKs: waydroid app install /path/to/app.apk"
|
||||||
|
'')
|
||||||
|
];
|
||||||
|
}
|
43
hosts/x86/cloud/config/backups.nix
Normal file
43
hosts/x86/cloud/config/backups.nix
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.backup = {
|
||||||
|
enable = true;
|
||||||
|
notificationUrl = lib.custom.mkAppriseUrl config.secretsSpec.users.admin.smtp "relay@ryot.foo";
|
||||||
|
enableChainTimer = true;
|
||||||
|
chainSchedule = "*-*-* 03:00:00";
|
||||||
|
|
||||||
|
# Maintenance jobs run first
|
||||||
|
maintenanceJobs = [
|
||||||
|
{
|
||||||
|
name = "snapraid";
|
||||||
|
title = "SnapRAID";
|
||||||
|
service = "snapraid-aio.service";
|
||||||
|
logPattern = "SnapRAID-*.out";
|
||||||
|
logPath = "/var/log/snapraid";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
# Backup jobs run after maintenance
|
||||||
|
jobs = [
|
||||||
|
{
|
||||||
|
name = "forgejo";
|
||||||
|
title = "Forgejo";
|
||||||
|
repo = "/pool/Backups/forgejo";
|
||||||
|
sourcePath = "/pool/forgejo";
|
||||||
|
verbose = true;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
name = "docker-storage";
|
||||||
|
title = "Docker Storage";
|
||||||
|
repo = "/pool/Backups/DockerStorage";
|
||||||
|
sourcePath = "/mnt/drive1/DockerStorage";
|
||||||
|
verbose = true;
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,207 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
# Common repositories
|
|
||||||
dockerStorageRepo = "/pool/Backups/DockerStorage";
|
|
||||||
forgejoRepo = "/pool/Backups/forgejo";
|
|
||||||
|
|
||||||
# Shared environment setup
|
|
||||||
borgCommonSettings = ''
|
|
||||||
# Don't use cache to avoid issues with concurrent backups
|
|
||||||
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
|
||||||
export BORG_NON_INTERACTIVE=yes
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Common packages needed for backups
|
|
||||||
commonBorgPath = with pkgs; [
|
|
||||||
borgbackup
|
|
||||||
coreutils
|
|
||||||
apprise
|
|
||||||
gnugrep
|
|
||||||
hostname
|
|
||||||
util-linux
|
|
||||||
gawk
|
|
||||||
];
|
|
||||||
|
|
||||||
# Repository initialization
|
|
||||||
initRepo = repo: ''
|
|
||||||
if [ ! -d "${repo}" ]; then
|
|
||||||
mkdir -p "${repo}"
|
|
||||||
${pkgs.borgbackup}/bin/borg init --encryption=none "${repo}"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Notification system
|
|
||||||
apprise-url = config.secretsSpec.users.admin.smtp.notifyUrl;
|
|
||||||
sendNotification = title: message: ''
|
|
||||||
${pkgs.apprise}/bin/apprise -t "${title}" -b "${message}" "${apprise-url}" || true
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Statistics generation
|
|
||||||
extractBorgStats = logFile: repoPath: ''
|
|
||||||
{
|
|
||||||
echo -e "\n==== BACKUP SUMMARY ====\n"
|
|
||||||
grep -A10 "Archive name:" ${logFile} || echo "No archive stats found"
|
|
||||||
echo -e "\n=== Compression ===\n"
|
|
||||||
grep "Compressed size:" ${logFile} || echo "No compression stats found"
|
|
||||||
echo -e "\n=== Duration ===\n"
|
|
||||||
grep "Duration:" ${logFile} || echo "No duration stats found"
|
|
||||||
grep "Throughput:" ${logFile} || echo "No throughput stats found"
|
|
||||||
echo -e "\n=== Repository ===\n"
|
|
||||||
${pkgs.borgbackup}/bin/borg info ${repoPath} --last 1 2>/dev/null || echo "Could not get repository info"
|
|
||||||
echo -e "\n=== Storage Space ===\n"
|
|
||||||
df -h ${repoPath} | grep -v "Filesystem" || echo "Could not get storage info"
|
|
||||||
} > ${logFile}.stats
|
|
||||||
STATS=$(cat ${logFile}.stats || echo "No stats available")
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Unified backup service generator with optional features
|
|
||||||
mkBorgBackupService =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
repo,
|
|
||||||
sourcePath,
|
|
||||||
keepDaily,
|
|
||||||
keepWeekly,
|
|
||||||
keepMonthly,
|
|
||||||
schedule ? null,
|
|
||||||
enableNotifications ? true,
|
|
||||||
verbose ? false,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
maybeCreateTimer = lib.optionalAttrs (schedule != null) {
|
|
||||||
timers."backup-${name}" = {
|
|
||||||
description = "Timer for ${title} Backup";
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = schedule;
|
|
||||||
Persistent = true;
|
|
||||||
RandomizedDelaySec = "5min";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
logPrefix = if verbose then "set -x;" else "";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services."backup-${name}" = {
|
|
||||||
description = "Backup ${title} with Borg";
|
|
||||||
inherit (commonServiceConfig) path serviceConfig;
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
${borgCommonSettings}
|
|
||||||
${logPrefix} # Add verbose logging if enabled
|
|
||||||
|
|
||||||
LOG_FILE="/tmp/borg-${name}-backup-$(date +%Y%m%d-%H%M%S).log"
|
|
||||||
${initRepo repo}
|
|
||||||
|
|
||||||
echo "Starting ${title} backup at $(date)" > $LOG_FILE
|
|
||||||
ARCHIVE_NAME="${name}-$(date +%Y-%m-%d_%H%M%S)"
|
|
||||||
START_TIME=$(date +%s)
|
|
||||||
|
|
||||||
# Add verbose output redirection if enabled
|
|
||||||
${if verbose then "exec 3>&1 4>&2" else ""}
|
|
||||||
${pkgs.borgbackup}/bin/borg create \
|
|
||||||
--stats \
|
|
||||||
--compression zstd,15 \
|
|
||||||
--exclude '*.tmp' \
|
|
||||||
--exclude '*/tmp/*' \
|
|
||||||
${repo}::$ARCHIVE_NAME \
|
|
||||||
${sourcePath} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
|
||||||
|
|
||||||
BACKUP_STATUS=$?
|
|
||||||
END_TIME=$(date +%s)
|
|
||||||
DURATION=$((END_TIME - START_TIME))
|
|
||||||
echo "Total time: $DURATION seconds ($(date -d@$DURATION -u +%H:%M:%S))" >> $LOG_FILE
|
|
||||||
|
|
||||||
${extractBorgStats "$LOG_FILE" "${repo}"}
|
|
||||||
|
|
||||||
echo -e "\nPruning old backups..." >> $LOG_FILE
|
|
||||||
${pkgs.borgbackup}/bin/borg prune \
|
|
||||||
--keep-daily ${toString keepDaily} \
|
|
||||||
--keep-weekly ${toString keepWeekly} \
|
|
||||||
--keep-monthly ${toString keepMonthly} \
|
|
||||||
${repo} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
|
||||||
|
|
||||||
PRUNE_STATUS=$?
|
|
||||||
|
|
||||||
echo -e "\nRemaining archives after pruning:" >> $LOG_FILE
|
|
||||||
${pkgs.borgbackup}/bin/borg list ${repo} >> $LOG_FILE 2>&1 || true
|
|
||||||
|
|
||||||
${
|
|
||||||
if enableNotifications then
|
|
||||||
''
|
|
||||||
if [ $BACKUP_STATUS -eq 0 ] && [ $PRUNE_STATUS -eq 0 ]; then
|
|
||||||
${sendNotification "✅ ${title} Backup Complete" "${title} backup completed successfully on $(hostname) at $(date)\nDuration: $(date -d@$DURATION -u +%H:%M:%S)\n\n$STATS"}
|
|
||||||
else
|
|
||||||
${sendNotification "❌ ${title} Backup Failed" "${title} backup failed on $(hostname) at $(date)\n\nBackup Status: $BACKUP_STATUS\nPrune Status: $PRUNE_STATUS\n\nPartial Stats:\n$STATS\n\nSee $LOG_FILE for details"}
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
else
|
|
||||||
"echo 'Notifications disabled' >> $LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
rm -f $LOG_FILE.stats
|
|
||||||
exit $BACKUP_STATUS
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
// maybeCreateTimer;
|
|
||||||
|
|
||||||
# Common service configuration
|
|
||||||
commonServiceConfig = {
|
|
||||||
path = commonBorgPath;
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
IOSchedulingClass = "idle";
|
|
||||||
CPUSchedulingPolicy = "idle";
|
|
||||||
Nice = 19;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
borgbackup
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd = lib.mkMerge [
|
|
||||||
(mkBorgBackupService {
|
|
||||||
name = "docker-storage";
|
|
||||||
title = "Docker Storage";
|
|
||||||
repo = dockerStorageRepo;
|
|
||||||
sourcePath = "/mnt/drive1/DockerStorage";
|
|
||||||
# INFO: This shit confusing but basically
|
|
||||||
# keeps the last 7 days,
|
|
||||||
# then keeps AT LEAST ONE for last 4 weeks
|
|
||||||
# and finally AT LEAST ONE for the last 3 months
|
|
||||||
keepDaily = 7;
|
|
||||||
keepWeekly = 4;
|
|
||||||
keepMonthly = 3;
|
|
||||||
# No schedule = no timer created
|
|
||||||
# schedule = "*-*-* 03:00:00";
|
|
||||||
enableNotifications = false;
|
|
||||||
verbose = true;
|
|
||||||
})
|
|
||||||
|
|
||||||
(mkBorgBackupService {
|
|
||||||
name = "forgejo";
|
|
||||||
title = "Forgejo";
|
|
||||||
repo = forgejoRepo;
|
|
||||||
sourcePath = "/pool/forgejo";
|
|
||||||
keepDaily = 7;
|
|
||||||
keepWeekly = 4;
|
|
||||||
keepMonthly = 3;
|
|
||||||
# schedule = "*-*-* 03:00:00";
|
|
||||||
enableNotifications = false;
|
|
||||||
verbose = true;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,197 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
# Shared configuration
|
|
||||||
logDir = "/var/log/backups";
|
|
||||||
backupServices = [
|
|
||||||
{
|
|
||||||
name = "forgejo";
|
|
||||||
title = "Forgejo";
|
|
||||||
service = "backup-forgejo.service";
|
|
||||||
logPattern = "borg-forgejo-backup-*.log";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "docker_storage";
|
|
||||||
title = "Docker Storage";
|
|
||||||
service = "backup-docker-storage.service";
|
|
||||||
logPattern = "borg-docker-storage-backup-*.log";
|
|
||||||
}
|
|
||||||
{
|
|
||||||
name = "snapraid";
|
|
||||||
title = "SnapRAID";
|
|
||||||
service = "snapraid-aio.service";
|
|
||||||
logPattern = "SnapRAID-*.out";
|
|
||||||
logPath = "/var/log/snapraid";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
users = config.secretsSpec.users;
|
|
||||||
notify =
|
|
||||||
title: message: logFile:
|
|
||||||
let
|
|
||||||
attachArg = if logFile == "" then "" else "--attach \"file://${logFile}\"";
|
|
||||||
appriseUrl = lib.custom.mkAppriseUrl users.admin.smtp "relay@ryot.foo";
|
|
||||||
in
|
|
||||||
''
|
|
||||||
${pkgs.apprise}/bin/apprise -vv -i "markdown" -t "${title}" \
|
|
||||||
-b "${message}" \
|
|
||||||
${attachArg} \
|
|
||||||
"${appriseUrl}" || true
|
|
||||||
'';
|
|
||||||
|
|
||||||
findLatestLog = pattern: path: ''
|
|
||||||
find "${path}" -name "${pattern}" -type f -printf "%T@ %p\\n" 2>/dev/null \
|
|
||||||
| sort -nr | head -1 | cut -d' ' -f2
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Generate safe variable name (replace hyphens with underscores)
|
|
||||||
safeName = name: lib.replaceStrings [ "-" ] [ "_" ] name;
|
|
||||||
|
|
||||||
# Generate status variable references
|
|
||||||
statusVarName = name: "STATUS_${safeName name}";
|
|
||||||
|
|
||||||
# Common script utilities
|
|
||||||
scriptPrelude = ''
|
|
||||||
set -uo pipefail
|
|
||||||
LOG_FILE="${logDir}/backup-chain-$(date +%Y%m%d-%H%M%S).log"
|
|
||||||
mkdir -p "${logDir}"
|
|
||||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "[$(date "+%Y-%m-%d %H:%M:%S")] $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Initialize all status variables
|
|
||||||
${lib.concatMapStringsSep "\n" (s: "${statusVarName s.name}=1") backupServices}
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Service runner template
|
|
||||||
runService =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
service,
|
|
||||||
logPattern,
|
|
||||||
logPath ? "/tmp",
|
|
||||||
}:
|
|
||||||
''
|
|
||||||
log "Starting ${title} maintenance..."
|
|
||||||
systemctl start ${service} || true
|
|
||||||
${statusVarName name}=$?
|
|
||||||
log "${title} completed with status $${statusVarName name}"
|
|
||||||
|
|
||||||
SERVICE_LOG=$(${findLatestLog logPattern logPath})
|
|
||||||
if [ -n "$SERVICE_LOG" ]; then
|
|
||||||
log "Appending ${title} log: $SERVICE_LOG"
|
|
||||||
echo -e "\n\n===== ${title} LOG ($(basename "$SERVICE_LOG")) =====\n" >> "$LOG_FILE"
|
|
||||||
cat "$SERVICE_LOG" >> "$LOG_FILE"
|
|
||||||
|
|
||||||
# Add SnapRAID-specific summary
|
|
||||||
if [ "${name}" = "snapraid" ]; then
|
|
||||||
echo -e "\n=== SnapRAID Summary ===" >> "$LOG_FILE"
|
|
||||||
grep -E '(Scrub|Sync|Diff|smart)' "$SERVICE_LOG" | tail -n 10 >> "$LOG_FILE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Build the service execution script
|
|
||||||
serviceExecution = lib.concatMapStrings runService backupServices;
|
|
||||||
|
|
||||||
# Generate status summary lines
|
|
||||||
statusSummaryLines = lib.concatMapStringsSep "\n" (
|
|
||||||
s:
|
|
||||||
let
|
|
||||||
varName = statusVarName s.name;
|
|
||||||
in
|
|
||||||
"- **${s.title}:** \$([ \$${varName} -eq 0 ] && echo '✅ Success' || echo '❌ Failed') (Exit: \$${varName})"
|
|
||||||
) backupServices;
|
|
||||||
|
|
||||||
# Notification logic with cleaner formatting
|
|
||||||
notificationLogic =
|
|
||||||
let
|
|
||||||
statusVars = map (s: statusVarName s.name) backupServices;
|
|
||||||
statusChecks = lib.concatMapStringsSep "\n" (var: "[ \$${var} -eq 0 ] && ") statusVars;
|
|
||||||
in
|
|
||||||
''
|
|
||||||
# Calculate overall status
|
|
||||||
OVERALL_STATUS=0
|
|
||||||
${lib.concatMapStringsSep "\n" (var: "if [ \$${var} -ne 0 ]; then OVERALL_STATUS=1; fi") statusVars}
|
|
||||||
|
|
||||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
|
||||||
HOSTNAME=$(hostname)
|
|
||||||
|
|
||||||
SUMMARY=$(cat << EOF
|
|
||||||
# Backup Chain Complete
|
|
||||||
|
|
||||||
**Host:** $HOSTNAME
|
|
||||||
**Timestamp:** $TIMESTAMP
|
|
||||||
**Overall Status:** $([ $OVERALL_STATUS -eq 0 ] && echo '✅ Success' || echo '⚠️ Failure')
|
|
||||||
|
|
||||||
## Service Status:
|
|
||||||
${statusSummaryLines}
|
|
||||||
|
|
||||||
**Log Path:** $LOG_FILE
|
|
||||||
EOF)
|
|
||||||
|
|
||||||
if [ $OVERALL_STATUS -eq 0 ]; then
|
|
||||||
${notify "✅ Backup Success" "$SUMMARY" "$LOG_FILE"}
|
|
||||||
else
|
|
||||||
${notify "⚠️ Backup Issues" "$SUMMARY" "$LOG_FILE"}
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit $OVERALL_STATUS
|
|
||||||
'';
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = lib.custom.scanPaths ./.;
|
|
||||||
|
|
||||||
systemd.services.backup-chain = {
|
|
||||||
description = "Orchestrated Backup Chain";
|
|
||||||
path = with pkgs; [
|
|
||||||
apprise
|
|
||||||
coreutils
|
|
||||||
findutils
|
|
||||||
gawk
|
|
||||||
gnugrep
|
|
||||||
hostname
|
|
||||||
systemd
|
|
||||||
util-linux
|
|
||||||
];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
Nice = 19;
|
|
||||||
IOSchedulingClass = "idle";
|
|
||||||
CPUSchedulingPolicy = "idle";
|
|
||||||
};
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
${scriptPrelude}
|
|
||||||
log "Initializing backup chain on $(hostname)"
|
|
||||||
|
|
||||||
${serviceExecution}
|
|
||||||
|
|
||||||
log "Finalizing backup chain"
|
|
||||||
${notificationLogic}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.timers.backup-chain = {
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "*-*-* 03:00:00";
|
|
||||||
Persistent = true;
|
|
||||||
RandomizedDelaySec = "5min";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [ pkgs.apprise ];
|
|
||||||
systemd.tmpfiles.rules = [ "d ${logDir} 0755 root root -" ];
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
{
|
|
||||||
pkgs,
|
|
||||||
inputs,
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
modulesPath,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
{
|
|
||||||
imports = lib.flatten [
|
|
||||||
(modulesPath + "/installer/scan/not-detected.nix")
|
|
||||||
];
|
|
||||||
|
|
||||||
## Boot ##
|
|
||||||
boot = {
|
|
||||||
loader = {
|
|
||||||
systemd-boot = {
|
|
||||||
enable = true;
|
|
||||||
# When using plymouth, initrd can expand by a lot each time, so limit how many we keep around
|
|
||||||
configurationLimit = lib.mkDefault 10;
|
|
||||||
};
|
|
||||||
efi.canTouchEfiVariables = true;
|
|
||||||
timeout = 3;
|
|
||||||
};
|
|
||||||
|
|
||||||
# Use the cachyos kernel for better performance
|
|
||||||
kernelPackages = pkgs.linuxPackages_cachyos;
|
|
||||||
|
|
||||||
initrd = {
|
|
||||||
systemd.enable = true;
|
|
||||||
verbose = false;
|
|
||||||
availableKernelModules = [
|
|
||||||
"nvme"
|
|
||||||
"xhci_pci"
|
|
||||||
"ahci"
|
|
||||||
"usb_storage"
|
|
||||||
"usbhid"
|
|
||||||
"sd_mod"
|
|
||||||
];
|
|
||||||
kernelModules = [ ];
|
|
||||||
};
|
|
||||||
kernelModules = [
|
|
||||||
"kvm-amd"
|
|
||||||
"amdgpu"
|
|
||||||
];
|
|
||||||
extraModulePackages = [ ];
|
|
||||||
};
|
|
||||||
|
|
||||||
# For less permission issues with SSHFS
|
|
||||||
programs.fuse.userAllowOther = true;
|
|
||||||
|
|
||||||
# FIXME: Fix on first boot
|
|
||||||
# fileSystems = {
|
|
||||||
# "/" = {
|
|
||||||
# device = "/dev/disk/by-uuid/d38c182c-6f05-4bf3-8a45-5532c10fd342";
|
|
||||||
# fsType = "ext4";
|
|
||||||
# };
|
|
||||||
|
|
||||||
# "/boot" = {
|
|
||||||
# device = "/dev/disk/by-uuid/5B39-A7CB";
|
|
||||||
# fsType = "vfat";
|
|
||||||
# options = [
|
|
||||||
# "fmask=0077"
|
|
||||||
# "dmask=0077"
|
|
||||||
# ];
|
|
||||||
# };
|
|
||||||
# };
|
|
||||||
|
|
||||||
# FIXME: Fix on first boot
|
|
||||||
# swapDevices = [ { device = "/dev/disk/by-uuid/6586847d-eba9-4317-9077-98ae9b2812c9"; } ];
|
|
||||||
|
|
||||||
time.hardwareClockInLocalTime = true; # Fixes windows dual-boot time issues
|
|
||||||
|
|
||||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
|
||||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
|
||||||
# still possible to use this option, but it's recommended to use it in conjunction
|
|
||||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
|
||||||
# networking.useDHCP = lib.mkDefault true;
|
|
||||||
# networking.interfaces.enp5s0.useDHCP = lib.mkDefault true;
|
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
|
||||||
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableAllFirmware;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
# STUFF ABOUT CHAOTIC NIX CACHE
|
|
||||||
# nix eval 'github:chaotic-cx/nyx/nyxpkgs-unstable#linuxPackages_cachyos.kernel.outPath'
|
|
||||||
# nix eval 'chaotic#linuxPackages_cachyos.kernel.outPath'
|
|
||||||
# nix eval '/pool/git/Nix/dot.nix#nixosConfigurations.rune.config.boot.kernelPackages.kernel.outPath'
|
|
||||||
# curl -L 'https://chaotic-nyx.cachix.org/{{HASH}}.narinfo'
|
|
||||||
# sudo nixos-rebuild switch --flake ./git/Nix/dot.nix/. --option 'extra-substituters' 'https://chaotic-nyx.cachix.org/' --option extra-trusted-public-keys "chaotic-nyx.cachix.org-1:HfnXSw4pj95iI/n17rIDy40agHj12WfF+Gqk6SonIT8="
|
|
|
@ -1,64 +1,90 @@
|
||||||
# FIXME: FIX to hardware.fix once out of VM, this is TEMP vm hardware config
|
|
||||||
{
|
{
|
||||||
|
pkgs,
|
||||||
|
inputs,
|
||||||
config,
|
config,
|
||||||
lib,
|
lib,
|
||||||
pkgs,
|
|
||||||
modulesPath,
|
modulesPath,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
|
||||||
username = config.hostSpec.username;
|
|
||||||
in
|
|
||||||
{
|
{
|
||||||
imports = lib.flatten [
|
imports = lib.flatten [
|
||||||
(modulesPath + "/profiles/qemu-guest.nix")
|
(modulesPath + "/installer/scan/not-detected.nix")
|
||||||
];
|
];
|
||||||
|
|
||||||
## Boot ##
|
## Boot ##
|
||||||
boot = {
|
boot = {
|
||||||
loader = {
|
loader = {
|
||||||
grub = {
|
systemd-boot = {
|
||||||
enable = true;
|
enable = true;
|
||||||
device = "/dev/vda";
|
# When using plymouth, initrd can expand by a lot each time, so limit how many we keep around
|
||||||
useOSProber = true;
|
configurationLimit = lib.mkDefault 10;
|
||||||
};
|
};
|
||||||
efi.canTouchEfiVariables = true;
|
efi.canTouchEfiVariables = true;
|
||||||
timeout = 3;
|
timeout = 3;
|
||||||
};
|
};
|
||||||
|
|
||||||
# use latest kernel
|
# Use the cachyos kernel for better performance
|
||||||
kernelPackages = pkgs.linuxPackages_latest;
|
kernelPackages = pkgs.linuxPackages_cachyos;
|
||||||
|
|
||||||
initrd = {
|
initrd = {
|
||||||
availableKernelModules = [
|
|
||||||
"ahci"
|
|
||||||
"xhci_pci"
|
|
||||||
"virtio_pci"
|
|
||||||
"sr_mod"
|
|
||||||
"virtio_blk"
|
|
||||||
];
|
|
||||||
systemd.enable = true;
|
systemd.enable = true;
|
||||||
verbose = false;
|
verbose = false;
|
||||||
|
availableKernelModules = [
|
||||||
|
"nvme"
|
||||||
|
"xhci_pci"
|
||||||
|
"ahci"
|
||||||
|
"usb_storage"
|
||||||
|
"usbhid"
|
||||||
|
"sd_mod"
|
||||||
|
];
|
||||||
|
kernelModules = [ ];
|
||||||
};
|
};
|
||||||
kernelModules = [ "kvm-amd" ];
|
kernelModules = [
|
||||||
|
"kvm-amd"
|
||||||
|
"amdgpu"
|
||||||
|
];
|
||||||
extraModulePackages = [ ];
|
extraModulePackages = [ ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
# For less permission issues with SSHFS
|
||||||
|
programs.fuse.userAllowOther = true;
|
||||||
|
|
||||||
fileSystems."/" = {
|
fileSystems."/" = {
|
||||||
device = "/dev/disk/by-uuid/5f1ad3a9-18ce-42ab-83ea-b67bccaa6972";
|
device = "/dev/disk/by-uuid/a19362ba-329b-4688-be05-c180f4212d97";
|
||||||
fsType = "ext4";
|
fsType = "ext4";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fileSystems."/boot" = {
|
||||||
|
device = "/dev/disk/by-uuid/8E6A-D6AE";
|
||||||
|
fsType = "vfat";
|
||||||
|
options = [
|
||||||
|
"fmask=0077"
|
||||||
|
"dmask=0077"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
swapDevices = [
|
swapDevices = [
|
||||||
{ device = "/dev/disk/by-uuid/e3fc8d25-31a5-48c1-8c81-c6c237f671bb"; }
|
{ device = "/dev/disk/by-uuid/2c3c3142-93c9-4be7-b9c1-5e9bc45d9fff"; }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
time.hardwareClockInLocalTime = true; # Fixes windows dual-boot time issues
|
||||||
|
|
||||||
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
# Enables DHCP on each ethernet and wireless interface. In case of scripted networking
|
||||||
# (the default) this is the recommended approach. When using systemd-networkd it's
|
# (the default) this is the recommended approach. When using systemd-networkd it's
|
||||||
# still possible to use this option, but it's recommended to use it in conjunction
|
# still possible to use this option, but it's recommended to use it in conjunction
|
||||||
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
# with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
|
||||||
# networking.useDHCP = lib.mkDefault true;
|
# networking.useDHCP = lib.mkDefault true;
|
||||||
# networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
|
# networking.interfaces.enp5s0.useDHCP = lib.mkDefault true;
|
||||||
|
|
||||||
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
|
||||||
|
hardware.cpu.amd.updateMicrocode = lib.mkDefault config.hardware.enableAllFirmware;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# STUFF ABOUT CHAOTIC NIX CACHE
|
||||||
|
# nix eval 'github:chaotic-cx/nyx/nyxpkgs-unstable#linuxPackages_cachyos.kernel.outPath'
|
||||||
|
# nix eval 'chaotic#linuxPackages_cachyos.kernel.outPath'
|
||||||
|
# nix eval '/pool/git/Nix/dot.nix#nixosConfigurations.rune.config.boot.kernelPackages.kernel.outPath'
|
||||||
|
# curl -L 'https://chaotic-nyx.cachix.org/{{HASH}}.narinfo'
|
||||||
|
# sudo nixos-rebuild switch --flake ./git/Nix/dot.nix/. --option 'extra-substituters' 'https://chaotic-nyx.cachix.org/' --option extra-trusted-public-keys "chaotic-nyx.cachix.org-1:HfnXSw4pj95iI/n17rIDy40agHj12WfF+Gqk6SonIT8="
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
###############################################################
|
###############################################################
|
||||||
#
|
#
|
||||||
# Haze - Cesar's Desktop
|
# Haze - Cesar's Desktop
|
||||||
# NixOS running on Ryzen 7 ___ , Radeon RX 6950 XT, 32GB RAM
|
# NixOS running on Ryzen 5 7600x, Radeon RX 7600, 32GB RAM
|
||||||
#
|
#
|
||||||
###############################################################
|
###############################################################
|
||||||
|
|
||||||
|
|
|
@ -43,6 +43,7 @@ in
|
||||||
"hosts/global/common/nvtop.nix" # GPU monitor (not available in home-manager)
|
"hosts/global/common/nvtop.nix" # GPU monitor (not available in home-manager)
|
||||||
"hosts/global/common/plymouth.nix" # fancy boot screen
|
"hosts/global/common/plymouth.nix" # fancy boot screen
|
||||||
"hosts/global/common/solaar.nix" # Logitech Unifying Receiver support
|
"hosts/global/common/solaar.nix" # Logitech Unifying Receiver support
|
||||||
|
"hosts/global/common/waydroid.nix" # Android container
|
||||||
"hosts/global/common/vial.nix" # KB setup
|
"hosts/global/common/vial.nix" # KB setup
|
||||||
])
|
])
|
||||||
];
|
];
|
||||||
|
|
23
hosts/x86/sock/config/backups.nix
Normal file
23
hosts/x86/sock/config/backups.nix
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
|
||||||
|
{
|
||||||
|
services.backup = {
|
||||||
|
enable = true;
|
||||||
|
notificationUrl = lib.custom.mkAppriseUrl config.secretsSpec.users.admin.smtp "relay@ryot.foo";
|
||||||
|
enableChainTimer = true;
|
||||||
|
|
||||||
|
jobs = [
|
||||||
|
{
|
||||||
|
name = "ochre-storage";
|
||||||
|
title = "Ochre Storage";
|
||||||
|
repo = "/pool/Backups/OchreStorage";
|
||||||
|
sourcePath = "/OchreStorage";
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,193 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
# Common repositories
|
|
||||||
ochreStorageRepo = "/pool/Backups/OchreStorage";
|
|
||||||
|
|
||||||
# Shared environment setup
|
|
||||||
borgCommonSettings = ''
|
|
||||||
# Don't use cache to avoid issues with concurrent backups
|
|
||||||
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
|
||||||
export BORG_NON_INTERACTIVE=yes
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Common packages needed for backups
|
|
||||||
commonBorgPath = with pkgs; [
|
|
||||||
borgbackup
|
|
||||||
coreutils
|
|
||||||
apprise
|
|
||||||
gnugrep
|
|
||||||
hostname
|
|
||||||
util-linux
|
|
||||||
gawk
|
|
||||||
];
|
|
||||||
|
|
||||||
# Repository initialization
|
|
||||||
initRepo = repo: ''
|
|
||||||
if [ ! -d "${repo}" ]; then
|
|
||||||
mkdir -p "${repo}"
|
|
||||||
${pkgs.borgbackup}/bin/borg init --encryption=none "${repo}"
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Notification system
|
|
||||||
apprise-url = config.secretsSpec.users.admin.smtp.notifyUrl;
|
|
||||||
sendNotification = title: message: ''
|
|
||||||
${pkgs.apprise}/bin/apprise -t "${title}" -b "${message}" "${apprise-url}" || true
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Statistics generation
|
|
||||||
extractBorgStats = logFile: repoPath: ''
|
|
||||||
{
|
|
||||||
echo -e "\n==== BACKUP SUMMARY ====\n"
|
|
||||||
grep -A10 "Archive name:" ${logFile} || echo "No archive stats found"
|
|
||||||
echo -e "\n=== Compression ===\n"
|
|
||||||
grep "Compressed size:" ${logFile} || echo "No compression stats found"
|
|
||||||
echo -e "\n=== Duration ===\n"
|
|
||||||
grep "Duration:" ${logFile} || echo "No duration stats found"
|
|
||||||
grep "Throughput:" ${logFile} || echo "No throughput stats found"
|
|
||||||
echo -e "\n=== Repository ===\n"
|
|
||||||
${pkgs.borgbackup}/bin/borg info ${repoPath} --last 1 2>/dev/null || echo "Could not get repository info"
|
|
||||||
echo -e "\n=== Storage Space ===\n"
|
|
||||||
df -h ${repoPath} | grep -v "Filesystem" || echo "Could not get storage info"
|
|
||||||
} > ${logFile}.stats
|
|
||||||
STATS=$(cat ${logFile}.stats || echo "No stats available")
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Unified backup service generator with optional features
|
|
||||||
mkBorgBackupService =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
repo,
|
|
||||||
sourcePath,
|
|
||||||
keepDaily,
|
|
||||||
keepWeekly,
|
|
||||||
keepMonthly,
|
|
||||||
schedule ? null,
|
|
||||||
enableNotifications ? true,
|
|
||||||
verbose ? false,
|
|
||||||
}:
|
|
||||||
let
|
|
||||||
maybeCreateTimer = lib.optionalAttrs (schedule != null) {
|
|
||||||
timers."backup-${name}" = {
|
|
||||||
description = "Timer for ${title} Backup";
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = schedule;
|
|
||||||
Persistent = true;
|
|
||||||
RandomizedDelaySec = "5min";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
logPrefix = if verbose then "set -x;" else "";
|
|
||||||
in
|
|
||||||
{
|
|
||||||
services."backup-${name}" = {
|
|
||||||
description = "Backup ${title} with Borg";
|
|
||||||
inherit (commonServiceConfig) path serviceConfig;
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
${borgCommonSettings}
|
|
||||||
${logPrefix} # Add verbose logging if enabled
|
|
||||||
|
|
||||||
LOG_FILE="/tmp/borg-${name}-backup-$(date +%Y%m%d-%H%M%S).log"
|
|
||||||
${initRepo repo}
|
|
||||||
|
|
||||||
echo "Starting ${title} backup at $(date)" > $LOG_FILE
|
|
||||||
ARCHIVE_NAME="${name}-$(date +%Y-%m-%d_%H%M%S)"
|
|
||||||
START_TIME=$(date +%s)
|
|
||||||
|
|
||||||
# Add verbose output redirection if enabled
|
|
||||||
${if verbose then "exec 3>&1 4>&2" else ""}
|
|
||||||
${pkgs.borgbackup}/bin/borg create \
|
|
||||||
--stats \
|
|
||||||
--compression zstd,15 \
|
|
||||||
--exclude '*.tmp' \
|
|
||||||
--exclude '*/tmp/*' \
|
|
||||||
${repo}::$ARCHIVE_NAME \
|
|
||||||
${sourcePath} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
|
||||||
|
|
||||||
BACKUP_STATUS=$?
|
|
||||||
END_TIME=$(date +%s)
|
|
||||||
DURATION=$((END_TIME - START_TIME))
|
|
||||||
echo "Total time: $DURATION seconds ($(date -d@$DURATION -u +%H:%M:%S))" >> $LOG_FILE
|
|
||||||
|
|
||||||
${extractBorgStats "$LOG_FILE" "${repo}"}
|
|
||||||
|
|
||||||
echo -e "\nPruning old backups..." >> $LOG_FILE
|
|
||||||
${pkgs.borgbackup}/bin/borg prune \
|
|
||||||
--keep-daily ${toString keepDaily} \
|
|
||||||
--keep-weekly ${toString keepWeekly} \
|
|
||||||
--keep-monthly ${toString keepMonthly} \
|
|
||||||
${repo} >> $LOG_FILE 2>&1 ${if verbose then "| tee /dev/fd/3" else ""}
|
|
||||||
|
|
||||||
PRUNE_STATUS=$?
|
|
||||||
|
|
||||||
echo -e "\nRemaining archives after pruning:" >> $LOG_FILE
|
|
||||||
${pkgs.borgbackup}/bin/borg list ${repo} >> $LOG_FILE 2>&1 || true
|
|
||||||
|
|
||||||
${
|
|
||||||
if enableNotifications then
|
|
||||||
''
|
|
||||||
if [ $BACKUP_STATUS -eq 0 ] && [ $PRUNE_STATUS -eq 0 ]; then
|
|
||||||
${sendNotification "✅ ${title} Backup Complete" "${title} backup completed successfully on $(hostname) at $(date)\nDuration: $(date -d@$DURATION -u +%H:%M:%S)\n\n$STATS"}
|
|
||||||
else
|
|
||||||
${sendNotification "❌ ${title} Backup Failed" "${title} backup failed on $(hostname) at $(date)\n\nBackup Status: $BACKUP_STATUS\nPrune Status: $PRUNE_STATUS\n\nPartial Stats:\n$STATS\n\nSee $LOG_FILE for details"}
|
|
||||||
fi
|
|
||||||
''
|
|
||||||
else
|
|
||||||
"echo 'Notifications disabled' >> $LOG_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
rm -f $LOG_FILE.stats
|
|
||||||
exit $BACKUP_STATUS
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
// maybeCreateTimer;
|
|
||||||
|
|
||||||
# Common service configuration
|
|
||||||
commonServiceConfig = {
|
|
||||||
path = commonBorgPath;
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
IOSchedulingClass = "idle";
|
|
||||||
CPUSchedulingPolicy = "idle";
|
|
||||||
Nice = 19;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
environment.systemPackages = with pkgs; [
|
|
||||||
borgbackup
|
|
||||||
];
|
|
||||||
|
|
||||||
systemd = lib.mkMerge [
|
|
||||||
(mkBorgBackupService {
|
|
||||||
name = "ochre-storage";
|
|
||||||
title = "Ochre Storage";
|
|
||||||
repo = ochreStorageRepo;
|
|
||||||
sourcePath = "/OchreStorage";
|
|
||||||
# INFO: This shit confusing but basically
|
|
||||||
# keeps the last 7 days,
|
|
||||||
# then keeps AT LEAST ONE for last 4 weeks
|
|
||||||
# and finally AT LEAST ONE for the last 3 months
|
|
||||||
keepDaily = 7;
|
|
||||||
keepWeekly = 4;
|
|
||||||
keepMonthly = 3;
|
|
||||||
# No schedule = no timer created
|
|
||||||
# schedule = "*-*-* 03:00:00";
|
|
||||||
enableNotifications = false;
|
|
||||||
verbose = true;
|
|
||||||
})
|
|
||||||
];
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
{
|
|
||||||
config,
|
|
||||||
lib,
|
|
||||||
pkgs,
|
|
||||||
...
|
|
||||||
}:
|
|
||||||
|
|
||||||
let
|
|
||||||
# Shared configuration
|
|
||||||
logDir = "/var/log/backups";
|
|
||||||
backupServices = [
|
|
||||||
{
|
|
||||||
name = "ochre_storage";
|
|
||||||
title = "Ochre Storage";
|
|
||||||
service = "backup-ochre-storage.service";
|
|
||||||
logPattern = "borg-ochre-storage-backup-*.log";
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
# Helper functions
|
|
||||||
users = config.secretsSpec.users;
|
|
||||||
notify =
|
|
||||||
title: message: logFile:
|
|
||||||
let
|
|
||||||
attachArg = if logFile == "" then "" else "--attach \"file://${logFile}\"";
|
|
||||||
appriseUrl = lib.custom.mkAppriseUrl users.admin.smtp "relay@ryot.foo";
|
|
||||||
in
|
|
||||||
''
|
|
||||||
${pkgs.apprise}/bin/apprise -vv -i "markdown" -t "${title}" \
|
|
||||||
-b "${message}" \
|
|
||||||
${attachArg} \
|
|
||||||
"${appriseUrl}" || true
|
|
||||||
'';
|
|
||||||
|
|
||||||
findLatestLog = pattern: path: ''
|
|
||||||
find "${path}" -name "${pattern}" -type f -printf "%T@ %p\\n" 2>/dev/null \
|
|
||||||
| sort -nr | head -1 | cut -d' ' -f2
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Generate safe variable name (replace hyphens with underscores)
|
|
||||||
safeName = name: lib.replaceStrings [ "-" ] [ "_" ] name;
|
|
||||||
|
|
||||||
# Generate status variable references
|
|
||||||
statusVarName = name: "STATUS_${safeName name}";
|
|
||||||
|
|
||||||
# Common script utilities
|
|
||||||
scriptPrelude = ''
|
|
||||||
set -uo pipefail
|
|
||||||
LOG_FILE="${logDir}/backup-chain-$(date +%Y%m%d-%H%M%S).log"
|
|
||||||
mkdir -p "${logDir}"
|
|
||||||
exec > >(tee -a "$LOG_FILE") 2>&1
|
|
||||||
|
|
||||||
log() {
|
|
||||||
echo "[$(date "+%Y-%m-%d %H:%M:%S")] $1"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Initialize all status variables
|
|
||||||
${lib.concatMapStringsSep "\n" (s: "${statusVarName s.name}=1") backupServices}
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Service runner template
|
|
||||||
runService =
|
|
||||||
{
|
|
||||||
name,
|
|
||||||
title,
|
|
||||||
service,
|
|
||||||
logPattern,
|
|
||||||
logPath ? "/tmp",
|
|
||||||
}:
|
|
||||||
''
|
|
||||||
log "Starting ${title} maintenance..."
|
|
||||||
systemctl start ${service} || true
|
|
||||||
${statusVarName name}=$?
|
|
||||||
log "${title} completed with status $${statusVarName name}"
|
|
||||||
|
|
||||||
SERVICE_LOG=$(${findLatestLog logPattern logPath})
|
|
||||||
if [ -n "$SERVICE_LOG" ]; then
|
|
||||||
log "Appending ${title} log: $SERVICE_LOG"
|
|
||||||
echo -e "\n\n===== ${title} LOG ($(basename "$SERVICE_LOG")) =====\n" >> "$LOG_FILE"
|
|
||||||
cat "$SERVICE_LOG" >> "$LOG_FILE"
|
|
||||||
|
|
||||||
# Add SnapRAID-specific summary
|
|
||||||
if [ "${name}" = "snapraid" ]; then
|
|
||||||
echo -e "\n=== SnapRAID Summary ===" >> "$LOG_FILE"
|
|
||||||
grep -E '(Scrub|Sync|Diff|smart)' "$SERVICE_LOG" | tail -n 10 >> "$LOG_FILE"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
'';
|
|
||||||
|
|
||||||
# Build the service execution script
|
|
||||||
serviceExecution = lib.concatMapStrings runService backupServices;
|
|
||||||
|
|
||||||
# Generate status summary lines
|
|
||||||
statusSummaryLines = lib.concatMapStringsSep "\n" (
|
|
||||||
s:
|
|
||||||
let
|
|
||||||
varName = statusVarName s.name;
|
|
||||||
in
|
|
||||||
"- **${s.title}:** \$([ \$${varName} -eq 0 ] && echo '✅ Success' || echo '❌ Failed') (Exit: \$${varName})"
|
|
||||||
) backupServices;
|
|
||||||
|
|
||||||
# Notification logic with cleaner formatting
|
|
||||||
notificationLogic =
|
|
||||||
let
|
|
||||||
statusVars = map (s: statusVarName s.name) backupServices;
|
|
||||||
statusChecks = lib.concatMapStringsSep "\n" (var: "[ \$${var} -eq 0 ] && ") statusVars;
|
|
||||||
in
|
|
||||||
''
|
|
||||||
# Calculate overall status
|
|
||||||
OVERALL_STATUS=0
|
|
||||||
${lib.concatMapStringsSep "\n" (var: "if [ \$${var} -ne 0 ]; then OVERALL_STATUS=1; fi") statusVars}
|
|
||||||
|
|
||||||
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
|
|
||||||
HOSTNAME=$(hostname)
|
|
||||||
|
|
||||||
SUMMARY=$(cat << EOF
|
|
||||||
# Backup Chain Complete
|
|
||||||
|
|
||||||
**Host:** $HOSTNAME
|
|
||||||
**Timestamp:** $TIMESTAMP
|
|
||||||
**Overall Status:** $([ $OVERALL_STATUS -eq 0 ] && echo '✅ Success' || echo '⚠️ Failure')
|
|
||||||
|
|
||||||
## Service Status:
|
|
||||||
${statusSummaryLines}
|
|
||||||
|
|
||||||
**Log Path:** $LOG_FILE
|
|
||||||
EOF)
|
|
||||||
|
|
||||||
if [ $OVERALL_STATUS -eq 0 ]; then
|
|
||||||
${notify "✅ Backup Success" "$SUMMARY" "$LOG_FILE"}
|
|
||||||
else
|
|
||||||
${notify "⚠️ Backup Issues" "$SUMMARY" "$LOG_FILE"}
|
|
||||||
fi
|
|
||||||
|
|
||||||
exit $OVERALL_STATUS
|
|
||||||
'';
|
|
||||||
|
|
||||||
in
|
|
||||||
{
|
|
||||||
imports = lib.custom.scanPaths ./.;
|
|
||||||
|
|
||||||
systemd.services.backup-chain = {
|
|
||||||
description = "Orchestrated Backup Chain";
|
|
||||||
path = with pkgs; [
|
|
||||||
apprise
|
|
||||||
coreutils
|
|
||||||
findutils
|
|
||||||
gawk
|
|
||||||
gnugrep
|
|
||||||
hostname
|
|
||||||
systemd
|
|
||||||
util-linux
|
|
||||||
];
|
|
||||||
|
|
||||||
serviceConfig = {
|
|
||||||
Type = "oneshot";
|
|
||||||
Nice = 19;
|
|
||||||
IOSchedulingClass = "idle";
|
|
||||||
CPUSchedulingPolicy = "idle";
|
|
||||||
};
|
|
||||||
|
|
||||||
script = ''
|
|
||||||
${scriptPrelude}
|
|
||||||
log "Initializing backup chain on $(hostname)"
|
|
||||||
|
|
||||||
${serviceExecution}
|
|
||||||
|
|
||||||
log "Finalizing backup chain"
|
|
||||||
${notificationLogic}
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
|
|
||||||
systemd.timers.backup-chain = {
|
|
||||||
wantedBy = [ "timers.target" ];
|
|
||||||
timerConfig = {
|
|
||||||
OnCalendar = "*-*-* 03:00:00";
|
|
||||||
Persistent = true;
|
|
||||||
RandomizedDelaySec = "5min";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
environment.systemPackages = [ pkgs.apprise ];
|
|
||||||
systemd.tmpfiles.rules = [ "d ${logDir} 0755 root root -" ];
|
|
||||||
}
|
|
|
@ -34,4 +34,18 @@
|
||||||
to = if builtins.isAttrs smtp then recipient else smtp.user;
|
to = if builtins.isAttrs smtp then recipient else smtp.user;
|
||||||
in
|
in
|
||||||
"mailtos://_?user=${smtpUser}&pass=${smtpPass}&smtp=${smtpHost}&from=${smtpFrom}&to=${to}";
|
"mailtos://_?user=${smtpUser}&pass=${smtpPass}&smtp=${smtpHost}&from=${smtpFrom}&to=${to}";
|
||||||
|
|
||||||
|
# Get the primary monitor from a list of monitors
|
||||||
|
# Falls back to first monitor if no primary is set
|
||||||
|
getPrimaryMonitor =
|
||||||
|
monitors:
|
||||||
|
let
|
||||||
|
primaryMonitors = builtins.filter (m: m.primary or false) monitors;
|
||||||
|
in
|
||||||
|
if builtins.length primaryMonitors > 0 then
|
||||||
|
builtins.head primaryMonitors
|
||||||
|
else if builtins.length monitors > 0 then
|
||||||
|
builtins.head monitors
|
||||||
|
else
|
||||||
|
null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,10 +13,6 @@
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = false;
|
default = false;
|
||||||
};
|
};
|
||||||
noBar = lib.mkOption {
|
|
||||||
type = lib.types.bool;
|
|
||||||
default = false;
|
|
||||||
};
|
|
||||||
width = lib.mkOption {
|
width = lib.mkOption {
|
||||||
type = lib.types.int;
|
type = lib.types.int;
|
||||||
example = 1920;
|
example = 1920;
|
||||||
|
@ -44,22 +40,21 @@
|
||||||
transform = lib.mkOption {
|
transform = lib.mkOption {
|
||||||
type = lib.types.int;
|
type = lib.types.int;
|
||||||
default = 0;
|
default = 0;
|
||||||
|
description = "Screen orientation: 0 = landscape, 1 = portrait left, 2 = portrait right, 3 = landscape flipped";
|
||||||
};
|
};
|
||||||
enabled = lib.mkOption {
|
enabled = lib.mkOption {
|
||||||
type = lib.types.bool;
|
type = lib.types.bool;
|
||||||
default = true;
|
default = true;
|
||||||
};
|
};
|
||||||
workspace = lib.mkOption {
|
hdr = lib.mkOption {
|
||||||
type = lib.types.nullOr lib.types.str;
|
type = lib.types.bool;
|
||||||
description = "Defines a workspace that should persist on this monitor.";
|
default = false;
|
||||||
default = null;
|
|
||||||
};
|
};
|
||||||
vrr = lib.mkOption {
|
vrr = lib.mkOption {
|
||||||
type = lib.types.int;
|
type = lib.types.bool;
|
||||||
description = "Variable Refresh Rate aka Adaptive Sync aka AMD FreeSync.\nValues are oriented towards hyprland's vrr values which are:\n0 = off, 1 = on, 2 = fullscreen only\nhttps://wiki.hyprland.org/Configuring/Variables/#misc";
|
description = "Variable Refresh Rate aka Adaptive Sync aka AMD FreeSync.";
|
||||||
default = 0;
|
default = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
404
modules/nixos/backups.nix
Normal file
404
modules/nixos/backups.nix
Normal file
|
@ -0,0 +1,404 @@
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
lib,
|
||||||
|
pkgs,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
with lib;
|
||||||
|
let
|
||||||
|
cfg = config.services.backup;
|
||||||
|
|
||||||
|
# Helper functions
|
||||||
|
safeName = name: replaceStrings [ "-" ] [ "_" ] name;
|
||||||
|
statusVar = name: "STATUS_${safeName name}";
|
||||||
|
findLatestLog = pattern: path: ''
|
||||||
|
find "${path}" -name "${pattern}" -type f -printf "%T@ %p\\n" 2>/dev/null \
|
||||||
|
| sort -nr | head -1 | cut -d' ' -f2
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Borg service generator
|
||||||
|
mkBorgService =
|
||||||
|
job:
|
||||||
|
let
|
||||||
|
borgCommon = ''
|
||||||
|
export BORG_UNKNOWN_UNENCRYPTED_REPO_ACCESS_IS_OK=yes
|
||||||
|
export BORG_NON_INTERACTIVE=yes
|
||||||
|
'';
|
||||||
|
|
||||||
|
initRepo = ''
|
||||||
|
if [ ! -d "${job.repo}" ]; then
|
||||||
|
mkdir -p "${job.repo}"
|
||||||
|
${pkgs.borgbackup}/bin/borg init --encryption=none "${job.repo}"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
|
||||||
|
extractStats = ''
|
||||||
|
{
|
||||||
|
echo -e "\n==== BACKUP SUMMARY ====\n"
|
||||||
|
grep -A10 "Archive name:" $LOG_FILE || echo "No archive stats found"
|
||||||
|
echo -e "\n=== Compression ===\n"
|
||||||
|
grep "Compressed size:" $LOG_FILE || echo "No compression stats found"
|
||||||
|
echo -e "\n=== Duration ===\n"
|
||||||
|
grep "Duration:" $LOG_FILE || echo "No duration stats found"
|
||||||
|
grep "Throughput:" $LOG_FILE || echo "No throughput stats found"
|
||||||
|
echo -e "\n=== Repository ===\n"
|
||||||
|
${pkgs.borgbackup}/bin/borg info ${job.repo} --last 1 2>/dev/null || echo "Could not get repository info"
|
||||||
|
echo -e "\n=== Storage Space ===\n"
|
||||||
|
df -h ${job.repo} | grep -v "Filesystem" || echo "Could not get storage info"
|
||||||
|
} > $LOG_FILE.stats
|
||||||
|
STATS=$(cat $LOG_FILE.stats || echo "No stats available")
|
||||||
|
'';
|
||||||
|
|
||||||
|
excludeArgs = concatMapStringsSep " " (pattern: "--exclude '${pattern}'") job.excludePatterns;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services."backup-${job.name}" = {
|
||||||
|
description = "Backup ${job.title} with Borg";
|
||||||
|
path = with pkgs; [
|
||||||
|
borgbackup
|
||||||
|
coreutils
|
||||||
|
gnugrep
|
||||||
|
hostname
|
||||||
|
util-linux
|
||||||
|
gawk
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
IOSchedulingClass = "idle";
|
||||||
|
CPUSchedulingPolicy = "idle";
|
||||||
|
Nice = 19;
|
||||||
|
};
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
${borgCommon}
|
||||||
|
${optionalString job.verbose "set -x"}
|
||||||
|
|
||||||
|
LOG_FILE="/tmp/borg-${job.name}-backup-$(date +%Y%m%d-%H%M%S).log"
|
||||||
|
${initRepo}
|
||||||
|
|
||||||
|
echo "Starting ${job.title} backup at $(date)" > $LOG_FILE
|
||||||
|
START_TIME=$(date +%s)
|
||||||
|
|
||||||
|
${pkgs.borgbackup}/bin/borg create \
|
||||||
|
--stats \
|
||||||
|
--compression ${job.compression} \
|
||||||
|
${excludeArgs} \
|
||||||
|
${job.repo}::${job.name}-$(date +%Y-%m-%d_%H%M%S) \
|
||||||
|
${job.sourcePath} >> $LOG_FILE 2>&1
|
||||||
|
|
||||||
|
BACKUP_STATUS=$?
|
||||||
|
echo "Total time: $(($(date +%s) - START_TIME)) seconds" >> $LOG_FILE
|
||||||
|
${extractStats}
|
||||||
|
|
||||||
|
echo -e "\nPruning old backups..." >> $LOG_FILE
|
||||||
|
${pkgs.borgbackup}/bin/borg prune \
|
||||||
|
--keep-daily ${toString job.keepDaily} \
|
||||||
|
--keep-weekly ${toString job.keepWeekly} \
|
||||||
|
--keep-monthly ${toString job.keepMonthly} \
|
||||||
|
${job.repo} >> $LOG_FILE 2>&1
|
||||||
|
|
||||||
|
PRUNE_STATUS=$?
|
||||||
|
|
||||||
|
rm -f $LOG_FILE.stats
|
||||||
|
exit $BACKUP_STATUS
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// optionalAttrs (job.schedule != null) {
|
||||||
|
timers."backup-${job.name}" = {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = job.schedule;
|
||||||
|
Persistent = true;
|
||||||
|
RandomizedDelaySec = "5min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Orchestration service
|
||||||
|
mkChainService =
|
||||||
|
let
|
||||||
|
allJobs = cfg.jobs ++ cfg.maintenanceJobs;
|
||||||
|
initVars = concatMapStringsSep "\n" (j: "${statusVar j.name}=1") allJobs;
|
||||||
|
|
||||||
|
runJob = job: ''
|
||||||
|
# Determine log pattern
|
||||||
|
LOG_PATTERN="${if job.logPattern != "" then job.logPattern else "borg-${job.name}-backup-*.log"}"
|
||||||
|
|
||||||
|
log "Starting ${job.title}..."
|
||||||
|
|
||||||
|
systemctl start ${if job ? service then job.service else "backup-${job.name}"} || true
|
||||||
|
|
||||||
|
# Wait for service to complete and get its exit status
|
||||||
|
while systemctl is-active ${
|
||||||
|
if job ? service then job.service else "backup-${job.name}"
|
||||||
|
} >/dev/null 2>&1; do
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Get the actual service exit status
|
||||||
|
${statusVar job.name}=$(systemctl show ${
|
||||||
|
if job ? service then job.service else "backup-${job.name}"
|
||||||
|
} --property=ExecMainStatus --value)
|
||||||
|
log "${job.title} completed with status $${statusVar job.name}"
|
||||||
|
|
||||||
|
# Give logs time to be written
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
SERVICE_LOG=$(${findLatestLog "$LOG_PATTERN" "${job.logPath}"})
|
||||||
|
if [ -n "$SERVICE_LOG" ] && [ -r "$SERVICE_LOG" ]; then
|
||||||
|
log "Appending ${job.title} log: $SERVICE_LOG"
|
||||||
|
echo -e "\n\n===== ${job.title} LOG =====\n" >> "$LOG_FILE"
|
||||||
|
cat "$SERVICE_LOG" >> "$LOG_FILE" 2>/dev/null || echo "Could not read log file" >> "$LOG_FILE"
|
||||||
|
else
|
||||||
|
log "No readable log found for ${job.title} (pattern: $LOG_PATTERN in ${job.logPath})"
|
||||||
|
fi
|
||||||
|
'';
|
||||||
|
in
|
||||||
|
{
|
||||||
|
services.backup-chain = {
|
||||||
|
description = "Backup Orchestration Chain";
|
||||||
|
path = with pkgs; [
|
||||||
|
apprise
|
||||||
|
coreutils
|
||||||
|
findutils
|
||||||
|
gnugrep
|
||||||
|
hostname
|
||||||
|
systemd
|
||||||
|
util-linux
|
||||||
|
];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
Nice = 19;
|
||||||
|
IOSchedulingClass = "idle";
|
||||||
|
CPUSchedulingPolicy = "idle";
|
||||||
|
};
|
||||||
|
|
||||||
|
script = ''
|
||||||
|
set -uo pipefail
|
||||||
|
LOG_FILE="${cfg.logDir}/backup-chain-$(date +%Y%m%d-%H%M%S).log"
|
||||||
|
mkdir -p "${cfg.logDir}"
|
||||||
|
exec > >(tee -a "$LOG_FILE") 2>&1
|
||||||
|
|
||||||
|
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"; }
|
||||||
|
|
||||||
|
# Initialize status tracking
|
||||||
|
declare -A JOB_STATUS
|
||||||
|
${concatMapStrings (j: "JOB_STATUS['${j.name}']=1\n") allJobs}
|
||||||
|
|
||||||
|
log "Starting backup chain on $(hostname)"
|
||||||
|
|
||||||
|
${concatMapStrings (job: ''
|
||||||
|
log "Starting ${job.title}..."
|
||||||
|
systemctl start ${if job ? service then job.service else "backup-${job.name}"} || true
|
||||||
|
|
||||||
|
# Wait for completion
|
||||||
|
while systemctl is-active ${
|
||||||
|
if job ? service then job.service else "backup-${job.name}"
|
||||||
|
} >/dev/null 2>&1; do
|
||||||
|
sleep 5
|
||||||
|
done
|
||||||
|
|
||||||
|
# Capture exit status
|
||||||
|
JOB_STATUS['${job.name}']=$(systemctl show ${
|
||||||
|
if job ? service then job.service else "backup-${job.name}"
|
||||||
|
} --property=ExecMainStatus --value)
|
||||||
|
log "${job.title} completed with status ''${JOB_STATUS['${job.name}']}"
|
||||||
|
|
||||||
|
# Give logs time to be written
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
# Find and append logs
|
||||||
|
LOG_PATTERN="${if job.logPattern != "" then job.logPattern else "borg-${job.name}-backup-*.log"}"
|
||||||
|
SERVICE_LOG=$(${findLatestLog "$LOG_PATTERN" "${job.logPath}"})
|
||||||
|
if [ -n "$SERVICE_LOG" ] && [ -r "$SERVICE_LOG" ]; then
|
||||||
|
log "Appending ${job.title} log: $SERVICE_LOG"
|
||||||
|
echo -e "\n\n===== ${job.title} LOG =====\n" >> "$LOG_FILE"
|
||||||
|
cat "$SERVICE_LOG" >> "$LOG_FILE" 2>/dev/null || echo "Could not read log file" >> "$LOG_FILE"
|
||||||
|
else
|
||||||
|
log "No readable log found for ${job.title} (pattern: $LOG_PATTERN in ${job.logPath})"
|
||||||
|
fi
|
||||||
|
'') allJobs}
|
||||||
|
|
||||||
|
# Calculate overall status
|
||||||
|
OVERALL_STATUS=0
|
||||||
|
for status in "''${JOB_STATUS[@]}"; do
|
||||||
|
if [ "$status" -ne 0 ]; then
|
||||||
|
OVERALL_STATUS=1
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# Build summary
|
||||||
|
HOSTNAME=$(hostname)
|
||||||
|
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# Build job status lines
|
||||||
|
JOB_LINES=""
|
||||||
|
${concatMapStrings (job: ''
|
||||||
|
if [ ''${JOB_STATUS['${job.name}']} -eq 0 ]; then
|
||||||
|
STATUS_ICON="✅"
|
||||||
|
else
|
||||||
|
STATUS_ICON="❌"
|
||||||
|
fi
|
||||||
|
JOB_LINES="$JOB_LINES- **${job.title}:** $STATUS_ICON (Exit: ''${JOB_STATUS['${job.name}']})
|
||||||
|
"
|
||||||
|
'') allJobs}
|
||||||
|
|
||||||
|
# Create the final summary
|
||||||
|
SUMMARY="# Backup Chain Complete
|
||||||
|
|
||||||
|
**Host:** $HOSTNAME
|
||||||
|
**Timestamp:** $TIMESTAMP
|
||||||
|
**Overall Status:** $([ $OVERALL_STATUS -eq 0 ] && echo '✅ Success' || echo '⚠️ Failure')
|
||||||
|
|
||||||
|
## Job Status:
|
||||||
|
$JOB_LINES
|
||||||
|
**Log Path:** $LOG_FILE"
|
||||||
|
|
||||||
|
# Send notification
|
||||||
|
${pkgs.apprise}/bin/apprise -vv -i "markdown" \
|
||||||
|
-t "Backup $([ $OVERALL_STATUS -eq 0 ] && echo '✅' || echo '⚠️')" \
|
||||||
|
-b "$SUMMARY" \
|
||||||
|
--attach "file://$LOG_FILE" \
|
||||||
|
"${cfg.notificationUrl}" || true
|
||||||
|
|
||||||
|
exit $OVERALL_STATUS
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
timers.backup-chain = mkIf cfg.enableChainTimer {
|
||||||
|
wantedBy = [ "timers.target" ];
|
||||||
|
timerConfig = {
|
||||||
|
OnCalendar = cfg.chainSchedule;
|
||||||
|
Persistent = true;
|
||||||
|
RandomizedDelaySec = "5min";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
# Job types
|
||||||
|
borgJobType = types.submodule {
|
||||||
|
options = {
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Unique service name";
|
||||||
|
};
|
||||||
|
title = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Human-readable title";
|
||||||
|
};
|
||||||
|
repo = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Borg repository path";
|
||||||
|
};
|
||||||
|
sourcePath = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Path to back up";
|
||||||
|
};
|
||||||
|
schedule = mkOption {
|
||||||
|
type = types.nullOr types.str;
|
||||||
|
default = null;
|
||||||
|
description = "Timer schedule for standalone execution";
|
||||||
|
};
|
||||||
|
keepDaily = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 7;
|
||||||
|
};
|
||||||
|
keepWeekly = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 4;
|
||||||
|
};
|
||||||
|
keepMonthly = mkOption {
|
||||||
|
type = types.int;
|
||||||
|
default = 3;
|
||||||
|
};
|
||||||
|
verbose = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
compression = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "zstd,15";
|
||||||
|
};
|
||||||
|
excludePatterns = mkOption {
|
||||||
|
type = types.listOf types.str;
|
||||||
|
default = [
|
||||||
|
"*.tmp"
|
||||||
|
"*/tmp/*"
|
||||||
|
];
|
||||||
|
};
|
||||||
|
logPattern = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "";
|
||||||
|
description = "Log pattern for chain to find";
|
||||||
|
};
|
||||||
|
logPath = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/tmp";
|
||||||
|
description = "Path to search for logs";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
maintenanceJobType = types.submodule {
|
||||||
|
options = {
|
||||||
|
name = mkOption { type = types.str; };
|
||||||
|
title = mkOption { type = types.str; };
|
||||||
|
service = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
description = "Systemd service to run";
|
||||||
|
};
|
||||||
|
logPattern = mkOption { type = types.str; };
|
||||||
|
logPath = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/tmp";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.backup = {
|
||||||
|
enable = mkEnableOption "backup system";
|
||||||
|
notificationUrl = mkOption { type = types.str; };
|
||||||
|
logDir = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "/var/log/backups";
|
||||||
|
};
|
||||||
|
jobs = mkOption {
|
||||||
|
type = types.listOf borgJobType;
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
maintenanceJobs = mkOption {
|
||||||
|
type = types.listOf maintenanceJobType;
|
||||||
|
default = [ ];
|
||||||
|
};
|
||||||
|
enableChainTimer = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
};
|
||||||
|
chainSchedule = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "*-*-* 03:00:00";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
borgbackup
|
||||||
|
apprise
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd = mkMerge [
|
||||||
|
# Create log directory
|
||||||
|
{ tmpfiles.rules = [ "d ${cfg.logDir} 0755 root root -" ]; }
|
||||||
|
|
||||||
|
# Individual backup services
|
||||||
|
(mkMerge (map mkBorgService cfg.jobs))
|
||||||
|
|
||||||
|
# Backup chain orchestration
|
||||||
|
(mkIf (cfg.jobs != [ ] || cfg.maintenanceJobs != [ ]) mkChainService)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
|
@ -90,7 +90,7 @@ Each system in `hosts/nixos/<hostname>/` contains:
|
||||||
| ---------- | ------------- | ---------------------- | --------------------------- | -------------------------------- |
|
| ---------- | ------------- | ---------------------- | --------------------------- | -------------------------------- |
|
||||||
| **rune** | Desktop | My workstation | Ryzen 9 7900X3D, RX 9070 XT | Gaming, Development, VMs |
|
| **rune** | Desktop | My workstation | Ryzen 9 7900X3D, RX 9070 XT | Gaming, Development, VMs |
|
||||||
| **gojo** | Desktop | Giovanni's workstation | Ryzen 7 7800X3D, RX 7900 XT | Gaming, Development |
|
| **gojo** | Desktop | Giovanni's workstation | Ryzen 7 7800X3D, RX 7900 XT | Gaming, Development |
|
||||||
| **haze** | Desktop | Cesar's workstation | Ryzen 7, RX 6950 XT | Gaming, Development |
|
| **haze** | Desktop | Cesar's workstation | Ryzen 5 7600x, RX 7600 | Gaming, Development |
|
||||||
| **caenus** | Server | Oracle VPS | ARM 4vCPU, 24GB RAM, 200GB | FRP, Public IP |
|
| **caenus** | Server | Oracle VPS | ARM 4vCPU, 24GB RAM, 200GB | FRP, Public IP |
|
||||||
| **sock** | Server | Backup & Storage | Intel N150 | Komodo (Docker), Backups, Newt |
|
| **sock** | Server | Backup & Storage | Intel N150 | Komodo (Docker), Backups, Newt |
|
||||||
| **cloud** | LXC Container | Storage & NFS | 4C/4GB | File storage, NFS, Newt |
|
| **cloud** | LXC Container | Storage & NFS | 4C/4GB | File storage, NFS, Newt |
|
||||||
|
|
Loading…
Add table
Reference in a new issue