Compare commits

...

56 Commits

Author SHA1 Message Date
56dbacfd8f added backup browse 2025-10-09 03:58:54 -05:00
7c0dd2185a added backup browse 2025-10-09 03:58:16 -05:00
a3554c71f0 147 current 2025-10-09 01:08:24 25.05.20251006.20c4598 6.12.50 * 2025-10-09 02:18:38 -05:00
706eb62f76 146 current 2025-10-09 01:01:33 25.05.20251006.20c4598 6.12.50 * 2025-10-09 01:08:25 -05:00
8d1c3e5539 145 current 2025-10-09 00:38:53 25.05.20251006.20c4598 6.12.50 * 2025-10-09 01:01:35 -05:00
d1190bacea 145 current 2025-10-09 00:38:53 25.05.20251006.20c4598 6.12.50 * 2025-10-09 00:39:10 -05:00
67d587518a 144 current 2025-10-09 00:31:21 25.05.20251006.20c4598 6.12.50 * 2025-10-09 00:38:56 -05:00
d44c8983d6 143 current 2025-10-09 00:24:29 25.05.20251006.20c4598 6.12.50 * 2025-10-09 00:31:24 -05:00
de0441e9cf 142 current 2025-10-08 23:44:07 25.05.20251006.20c4598 6.12.50 * 2025-10-09 00:24:31 -05:00
6c5f3127bd 142 current 2025-10-08 23:44:07 25.05.20251006.20c4598 6.12.50 * 2025-10-09 00:23:43 -05:00
b659989abc 142 current 2025-10-08 23:44:07 25.05.20251006.20c4598 6.12.50 * 2025-10-09 00:22:59 -05:00
13ed470439 142 current 2025-10-08 23:44:07 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:44:28 -05:00
10ca6b492a 141 current 2025-10-08 23:40:02 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:44:10 -05:00
5a8e2f30db 140 current 2025-10-08 23:21:40 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:40:05 -05:00
c0444538e4 140 current 2025-10-08 23:21:40 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:22:01 -05:00
a376b6f4dd 139 current 2025-10-08 23:18:59 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:21:43 -05:00
25d440e994 138 current 2025-10-08 23:11:11 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:19:01 -05:00
5e34b30e25 138 current 2025-10-08 23:11:11 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:11:31 -05:00
fbf86d1572 137 current 2025-10-08 23:08:33 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:11:13 -05:00
7d380be4a4 137 current 2025-10-08 23:08:33 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:09:03 -05:00
fcdfa0de1f 136 current 2025-10-08 23:02:33 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:08:36 -05:00
1e1d40a098 135 current 2025-10-08 22:40:45 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:02:36 -05:00
7875d0cee1 135 current 2025-10-08 22:40:45 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:01:44 -05:00
83a45b2bae 135 current 2025-10-08 22:40:45 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:01:17 -05:00
e012243194 135 current 2025-10-08 22:40:45 25.05.20251006.20c4598 6.12.50 * 2025-10-08 23:00:15 -05:00
34397fbb50 134 current 2025-10-08 22:40:11 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:40:46 -05:00
bc4dac57a2 133 current 2025-10-08 22:39:25 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:40:13 -05:00
faef305ec0 132 current 2025-10-08 22:37:42 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:39:27 -05:00
a012b8e62b 132 current 2025-10-08 22:37:42 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:38:22 -05:00
3c7b531f18 131 current 2025-10-08 22:30:57 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:37:44 -05:00
d82eb2d366 131 current 2025-10-08 22:30:57 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:36:48 -05:00
c675d32a20 131 current 2025-10-08 22:30:57 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:36:02 -05:00
66709ba136 130 current 2025-10-08 22:29:57 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:30:59 -05:00
75b2efc19c 129 current 2025-10-08 22:29:19 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:29:59 -05:00
0887c39e10 128 current 2025-10-08 22:26:48 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:29:21 -05:00
700dcfe444 127 current 2025-10-08 22:24:23 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:26:50 -05:00
38ab3d07cb 126 current 2025-10-08 22:04:52 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:24:25 -05:00
f65a3d857a 125 current 2025-10-08 21:31:26 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:04:55 -05:00
9d7dd26b7d 125 current 2025-10-08 21:31:26 25.05.20251006.20c4598 6.12.50 * 2025-10-08 22:04:04 -05:00
78a14a7feb 124 current 2025-10-08 20:35:43 25.05.20251006.20c4598 6.12.50 * 2025-10-08 21:31:33 -05:00
b04632d191 123 current 2025-10-08 20:12:21 25.05.20251006.20c4598 6.12.50 * 2025-10-08 20:35:45 -05:00
a427fc9e05 122 current 2025-10-08 20:00:40 25.05.20251006.20c4598 6.12.50 * 2025-10-08 20:12:23 -05:00
1c71771cf4 122 current 2025-10-08 20:00:40 25.05.20251006.20c4598 6.12.50 * 2025-10-08 20:07:47 -05:00
70f9bbc727 122 current 2025-10-08 20:00:40 25.05.20251006.20c4598 6.12.50 * 2025-10-08 20:04:46 -05:00
68a5ae410c 121 current 2025-10-08 19:57:56 25.05.20251006.20c4598 6.12.50 * 2025-10-08 20:00:42 -05:00
345fa1c53b 120 current 2025-10-08 19:53:25 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:57:58 -05:00
4fae071bbb 119 current 2025-10-08 19:51:40 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:53:28 -05:00
3ba095c86e 118 current 2025-10-08 19:48:52 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:51:42 -05:00
42a1085e77 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:48:55 -05:00
e9d6449f9b 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:47:48 -05:00
02b2cd894b 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:47:10 -05:00
1fac4db005 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:45:23 -05:00
6872ed49ff 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:43:45 -05:00
11e1ad3afb 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:42:49 -05:00
ecb7f984aa adding vpn-confinement 2025-10-08 19:42:00 -05:00
f106d9b565 117 current 2025-10-08 19:07:36 25.05.20251006.20c4598 6.12.50 * 2025-10-08 19:41:45 -05:00
14 changed files with 596 additions and 39 deletions

