Compare commits
46 Commits
d1b81ce0db
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
0f90778b53
|
|||
|
dec32b9766
|
|||
|
64c13da521
|
|||
|
13f8c64f29
|
|||
|
27681c3ff5
|
|||
|
f83dca42ea
|
|||
|
94e550787e
|
|||
|
508d5a3525
|
|||
|
a42e02514e
|
|||
|
413f16fb6f
|
|||
|
c85cf06186
|
|||
|
63f9d3418c
|
|||
|
46460039af
|
|||
|
b9709dd655
|
|||
|
165eee9dd7
|
|||
|
fd28865071
|
|||
|
51afe1240d
|
|||
|
74909b9cd4
|
|||
|
bd4dd7ba23
|
|||
|
ae407c99c1
|
|||
|
d086bb61ed
|
|||
|
4d003329c7
|
|||
|
80ae32d799
|
|||
|
58ead2bb23
|
|||
|
5c3252746f
|
|||
|
68588f5d72
|
|||
|
ee0cb47e34
|
|||
|
c9866fd424
|
|||
|
b1ecb9f021
|
|||
|
da17ccc446
|
|||
|
73f8cb91c4
|
|||
|
29cff3bf84
|
|||
|
dd904c151d
|
|||
|
b02a06faa7
|
|||
|
7b76ffd34f
|
|||
|
4f79df9bf2
|
|||
|
7cfa0e6375
|
|||
|
3005a07e28
|
|||
|
8fa31858b4
|
|||
|
120e38fd46
|
|||
|
af6952fb1e
|
|||
|
c5dc30dd4a
|
|||
|
3d77a1478a
|
|||
|
f54c95980f
|
|||
|
f89a7e1813
|
|||
|
0993820675
|
@@ -6,12 +6,13 @@ return {
|
|||||||
lua = { "stylua" },
|
lua = { "stylua" },
|
||||||
luau = { "stylua" },
|
luau = { "stylua" },
|
||||||
rust = { "rustfmt", lsp_format = "fallback" },
|
rust = { "rustfmt", lsp_format = "fallback" },
|
||||||
javascript = { "prettier" },
|
javascript = { "prettierd" },
|
||||||
typescript = { "prettier" },
|
typescript = { "prettierd" },
|
||||||
json = { "prettier" },
|
json = { "prettierd" },
|
||||||
tsx = { "prettier" },
|
tsx = { "prettierd" },
|
||||||
nix = { "nixfmt" },
|
nix = { "nixfmt" },
|
||||||
go = { "gofmt" },
|
go = { "gofmt" },
|
||||||
|
svelte = { "prettierd" },
|
||||||
},
|
},
|
||||||
format_on_save = {
|
format_on_save = {
|
||||||
-- These options will be passed to conform.format()
|
-- These options will be passed to conform.format()
|
||||||
|
|||||||
@@ -69,6 +69,16 @@ local function setup_java()
|
|||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function setup_svelte()
|
||||||
|
require("lspconfig").svelte.setup({
|
||||||
|
root_dir = require("lspconfig.util").root_pattern("svelte.config.js", "svelte.config.ts", "package.json"),
|
||||||
|
})
|
||||||
|
end
|
||||||
|
|
||||||
|
local function setup_qml()
|
||||||
|
require("lspconfig").qmlls.setup({})
|
||||||
|
end
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{
|
{
|
||||||
"neovim/nvim-lspconfig",
|
"neovim/nvim-lspconfig",
|
||||||
@@ -101,14 +111,18 @@ return {
|
|||||||
"jdtls",
|
"jdtls",
|
||||||
"clangd",
|
"clangd",
|
||||||
"cmake",
|
"cmake",
|
||||||
|
"cssls",
|
||||||
|
"qmlls",
|
||||||
},
|
},
|
||||||
automatic_enable = { exclude = { "luau_lsp", "lua_ls" } },
|
automatic_enable = { exclude = { "luau_lsp", "lua_ls", "svelte" } },
|
||||||
})
|
})
|
||||||
setup_luau()
|
setup_luau()
|
||||||
setup_lua()
|
setup_lua()
|
||||||
setup_ts()
|
setup_ts()
|
||||||
setup_nix()
|
setup_nix()
|
||||||
setup_java()
|
setup_java()
|
||||||
|
setup_svelte()
|
||||||
|
setup_qml()
|
||||||
end,
|
end,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ return {
|
|||||||
"html",
|
"html",
|
||||||
"templ",
|
"templ",
|
||||||
"go",
|
"go",
|
||||||
|
"qmljs",
|
||||||
},
|
},
|
||||||
auto_install = true,
|
auto_install = true,
|
||||||
highlight = {
|
highlight = {
|
||||||
|
|||||||
1
.config/quickshell/.qmlls.ini
Symbolic link
1
.config/quickshell/.qmlls.ini
Symbolic link
@@ -0,0 +1 @@
|
|||||||
|
/run/user/1000/quickshell/vfs/d51aaef3451280dfdf2f7cf03412fd13/.qmlls.ini
|
||||||
52
.config/quickshell/Bar.qml
Normal file
52
.config/quickshell/Bar.qml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
//qmllint disable unqualified
|
||||||
|
//qmllint disable unused-imports
|
||||||
|
//qmllint disable uncreatable-type
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import "components"
|
||||||
|
|
||||||
|
PanelWindow {
|
||||||
|
id: root
|
||||||
|
anchors.top: true
|
||||||
|
anchors.left: true
|
||||||
|
anchors.right: true
|
||||||
|
implicitHeight: 34
|
||||||
|
color: "#1a1b26"
|
||||||
|
required property var modelData
|
||||||
|
screen: modelData
|
||||||
|
|
||||||
|
property int fontSize: 18
|
||||||
|
property color colBg: "#1a1b26"
|
||||||
|
property color colFg: "#a9b1d6"
|
||||||
|
property color colMuted: "#444b6a"
|
||||||
|
property color colCyan: "#0db9d7"
|
||||||
|
property color colPurple: "#ad8ee6"
|
||||||
|
property color colRed: "#f7768e"
|
||||||
|
property color colYellow: "#e0af68"
|
||||||
|
property color colBlue: "#7aa2f7"
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.preferredWidth: 30
|
||||||
|
Layout.preferredHeight: 28
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: "file:///home/luca/dotfiles/.config/quickshell/icons/nixos.png"
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Workspaces {}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
|
Clock {}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
.config/quickshell/components/Clock.qml
Normal file
34
.config/quickshell/components/Clock.qml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: clockRoot
|
||||||
|
implicitWidth: timeText.implicitWidth
|
||||||
|
implicitHeight: timeText.implicitHeight
|
||||||
|
|
||||||
|
property string dateTime: ""
|
||||||
|
|
||||||
|
function updateTime() {
|
||||||
|
dateTime = Qt.formatDateTime(new Date(), "ddd, MMM dd | HH:mm:ss");
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1000
|
||||||
|
running: true
|
||||||
|
repeat: true
|
||||||
|
triggeredOnStart: true
|
||||||
|
onTriggered: clockRoot.updateTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: timeText
|
||||||
|
text: clockRoot.dateTime
|
||||||
|
color: "#a9b1d6"
|
||||||
|
font {
|
||||||
|
pixelSize: 16
|
||||||
|
family: "Comic Relief"
|
||||||
|
bold: true
|
||||||
|
}
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
84
.config/quickshell/components/Workspaces.qml
Normal file
84
.config/quickshell/components/Workspaces.qml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
//qmllint disable unqualified
|
||||||
|
//qmllint disable unused-imports
|
||||||
|
//qmllint disable uncreatable-type
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Hyprland
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: Hyprland.workspaces.values
|
||||||
|
|
||||||
|
delegate: Rectangle {
|
||||||
|
id: workspaceComponent
|
||||||
|
Layout.preferredWidth: 30
|
||||||
|
Layout.preferredHeight: parent.height
|
||||||
|
color: "transparent"
|
||||||
|
|
||||||
|
property bool isActive: modelData.focused
|
||||||
|
property int lastActive: 1
|
||||||
|
property int wsId: modelData.id
|
||||||
|
property bool isHovered: mouseHandler.containsMouse
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "active"
|
||||||
|
when: isActive
|
||||||
|
PropertyChanges {
|
||||||
|
target: workspaceComponent
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.2)
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: underline
|
||||||
|
color: root.colPurple
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "hovered"
|
||||||
|
when: isHovered && !isActive
|
||||||
|
PropertyChanges {
|
||||||
|
target: workspaceComponent
|
||||||
|
color: Qt.rgba(1, 1, 1, 0.1)
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: underline
|
||||||
|
color: "#7aa2f7"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
transitions: Transition {
|
||||||
|
ColorAnimation {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: modelData.id
|
||||||
|
color: isActive ? "#a9b1d6" : "#7aa2f7"
|
||||||
|
font {
|
||||||
|
pixelSize: root.fontSize
|
||||||
|
bold: true
|
||||||
|
family: "Comic Relief"
|
||||||
|
}
|
||||||
|
anchors.centerIn: parent
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: underline
|
||||||
|
width: 30
|
||||||
|
height: 3
|
||||||
|
color: root.colBg
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseHandler
|
||||||
|
hoverEnabled: true
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: Hyprland.dispatch("workspace " + modelData.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
.config/quickshell/icons/nixos.png
Normal file
BIN
.config/quickshell/icons/nixos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 97 KiB |
15
.config/quickshell/shell.qml
Normal file
15
.config/quickshell/shell.qml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
//qmllint disable unqualified
|
||||||
|
//qmllint disable unused-imports
|
||||||
|
//qmllint disable uncreatable-type
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Layouts
|
||||||
|
import Quickshell
|
||||||
|
|
||||||
|
Scope {
|
||||||
|
Variants {
|
||||||
|
model: Quickshell.screens
|
||||||
|
delegate: Component {
|
||||||
|
Bar {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
nix/homelab/target
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
keys:
|
keys:
|
||||||
- &luca age1qu9y0dn5a704dggwmpaaurxqrhxm0qn8czgv5phka56y48sw7u8qkyn637
|
- &luca age13rqgrxh0fm23n3krf6v7yrrlnhhvs8256cusxqfs2l5xz8rgavssdhte4r
|
||||||
|
|
||||||
creation_rules:
|
creation_rules:
|
||||||
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
|
- path_regex: secrets/[^/]+\.(yaml|json|env|ini)$
|
||||||
|
|||||||
1
aliases/happly.sh
Executable file
1
aliases/happly.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
helmfile apply -f ~/dotfiles/nix/homelab/helm "$@"
|
||||||
1
aliases/kapply.sh
Executable file
1
aliases/kapply.sh
Executable file
@@ -0,0 +1 @@
|
|||||||
|
kubectl apply -k ~/dotfiles/nix/homelab/kustomize
|
||||||
60
nix/flake.lock
generated
60
nix/flake.lock
generated
@@ -81,6 +81,27 @@
|
|||||||
},
|
},
|
||||||
"parent": []
|
"parent": []
|
||||||
},
|
},
|
||||||
|
"fenix": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
],
|
||||||
|
"rust-analyzer-src": "rust-analyzer-src"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768632427,
|
||||||
|
"narHash": "sha256-Y6kP10exkn5UiK9ead2Gky8TFsFZSsyT4f69DMKm0Wo=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"rev": "edd560269f0d9ad75bd3da292ce4d9d27efdd22a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "fenix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"flake-compat": {
|
"flake-compat": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
@@ -186,17 +207,56 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"quickshell": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768689040,
|
||||||
|
"narHash": "sha256-Tlnr5BulJcMers/cb+YvmBQW4nKHjdKo9loInJkyO2k=",
|
||||||
|
"ref": "refs/heads/master",
|
||||||
|
"rev": "7a427ce1979ce7447e885c4f30129b40f3d466f5",
|
||||||
|
"revCount": 729,
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://git.outfoxxed.me/outfoxxed/quickshell"
|
||||||
|
}
|
||||||
|
},
|
||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"custom-fonts": "custom-fonts",
|
"custom-fonts": "custom-fonts",
|
||||||
|
"fenix": "fenix",
|
||||||
"home-manager": "home-manager",
|
"home-manager": "home-manager",
|
||||||
"nixos-wsl": "nixos-wsl",
|
"nixos-wsl": "nixos-wsl",
|
||||||
"nixpkgs": "nixpkgs_2",
|
"nixpkgs": "nixpkgs_2",
|
||||||
"nixpkgs-before": "nixpkgs-before",
|
"nixpkgs-before": "nixpkgs-before",
|
||||||
|
"quickshell": "quickshell",
|
||||||
"sops-nix": "sops-nix",
|
"sops-nix": "sops-nix",
|
||||||
"status-bar": "status-bar"
|
"status-bar": "status-bar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rust-analyzer-src": {
|
||||||
|
"flake": false,
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768468158,
|
||||||
|
"narHash": "sha256-DfifO/Se9ogmp5rxe/OwmRIz20/w6BsbWC1s4kL1Bzc=",
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"rev": "adbff8baedae53f9955fe60c0d470ecd77b4f548",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "rust-lang",
|
||||||
|
"ref": "nightly",
|
||||||
|
"repo": "rust-analyzer",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sops-nix": {
|
"sops-nix": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
|
|||||||
@@ -21,6 +21,15 @@
|
|||||||
url = "github:Mic92/sops-nix";
|
url = "github:Mic92/sops-nix";
|
||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fenix = {
|
||||||
|
url = "github:nix-community/fenix";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
quickshell = {
|
||||||
|
url = "git+https://git.outfoxxed.me/outfoxxed/quickshell";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
outputs =
|
outputs =
|
||||||
@@ -70,6 +79,7 @@
|
|||||||
inherit inputs;
|
inherit inputs;
|
||||||
meta = {
|
meta = {
|
||||||
hostname = host.name;
|
hostname = host.name;
|
||||||
|
architecture = host.architecture;
|
||||||
};
|
};
|
||||||
pkgs-before = import inputs.nixpkgs-before { system = host.architecture; };
|
pkgs-before = import inputs.nixpkgs-before { system = host.architecture; };
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
{ config, pkgs, ... }:
|
|
||||||
|
|
||||||
{
|
|
||||||
home.username = "luca";
|
|
||||||
home.homeDirectory = "/home/luca";
|
|
||||||
|
|
||||||
programs = {
|
|
||||||
git = import ./git.nix;
|
|
||||||
alacritty = {
|
|
||||||
enable = true;
|
|
||||||
settings.window.opacity = 0.6;
|
|
||||||
};
|
|
||||||
zsh = {
|
|
||||||
enable = true;
|
|
||||||
enableCompletion = true;
|
|
||||||
autosuggestion.enable = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
services.picom = {
|
|
||||||
enable = true;
|
|
||||||
vSync = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
xsession.windowManager.i3 = {
|
|
||||||
enable = true;
|
|
||||||
config = {
|
|
||||||
modifier = "Mod4";
|
|
||||||
defaultWorkspace = "workspace number 1";
|
|
||||||
terminal = "alacritty";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
home.stateVersion = "24.11";
|
|
||||||
|
|
||||||
programs.home-manager.enable = true;
|
|
||||||
|
|
||||||
}
|
|
||||||
3291
nix/homelab/Cargo.lock
generated
Normal file
3291
nix/homelab/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
27
nix/homelab/Cargo.toml
Normal file
27
nix/homelab/Cargo.toml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
[package]
|
||||||
|
name = "homelab"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
askama = "0.15.1"
|
||||||
|
clap = { version = "4.5.53", features = ["derive"] }
|
||||||
|
nom = "8.0.0"
|
||||||
|
reqwest = { version = "0.13.1", features = ["json"] }
|
||||||
|
serde = { version = "1.0.228", features = ["serde_derive"] }
|
||||||
|
ssh2 = "0.9.5"
|
||||||
|
thiserror = "2.0.17"
|
||||||
|
tokio = { version = "1.49.0", features = ["macros", "rt"] }
|
||||||
|
toml = { version = "0.9.10", optional = true }
|
||||||
|
urlencoding = "2"
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
".",
|
||||||
|
"cli"
|
||||||
|
]
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["file-config"]
|
||||||
|
file-config = ["dep:toml"]
|
||||||
2
nix/homelab/api/.gitignore
vendored
Normal file
2
nix/homelab/api/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
target
|
||||||
|
.env
|
||||||
2842
nix/homelab/api/Cargo.lock
generated
Normal file
2842
nix/homelab/api/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
19
nix/homelab/api/Cargo.toml
Normal file
19
nix/homelab/api/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "homelab-api"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
actix-web = "4.12.1"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
nom = "8.0.0"
|
||||||
|
rcon = { version = "0.6.0", features = ["rt-tokio"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
thiserror = "2.0.17"
|
||||||
|
tokio = "1.49.0"
|
||||||
|
tokio-util = { version = "0.7", features = ["io"] }
|
||||||
|
kube = { version = "2.0.1", features = ["client", "runtime", "ws"] }
|
||||||
|
k8s-openapi = { version = "0.26", features = ["v1_32"] }
|
||||||
|
chrono = "0.4"
|
||||||
|
futures = "0.3"
|
||||||
27
nix/homelab/api/flake.lock
generated
Normal file
27
nix/homelab/api/flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767799921,
|
||||||
|
"narHash": "sha256-r4GVX+FToWVE2My8VVZH4V0pTIpnu2ZE8/Z4uxGEMBE=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "d351d0653aeb7877273920cd3e823994e7579b0b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-25.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
38
nix/homelab/api/flake.nix
Normal file
38
nix/homelab/api/flake.nix
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
description = "Homelab api";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-25.11";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ nixpkgs, ... }@inputs:
|
||||||
|
let
|
||||||
|
systems = [
|
||||||
|
"x86_64-linux"
|
||||||
|
"aarch64-linux"
|
||||||
|
];
|
||||||
|
forAllSystems =
|
||||||
|
f:
|
||||||
|
nixpkgs.lib.genAttrs systems (
|
||||||
|
system:
|
||||||
|
f {
|
||||||
|
inherit system;
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells = forAllSystems (
|
||||||
|
{ system, pkgs }:
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
openssl
|
||||||
|
pkgconf
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
36
nix/homelab/api/src/endpoints/kubernetes.rs
Normal file
36
nix/homelab/api/src/endpoints/kubernetes.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use actix_web::{HttpResponse, web};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::kubernetes;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct RestoreRequest {
|
||||||
|
pub backup_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct RestoreResponse {
|
||||||
|
pub server: String,
|
||||||
|
pub job_name: String,
|
||||||
|
pub backup_file: String,
|
||||||
|
pub status: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// POST /api/minecraft/{server}/restore
|
||||||
|
pub async fn create_restore(
|
||||||
|
app_state: web::Data<AppState>,
|
||||||
|
path: web::Path<ServerPath>,
|
||||||
|
body: web::Json<RestoreRequest>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let job_name =
|
||||||
|
kubernetes::create_restore_job(&app_state.kube, &path.server, &body.backup_file).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Created().json(RestoreResponse {
|
||||||
|
server: path.server.clone(),
|
||||||
|
job_name,
|
||||||
|
backup_file: body.backup_file.clone(),
|
||||||
|
status: "created".to_string(),
|
||||||
|
}))
|
||||||
|
}
|
||||||
5
nix/homelab/api/src/endpoints/mod.rs
Normal file
5
nix/homelab/api/src/endpoints/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
pub mod server_stats;
|
||||||
|
pub mod server_uptime;
|
||||||
|
pub mod world_size;
|
||||||
|
|
||||||
|
pub(self) const MINECRAFT_NAMESPACE: &str = "minecraft";
|
||||||
36
nix/homelab/api/src/endpoints/server_stats.rs
Normal file
36
nix/homelab/api/src/endpoints/server_stats.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use actix_web::{HttpResponse, web};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
endpoints::server_uptime::{PodUptime, get_pod_uptime},
|
||||||
|
error::Result,
|
||||||
|
rcon::parse_online_list,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ServerStats {
|
||||||
|
pub status: String,
|
||||||
|
pub players_online: u16,
|
||||||
|
pub max_players: u16,
|
||||||
|
pub uptime: PodUptime,
|
||||||
|
pub world_size: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_server_stats(app_state: web::Data<AppState>) -> Result<HttpResponse> {
|
||||||
|
let list_response = app_state.rcon.cmd("list").await?;
|
||||||
|
|
||||||
|
let (_, (players_online, max_players)) =
|
||||||
|
parse_online_list(&list_response).map_err(|e| crate::error::Error::Parse(e.to_string()))?;
|
||||||
|
let uptime = get_pod_uptime(&app_state.kube, "main").await?;
|
||||||
|
|
||||||
|
let stats = ServerStats {
|
||||||
|
status: "Online".to_string(),
|
||||||
|
players_online,
|
||||||
|
max_players,
|
||||||
|
uptime,
|
||||||
|
world_size: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(stats))
|
||||||
|
}
|
||||||
56
nix/homelab/api/src/endpoints/server_uptime.rs
Normal file
56
nix/homelab/api/src/endpoints/server_uptime.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use actix_web::{HttpResponse, web};
|
||||||
|
use k8s_openapi::api::core::v1::Pod;
|
||||||
|
use kube::{Api, Client, api::ListParams};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
endpoints::MINECRAFT_NAMESPACE,
|
||||||
|
error::{Error, Result},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(serde::Serialize)]
|
||||||
|
pub(super) struct PodUptime {
|
||||||
|
pub seconds: i64,
|
||||||
|
pub started_at: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_uptime(
|
||||||
|
app_state: web::Data<AppState>,
|
||||||
|
path: web::Path<String>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let server = path.into_inner();
|
||||||
|
let uptime = get_pod_uptime(&app_state.kube, &server).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(uptime))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn find_minecraft_pod(client: &Client, server_name: &str) -> Result<Pod> {
|
||||||
|
let pods: Api<Pod> = Api::namespaced(client.clone(), MINECRAFT_NAMESPACE);
|
||||||
|
|
||||||
|
let label_selector = format!("app=minecraft-{server_name}");
|
||||||
|
let lp = ListParams::default().labels(&label_selector);
|
||||||
|
|
||||||
|
let pod_list = pods.list(&lp).await?;
|
||||||
|
|
||||||
|
pod_list
|
||||||
|
.items
|
||||||
|
.into_iter()
|
||||||
|
.next()
|
||||||
|
.ok_or_else(|| Error::PodNotFound(format!("minecraft-{server_name}")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn get_pod_uptime(client: &Client, server_name: &str) -> Result<PodUptime> {
|
||||||
|
let pod = find_minecraft_pod(client, server_name).await?;
|
||||||
|
|
||||||
|
let start_time = pod
|
||||||
|
.status
|
||||||
|
.and_then(|s| s.start_time)
|
||||||
|
.ok_or_else(|| Error::PodExec("pod has no start time".into()))?;
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
let duration = now.signed_duration_since(&start_time.0);
|
||||||
|
|
||||||
|
Ok(PodUptime {
|
||||||
|
seconds: duration.num_seconds(),
|
||||||
|
started_at: start_time.0.to_rfc3339(),
|
||||||
|
})
|
||||||
|
}
|
||||||
56
nix/homelab/api/src/endpoints/world_size.rs
Normal file
56
nix/homelab/api/src/endpoints/world_size.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use actix_web::{HttpResponse, web};
|
||||||
|
use k8s_openapi::api::core::v1::Pod;
|
||||||
|
use kube::{Api, Client};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
AppState,
|
||||||
|
endpoints::{MINECRAFT_NAMESPACE, server_uptime::find_minecraft_pod},
|
||||||
|
error::{Error, Result},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub struct WorldSizeResponse {
|
||||||
|
pub server: String,
|
||||||
|
pub size: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_world_size(
|
||||||
|
app_state: web::Data<AppState>,
|
||||||
|
path: web::Path<String>,
|
||||||
|
) -> Result<HttpResponse> {
|
||||||
|
let server = path.into_inner();
|
||||||
|
let size = get_size_inner(&app_state.kube, &server).await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().json(WorldSizeResponse {
|
||||||
|
server: server.clone(),
|
||||||
|
size,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_size_inner(client: &Client, server_name: &str) -> Result<String> {
|
||||||
|
let pod = find_minecraft_pod(client, server_name).await?;
|
||||||
|
let pod_name = pod
|
||||||
|
.metadata
|
||||||
|
.name
|
||||||
|
.ok_or_else(|| Error::PodNotFound("no pod name".into()))?;
|
||||||
|
|
||||||
|
let output = exec_in_pod(client, &pod_name, vec!["du", "-sh", "/data"]).await?;
|
||||||
|
|
||||||
|
let size = output
|
||||||
|
.split_whitespace()
|
||||||
|
.next()
|
||||||
|
.unwrap_or("unknown")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
Ok(size)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn exec_in_pod(client: &Client, pod_name: &str, command: &str) {
|
||||||
|
let pods: Api<Pod> = Api::namespaced(client.clone(), MINECRAFT_NAMESPACE);
|
||||||
|
|
||||||
|
const params = AttachParams {
|
||||||
|
stdin: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
}
|
||||||
33
nix/homelab/api/src/error.rs
Normal file
33
nix/homelab/api/src/error.rs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
use actix_web::{HttpResponse, ResponseError, body::BoxBody, http::StatusCode};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("rcon error: {0}")]
|
||||||
|
Rcon(#[from] rcon::Error),
|
||||||
|
#[error("parse error: {0}")]
|
||||||
|
Parse(String),
|
||||||
|
#[error("kubernetes error: {0}")]
|
||||||
|
Kube(#[from] kube::Error),
|
||||||
|
#[error("pod not found: {0}")]
|
||||||
|
PodNotFound(String),
|
||||||
|
#[error("pod exec error: {0}")]
|
||||||
|
PodExec(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for Error {
|
||||||
|
fn status_code(&self) -> StatusCode {
|
||||||
|
match self {
|
||||||
|
Self::Rcon(_) => StatusCode::SERVICE_UNAVAILABLE,
|
||||||
|
Self::PodNotFound(_) => StatusCode::NOT_FOUND,
|
||||||
|
Self::Kube(_) | Self::PodExec(_) => StatusCode::BAD_GATEWAY,
|
||||||
|
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||||
|
HttpResponse::build(self.status_code()).body(self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
219
nix/homelab/api/src/kubernetes.rs
Normal file
219
nix/homelab/api/src/kubernetes.rs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
use futures::TryStreamExt;
|
||||||
|
use k8s_openapi::api::batch::v1::Job;
|
||||||
|
use k8s_openapi::api::core::v1::Pod;
|
||||||
|
use kube::Client;
|
||||||
|
use kube::api::{Api, AttachParams, ListParams, PostParams};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
const NFS_SERVER: &str = "192.168.27.2";
|
||||||
|
const NFS_BACKUP_PATH: &str = "/backup/minecraft";
|
||||||
|
|
||||||
|
/// Find the Minecraft server pod by server name (e.g., "main", "creative")
|
||||||
|
|
||||||
|
/// Execute a command in a pod and return stdout
|
||||||
|
pub async fn exec_in_pod(client: &Client, pod_name: &str, command: Vec<&str>) -> Result<String> {
|
||||||
|
let pods: Api<Pod> = Api::namespaced(client.clone(), MINECRAFT_NAMESPACE);
|
||||||
|
|
||||||
|
let ap = AttachParams {
|
||||||
|
stdout: true,
|
||||||
|
stderr: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut attached = pods.exec(pod_name, command, &ap).await?;
|
||||||
|
|
||||||
|
let mut stdout_str = String::new();
|
||||||
|
if let Some(stdout) = attached.stdout() {
|
||||||
|
let bytes: Vec<u8> = tokio_util::io::ReaderStream::new(stdout)
|
||||||
|
.try_collect::<Vec<_>>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::PodExec(e.to_string()))?
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.collect();
|
||||||
|
stdout_str = String::from_utf8_lossy(&bytes).to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for exec to finish
|
||||||
|
attached
|
||||||
|
.join()
|
||||||
|
.await
|
||||||
|
.map_err(|e| Error::PodExec(e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(stdout_str.trim().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the world size by running du -sh /data in the pod
|
||||||
|
/// Get pod uptime by calculating time since pod started
|
||||||
|
|
||||||
|
/// Create a restore job for a Minecraft server
|
||||||
|
pub async fn create_restore_job(
|
||||||
|
client: &Client,
|
||||||
|
server_name: &str,
|
||||||
|
backup_file: &str,
|
||||||
|
) -> Result<String> {
|
||||||
|
let jobs: Api<Job> = Api::namespaced(client.clone(), MINECRAFT_NAMESPACE);
|
||||||
|
|
||||||
|
let job = build_restore_job(server_name, backup_file);
|
||||||
|
let job_name = job
|
||||||
|
.metadata
|
||||||
|
.name
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| "unknown".into());
|
||||||
|
|
||||||
|
jobs.create(&PostParams::default(), &job).await?;
|
||||||
|
|
||||||
|
Ok(job_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_restore_job(server_name: &str, backup_file: &str) -> Job {
|
||||||
|
use k8s_openapi::api::core::v1::{
|
||||||
|
Container, EnvVar, NFSVolumeSource, PersistentVolumeClaimVolumeSource, PodSecurityContext,
|
||||||
|
PodSpec, PodTemplateSpec, Volume, VolumeMount,
|
||||||
|
};
|
||||||
|
|
||||||
|
let job_name = format!(
|
||||||
|
"minecraft-restore-{server_name}-{}",
|
||||||
|
chrono::Utc::now().timestamp()
|
||||||
|
);
|
||||||
|
|
||||||
|
let restore_script = r#"
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Minecraft World Restore Job"
|
||||||
|
echo "=========================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
SERVER_NAME="${SERVER_NAME}"
|
||||||
|
BACKUP_FILE="${BACKUP_FILE:-latest.tgz}"
|
||||||
|
BACKUP_PATH="/backups/${BACKUP_FILE}"
|
||||||
|
DATA_DIR="/data"
|
||||||
|
|
||||||
|
echo "Configuration:"
|
||||||
|
echo " Server: ${SERVER_NAME}"
|
||||||
|
echo " Backup file: ${BACKUP_FILE}"
|
||||||
|
echo " Backup path: ${BACKUP_PATH}"
|
||||||
|
echo " Data directory: ${DATA_DIR}"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ ! -f "${BACKUP_PATH}" ]; then
|
||||||
|
echo "ERROR: Backup file not found: ${BACKUP_PATH}"
|
||||||
|
echo ""
|
||||||
|
echo "Available backups:"
|
||||||
|
ls -lh /backups/ | grep "minecraft-${SERVER_NAME}" || echo " (none found)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Backup file found"
|
||||||
|
echo " Size: $(du -hL ${BACKUP_PATH} | cut -f1)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ -d "${DATA_DIR}/world" ]; then
|
||||||
|
echo "WARNING: Existing world data found!"
|
||||||
|
echo "Removing existing world data..."
|
||||||
|
rm -rf "${DATA_DIR}/world" "${DATA_DIR}/world_nether" "${DATA_DIR}/world_the_end"
|
||||||
|
echo "Old world data removed"
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "Extracting backup..."
|
||||||
|
tar -xzf "${BACKUP_PATH}" -C "${DATA_DIR}/"
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Restore Complete!"
|
||||||
|
echo "=========================================="
|
||||||
|
"#;
|
||||||
|
|
||||||
|
Job {
|
||||||
|
metadata: kube::core::ObjectMeta {
|
||||||
|
name: Some(job_name),
|
||||||
|
namespace: Some(MINECRAFT_NAMESPACE.to_string()),
|
||||||
|
labels: Some(
|
||||||
|
[("app".to_string(), "minecraft-restore".to_string())]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
spec: Some(k8s_openapi::api::batch::v1::JobSpec {
|
||||||
|
backoff_limit: Some(0),
|
||||||
|
ttl_seconds_after_finished: Some(3600),
|
||||||
|
template: PodTemplateSpec {
|
||||||
|
metadata: Some(kube::core::ObjectMeta {
|
||||||
|
labels: Some(
|
||||||
|
[("app".to_string(), "minecraft-restore".to_string())]
|
||||||
|
.into_iter()
|
||||||
|
.collect(),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
spec: Some(PodSpec {
|
||||||
|
restart_policy: Some("Never".to_string()),
|
||||||
|
security_context: Some(PodSecurityContext {
|
||||||
|
fs_group: Some(2000),
|
||||||
|
run_as_user: Some(1000),
|
||||||
|
run_as_group: Some(3000),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
containers: vec![Container {
|
||||||
|
name: "restore".to_string(),
|
||||||
|
image: Some("busybox:latest".to_string()),
|
||||||
|
command: Some(vec!["sh".to_string(), "-c".to_string()]),
|
||||||
|
args: Some(vec![restore_script.to_string()]),
|
||||||
|
env: Some(vec![
|
||||||
|
EnvVar {
|
||||||
|
name: "SERVER_NAME".to_string(),
|
||||||
|
value: Some(server_name.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
EnvVar {
|
||||||
|
name: "BACKUP_FILE".to_string(),
|
||||||
|
value: Some(backup_file.to_string()),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
volume_mounts: Some(vec![
|
||||||
|
VolumeMount {
|
||||||
|
name: "data".to_string(),
|
||||||
|
mount_path: "/data".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
VolumeMount {
|
||||||
|
name: "backups".to_string(),
|
||||||
|
mount_path: "/backups".to_string(),
|
||||||
|
read_only: Some(true),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}],
|
||||||
|
volumes: Some(vec![
|
||||||
|
Volume {
|
||||||
|
name: "data".to_string(),
|
||||||
|
persistent_volume_claim: Some(PersistentVolumeClaimVolumeSource {
|
||||||
|
claim_name: format!("minecraft-{server_name}-datadir"),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
Volume {
|
||||||
|
name: "backups".to_string(),
|
||||||
|
nfs: Some(NFSVolumeSource {
|
||||||
|
server: NFS_SERVER.to_string(),
|
||||||
|
path: NFS_BACKUP_PATH.to_string(),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
83
nix/homelab/api/src/main.rs
Normal file
83
nix/homelab/api/src/main.rs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
mod endpoints;
|
||||||
|
mod error;
|
||||||
|
mod rcon;
|
||||||
|
|
||||||
|
use std::env;
|
||||||
|
|
||||||
|
use actix_web::{App, HttpServer, web};
|
||||||
|
use kube::Client;
|
||||||
|
|
||||||
|
use crate::rcon::RconClient;
|
||||||
|
|
||||||
|
pub struct AppState {
|
||||||
|
rcon: RconClient,
|
||||||
|
kube: Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Env {
|
||||||
|
rcon_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
fn load_env() -> Env {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
Env {
|
||||||
|
rcon_password: env::var("RCON_PASSWORD")
|
||||||
|
.expect("environment variable RCON_PASSWORD must be set"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
fn load_env() -> Env {
|
||||||
|
Env {
|
||||||
|
rcon_password: env::var("RCON_PASSWORD")
|
||||||
|
.expect("environment variable RCON_PASSWORD must be set"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[actix_web::main]
|
||||||
|
async fn main() -> std::io::Result<()> {
|
||||||
|
let env = load_env();
|
||||||
|
|
||||||
|
// Initialize Kubernetes client
|
||||||
|
// Uses in-cluster config when running in k3s, falls back to ~/.kube/config locally
|
||||||
|
let kube_client = Client::try_default()
|
||||||
|
.await
|
||||||
|
.expect("failed to create Kubernetes client");
|
||||||
|
|
||||||
|
let app_state = web::Data::new(AppState {
|
||||||
|
rcon: RconClient::new(env.rcon_password),
|
||||||
|
kube: kube_client,
|
||||||
|
});
|
||||||
|
|
||||||
|
HttpServer::new(move || {
|
||||||
|
App::new()
|
||||||
|
.app_data(app_state.clone())
|
||||||
|
.route(
|
||||||
|
"/",
|
||||||
|
web::get()
|
||||||
|
.to(async || concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION"))),
|
||||||
|
)
|
||||||
|
.service(
|
||||||
|
web::scope("/api")
|
||||||
|
.route(
|
||||||
|
"/minecraft-server-stats",
|
||||||
|
web::get().to(endpoints::server_stats::get_server_stats),
|
||||||
|
)
|
||||||
|
// .route(
|
||||||
|
// "/minecraft/{server}/world-size",
|
||||||
|
// web::get().to(endpoints::kubernetes::get_world_size),
|
||||||
|
// )
|
||||||
|
.route(
|
||||||
|
"/minecraft/{server}/uptime",
|
||||||
|
web::get().to(endpoints::server_uptime::get_uptime),
|
||||||
|
), // .route(
|
||||||
|
// "/minecraft/{server}/restore",
|
||||||
|
// web::post().to(endpoints::kubernetes::create_restore),
|
||||||
|
// ),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.bind(("127.0.0.1", 8080))?
|
||||||
|
.run()
|
||||||
|
.await
|
||||||
|
}
|
||||||
53
nix/homelab/api/src/rcon.rs
Normal file
53
nix/homelab/api/src/rcon.rs
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
use nom::{
|
||||||
|
IResult, Parser, bytes::complete::tag, character::complete::digit1, combinator::map_res,
|
||||||
|
sequence::preceded,
|
||||||
|
};
|
||||||
|
use rcon::Connection;
|
||||||
|
use tokio::{net::TcpStream, sync::Mutex};
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
fn parse_u16(input: &str) -> IResult<&str, u16> {
|
||||||
|
map_res(digit1, |s: &str| s.parse::<u16>()).parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_online_list(input: &str) -> IResult<&str, (u16, u16)> {
|
||||||
|
let (remaining, (online, max)) = (
|
||||||
|
preceded(tag("There are "), parse_u16),
|
||||||
|
preceded(tag(" of a max of "), parse_u16),
|
||||||
|
)
|
||||||
|
.parse(input)?;
|
||||||
|
Ok((remaining, (online, max)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct RconClient {
|
||||||
|
connection: Mutex<Option<Connection<TcpStream>>>,
|
||||||
|
rcon_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RconClient {
|
||||||
|
pub fn new(rcon_password: String) -> Self {
|
||||||
|
Self {
|
||||||
|
connection: None.into(),
|
||||||
|
rcon_password,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn cmd(&self, command: &str) -> Result<String> {
|
||||||
|
let mut connection = self.connection.lock().await;
|
||||||
|
|
||||||
|
if connection.is_none() {
|
||||||
|
let conn = create_connection(&self.rcon_password).await?;
|
||||||
|
*connection = Some(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(connection.as_mut().unwrap().cmd(command).await?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_connection(rcon_password: &str) -> Result<Connection<TcpStream>> {
|
||||||
|
Ok(Connection::<TcpStream>::builder()
|
||||||
|
.enable_minecraft_quirks(true)
|
||||||
|
.connect("192.168.27.12:25575", rcon_password)
|
||||||
|
.await?)
|
||||||
|
}
|
||||||
2
nix/homelab/askama.toml
Normal file
2
nix/homelab/askama.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[general]
|
||||||
|
dirs = ["src/templates"]
|
||||||
21
nix/homelab/cli/Cargo.toml
Normal file
21
nix/homelab/cli/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "homelab-cli"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow = "1.0.100"
|
||||||
|
askama = "0.15.1"
|
||||||
|
chrono = "0.4.43"
|
||||||
|
clap = { version = "4.5.54", features = ["derive"] }
|
||||||
|
dialoguer = "0.12.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
indicatif = "0.18.3"
|
||||||
|
k8s-openapi = { version = "0.27.0", features = ["latest", "schemars", "v1_35"] }
|
||||||
|
kube = { version = "3.0.0", features = ["runtime", "derive"] }
|
||||||
|
schemars = "1.2.0"
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
serde_yaml = "0.9.34"
|
||||||
|
thiserror = "2.0.18"
|
||||||
|
thiserror-ext = "0.3.0"
|
||||||
|
tokio = { version = "1.49.0", features = ["macros", "rt"] }
|
||||||
3
nix/homelab/cli/askama.toml
Normal file
3
nix/homelab/cli/askama.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[[escaper]]
|
||||||
|
path = "askama::filters::Text"
|
||||||
|
extensions = ["yaml"]
|
||||||
144
nix/homelab/cli/src/commands/minecraft.rs
Normal file
144
nix/homelab/cli/src/commands/minecraft.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
use askama::Template;
|
||||||
|
use clap::{Args, Subcommand};
|
||||||
|
use futures::{AsyncBufReadExt, StreamExt, TryStreamExt};
|
||||||
|
use k8s_openapi::api::apps::v1::Deployment;
|
||||||
|
use k8s_openapi::api::batch::v1::Job;
|
||||||
|
use k8s_openapi::api::core::v1::Pod;
|
||||||
|
use kube::api::{Api, LogParams, Patch, PatchParams, PostParams};
|
||||||
|
use kube::runtime::{WatchStreamExt, watcher};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::State;
|
||||||
|
use crate::error::{ErrorKind, Result};
|
||||||
|
use crate::reporter::Reporter;
|
||||||
|
|
||||||
|
const NAMESPACE: &str = "minecraft";
|
||||||
|
|
||||||
|
#[derive(Debug, Args)]
|
||||||
|
pub struct MinecraftCommand {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: MinecraftSubcommand,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Subcommand)]
|
||||||
|
enum MinecraftSubcommand {
|
||||||
|
/// Backup a minecraft world
|
||||||
|
Backup {
|
||||||
|
/// the world to backup
|
||||||
|
#[arg(short, long)]
|
||||||
|
world: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "backup-job.yaml")]
|
||||||
|
struct BackupJobTemplate<'a> {
|
||||||
|
world: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MinecraftCommand {
|
||||||
|
pub async fn run(&self, state: State) -> Result<()> {
|
||||||
|
match &self.command {
|
||||||
|
MinecraftSubcommand::Backup { world } => backup_world(state, world).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn backup_world(state: State, world: &str) -> Result<()> {
|
||||||
|
let reporter = Reporter::new();
|
||||||
|
let job_name = format!("minecraft-{}-backup", world);
|
||||||
|
|
||||||
|
reporter.log(format!("Scaling deployment minecraft-{world}"));
|
||||||
|
scale_deployment(&state.client, NAMESPACE, &format!("minecraft-{world}"), 0).await?;
|
||||||
|
|
||||||
|
reporter.status("Creating backup job...");
|
||||||
|
|
||||||
|
let job = build_backup_job(world)?;
|
||||||
|
let jobs: Api<Job> = Api::namespaced(state.client.clone(), NAMESPACE);
|
||||||
|
jobs.create(&PostParams::default(), &job).await?;
|
||||||
|
|
||||||
|
reporter.status("Waiting for pod to start...");
|
||||||
|
|
||||||
|
let pods: Api<Pod> = Api::namespaced(state.client.clone(), NAMESPACE);
|
||||||
|
let pod_name = wait_for_job_pod(&pods, &job_name).await?;
|
||||||
|
|
||||||
|
reporter.status("Running backup...");
|
||||||
|
|
||||||
|
stream_pod_logs(&pods, &pod_name, &reporter).await?;
|
||||||
|
|
||||||
|
let job = jobs.get(&job_name).await?;
|
||||||
|
let status = job.status.as_ref();
|
||||||
|
let succeeded = status.and_then(|s| s.succeeded).unwrap_or(0);
|
||||||
|
let failed = status.and_then(|s| s.failed).unwrap_or(0);
|
||||||
|
|
||||||
|
reporter.log(format!("Scaling deployment minecraft-{world}, replicas: 1"));
|
||||||
|
scale_deployment(&state.client, NAMESPACE, &format!("minecraft-{world}"), 1).await?;
|
||||||
|
if succeeded > 0 {
|
||||||
|
reporter.success("Backup complete");
|
||||||
|
Ok(())
|
||||||
|
} else if failed > 0 {
|
||||||
|
reporter.fail("Backup job failed");
|
||||||
|
Err(ErrorKind::BackupFailed("Job failed".to_string()).into())
|
||||||
|
} else {
|
||||||
|
reporter.fail("Backup job status unknown");
|
||||||
|
Err(ErrorKind::BackupFailed("Unknown status".to_string()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn scale_deployment(
|
||||||
|
client: &kube::Client,
|
||||||
|
namespace: &str,
|
||||||
|
name: &str,
|
||||||
|
replicas: i32,
|
||||||
|
) -> Result<()> {
|
||||||
|
let deployments: Api<Deployment> = Api::namespaced(client.clone(), namespace);
|
||||||
|
let patch = json!({ "spec": { "replicas": replicas } });
|
||||||
|
deployments
|
||||||
|
.patch(name, &PatchParams::default(), &Patch::Merge(&patch))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_job_pod(pods: &Api<Pod>, job_name: &str) -> Result<String> {
|
||||||
|
let label_selector = format!("job-name={}", job_name);
|
||||||
|
let config = watcher::Config::default().labels(&label_selector);
|
||||||
|
|
||||||
|
let mut stream = watcher(pods.clone(), config).applied_objects().boxed();
|
||||||
|
|
||||||
|
while let Some(pod) = stream.try_next().await? {
|
||||||
|
let name = pod.metadata.name.as_deref().unwrap_or_default();
|
||||||
|
let phase = pod
|
||||||
|
.status
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| s.phase.as_deref())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if phase == "Running" || phase == "Succeeded" || phase == "Failed" {
|
||||||
|
return Ok(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ErrorKind::BackupFailed("Pod never started".to_string()).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_backup_job(world: &str) -> Result<Job> {
|
||||||
|
let template = BackupJobTemplate { world };
|
||||||
|
let yaml = template.render()?;
|
||||||
|
Ok(serde_yaml::from_str(&yaml)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stream_pod_logs(pods: &Api<Pod>, pod_name: &str, reporter: &Reporter) -> Result<()> {
|
||||||
|
let params = LogParams {
|
||||||
|
follow: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = pods.log_stream(pod_name, ¶ms).await?;
|
||||||
|
let mut lines = stream.lines();
|
||||||
|
|
||||||
|
while let Some(line) = lines.try_next().await? {
|
||||||
|
reporter.log_event(&line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
8
nix/homelab/cli/src/commands/mod.rs
Normal file
8
nix/homelab/cli/src/commands/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use clap::Subcommand;
|
||||||
|
mod minecraft;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// minecraft management
|
||||||
|
Minecraft(minecraft::MinecraftCommand),
|
||||||
|
}
|
||||||
20
nix/homelab/cli/src/error.rs
Normal file
20
nix/homelab/cli/src/error.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug, thiserror_ext::Box)]
|
||||||
|
#[thiserror_ext(newtype(name = Error))]
|
||||||
|
pub enum ErrorKind {
|
||||||
|
#[error("kube error: {0}")]
|
||||||
|
Kube(#[from] kube::Error),
|
||||||
|
#[error("watcher error: {0}")]
|
||||||
|
Watcher(#[from] kube::runtime::watcher::Error),
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("backup failed: {0}")]
|
||||||
|
BackupFailed(String),
|
||||||
|
#[error("template error: {0}")]
|
||||||
|
Template(#[from] askama::Error),
|
||||||
|
#[error("error deserializing yaml: {0}")]
|
||||||
|
Yaml(#[from] serde_yaml::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
57
nix/homelab/cli/src/main.rs
Normal file
57
nix/homelab/cli/src/main.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
mod commands;
|
||||||
|
mod error;
|
||||||
|
mod reporter;
|
||||||
|
|
||||||
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
|
use clap::{CommandFactory, Parser};
|
||||||
|
|
||||||
|
use crate::{commands::Commands, error::Result};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version, long_about = "homelab cli")]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Option<Commands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AppState {
|
||||||
|
client: kube::Client,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct State(Arc<AppState>);
|
||||||
|
|
||||||
|
impl State {
|
||||||
|
pub fn new(inner: AppState) -> Self {
|
||||||
|
Self(Arc::new(inner))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for State {
|
||||||
|
type Target = AppState;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
pub async fn new() -> Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
client: kube::Client::try_default().await?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
let app_state = State::new(AppState::new().await?);
|
||||||
|
|
||||||
|
match cli.command {
|
||||||
|
Some(Commands::Minecraft(cmd)) => cmd.run(app_state.clone()).await?,
|
||||||
|
_ => Cli::command().print_long_help()?,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
52
nix/homelab/cli/src/reporter.rs
Normal file
52
nix/homelab/cli/src/reporter.rs
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
|
pub struct Reporter {
|
||||||
|
spinner: ProgressBar,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TICK_CHARS: &str = "⣷⣯⣟⡿⢿⣻⣽⣾";
|
||||||
|
|
||||||
|
impl Reporter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let spinner = ProgressBar::new_spinner();
|
||||||
|
spinner.set_style(
|
||||||
|
ProgressStyle::with_template(
|
||||||
|
"{prefix:.dim}{msg:>8.214/yellow} {spinner} [{elapsed_precise}]",
|
||||||
|
)
|
||||||
|
.unwrap()
|
||||||
|
.tick_chars(TICK_CHARS),
|
||||||
|
);
|
||||||
|
spinner.enable_steady_tick(std::time::Duration::from_millis(100));
|
||||||
|
Self { spinner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self, msg: impl Into<String>) {
|
||||||
|
self.spinner.set_message(msg.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_event(&self, line: &str) {
|
||||||
|
self.spinner.suspend(|| {
|
||||||
|
println!(" │ {}", line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log<T: Display>(&self, text: T) {
|
||||||
|
self.spinner.suspend(|| println!("{}", text))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn success(&self, msg: &str) {
|
||||||
|
self.spinner.finish_with_message(format!("✓ {}", msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fail(&self, msg: &str) {
|
||||||
|
self.spinner.finish_with_message(format!("✗ {}", msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Reporter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
40
nix/homelab/cli/templates/backup-job.yaml
Normal file
40
nix/homelab/cli/templates/backup-job.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: minecraft-{{ world }}-backup
|
||||||
|
namespace: minecraft
|
||||||
|
labels:
|
||||||
|
app: minecraft-backup
|
||||||
|
world: {{ world }}
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 300
|
||||||
|
backoffLimit: 0
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
job-name: minecraft-{{ world }}-backup
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: backup
|
||||||
|
image: busybox
|
||||||
|
command:
|
||||||
|
- "sh"
|
||||||
|
- "-c"
|
||||||
|
- |
|
||||||
|
tar -czvf /backups/minecraft-{{ world }}-manual.tar.gz -C /data .
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
readOnly: true
|
||||||
|
- name: backups
|
||||||
|
mountPath: /backups
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: minecraft-{{ world }}-datadir
|
||||||
|
readOnly: true
|
||||||
|
- name: backups
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /backup/minecraft
|
||||||
1
nix/homelab/cli/templates/sync.yaml
Normal file
1
nix/homelab/cli/templates/sync.yaml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
130
nix/homelab/config.toml
Normal file
130
nix/homelab/config.toml
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
routes = [
|
||||||
|
{
|
||||||
|
name = "gitea",
|
||||||
|
hostname = "git",
|
||||||
|
namespace = "git",
|
||||||
|
service = "gitea-http",
|
||||||
|
port = 3000,
|
||||||
|
private = false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind = "TCP",
|
||||||
|
name = "gitea-ssh",
|
||||||
|
namespace = "git",
|
||||||
|
entrypoint = "ssh",
|
||||||
|
service = "gitea-ssh",
|
||||||
|
port = 22,
|
||||||
|
private = false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "home-assistant",
|
||||||
|
namespace = "home",
|
||||||
|
port = 8080,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "sonarr",
|
||||||
|
namespace = "media",
|
||||||
|
port = 8989,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "longhorn",
|
||||||
|
hostname = "storage",
|
||||||
|
namespace = "longhorn-system",
|
||||||
|
service = "longhorn-frontend",
|
||||||
|
port = 80,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "pihole",
|
||||||
|
namespace = "pihole-system",
|
||||||
|
service = "pihole-web",
|
||||||
|
port = 80,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind = "TCP",
|
||||||
|
name = "minecraft-router",
|
||||||
|
hostname = "minecraft",
|
||||||
|
namespace = "minecraft",
|
||||||
|
entrypoint = "minecraft",
|
||||||
|
service = "minecraft-router-mc-router",
|
||||||
|
port = 25565,
|
||||||
|
private = false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind = "TCP",
|
||||||
|
name = "minecraft-rcon",
|
||||||
|
namespace = "minecraft",
|
||||||
|
entrypoint = "rcon",
|
||||||
|
service = "minecraft-main-rcon",
|
||||||
|
port = 25575,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "prowlarr",
|
||||||
|
namespace = "media",
|
||||||
|
port = 9696,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "radarr",
|
||||||
|
namespace = "media",
|
||||||
|
port = 7878,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "qbittorrent",
|
||||||
|
hostname = "qbit",
|
||||||
|
namespace = "media",
|
||||||
|
port = 8080,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "flaresolverr",
|
||||||
|
hostname = "flare",
|
||||||
|
namespace = "media",
|
||||||
|
port = 8191,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "jellyfin",
|
||||||
|
hostname = "media",
|
||||||
|
namespace = "media",
|
||||||
|
port = 8096,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "grafana",
|
||||||
|
namespace = "monitoring",
|
||||||
|
service = "prometheus-stack-grafana",
|
||||||
|
port = 80,
|
||||||
|
private = true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name = "mesh",
|
||||||
|
namespace = "networking",
|
||||||
|
service = "headscale",
|
||||||
|
port = 8080,
|
||||||
|
private = false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
[pihole]
|
||||||
|
url = "https://pihole.lucalise.ca"
|
||||||
|
password_file = "/run/secrets/pihole_password"
|
||||||
|
extra_hosts = [
|
||||||
|
"192.168.27.12 mc-rocket.privatedns.org",
|
||||||
|
"192.168.27.12 mc-rocket-creative.privatedns.org",
|
||||||
|
"192.168.27.12 mc-rocket-creative.duckdns.org",
|
||||||
|
"192.168.27.12 git.lucalise.ca",
|
||||||
|
"192.168.27.2 rufus",
|
||||||
|
"192.168.27.11 kube"
|
||||||
|
]
|
||||||
|
|
||||||
|
[router]
|
||||||
|
host = "192.168.15.1:22"
|
||||||
|
user = "luca"
|
||||||
|
key_path = "/home/luca/.ssh/id_ed25519"
|
||||||
|
lease_file = "/var/dhcpd/var/db/dhcpd.leases"
|
||||||
@@ -20,6 +20,16 @@
|
|||||||
architecture = "x86_64-linux";
|
architecture = "x86_64-linux";
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
systems = [ "x86_64-linux" ];
|
||||||
|
forAllSystems =
|
||||||
|
f:
|
||||||
|
nixpkgs.lib.genAttrs systems (
|
||||||
|
system:
|
||||||
|
f {
|
||||||
|
inherit system;
|
||||||
|
pkgs = nixpkgs.legacyPackages.${system};
|
||||||
|
}
|
||||||
|
);
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
nixosConfigurations = builtins.listToAttrs (
|
nixosConfigurations = builtins.listToAttrs (
|
||||||
@@ -49,5 +59,16 @@
|
|||||||
};
|
};
|
||||||
}) nodes
|
}) nodes
|
||||||
);
|
);
|
||||||
|
devShells = forAllSystems (
|
||||||
|
{ system, pkgs }:
|
||||||
|
{
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
buildInputs = with pkgs; [
|
||||||
|
openssl
|
||||||
|
pkgconf
|
||||||
|
];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ releases:
|
|||||||
- crds:
|
- crds:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|
||||||
|
- name: prometheus-stack
|
||||||
|
chart: oci://ghcr.io/prometheus-community/charts/kube-prometheus-stack
|
||||||
|
namespace: monitoring
|
||||||
|
version: 80.9.2
|
||||||
|
values:
|
||||||
|
- values/prometheus.yaml
|
||||||
|
|
||||||
# Git
|
# Git
|
||||||
- name: gitea
|
- name: gitea
|
||||||
namespace: git
|
namespace: git
|
||||||
@@ -38,6 +45,13 @@ releases:
|
|||||||
values:
|
values:
|
||||||
- values/gitea.yaml
|
- values/gitea.yaml
|
||||||
|
|
||||||
|
- name: gitea-runners
|
||||||
|
namespace: git
|
||||||
|
chart: gitea-charts/actions
|
||||||
|
version: 0.0.2
|
||||||
|
values:
|
||||||
|
- values/gitea-runners.yaml
|
||||||
|
|
||||||
# Storage
|
# Storage
|
||||||
- name: longhorn
|
- name: longhorn
|
||||||
namespace: longhorn-system
|
namespace: longhorn-system
|
||||||
@@ -46,6 +60,8 @@ releases:
|
|||||||
values:
|
values:
|
||||||
- defaultSettings:
|
- defaultSettings:
|
||||||
defaultReplicaCount: 1
|
defaultReplicaCount: 1
|
||||||
|
- defaultBackupStore:
|
||||||
|
backupTarget: nfs://192.168.27.2:/backup/longhorn
|
||||||
- persistence:
|
- persistence:
|
||||||
defaultClassReplicaCount: 1
|
defaultClassReplicaCount: 1
|
||||||
|
|
||||||
@@ -56,6 +72,14 @@ releases:
|
|||||||
values:
|
values:
|
||||||
- values/pihole.yaml
|
- values/pihole.yaml
|
||||||
|
|
||||||
|
# Media
|
||||||
|
- name: jellyfin
|
||||||
|
namespace: media
|
||||||
|
chart: jellyfin/jellyfin
|
||||||
|
version: 2.7.0
|
||||||
|
values:
|
||||||
|
- values/media/jellyfin.yaml
|
||||||
|
|
||||||
# Minecraft
|
# Minecraft
|
||||||
- name: minecraft-router
|
- name: minecraft-router
|
||||||
namespace: minecraft
|
namespace: minecraft
|
||||||
@@ -78,6 +102,13 @@ releases:
|
|||||||
values:
|
values:
|
||||||
- values/minecraft/creative.yaml
|
- values/minecraft/creative.yaml
|
||||||
|
|
||||||
|
- name: minecraft-old
|
||||||
|
namespace: minecraft
|
||||||
|
chart: minecraft-charts/minecraft
|
||||||
|
version: 5.0.0
|
||||||
|
values:
|
||||||
|
- values/minecraft/old.yaml
|
||||||
|
|
||||||
- name: home-assistant
|
- name: home-assistant
|
||||||
namespace: home
|
namespace: home
|
||||||
chart: home-assistant/home-assistant
|
chart: home-assistant/home-assistant
|
||||||
|
|||||||
5
nix/homelab/helm/values/gitea-runners.yaml
Normal file
5
nix/homelab/helm/values/gitea-runners.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
enabled: true
|
||||||
|
statefulset:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: rufus
|
||||||
|
giteaRootURL: https://git.lucalise.ca
|
||||||
@@ -13,10 +13,13 @@ gitea:
|
|||||||
config:
|
config:
|
||||||
repository:
|
repository:
|
||||||
ROOT: /mnt/git-data/git/repositories
|
ROOT: /mnt/git-data/git/repositories
|
||||||
|
server:
|
||||||
|
ROOT_URL: https://git.lucalise.ca
|
||||||
|
SSH_DOMAIN: git.lucalise.ca
|
||||||
database:
|
database:
|
||||||
DB_TYPE: sqlite3
|
DB_TYPE: sqlite3
|
||||||
session:
|
session:
|
||||||
PROVIDER: memory
|
PROVIDER: db
|
||||||
cache:
|
cache:
|
||||||
ADAPTER: memory
|
ADAPTER: memory
|
||||||
queue:
|
queue:
|
||||||
|
|||||||
21
nix/homelab/helm/values/media/jellyfin.yaml
Normal file
21
nix/homelab/helm/values/media/jellyfin.yaml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
persistence:
|
||||||
|
media:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 200m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /mnt/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /data
|
||||||
@@ -3,23 +3,37 @@ resources:
|
|||||||
cpu: 1
|
cpu: 1
|
||||||
memory: 500Mi
|
memory: 500Mi
|
||||||
limits:
|
limits:
|
||||||
memory: 4Gi
|
memory: 5Gi
|
||||||
cpu: 2000m
|
cpu: 2000m
|
||||||
|
|
||||||
minecraftServer:
|
minecraftServer:
|
||||||
eula: "TRUE"
|
eula: "TRUE"
|
||||||
type: "PAPER"
|
type: "FABRIC"
|
||||||
version: "1.21.11"
|
version: "1.21.11"
|
||||||
difficulty: hard
|
difficulty: hard
|
||||||
motd: "A Minecraft Server."
|
motd: "A Minecraft Server."
|
||||||
gameMode: creative
|
gameMode: creative
|
||||||
memory: 4G
|
memory: 5G
|
||||||
rcon:
|
rcon:
|
||||||
enabled: true
|
enabled: true
|
||||||
withGeneratedPassword: false
|
withGeneratedPassword: false
|
||||||
port: 25575
|
port: 25575
|
||||||
existingSecret: rcon-credentials
|
existingSecret: rcon-credentials
|
||||||
secretKey: rcon-password
|
secretKey: rcon-password
|
||||||
|
modrinth:
|
||||||
|
projects:
|
||||||
|
- fabric-api
|
||||||
|
- tree-vein-miner
|
||||||
|
- lithium
|
||||||
|
- servux
|
||||||
|
- ferrite-core
|
||||||
|
- carpet
|
||||||
|
- elytra-trims
|
||||||
|
- fabric-language-kotlin
|
||||||
|
- c2me-fabric
|
||||||
|
- scalablelux
|
||||||
|
- axiom
|
||||||
|
|
||||||
|
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: kube
|
kubernetes.io/hostname: kube
|
||||||
|
|||||||
@@ -3,22 +3,35 @@ resources:
|
|||||||
cpu: 1
|
cpu: 1
|
||||||
memory: 500Mi
|
memory: 500Mi
|
||||||
limits:
|
limits:
|
||||||
memory: 6Gi
|
memory: 7Gi
|
||||||
cpu: 2500m
|
cpu: 2500m
|
||||||
|
|
||||||
minecraftServer:
|
minecraftServer:
|
||||||
eula: "TRUE"
|
eula: "TRUE"
|
||||||
type: "PAPER"
|
type: "FABRIC"
|
||||||
version: "1.21.11"
|
version: "1.21.11"
|
||||||
difficulty: hard
|
difficulty: hard
|
||||||
motd: "A Minecraft Server."
|
motd: "A Minecraft Server."
|
||||||
memory: 6G
|
memory: 7G
|
||||||
rcon:
|
rcon:
|
||||||
enabled: true
|
enabled: true
|
||||||
withGeneratedPassword: false
|
withGeneratedPassword: false
|
||||||
port: 25575
|
port: 25575
|
||||||
existingSecret: rcon-credentials
|
existingSecret: rcon-credentials
|
||||||
secretKey: rcon-password
|
secretKey: rcon-password
|
||||||
|
modrinth:
|
||||||
|
projects:
|
||||||
|
- fabric-api
|
||||||
|
- tree-vein-miner
|
||||||
|
- lithium
|
||||||
|
- servux
|
||||||
|
- ferrite-core
|
||||||
|
- carpet
|
||||||
|
- elytra-trims
|
||||||
|
- fabric-language-kotlin
|
||||||
|
- c2me-fabric
|
||||||
|
- scalablelux
|
||||||
|
- no-chat-reports
|
||||||
|
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: kube
|
kubernetes.io/hostname: kube
|
||||||
|
|||||||
29
nix/homelab/helm/values/minecraft/old.yaml
Normal file
29
nix/homelab/helm/values/minecraft/old.yaml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
memory: 2Gi
|
||||||
|
cpu: 1
|
||||||
|
|
||||||
|
minecraftServer:
|
||||||
|
eula: "TRUE"
|
||||||
|
type: "VANILLA"
|
||||||
|
version: "1.7.10"
|
||||||
|
difficulty: hard
|
||||||
|
motd: "A Minecraft Server."
|
||||||
|
memory: 4G
|
||||||
|
rcon:
|
||||||
|
enabled: true
|
||||||
|
withGeneratedPassword: false
|
||||||
|
port: 25575
|
||||||
|
existingSecret: rcon-credentials
|
||||||
|
secretKey: rcon-password
|
||||||
|
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: rufus
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
dataDir:
|
||||||
|
enabled: true
|
||||||
|
Size: 2Gi
|
||||||
@@ -8,9 +8,12 @@ resources:
|
|||||||
|
|
||||||
minecraftRouter:
|
minecraftRouter:
|
||||||
mappings:
|
mappings:
|
||||||
- externalHostname: "mc-rocket.duckdns.org"
|
- externalHostname: "mc-rocket.privatedns.org"
|
||||||
host: "minecraft-main"
|
host: "minecraft-main"
|
||||||
port: 25565
|
port: 25565
|
||||||
- externalHostname: "mc-rocket-creative.duckdns.org"
|
- externalHostname: "mc-rocket-creative.privatedns.org"
|
||||||
host: "minecraft-creative"
|
host: "minecraft-creative"
|
||||||
port: 25565
|
port: 25565
|
||||||
|
- externalHostname: "mc-rocket-old.privatedns.org"
|
||||||
|
host: "minecraft-old"
|
||||||
|
port: 25565
|
||||||
|
|||||||
@@ -8,6 +8,12 @@ serviceWeb:
|
|||||||
https:
|
https:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
|
||||||
|
serviceDns:
|
||||||
|
type: LoadBalancer
|
||||||
|
mixedService: true
|
||||||
|
annotations:
|
||||||
|
metallb.universe.tf/loadBalancerIPs: "192.168.27.13"
|
||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
|
|||||||
58
nix/homelab/helm/values/prometheus.yaml
Normal file
58
nix/homelab/helm/values/prometheus.yaml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
# Install Prometheus Operator CRDs
|
||||||
|
crds:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
alertmanager:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
prometheusSpec:
|
||||||
|
storageSpec:
|
||||||
|
volumeClaimTemplate:
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 20Gi
|
||||||
|
|
||||||
|
retention: 15d
|
||||||
|
retentionSize: "18GB"
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 512Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
enabled: true
|
||||||
|
size: 5Gi
|
||||||
|
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 300m
|
||||||
|
memory: 512Mi
|
||||||
|
|
||||||
|
nodeExporter:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
kubeStateMetrics:
|
||||||
|
enabled: true
|
||||||
|
|
||||||
|
kubeEtcd:
|
||||||
|
enabled: false
|
||||||
|
kubeControllerManager:
|
||||||
|
enabled: false
|
||||||
|
kubeScheduler:
|
||||||
|
enabled: false
|
||||||
|
kubeProxy:
|
||||||
|
enabled: false
|
||||||
33
nix/homelab/kustomize/backup-job-manual.yaml
Normal file
33
nix/homelab/kustomize/backup-job-manual.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: minecraft-backup-manual
|
||||||
|
namespace: minecraft
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: backup
|
||||||
|
image: busybox
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
set -e
|
||||||
|
BACKUP_FILE=/backups/manual/minecraft-main-manual.tar.gz
|
||||||
|
echo "creating backup: ${BACKUP_FILE}"
|
||||||
|
tar -czf "${BACKUP_FILE}" -C /data .
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
- name: backups
|
||||||
|
mountPath: /backups
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: minecraft-main-datadir
|
||||||
|
- name: backups
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /backup/minecraft
|
||||||
18
nix/homelab/kustomize/headscale-migrate.yaml
Normal file
18
nix/homelab/kustomize/headscale-migrate.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Pod
|
||||||
|
metadata:
|
||||||
|
name: headscale-migrate
|
||||||
|
namespace: networking
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: migrate
|
||||||
|
image: nouchka/sqlite3
|
||||||
|
command: ["sleep", "infinity"]
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /var/lib/headscale
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: headscale-data
|
||||||
@@ -5,13 +5,16 @@ resources:
|
|||||||
- ./metallb/pool.yaml
|
- ./metallb/pool.yaml
|
||||||
- ./traefik/config.yaml
|
- ./traefik/config.yaml
|
||||||
- ./traefik/private-networks.yaml
|
- ./traefik/private-networks.yaml
|
||||||
|
- ./traefik/private-networks-tcp.yaml
|
||||||
- ./traefik/chains.yaml
|
- ./traefik/chains.yaml
|
||||||
- ./cert-manager/config.yaml
|
- ./cert-manager/config.yaml
|
||||||
- ./routes/minecraft.yaml
|
- ./routes.yaml
|
||||||
- ./routes/gitea/ssh.yaml
|
|
||||||
- ./routes/gitea/http.yaml
|
- ./media/sonarr.yaml
|
||||||
- ./routes/longhorn.yaml
|
- ./media/prowlarr.yaml
|
||||||
- ./routes/home-assistant.yaml
|
- ./media/radarr.yaml
|
||||||
- ./routes/consul-media.yaml
|
- ./media/qbittorrent.yaml
|
||||||
- ./routes/consul-vaultwarden.yaml
|
- ./media/flaresolverr.yaml
|
||||||
- ./routes/pihole.yaml
|
|
||||||
|
- ./networking/headscale/config.yaml
|
||||||
|
- ./networking/headscale/headscale.yaml
|
||||||
|
|||||||
76
nix/homelab/kustomize/media/flaresolverr.yaml
Normal file
76
nix/homelab/kustomize/media/flaresolverr.yaml
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: flaresolverr-config
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: flaresolverr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: flaresolverr
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: flaresolverr
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: flaresolverr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: flaresolverr
|
||||||
|
image: ghcr.io/flaresolverr/flaresolverr
|
||||||
|
ports:
|
||||||
|
- containerPort: 8191
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: TZ
|
||||||
|
value: America/Vancouver
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1
|
||||||
|
memory: 4Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8191
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: 8191
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: flaresolverr-config
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: flaresolverr
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: flaresolverr
|
||||||
|
ports:
|
||||||
|
- port: 8191
|
||||||
|
targetPort: 8191
|
||||||
|
name: http
|
||||||
84
nix/homelab/kustomize/media/prowlarr.yaml
Normal file
84
nix/homelab/kustomize/media/prowlarr.yaml
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: prowlarr-config
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: prowlarr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: prowlarr
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: prowlarr
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: prowlarr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: prowlarr
|
||||||
|
image: lscr.io/linuxserver/prowlarr
|
||||||
|
ports:
|
||||||
|
- containerPort: 9696
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: PUID
|
||||||
|
value: "1000"
|
||||||
|
- name: PGID
|
||||||
|
value: "1000"
|
||||||
|
- name: TZ
|
||||||
|
value: "America/Vancouver"
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1
|
||||||
|
memory: 1Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 9696
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 9696
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: prowlarr-config
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: prowlarr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: prowlarr
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: prowlarr
|
||||||
|
ports:
|
||||||
|
- port: 9696
|
||||||
|
targetPort: 9696
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
141
nix/homelab/kustomize/media/qbittorrent.yaml
Normal file
141
nix/homelab/kustomize/media/qbittorrent.yaml
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: qbittorrent-config
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: qbittorrent
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: qbittorrent
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: qbittorrent
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: qbittorrent
|
||||||
|
annotations:
|
||||||
|
kubectl.kubernetes.io/default-container: qbittorrent
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: gluetun
|
||||||
|
image: qmcgaw/gluetun
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_ADMIN
|
||||||
|
env:
|
||||||
|
- name: VPN_SERVICE_PROVIDER
|
||||||
|
value: "custom"
|
||||||
|
- name: VPN_TYPE
|
||||||
|
value: "wireguard"
|
||||||
|
- name: TZ
|
||||||
|
value: "America/Vancouver"
|
||||||
|
- name: FIREWALL
|
||||||
|
value: "on"
|
||||||
|
- name: FIREWALL_OUTBOUND_SUBNETS
|
||||||
|
value: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16"
|
||||||
|
- name: DNS_KEEP_NAMESERVER
|
||||||
|
value: "on"
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: "info"
|
||||||
|
volumeMounts:
|
||||||
|
- name: wireguard-config
|
||||||
|
mountPath: /gluetun/wireguard/wg0.conf
|
||||||
|
subPath: wg0.conf
|
||||||
|
readOnly: true
|
||||||
|
- name: tun
|
||||||
|
mountPath: /dev/net/tun
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 512Mi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /v1/vpn/status
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 60
|
||||||
|
timeoutSeconds: 10
|
||||||
|
failureThreshold: 3
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /v1/vpn/status
|
||||||
|
port: 8000
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 60
|
||||||
|
timeoutSeconds: 5
|
||||||
|
|
||||||
|
- name: qbittorrent
|
||||||
|
image: lscr.io/linuxserver/qbittorrent
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: PUID
|
||||||
|
value: "1000"
|
||||||
|
- name: PGID
|
||||||
|
value: "1000"
|
||||||
|
- name: TZ
|
||||||
|
value: "America/Vancouver"
|
||||||
|
- name: WEBUI_PORT
|
||||||
|
value: "8080"
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
- name: data
|
||||||
|
mountPath: /mnt/data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 2
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: qbittorrent-config
|
||||||
|
- name: data
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /data
|
||||||
|
- name: wireguard-config
|
||||||
|
secret:
|
||||||
|
secretName: wireguard-config
|
||||||
|
- name: tun
|
||||||
|
hostPath:
|
||||||
|
path: /dev/net/tun
|
||||||
|
type: CharDevice
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: qbittorrent
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: qbittorrent
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: qbittorrent
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
90
nix/homelab/kustomize/media/radarr.yaml
Normal file
90
nix/homelab/kustomize/media/radarr.yaml
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: radarr-config
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: radarr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: radarr
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: radarr
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: radarr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: radarr
|
||||||
|
image: lscr.io/linuxserver/radarr
|
||||||
|
ports:
|
||||||
|
- containerPort: 7878
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: PUID
|
||||||
|
value: "1000"
|
||||||
|
- name: PGID
|
||||||
|
value: "1000"
|
||||||
|
- name: TZ
|
||||||
|
value: "America/Vancouver"
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
- name: data
|
||||||
|
mountPath: /mnt/data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1
|
||||||
|
memory: 1Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 7878
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 7878
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: radarr-config
|
||||||
|
- name: data
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: radarr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: radarr
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: radarr
|
||||||
|
ports:
|
||||||
|
- port: 7878
|
||||||
|
targetPort: 7878
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
98
nix/homelab/kustomize/media/sonarr.yaml
Normal file
98
nix/homelab/kustomize/media/sonarr.yaml
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: media
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: sonarr-config
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: sonarr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: sonarr
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: sonarr
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: sonarr
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: sonarr
|
||||||
|
image: lscr.io/linuxserver/sonarr:4.0.16
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsGroup: 0
|
||||||
|
ports:
|
||||||
|
- containerPort: 8989
|
||||||
|
name: http
|
||||||
|
env:
|
||||||
|
- name: PUID
|
||||||
|
value: "0"
|
||||||
|
- name: PGID
|
||||||
|
value: "0"
|
||||||
|
- name: TZ
|
||||||
|
value: "America/Vancouver"
|
||||||
|
volumeMounts:
|
||||||
|
- name: config
|
||||||
|
mountPath: /config
|
||||||
|
- name: data
|
||||||
|
mountPath: /mnt/data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 1
|
||||||
|
memory: 1Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 8989
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /ping
|
||||||
|
port: 8989
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 10
|
||||||
|
volumes:
|
||||||
|
- name: config
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: sonarr-config
|
||||||
|
- name: data
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /data
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: sonarr
|
||||||
|
namespace: media
|
||||||
|
labels:
|
||||||
|
app: sonarr
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: sonarr
|
||||||
|
ports:
|
||||||
|
- port: 8989
|
||||||
|
targetPort: 8989
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
@@ -5,17 +5,7 @@ metadata:
|
|||||||
namespace: metallb-system
|
namespace: metallb-system
|
||||||
spec:
|
spec:
|
||||||
addresses:
|
addresses:
|
||||||
- 192.168.18.31-192.168.18.61
|
- 192.168.27.12-192.168.27.40
|
||||||
---
|
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: IPAddressPool
|
|
||||||
metadata:
|
|
||||||
name: rufus-pool
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
addresses:
|
|
||||||
- 192.168.27.10-192.168.27.30
|
|
||||||
autoAssign: false
|
|
||||||
---
|
---
|
||||||
apiVersion: metallb.io/v1beta1
|
apiVersion: metallb.io/v1beta1
|
||||||
kind: L2Advertisement
|
kind: L2Advertisement
|
||||||
@@ -25,15 +15,3 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
ipAddressPools:
|
ipAddressPools:
|
||||||
- pool
|
- pool
|
||||||
---
|
|
||||||
apiVersion: metallb.io/v1beta1
|
|
||||||
kind: L2Advertisement
|
|
||||||
metadata:
|
|
||||||
name: rufus-advertisement
|
|
||||||
namespace: metallb-system
|
|
||||||
spec:
|
|
||||||
ipAddressPools:
|
|
||||||
- rufus-pool
|
|
||||||
nodeSelectors:
|
|
||||||
- matchLabels:
|
|
||||||
kubernetes.io/hostname: rufus
|
|
||||||
|
|||||||
50
nix/homelab/kustomize/networking/headscale/config.yaml
Normal file
50
nix/homelab/kustomize/networking/headscale/config.yaml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: headscale-config
|
||||||
|
namespace: networking
|
||||||
|
data:
|
||||||
|
acl.json: |
|
||||||
|
{
|
||||||
|
"tagOwners": {
|
||||||
|
"tag:personal": ["lucalise@"],
|
||||||
|
},
|
||||||
|
"acls": [
|
||||||
|
{"action": "accept", "src": ["tag:personal"], "dst": ["tag:personal:*"]},
|
||||||
|
{"action": "accept", "src": ["tag:personal"], "dst": ["autogroup:internet:*"]},
|
||||||
|
{"action": "accept", "src": ["tag:personal"], "dst": ["192.168.15.0/27:*", "192.168.27.0/24:*", "192.168.20.0/26:*"]}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
config.yaml: |
|
||||||
|
server_url: https://mesh.lucalise.ca
|
||||||
|
listen_addr: 0.0.0.0:8080
|
||||||
|
metrics_listen_addr: 0.0.0.0:9090
|
||||||
|
|
||||||
|
noise:
|
||||||
|
private_key_path: /var/lib/headscale/noise_private.key
|
||||||
|
|
||||||
|
prefixes:
|
||||||
|
v4: 10.100.0.0/24
|
||||||
|
v6: fd7a:115c:a1e0::/48
|
||||||
|
|
||||||
|
database:
|
||||||
|
type: sqlite3
|
||||||
|
sqlite:
|
||||||
|
path: /var/lib/headscale/db.sqlite
|
||||||
|
policy:
|
||||||
|
path: /etc/headscale/acl.json
|
||||||
|
|
||||||
|
dns:
|
||||||
|
override_local_dns: false
|
||||||
|
base_domain: m.net
|
||||||
|
|
||||||
|
derp:
|
||||||
|
server:
|
||||||
|
enabled: false
|
||||||
|
urls:
|
||||||
|
- https://controlplane.tailscale.com/derpmap/default
|
||||||
|
auto_update_enabled: true
|
||||||
|
update_frequency: 24h
|
||||||
|
|
||||||
|
log:
|
||||||
|
level: info
|
||||||
88
nix/homelab/kustomize/networking/headscale/headscale.yaml
Normal file
88
nix/homelab/kustomize/networking/headscale/headscale.yaml
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: headscale-data
|
||||||
|
namespace: networking
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteMany
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 2Gi
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
namespace: networking
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: headscale
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: headscale
|
||||||
|
image: docker.io/headscale/headscale
|
||||||
|
command: ["headscale", "serve"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 8080
|
||||||
|
name: http
|
||||||
|
- containerPort: 9090
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: 512m
|
||||||
|
memory: 1Gi
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
volumeMounts:
|
||||||
|
- name: headscale-data
|
||||||
|
mountPath: /var/lib/headscale
|
||||||
|
- name: headscale-config
|
||||||
|
mountPath: /etc/headscale/config.yaml
|
||||||
|
subPath: config.yaml
|
||||||
|
- name: headscale-config
|
||||||
|
mountPath: /etc/headscale/acl.json
|
||||||
|
subPath: acl.json
|
||||||
|
volumes:
|
||||||
|
- name: headscale-data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: headscale-data
|
||||||
|
- name: headscale-config
|
||||||
|
configMap:
|
||||||
|
name: headscale-config
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: headscale
|
||||||
|
namespace: networking
|
||||||
|
labels:
|
||||||
|
app: headscale
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: headscale
|
||||||
|
ports:
|
||||||
|
- port: 8080
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
296
nix/homelab/kustomize/routes.yaml
Normal file
296
nix/homelab/kustomize/routes.yaml
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: gitea
|
||||||
|
namespace: git
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- git.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: gitea-http
|
||||||
|
port: 3000
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: gitea-ssh
|
||||||
|
namespace: git
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- ssh
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`*`)
|
||||||
|
services:
|
||||||
|
- name: gitea-ssh
|
||||||
|
port: 22
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: home-assistant
|
||||||
|
namespace: home
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- home-assistant.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: home-assistant
|
||||||
|
port: 8080
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: sonarr
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- sonarr.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: sonarr
|
||||||
|
port: 8989
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: longhorn
|
||||||
|
namespace: longhorn-system
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- storage.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: longhorn-frontend
|
||||||
|
port: 80
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: pihole
|
||||||
|
namespace: pihole-system
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- pihole.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: pihole-web
|
||||||
|
port: 80
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: minecraft-router
|
||||||
|
namespace: minecraft
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- minecraft
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`*`)
|
||||||
|
services:
|
||||||
|
- name: minecraft-router-mc-router
|
||||||
|
port: 25565
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: minecraft-rcon
|
||||||
|
namespace: minecraft
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- rcon
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`*`)
|
||||||
|
middlewares:
|
||||||
|
- name: private-networks-tcp
|
||||||
|
namespace: kube-system
|
||||||
|
services:
|
||||||
|
- name: minecraft-main-rcon
|
||||||
|
port: 25575
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: prowlarr
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- prowlarr.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: prowlarr
|
||||||
|
port: 9696
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: radarr
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- radarr.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: radarr
|
||||||
|
port: 7878
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: qbittorrent
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- qbit.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: qbittorrent
|
||||||
|
port: 8080
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: flaresolverr
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- flare.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: flaresolverr
|
||||||
|
port: 8191
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: jellyfin
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- media.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: jellyfin
|
||||||
|
port: 8096
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: grafana
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- grafana.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: prometheus-stack-grafana
|
||||||
|
port: 80
|
||||||
|
filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
---
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: mesh
|
||||||
|
namespace: networking
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- mesh.lucalise.ca
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- name: headscale
|
||||||
|
port: 8080
|
||||||
@@ -1,272 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: bazarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 6767
|
|
||||||
targetPort: 6767
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: bazarr
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.20
|
|
||||||
ports:
|
|
||||||
- port: 6767
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: bazarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "bazarr.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: bazarr
|
|
||||||
port: 6767
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: prowlarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 9696
|
|
||||||
targetPort: 9696
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: prowlarr
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.17
|
|
||||||
ports:
|
|
||||||
- port: 9696
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: prowlarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "prowlarr.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: prowlarr
|
|
||||||
port: 9696
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: radarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 7878
|
|
||||||
targetPort: 7878
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: radarr
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.15
|
|
||||||
ports:
|
|
||||||
- port: 7878
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: radarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "radarr.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: radarr
|
|
||||||
port: 7878
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: sonarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8989
|
|
||||||
targetPort: 8989
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: sonarr
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.16
|
|
||||||
ports:
|
|
||||||
- port: 8989
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: sonarr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "sonarr.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: sonarr
|
|
||||||
port: 8989
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: qbittorrent
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8090
|
|
||||||
targetPort: 8090
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: qbittorrent
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.6
|
|
||||||
ports:
|
|
||||||
- port: 8090
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: qbittorrent
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "qbit.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: qbittorrent
|
|
||||||
port: 8090
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: flaresolverr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8191
|
|
||||||
targetPort: 8191
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: flaresolverr
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.4
|
|
||||||
ports:
|
|
||||||
- port: 8191
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: flaresolverr
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "flare.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: flaresolverr
|
|
||||||
port: 8191
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: jellyfin
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8096
|
|
||||||
targetPort: 8096
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: jellyfin
|
|
||||||
namespace: media
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.2
|
|
||||||
ports:
|
|
||||||
- port: 8096
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: jellyfin
|
|
||||||
namespace: media
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "media.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: jellyfin
|
|
||||||
port: 8096
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
apiVersion: v1
|
|
||||||
kind: Namespace
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Service
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden
|
|
||||||
namespace: vaultwarden
|
|
||||||
spec:
|
|
||||||
ports:
|
|
||||||
- port: 8000
|
|
||||||
targetPort: 8000
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: v1
|
|
||||||
kind: Endpoints
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden
|
|
||||||
namespace: vaultwarden
|
|
||||||
subsets:
|
|
||||||
- addresses:
|
|
||||||
- ip: 192.168.20.22
|
|
||||||
ports:
|
|
||||||
- port: 8000
|
|
||||||
protocol: TCP
|
|
||||||
---
|
|
||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: vaultwarden
|
|
||||||
namespace: vaultwarden
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "vault.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: vaultwarden
|
|
||||||
port: 8000
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: gitea
|
|
||||||
namespace: git
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "git.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- backendRefs:
|
|
||||||
- name: gitea-http
|
|
||||||
port: 3000
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRouteTCP
|
|
||||||
metadata:
|
|
||||||
name: gitea-ssh
|
|
||||||
namespace: git
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- ssh
|
|
||||||
routes:
|
|
||||||
- match: HostSNI(`*`)
|
|
||||||
services:
|
|
||||||
- name: gitea-ssh
|
|
||||||
port: 22
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: longhorn
|
|
||||||
namespace: longhorn-system
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "storage.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- filters:
|
|
||||||
- type: ExtensionRef
|
|
||||||
extensionRef:
|
|
||||||
group: traefik.io
|
|
||||||
kind: Middleware
|
|
||||||
name: private-networks
|
|
||||||
backendRefs:
|
|
||||||
- name: longhorn-frontend
|
|
||||||
port: 80
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
apiVersion: traefik.io/v1alpha1
|
|
||||||
kind: IngressRouteTCP
|
|
||||||
metadata:
|
|
||||||
name: minecraft-router
|
|
||||||
namespace: minecraft
|
|
||||||
spec:
|
|
||||||
entryPoints:
|
|
||||||
- minecraft
|
|
||||||
routes:
|
|
||||||
- match: HostSNI(`*`)
|
|
||||||
services:
|
|
||||||
- name: minecraft-router-mc-router
|
|
||||||
port: 25565
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
apiVersion: gateway.networking.k8s.io/v1
|
|
||||||
kind: HTTPRoute
|
|
||||||
metadata:
|
|
||||||
name: pihole
|
|
||||||
namespace: pihole-system
|
|
||||||
spec:
|
|
||||||
parentRefs:
|
|
||||||
- name: traefik-gateway
|
|
||||||
namespace: kube-system
|
|
||||||
hostnames:
|
|
||||||
- "pihole.lucalise.ca"
|
|
||||||
rules:
|
|
||||||
- filters:
|
|
||||||
- type: ExtensionRef
|
|
||||||
extensionRef:
|
|
||||||
group: traefik.io
|
|
||||||
kind: Middleware
|
|
||||||
name: private-networks
|
|
||||||
backendRefs:
|
|
||||||
- name: pihole-web
|
|
||||||
port: 80
|
|
||||||
@@ -24,9 +24,31 @@ apiVersion: traefik.io/v1alpha1
|
|||||||
kind: Middleware
|
kind: Middleware
|
||||||
metadata:
|
metadata:
|
||||||
name: private-networks
|
name: private-networks
|
||||||
namespace: pihole-system
|
namespace: media
|
||||||
spec:
|
spec:
|
||||||
chain:
|
chain:
|
||||||
middlewares:
|
middlewares:
|
||||||
- name: private-networks
|
- name: private-networks
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: private-networks
|
||||||
|
namespace: monitoring
|
||||||
|
spec:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- name: private-networks
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: private-networks
|
||||||
|
namespace: pihole-system
|
||||||
|
spec:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- name: private-networks
|
||||||
|
namespace: kube-system
|
||||||
@@ -9,8 +9,6 @@ spec:
|
|||||||
kubernetes.io/hostname: rufus
|
kubernetes.io/hostname: rufus
|
||||||
|
|
||||||
service:
|
service:
|
||||||
annotations:
|
|
||||||
metallb.universe.tf/address-pool: rufus-pool
|
|
||||||
spec:
|
spec:
|
||||||
externalTrafficPolicy: Local
|
externalTrafficPolicy: Local
|
||||||
|
|
||||||
@@ -38,6 +36,12 @@ spec:
|
|||||||
default: true
|
default: true
|
||||||
exposedPort: 25565
|
exposedPort: 25565
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
|
rcon:
|
||||||
|
port: 25575
|
||||||
|
expose:
|
||||||
|
default: true
|
||||||
|
exposedPort: 25575
|
||||||
|
protocol: TCP
|
||||||
|
|
||||||
persistence:
|
persistence:
|
||||||
enabled: true
|
enabled: true
|
||||||
|
|||||||
12
nix/homelab/kustomize/traefik/private-networks-tcp.yaml
Normal file
12
nix/homelab/kustomize/traefik/private-networks-tcp.yaml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: MiddlewareTCP
|
||||||
|
metadata:
|
||||||
|
name: private-networks-tcp
|
||||||
|
namespace: kube-system
|
||||||
|
spec:
|
||||||
|
ipAllowList:
|
||||||
|
sourceRange:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 172.16.0.0/12
|
||||||
|
- 192.168.0.0/16
|
||||||
|
- 100.64.0.0/10
|
||||||
@@ -20,12 +20,6 @@
|
|||||||
|
|
||||||
networking.hostName = meta.hostname;
|
networking.hostName = meta.hostname;
|
||||||
networking.networkmanager.enable = true;
|
networking.networkmanager.enable = true;
|
||||||
networking.firewall.extraCommands = ''
|
|
||||||
iptables -I INPUT -d 192.168.27.10/32 -s 10.0.0.0/8 -j ACCEPT
|
|
||||||
iptables -I INPUT -d 192.168.27.10/32 -s 172.16.0.0/12 -j ACCEPT
|
|
||||||
iptables -I INPUT -d 192.168.27.10/32 -s 192.168.0.0/16 -j ACCEPT
|
|
||||||
iptables -I INPUT -d 192.168.27.10/32 -j DROP
|
|
||||||
'';
|
|
||||||
|
|
||||||
time.timeZone = "America/Vancouver";
|
time.timeZone = "America/Vancouver";
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
[ (modulesPath + "/installer/scan/not-detected.nix")
|
[ (modulesPath + "/installer/scan/not-detected.nix")
|
||||||
];
|
];
|
||||||
|
|
||||||
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "usb_storage" "sd_mod" ];
|
boot.initrd.availableKernelModules = [ "nvme" "xhci_pci" "usbhid" "usb_storage" "sd_mod" ];
|
||||||
boot.initrd.kernelModules = [ "dm-snapshot" ];
|
boot.initrd.kernelModules = [ "dm-snapshot" ];
|
||||||
boot.kernelModules = [ "kvm-amd" ];
|
boot.kernelModules = [ "kvm-amd" ];
|
||||||
boot.extraModulePackages = [ ];
|
boot.extraModulePackages = [ ];
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ NAMESPACES=(
|
|||||||
"home"
|
"home"
|
||||||
"longhorn-system"
|
"longhorn-system"
|
||||||
"pihole-system"
|
"pihole-system"
|
||||||
|
"media"
|
||||||
)
|
)
|
||||||
|
|
||||||
OUTPUT_FILE="kustomize/traefik/chains.yaml"
|
OUTPUT_FILE="kustomize/traefik/chains.yaml"
|
||||||
|
|||||||
155
nix/homelab/scripts/generate-routes.sh
Executable file
155
nix/homelab/scripts/generate-routes.sh
Executable file
@@ -0,0 +1,155 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Route definitions: name:hostname:port:protocol:private
|
||||||
|
# - name: service name (required)
|
||||||
|
# - hostname: custom hostname, use '-' for default (name.lucalise.ca)
|
||||||
|
# - port: service port (required)
|
||||||
|
# - protocol: TCP (default) or UDP
|
||||||
|
# - private: true/false (default false) - adds private-networks middleware
|
||||||
|
ROUTES=(
|
||||||
|
"sonarr:-:8989:TCP:true"
|
||||||
|
"radarr:-:7878:TCP:true"
|
||||||
|
"prowlarr:-:9696:TCP:true"
|
||||||
|
"bazarr:-:6767:TCP:true"
|
||||||
|
"jellyfin:media:8096:TCP:false"
|
||||||
|
"home-assistant:-:8123:TCP:true"
|
||||||
|
)
|
||||||
|
|
||||||
|
DOMAIN="lucalise.ca"
|
||||||
|
OUTPUT_DIR="kustomize/routes"
|
||||||
|
|
||||||
|
generate_http_route() {
|
||||||
|
local name="$1"
|
||||||
|
local hostname="$2"
|
||||||
|
local port="$3"
|
||||||
|
local protocol="$4"
|
||||||
|
local private="$5"
|
||||||
|
|
||||||
|
if [[ -z "$hostname" || "$hostname" == "-" ]]; then
|
||||||
|
hostname="$name"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$protocol" ]]; then
|
||||||
|
protocol="TCP"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$private" ]]; then
|
||||||
|
private="false"
|
||||||
|
fi
|
||||||
|
|
||||||
|
local fqdn="${hostname}.${DOMAIN}"
|
||||||
|
|
||||||
|
local filters_section=""
|
||||||
|
if [[ "$private" == "true" ]]; then
|
||||||
|
filters_section=" - filters:
|
||||||
|
- type: ExtensionRef
|
||||||
|
extensionRef:
|
||||||
|
group: traefik.io
|
||||||
|
kind: Middleware
|
||||||
|
name: private-networks
|
||||||
|
backendRefs:"
|
||||||
|
else
|
||||||
|
filters_section=" - backendRefs:"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: ${name}
|
||||||
|
namespace: media
|
||||||
|
spec:
|
||||||
|
parentRefs:
|
||||||
|
- name: traefik-gateway
|
||||||
|
namespace: kube-system
|
||||||
|
hostnames:
|
||||||
|
- "${fqdn}"
|
||||||
|
rules:
|
||||||
|
${filters_section}
|
||||||
|
- name: ${name}
|
||||||
|
port: ${port}
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
write_kustomization() {
|
||||||
|
local kustomization_file="${OUTPUT_DIR}/../kustomization.yaml"
|
||||||
|
local temp_file=$(mktemp)
|
||||||
|
|
||||||
|
# Collect new route paths
|
||||||
|
local route_paths=()
|
||||||
|
for route in "${ROUTES[@]}"; do
|
||||||
|
IFS=':' read -r name _ _ _ _ <<< "$route"
|
||||||
|
route_paths+=(" - ./routes/${name}.yaml")
|
||||||
|
done
|
||||||
|
|
||||||
|
local in_resources=false
|
||||||
|
local resources_written=false
|
||||||
|
|
||||||
|
while IFS= read -r line; do
|
||||||
|
# Detect resources section
|
||||||
|
if [[ "$line" == "resources:" ]]; then
|
||||||
|
in_resources=true
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If in resources section
|
||||||
|
if [[ "$in_resources" == true ]]; then
|
||||||
|
# Check if line is a resource entry (starts with " - ")
|
||||||
|
if [[ "$line" =~ ^[[:space:]]*-[[:space:]] ]]; then
|
||||||
|
# Skip route entries, keep everything else
|
||||||
|
if [[ "$line" =~ \./routes/ ]]; then
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# End of resources section - write new routes before moving on
|
||||||
|
if [[ "$resources_written" == false ]]; then
|
||||||
|
for route_path in "${route_paths[@]}"; do
|
||||||
|
echo "$route_path" >> "$temp_file"
|
||||||
|
done
|
||||||
|
resources_written=true
|
||||||
|
fi
|
||||||
|
in_resources=false
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "$line" >> "$temp_file"
|
||||||
|
fi
|
||||||
|
done < "$kustomization_file"
|
||||||
|
|
||||||
|
# If file ended while still in resources section, write routes now
|
||||||
|
if [[ "$in_resources" == true && "$resources_written" == false ]]; then
|
||||||
|
for route_path in "${route_paths[@]}"; do
|
||||||
|
echo "$route_path" >> "$temp_file"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "$temp_file" "$kustomization_file"
|
||||||
|
echo "Updated ${kustomization_file} with ${#route_paths[@]} routes"
|
||||||
|
}
|
||||||
|
|
||||||
|
main() {
|
||||||
|
mkdir -p "${OUTPUT_DIR}"
|
||||||
|
|
||||||
|
for route in "${ROUTES[@]}"; do
|
||||||
|
IFS=':' read -r name hostname port protocol private <<< "$route"
|
||||||
|
|
||||||
|
echo "Generating route for ${name}..."
|
||||||
|
|
||||||
|
output_file="${OUTPUT_DIR}/${name}.yaml"
|
||||||
|
generate_http_route "$name" "$hostname" "$port" "$protocol" "$private" > "$output_file"
|
||||||
|
|
||||||
|
echo " -> ${output_file}"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
write_kustomization
|
||||||
|
echo ""
|
||||||
|
echo "Done! Generated ${#ROUTES[@]} routes."
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"
|
||||||
11
nix/homelab/src/commands.rs
Normal file
11
nix/homelab/src/commands.rs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
pub mod generate_routes;
|
||||||
|
|
||||||
|
use clap::Subcommand;
|
||||||
|
|
||||||
|
#[derive(Subcommand, Debug)]
|
||||||
|
pub enum Commands {
|
||||||
|
/// generate gateway api routes
|
||||||
|
GenerateRoutes,
|
||||||
|
/// sync dns records with pi-hole
|
||||||
|
SyncDNS,
|
||||||
|
}
|
||||||
144
nix/homelab/src/commands/generate_routes.rs
Normal file
144
nix/homelab/src/commands/generate_routes.rs
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::HelperError;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, Debug, Clone)]
|
||||||
|
pub struct Route {
|
||||||
|
#[serde(default)]
|
||||||
|
kind: RouteKind,
|
||||||
|
name: String,
|
||||||
|
hostname: Option<String>,
|
||||||
|
entrypoint: Option<String>,
|
||||||
|
namespace: String,
|
||||||
|
service: Option<String>,
|
||||||
|
port: i16,
|
||||||
|
private: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Route {
|
||||||
|
fn hostname(&self) -> &str {
|
||||||
|
self.hostname.as_ref().unwrap_or(&self.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn service(&self) -> &str {
|
||||||
|
self.service.as_ref().unwrap_or(&self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
|
enum RouteKind {
|
||||||
|
#[default]
|
||||||
|
HTTP,
|
||||||
|
TCP,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "httproute.yaml", escape = "none")]
|
||||||
|
struct HttpRoute<'a> {
|
||||||
|
name: &'a str,
|
||||||
|
namespace: &'a str,
|
||||||
|
hostname: &'a str,
|
||||||
|
service: &'a str,
|
||||||
|
port: i16,
|
||||||
|
private: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "ingressroutetcp.yaml", escape = "none")]
|
||||||
|
struct TcpRoute<'a> {
|
||||||
|
name: &'a str,
|
||||||
|
namespace: &'a str,
|
||||||
|
entrypoint: &'a str,
|
||||||
|
service: &'a str,
|
||||||
|
port: i16,
|
||||||
|
private: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "http_middleware_chain.yaml", escape = "none")]
|
||||||
|
struct MiddlewareChain<'a> {
|
||||||
|
namespace: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_routes(routes: &Vec<Route>) -> Result<(), HelperError> {
|
||||||
|
let routes_content = routes.iter().enumerate().try_fold(
|
||||||
|
String::new(),
|
||||||
|
|mut acc, (i, r)| -> Result<_, HelperError> {
|
||||||
|
if i > 0 {
|
||||||
|
acc.push_str("\n---\n");
|
||||||
|
}
|
||||||
|
acc.push_str(&generate_route(r)?);
|
||||||
|
Ok(acc)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
let chains = generate_chains(routes)?;
|
||||||
|
std::fs::write("kustomize/routes.yaml", &routes_content)?;
|
||||||
|
std::fs::write("kustomize/traefik/chains.yaml", &chains)?;
|
||||||
|
println!("Wrote: {}", routes_content);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_route(route: &Route) -> Result<String, HelperError> {
|
||||||
|
match route.kind {
|
||||||
|
RouteKind::HTTP => {
|
||||||
|
let template = HttpRoute {
|
||||||
|
name: &route.name,
|
||||||
|
namespace: &route.namespace,
|
||||||
|
hostname: route.hostname(),
|
||||||
|
service: route.service(),
|
||||||
|
port: route.port,
|
||||||
|
private: route.private,
|
||||||
|
};
|
||||||
|
Ok(template.render()?)
|
||||||
|
}
|
||||||
|
RouteKind::TCP => {
|
||||||
|
let entrypoint = route
|
||||||
|
.entrypoint
|
||||||
|
.as_ref()
|
||||||
|
.ok_or(HelperError::TCPEntryPoint(route.name.clone()))?;
|
||||||
|
let template = TcpRoute {
|
||||||
|
name: &route.name,
|
||||||
|
namespace: &route.namespace,
|
||||||
|
entrypoint,
|
||||||
|
service: route.service(),
|
||||||
|
port: route.port,
|
||||||
|
private: route.private,
|
||||||
|
};
|
||||||
|
Ok(template.render()?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_chains(routes: &[Route]) -> Result<String, HelperError> {
|
||||||
|
let namespaces = routes
|
||||||
|
.iter()
|
||||||
|
.filter_map(|r| r.private.then_some((r.kind.clone(), &r.namespace)))
|
||||||
|
.collect::<BTreeSet<_>>();
|
||||||
|
|
||||||
|
namespaces
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.try_fold(String::new(), |mut acc, (i, (kind, namespace))| {
|
||||||
|
match kind {
|
||||||
|
RouteKind::HTTP => {}
|
||||||
|
_ => {
|
||||||
|
return Ok(acc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
acc.push_str("\n---\n");
|
||||||
|
}
|
||||||
|
let rendered = match kind {
|
||||||
|
RouteKind::HTTP => Some(MiddlewareChain { namespace }.render()?),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(rendered) = rendered {
|
||||||
|
acc.push_str(&rendered);
|
||||||
|
}
|
||||||
|
Ok(acc)
|
||||||
|
})
|
||||||
|
}
|
||||||
87
nix/homelab/src/config.rs
Normal file
87
nix/homelab/src/config.rs
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#[cfg(not(feature = "file-config"))]
|
||||||
|
use std::{collections::HashSet, env};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::{PiHoleConfig, RouterConfig, commands::generate_routes::Route, error::Result};
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub routes: Option<Vec<Route>>,
|
||||||
|
pub pihole: Option<PiHoleConfig>,
|
||||||
|
pub router: Option<RouterConfig>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "file-config"))]
|
||||||
|
struct EnvCollector {
|
||||||
|
missing: Vec<&'static str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "file-config"))]
|
||||||
|
impl EnvCollector {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
missing: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(&mut self, key: &'static str) -> Option<String> {
|
||||||
|
match env::var(key) {
|
||||||
|
Ok(val) => Some(val),
|
||||||
|
Err(_) => {
|
||||||
|
self.missing.push(key);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finish(self) -> Result<()> {
|
||||||
|
if self.missing.is_empty() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(crate::error::Error::MissingEnvVars(self.missing.join(", ")))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "file-config"))]
|
||||||
|
pub fn parse_config() -> Result<Config> {
|
||||||
|
let mut env = EnvCollector::new();
|
||||||
|
|
||||||
|
let pihole_url = env.get("PIHOLE_URL");
|
||||||
|
let pihole_password = env.get("PIHOLE_PASSWORD_FILE");
|
||||||
|
let pihole_hosts = env.get("PIHOLE_EXTRA_HOSTS");
|
||||||
|
let router_host = env.get("ROUTER_HOST");
|
||||||
|
let router_user = env.get("ROUTER_USER");
|
||||||
|
let router_key = env.get("ROUTER_KEY_PATH");
|
||||||
|
let router_lease = env.get("ROUTER_LEASE_FILE");
|
||||||
|
|
||||||
|
env.finish()?;
|
||||||
|
|
||||||
|
Ok(Config {
|
||||||
|
routes: None,
|
||||||
|
pihole: Some(PiHoleConfig {
|
||||||
|
url: pihole_url.unwrap(),
|
||||||
|
password_file: pihole_password.unwrap(),
|
||||||
|
extra_hosts: Some(
|
||||||
|
pihole_hosts
|
||||||
|
.unwrap()
|
||||||
|
.split('\n')
|
||||||
|
.map(String::from)
|
||||||
|
.collect::<HashSet<String>>(),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
router: Some(RouterConfig {
|
||||||
|
host: router_host.unwrap(),
|
||||||
|
user: router_user.unwrap(),
|
||||||
|
key_path: router_key.unwrap().into(),
|
||||||
|
lease_file: router_lease.unwrap(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "file-config")]
|
||||||
|
pub fn parse_config() -> Result<Config> {
|
||||||
|
let bytes = std::fs::read("./config.toml")?;
|
||||||
|
Ok(toml::from_slice::<Config>(&bytes)?)
|
||||||
|
}
|
||||||
20
nix/homelab/src/dns.rs
Normal file
20
nix/homelab/src/dns.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use crate::{
|
||||||
|
error::Result,
|
||||||
|
lease_parser::{Lease, parse_leases},
|
||||||
|
transport::Transport,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct Router<T: Transport> {
|
||||||
|
transport: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Transport> Router<T> {
|
||||||
|
pub fn new(transport: T) -> Self {
|
||||||
|
Self { transport }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dhcp_leases(&self, resource: &str) -> Result<Vec<Lease>> {
|
||||||
|
let raw = self.transport.fetch(resource)?;
|
||||||
|
parse_leases(&raw).map_err(|e| crate::error::Error::Parse(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
25
nix/homelab/src/error.rs
Normal file
25
nix/homelab/src/error.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum Error {
|
||||||
|
#[error("SSH error: {0}")]
|
||||||
|
SSH(#[from] ssh2::Error),
|
||||||
|
#[error("parse error: {0}")]
|
||||||
|
Parse(String),
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
IO(#[from] std::io::Error),
|
||||||
|
#[error("command return non 0 exit code: {0}")]
|
||||||
|
ExitCode(i32),
|
||||||
|
#[error("HTTP error: {0}")]
|
||||||
|
Http(#[from] reqwest::Error),
|
||||||
|
#[error("Pi-hole API error: {0}")]
|
||||||
|
PiHole(String),
|
||||||
|
#[cfg(not(feature = "file-config"))]
|
||||||
|
#[error("missing environment variables: {0}")]
|
||||||
|
MissingEnvVars(String),
|
||||||
|
#[cfg(feature = "file-config")]
|
||||||
|
#[error("error parsing toml: {0}")]
|
||||||
|
TomlParse(#[from] toml::de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
146
nix/homelab/src/lease_parser.rs
Normal file
146
nix/homelab/src/lease_parser.rs
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
use std::net::IpAddr;
|
||||||
|
|
||||||
|
use nom::{
|
||||||
|
IResult, Parser,
|
||||||
|
branch::alt,
|
||||||
|
bytes::complete::{tag, take_till, take_until, take_while1},
|
||||||
|
character::complete::{char, digit1, multispace1, space0, space1},
|
||||||
|
combinator::{map, map_res, value},
|
||||||
|
multi::many0,
|
||||||
|
sequence::{delimited, preceded, terminated},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Lease {
|
||||||
|
pub ip: IpAddr,
|
||||||
|
pub binding_state: BindingState,
|
||||||
|
pub client_hostname: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum BindingState {
|
||||||
|
Active,
|
||||||
|
Free,
|
||||||
|
Abandoned,
|
||||||
|
Backup,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
enum LeaseField {
|
||||||
|
BindingState(BindingState),
|
||||||
|
ClientHostname(String),
|
||||||
|
Other,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ws(input: &str) -> IResult<&str, ()> {
|
||||||
|
value(
|
||||||
|
(),
|
||||||
|
many0(alt((
|
||||||
|
value((), multispace1),
|
||||||
|
value((), (tag("#"), take_until("\n"), tag("\n"))),
|
||||||
|
))),
|
||||||
|
)
|
||||||
|
.parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_ip(input: &str) -> IResult<&str, IpAddr> {
|
||||||
|
map_res(
|
||||||
|
take_while1(|c: char| c.is_ascii_digit() || c == '.'),
|
||||||
|
str::parse,
|
||||||
|
)
|
||||||
|
.parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_field(input: &str) -> IResult<&str, LeaseField> {
|
||||||
|
alt((
|
||||||
|
map(
|
||||||
|
preceded(
|
||||||
|
(tag("client-hostname"), space1),
|
||||||
|
terminated(
|
||||||
|
delimited(char('"'), take_until("\""), char('"')),
|
||||||
|
(space0, char(';')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|h: &str| LeaseField::ClientHostname(h.to_string()),
|
||||||
|
),
|
||||||
|
map(
|
||||||
|
preceded(
|
||||||
|
(tag("binding"), space1, tag("state"), space1),
|
||||||
|
terminated(
|
||||||
|
alt((
|
||||||
|
value(BindingState::Active, tag("active")),
|
||||||
|
value(BindingState::Free, tag("free")),
|
||||||
|
value(BindingState::Abandoned, tag("abandoned")),
|
||||||
|
value(BindingState::Backup, tag("backup")),
|
||||||
|
)),
|
||||||
|
(space0, char(';')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
LeaseField::BindingState,
|
||||||
|
),
|
||||||
|
value(
|
||||||
|
LeaseField::Other,
|
||||||
|
preceded(
|
||||||
|
(tag("uid"), space1),
|
||||||
|
terminated(
|
||||||
|
delimited(char('"'), take_until("\""), char('"')),
|
||||||
|
(space0, char(';')),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
value(
|
||||||
|
LeaseField::Other,
|
||||||
|
(take_till(|c| c == ';' || c == '}'), char(';')),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
.parse(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_lease(input: &str) -> IResult<&str, Lease> {
|
||||||
|
let (input, _) = ws(input)?;
|
||||||
|
let (input, _) = tag("lease").parse(input)?;
|
||||||
|
let (input, _) = space1(input)?;
|
||||||
|
let (input, ip) = parse_ip(input)?;
|
||||||
|
let (input, _) = space0(input)?;
|
||||||
|
let (input, _) = char('{').parse(input)?;
|
||||||
|
let (input, _) = ws(input)?;
|
||||||
|
|
||||||
|
let (input, fields) = many0(terminated(parse_field, ws)).parse(input)?;
|
||||||
|
|
||||||
|
let (input, _) = char('}').parse(input)?;
|
||||||
|
let (input, _) = ws(input)?;
|
||||||
|
|
||||||
|
let mut lease = Lease {
|
||||||
|
ip,
|
||||||
|
binding_state: BindingState::Free,
|
||||||
|
client_hostname: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for field in fields {
|
||||||
|
match field {
|
||||||
|
LeaseField::BindingState(s) => lease.binding_state = s,
|
||||||
|
LeaseField::ClientHostname(h) => lease.client_hostname = Some(h),
|
||||||
|
LeaseField::Other => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((input, lease))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_leases(input: &str) -> Result<Vec<Lease>, nom::Err<nom::error::Error<&str>>> {
|
||||||
|
let mut leases = Vec::new();
|
||||||
|
let Some(start) = input.find("\nlease ") else {
|
||||||
|
return Ok(leases);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut remaining = &input[start..];
|
||||||
|
|
||||||
|
while !remaining.is_empty() {
|
||||||
|
let (rest, lease) = parse_lease(remaining)?;
|
||||||
|
leases.push(lease);
|
||||||
|
remaining = rest;
|
||||||
|
}
|
||||||
|
Ok(leases)
|
||||||
|
}
|
||||||
135
nix/homelab/src/main.rs
Normal file
135
nix/homelab/src/main.rs
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
mod commands;
|
||||||
|
mod config;
|
||||||
|
mod dns;
|
||||||
|
mod error;
|
||||||
|
mod lease_parser;
|
||||||
|
mod pihole;
|
||||||
|
mod transport;
|
||||||
|
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::{collections::HashSet, env};
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
use clap::{CommandFactory, Parser};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::config::parse_config;
|
||||||
|
use crate::{
|
||||||
|
commands::{
|
||||||
|
Commands,
|
||||||
|
generate_routes::{Route, generate_routes},
|
||||||
|
},
|
||||||
|
dns::Router,
|
||||||
|
lease_parser::{BindingState, Lease},
|
||||||
|
pihole::PiHoleClient,
|
||||||
|
transport::SSHTransport,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(version = "0.1.0", about = "Helper for k3s", long_about = None)]
|
||||||
|
struct Cli {
|
||||||
|
#[command(subcommand)]
|
||||||
|
command: Option<Commands>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum HelperError {
|
||||||
|
#[error("error reading file")]
|
||||||
|
ReadFile(#[from] std::io::Error),
|
||||||
|
#[error("entrypoint required for tcproute: {0:?}")]
|
||||||
|
TCPEntryPoint(String),
|
||||||
|
#[error("template rendering error: {0}")]
|
||||||
|
Template(#[from] askama::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct PiHoleConfig {
|
||||||
|
url: String,
|
||||||
|
password_file: String,
|
||||||
|
extra_hosts: Option<HashSet<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct RouterConfig {
|
||||||
|
host: String,
|
||||||
|
user: String,
|
||||||
|
key_path: PathBuf,
|
||||||
|
lease_file: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
async fn main() -> anyhow::Result<()> {
|
||||||
|
let cli = Cli::parse();
|
||||||
|
|
||||||
|
match &cli.command {
|
||||||
|
Some(Commands::GenerateRoutes {}) => {
|
||||||
|
let config = parse_config()?;
|
||||||
|
let routes = config
|
||||||
|
.routes
|
||||||
|
.context("routes in config are required for generating route manifests")?;
|
||||||
|
generate_routes(&routes)?;
|
||||||
|
}
|
||||||
|
Some(Commands::SyncDNS {}) => {
|
||||||
|
let config = parse_config()?;
|
||||||
|
let pihole_config = config
|
||||||
|
.pihole
|
||||||
|
.context("pihole configuration is necessary for syncing dns")?;
|
||||||
|
let router_config = config
|
||||||
|
.router
|
||||||
|
.context("router configuration is necessary for syncing dns")?;
|
||||||
|
|
||||||
|
let password = std::fs::read_to_string(&pihole_config.password_file)
|
||||||
|
.context(format!(
|
||||||
|
"failed to read pihole password from {}",
|
||||||
|
pihole_config.password_file
|
||||||
|
))?
|
||||||
|
.trim()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let leases = tokio::task::spawn_blocking(move || -> anyhow::Result<Vec<Lease>> {
|
||||||
|
let r = Router::new(SSHTransport::new(
|
||||||
|
&router_config.host,
|
||||||
|
&router_config.user,
|
||||||
|
&router_config.key_path,
|
||||||
|
)?);
|
||||||
|
let leases = r
|
||||||
|
.dhcp_leases(&router_config.lease_file)?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|l| {
|
||||||
|
l.binding_state == BindingState::Active && l.client_hostname.is_some()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
Ok(leases)
|
||||||
|
})
|
||||||
|
.await??;
|
||||||
|
|
||||||
|
let mut desired = leases
|
||||||
|
.into_iter()
|
||||||
|
.map(|l| {
|
||||||
|
format!(
|
||||||
|
"{} {}",
|
||||||
|
l.ip,
|
||||||
|
l.client_hostname.expect("filtered for Some above")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<HashSet<String>>();
|
||||||
|
if let Some(extra_hosts) = pihole_config.extra_hosts {
|
||||||
|
desired.extend(extra_hosts);
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Found {} active leases with hostnames", desired.len());
|
||||||
|
|
||||||
|
let client = PiHoleClient::new(&pihole_config.url, &password).await?;
|
||||||
|
|
||||||
|
let stats = client.sync_hosts(desired).await?;
|
||||||
|
println!(
|
||||||
|
"Sync complete: added {}, removed {}",
|
||||||
|
stats.added, stats.removed
|
||||||
|
);
|
||||||
|
}
|
||||||
|
None => Cli::command().print_long_help()?,
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
171
nix/homelab/src/pihole.rs
Normal file
171
nix/homelab/src/pihole.rs
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use reqwest::Client;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
|
pub struct PiHoleClient {
|
||||||
|
client: Client,
|
||||||
|
base_url: String,
|
||||||
|
sid: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SyncStats {
|
||||||
|
pub added: usize,
|
||||||
|
pub removed: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct AuthResponse {
|
||||||
|
session: Session,
|
||||||
|
error: Option<AuthError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Session {
|
||||||
|
valid: bool,
|
||||||
|
sid: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct AuthError {
|
||||||
|
message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize)]
|
||||||
|
struct AuthRequest {
|
||||||
|
password: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ConfigResponse {
|
||||||
|
config: DnsConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DnsConfig {
|
||||||
|
dns: DnsHosts,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct DnsHosts {
|
||||||
|
hosts: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PiHoleClient {
|
||||||
|
pub async fn new(base_url: &str, password: &str) -> Result<Self> {
|
||||||
|
let client = Client::new();
|
||||||
|
let url = format!("{}/api/auth", base_url.trim_end_matches('/'));
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.post(&url)
|
||||||
|
.json(&AuthRequest {
|
||||||
|
password: password.to_string(),
|
||||||
|
})
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let auth: AuthResponse = response.json().await?;
|
||||||
|
|
||||||
|
if !auth.session.valid {
|
||||||
|
return Err(Error::PiHole(format!(
|
||||||
|
"authentication failed: {}",
|
||||||
|
auth.error.unwrap().message
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let sid = auth.session.sid.ok_or_else(|| {
|
||||||
|
Error::PiHole("authentication succeeded but no session ID returned".to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
client,
|
||||||
|
base_url: base_url.trim_end_matches('/').to_string(),
|
||||||
|
sid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_hosts(&self) -> Result<HashSet<String>> {
|
||||||
|
let url = format!("{}/api/config/dns/hosts", self.base_url);
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.get(&url)
|
||||||
|
.header("X-FTL-SID", &self.sid)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Err(e) = response.error_for_status_ref() {
|
||||||
|
return Err(Error::PiHole(format!("failed to get hosts: {}", e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let config: ConfigResponse = response.json().await?;
|
||||||
|
Ok(config.config.dns.hosts.into_iter().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_host(&self, entry: &str) -> Result<()> {
|
||||||
|
let encoded = urlencoding::encode(entry);
|
||||||
|
let url = format!("{}/api/config/dns/hosts/{}", self.base_url, encoded);
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.put(&url)
|
||||||
|
.header("X-FTL-SID", &self.sid)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Err(e) = response.error_for_status_ref() {
|
||||||
|
let body = response.text().await.unwrap_or_default();
|
||||||
|
return Err(Error::PiHole(format!(
|
||||||
|
"failed to add host '{}': {} - {}",
|
||||||
|
entry, e, body
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete_host(&self, entry: &str) -> Result<()> {
|
||||||
|
let encoded = urlencoding::encode(entry);
|
||||||
|
let url = format!("{}/api/config/dns/hosts/{}", self.base_url, encoded);
|
||||||
|
|
||||||
|
let response = self
|
||||||
|
.client
|
||||||
|
.delete(&url)
|
||||||
|
.header("X-FTL-SID", &self.sid)
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if let Err(e) = response.error_for_status_ref() {
|
||||||
|
let body = response.text().await.unwrap_or_default();
|
||||||
|
return Err(Error::PiHole(format!(
|
||||||
|
"failed to delete host '{}': {} - {}",
|
||||||
|
entry, e, body
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn sync_hosts(&self, desired: HashSet<String>) -> Result<SyncStats> {
|
||||||
|
let current = self.get_hosts().await?;
|
||||||
|
|
||||||
|
let to_delete = current.difference(&desired).cloned().collect::<Vec<_>>();
|
||||||
|
let to_add = desired.difference(¤t).cloned().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for entry in &to_delete {
|
||||||
|
self.delete_host(entry).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
for entry in &to_add {
|
||||||
|
self.add_host(entry).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(SyncStats {
|
||||||
|
added: to_add.len(),
|
||||||
|
removed: to_delete.len(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
10
nix/homelab/src/templates/http_middleware_chain.yaml
Normal file
10
nix/homelab/src/templates/http_middleware_chain.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: private-networks
|
||||||
|
namespace: {{ namespace }}
|
||||||
|
spec:
|
||||||
|
chain:
|
||||||
|
middlewares:
|
||||||
|
- name: private-networks
|
||||||
|
namespace: kube-system
|
||||||
@@ -1,21 +1,23 @@
|
|||||||
apiVersion: gateway.networking.k8s.io/v1
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
kind: HTTPRoute
|
kind: HTTPRoute
|
||||||
metadata:
|
metadata:
|
||||||
name: home-assistant
|
name: {{ name }}
|
||||||
namespace: home
|
namespace: {{ namespace }}
|
||||||
spec:
|
spec:
|
||||||
parentRefs:
|
parentRefs:
|
||||||
- name: traefik-gateway
|
- name: traefik-gateway
|
||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
hostnames:
|
hostnames:
|
||||||
- "home-assistant.lucalise.ca"
|
- {{ hostname }}.lucalise.ca
|
||||||
rules:
|
rules:
|
||||||
- filters:
|
- backendRefs:
|
||||||
|
- name: {{ service }}
|
||||||
|
port: {{ port }}
|
||||||
|
{%- if private %}
|
||||||
|
filters:
|
||||||
- type: ExtensionRef
|
- type: ExtensionRef
|
||||||
extensionRef:
|
extensionRef:
|
||||||
group: traefik.io
|
group: traefik.io
|
||||||
kind: Middleware
|
kind: Middleware
|
||||||
name: private-networks
|
name: private-networks
|
||||||
backendRefs:
|
{%- endif %}
|
||||||
- name: home-assistant
|
|
||||||
port: 8080
|
|
||||||
18
nix/homelab/src/templates/ingressroutetcp.yaml
Normal file
18
nix/homelab/src/templates/ingressroutetcp.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRouteTCP
|
||||||
|
metadata:
|
||||||
|
name: {{ name }}
|
||||||
|
namespace: {{ namespace }}
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- {{ entrypoint }}
|
||||||
|
routes:
|
||||||
|
- match: HostSNI(`*`)
|
||||||
|
{%- if private %}
|
||||||
|
middlewares:
|
||||||
|
- name: private-networks-tcp
|
||||||
|
namespace: kube-system
|
||||||
|
{%- endif %}
|
||||||
|
services:
|
||||||
|
- name: {{ service }}
|
||||||
|
port: {{ port }}
|
||||||
9
nix/homelab/src/transport.rs
Normal file
9
nix/homelab/src/transport.rs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
mod ssh;
|
||||||
|
|
||||||
|
pub use ssh::SSHTransport;
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
|
pub trait Transport {
|
||||||
|
fn fetch(&self, resource: &str) -> Result<String>;
|
||||||
|
}
|
||||||
41
nix/homelab/src/transport/ssh.rs
Normal file
41
nix/homelab/src/transport/ssh.rs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
use std::{io::Read, net::TcpStream, path::Path};
|
||||||
|
|
||||||
|
use ssh2::Session;
|
||||||
|
|
||||||
|
use crate::{error::Result, transport::Transport};
|
||||||
|
|
||||||
|
pub struct SSHTransport {
|
||||||
|
session: Session,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SSHTransport {
|
||||||
|
pub fn new(host: &str, user: &str, key_path: &Path) -> Result<Self> {
|
||||||
|
let stream = TcpStream::connect(host)?;
|
||||||
|
|
||||||
|
let mut s = Self {
|
||||||
|
session: Session::new()?,
|
||||||
|
};
|
||||||
|
s.session.set_tcp_stream(stream);
|
||||||
|
s.session.handshake()?;
|
||||||
|
s.session.userauth_pubkey_file(user, None, key_path, None)?;
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Transport for SSHTransport {
|
||||||
|
fn fetch(&self, resource: &str) -> Result<String> {
|
||||||
|
let mut channel = self.session.channel_session()?;
|
||||||
|
channel.exec(&format!("cat {}", resource))?;
|
||||||
|
|
||||||
|
let mut output = String::new();
|
||||||
|
channel.read_to_string(&mut output)?;
|
||||||
|
channel.wait_close()?;
|
||||||
|
|
||||||
|
let c = channel.exit_status()?;
|
||||||
|
if c != 0 {
|
||||||
|
return Err(crate::error::Error::ExitCode(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
27
nix/iso/flake.lock
generated
Normal file
27
nix/iso/flake.lock
generated
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1767325753,
|
||||||
|
"narHash": "sha256-yA/CuWyqm+AQo2ivGy6PlYrjZBQm7jfbe461+4HF2fo=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "64049ca74d63e971b627b5f3178d95642e61cedd",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"ref": "nixos-25.11",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
34
nix/iso/flake.nix
Normal file
34
nix/iso/flake.nix
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
description = "NixOS ISO";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-25.11";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs =
|
||||||
|
{ nixpkgs, ... }:
|
||||||
|
{
|
||||||
|
nixosConfigurations = {
|
||||||
|
iso = nixpkgs.lib.nixosSystem {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
modules = [
|
||||||
|
"${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix"
|
||||||
|
../modules/keys.nix
|
||||||
|
(
|
||||||
|
{
|
||||||
|
config,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
{
|
||||||
|
users.users = {
|
||||||
|
nixos.openssh.authorizedKeys.keys = config.authorized_ssh;
|
||||||
|
root.openssh.authorizedKeys.keys = config.authorized_ssh;
|
||||||
|
};
|
||||||
|
services.openssh.enable = true;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -21,6 +21,7 @@
|
|||||||
roboto
|
roboto
|
||||||
roboto-mono
|
roboto-mono
|
||||||
open-sans
|
open-sans
|
||||||
|
comic-relief
|
||||||
];
|
];
|
||||||
fonts.fontDir.enable = true;
|
fonts.fontDir.enable = true;
|
||||||
commonPackages = with pkgs; [
|
commonPackages = with pkgs; [
|
||||||
@@ -50,7 +51,6 @@
|
|||||||
gnumake
|
gnumake
|
||||||
watchman
|
watchman
|
||||||
bat
|
bat
|
||||||
rustup
|
|
||||||
emote
|
emote
|
||||||
pkg-config
|
pkg-config
|
||||||
openssl
|
openssl
|
||||||
@@ -59,7 +59,6 @@
|
|||||||
sops
|
sops
|
||||||
yubikey-personalization
|
yubikey-personalization
|
||||||
yubikey-manager
|
yubikey-manager
|
||||||
gnupg
|
|
||||||
(pass.withExtensions (exts: with exts; [ pass-import ]))
|
(pass.withExtensions (exts: with exts; [ pass-import ]))
|
||||||
python3
|
python3
|
||||||
jdt-language-server
|
jdt-language-server
|
||||||
@@ -76,25 +75,44 @@
|
|||||||
kubectl
|
kubectl
|
||||||
kubernetes-helm
|
kubernetes-helm
|
||||||
helmfile
|
helmfile
|
||||||
|
jless
|
||||||
|
fd
|
||||||
|
dig
|
||||||
|
just
|
||||||
];
|
];
|
||||||
programs.nix-ld.enable = lib.mkDefault true;
|
programs.nix-ld.enable = lib.mkDefault true;
|
||||||
programs.zsh.enable = lib.mkDefault true;
|
programs.zsh.enable = lib.mkDefault true;
|
||||||
services.openssh.enable = lib.mkDefault true;
|
services.openssh.enable = lib.mkDefault true;
|
||||||
hardware.enableAllFirmware = true;
|
hardware.enableAllFirmware = true;
|
||||||
sops.defaultSopsFile = ../../secrets/sops.yaml;
|
|
||||||
sops.age.sshKeyPaths = [ "/etc/ssh/id_ed25519" ];
|
|
||||||
programs.gnupg.agent = {
|
programs.gnupg.agent = {
|
||||||
enable = true;
|
enable = true;
|
||||||
enableSSHSupport = true;
|
enableSSHSupport = false;
|
||||||
pinentryPackage = pkgs.pinentry-gtk2;
|
pinentryPackage = pkgs.pinentry-gtk2;
|
||||||
};
|
};
|
||||||
services.pcscd.enable = true;
|
services.pcscd.enable = true;
|
||||||
services.udev.packages = with pkgs; [ yubikey-personalization ];
|
services.udev.packages = with pkgs; [
|
||||||
|
yubikey-personalization
|
||||||
|
yubikey-manager
|
||||||
|
];
|
||||||
|
programs.ssh.startAgent = true;
|
||||||
|
|
||||||
programs.neovim = lib.mkDefault {
|
programs.neovim = lib.mkDefault {
|
||||||
enable = true;
|
enable = true;
|
||||||
defaultEditor = true;
|
defaultEditor = true;
|
||||||
vimAlias = true;
|
vimAlias = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
sops = {
|
||||||
|
defaultSopsFile = ../../secrets/secrets.yaml;
|
||||||
|
age.sshKeyPaths = [ "/home/luca/.ssh/id_ed25519" ];
|
||||||
|
secrets = {
|
||||||
|
"pihole_password" = {
|
||||||
|
owner = "luca";
|
||||||
|
};
|
||||||
|
"k3s_token" = {
|
||||||
|
owner = "luca";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,5 +21,7 @@
|
|||||||
./dns.nix
|
./dns.nix
|
||||||
./mounts.nix
|
./mounts.nix
|
||||||
./nfs-mesh.nix
|
./nfs-mesh.nix
|
||||||
|
./rust.nix
|
||||||
|
# ./networking/wireguard-mesh.nix
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
lib,
|
lib,
|
||||||
config,
|
config,
|
||||||
pkgs-before,
|
pkgs-before,
|
||||||
|
inputs,
|
||||||
|
meta,
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
|
|
||||||
@@ -12,6 +14,15 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.desktop.enable {
|
config = lib.mkIf config.desktop.enable {
|
||||||
|
i18n.inputMethod = {
|
||||||
|
enable = true;
|
||||||
|
type = "fcitx5";
|
||||||
|
fcitx5.addons = with pkgs; [
|
||||||
|
fcitx5-mozc
|
||||||
|
fcitx5-gtk
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
vscode-fhs
|
vscode-fhs
|
||||||
pavucontrol
|
pavucontrol
|
||||||
@@ -50,6 +61,8 @@
|
|||||||
lm_sensors
|
lm_sensors
|
||||||
fanctl
|
fanctl
|
||||||
waypipe
|
waypipe
|
||||||
|
inputs.quickshell.packages.${meta.architecture}.default
|
||||||
|
alacritty
|
||||||
];
|
];
|
||||||
boot.kernelModules = [
|
boot.kernelModules = [
|
||||||
"iptables"
|
"iptables"
|
||||||
@@ -108,6 +121,14 @@
|
|||||||
};
|
};
|
||||||
xdg.configFile = {
|
xdg.configFile = {
|
||||||
"hypr/hyprlock.conf".source = ../../custom/hyprlock/hyprlock.conf;
|
"hypr/hyprlock.conf".source = ../../custom/hyprlock/hyprlock.conf;
|
||||||
|
"fcitx5/config".text = ''
|
||||||
|
[Hotkey]
|
||||||
|
TriggerKeys=
|
||||||
|
EnumerateWithTriggerKeys=True
|
||||||
|
EnumerateForwardKeys=
|
||||||
|
EnumerateBackwardKeys=
|
||||||
|
EnumerateSkipFirst=False
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
services.dunst = {
|
services.dunst = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -202,9 +223,15 @@
|
|||||||
"$mod, k, movefocus, u"
|
"$mod, k, movefocus, u"
|
||||||
"$mod, j, movefocus, d"
|
"$mod, j, movefocus, d"
|
||||||
"$mod, Space, togglesplit"
|
"$mod, Space, togglesplit"
|
||||||
|
"$mod SHIFT, h, movewindow, l"
|
||||||
|
"$mod SHIFT, l, movewindow, r"
|
||||||
"$mod SHIFT, v, exec, bash -c ~/dotfiles/scripts/copy.sh"
|
"$mod SHIFT, v, exec, bash -c ~/dotfiles/scripts/copy.sh"
|
||||||
"$mod SHIFT, s, exec, bash -c ~/dotfiles/scripts/screenshot.sh"
|
"$mod SHIFT, s, exec, bash -c ~/dotfiles/scripts/screenshot.sh"
|
||||||
"$mod, p, exec, bash -c ~/dotfiles/scripts/project.sh"
|
"$mod, p, exec, bash -c ~/dotfiles/scripts/project.sh"
|
||||||
|
"$mod SHIFT, k, exec, bash -c ~/dotfiles/scripts/layout.sh"
|
||||||
|
"$mod SHIFT, j, exec, fcitx5-remote -t"
|
||||||
|
"$mod CTRL, h, focusmonitor, l"
|
||||||
|
"$mod CTRL, l, focusmonitor, r"
|
||||||
|
|
||||||
"$mod, 0, workspace, 10"
|
"$mod, 0, workspace, 10"
|
||||||
"$mod SHIFT, 0, movetoworkspacesilent, 10"
|
"$mod SHIFT, 0, movetoworkspacesilent, 10"
|
||||||
@@ -235,14 +262,14 @@
|
|||||||
",XF86MonBrightnessDown, exec, bash -c 'brightnessctl s 5%- && perc=$(( \$(brightnessctl get) * 100 / \$(brightnessctl max) )) && notify-send \"Brightness\" -h int:value:\$perc -h string:synchronous:brightness -u low'"
|
",XF86MonBrightnessDown, exec, bash -c 'brightnessctl s 5%- && perc=$(( \$(brightnessctl get) * 100 / \$(brightnessctl max) )) && notify-send \"Brightness\" -h int:value:\$perc -h string:synchronous:brightness -u low'"
|
||||||
];
|
];
|
||||||
general = {
|
general = {
|
||||||
gaps_in = 0;
|
gaps_in = 5;
|
||||||
gaps_out = 10;
|
gaps_out = 10;
|
||||||
};
|
};
|
||||||
dwindle = {
|
dwindle = {
|
||||||
preserve_split = true;
|
preserve_split = true;
|
||||||
};
|
};
|
||||||
decoration = {
|
decoration = {
|
||||||
rounding = 0;
|
rounding = 10;
|
||||||
blur = {
|
blur = {
|
||||||
enabled = false;
|
enabled = false;
|
||||||
};
|
};
|
||||||
@@ -252,16 +279,21 @@
|
|||||||
"XCURSOR_SIZE,24"
|
"XCURSOR_SIZE,24"
|
||||||
"LIBVA_DRIVER_NAME,nvidia"
|
"LIBVA_DRIVER_NAME,nvidia"
|
||||||
"__GLX_VENDOR_LIBRARY_NAME,nvidia"
|
"__GLX_VENDOR_LIBRARY_NAME,nvidia"
|
||||||
|
# "GTK_IM_MODULE,fcitx"
|
||||||
|
# "QT_IM_MODULE,fcitx"
|
||||||
|
"XMODIFIERS,@im=fcitx"
|
||||||
];
|
];
|
||||||
exec-once = [
|
exec-once = [
|
||||||
"status-bar"
|
# "status-bar"
|
||||||
|
"qs"
|
||||||
"wl-clip-persist --clipboard regular"
|
"wl-clip-persist --clipboard regular"
|
||||||
|
"fcitx5 -d"
|
||||||
];
|
];
|
||||||
monitor = [
|
monitor = [
|
||||||
"eDP-1, 1920x1080, 0x0, 1"
|
"eDP-1, 1920x1080, 0x0, 1"
|
||||||
];
|
];
|
||||||
input = {
|
input = {
|
||||||
kb_layout = "us";
|
kb_layout = "us,jp";
|
||||||
touchpad = {
|
touchpad = {
|
||||||
natural_scroll = true;
|
natural_scroll = true;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,13 +17,6 @@
|
|||||||
enable = true;
|
enable = true;
|
||||||
dns = "systemd-resolved";
|
dns = "systemd-resolved";
|
||||||
};
|
};
|
||||||
# networking.extraHosts = ''
|
|
||||||
# 75.157.238.86 traefik.lucalise.ca
|
|
||||||
# 75.157.238.86 media.lucalise.ca
|
|
||||||
# 75.157.238.86 git.lucalise.ca
|
|
||||||
# 75.157.238.86 storage.lucalise.ca
|
|
||||||
# 75.157.238.86 home-assistant.lucalise.ca
|
|
||||||
# '';
|
|
||||||
|
|
||||||
services.resolved = {
|
services.resolved = {
|
||||||
enable = true;
|
enable = true;
|
||||||
@@ -32,13 +25,12 @@
|
|||||||
"1.0.0.1"
|
"1.0.0.1"
|
||||||
];
|
];
|
||||||
domains = [
|
domains = [
|
||||||
"consul"
|
"~."
|
||||||
"service.consul"
|
|
||||||
"node.consul"
|
|
||||||
];
|
];
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
[Resolve]
|
[Resolve]
|
||||||
DNS=192.168.20.5:8600
|
DNS=192.168.27.13:53 1.1.1.1 1.0.0.1
|
||||||
|
ResolveUnicastSingleLabel=yes
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user