Compare commits
No commits in common. "10bad206e5873fafbf7d719e187a3711aa544f49" and "3dd421cb08d3d6e71b3092593846f40e81a767fb" have entirely different histories.
10bad206e5
...
3dd421cb08
30 changed files with 1030 additions and 877 deletions
58
flake.lock
generated
58
flake.lock
generated
|
@ -69,24 +69,46 @@
|
|||
},
|
||||
"chaotic": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"flake-schemas": "flake-schemas",
|
||||
"home-manager": "home-manager",
|
||||
"jovian": "jovian",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750195929,
|
||||
"narHash": "sha256-5gaf/9wuxtfKqAFnNlX74Vz2VMURa/UzyfuEyYv4tZw=",
|
||||
"lastModified": 1748304776,
|
||||
"narHash": "sha256-Eb+kBcm7ECpJ1HKjMgvZPo9TpGG0CpzfGRUc0FCZKP0=",
|
||||
"owner": "chaotic-cx",
|
||||
"repo": "nyx",
|
||||
"rev": "419a1cfaf34100008ff5fa97ce9ef3b194472f71",
|
||||
"rev": "5278f55d2c2c568db38ed03370606b5e009e34df",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "chaotic-cx",
|
||||
"ref": "nyxpkgs-unstable",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
|
@ -738,24 +760,20 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"chaotic",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1750214276,
|
||||
"narHash": "sha256-1kniuhH70q4TAC/xIvjFYH46aHiLrbIlcr6fdrRwO1A=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "f9b2b2b1327ff6beab4662b8ea41689e0a57b8d4",
|
||||
"lastModified": 1747323949,
|
||||
"narHash": "sha256-G4NwzhODScKnXqt2mEQtDFOnI0wU3L1WxsiHX3cID/0=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "f8e784353bde7cbf9a9046285c1caf41ac484ebe",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
nixpkgs-stable.url = "github:NixOS/nixpkgs/nixos-25.05";
|
||||
nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
chaotic.url = "github:chaotic-cx/nyx/nyxpkgs-unstable"; # Bleeding edge packages from Chaotic-AUR
|
||||
chaotic.url = "github:chaotic-cx/nyx/5278f55d2c2c568db38ed03370606b5e009e34df"; # Bleeding edge packages from Chaotic-AUR
|
||||
|
||||
## NixOS ##
|
||||
|
||||
|
|
|
@ -4,141 +4,31 @@
|
|||
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 ./.;
|
||||
|
||||
home.packages = with pkgs; [
|
||||
prismlauncher
|
||||
steam-run
|
||||
heroic
|
||||
gamescope-run
|
||||
# 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,12 +322,9 @@ with lib.hm.gvariant;
|
|||
vertical-margin-bottom = 8;
|
||||
window-gap = 8;
|
||||
winprops = [
|
||||
''
|
||||
''
|
||||
{"wm_class":"Code","spaceIndex":0}
|
||||
''
|
||||
''
|
||||
{"wm_class":"com.jaoushingan.WaydroidHelper","scratch_layer":true}
|
||||
''
|
||||
''
|
||||
{"wm_class":"com.mitchellh.ghostty","scratch_layer":true}
|
||||
''
|
||||
|
@ -340,21 +337,12 @@ with lib.hm.gvariant;
|
|||
''
|
||||
{"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":"TelegramDesktop","spaceIndex":1}
|
||||
''
|
||||
''
|
||||
{"wm_class":"Waydroid","preferredWidth":"100%","spaceIndex":0,"title":""}
|
||||
''
|
||||
''
|
||||
{"wm_class":".gamescope-wrapped","preferredWidth":"100%","spaceIndex":2}
|
||||
''
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
@ -6,14 +6,15 @@
|
|||
...
|
||||
}:
|
||||
{
|
||||
#TODO: Scripts might need a rework
|
||||
programs.fastfetch =
|
||||
let
|
||||
hostname = hostSpec.hostName;
|
||||
logoFile =
|
||||
let
|
||||
hostLogoPath = ./. + "/host/images/${hostname}.png";
|
||||
hostLogoPath = ./. + "/host/${hostname}.txt";
|
||||
in
|
||||
if builtins.pathExists hostLogoPath then hostLogoPath else ./host/images/nix.png;
|
||||
if builtins.pathExists hostLogoPath then hostLogoPath else ./host/nix.txt;
|
||||
weather = import ./scripts/weather.nix { inherit pkgs; };
|
||||
title = import ./scripts/title.nix { inherit pkgs; };
|
||||
in
|
||||
|
@ -21,14 +22,12 @@
|
|||
enable = true;
|
||||
settings = {
|
||||
logo = {
|
||||
type = "kitty";
|
||||
source = logoFile;
|
||||
width = 21; # columns
|
||||
height = 12; # rows
|
||||
source = builtins.readFile logoFile;
|
||||
type = "data";
|
||||
position = "left";
|
||||
padding = {
|
||||
top = 1;
|
||||
right = 2;
|
||||
left = 2;
|
||||
top = 0;
|
||||
right = 0;
|
||||
};
|
||||
};
|
||||
display = {
|
||||
|
@ -73,11 +72,7 @@
|
|||
type = "wm";
|
||||
}
|
||||
{
|
||||
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}))";
|
||||
text = "printf '%s%s' (string upper (string sub -l 1 $SHELL)) (string lower (string sub -s 2 $SHELL))";
|
||||
key = "shell » {#keys}";
|
||||
keyColor = "1;33";
|
||||
type = "command";
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 56 KiB |
Binary file not shown.
Before Width: | Height: | Size: 123 KiB After Width: | Height: | Size: 13 KiB |
|
@ -21,10 +21,7 @@ for i in (seq 1 $num)
|
|||
set type2 $types[$j]
|
||||
set combination "$type1+$type2"
|
||||
echo "Creating with type $combination"
|
||||
yay try chafa -- chafa -s 24x11 -w 9 --symbols $combination --view-size 24x11 $input_png
|
||||
# yay try chafa -- chafa -s 23x12 -w 9 --stretch --symbols $combination --view-size 23x12 $input_png
|
||||
nix run nixpkgs#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
|
||||
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,10 +1,4 @@
|
|||
function fish_greeting
|
||||
if test "$SIXEL" = true; or string match -q xterm-256color "$TERM"
|
||||
fastfetch --logo-type sixel
|
||||
else
|
||||
fastfetch
|
||||
end
|
||||
end
|
||||
set fish_greeting # Disable greeting
|
||||
|
||||
## Aliases and Overrides ##
|
||||
|
||||
|
@ -114,3 +108,7 @@ function unzipz
|
|||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
## Fish Prompt ##
|
||||
|
||||
fastfetch
|
||||
|
|
|
@ -2,11 +2,10 @@
|
|||
{
|
||||
imports = lib.custom.scanPaths ./.;
|
||||
|
||||
# home.file.".config/monitors_source" = {
|
||||
# source = ./monitors.xml;
|
||||
# onChange = ''
|
||||
# cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||
# chmod 755 $HOME/.config/monitors.xml
|
||||
# '';
|
||||
# };
|
||||
# home.file.".config/monitors_source" = {
|
||||
# source = ./monitors.xml;
|
||||
# onChange = ''
|
||||
# cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||
# '';
|
||||
# };
|
||||
}
|
||||
|
|
|
@ -30,45 +30,10 @@
|
|||
};
|
||||
};
|
||||
|
||||
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" = {
|
||||
source = ./monitors.xml;
|
||||
onChange = ''
|
||||
cp $HOME/.config/monitors_source $HOME/.config/monitors.xml
|
||||
chmod 755 $HOME/.config/monitors.xml
|
||||
'';
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,28 @@
|
|||
<monitors version="2">
|
||||
<configuration>
|
||||
<layoutmode>logical</layoutmode>
|
||||
<layoutmode>physical</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>
|
||||
<x>1080</x>
|
||||
<y>0</y>
|
||||
|
@ -20,27 +42,5 @@
|
|||
</mode>
|
||||
</monitor>
|
||||
</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>
|
||||
</monitors>
|
||||
|
|
|
@ -2,33 +2,22 @@
|
|||
pkgs,
|
||||
lib,
|
||||
config,
|
||||
inputs,
|
||||
...
|
||||
}:
|
||||
{
|
||||
|
||||
hardware.graphics = {
|
||||
hardware.opengl = {
|
||||
enable = true;
|
||||
enable32Bit = true;
|
||||
driSupport32Bit = true;
|
||||
};
|
||||
|
||||
# AMDgpu tool
|
||||
environment.systemPackages = with pkgs; [
|
||||
lact
|
||||
gamescope
|
||||
# gamescope-wsi
|
||||
];
|
||||
|
||||
systemd = {
|
||||
packages = with pkgs; [ lact ];
|
||||
services.lactd.wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
environment.systemPackages = with pkgs; [ lact ];
|
||||
systemd.packages = with pkgs; [ lact ];
|
||||
systemd.services.lactd.wantedBy = [ "multi-user.target" ];
|
||||
|
||||
programs = {
|
||||
steam = {
|
||||
enable = true;
|
||||
remotePlay.openFirewall = true;
|
||||
dedicatedServer.openFirewall = true;
|
||||
protontricks = {
|
||||
enable = true;
|
||||
package = pkgs.protontricks;
|
||||
|
@ -52,7 +41,7 @@
|
|||
|
||||
inherit (pkgs)
|
||||
gamemode
|
||||
mangohud
|
||||
gamescope # !!!: DO NOT ADD GAMESCOPE ANYWHERE ELSE IN CONFIG, IT WILL BREAK!
|
||||
gperftools
|
||||
keyutils
|
||||
libkrb5
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
{ pkgs, config, ... }:
|
||||
{
|
||||
## DE ##
|
||||
services.xserver = {
|
||||
enable = true;
|
||||
xkb = {
|
||||
layout = "us";
|
||||
variant = "";
|
||||
};
|
||||
};
|
||||
|
||||
services.desktopManager.gnome = {
|
||||
enable = true;
|
||||
extraGSettingsOverridePackages = [ pkgs.mutter ];
|
||||
|
@ -20,25 +23,12 @@
|
|||
enable = true;
|
||||
wayland = true;
|
||||
};
|
||||
|
||||
# Set the custom session as default
|
||||
defaultSession = lib.mkForce "gnome";
|
||||
|
||||
autoLogin = {
|
||||
enable = true;
|
||||
user = config.hostSpec.username;
|
||||
};
|
||||
};
|
||||
|
||||
# Configure keyboard layout for Wayland
|
||||
services.xserver = {
|
||||
enable = false;
|
||||
xkb = {
|
||||
layout = "us";
|
||||
variant = "";
|
||||
};
|
||||
};
|
||||
|
||||
#INFO: Fix for autoLogin
|
||||
systemd.services."getty@tty1".enable = false;
|
||||
systemd.services."autovt@tty1".enable = false;
|
||||
|
@ -63,6 +53,7 @@
|
|||
];
|
||||
|
||||
## Exclusions ##
|
||||
services.xserver.excludePackages = [ pkgs.xterm ];
|
||||
environment.gnome.excludePackages = (
|
||||
with pkgs;
|
||||
[
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
{ 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"
|
||||
'')
|
||||
];
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
{
|
||||
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;
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
207
hosts/x86/cloud/config/backups/borg.nix
Normal file
207
hosts/x86/cloud/config/backups/borg.nix
Normal file
|
@ -0,0 +1,207 @@
|
|||
{
|
||||
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;
|
||||
})
|
||||
];
|
||||
}
|
197
hosts/x86/cloud/config/backups/default.nix
Normal file
197
hosts/x86/cloud/config/backups/default.nix
Normal file
|
@ -0,0 +1,197 @@
|
|||
{
|
||||
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 -" ];
|
||||
}
|
92
hosts/x86/gojo/hardware.fix
Normal file
92
hosts/x86/gojo/hardware.fix
Normal file
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
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,90 +1,64 @@
|
|||
# FIXME: FIX to hardware.fix once out of VM, this is TEMP vm hardware config
|
||||
{
|
||||
pkgs,
|
||||
inputs,
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
modulesPath,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
username = config.hostSpec.username;
|
||||
in
|
||||
{
|
||||
imports = lib.flatten [
|
||||
(modulesPath + "/installer/scan/not-detected.nix")
|
||||
(modulesPath + "/profiles/qemu-guest.nix")
|
||||
];
|
||||
|
||||
## Boot ##
|
||||
boot = {
|
||||
loader = {
|
||||
systemd-boot = {
|
||||
grub = {
|
||||
enable = true;
|
||||
# When using plymouth, initrd can expand by a lot each time, so limit how many we keep around
|
||||
configurationLimit = lib.mkDefault 10;
|
||||
device = "/dev/vda";
|
||||
useOSProber = true;
|
||||
};
|
||||
efi.canTouchEfiVariables = true;
|
||||
timeout = 3;
|
||||
};
|
||||
|
||||
# Use the cachyos kernel for better performance
|
||||
kernelPackages = pkgs.linuxPackages_cachyos;
|
||||
# use latest kernel
|
||||
kernelPackages = pkgs.linuxPackages_latest;
|
||||
|
||||
initrd = {
|
||||
availableKernelModules = [
|
||||
"ahci"
|
||||
"xhci_pci"
|
||||
"virtio_pci"
|
||||
"sr_mod"
|
||||
"virtio_blk"
|
||||
];
|
||||
systemd.enable = true;
|
||||
verbose = false;
|
||||
availableKernelModules = [
|
||||
"nvme"
|
||||
"xhci_pci"
|
||||
"ahci"
|
||||
"usb_storage"
|
||||
"usbhid"
|
||||
"sd_mod"
|
||||
];
|
||||
kernelModules = [ ];
|
||||
};
|
||||
kernelModules = [
|
||||
"kvm-amd"
|
||||
"amdgpu"
|
||||
];
|
||||
kernelModules = [ "kvm-amd" ];
|
||||
extraModulePackages = [ ];
|
||||
};
|
||||
|
||||
# For less permission issues with SSHFS
|
||||
programs.fuse.userAllowOther = true;
|
||||
|
||||
fileSystems."/" = {
|
||||
device = "/dev/disk/by-uuid/a19362ba-329b-4688-be05-c180f4212d97";
|
||||
device = "/dev/disk/by-uuid/5f1ad3a9-18ce-42ab-83ea-b67bccaa6972";
|
||||
fsType = "ext4";
|
||||
};
|
||||
|
||||
fileSystems."/boot" = {
|
||||
device = "/dev/disk/by-uuid/8E6A-D6AE";
|
||||
fsType = "vfat";
|
||||
options = [
|
||||
"fmask=0077"
|
||||
"dmask=0077"
|
||||
];
|
||||
};
|
||||
|
||||
swapDevices = [
|
||||
{ device = "/dev/disk/by-uuid/2c3c3142-93c9-4be7-b9c1-5e9bc45d9fff"; }
|
||||
{ device = "/dev/disk/by-uuid/e3fc8d25-31a5-48c1-8c81-c6c237f671bb"; }
|
||||
];
|
||||
|
||||
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;
|
||||
# networking.interfaces.enp1s0.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,7 +1,7 @@
|
|||
###############################################################
|
||||
#
|
||||
# Haze - Cesar's Desktop
|
||||
# NixOS running on Ryzen 5 7600x, Radeon RX 7600, 32GB RAM
|
||||
# NixOS running on Ryzen 7 ___ , Radeon RX 6950 XT, 32GB RAM
|
||||
#
|
||||
###############################################################
|
||||
|
||||
|
|
|
@ -43,7 +43,6 @@ in
|
|||
"hosts/global/common/nvtop.nix" # GPU monitor (not available in home-manager)
|
||||
"hosts/global/common/plymouth.nix" # fancy boot screen
|
||||
"hosts/global/common/solaar.nix" # Logitech Unifying Receiver support
|
||||
"hosts/global/common/waydroid.nix" # Android container
|
||||
"hosts/global/common/vial.nix" # KB setup
|
||||
])
|
||||
];
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
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";
|
||||
}
|
||||
];
|
||||
};
|
||||
}
|
193
hosts/x86/sock/config/backups/borg.nix
Normal file
193
hosts/x86/sock/config/backups/borg.nix
Normal file
|
@ -0,0 +1,193 @@
|
|||
{
|
||||
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;
|
||||
})
|
||||
];
|
||||
}
|
184
hosts/x86/sock/config/backups/default.nix
Normal file
184
hosts/x86/sock/config/backups/default.nix
Normal file
|
@ -0,0 +1,184 @@
|
|||
{
|
||||
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,18 +34,4 @@
|
|||
to = if builtins.isAttrs smtp then recipient else smtp.user;
|
||||
in
|
||||
"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,6 +13,10 @@
|
|||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
noBar = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
};
|
||||
width = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
example = 1920;
|
||||
|
@ -40,21 +44,22 @@
|
|||
transform = lib.mkOption {
|
||||
type = lib.types.int;
|
||||
default = 0;
|
||||
description = "Screen orientation: 0 = landscape, 1 = portrait left, 2 = portrait right, 3 = landscape flipped";
|
||||
};
|
||||
enabled = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = true;
|
||||
};
|
||||
hdr = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
default = false;
|
||||
workspace = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = "Defines a workspace that should persist on this monitor.";
|
||||
default = null;
|
||||
};
|
||||
vrr = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Variable Refresh Rate aka Adaptive Sync aka AMD FreeSync.";
|
||||
default = false;
|
||||
type = lib.types.int;
|
||||
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";
|
||||
default = 0;
|
||||
};
|
||||
|
||||
};
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,404 +0,0 @@
|
|||
{
|
||||
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 |
|
||||
| **gojo** | Desktop | Giovanni's workstation | Ryzen 7 7800X3D, RX 7900 XT | Gaming, Development |
|
||||
| **haze** | Desktop | Cesar's workstation | Ryzen 5 7600x, RX 7600 | Gaming, Development |
|
||||
| **haze** | Desktop | Cesar's workstation | Ryzen 7, RX 6950 XT | Gaming, Development |
|
||||
| **caenus** | Server | Oracle VPS | ARM 4vCPU, 24GB RAM, 200GB | FRP, Public IP |
|
||||
| **sock** | Server | Backup & Storage | Intel N150 | Komodo (Docker), Backups, Newt |
|
||||
| **cloud** | LXC Container | Storage & NFS | 4C/4GB | File storage, NFS, Newt |
|
||||
|
|
Loading…
Add table
Reference in a new issue