203
bin/backup_browse.sh Executable file
View File

@@ -0,0 +1,203 @@
#!/usr/bin/env bash
set -euo pipefail
# --- SUDO CHECK ---
if [ "$EUID" -ne 0 ]; then
echo "This script requires root privileges. Re-running with sudo..."
exec sudo "$0" "$@"
fi
# --- HANDLE OPTIONS ---
BORG_PASSPHRASE=""
SHOW_FOLDERS_ONLY=false
SHOW_FILES=true # Default to showing files and directories
while getopts "k:fp" opt; do
case "$opt" in
k)
BORG_PASSPHRASE=$(<"$OPTARG")
if [ -z "$BORG_PASSPHRASE" ]; then
echo "Error: The key file is empty."
exit 1
fi
echo "Using passphrase from key file: $OPTARG"
;;
f)
SHOW_FOLDERS_ONLY=true
echo "Only directories will be shown in fzf by default."
SHOW_FILES=false
;;
p)
SHOW_FILES=true
echo "Both files and directories will be shown in fzf."
;;
*)
echo "Usage: $0 [-k passphrase_file] [-f] [-p] <repo>"
exit 1
;;
esac
done
shift $((OPTIND - 1))
# --- FALLBACK TO /run/secrets/borg_passwd IF NO KEY FILE ---
if [ -z "$BORG_PASSPHRASE" ]; then
if [ $# -eq 0 ]; then
BORG_PASSPHRASE=$(<"/run/secrets/borg_passwd")
echo "Using passphrase from /run/secrets/borg_passwd"
else
# Prompt user for passphrase if neither -k nor /run/secrets/borg_passwd is available
read -s -p "Enter Borg repository passphrase: " BORG_PASSPHRASE
echo
fi
fi
export BORG_PASSPHRASE
# --- DEFAULT REPO ---
REPO="${1:-/holocron/backups}"
# --- CHECK REQUIRED COMMANDS ---
for cmd in borg fzf find tree cp mkdir; do
command -v "$cmd" >/dev/null || { echo "Error: '$cmd' is required but not installed."; exit 1; }
done
# --- LIST ARCHIVES (sorted, newest last) ---
mapfile -t archives < <(borg list --format="{archive}{NL}" "$REPO" | sort -r)
if [ ${#archives[@]} -eq 0 ]; then
echo "No archives found in $REPO"
exit 1
fi
# --- FZF ARCHIVE SELECT ---
selected=$(printf '%s\n' "${archives[@]}" | fzf --prompt="Select archive: " --height=40% --border)
if [ -z "$selected" ]; then
echo "No archive selected."
exit 1
fi
echo "Selected archive: $selected"
# --- GENERATE A UNIQUE, SHORTER MOUNT POINT ---
MOUNT_POINT="/tmp/$(uuidgen | sha256sum | head -c 2)-restore-${selected}"
mkdir -p "$MOUNT_POINT"
# --- MOUNT ARCHIVE ---
echo "Mounting '$selected' to $MOUNT_POINT..."
borg mount "$REPO::$selected" "$MOUNT_POINT"
if [ ! -d "$MOUNT_POINT" ]; then
echo "Error: mount failed."
exit 1
fi
# --- LIST FILES AND DIRECTORIES ---
echo "Scanning files and directories..."
if command -v fd >/dev/null 2>&1; then
# List both files and directories with fd
if [ "$SHOW_FILES" = true ]; then
files=$(fd . "$MOUNT_POINT" | sort) # Show both files and directories
else
files=$(fd --type d . "$MOUNT_POINT" | sort) # Only directories
fi
else
# Fall back to find if fd is not available
if [ "$SHOW_FILES" = true ]; then
files=$(find "$MOUNT_POINT" | sort) # Show both files and directories
else
files=$(find "$MOUNT_POINT" -type d | sort) # Only directories
fi
fi
if [ -z "$files" ]; then
echo "No files or directories found in archive."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 1
fi
# --- FZF FILE/DIRECTORY SELECTION ---
if [ "$SHOW_FILES" = true ]; then
# If showing files and directories, we pass everything to fzf
selected_items=$(printf '%s\n' "$files" | sed "s|$MOUNT_POINT/||" | tac | fzf \
--multi \
--height=50% \
--border \
--prompt="Select files or directories to restore: " \
--preview "tree -C -L 5 $MOUNT_POINT/$(dirname {})" \
--preview-window=right:50% \
--delimiter='/' \
--with-nth=1..)
else
# If only showing directories, pass directories to fzf
selected_items=$(printf '%s\n' "$files" | sed "s|$MOUNT_POINT/||" | tac | fzf \
--multi \
--height=50% \
--border \
--prompt="Select directories to restore: " \
--preview "tree -C -d -L 5 $MOUNT_POINT/$(dirname {})" \
--preview-window=right:50% \
--delimiter='/' \
--with-nth=1..)
fi
if [ -z "$selected_items" ]; then
echo "No items selected. Exiting."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 0
fi
# --- SUMMARY OF SELECTED ITEMS ---
echo "Selected items:"
for item in $selected_items; do
echo " $item"
done
# --- OPTIONS MENU (concise) ---
# Default to option 1 if no input is given
echo "Select restore destination: 1) Restore to ./${selected}_restore 2) Restore to original dirs 3) Quit"
read -p "Enter your choice (1/2/3) [default: 1]: " choice
# Default to option 1 if user presses Enter without providing input
choice="${choice:-1}"
# --- SET RESTORE DESTINATION BASED ON USER CHOICE ---
case "$choice" in
1)
DEST="./${selected}_restore"
;;
2)
DEST="$MOUNT_POINT"
;;
3)
echo "Quitting. No items restored."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 0
;;
*)
echo "Invalid choice. Exiting."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 1
;;
esac
mkdir -p "$DEST"
# --- RESTORE SELECTED ITEMS (FILES OR DIRECTORIES) ---
echo "Restoring selected items..."
while IFS= read -r item; do
dest_path="$DEST/$item"
mkdir -p "$(dirname "$dest_path")"
if [ -d "$MOUNT_POINT/$item" ]; then
cp -r "$MOUNT_POINT/$item" "$dest_path"
else
cp -a "$MOUNT_POINT/$item" "$dest_path"
fi
echo "Restored: $item"
done <<< "$selected_items"
# --- CLEANUP ---
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
echo "Restore complete."

173
bin/backup_browse.sh.bak Executable file
View File

@@ -0,0 +1,173 @@
#!/usr/bin/env bash
set -euo pipefail
# --- SUDO CHECK ---
if [ "$EUID" -ne 0 ]; then
echo "This script requires root privileges. Re-running with sudo..."
exec sudo "$0" "$@"
fi
# --- HANDLE -k OPTION FOR KEY FILE ---
BORG_PASSPHRASE=""
while getopts "k:" opt; do
case "$opt" in
k)
BORG_PASSPHRASE=$(<"$OPTARG")
if [ -z "$BORG_PASSPHRASE" ]; then
echo "Error: The key file is empty."
exit 1
fi
echo "Using passphrase from key file: $OPTARG"
;;
*)
echo "Usage: $0 [-k passphrase_file] <repo>"
exit 1
;;
esac
done
shift $((OPTIND - 1))
# --- FALLBACK TO /run/secrets/borg_passwd IF NO KEY FILE ---
if [ -z "$BORG_PASSPHRASE" ]; then
if [ -f "/run/secrets/borg_passwd" ]; then
BORG_PASSPHRASE=$(<"/run/secrets/borg_passwd")
echo "Using passphrase from /run/secrets/borg_passwd"
else
# Prompt user for passphrase if neither -k nor /run/secrets/borg_passwd is available
read -s -p "Enter Borg repository passphrase: " BORG_PASSPHRASE
echo
fi
fi
export BORG_PASSPHRASE
# --- DEFAULT REPO ---
REPO="${1:-/holocron/backups}"
# --- CHECK REQUIRED COMMANDS ---
for cmd in borg fzf find tree cp mkdir; do
command -v "$cmd" >/dev/null || { echo "Error: '$cmd' is required but not installed."; exit 1; }
done
# --- LIST ARCHIVES (sorted, newest last) ---
mapfile -t archives < <(borg list --format="{archive}{NL}" "$REPO" | sort)
if [ ${#archives[@]} -eq 0 ]; then
echo "No archives found in $REPO"
exit 1
fi
# --- FZF ARCHIVE SELECT ---
selected=$(printf '%s\n' "${archives[@]}" | fzf --prompt="Select archive: " --height=40% --border --reverse)
if [ -z "$selected" ]; then
echo "No archive selected."
exit 1
fi
echo "Selected archive: $selected"
# --- GENERATE A UNIQUE, SHORTER MOUNT POINT ---
MOUNT_POINT="/tmp/borg-mount-${selected}-$(uuidgen | sha256sum | head -c 6)"
mkdir -p "$MOUNT_POINT"
# --- MOUNT ARCHIVE ---
echo "Mounting '$selected' to $MOUNT_POINT..."
borg mount "$REPO::$selected" "$MOUNT_POINT"
if [ ! -d "$MOUNT_POINT" ]; then
echo "Error: mount failed."
exit 1
fi
# --- LIST FILES AND DIRECTORIES ---
echo "Scanning files and directories..."
if command -v fd >/dev/null 2>&1; then
# List files and directories using fd (can handle both files and dirs)
files=$(fd --type f --type d . "$MOUNT_POINT" | sort)
else
# Fall back to find if fd is not available
files=$(find "$MOUNT_POINT" -type f -o -type d | sort)
fi
if [ -z "$files" ]; then
echo "No files or directories found in archive."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 1
fi
# --- HIERARCHICAL FZF FILE/DIRECTORY SELECTION (REVERSED) ---
# We reverse the order of files to display the latest (newest) files/folders at the top.
selected_files=$(printf '%s\n' "$files" | sed "s|$MOUNT_POINT/||" | tac | fzf \
--multi \
--height=50% \
--border \
--prompt="Select files or directories to restore: " \
--preview "tree -C -L 5 $MOUNT_POINT/$(dirname {})" \
--preview-window=right:50% \
--delimiter='/' \
--with-nth=1..)
if [ -z "$selected_files" ]; then
echo "No files or directories selected. Exiting."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 0
fi
# --- SUMMARY OF SELECTED FILES/DIRECTORIES ---
echo "Selected files and directories:"
for file in $selected_files; do
echo " $file"
done
# --- OPTIONS MENU (concise) ---
# Default to option 1 if no input is given
echo "Select restore destination: 1) Restore to ./${selected}_restore 2) Restore to original dirs 3) Quit"
read -p "Enter your choice (1/2/3) [default: 1]: " choice
# Default to option 1 if user presses Enter without providing input
choice="${choice:-1}"
# --- SET RESTORE DESTINATION BASED ON USER CHOICE ---
case "$choice" in
1)
DEST="./${selected}_restore"
;;
2)
DEST="$MOUNT_POINT"
;;
3)
echo "Quitting. No files restored."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 0
;;
*)
echo "Invalid choice. Exiting."
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
exit 1
;;
esac
mkdir -p "$DEST"
# --- RESTORE FILES AND DIRECTORIES ---
echo "Restoring selected files and directories..."
while IFS= read -r file; do
# Path is already stripped of /tmp, so no need for further modification
dest_path="$DEST/$file"
mkdir -p "$(dirname "$dest_path")"
# If it's a directory, we use cp -r to ensure the directory structure is restored
if [ -d "$MOUNT_POINT/$file" ]; then
cp -r "$MOUNT_POINT/$file" "$dest_path"
else
cp -a "$MOUNT_POINT/$file" "$dest_path"
fi
echo "Restored: $file"
done <<< "$selected_files"
# --- CLEANUP ---
borg umount "$MOUNT_POINT"
rm -rf "$MOUNT_POINT"
echo "Restore complete."

64
bin/backup_browse_fzf.sh Executable file
View File

@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# borg-browser.sh — fzf-based Borg archive browser with passphrase prompt
[ "$EUID" -ne 0 ] && { echo "Please run as root."; exec sudo "$0" "$@"; }
REPO="/holocron/backups"
# Prompt once for Borg passphrase
read -rs -p "Borg passphrase: " BORG_PASSPHRASE
echo
export BORG_PASSPHRASE
# Pick an archive
ARCHIVE=$(borg list --short "$REPO" | fzf --prompt="Select archive: ") || {
unset BORG_PASSPHRASE
exit
}
[ -z "$ARCHIVE" ] && { unset BORG_PASSPHRASE; exit; }
# Function to browse directories hierarchically
browse_borg_dir() {
local prefix="$1"
while true; do
# Get immediate children of the current path
ITEMS=$(borg list --format='{path}{NL}' "$REPO::$ARCHIVE" \
| awk -v p="$prefix" -F/ '
BEGIN{n=split(p,a,"/")}
index($0,p)==1 && NF>n {
if (NF==n+1) print $NF;
else print $(n+1)"/";
}' \
| sort -u)
[ -z "$ITEMS" ] && { echo "No items found in $prefix"; return; }
SELECTION=$(echo -e "../\n$ITEMS" | fzf --prompt="${prefix:-/}> ")
case "$SELECTION" in
"../")
prefix="${prefix%/*}"
prefix="${prefix%/}"
;;
"")
return
;;
*/)
prefix="${prefix:+$prefix/}${SELECTION%/}"
;;
*)
local fullpath="${prefix:+$prefix/}$SELECTION"
echo "Selected file: $fullpath"
read -rp "Extract it here? [y/N]: " yn
if [[ $yn =~ ^[Yy]$ ]]; then
borg extract "$REPO::$ARCHIVE" "$fullpath"
fi
return
;;
esac
done
}
browse_borg_dir ""
unset BORG_PASSPHRASE

18
flake.lock generated
View File

@@ -40,7 +40,8 @@
"inputs": { "inputs": {
"home-manager": "home-manager", "home-manager": "home-manager",
"nixpkgs": "nixpkgs", "nixpkgs": "nixpkgs",
"sops-nix": "sops-nix" "sops-nix": "sops-nix",
"vpn-confinement": "vpn-confinement"
} }
}, },
"sops-nix": { "sops-nix": {
@@ -62,6 +63,21 @@
"repo": "sops-nix", "repo": "sops-nix",
"type": "github" "type": "github"
} }
},
"vpn-confinement": {
"locked": {
"lastModified": 1759956062,
"narHash": "sha256-NUZu0Rb0fwUjfdp51zMm0xM3lcK8Kw4c97LLog7+JjA=",
"owner": "Maroka-chan",
"repo": "VPN-Confinement",
"rev": "fabe7247b720b5eb4c3c053e24a2b3b70e64c52b",
"type": "github"
},
"original": {
"owner": "Maroka-chan",
"repo": "VPN-Confinement",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@@ -1,6 +1,6 @@
# flake for blakes nixos config # flake for blakes nixos config
# define new devices in outputs # define new devices in outputs
# generation: 116 current 2025-10-08 19:06:36 25.05.20251006.20c4598 6.12.50 * # generation: 147 current 2025-10-09 01:08:24 25.05.20251006.20c4598 6.12.50 *
{ {
description = "blakes nix config"; description = "blakes nix config";
inputs = { inputs = {
@@ -13,9 +13,11 @@
url = "github:Mic92/sops-nix"; url = "github:Mic92/sops-nix";
inputs.nixpkgs.follows = "nixpkgs"; inputs.nixpkgs.follows = "nixpkgs";
}; };
vpn-confinement = {
url = "github:Maroka-chan/VPN-Confinement";
};
}; };
outputs = { self, nixpkgs, home-manager, vpn-confinement, ... }@inputs:
outputs = { self, nixpkgs, home-manager, ... }@inputs:
let let
system = "x86_64-linux"; system = "x86_64-linux";
pkgs = nixpkgs.legacyPackages.${system}; pkgs = nixpkgs.legacyPackages.${system};
@@ -27,6 +29,7 @@
modules = [ modules = [
./hosts/snowbelle/configuration.nix ./hosts/snowbelle/configuration.nix
inputs.home-manager.nixosModules.default inputs.home-manager.nixosModules.default
vpn-confinement.nixosModules.default
]; ];
}; };
vaniville = nixpkgs.lib.nixosSystem { vaniville = nixpkgs.lib.nixosSystem {

View File

@@ -18,8 +18,9 @@
docker.enable = true; docker.enable = true;
syncthing.enable = true; syncthing.enable = true;
tailscale.enable = true; tailscale.enable = true;
vpns.enable = true; vpns.enable = false;
vpns.wg_mex = false; vpns.wg_mex = false;
vpn-confinement.enable = false;
nvidia.enable = true; nvidia.enable = true;
}; };
homelab = { homelab = {
@@ -41,47 +42,69 @@
}; };
}; };
# enable users # configure users & groups
users = { users = {
blake.enable = true; blake.enable = true; # main user, home manager
groups.media = { gid = 700; }; # user for share permissions with mediastack
defaultUserShell = pkgs.zsh; # the goat
}; };
users.groups.media = { gid = 700; }; # boot (systemd is going on me)
boot.loader.systemd-boot.enable = true; # systemd your pretty cool ya know
# testing!
#boot.kernelParams = [ "quiet" ]; # remove splash
#boot.plymouth.enable = true;
boot.initrd.systemd.enable = true; # optional, for nicer initrd logs
# use the systemd-boot EFI boot loader.
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true; boot.loader.efi.canTouchEfiVariables = true;
boot.initrd.systemd.enable = true; # better logging
# setup hostname and networking stack # setup hostname and networking stack
networking.hostName = "snowbelle"; # Define your hostname. services.resolved = {
networking.networkmanager.enable = true; # Easiest to use and most distros use this by default. enable = true;
networking.hostId = "3e6e7055"; fallbackDns = [ "1.1.1.1" "9.9.9.9" ];
dnsovertls = "opportunistic";
};
networking = {
hostName = "snowbelle"; # hostname
hostId = "3e6e7055"; # zfs wants this
networkmanager = {
enable = true; # the goat
dns = "systemd-resolved"; # the backup dancer!
ensureProfiles.profiles = {
vpn = {
ethernet.mac-address = "7a:e4:07:8d:22:76";
connection.type = "vlan";
connection.id = "vpn";
connection.interface-name = "enp89s0.69"; # or just "vpn-vlan"
vlan.interface-name = "enp89s0.69"; # or just "vpn-vlan"
vlan.parent = "enp89s0";
vlan.id = 69;
#ipv4.dns = "9.9.9.9";
};
};
};
};
# set timezone # set timezone
time.timeZone = "America/Chicago"; time.timeZone = "America/Chicago";
# define shell # define shell
programs.zsh.enable = true; programs.zsh.enable = true;
users.defaultUserShell = pkgs.zsh;
# package install list # package install list
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
vim git
lf age
rsync rsync
wget wget
git curl
iptables fzf
nettools fd
neofetch tree
vim
lf
btop btop
age neofetch
usbutils
inetutils
iptables
]; ];

View File

@@ -4,7 +4,7 @@ let
cfg = config.modules.services.prowlarr; cfg = config.modules.services.prowlarr;
ids = 2004; ids = 2004;
default_port = 9696; default_port = 9696;
data_dir = "/var/lib/prowlarr"; data_dir = "/var/lib/private";
in in
{ {
options.modules.services.prowlarr = { options.modules.services.prowlarr = {

View File

@@ -57,7 +57,7 @@ in
# internal reverse proxy entry # internal reverse proxy entry
services.nginx.virtualHosts."jellyfin.snowbelle.lan" = { services.nginx.virtualHosts."jellyfin.snowbelle.lan" = {
enableACME = false; enableACME = false;
forceSSL = false; forceSSL = true;
sslCertificate = config.sops.secrets."ssl_blakedheld_crt".path; sslCertificate = config.sops.secrets."ssl_blakedheld_crt".path;
sslCertificateKey = config.sops.secrets."ssl_blakedheld_key".path; sslCertificateKey = config.sops.secrets."ssl_blakedheld_key".path;
locations."/" = { locations."/" = {

View File

@@ -2,9 +2,10 @@
let let
cfg = config.modules.services.qbittorrent; cfg = config.modules.services.qbittorrent;
ids = 2003;
default_port = 8080; default_port = 8080;
data_dir = "/var/lib/qBittorrent"; data_dir = "/var/lib/qBittorrent";
ids = 2003;
vpn_inf = "enp89s0.69"; # vpn interfacve
in in
{ {
options.modules.services.qbittorrent = { options.modules.services.qbittorrent = {
@@ -49,21 +50,43 @@ in
profileDir = data_dir; profileDir = data_dir;
webuiPort = cfg.port; webuiPort = cfg.port;
# torrentingPort = cfg.port; # torrentingPort = cfg.port;
}; };
# override umask to make permissions work out # override umask to make permissions work out
systemd.services.qbittorrent.serviceConfig = { systemd.services.qbittorrent = {
UMask = lib.mkForce "0007"; serviceConfig = {
# User = "qbittorrent"; UMask = lib.mkForce "0007";
# Group = "qbittorrent"; };
}; };
networking.firewall.extraCommands = ''
iptables -F QBIT
iptables -X QBIT
iptables -N QBIT
iptables -A OUTPUT -m owner --uid-owner ${toString ids} -j QBIT
iptables -A QBIT -o ${vpn_inf} -j ACCEPT
iptables -A QBIT -p udp --dport 53 -o ${vpn_inf} -j ACCEPT
iptables -A QBIT -p tcp --dport 53 -o ${vpn_inf} -j ACCEPT
iptables -A QBIT -p tcp -d 127.0.0.1 --dport ${toString cfg.port} -j ACCEPT
iptables -A QBIT -p tcp -o enp89s0 -d 10.0.0.0/8 --dport ${toString cfg.port} -j ACCEPT
iptables -A QBIT -j DROP
'';
# ------------------------------------------------------------------------------
# # add systemd service to VPN network namespace
# vpnConfinement = {
# enable = true;
# vpnNamespace = "wgmex";
# };
# ------------------------------------------------------------------------------
# # open firewall # # open firewall
# networking.firewall.allowedTCPPorts = [ cfg.port ]; # networking.firewall.allowedTCPPorts = [ cfg.port ];
# internal reverse proxy entry # internal reverse proxy entry
services.nginx.virtualHosts.".snowbelle.lan" = { services.nginx.virtualHosts."qbit.snowbelle.lan" = {
enableACME = false; enableACME = false;
forceSSL = true; forceSSL = true;
sslCertificate = config.sops.secrets."ssl_blakedheld_crt".path; sslCertificate = config.sops.secrets."ssl_blakedheld_crt".path;

View File

@@ -8,6 +8,7 @@
./docker.nix ./docker.nix
./tailscale.nix ./tailscale.nix
./vpns.nix ./vpns.nix
./vpn-confinement.nix
./syncthing.nix ./syncthing.nix
./nvidia.nix ./nvidia.nix
]; ];
@@ -18,6 +19,7 @@
modules.system.docker.enable = lib.mkDefault false; modules.system.docker.enable = lib.mkDefault false;
modules.system.tailscale.enable = lib.mkDefault true; modules.system.tailscale.enable = lib.mkDefault true;
modules.system.vpns.enable = lib.mkDefault false; modules.system.vpns.enable = lib.mkDefault false;
modules.system.vpn-confinement.enable = lib.mkDefault false;
modules.system.syncthing.enable = lib.mkDefault false; modules.system.syncthing.enable = lib.mkDefault false;
modules.system.nvidia.enable = lib.mkDefault false; modules.system.nvidia.enable = lib.mkDefault false;

View File

@@ -14,7 +14,12 @@ in
enable = true; enable = true;
useRoutingFeatures = "both"; useRoutingFeatures = "both";
authKeyFile = authkey_file; authKeyFile = authkey_file;
extraUpFlags = [
"--accept-routes=false" # true is equilivant to useRoutingFeatures = "client" (breaks shit)
"--accept-dns=true" # explicitly allow resolved
];
}; };
# declare authkey secrets # declare authkey secrets
sops.secrets = { sops.secrets = {
"tailscale_authkey" = { "tailscale_authkey" = {

View File

@@ -0,0 +1,41 @@
{ pkgs, config, lib, ... }:
let
cfg = config.modules.system.vpn-confinement;
in
{
options.modules.system.vpn-confinement = {
enable = lib.mkEnableOption "enables vpn-confinement";
# toggle for mullvad mexico w/ openvpn
vpncon_mex = lib.mkOption {
type = lib.types.bool;
default = false;
description = "enable pia vpn to mexico using openvpn";
};
};
config = lib.mkIf cfg.enable {
# Define VPN network namespace
vpnNamespaces.wgmex = {
enable = true;
wireguardConfigFile = "/etc/wireguard/mullvad.conf";
accessibleFrom = [
"10.0.0.0/8"
];
portMappings = [
{ from = 7103; to = 7103; }
];
openVPNPorts = [{
port = 51820;
protocol = "both";
}];
};
# secrets only if VPN is enabled
sops.secrets = {
"vpncon_mex_config" = { owner = "root"; group = "root"; };
};
};
}

View File

@@ -14,6 +14,8 @@ pia_auth: ENC[AES256_GCM,data:rwAu4f5XVS4v4FCLj2zXAegIZeRPLIzUVv6TCrdfg9RGSDJYHg
openvpn_pia_mexico_config: ENC[AES256_GCM,data:59HQ3OZ0QKq92jI=,iv:DZTNvfi6kLXG7dsNkPcXUmXhAG2UdPZBy/L9eWNmRdE=,tag:ndxDDQNL2z1fjxFfU2VRwQ==,type:str] openvpn_pia_mexico_config: ENC[AES256_GCM,data:59HQ3OZ0QKq92jI=,iv:DZTNvfi6kLXG7dsNkPcXUmXhAG2UdPZBy/L9eWNmRdE=,tag:ndxDDQNL2z1fjxFfU2VRwQ==,type:str]
#ENC[AES256_GCM,data:mbIgMJBhL8nWJzl8q2dFL8XtO1Xa1Q==,iv:caYHYp1boK9wRgCcQe40HTWT/HxAIvYe+HyaruI53Vc=,tag:S6wowhAHObEcs7z8FimZ1g==,type:comment] #ENC[AES256_GCM,data:mbIgMJBhL8nWJzl8q2dFL8XtO1Xa1Q==,iv:caYHYp1boK9wRgCcQe40HTWT/HxAIvYe+HyaruI53Vc=,tag:S6wowhAHObEcs7z8FimZ1g==,type:comment]
wg_mex_key: ENC[AES256_GCM,data:vxDXixo6X6D33+p21L4hB0/yCH+TvMHZl991BkRsE/jdz7rzZuJF+zI7h+Q=,iv:8WR+feHXNUcat8DB2wY7wpos+P7TzgRF7rFD0fYosjY=,tag:p9b9ck0/VZjyLxtHut3n5Q==,type:str] wg_mex_key: ENC[AES256_GCM,data:vxDXixo6X6D33+p21L4hB0/yCH+TvMHZl991BkRsE/jdz7rzZuJF+zI7h+Q=,iv:8WR+feHXNUcat8DB2wY7wpos+P7TzgRF7rFD0fYosjY=,tag:p9b9ck0/VZjyLxtHut3n5Q==,type:str]
#ENC[AES256_GCM,data:3ATkokBKeOp97uORzaePROrKKfG94ic=,iv:MNJRh6Vrso1heqNUJc0M4xGNcMLGwcF9IzoiQ5+SS+g=,tag:xj8Actwkirvq4GE+Ly1M9w==,type:comment]
vpncon_mex_config: ENC[AES256_GCM,data:4i356X97sBoRliskmh5ewcEwZHkpo37IhPcemKVdWJgWFWtA+AhTeEo4KQ3dRA1H/n8VjVX7CKZKPDxpmHfcUlnTLT0agtOjjyjf60kWoL8noJqcbDB4wGiYT910rPToVnYMFk0H2lerYp+/n2bhg8BHxn++VlPOOZsgla4El+FNXUqhScpAawySPSF36ocdRJ3r3DuflIhnTBXxSZukMf9Ux1uaFldSG7KasCQlStKy9O2Odd2AvAuGXOHch5KecRPT3WnonQ8oDJpuxbeaosLmtJKHL9oeXHPId2Unc1GNoOpnDC3Y/xGnrPb9WFXWYOSQ/1A3mNKwnVq0FEhluVbqodES4PVIlCS0koiQJq15P15G2z0jO+OhAQrRI5vn3Fki5A==,iv:tQvTpzhl7F7niigAXl61FMHbg6QqI2R7yGD/C2lwOR4=,tag:c+CVLd6lGrAfm38pFXOXTw==,type:str]
#ENC[AES256_GCM,data:CO5nrcDbgymnEmCvuTexOBEMncuNM5lQ,iv:6HrxqSN6e7ODuz09MIFgPbIqDCKQySRDaKk5Wdu4HoQ=,tag:JBRjZeEdOg+trohfanO6Mg==,type:comment] #ENC[AES256_GCM,data:CO5nrcDbgymnEmCvuTexOBEMncuNM5lQ,iv:6HrxqSN6e7ODuz09MIFgPbIqDCKQySRDaKk5Wdu4HoQ=,tag:JBRjZeEdOg+trohfanO6Mg==,type:comment]
vaultwarden_admin_token: ENC[AES256_GCM,data:G1v3N064ci0Fw5EtTzaryailWpsv6f4w6eoHp2vjXIBtIlScdQk1Q0W+eDNRk8Wr2C3ysTXQNbyYismNsls+jeS3W+YqkKL4fnh3a5UTzQrMqvaH11n3ak0X9R9vmt+ZJXBrUrAOKJ6RPHJJSWenhjDB77kwEdQ=,iv:f8X+x/AdmZ3b3dtcSFrxGgA2tCgDRpgddjlVu3mdCmM=,tag:c0MXljVvhwOdvrb/8hWlsQ==,type:str] vaultwarden_admin_token: ENC[AES256_GCM,data:G1v3N064ci0Fw5EtTzaryailWpsv6f4w6eoHp2vjXIBtIlScdQk1Q0W+eDNRk8Wr2C3ysTXQNbyYismNsls+jeS3W+YqkKL4fnh3a5UTzQrMqvaH11n3ak0X9R9vmt+ZJXBrUrAOKJ6RPHJJSWenhjDB77kwEdQ=,iv:f8X+x/AdmZ3b3dtcSFrxGgA2tCgDRpgddjlVu3mdCmM=,tag:c0MXljVvhwOdvrb/8hWlsQ==,type:str]
#ENC[AES256_GCM,data:2ESzSsQZqKdjD7OXN8ZPThj6g9acJREe,iv:aDFPB0vs8NNo8ExLcJw7qtQvWbCb1XK6TJrHSK86qss=,tag:z+dypHAGUjEXP7Y9MHYWwg==,type:comment] #ENC[AES256_GCM,data:2ESzSsQZqKdjD7OXN8ZPThj6g9acJREe,iv:aDFPB0vs8NNo8ExLcJw7qtQvWbCb1XK6TJrHSK86qss=,tag:z+dypHAGUjEXP7Y9MHYWwg==,type:comment]
@@ -29,7 +31,7 @@ sops:
U0tmdFBuZnJES3piOTZNV0VKQmQ0eVUKCWRQ/flLzmpC64WyLoipklZBmrkpYiUg U0tmdFBuZnJES3piOTZNV0VKQmQ0eVUKCWRQ/flLzmpC64WyLoipklZBmrkpYiUg
PRu+itNolpPTHm96pe+P93g2iP0wgekG0cX21wkiU2xaLF3dY2FEIA== PRu+itNolpPTHm96pe+P93g2iP0wgekG0cX21wkiU2xaLF3dY2FEIA==
-----END AGE ENCRYPTED FILE----- -----END AGE ENCRYPTED FILE-----
lastmodified: "2025-10-08T20:46:50Z" lastmodified: "2025-10-09T00:57:39Z"
mac: ENC[AES256_GCM,data:kSWpiorgrx4Ohv/ZpUCKuBy+g3VZ95UjaOeotUwXJzao3qbHHAKIRLCJnlJPjMDyT3aZc8AF3urQunl65LDHYAisTV1LxTAeFSsWm4xkJ5DcyhvTHh1yxa+G9lGZ6mBQK60Hg92+fqwS43ObYz8hwoVeeKXc0ZSwDqI5d8gSF9o=,iv:gVonEcRQTupdLEYgAfgI10L86h6q+PFdgpLHNsLHB/8=,tag:Rd2nlookzmUc0ZWnC/f1Dg==,type:str] mac: ENC[AES256_GCM,data:NCsVDJnzb/x6/InOrE7Aco1bEfbcOA0t5mEQOO/tSM4uj5QjDeCTUyfzfK0A7LHdQSMvRpZLZuz7xDg/WA/QLe0F/CdA1h5HJucpop4NWN/bnJrVNIcik/YlvB6xSWojimZF9sbWZQQb2lPsn3GWt9wIHIHWlBhjIMfHHpLANq8=,iv:yQgpRv+xCvKcBYCyVac66egptSbF/8vi4TtQ5vL5xWQ=,tag:JdsI0yAecTnNO9UiE2IEwQ==,type:str]
unencrypted_suffix: _unencrypted unencrypted_suffix: _unencrypted
version: 3.11.0 version: 3.11.0

View File

@@ -46,6 +46,7 @@ alias ds='du -hs'
# shortcuts # shortcuts
alias vswap='cd ~/.local/state/nvim/swap' alias vswap='cd ~/.local/state/nvim/swap'
alias rswap='rm ~/.local/state/nvim/swap/*'
alias tn='lf ~/documents/holocron/notes/tech' alias tn='lf ~/documents/holocron/notes/tech'
alias nhc='lf ~/documents/holocron/work/nhc' alias nhc='lf ~/documents/holocron/work/nhc'
alias diary='cd ~/documents/holocron/notes/journal/diary' alias diary='cd ~/documents/holocron/notes/journal/diary'
@@ -65,6 +66,7 @@ alias egrep='egrep --color=auto'
# scripts # scripts
alias rebuild='sh ~/.nix/bin/rebuild.sh' alias rebuild='sh ~/.nix/bin/rebuild.sh'
alias perms='sudo sh ~/.nix/bin/perms.sh' alias perms='sudo sh ~/.nix/bin/perms.sh'
alias bb='sudo sh ~/.nix/bin/backup_browse.sh'
# tools # tools
alias v='nvim' alias v='nvim'