Compare commits
116
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c355124a8a | ||
|
|
1091d6bd14 | ||
|
|
9f56045009 | ||
|
|
2583236f6d | ||
|
|
80b792ce8c | ||
|
|
e40a4d0c72 | ||
|
|
03d580999a | ||
|
|
a28fed03ed | ||
|
|
99a69257e6 | ||
|
|
f0f455c7dd | ||
|
|
ede07b72e8 | ||
|
|
4662797118 | ||
|
|
505bfec45a | ||
|
|
a3b5811b83 | ||
|
|
f58f979b3b | ||
|
|
c4a3623e55 | ||
|
|
dd3693095f | ||
|
|
4273e62d68 | ||
|
|
5d8a582e1e | ||
|
|
31714fe357 | ||
|
|
2e22f98eb4 | ||
|
|
69186d807e | ||
|
|
719504a399 | ||
|
|
1059e430a0 | ||
|
|
f3d199415f | ||
|
|
ef0123b5fb | ||
|
|
207b04d0a8 | ||
|
|
5ef058fbef | ||
|
|
ff57d1a07c | ||
|
|
a93a29655b | ||
|
|
bd8963bcb9 | ||
|
|
55be79a361 | ||
|
|
62bd2291d0 | ||
|
|
0e08a46926 | ||
|
|
c4432de7d5 | ||
|
|
6dbf61f182 | ||
|
|
859a118e0a | ||
|
|
dc8e66e604 | ||
|
|
90bce2980e | ||
|
|
69947c9eee | ||
|
|
4d98223004 | ||
|
|
fdd79fcff3 | ||
|
|
04044b32e0 | ||
|
|
aa4c9dce08 | ||
|
|
c31ca20fb0 | ||
|
|
b42ba8d68f | ||
|
|
f8c69c2434 | ||
|
|
67104123a5 | ||
|
|
976ea1fbe1 | ||
|
|
7cfcfac94c | ||
|
|
4b981e3d97 | ||
|
|
f53ab23d8e | ||
|
|
df1aa96316 | ||
|
|
3d58baaf2f | ||
|
|
78c1519398 | ||
|
|
d8a5a916e1 | ||
|
|
a840dd674a | ||
|
|
a094d3b925 | ||
|
|
9508a8483c | ||
|
|
c5919259f6 | ||
|
|
83de150f87 | ||
|
|
70d785769e | ||
|
|
f129977993 | ||
|
|
cf4c70075c | ||
|
|
2b979b5f43 | ||
|
|
dbecdb7069 | ||
|
|
fb7dfbee57 | ||
|
|
6b5a0fc31f | ||
|
|
47adf8e718 | ||
|
|
54980ff18b | ||
|
|
ccfa5df898 | ||
|
|
3cd60a353e | ||
|
|
4d000080d8 | ||
|
|
3881b5b3ba | ||
|
|
c850ad291a | ||
|
|
4228217497 | ||
|
|
df2e2ec68d | ||
|
|
180fd33ffc | ||
|
|
bbb8ca0323 | ||
|
|
ec91419b36 | ||
|
|
1384b96742 | ||
|
|
ab138b033a | ||
|
|
968e633bf8 | ||
|
|
aa6c02f60b | ||
|
|
60423333fe | ||
|
|
4026adc04a | ||
|
|
94669590b1 | ||
|
|
cf6223f0b5 | ||
|
|
89ade00efb | ||
|
|
4f67181637 | ||
|
|
2bd3d91595 | ||
|
|
6f1f6c349d | ||
|
|
a8ee4bd2b2 | ||
|
|
0018d2b418 | ||
|
|
82dbe84075 | ||
|
|
6b717f5219 | ||
|
|
0c1aa7d633 | ||
|
|
f7c279a67a | ||
|
|
09e43ded52 | ||
|
|
7d766d1cf9 | ||
|
|
2b17fe67e5 | ||
|
|
2b26f21649 | ||
|
|
0a3430ea97 | ||
|
|
1212017945 | ||
|
|
d11e44ad1a | ||
|
|
ef8e317bf5 | ||
|
|
e8978369b6 | ||
|
|
29b254c466 | ||
|
|
f582a36f0e | ||
|
|
1dc114b2ad | ||
|
|
62ccae05c2 | ||
|
|
9bfc125baf | ||
|
|
635bbeaec9 | ||
|
|
a9d1df8fc6 | ||
|
|
6ea4a99ebd | ||
|
|
13f7e13f9b |
@@ -13,15 +13,19 @@ ArgoCD homelab project
|
|||||||
| Application | Status |
|
| Application | Status |
|
||||||
| :--- | :---: |
|
| :--- | :---: |
|
||||||
| **argocd** | [](https://ag.hexor.cy/applications/argocd/argocd) |
|
| **argocd** | [](https://ag.hexor.cy/applications/argocd/argocd) |
|
||||||
|
| **auth-proxy** | [](https://ag.hexor.cy/applications/argocd/auth-proxy) |
|
||||||
| **authentik** | [](https://ag.hexor.cy/applications/argocd/authentik) |
|
| **authentik** | [](https://ag.hexor.cy/applications/argocd/authentik) |
|
||||||
| **cert-manager** | [](https://ag.hexor.cy/applications/argocd/cert-manager) |
|
| **cert-manager** | [](https://ag.hexor.cy/applications/argocd/cert-manager) |
|
||||||
| **external-secrets** | [](https://ag.hexor.cy/applications/argocd/external-secrets) |
|
| **external-secrets** | [](https://ag.hexor.cy/applications/argocd/external-secrets) |
|
||||||
| **gpu** | [](https://ag.hexor.cy/applications/argocd/gpu) |
|
| **gpu** | [](https://ag.hexor.cy/applications/argocd/gpu) |
|
||||||
|
| **kanidm** | [](https://ag.hexor.cy/applications/argocd/kanidm) |
|
||||||
|
| **keycloak** | [](https://ag.hexor.cy/applications/argocd/keycloak) |
|
||||||
| **kube-system-custom** | [](https://ag.hexor.cy/applications/argocd/kube-system-custom) |
|
| **kube-system-custom** | [](https://ag.hexor.cy/applications/argocd/kube-system-custom) |
|
||||||
| **kubernetes-dashboard** | [](https://ag.hexor.cy/applications/argocd/kubernetes-dashboard) |
|
| **kubernetes-dashboard** | [](https://ag.hexor.cy/applications/argocd/kubernetes-dashboard) |
|
||||||
| **longhorn** | [](https://ag.hexor.cy/applications/argocd/longhorn) |
|
| **longhorn** | [](https://ag.hexor.cy/applications/argocd/longhorn) |
|
||||||
| **postgresql** | [](https://ag.hexor.cy/applications/argocd/postgresql) |
|
| **postgresql** | [](https://ag.hexor.cy/applications/argocd/postgresql) |
|
||||||
| **prom-stack** | [](https://ag.hexor.cy/applications/argocd/prom-stack) |
|
| **prom-stack** | [](https://ag.hexor.cy/applications/argocd/prom-stack) |
|
||||||
|
| **reloader** | [](https://ag.hexor.cy/applications/argocd/reloader) |
|
||||||
| **system-upgrade** | [](https://ag.hexor.cy/applications/argocd/system-upgrade) |
|
| **system-upgrade** | [](https://ag.hexor.cy/applications/argocd/system-upgrade) |
|
||||||
|
|
||||||
### Games
|
### Games
|
||||||
@@ -38,9 +42,11 @@ ArgoCD homelab project
|
|||||||
|
|
||||||
| Application | Status |
|
| Application | Status |
|
||||||
| :--- | :---: |
|
| :--- | :---: |
|
||||||
|
| **amnezia** | [](https://ag.hexor.cy/applications/argocd/amnezia) |
|
||||||
| **comfyui** | [](https://ag.hexor.cy/applications/argocd/comfyui) |
|
| **comfyui** | [](https://ag.hexor.cy/applications/argocd/comfyui) |
|
||||||
| **furumi-dev** | [](https://ag.hexor.cy/applications/argocd/furumi-dev) |
|
| **doka2-lobby-list** | [](https://ag.hexor.cy/applications/argocd/doka2-lobby-list) |
|
||||||
| **furumi-server** | [](https://ag.hexor.cy/applications/argocd/furumi-server) |
|
| **firefly-iii** | [](https://ag.hexor.cy/applications/argocd/firefly-iii) |
|
||||||
|
| **furumi** | [](https://ag.hexor.cy/applications/argocd/furumi) |
|
||||||
| **gitea** | [](https://ag.hexor.cy/applications/argocd/gitea) |
|
| **gitea** | [](https://ag.hexor.cy/applications/argocd/gitea) |
|
||||||
| **greece-notifier** | [](https://ag.hexor.cy/applications/argocd/greece-notifier) |
|
| **greece-notifier** | [](https://ag.hexor.cy/applications/argocd/greece-notifier) |
|
||||||
| **hexound** | [](https://ag.hexor.cy/applications/argocd/hexound) |
|
| **hexound** | [](https://ag.hexor.cy/applications/argocd/hexound) |
|
||||||
@@ -50,6 +56,7 @@ ArgoCD homelab project
|
|||||||
| **k8s-secrets** | [](https://ag.hexor.cy/applications/argocd/k8s-secrets) |
|
| **k8s-secrets** | [](https://ag.hexor.cy/applications/argocd/k8s-secrets) |
|
||||||
| **khm** | [](https://ag.hexor.cy/applications/argocd/khm) |
|
| **khm** | [](https://ag.hexor.cy/applications/argocd/khm) |
|
||||||
| **lidarr** | [](https://ag.hexor.cy/applications/argocd/lidarr) |
|
| **lidarr** | [](https://ag.hexor.cy/applications/argocd/lidarr) |
|
||||||
|
| **llamacpp** | [](https://ag.hexor.cy/applications/argocd/llamacpp) |
|
||||||
| **matrix** | [](https://ag.hexor.cy/applications/argocd/matrix) |
|
| **matrix** | [](https://ag.hexor.cy/applications/argocd/matrix) |
|
||||||
| **mtproxy** | [](https://ag.hexor.cy/applications/argocd/mtproxy) |
|
| **mtproxy** | [](https://ag.hexor.cy/applications/argocd/mtproxy) |
|
||||||
| **n8n** | [](https://ag.hexor.cy/applications/argocd/n8n) |
|
| **n8n** | [](https://ag.hexor.cy/applications/argocd/n8n) |
|
||||||
@@ -62,9 +69,12 @@ ArgoCD homelab project
|
|||||||
| **sonarr-stack** | [](https://ag.hexor.cy/applications/argocd/sonarr-stack) |
|
| **sonarr-stack** | [](https://ag.hexor.cy/applications/argocd/sonarr-stack) |
|
||||||
| **stirling-pdf** | [](https://ag.hexor.cy/applications/argocd/stirling-pdf) |
|
| **stirling-pdf** | [](https://ag.hexor.cy/applications/argocd/stirling-pdf) |
|
||||||
| **syncthing** | [](https://ag.hexor.cy/applications/argocd/syncthing) |
|
| **syncthing** | [](https://ag.hexor.cy/applications/argocd/syncthing) |
|
||||||
|
| **teamspeak** | [](https://ag.hexor.cy/applications/argocd/teamspeak) |
|
||||||
| **tg-bots** | [](https://ag.hexor.cy/applications/argocd/tg-bots) |
|
| **tg-bots** | [](https://ag.hexor.cy/applications/argocd/tg-bots) |
|
||||||
| **vaultwarden** | [](https://ag.hexor.cy/applications/argocd/vaultwarden) |
|
| **vaultwarden** | [](https://ag.hexor.cy/applications/argocd/vaultwarden) |
|
||||||
| **vpn** | [](https://ag.hexor.cy/applications/argocd/vpn) |
|
| **vpn** | [](https://ag.hexor.cy/applications/argocd/vpn) |
|
||||||
|
| **web-petting** | [](https://ag.hexor.cy/applications/argocd/web-petting) |
|
||||||
|
| **wedding** | [](https://ag.hexor.cy/applications/argocd/wedding) |
|
||||||
| **xandikos** | [](https://ag.hexor.cy/applications/argocd/xandikos) |
|
| **xandikos** | [](https://ag.hexor.cy/applications/argocd/xandikos) |
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: amnezia
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: apps
|
||||||
|
destination:
|
||||||
|
namespace: amnezia
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
source:
|
||||||
|
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: k8s/apps/amnezia
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
selfHeal: true
|
||||||
|
prune: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,531 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-scripts
|
||||||
|
data:
|
||||||
|
firewall-up.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PORT="${1:-5847}"
|
||||||
|
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||||
|
POLICY_FILE="${3:-/run/amnezia/policy/policy.conf}"
|
||||||
|
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||||
|
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
|
||||||
|
POLICY_CHAIN_B="AMNEZIAWG-POLICY-B"
|
||||||
|
IPTABLES=(iptables -w 30)
|
||||||
|
|
||||||
|
external_interface() {
|
||||||
|
ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_rule() {
|
||||||
|
local mode="$1"
|
||||||
|
shift
|
||||||
|
local table_args=()
|
||||||
|
if [ "${1:-}" = "-t" ]; then
|
||||||
|
table_args=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
local chain="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
if ! "${IPTABLES[@]}" "${table_args[@]}" -C "${chain}" "$@" >/dev/null 2>&1; then
|
||||||
|
"${IPTABLES[@]}" "${table_args[@]}" "${mode}" "${chain}" "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_rule() {
|
||||||
|
local table_args=()
|
||||||
|
if [ "${1:-}" = "-t" ]; then
|
||||||
|
table_args=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
local chain="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
while "${IPTABLES[@]}" "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do
|
||||||
|
true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
ensure_chain() {
|
||||||
|
"${IPTABLES[@]}" -N "$1" >/dev/null 2>&1 || true
|
||||||
|
}
|
||||||
|
|
||||||
|
valid_ipv4_32() {
|
||||||
|
local value="$1"
|
||||||
|
[[ "${value}" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}/32$ ]] || return 1
|
||||||
|
local address="${value%/32}"
|
||||||
|
local octet
|
||||||
|
IFS=. read -r -a octets <<< "${address}"
|
||||||
|
[ "${#octets[@]}" -eq 4 ] || return 1
|
||||||
|
for octet in "${octets[@]}"; do
|
||||||
|
[[ "${octet}" =~ ^[0-9]+$ ]] || return 1
|
||||||
|
[ "${octet}" -le 255 ] || return 1
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
routed_through_awg0() {
|
||||||
|
ip -4 route get "${1%/32}" 2>/dev/null | grep -Eq '(^|[[:space:]])dev awg0([[:space:]]|$)'
|
||||||
|
}
|
||||||
|
|
||||||
|
active_policy_chain() {
|
||||||
|
"${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null \
|
||||||
|
| awk '$1 == "-A" && $3 == "-j" && $4 ~ /^AMNEZIAWG-POLICY-[AB]$/ { print $4; exit }'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_policy() {
|
||||||
|
local active inactive source destination extra
|
||||||
|
active="$(active_policy_chain || true)"
|
||||||
|
if [ "${active}" = "${POLICY_CHAIN_A}" ]; then
|
||||||
|
inactive="${POLICY_CHAIN_B}"
|
||||||
|
else
|
||||||
|
inactive="${POLICY_CHAIN_A}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ensure_chain "${inactive}"
|
||||||
|
"${IPTABLES[@]}" -F "${inactive}"
|
||||||
|
|
||||||
|
if [ -f "${POLICY_FILE}" ]; then
|
||||||
|
while read -r source destination extra; do
|
||||||
|
[ -n "${source:-}" ] || continue
|
||||||
|
[[ "${source}" == \#* ]] && continue
|
||||||
|
if [ -n "${extra:-}" ] \
|
||||||
|
|| ! valid_ipv4_32 "${source}" \
|
||||||
|
|| ! valid_ipv4_32 "${destination:-}" \
|
||||||
|
|| ! routed_through_awg0 "${source}" \
|
||||||
|
|| ! routed_through_awg0 "${destination}"; then
|
||||||
|
echo "Invalid or out-of-tunnel policy line: ${source:-} ${destination:-} ${extra:-}" >&2
|
||||||
|
"${IPTABLES[@]}" -F "${inactive}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
"${IPTABLES[@]}" -A "${inactive}" \
|
||||||
|
-i awg0 -o awg0 -s "${source}" -d "${destination}" \
|
||||||
|
-m comment --comment amneziawg-group-pair -j ACCEPT
|
||||||
|
done < "${POLICY_FILE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# This DROP is deliberately restricted to packets entering and leaving
|
||||||
|
# awg0. It cannot match Kubernetes, CNI, kubelet, Tailscale, or host traffic.
|
||||||
|
"${IPTABLES[@]}" -A "${inactive}" \
|
||||||
|
-i awg0 -o awg0 -s "${VPN_CIDR}" -d "${VPN_CIDR}" \
|
||||||
|
-m comment --comment amneziawg-client-isolation -j DROP
|
||||||
|
"${IPTABLES[@]}" -A "${inactive}" \
|
||||||
|
-i awg0 -o "${EXT_IF}" -s "${VPN_CIDR}" \
|
||||||
|
-m conntrack --ctstate NEW,ESTABLISHED,RELATED \
|
||||||
|
-m comment --comment amneziawg-internet-out -j ACCEPT
|
||||||
|
"${IPTABLES[@]}" -A "${inactive}" \
|
||||||
|
-i "${EXT_IF}" -o awg0 -d "${VPN_CIDR}" \
|
||||||
|
-m conntrack --ctstate ESTABLISHED,RELATED \
|
||||||
|
-m comment --comment amneziawg-internet-return -j ACCEPT
|
||||||
|
"${IPTABLES[@]}" -A "${inactive}" -j RETURN
|
||||||
|
|
||||||
|
# Insert the complete new policy before removing the old jump. There is
|
||||||
|
# never a window with a partially built or absent active policy.
|
||||||
|
"${IPTABLES[@]}" -I "${FORWARD_CHAIN}" 1 -j "${inactive}"
|
||||||
|
if [ -n "${active}" ]; then
|
||||||
|
"${IPTABLES[@]}" -D "${FORWARD_CHAIN}" -j "${active}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
EXT_IF="$(external_interface || true)"
|
||||||
|
if [ -z "${EXT_IF}" ]; then
|
||||||
|
EXT_IF="$(ip route show default | awk '{print $5; exit}')"
|
||||||
|
fi
|
||||||
|
if [ -z "${EXT_IF}" ]; then
|
||||||
|
echo "Unable to detect external interface"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sysctl -w net.ipv4.ip_forward=1
|
||||||
|
sysctl -w net.ipv4.conf.awg0.send_redirects=0
|
||||||
|
|
||||||
|
# Remove rules written by older revisions. In particular, the unqualified
|
||||||
|
# tailscale UDP DROP also blocks Flannel VXLAN (UDP/8472).
|
||||||
|
delete_rule INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP
|
||||||
|
delete_rule INPUT -i "${EXT_IF}" -p udp -m comment --comment amneziawg-allow-external -j ACCEPT
|
||||||
|
delete_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT
|
||||||
|
delete_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP
|
||||||
|
delete_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT
|
||||||
|
delete_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
|
||||||
|
delete_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
|
||||||
|
delete_rule INPUT -m comment --comment amneziawg-input-jump -j AMNEZIAWG-INPUT
|
||||||
|
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -X AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
ensure_chain "${FORWARD_CHAIN}"
|
||||||
|
ensure_chain "${POLICY_CHAIN_A}"
|
||||||
|
ensure_chain "${POLICY_CHAIN_B}"
|
||||||
|
if ! "${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null | grep -q -- '-j RETURN'; then
|
||||||
|
"${IPTABLES[@]}" -A "${FORWARD_CHAIN}" -j RETURN
|
||||||
|
fi
|
||||||
|
apply_policy
|
||||||
|
delete_rule "${FORWARD_CHAIN}" -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
|
||||||
|
delete_rule "${FORWARD_CHAIN}" -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
|
||||||
|
|
||||||
|
# The jump is first, but non-awg traffic immediately RETURNs to the original
|
||||||
|
# host FORWARD chain without an ACCEPT or DROP decision.
|
||||||
|
delete_rule FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
|
||||||
|
"${IPTABLES[@]}" -I FORWARD 1 \
|
||||||
|
-m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
|
||||||
|
|
||||||
|
ensure_rule -A -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
|
||||||
|
|
||||||
|
/scripts/firewall-check.sh "${PORT}" "${VPN_CIDR}"
|
||||||
|
|
||||||
|
firewall-down.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PORT="${1:-5847}"
|
||||||
|
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||||
|
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||||
|
POLICY_CHAIN_A="AMNEZIAWG-POLICY-A"
|
||||||
|
POLICY_CHAIN_B="AMNEZIAWG-POLICY-B"
|
||||||
|
IPTABLES=(iptables -w 30)
|
||||||
|
|
||||||
|
external_interface() {
|
||||||
|
ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
delete_rule() {
|
||||||
|
local table_args=()
|
||||||
|
if [ "${1:-}" = "-t" ]; then
|
||||||
|
table_args=("$1" "$2")
|
||||||
|
shift 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
local chain="$1"
|
||||||
|
shift
|
||||||
|
|
||||||
|
while "${IPTABLES[@]}" "${table_args[@]}" -D "${chain}" "$@" >/dev/null 2>&1; do
|
||||||
|
true
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
EXT_IF="$(external_interface || true)"
|
||||||
|
if [ -z "${EXT_IF}" ]; then
|
||||||
|
EXT_IF="$(ip route show default | awk '{print $5; exit}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${EXT_IF}" ]; then
|
||||||
|
delete_rule INPUT -i "${EXT_IF}" -p udp -m comment --comment amneziawg-allow-external -j ACCEPT
|
||||||
|
delete_rule INPUT -i "${EXT_IF}" -p udp --dport "${PORT}" -m comment --comment amneziawg-allow-external -j ACCEPT
|
||||||
|
delete_rule -t nat POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
|
||||||
|
fi
|
||||||
|
|
||||||
|
delete_rule INPUT -i tailscale0 -p udp --dport "${PORT}" -m comment --comment amneziawg-block-tailscale -j DROP
|
||||||
|
delete_rule INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP
|
||||||
|
delete_rule INPUT -i awg0 -m comment --comment amneziawg-awg-input -j ACCEPT
|
||||||
|
delete_rule FORWARD -i awg0 -m comment --comment amneziawg-forward-in -j ACCEPT
|
||||||
|
delete_rule FORWARD -o awg0 -m comment --comment amneziawg-forward-out -j ACCEPT
|
||||||
|
delete_rule INPUT -m comment --comment amneziawg-input-jump -j AMNEZIAWG-INPUT
|
||||||
|
delete_rule FORWARD -m comment --comment amneziawg-forward-jump -j "${FORWARD_CHAIN}"
|
||||||
|
|
||||||
|
"${IPTABLES[@]}" -F AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -X AMNEZIAWG-INPUT >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -F "${FORWARD_CHAIN}" >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -F "${POLICY_CHAIN_A}" >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -F "${POLICY_CHAIN_B}" >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -X "${POLICY_CHAIN_A}" >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -X "${POLICY_CHAIN_B}" >/dev/null 2>&1 || true
|
||||||
|
"${IPTABLES[@]}" -X "${FORWARD_CHAIN}" >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
firewall-check.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PORT="${1:-5847}"
|
||||||
|
VPN_CIDR="${2:-10.8.0.0/16}"
|
||||||
|
FORWARD_CHAIN="AMNEZIAWG-FORWARD"
|
||||||
|
IPTABLES=(iptables -w 5)
|
||||||
|
|
||||||
|
external_interface() {
|
||||||
|
ip route get 1.1.1.1 | awk '{for (i=1;i<=NF;i++) if ($i=="dev") {print $(i+1); exit}}'
|
||||||
|
}
|
||||||
|
|
||||||
|
EXT_IF="$(external_interface || true)"
|
||||||
|
if [ -z "${EXT_IF}" ]; then
|
||||||
|
EXT_IF="$(ip route show default | awk '{print $5; exit}')"
|
||||||
|
fi
|
||||||
|
if [ -z "${EXT_IF}" ]; then
|
||||||
|
echo "Unable to detect external interface" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if "${IPTABLES[@]}" -C INPUT -i tailscale0 -p udp -m comment --comment amneziawg-block-tailscale -j DROP >/dev/null 2>&1; then
|
||||||
|
echo "Unsafe broad UDP DROP on tailscale0 is present" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if "${IPTABLES[@]}" -C INPUT -i "${EXT_IF}" -p udp -m comment --comment amneziawg-allow-external -j ACCEPT >/dev/null 2>&1; then
|
||||||
|
echo "Unsafe broad UDP ACCEPT on ${EXT_IF} is present" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if "${IPTABLES[@]}" -C INPUT -m comment --comment amneziawg-input-jump -j AMNEZIAWG-INPUT >/dev/null 2>&1; then
|
||||||
|
echo "Unexpected AmneziaWG INPUT chain is present" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
FIRST_FORWARD_RULE="$("${IPTABLES[@]}" -S FORWARD | grep '^-A FORWARD ' | sed -n '1p')"
|
||||||
|
[[ "${FIRST_FORWARD_RULE}" == *"--comment amneziawg-forward-jump"* ]]
|
||||||
|
[[ "${FIRST_FORWARD_RULE}" == *"-j ${FORWARD_CHAIN}"* ]]
|
||||||
|
ACTIVE_POLICY="$("${IPTABLES[@]}" -S "${FORWARD_CHAIN}" 2>/dev/null \
|
||||||
|
| awk '$1 == "-A" && $3 == "-j" && $4 ~ /^AMNEZIAWG-POLICY-[AB]$/ { print $4; exit }')"
|
||||||
|
[ -n "${ACTIVE_POLICY}" ]
|
||||||
|
"${IPTABLES[@]}" -C "${ACTIVE_POLICY}" \
|
||||||
|
-i awg0 -o awg0 -s "${VPN_CIDR}" -d "${VPN_CIDR}" \
|
||||||
|
-m comment --comment amneziawg-client-isolation -j DROP
|
||||||
|
"${IPTABLES[@]}" -C "${ACTIVE_POLICY}" \
|
||||||
|
-i awg0 -o "${EXT_IF}" -s "${VPN_CIDR}" \
|
||||||
|
-m conntrack --ctstate NEW,ESTABLISHED,RELATED \
|
||||||
|
-m comment --comment amneziawg-internet-out -j ACCEPT
|
||||||
|
"${IPTABLES[@]}" -C "${ACTIVE_POLICY}" \
|
||||||
|
-i "${EXT_IF}" -o awg0 -d "${VPN_CIDR}" \
|
||||||
|
-m conntrack --ctstate ESTABLISHED,RELATED \
|
||||||
|
-m comment --comment amneziawg-internet-return -j ACCEPT
|
||||||
|
|
||||||
|
"${IPTABLES[@]}" -t nat -C POSTROUTING -s "${VPN_CIDR}" -o "${EXT_IF}" -m comment --comment amneziawg-masquerade -j MASQUERADE
|
||||||
|
[ "$(sysctl -n net.ipv4.conf.awg0.send_redirects)" = "0" ]
|
||||||
|
awg show awg0 >/dev/null 2>&1
|
||||||
|
|
||||||
|
run.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
SERVER_CONFIG="/etc/amnezia/server/awg0.conf"
|
||||||
|
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}"
|
||||||
|
POLICY_FILE="${AMNEZIAWG_POLICY_FILE:-/run/amnezia/policy/policy.conf}"
|
||||||
|
CONFIG_GENERATION="${AMNEZIAWG_CONFIG_GENERATION:-/run/amnezia/config-generation}"
|
||||||
|
RUNTIME_CONFIG="/run/amnezia/awg0.conf"
|
||||||
|
SYNC_CONFIG="/run/amnezia/awg0.sync.conf"
|
||||||
|
STATUS_FILE="/run/amnezia/reload-status"
|
||||||
|
RELOAD_INTERVAL="${AMNEZIAWG_RELOAD_INTERVAL:-10}"
|
||||||
|
|
||||||
|
cleanup() {
|
||||||
|
if awg show awg0 >/dev/null 2>&1; then
|
||||||
|
awg-quick down "${RUNTIME_CONFIG}" || ip link delete awg0 || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
render_config() {
|
||||||
|
mkdir -p "$(dirname "${RUNTIME_CONFIG}")"
|
||||||
|
local tmp_config="${RUNTIME_CONFIG}.tmp"
|
||||||
|
cp "${SERVER_CONFIG}" "${tmp_config}"
|
||||||
|
chmod 0600 "${tmp_config}"
|
||||||
|
|
||||||
|
local clients_found=0
|
||||||
|
for client_config in "${CLIENTS_DIR}"/*; do
|
||||||
|
[ -f "${client_config}" ] || continue
|
||||||
|
[ -s "${client_config}" ] || continue
|
||||||
|
printf '\n' >> "${tmp_config}"
|
||||||
|
cat "${client_config}" >> "${tmp_config}"
|
||||||
|
clients_found=1
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "${clients_found}" = "0" ]; then
|
||||||
|
echo "No client peer configs found in ${CLIENTS_DIR}; starting without peers"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "${tmp_config}" "${RUNTIME_CONFIG}"
|
||||||
|
chmod 0600 "${RUNTIME_CONFIG}"
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime_config_hash() {
|
||||||
|
if [ -f "${CONFIG_GENERATION}" ]; then
|
||||||
|
sha256sum "${CONFIG_GENERATION}" | awk '{print $1}'
|
||||||
|
else
|
||||||
|
printf 'missing\n'
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
write_reload_status() {
|
||||||
|
local state="${1}"
|
||||||
|
local hash="${2:-}"
|
||||||
|
local applied_at_ms=""
|
||||||
|
if [ "${state}" = "applied" ]; then
|
||||||
|
applied_at_ms="$(($(date +%s) * 1000))"
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "${STATUS_FILE}")"
|
||||||
|
{
|
||||||
|
printf 'state=%s\n' "${state}"
|
||||||
|
printf 'hash=%s\n' "${hash}"
|
||||||
|
printf 'applied_at_ms=%s\n' "${applied_at_ms}"
|
||||||
|
} > "${STATUS_FILE}.tmp"
|
||||||
|
mv "${STATUS_FILE}.tmp" "${STATUS_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
apply_live_config() {
|
||||||
|
# Fail closed during a peer/key and policy transition. /dev/null produces
|
||||||
|
# an Internet-only policy with all awg0-to-awg0 traffic isolated.
|
||||||
|
/scripts/firewall-up.sh 5847 10.8.0.0/16 /dev/null
|
||||||
|
render_config
|
||||||
|
awg-quick strip "${RUNTIME_CONFIG}" > "${SYNC_CONFIG}"
|
||||||
|
chmod 0600 "${SYNC_CONFIG}"
|
||||||
|
awg syncconf awg0 "${SYNC_CONFIG}"
|
||||||
|
/scripts/firewall-up.sh 5847 10.8.0.0/16 "${POLICY_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
watch_client_config() {
|
||||||
|
local last_hash="${1}"
|
||||||
|
while true; do
|
||||||
|
sleep "${RELOAD_INTERVAL}" &
|
||||||
|
wait "$!" || return 0
|
||||||
|
|
||||||
|
local current_hash
|
||||||
|
current_hash="$(runtime_config_hash)"
|
||||||
|
if [ "${current_hash}" = "${last_hash}" ]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Detected AmneziaWG client peer config change; applying with awg syncconf"
|
||||||
|
if apply_live_config; then
|
||||||
|
last_hash="${current_hash}"
|
||||||
|
write_reload_status applied "${current_hash}"
|
||||||
|
awg show awg0 || true
|
||||||
|
else
|
||||||
|
echo "ERROR: failed to hot-reload AmneziaWG client peer config" >&2
|
||||||
|
write_reload_status error "${current_hash}"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT
|
||||||
|
trap 'exit 0' TERM INT
|
||||||
|
|
||||||
|
initial_hash="$(runtime_config_hash)"
|
||||||
|
render_config
|
||||||
|
cleanup
|
||||||
|
awg-quick up "${RUNTIME_CONFIG}"
|
||||||
|
awg show awg0 || true
|
||||||
|
write_reload_status applied "${initial_hash}"
|
||||||
|
watch_client_config "${initial_hash}"
|
||||||
|
|
||||||
|
client-secret-sync.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
CLIENT_SECRET="${AMNEZIAWG_CLIENT_SECRET:-amneziawg-clients}"
|
||||||
|
CLIENT_SECRET_KEY="${AMNEZIAWG_CLIENT_SECRET_KEY:-peers.conf}"
|
||||||
|
CLIENTS_DIR="${AMNEZIAWG_CLIENTS_DIR:-/run/amnezia/clients}"
|
||||||
|
PEERS_FILE="${CLIENTS_DIR}/peers.conf"
|
||||||
|
POLICY_FILE="${AMNEZIAWG_POLICY_FILE:-/run/amnezia/policy/policy.conf}"
|
||||||
|
CONFIG_GENERATION="${AMNEZIAWG_CONFIG_GENERATION:-/run/amnezia/config-generation}"
|
||||||
|
SYNC_INTERVAL="${AMNEZIAWG_CLIENT_SECRET_SYNC_INTERVAL:-5}"
|
||||||
|
NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}"
|
||||||
|
|
||||||
|
write_empty_once() {
|
||||||
|
mkdir -p "${CLIENTS_DIR}"
|
||||||
|
if [ ! -f "${PEERS_FILE}" ]; then
|
||||||
|
: > "${PEERS_FILE}"
|
||||||
|
chmod 0600 "${PEERS_FILE}"
|
||||||
|
fi
|
||||||
|
mkdir -p "$(dirname "${POLICY_FILE}")"
|
||||||
|
if [ ! -f "${POLICY_FILE}" ]; then
|
||||||
|
: > "${POLICY_FILE}"
|
||||||
|
chmod 0600 "${POLICY_FILE}"
|
||||||
|
fi
|
||||||
|
{
|
||||||
|
sha256sum "${PEERS_FILE}"
|
||||||
|
sha256sum "${POLICY_FILE}"
|
||||||
|
} | sha256sum | awk '{print $1}' > "${CONFIG_GENERATION}"
|
||||||
|
}
|
||||||
|
|
||||||
|
sync_once() {
|
||||||
|
mkdir -p "${CLIENTS_DIR}"
|
||||||
|
mkdir -p "$(dirname "${POLICY_FILE}")"
|
||||||
|
local snapshot="${CONFIG_GENERATION}.snapshot"
|
||||||
|
local peers_tmp="${PEERS_FILE}.tmp"
|
||||||
|
local policy_tmp="${POLICY_FILE}.tmp"
|
||||||
|
local peers_encoded policy_encoded generation
|
||||||
|
|
||||||
|
if ! kubectl get secret "${CLIENT_SECRET}" -n "${NAMESPACE}" \
|
||||||
|
-o "go-template={{ with index .data \"${CLIENT_SECRET_KEY}\" }}{{ . }}{{ end }}{{ \"\n\" }}{{ with index .data \"policy.conf\" }}{{ . }}{{ end }}{{ \"\n\" }}" \
|
||||||
|
> "${snapshot}" 2>/dev/null; then
|
||||||
|
echo "WARN: failed to read Secret ${NAMESPACE}/${CLIENT_SECRET}; keeping current peers" >&2
|
||||||
|
rm -f "${snapshot}"
|
||||||
|
write_empty_once
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
peers_encoded="$(sed -n '1p' "${snapshot}")"
|
||||||
|
policy_encoded="$(sed -n '2p' "${snapshot}")"
|
||||||
|
rm -f "${snapshot}"
|
||||||
|
|
||||||
|
if [ -n "${peers_encoded}" ]; then
|
||||||
|
printf '%s' "${peers_encoded}" | base64 -d > "${peers_tmp}"
|
||||||
|
else
|
||||||
|
: > "${peers_tmp}"
|
||||||
|
fi
|
||||||
|
if [ -n "${policy_encoded}" ]; then
|
||||||
|
printf '%s' "${policy_encoded}" | base64 -d > "${policy_tmp}"
|
||||||
|
else
|
||||||
|
# Missing policy is fail-closed: no client-to-client pairs are allowed.
|
||||||
|
: > "${policy_tmp}"
|
||||||
|
fi
|
||||||
|
chmod 0600 "${peers_tmp}" "${policy_tmp}"
|
||||||
|
|
||||||
|
generation="$({
|
||||||
|
sha256sum "${peers_tmp}"
|
||||||
|
sha256sum "${policy_tmp}"
|
||||||
|
} | sha256sum | awk '{print $1}')"
|
||||||
|
if [ -f "${CONFIG_GENERATION}" ] \
|
||||||
|
&& [ "$(sed -n '1p' "${CONFIG_GENERATION}")" = "${generation}" ]; then
|
||||||
|
rm -f "${peers_tmp}" "${policy_tmp}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mv "${policy_tmp}" "${POLICY_FILE}"
|
||||||
|
mv "${peers_tmp}" "${PEERS_FILE}"
|
||||||
|
printf '%s\n' "${generation}" > "${CONFIG_GENERATION}.tmp"
|
||||||
|
mv "${CONFIG_GENERATION}.tmp" "${CONFIG_GENERATION}"
|
||||||
|
echo "Synced AmneziaWG peers and policy from Secret ${NAMESPACE}/${CLIENT_SECRET}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${1:-}" = "once" ]; then
|
||||||
|
sync_once
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
sync_once || true
|
||||||
|
sleep "${SYNC_INTERVAL}"
|
||||||
|
done
|
||||||
|
|
||||||
|
status-patch.sh: |
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
STATUS_FILE="/run/amnezia/reload-status"
|
||||||
|
PATCH_INTERVAL="${AMNEZIAWG_STATUS_PATCH_INTERVAL:-5}"
|
||||||
|
NAMESPACE="${POD_NAMESPACE:-$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)}"
|
||||||
|
: "${POD_NAME:?POD_NAME is required}"
|
||||||
|
|
||||||
|
last_file_hash=""
|
||||||
|
|
||||||
|
patch_status() {
|
||||||
|
local state="unknown"
|
||||||
|
local hash=""
|
||||||
|
local applied_at_ms=""
|
||||||
|
|
||||||
|
# The file is generated by run.sh and contains only shell assignments.
|
||||||
|
# shellcheck disable=SC1090
|
||||||
|
source "${STATUS_FILE}"
|
||||||
|
|
||||||
|
kubectl patch pod "${POD_NAME}" -n "${NAMESPACE}" --type merge -p "{\"metadata\":{\"annotations\":{\"amnezia-fellow.hexor.cy/client-secret-reload-status\":\"${state}\",\"amnezia-fellow.hexor.cy/client-secret-applied-at-ms\":\"${applied_at_ms}\",\"amnezia-fellow.hexor.cy/client-secret-applied-hash\":\"${hash}\"}}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
if [ -f "${STATUS_FILE}" ]; then
|
||||||
|
file_hash="$(sha256sum "${STATUS_FILE}" | awk '{print $1}')"
|
||||||
|
if [ "${file_hash}" != "${last_file_hash}" ]; then
|
||||||
|
patch_status || true
|
||||||
|
last_file_hash="${file_hash}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
sleep "${PATCH_INTERVAL}"
|
||||||
|
done
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: DaemonSet
|
||||||
|
metadata:
|
||||||
|
name: amneziawg
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
annotations:
|
||||||
|
reloader.stakater.com/auto: "false"
|
||||||
|
secret.reloader.stakater.com/reload: "amneziawg-server"
|
||||||
|
configmap.reloader.stakater.com/reload: "amneziawg-scripts,amneziawg-exporter-redis"
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: amneziawg
|
||||||
|
updateStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
spec:
|
||||||
|
serviceAccountName: amneziawg
|
||||||
|
hostNetwork: true
|
||||||
|
dnsPolicy: ClusterFirstWithHostNet
|
||||||
|
nodeSelector:
|
||||||
|
amnezia-vpn: "true"
|
||||||
|
tolerations:
|
||||||
|
- operator: Exists
|
||||||
|
initContainers:
|
||||||
|
- name: install-awg
|
||||||
|
image: amneziavpn/amneziawg-go:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
cp /usr/bin/awg /shared-bin/awg
|
||||||
|
cp /lib/ld-musl-x86_64.so.1 /shared-bin/ld-musl-x86_64.so.1
|
||||||
|
cp /lib/ld-musl-x86_64.so.1 /shared-bin/libc.musl-x86_64.so.1
|
||||||
|
chmod 0755 /shared-bin/awg /shared-bin/ld-musl-x86_64.so.1 /shared-bin/libc.musl-x86_64.so.1
|
||||||
|
volumeMounts:
|
||||||
|
- name: awg-bin
|
||||||
|
mountPath: /shared-bin
|
||||||
|
- name: register-endpoint
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: NODE_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: spec.nodeName
|
||||||
|
- name: PORT
|
||||||
|
value: "5847"
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
NAMESPACE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)"
|
||||||
|
ENDPOINT="$(kubectl get node "${NODE_NAME}" -o jsonpath="{.metadata.labels['external-ipv4']}")"
|
||||||
|
|
||||||
|
if [ -z "${ENDPOINT}" ]; then
|
||||||
|
ENDPOINT="$(kubectl get node "${NODE_NAME}" -o jsonpath='{range .status.addresses[?(@.type=="ExternalIP")]}{.address}{end}')"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${ENDPOINT}" ]; then
|
||||||
|
echo "ERROR: node ${NODE_NAME} has no external-ipv4 label and no ExternalIP"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
VALUE="${ENDPOINT}:${PORT}"
|
||||||
|
echo "Registering AmneziaWG endpoint: ${NODE_NAME} -> ${VALUE}"
|
||||||
|
|
||||||
|
if kubectl get secret amneziawg-endpoints -n "${NAMESPACE}" >/dev/null 2>&1; then
|
||||||
|
kubectl patch secret amneziawg-endpoints -n "${NAMESPACE}" \
|
||||||
|
--type merge -p "{\"stringData\":{\"${NODE_NAME}\":\"${VALUE}\"}}"
|
||||||
|
else
|
||||||
|
kubectl create secret generic amneziawg-endpoints -n "${NAMESPACE}" \
|
||||||
|
--from-literal="${NODE_NAME}=${VALUE}"
|
||||||
|
fi
|
||||||
|
- name: sync-client-secret
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- /scripts/client-secret-sync.sh
|
||||||
|
- once
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "10m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
readOnly: true
|
||||||
|
- name: runtime-config
|
||||||
|
mountPath: /run/amnezia
|
||||||
|
containers:
|
||||||
|
- name: amneziawg
|
||||||
|
image: amneziavpn/amneziawg-go:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
privileged: true
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_ADMIN
|
||||||
|
- SYS_MODULE
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- /scripts/run.sh
|
||||||
|
ports:
|
||||||
|
- name: awg
|
||||||
|
containerPort: 5847
|
||||||
|
protocol: UDP
|
||||||
|
readinessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
- /scripts/firewall-check.sh 5847 10.8.0.0/16
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 8
|
||||||
|
failureThreshold: 2
|
||||||
|
startupProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
|
||||||
|
periodSeconds: 5
|
||||||
|
timeoutSeconds: 15
|
||||||
|
failureThreshold: 12
|
||||||
|
livenessProbe:
|
||||||
|
exec:
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
- /scripts/firewall-check.sh 5847 10.8.0.0/16 || /scripts/firewall-up.sh 5847 10.8.0.0/16
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 15
|
||||||
|
failureThreshold: 3
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "50m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "500m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: server-config
|
||||||
|
mountPath: /etc/amnezia/server
|
||||||
|
readOnly: true
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
readOnly: true
|
||||||
|
- name: runtime-config
|
||||||
|
mountPath: /run/amnezia
|
||||||
|
- name: dev-net-tun
|
||||||
|
mountPath: /dev/net/tun
|
||||||
|
- name: reload-status
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: POD_NAME
|
||||||
|
valueFrom:
|
||||||
|
fieldRef:
|
||||||
|
fieldPath: metadata.name
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- /scripts/status-patch.sh
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "10m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
readOnly: true
|
||||||
|
- name: runtime-config
|
||||||
|
mountPath: /run/amnezia
|
||||||
|
- name: client-secret-sync
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- /scripts/client-secret-sync.sh
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "10m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: scripts
|
||||||
|
mountPath: /scripts
|
||||||
|
readOnly: true
|
||||||
|
- name: runtime-config
|
||||||
|
mountPath: /run/amnezia
|
||||||
|
- name: amneziawg-exporter-redis
|
||||||
|
image: redis:alpine
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- redis-server
|
||||||
|
- /etc/redis/redis.conf
|
||||||
|
ports:
|
||||||
|
- name: redis
|
||||||
|
containerPort: 6379
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "10m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: exporter-redis-config
|
||||||
|
mountPath: /etc/redis
|
||||||
|
readOnly: true
|
||||||
|
- name: exporter-redis-data
|
||||||
|
mountPath: /data
|
||||||
|
- name: amneziawg-exporter
|
||||||
|
image: amneziavpn/amneziawg-exporter:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
securityContext:
|
||||||
|
capabilities:
|
||||||
|
add:
|
||||||
|
- NET_ADMIN
|
||||||
|
env:
|
||||||
|
- name: AWG_EXPORTER_REDIS_HOST
|
||||||
|
value: "127.0.0.1"
|
||||||
|
- name: AWG_EXPORTER_REDIS_PORT
|
||||||
|
value: "6379"
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
containerPort: 9351
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "64Mi"
|
||||||
|
cpu: "25m"
|
||||||
|
limits:
|
||||||
|
memory: "256Mi"
|
||||||
|
cpu: "200m"
|
||||||
|
volumeMounts:
|
||||||
|
- name: awg-bin
|
||||||
|
mountPath: /usr/bin/awg
|
||||||
|
subPath: awg
|
||||||
|
readOnly: true
|
||||||
|
- name: awg-bin
|
||||||
|
mountPath: /lib/ld-musl-x86_64.so.1
|
||||||
|
subPath: ld-musl-x86_64.so.1
|
||||||
|
readOnly: true
|
||||||
|
- name: awg-bin
|
||||||
|
mountPath: /lib/libc.musl-x86_64.so.1
|
||||||
|
subPath: libc.musl-x86_64.so.1
|
||||||
|
readOnly: true
|
||||||
|
volumes:
|
||||||
|
- name: server-config
|
||||||
|
secret:
|
||||||
|
secretName: amneziawg-server
|
||||||
|
defaultMode: 0600
|
||||||
|
items:
|
||||||
|
- key: awg0.conf
|
||||||
|
path: awg0.conf
|
||||||
|
- name: scripts
|
||||||
|
configMap:
|
||||||
|
name: amneziawg-scripts
|
||||||
|
defaultMode: 0755
|
||||||
|
- name: runtime-config
|
||||||
|
emptyDir: {}
|
||||||
|
- name: awg-bin
|
||||||
|
emptyDir: {}
|
||||||
|
- name: exporter-redis-config
|
||||||
|
configMap:
|
||||||
|
name: amneziawg-exporter-redis
|
||||||
|
- name: exporter-redis-data
|
||||||
|
emptyDir: {}
|
||||||
|
- name: dev-net-tun
|
||||||
|
hostPath:
|
||||||
|
path: /dev/net/tun
|
||||||
|
type: CharDevice
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-exporter-redis
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
component: exporter
|
||||||
|
data:
|
||||||
|
redis.conf: |
|
||||||
|
bind 127.0.0.1
|
||||||
|
protected-mode yes
|
||||||
|
port 6379
|
||||||
|
tcp-backlog 511
|
||||||
|
timeout 0
|
||||||
|
tcp-keepalive 300
|
||||||
|
daemonize no
|
||||||
|
pidfile /run/redis.pid
|
||||||
|
loglevel warning
|
||||||
|
logfile ""
|
||||||
|
databases 16
|
||||||
|
always-show-logo no
|
||||||
|
set-proc-title no
|
||||||
|
save ""
|
||||||
|
appendonly no
|
||||||
|
stop-writes-on-bgsave-error no
|
||||||
|
rdbcompression yes
|
||||||
|
rdbchecksum yes
|
||||||
|
dir /data
|
||||||
|
rename-command CONFIG ""
|
||||||
|
rename-command SAVE ""
|
||||||
|
rename-command BGSAVE ""
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-exporter
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
component: exporter
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: amneziawg
|
||||||
|
ports:
|
||||||
|
- name: metrics
|
||||||
|
protocol: TCP
|
||||||
|
port: 9351
|
||||||
|
targetPort: 9351
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-server
|
||||||
|
spec:
|
||||||
|
target:
|
||||||
|
name: amneziawg-server
|
||||||
|
deletionPolicy: Delete
|
||||||
|
template:
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
server-public-key: |-
|
||||||
|
{{ .server_public_key }}
|
||||||
|
awg0.conf: |-
|
||||||
|
[Interface]
|
||||||
|
PrivateKey = {{ .server_private_key }}
|
||||||
|
Address = 10.8.0.1/16
|
||||||
|
ListenPort = 5847
|
||||||
|
MTU = 1376
|
||||||
|
Jc = 4
|
||||||
|
Jmin = 64
|
||||||
|
Jmax = 128
|
||||||
|
S1 = 15
|
||||||
|
S2 = 18
|
||||||
|
S3 = 20
|
||||||
|
S4 = 23
|
||||||
|
H1 = 1020325451
|
||||||
|
H2 = 3288052141
|
||||||
|
H3 = 1766607858
|
||||||
|
H4 = 2528465083
|
||||||
|
PostUp = /scripts/firewall-up.sh 5847 10.8.0.0/16
|
||||||
|
PostDown = /scripts/firewall-down.sh 5847 10.8.0.0/16
|
||||||
|
data:
|
||||||
|
- secretKey: server_private_key
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: 3092dc7c-41dd-461a-9f7a-377727f47e93
|
||||||
|
property: fields[0].value
|
||||||
|
- secretKey: server_public_key
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: 3092dc7c-41dd-461a-9f7a-377727f47e93
|
||||||
|
property: fields[1].value
|
||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow
|
||||||
|
spec:
|
||||||
|
target:
|
||||||
|
name: amnezia-fellow
|
||||||
|
deletionPolicy: Delete
|
||||||
|
template:
|
||||||
|
engineVersion: v2
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
database-url: |-
|
||||||
|
postgresql://amnezia_fellow:{{ .amnezia_fellow | urlquery }}@psql.psql.svc:5432/amnezia_fellow
|
||||||
|
postgres-password: |-
|
||||||
|
{{ .amnezia_fellow }}
|
||||||
|
data:
|
||||||
|
- secretKey: amnezia_fellow
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
conversionStrategy: Default
|
||||||
|
decodingStrategy: None
|
||||||
|
metadataPolicy: None
|
||||||
|
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||||
|
property: fields[19].value
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
annotations:
|
||||||
|
secret.reloader.stakater.com/reload: "amnezia-fellow"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
spec:
|
||||||
|
serviceAccountName: amnezia-fellow
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/os: linux
|
||||||
|
kubernetes.io/hostname: cy.tail2fe2d.ts.net
|
||||||
|
containers:
|
||||||
|
- name: amnezia-fellow
|
||||||
|
image: ultradesu/amnezia-fellow:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8000
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: AMNEZIA_FELLOW_DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: amnezia-fellow
|
||||||
|
key: database-url
|
||||||
|
- name: AMNEZIA_FELLOW_K8S_NAMESPACE
|
||||||
|
value: "amnezia"
|
||||||
|
- name: AMNEZIA_FELLOW_K8S_CLIENTS_SECRET
|
||||||
|
value: "amneziawg-clients"
|
||||||
|
- name: AMNEZIA_FELLOW_K8S_CLIENTS_SECRET_KEY
|
||||||
|
value: "peers.conf"
|
||||||
|
- name: AMNEZIA_FELLOW_K8S_SERVER_SECRET
|
||||||
|
value: "amneziawg-server"
|
||||||
|
- name: AMNEZIA_FELLOW_K8S_ENDPOINTS_SECRET
|
||||||
|
value: "amneziawg-endpoints"
|
||||||
|
- name: AMNEZIA_FELLOW_VPN_CLIENT_CIDR
|
||||||
|
value: "10.8.0.0/16"
|
||||||
|
- name: AMNEZIA_FELLOW_VPN_MTU
|
||||||
|
value: "1376"
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 30
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 5
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "50m"
|
||||||
|
memory: "128Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow-tls-ingress
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
|
||||||
|
acme.cert-manager.io/http01-edit-in-place: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: awg.hexor.cy
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: amnezia-fellow
|
||||||
|
port:
|
||||||
|
number: 8000
|
||||||
|
tls:
|
||||||
|
- secretName: amnezia-fellow-tls
|
||||||
|
hosts:
|
||||||
|
- awg.hexor.cy
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "create", "update", "patch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: amnezia-fellow
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: amnezia-fellow
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: amnezia-fellow
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
protocol: TCP
|
||||||
|
port: 8000
|
||||||
|
targetPort: 8000
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: amnezia-fellow-data
|
||||||
|
labels:
|
||||||
|
app: amnezia-fellow
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: longhorn
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 3Gi
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- app.yaml
|
||||||
|
- namespace.yaml
|
||||||
|
- external-secrets.yaml
|
||||||
|
- configmap-scripts.yaml
|
||||||
|
- rbac.yaml
|
||||||
|
- fellow-rbac.yaml
|
||||||
|
- fellow-storage.yaml
|
||||||
|
- fellow-service.yaml
|
||||||
|
- fellow-ingress.yaml
|
||||||
|
- fellow-deployment.yaml
|
||||||
|
- exporter-redis-configmap.yaml
|
||||||
|
- exporter-service.yaml
|
||||||
|
- servicemonitor.yaml
|
||||||
|
- daemonset.yaml
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: amnezia
|
||||||
|
labels:
|
||||||
|
pod-security.kubernetes.io/enforce: privileged
|
||||||
|
pod-security.kubernetes.io/audit: privileged
|
||||||
|
pod-security.kubernetes.io/warn: privileged
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: amneziawg
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-node-reader
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["nodes"]
|
||||||
|
verbs: ["get", "list"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-node-reader
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: amneziawg-node-reader
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: amneziawg
|
||||||
|
namespace: amnezia
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-endpoint-manager
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["secrets"]
|
||||||
|
verbs: ["get", "create", "patch"]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["pods"]
|
||||||
|
verbs: ["get", "patch"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-endpoint-manager
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: amneziawg-endpoint-manager
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: amneziawg
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: amneziawg-exporter
|
||||||
|
labels:
|
||||||
|
app: amneziawg
|
||||||
|
component: exporter
|
||||||
|
release: prometheus
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: amneziawg
|
||||||
|
component: exporter
|
||||||
|
endpoints:
|
||||||
|
- port: metrics
|
||||||
|
path: /metrics
|
||||||
|
interval: 30s
|
||||||
|
scrapeTimeout: 10s
|
||||||
|
honorLabels: true
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- amnezia
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: doka2-lobby-list
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: apps
|
||||||
|
destination:
|
||||||
|
namespace: doka2-lobby-list
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
source:
|
||||||
|
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: k8s/apps/doka2-lobby-list
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
selfHeal: true
|
||||||
|
prune: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: doka2-lobby-list
|
||||||
|
labels:
|
||||||
|
app: doka2-lobby-list
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: doka2-lobby-list
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: doka2-lobby-list
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/os: linux
|
||||||
|
securityContext:
|
||||||
|
fsGroup: 1000
|
||||||
|
fsGroupChangePolicy: OnRootMismatch
|
||||||
|
volumes:
|
||||||
|
- name: creds
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: doka2-lobby-list-creds
|
||||||
|
containers:
|
||||||
|
- name: doka2-lobby-list
|
||||||
|
image: ultradesu/doka2-lobby-list:latest
|
||||||
|
imagePullPolicy: Always
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 3000
|
||||||
|
protocol: TCP
|
||||||
|
env:
|
||||||
|
- name: ADMIN_PASSWORD
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: doka2-lobby-list-secrets
|
||||||
|
key: ADMIN_PASSWORD
|
||||||
|
- name: CREDS_DIR
|
||||||
|
value: /data
|
||||||
|
volumeMounts:
|
||||||
|
- name: creds
|
||||||
|
mountPath: /data
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: "50m"
|
||||||
|
memory: "128Mi"
|
||||||
|
limits:
|
||||||
|
cpu: "500m"
|
||||||
|
memory: "512Mi"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: doka2-lobby-list-secrets
|
||||||
|
spec:
|
||||||
|
target:
|
||||||
|
name: doka2-lobby-list-secrets
|
||||||
|
deletionPolicy: Delete
|
||||||
|
template:
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
ADMIN_PASSWORD: |-
|
||||||
|
{{ .admin_password | trim }}
|
||||||
|
data:
|
||||||
|
- secretKey: admin_password
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
conversionStrategy: Default
|
||||||
|
decodingStrategy: None
|
||||||
|
metadataPolicy: None
|
||||||
|
key: af4618dd-6431-4b20-b617-c00bae9ceff6
|
||||||
|
property: login.password
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: Ingress
|
||||||
|
metadata:
|
||||||
|
name: doka2-lobby-list-tls-ingress
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
|
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
|
||||||
|
acme.cert-manager.io/http01-edit-in-place: "true"
|
||||||
|
spec:
|
||||||
|
ingressClassName: traefik
|
||||||
|
rules:
|
||||||
|
- host: doka.hexor.cy
|
||||||
|
http:
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
backend:
|
||||||
|
service:
|
||||||
|
name: doka2-lobby-list
|
||||||
|
port:
|
||||||
|
number: 80
|
||||||
|
tls:
|
||||||
|
- secretName: doka2-lobby-list-tls
|
||||||
|
hosts:
|
||||||
|
- doka.hexor.cy
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- app.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- external-secrets.yaml
|
||||||
|
- service.yaml
|
||||||
|
- ingress.yaml
|
||||||
|
- storage.yaml
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: doka2-lobby-list
|
||||||
|
labels:
|
||||||
|
app: doka2-lobby-list
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: doka2-lobby-list
|
||||||
|
ports:
|
||||||
|
- port: 80
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
|
name: http
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: doka2-lobby-list-creds
|
||||||
|
labels:
|
||||||
|
app: doka2-lobby-list
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: longhorn
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 1Gi
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: firefly-iii
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: apps
|
||||||
|
destination:
|
||||||
|
namespace: firefly-iii
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
source:
|
||||||
|
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: k8s/apps/firefly-iii
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
selfHeal: true
|
||||||
|
prune: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer-auth
|
||||||
|
spec:
|
||||||
|
forwardAuth:
|
||||||
|
address: http://auth-proxy.auth-proxy.svc:80/auth
|
||||||
|
trustForwardHeader: true
|
||||||
|
authResponseHeaders:
|
||||||
|
- X-Auth-Request-User
|
||||||
|
- X-Auth-Request-Email
|
||||||
|
- X-Auth-Request-Groups
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
# OAuth callback is intentionally public. Data Importer validates both the
|
||||||
|
# state parameter and its browser session before exchanging the code.
|
||||||
|
- match: Host(`import.hexor.cy`) && Path(`/eb-callback`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 200
|
||||||
|
services:
|
||||||
|
- name: firefly-data-importer
|
||||||
|
port: 80
|
||||||
|
# Everything else requires a valid auth-proxy session and group.
|
||||||
|
- match: Host(`import.hexor.cy`)
|
||||||
|
kind: Rule
|
||||||
|
priority: 100
|
||||||
|
middlewares:
|
||||||
|
- name: firefly-data-importer-auth
|
||||||
|
services:
|
||||||
|
- name: firefly-data-importer
|
||||||
|
port: 80
|
||||||
|
tls:
|
||||||
|
secretName: firefly-data-importer-tls
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer-tls
|
||||||
|
spec:
|
||||||
|
secretName: firefly-data-importer-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- import.hexor.cy
|
||||||
|
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer-sync
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer-sync
|
||||||
|
spec:
|
||||||
|
schedule: "17 */6 * * *"
|
||||||
|
timeZone: Europe/London
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
startingDeadlineSeconds: 1800
|
||||||
|
successfulJobsHistoryLimit: 2
|
||||||
|
failedJobsHistoryLimit: 5
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
activeDeadlineSeconds: 1200
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer-sync
|
||||||
|
spec:
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
restartPolicy: Never
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: data-importer-sync
|
||||||
|
image: fireflyiii/data-importer:version-2.3.4
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- |-
|
||||||
|
php artisan importer:import /import/import.json
|
||||||
|
status=$?
|
||||||
|
if [ "${status}" -eq 73 ]; then
|
||||||
|
echo "No new transactions; considering the sync successful."
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
exit "${status}"
|
||||||
|
env:
|
||||||
|
- name: FIREFLY_III_URL
|
||||||
|
value: http://firefly-iii
|
||||||
|
- name: VANITY_URL
|
||||||
|
value: http://ff.lan
|
||||||
|
- name: FIREFLY_III_ACCESS_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
key: FIREFLY_III_ACCESS_TOKEN
|
||||||
|
- name: ENABLE_BANKING_APP_ID
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
key: ENABLE_BANKING_APP_ID
|
||||||
|
- name: ENABLE_BANKING_PRIVATE_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
key: ENABLE_BANKING_PRIVATE_KEY
|
||||||
|
- name: IMPORT_DIR_ALLOWLIST
|
||||||
|
value: /import
|
||||||
|
# Recurring imports intentionally overlap their date range.
|
||||||
|
# Keep rejecting duplicates, but do not fail the Job for them.
|
||||||
|
- name: IGNORE_DUPLICATE_ERRORS
|
||||||
|
value: "true"
|
||||||
|
- name: TZ
|
||||||
|
value: Europe/London
|
||||||
|
- name: APP_ENV
|
||||||
|
value: production
|
||||||
|
- name: APP_DEBUG
|
||||||
|
value: "false"
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: notice
|
||||||
|
volumeMounts:
|
||||||
|
- name: import-config
|
||||||
|
mountPath: /import
|
||||||
|
readOnly: true
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 512Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
volumes:
|
||||||
|
- name: import-config
|
||||||
|
secret:
|
||||||
|
secretName: firefly-data-importer-secrets
|
||||||
|
items:
|
||||||
|
- key: import.json
|
||||||
|
path: import.json
|
||||||
|
securityContext:
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
annotations:
|
||||||
|
reloader.stakater.com/auto: "true"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: data-importer
|
||||||
|
image: fireflyiii/data-importer:version-2.3.4
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: FIREFLY_III_URL
|
||||||
|
value: http://firefly-iii
|
||||||
|
- name: VANITY_URL
|
||||||
|
value: http://ff.lan
|
||||||
|
- name: TRUSTED_PROXIES
|
||||||
|
value: "**"
|
||||||
|
- name: EXPECT_SECURE_URL
|
||||||
|
value: "false"
|
||||||
|
- name: VERIFY_TLS_SECURITY
|
||||||
|
value: "true"
|
||||||
|
- name: TZ
|
||||||
|
value: Europe/London
|
||||||
|
- name: APP_ENV
|
||||||
|
value: production
|
||||||
|
- name: APP_DEBUG
|
||||||
|
value: "false"
|
||||||
|
- name: LOG_LEVEL
|
||||||
|
value: notice
|
||||||
|
- name: ENABLE_BANKING_APP_ID
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
key: ENABLE_BANKING_APP_ID
|
||||||
|
- name: ENABLE_BANKING_PRIVATE_KEY
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
key: ENABLE_BANKING_PRIVATE_KEY
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 30
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 512Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
securityContext:
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 80
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: firefly-secrets
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
target:
|
||||||
|
name: firefly-secrets
|
||||||
|
creationPolicy: Owner
|
||||||
|
deletionPolicy: Delete
|
||||||
|
template:
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
# Firefly and CloudNativePG deliberately share this generated Secret.
|
||||||
|
username: firefly
|
||||||
|
password: |-
|
||||||
|
{{ .db_password }}
|
||||||
|
DB_PASSWORD: |-
|
||||||
|
{{ .db_password }}
|
||||||
|
APP_KEY: |-
|
||||||
|
{{ .app_key }}
|
||||||
|
STATIC_CRON_TOKEN: |-
|
||||||
|
{{ .cron_token }}
|
||||||
|
data:
|
||||||
|
- secretKey: db_password
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[0].value
|
||||||
|
- secretKey: app_key
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[1].value
|
||||||
|
- secretKey: cron_token
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[2].value
|
||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
spec:
|
||||||
|
refreshInterval: 1h
|
||||||
|
target:
|
||||||
|
name: firefly-data-importer-secrets
|
||||||
|
creationPolicy: Owner
|
||||||
|
deletionPolicy: Delete
|
||||||
|
template:
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
ENABLE_BANKING_APP_ID: |-
|
||||||
|
{{ .app_id }}
|
||||||
|
ENABLE_BANKING_PRIVATE_KEY: |-
|
||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
{{ .private_key | replace "-----BEGIN PRIVATE KEY-----" "" | replace "-----END PRIVATE KEY-----" "" | replace " " "" | trim }}
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
|
FIREFLY_III_ACCESS_TOKEN: |-
|
||||||
|
{{ .firefly_access_token }}
|
||||||
|
import.json: |-
|
||||||
|
{{ .import_config }}
|
||||||
|
data:
|
||||||
|
- secretKey: app_id
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[3].value
|
||||||
|
- secretKey: private_key
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[4].value
|
||||||
|
- secretKey: firefly_access_token
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[6].value
|
||||||
|
- secretKey: import_config
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
key: a1867c81-715c-47cd-978d-14ea5bcedea9
|
||||||
|
property: fields[5].value
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- app.yaml
|
||||||
|
- data-importer.yaml
|
||||||
|
- data-importer-sync.yaml
|
||||||
|
- data-importer-ingress.yaml
|
||||||
|
- external-secrets.yaml
|
||||||
|
- postgres.yaml
|
||||||
|
- storage.yaml
|
||||||
|
- storage-prep.yaml
|
||||||
|
- traefik-ai.yaml
|
||||||
|
- network-policy.yaml
|
||||||
|
|
||||||
|
helmCharts:
|
||||||
|
- name: firefly-iii
|
||||||
|
repo: https://harish2k01.github.io/helm-charts
|
||||||
|
version: 0.1.2
|
||||||
|
releaseName: firefly-iii
|
||||||
|
namespace: firefly-iii
|
||||||
|
valuesFile: values.yaml
|
||||||
|
includeCRDs: true
|
||||||
|
|
||||||
|
patches:
|
||||||
|
# Chart 0.1.2 requires postgres.enabled=true. Remove its StatefulSet and
|
||||||
|
# make the generated database Service select the CloudNativePG instance.
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: StatefulSet
|
||||||
|
name: firefly-iii-postgres
|
||||||
|
patch: |-
|
||||||
|
$patch: delete
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: StatefulSet
|
||||||
|
metadata:
|
||||||
|
name: firefly-iii-postgres
|
||||||
|
- target:
|
||||||
|
version: v1
|
||||||
|
kind: Service
|
||||||
|
name: firefly-iii-postgres
|
||||||
|
patch: |-
|
||||||
|
- op: replace
|
||||||
|
path: /spec/selector
|
||||||
|
value:
|
||||||
|
cnpg.io/cluster: firefly-postgres
|
||||||
|
# The upstream chart does not expose scheduling settings for its CronJob.
|
||||||
|
- target:
|
||||||
|
group: batch
|
||||||
|
version: v1
|
||||||
|
kind: CronJob
|
||||||
|
name: firefly-iii-cron
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /spec/concurrencyPolicy
|
||||||
|
value: Forbid
|
||||||
|
- op: add
|
||||||
|
path: /spec/jobTemplate/spec/backoffLimit
|
||||||
|
value: 1
|
||||||
|
- op: add
|
||||||
|
path: /spec/jobTemplate/spec/activeDeadlineSeconds
|
||||||
|
value: 600
|
||||||
|
- op: add
|
||||||
|
path: /spec/jobTemplate/spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
- op: add
|
||||||
|
path: /spec/jobTemplate/spec/template/spec/tolerations
|
||||||
|
value:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
- op: replace
|
||||||
|
path: /spec/jobTemplate/spec/template/spec/containers/0/command
|
||||||
|
value:
|
||||||
|
- /bin/sh
|
||||||
|
- -ec
|
||||||
|
- >-
|
||||||
|
curl --fail --silent --show-error
|
||||||
|
--retry 30 --retry-delay 10 --retry-all-errors
|
||||||
|
--connect-timeout 5 --max-time 30
|
||||||
|
"http://firefly-iii:80/api/v1/cron/${STATIC_CRON_TOKEN}"
|
||||||
|
# A second independent filter in addition to ingressClassName.
|
||||||
|
- target:
|
||||||
|
group: networking.k8s.io
|
||||||
|
version: v1
|
||||||
|
kind: Ingress
|
||||||
|
name: firefly-iii
|
||||||
|
patch: |-
|
||||||
|
- op: add
|
||||||
|
path: /metadata/labels/firefly.hexor.cy~1private-ai
|
||||||
|
value: "true"
|
||||||
|
# The shared Traefik has the same controller identifier and can discover
|
||||||
|
# IngressClass objects. The explicit annotation partitions this Ingress
|
||||||
|
# so only the instance configured with ingressclass=traefik-ai accepts it.
|
||||||
|
- op: add
|
||||||
|
path: /metadata/annotations/kubernetes.io~1ingress.class
|
||||||
|
value: traefik-ai
|
||||||
|
- op: remove
|
||||||
|
path: /spec/ingressClassName
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: firefly-ingress
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/instance: firefly-iii
|
||||||
|
app.kubernetes.io/name: firefly-iii
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik-ai
|
||||||
|
# The chart's CronJob uses the same selector labels as the application.
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/instance: firefly-iii
|
||||||
|
app.kubernetes.io/name: firefly-iii
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer-sync
|
||||||
|
# hostNetwork traffic is seen as node traffic rather than Pod traffic.
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 192.168.1.117/32
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 100.77.155.120/32
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: firefly-postgres-ingress
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
cnpg.io/cluster: firefly-postgres
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/instance: firefly-iii
|
||||||
|
app.kubernetes.io/name: firefly-iii
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
cnpg.io/cluster: firefly-postgres
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5432
|
||||||
|
# CloudNativePG operator health/status traffic to the instance manager.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: psql
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: cloudnative-pg
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8000
|
||||||
|
---
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: firefly-data-importer-ingress
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: firefly-data-importer
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
ingress:
|
||||||
|
# Shared Traefik serves the public HTTPS importer.
|
||||||
|
- from:
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: kube-system
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
|
# Keep node traffic allowed for probes and local diagnostics.
|
||||||
|
- from:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 192.168.1.117/32
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 100.77.155.120/32
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 8080
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
apiVersion: postgresql.cnpg.io/v1
|
||||||
|
kind: Cluster
|
||||||
|
metadata:
|
||||||
|
name: firefly-postgres
|
||||||
|
spec:
|
||||||
|
description: PostgreSQL for Firefly III
|
||||||
|
instances: 1
|
||||||
|
enableSuperuserAccess: false
|
||||||
|
primaryUpdateStrategy: unsupervised
|
||||||
|
bootstrap:
|
||||||
|
initdb:
|
||||||
|
database: firefly
|
||||||
|
owner: firefly
|
||||||
|
secret:
|
||||||
|
name: firefly-secrets
|
||||||
|
dataChecksums: true
|
||||||
|
storage:
|
||||||
|
size: 10Gi
|
||||||
|
storageClass: firefly-local
|
||||||
|
pvcTemplate:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: firefly-local
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: firefly-postgres
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Gi
|
||||||
|
volumeMode: Filesystem
|
||||||
|
affinity:
|
||||||
|
enablePodAntiAffinity: false
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: "1"
|
||||||
|
memory: 1Gi
|
||||||
|
monitoring:
|
||||||
|
enablePodMonitor: true
|
||||||
|
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: firefly-storage-prep
|
||||||
|
annotations:
|
||||||
|
argocd.argoproj.io/hook: PreSync
|
||||||
|
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
|
||||||
|
spec:
|
||||||
|
backoffLimit: 3
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-storage-prep
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: prepare
|
||||||
|
image: busybox:1.37.0
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -ec
|
||||||
|
- |
|
||||||
|
mkdir -p /host/k8s/firefly-iii/postgres
|
||||||
|
chown 26:26 /host/k8s/firefly-iii/postgres
|
||||||
|
chmod 0700 /host/k8s/firefly-iii/postgres
|
||||||
|
securityContext:
|
||||||
|
runAsUser: 0
|
||||||
|
runAsGroup: 0
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- CHOWN
|
||||||
|
- DAC_OVERRIDE
|
||||||
|
- FOWNER
|
||||||
|
volumeMounts:
|
||||||
|
- name: host-root
|
||||||
|
mountPath: /host/k8s/firefly-iii
|
||||||
|
volumes:
|
||||||
|
- name: host-root
|
||||||
|
hostPath:
|
||||||
|
path: /k8s/firefly-iii
|
||||||
|
type: DirectoryOrCreate
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
apiVersion: storage.k8s.io/v1
|
||||||
|
kind: StorageClass
|
||||||
|
metadata:
|
||||||
|
name: firefly-local
|
||||||
|
provisioner: kubernetes.io/no-provisioner
|
||||||
|
reclaimPolicy: Retain
|
||||||
|
volumeBindingMode: WaitForFirstConsumer
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: firefly-postgres
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-postgres
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 10Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
storageClassName: firefly-local
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /k8s/firefly-iii/postgres
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- ai.tail2fe2d.ts.net
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolume
|
||||||
|
metadata:
|
||||||
|
name: firefly-uploads
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: firefly-uploads
|
||||||
|
spec:
|
||||||
|
capacity:
|
||||||
|
storage: 5Gi
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
persistentVolumeReclaimPolicy: Retain
|
||||||
|
storageClassName: firefly-local
|
||||||
|
volumeMode: Filesystem
|
||||||
|
hostPath:
|
||||||
|
path: /k8s/firefly-iii/uploads
|
||||||
|
type: DirectoryOrCreate
|
||||||
|
nodeAffinity:
|
||||||
|
required:
|
||||||
|
nodeSelectorTerms:
|
||||||
|
- matchExpressions:
|
||||||
|
- key: kubernetes.io/hostname
|
||||||
|
operator: In
|
||||||
|
values:
|
||||||
|
- ai.tail2fe2d.ts.net
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: firefly-uploads
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
storageClassName: firefly-local
|
||||||
|
volumeName: firefly-uploads
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 5Gi
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: IngressClass
|
||||||
|
metadata:
|
||||||
|
name: traefik-ai
|
||||||
|
spec:
|
||||||
|
controller: traefik.io/ingress-controller
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: traefik-ai
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: traefik-ai
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- services
|
||||||
|
- secrets
|
||||||
|
- endpoints
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- discovery.k8s.io
|
||||||
|
resources:
|
||||||
|
- endpointslices
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingresses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingresses/status
|
||||||
|
verbs:
|
||||||
|
- update
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: traefik-ai
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: traefik-ai
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: traefik-ai
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: firefly-traefik-ai
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
- apiGroups:
|
||||||
|
- networking.k8s.io
|
||||||
|
resources:
|
||||||
|
- ingressclasses
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- watch
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: firefly-traefik-ai
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: firefly-traefik-ai
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: traefik-ai
|
||||||
|
namespace: firefly-iii
|
||||||
|
---
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: traefik-ai
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: traefik-ai
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: traefik-ai
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: traefik-ai
|
||||||
|
spec:
|
||||||
|
serviceAccountName: traefik-ai
|
||||||
|
hostNetwork: true
|
||||||
|
dnsPolicy: ClusterFirstWithHostNet
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: traefik
|
||||||
|
image: rancher/mirrored-library-traefik:3.6.13
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
args:
|
||||||
|
- --entrypoints.web.address=192.168.1.117:80
|
||||||
|
- --providers.kubernetesingress=true
|
||||||
|
- --providers.kubernetesingress.namespaces=firefly-iii
|
||||||
|
- --providers.kubernetesingress.ingressclass=traefik-ai
|
||||||
|
- --providers.kubernetesingress.labelselector=firefly.hexor.cy/private-ai=true
|
||||||
|
- --providers.kubernetescrd=false
|
||||||
|
- --api.dashboard=false
|
||||||
|
- --log.level=INFO
|
||||||
|
ports:
|
||||||
|
- name: web
|
||||||
|
containerPort: 80
|
||||||
|
hostPort: 80
|
||||||
|
hostIP: 192.168.1.117
|
||||||
|
protocol: TCP
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
host: 192.168.1.117
|
||||||
|
port: web
|
||||||
|
periodSeconds: 10
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
host: 192.168.1.117
|
||||||
|
port: web
|
||||||
|
periodSeconds: 30
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 50m
|
||||||
|
memory: 64Mi
|
||||||
|
limits:
|
||||||
|
cpu: 500m
|
||||||
|
memory: 256Mi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
add:
|
||||||
|
- NET_BIND_SERVICE
|
||||||
|
readOnlyRootFilesystem: true
|
||||||
|
runAsNonRoot: false
|
||||||
|
runAsUser: 0
|
||||||
|
runAsGroup: 0
|
||||||
|
securityContext:
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
replicaCount: 1
|
||||||
|
|
||||||
|
image:
|
||||||
|
repository: fireflyiii/core
|
||||||
|
tag: version-6.6.1
|
||||||
|
pullPolicy: IfNotPresent
|
||||||
|
|
||||||
|
service:
|
||||||
|
type: ClusterIP
|
||||||
|
port: 80
|
||||||
|
targetPort: 8080
|
||||||
|
|
||||||
|
ingress:
|
||||||
|
enabled: true
|
||||||
|
className: traefik-ai
|
||||||
|
hosts:
|
||||||
|
- host: ff.lan
|
||||||
|
paths:
|
||||||
|
- path: /
|
||||||
|
pathType: Prefix
|
||||||
|
|
||||||
|
persistence:
|
||||||
|
upload:
|
||||||
|
enabled: true
|
||||||
|
existingClaim: firefly-uploads
|
||||||
|
|
||||||
|
# Must remain true because chart 0.1.2 validates it. Kustomize removes the
|
||||||
|
# generated StatefulSet and makes this Service select the CloudNativePG Pod.
|
||||||
|
postgres:
|
||||||
|
enabled: true
|
||||||
|
auth:
|
||||||
|
database: firefly
|
||||||
|
username: firefly
|
||||||
|
service:
|
||||||
|
port: 5432
|
||||||
|
|
||||||
|
cronjob:
|
||||||
|
enabled: true
|
||||||
|
schedule: "0 3 * * *"
|
||||||
|
timeZone: Europe/London
|
||||||
|
|
||||||
|
firefly:
|
||||||
|
env:
|
||||||
|
APP_ENV: production
|
||||||
|
APP_DEBUG: "false"
|
||||||
|
APP_URL: http://ff.lan
|
||||||
|
SITE_OWNER: owner@ff.lan
|
||||||
|
TZ: Europe/London
|
||||||
|
DEFAULT_LANGUAGE: en_US
|
||||||
|
TRUSTED_PROXIES: 10.42.0.0/16
|
||||||
|
COOKIE_SECURE: "false"
|
||||||
|
PGSQL_SSL_MODE: prefer
|
||||||
|
secrets:
|
||||||
|
existingSecret: firefly-secrets
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
cpu: "2"
|
||||||
|
memory: 1Gi
|
||||||
@@ -7,3 +7,4 @@ resources:
|
|||||||
- external-secrets.yaml
|
- external-secrets.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
- servicemonitor.yaml
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
apiVersion: monitoring.coreos.com/v1
|
||||||
|
kind: ServiceMonitor
|
||||||
|
metadata:
|
||||||
|
name: furumi-player-metrics
|
||||||
|
labels:
|
||||||
|
app: furumi-player
|
||||||
|
release: prometheus
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: furumi-player
|
||||||
|
endpoints:
|
||||||
|
- port: http
|
||||||
|
path: /metrics
|
||||||
|
interval: 30s
|
||||||
|
scrapeTimeout: 10s
|
||||||
|
honorLabels: true
|
||||||
|
namespaceSelector:
|
||||||
|
matchNames:
|
||||||
|
- furumi
|
||||||
@@ -41,18 +41,18 @@ spec:
|
|||||||
- name: GITEA__service__REGISTER_MANUAL_CONFIRM
|
- name: GITEA__service__REGISTER_MANUAL_CONFIRM
|
||||||
value: "true"
|
value: "true"
|
||||||
- name: GITEA__service__ENABLE_CAPTCHA
|
- name: GITEA__service__ENABLE_CAPTCHA
|
||||||
value: "false"
|
|
||||||
- name: GITEA__service__REQUIRE_CAPTCHA_FOR_LOGIN
|
|
||||||
value: "true"
|
value: "true"
|
||||||
|
- name: GITEA__service__REQUIRE_CAPTCHA_FOR_LOGIN
|
||||||
|
value: "false"
|
||||||
- name: GITEA__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA
|
- name: GITEA__service__REQUIRE_EXTERNAL_REGISTRATION_CAPTCHA
|
||||||
value: "true"
|
value: "true"
|
||||||
- name: GITEA__service__CAPTCHA_TYPE
|
- name: GITEA__service__CAPTCHA_TYPE
|
||||||
value: "hcaptcha"
|
value: "cfturnstile"
|
||||||
- name: GITEA__webhook__ALLOWED_HOST_LIST
|
- name: GITEA__webhook__ALLOWED_HOST_LIST
|
||||||
value: "*"
|
value: "*"
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
name: gitea-recapcha-creds
|
name: gitea-runner-act-runner-secrets
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: http
|
||||||
containerPort: 3000
|
containerPort: 3000
|
||||||
@@ -70,7 +70,7 @@ kind: Deployment
|
|||||||
metadata:
|
metadata:
|
||||||
name: gitea-runner
|
name: gitea-runner
|
||||||
spec:
|
spec:
|
||||||
replicas: 2
|
replicas: 1
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: gitea-runner
|
app: gitea-runner
|
||||||
@@ -79,6 +79,7 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: gitea-runner
|
app: gitea-runner
|
||||||
spec:
|
spec:
|
||||||
|
serviceAccountName: gitea-runner
|
||||||
dnsConfig:
|
dnsConfig:
|
||||||
options:
|
options:
|
||||||
- name: ndots
|
- name: ndots
|
||||||
@@ -115,7 +116,7 @@ spec:
|
|||||||
- key: kubernetes.io/hostname
|
- key: kubernetes.io/hostname
|
||||||
operator: In
|
operator: In
|
||||||
values:
|
values:
|
||||||
- uk-desktop.tail2fe2d.ts.net
|
#- uk-desktop.tail2fe2d.ts.net
|
||||||
- ai.tail2fe2d.ts.net
|
- ai.tail2fe2d.ts.net
|
||||||
- weight: 50
|
- weight: 50
|
||||||
preference:
|
preference:
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ spec:
|
|||||||
data:
|
data:
|
||||||
token: |-
|
token: |-
|
||||||
{{ .password }}
|
{{ .password }}
|
||||||
|
GITEA__service__CF_TURNSTILE_SITEKEY: |-
|
||||||
|
{{ .CF_TURNSTILE_SITEKEY }}
|
||||||
|
GITEA__service__CF_TURNSTILE_SECRET: |-
|
||||||
|
{{ .CF_TURNSTILE_SECRET }}
|
||||||
data:
|
data:
|
||||||
- secretKey: password
|
- secretKey: password
|
||||||
sourceRef:
|
sourceRef:
|
||||||
@@ -22,38 +26,19 @@ spec:
|
|||||||
remoteRef:
|
remoteRef:
|
||||||
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
|
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
|
||||||
property: login.password
|
property: login.password
|
||||||
|
- secretKey: CF_TURNSTILE_SITEKEY
|
||||||
---
|
|
||||||
apiVersion: external-secrets.io/v1
|
|
||||||
kind: ExternalSecret
|
|
||||||
metadata:
|
|
||||||
name: gitea-recapcha-creds
|
|
||||||
spec:
|
|
||||||
refreshInterval: 1m
|
|
||||||
target:
|
|
||||||
name: gitea-recapcha-creds
|
|
||||||
deletionPolicy: Delete
|
|
||||||
template:
|
|
||||||
type: Opaque
|
|
||||||
data:
|
|
||||||
GITEA__service__HCAPTCHA_SITEKEY: |-
|
|
||||||
{{ .HCAPTCHA_SITEKEY }}
|
|
||||||
GITEA__service__HCAPTCHA_SECRET: |-
|
|
||||||
{{ .HCAPTCHA_SECRET }}
|
|
||||||
data:
|
|
||||||
- secretKey: HCAPTCHA_SITEKEY
|
|
||||||
sourceRef:
|
sourceRef:
|
||||||
storeRef:
|
storeRef:
|
||||||
name: vaultwarden-login
|
name: vaultwarden-login
|
||||||
kind: ClusterSecretStore
|
kind: ClusterSecretStore
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: 89c8d8d2-6b53-42c5-805f-38a341ef163e
|
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
|
||||||
property: login.username
|
property: fields[0].value
|
||||||
- secretKey: HCAPTCHA_SECRET
|
- secretKey: CF_TURNSTILE_SECRET
|
||||||
sourceRef:
|
sourceRef:
|
||||||
storeRef:
|
storeRef:
|
||||||
name: vaultwarden-login
|
name: vaultwarden-login
|
||||||
kind: ClusterSecretStore
|
kind: ClusterSecretStore
|
||||||
remoteRef:
|
remoteRef:
|
||||||
key: 89c8d8d2-6b53-42c5-805f-38a341ef163e
|
key: e475b5ab-ea3c-48a5-bb4c-a6bc552fc064
|
||||||
property: login.password
|
property: fields[1].value
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ resources:
|
|||||||
- app.yaml
|
- app.yaml
|
||||||
- external-secrets.yaml
|
- external-secrets.yaml
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
- runner-rbac.yaml
|
||||||
|
- runner-rebalance.yaml
|
||||||
|
- user-unban-cronjob.yaml
|
||||||
- service.yaml
|
- service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner
|
||||||
|
namespace: gitea
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Secret
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-kubernetes-token
|
||||||
|
namespace: gitea
|
||||||
|
annotations:
|
||||||
|
kubernetes.io/service-account.name: gitea-runner
|
||||||
|
type: kubernetes.io/service-account-token
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: auth-proxy-routes-manager
|
||||||
|
namespace: auth-proxy
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
resourceNames:
|
||||||
|
- auth-proxy-routes
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- update
|
||||||
|
- patch
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- configmaps
|
||||||
|
verbs:
|
||||||
|
- create
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-auth-proxy-routes
|
||||||
|
namespace: auth-proxy
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: auth-proxy-routes-manager
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: gitea-runner
|
||||||
|
namespace: gitea
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
namespace: gitea
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
namespace: gitea
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- pods
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
- list
|
||||||
|
- delete
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
namespace: gitea
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
namespace: gitea
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
rules:
|
||||||
|
- apiGroups:
|
||||||
|
- ""
|
||||||
|
resources:
|
||||||
|
- nodes
|
||||||
|
resourceNames:
|
||||||
|
- ai.tail2fe2d.ts.net
|
||||||
|
verbs:
|
||||||
|
- get
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
namespace: gitea
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: gitea-runner-rebalancer
|
||||||
|
namespace: gitea
|
||||||
|
spec:
|
||||||
|
schedule: "*/2 * * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 1
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 300
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: gitea-runner-rebalancer
|
||||||
|
spec:
|
||||||
|
serviceAccountName: gitea-runner-rebalancer
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: rebalance
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -ec
|
||||||
|
- |
|
||||||
|
target_node="ai.tail2fe2d.ts.net"
|
||||||
|
ready="$(
|
||||||
|
kubectl get node "${target_node}" \
|
||||||
|
-o jsonpath='{range .status.conditions[?(@.type=="Ready")]}{.status}{end}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ "${ready}" != "True" ]; then
|
||||||
|
echo "${target_node} is not Ready; keeping the runner on its current node"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
runner_pod="$(
|
||||||
|
kubectl -n gitea get pods \
|
||||||
|
-l app=gitea-runner \
|
||||||
|
--field-selector=status.phase=Running \
|
||||||
|
-o jsonpath='{.items[0].metadata.name}'
|
||||||
|
)"
|
||||||
|
runner_node="$(
|
||||||
|
kubectl -n gitea get pods \
|
||||||
|
-l app=gitea-runner \
|
||||||
|
--field-selector=status.phase=Running \
|
||||||
|
-o jsonpath='{.items[0].spec.nodeName}'
|
||||||
|
)"
|
||||||
|
|
||||||
|
if [ -z "${runner_pod}" ] || [ -z "${runner_node}" ]; then
|
||||||
|
echo "No running Gitea runner found"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${runner_node}" = "${target_node}" ]; then
|
||||||
|
echo "${runner_pod} already runs on ${target_node}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Moving ${runner_pod} from ${runner_node} to preferred node ${target_node}"
|
||||||
|
kubectl -n gitea delete pod "${runner_pod}" --wait=false
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: gitea-user-unban
|
||||||
|
labels:
|
||||||
|
app: gitea-user-unban
|
||||||
|
spec:
|
||||||
|
schedule: "*/10 * * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: gitea-user-unban
|
||||||
|
spec:
|
||||||
|
restartPolicy: OnFailure
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: master.tail2fe2d.ts.net
|
||||||
|
volumes:
|
||||||
|
- name: storage
|
||||||
|
hostPath:
|
||||||
|
path: /k8s/gitea
|
||||||
|
type: Directory
|
||||||
|
containers:
|
||||||
|
- name: sqlite-unban
|
||||||
|
image: 'gitea/gitea:latest'
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
memory: "32Mi"
|
||||||
|
cpu: "10m"
|
||||||
|
limits:
|
||||||
|
memory: "128Mi"
|
||||||
|
cpu: "100m"
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -ec
|
||||||
|
- |
|
||||||
|
sqlite3 -cmd ".timeout 30000" /data/gitea/gitea.db "
|
||||||
|
UPDATE \"user\"
|
||||||
|
SET is_active = 1,
|
||||||
|
prohibit_login = 0,
|
||||||
|
updated_unix = unixepoch()
|
||||||
|
WHERE lower(email) = lower('ab@hexor.cy')
|
||||||
|
AND (is_active <> 1 OR prohibit_login <> 0);
|
||||||
|
|
||||||
|
SELECT printf(
|
||||||
|
'gitea user watchdog: id=%d login=%s email=%s is_active=%d prohibit_login=%d updated_unix=%d',
|
||||||
|
id, lower_name, email, is_active, prohibit_login, updated_unix
|
||||||
|
)
|
||||||
|
FROM \"user\"
|
||||||
|
WHERE lower(email) = lower('ab@hexor.cy');
|
||||||
|
"
|
||||||
|
volumeMounts:
|
||||||
|
- name: storage
|
||||||
|
mountPath: /data
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: llamacpp
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: apps
|
||||||
|
destination:
|
||||||
|
namespace: llamacpp
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
source:
|
||||||
|
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: k8s/apps/llamacpp
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
selfHeal: true
|
||||||
|
prune: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: llamacpp-cuda-config
|
||||||
|
data:
|
||||||
|
LLAMA_CACHE: /models
|
||||||
|
LLAMA_ARG_HOST: 0.0.0.0
|
||||||
|
LLAMA_ARG_PORT: "8080"
|
||||||
|
LLAMA_ARG_HF_REPO: "unsloth/gemma-4-12b-it-GGUF:Q6_K"
|
||||||
|
LLAMA_ARG_CTX_SIZE: "128000"
|
||||||
|
LLAMA_ARG_FLASH_ATTN: auto
|
||||||
|
LLAMA_ARG_FIT: "on"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: llamacpp-config
|
||||||
|
data:
|
||||||
|
LLAMA_CACHE: /models
|
||||||
|
LLAMA_ARG_HOST: 0.0.0.0
|
||||||
|
LLAMA_ARG_PORT: "8080"
|
||||||
|
LLAMA_ARG_HF_REPO: "unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-Q6_K"
|
||||||
|
LLAMA_ARG_CTX_SIZE: "32768"
|
||||||
|
LLAMA_ARG_FLASH_ATTN: auto
|
||||||
|
LLAMA_ARG_FIT: "on"
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: llamacpp-cuda
|
||||||
|
annotations:
|
||||||
|
reloader.stakater.com/auto: "true"
|
||||||
|
labels:
|
||||||
|
app: llamacpp-cuda
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: llamacpp-cuda
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: llamacpp-cuda
|
||||||
|
spec:
|
||||||
|
dnsPolicy: Default
|
||||||
|
runtimeClassName: nvidia
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: uk-desktop.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: desktop
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: llamacpp
|
||||||
|
image: ghcr.io/ggml-org/llama.cpp:server-cuda-b9501
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: llamacpp-cuda-config
|
||||||
|
env:
|
||||||
|
- name: HF_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: llamacpp-hf-token
|
||||||
|
key: token
|
||||||
|
optional: true
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
nvidia.com/gpu: 1
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
failureThreshold: 180
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
volumeMounts:
|
||||||
|
- name: models
|
||||||
|
mountPath: /models
|
||||||
|
volumes:
|
||||||
|
- name: models
|
||||||
|
hostPath:
|
||||||
|
path: /data/llama.cpp/models
|
||||||
|
type: DirectoryOrCreate
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: llamacpp
|
||||||
|
annotations:
|
||||||
|
reloader.stakater.com/auto: "true"
|
||||||
|
labels:
|
||||||
|
app: llamacpp
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
strategy:
|
||||||
|
type: Recreate
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: llamacpp
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: llamacpp
|
||||||
|
spec:
|
||||||
|
dnsPolicy: Default
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
containers:
|
||||||
|
- name: llamacpp
|
||||||
|
image: ghcr.io/ggml-org/llama.cpp:server-rocm-b9501
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
envFrom:
|
||||||
|
- configMapRef:
|
||||||
|
name: llamacpp-config
|
||||||
|
env:
|
||||||
|
- name: HF_TOKEN
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: llamacpp-hf-token
|
||||||
|
key: token
|
||||||
|
optional: true
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
containerPort: 8080
|
||||||
|
protocol: TCP
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
amd.com/gpu: 1
|
||||||
|
startupProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
failureThreshold: 180
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
failureThreshold: 3
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
volumeMounts:
|
||||||
|
- name: models
|
||||||
|
mountPath: /models
|
||||||
|
volumes:
|
||||||
|
- name: models
|
||||||
|
hostPath:
|
||||||
|
path: /k8s/llamacpp/models
|
||||||
|
type: DirectoryOrCreate
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- app.yaml
|
||||||
|
- configmap-cuda.yaml
|
||||||
|
- configmap.yaml
|
||||||
|
- deployment-cuda.yaml
|
||||||
|
- deployment.yaml
|
||||||
|
- service-cuda.yaml
|
||||||
|
- service.yaml
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: llamacpp-cuda
|
||||||
|
labels:
|
||||||
|
app: llamacpp-cuda
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: llamacpp-cuda
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: llamacpp
|
||||||
|
labels:
|
||||||
|
app: llamacpp
|
||||||
|
spec:
|
||||||
|
type: ClusterIP
|
||||||
|
selector:
|
||||||
|
app: llamacpp
|
||||||
|
ports:
|
||||||
|
- name: http
|
||||||
|
port: 8080
|
||||||
|
targetPort: http
|
||||||
|
protocol: TCP
|
||||||
@@ -53,8 +53,8 @@ spec:
|
|||||||
upstream_oauth2:
|
upstream_oauth2:
|
||||||
providers:
|
providers:
|
||||||
- id: 001KKV4EKY7KG98W2M9T806K6A
|
- id: 001KKV4EKY7KG98W2M9T806K6A
|
||||||
human_name: Authentik
|
human_name: SSO Login
|
||||||
issuer: https://idm.hexor.cy/application/o/matrix/
|
issuer: https://auth.hexor.cy/auth/realms/hexor
|
||||||
client_id: "{{ .oauth_client_id }}"
|
client_id: "{{ .oauth_client_id }}"
|
||||||
client_secret: "{{ .oauth_client_secret }}"
|
client_secret: "{{ .oauth_client_secret }}"
|
||||||
token_endpoint_auth_method: client_secret_post
|
token_endpoint_auth_method: client_secret_post
|
||||||
@@ -93,3 +93,4 @@ spec:
|
|||||||
metadataPolicy: None
|
metadataPolicy: None
|
||||||
key: ca76867f-49f3-4a30-9ef3-b05af35ee49a
|
key: ca76867f-49f3-4a30-9ef3-b05af35ee49a
|
||||||
property: fields[1].value
|
property: fields[1].value
|
||||||
|
on_conflict: replace
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ spec:
|
|||||||
entryPoints:
|
entryPoints:
|
||||||
- websecure
|
- websecure
|
||||||
routes:
|
routes:
|
||||||
- match: Host(`secret-reader.hexor.cy`)
|
- match: Host(`proxy.hexor.cy`)
|
||||||
kind: Rule
|
kind: Rule
|
||||||
middlewares:
|
middlewares:
|
||||||
- name: auth-proxy
|
- name: auth-proxy
|
||||||
@@ -30,16 +30,16 @@ spec:
|
|||||||
- name: secret-reader
|
- name: secret-reader
|
||||||
port: 80
|
port: 80
|
||||||
tls:
|
tls:
|
||||||
secretName: secret-reader-tls
|
secretName: proxy-tls
|
||||||
---
|
---
|
||||||
apiVersion: cert-manager.io/v1
|
apiVersion: cert-manager.io/v1
|
||||||
kind: Certificate
|
kind: Certificate
|
||||||
metadata:
|
metadata:
|
||||||
name: secret-reader-tls
|
name: proxy-tls
|
||||||
spec:
|
spec:
|
||||||
secretName: secret-reader-tls
|
secretName: proxy-tls
|
||||||
issuerRef:
|
issuerRef:
|
||||||
name: letsencrypt
|
name: letsencrypt
|
||||||
kind: ClusterIssuer
|
kind: ClusterIssuer
|
||||||
dnsNames:
|
dnsNames:
|
||||||
- secret-reader.hexor.cy
|
- proxy.hexor.cy
|
||||||
|
|||||||
@@ -15,14 +15,14 @@ resources:
|
|||||||
- service.yaml
|
- service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
|
|
||||||
helmCharts:
|
# helmCharts:
|
||||||
- name: yacy
|
# - name: yacy
|
||||||
repo: https://gt.hexor.cy/api/packages/ab/helm
|
# repo: https://gt.hexor.cy/api/packages/ab/helm
|
||||||
version: 0.1.2
|
# version: 0.1.2
|
||||||
releaseName: yacy
|
# releaseName: yacy
|
||||||
namespace: n8n
|
# namespace: n8n
|
||||||
valuesFile: values-yacy.yaml
|
# valuesFile: values-yacy.yaml
|
||||||
includeCRDs: true
|
# includeCRDs: true
|
||||||
|
|
||||||
commonLabels:
|
commonLabels:
|
||||||
app.kubernetes.io/name: n8n
|
app.kubernetes.io/name: n8n
|
||||||
|
|||||||
@@ -9,18 +9,18 @@ resources:
|
|||||||
helmCharts:
|
helmCharts:
|
||||||
- name: ollama
|
- name: ollama
|
||||||
repo: https://otwld.github.io/ollama-helm/
|
repo: https://otwld.github.io/ollama-helm/
|
||||||
version: 1.49.0
|
version: 1.58.0
|
||||||
releaseName: ollama
|
releaseName: ollama
|
||||||
namespace: ollama
|
namespace: ollama
|
||||||
valuesFile: ollama-values.yaml
|
valuesFile: ollama-values.yaml
|
||||||
includeCRDs: true
|
includeCRDs: true
|
||||||
- name: open-webui
|
- name: open-webui
|
||||||
repo: https://helm.openwebui.com/
|
repo: https://helm.openwebui.com/
|
||||||
version: 12.10.0
|
version: 14.8.0
|
||||||
releaseName: openweb-ui
|
releaseName: openweb-ui
|
||||||
namespace: ollama
|
namespace: ollama
|
||||||
valuesFile: openweb-ui-values.yaml
|
valuesFile: openweb-ui-values.yaml
|
||||||
includeCRDs: true
|
includeCRDs: true
|
||||||
|
|
||||||
patches:
|
patches:
|
||||||
- path: patch-runtimeclass.yaml
|
- path: patch-runtimeclass.yaml
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ clusterDomain: cluster.local
|
|||||||
|
|
||||||
extraEnvVars:
|
extraEnvVars:
|
||||||
GLOBAL_LOG_LEVEL: debug
|
GLOBAL_LOG_LEVEL: debug
|
||||||
OAUTH_PROVIDER_NAME: authentik
|
OAUTH_PROVIDER_NAME: keycloak
|
||||||
OPENID_PROVIDER_URL: https://idm.hexor.cy/application/o/openwebui/.well-known/openid-configuration
|
OPENID_PROVIDER_URL: https://auth.hexor.cy/auth/realms/hexor/.well-known/openid-configuration
|
||||||
OPENID_REDIRECT_URI: https://ai.hexor.cy/oauth/oidc/callback
|
OPENID_REDIRECT_URI: https://ai.hexor.cy/oauth/oidc/callback
|
||||||
WEBUI_URL: https://ai.hexor.cy
|
WEBUI_URL: https://ai.hexor.cy
|
||||||
# Allows auto-creation of new users using OAuth. Must be paired with ENABLE_LOGIN_FORM=false.
|
# Allows auto-creation of new users using OAuth. Must be paired with ENABLE_LOGIN_FORM=false.
|
||||||
@@ -31,7 +31,7 @@ ollama:
|
|||||||
- qwen3-vl:8b
|
- qwen3-vl:8b
|
||||||
|
|
||||||
pipelines:
|
pipelines:
|
||||||
enabled: true
|
enabled: false
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: master.tail2fe2d.ts.net
|
kubernetes.io/hostname: master.tail2fe2d.ts.net
|
||||||
|
|
||||||
@@ -57,4 +57,4 @@ ingress:
|
|||||||
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
|
traefik.ingress.kubernetes.io/router.middlewares: kube-system-https-redirect@kubernetescrd
|
||||||
host: "ai.hexor.cy"
|
host: "ai.hexor.cy"
|
||||||
tls: true
|
tls: true
|
||||||
existingSecret: ollama-tls
|
existingSecret: ollama-tls
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ spec:
|
|||||||
"openid_connect": {
|
"openid_connect": {
|
||||||
"APPS": [
|
"APPS": [
|
||||||
{
|
{
|
||||||
"provider_id": "authentik",
|
"provider_id": "keycloak",
|
||||||
"name": "Authentik",
|
"name": "Keycloak",
|
||||||
"client_id": "{{ .oauth_id }}",
|
"client_id": "{{ .oauth_id }}",
|
||||||
"secret": "{{ .oauth_secret }}",
|
"secret": "{{ .oauth_secret }}",
|
||||||
"settings": {
|
"settings": {
|
||||||
|
|||||||
@@ -236,29 +236,52 @@ data:
|
|||||||
|
|
||||||
cd /app
|
cd /app
|
||||||
|
|
||||||
# Start main process in background
|
write_xray_api_port() {
|
||||||
./main &
|
API_PORT="$1"
|
||||||
MAIN_PID=$!
|
case "$API_PORT" in
|
||||||
|
""|*[!0-9]*)
|
||||||
# Start continuous port monitoring in background
|
return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CURRENT_PORT=""
|
||||||
|
if [ -f /shared/xray-api-port ]; then
|
||||||
|
CURRENT_PORT=$(cat /shared/xray-api-port)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$API_PORT" != "$CURRENT_PORT" ]; then
|
||||||
|
echo "Found xray API port: $API_PORT"
|
||||||
|
echo -n "$API_PORT" > /shared/xray-api-port
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_PIPE="/tmp/pasarguard-main.log"
|
||||||
|
rm -f "$LOG_PIPE"
|
||||||
|
mkfifo "$LOG_PIPE"
|
||||||
|
|
||||||
|
# Capture main logs so the Xray API listener is not confused with Xray's metrics listener.
|
||||||
{
|
{
|
||||||
sleep 10 # Wait for xray to start initially
|
while IFS= read -r line; do
|
||||||
LAST_PORT=""
|
echo "$line"
|
||||||
|
case "$line" in
|
||||||
while true; do
|
*"transport/internet/tcp: listening TCP on 127.0.0.1:"*)
|
||||||
API_PORT=$(netstat -tlpn | grep xray | grep 127.0.0.1 | awk '{print $4}' | cut -d: -f2 | head -1)
|
API_PORT=$(echo "$line" | sed -n 's/.*listening TCP on 127\.0\.0\.1:\([0-9][0-9]*\).*/\1/p')
|
||||||
if [ -n "$API_PORT" ] && [ "$API_PORT" != "$LAST_PORT" ]; then
|
write_xray_api_port "$API_PORT"
|
||||||
echo "Found xray API port: $API_PORT"
|
;;
|
||||||
echo -n "$API_PORT" > /shared/xray-api-port
|
esac
|
||||||
LAST_PORT="$API_PORT"
|
|
||||||
fi
|
|
||||||
sleep 5 # Check every 5 seconds
|
|
||||||
done
|
done
|
||||||
} &
|
} < "$LOG_PIPE" &
|
||||||
PORT_MONITOR_PID=$!
|
LOG_READER_PID=$!
|
||||||
|
|
||||||
|
# Start main process in background
|
||||||
|
./main > "$LOG_PIPE" 2>&1 &
|
||||||
|
MAIN_PID=$!
|
||||||
|
|
||||||
# Wait for main process to finish
|
# Wait for main process to finish
|
||||||
wait $MAIN_PID
|
wait $MAIN_PID
|
||||||
|
MAIN_STATUS=$?
|
||||||
# Clean up port monitor
|
|
||||||
kill $PORT_MONITOR_PID 2>/dev/null
|
# Clean up log reader
|
||||||
|
wait $LOG_READER_PID 2>/dev/null
|
||||||
|
rm -f "$LOG_PIPE"
|
||||||
|
exit $MAIN_STATUS
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ spec:
|
|||||||
mountPath: /scripts
|
mountPath: /scripts
|
||||||
containers:
|
containers:
|
||||||
- name: pasarguard-node
|
- name: pasarguard-node
|
||||||
image: pasarguard/node:v0.4.0
|
image: pasarguard/node:v0.5.2
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
command:
|
command:
|
||||||
- /bin/sh
|
- /bin/sh
|
||||||
@@ -116,14 +116,20 @@ spec:
|
|||||||
- name: metrics
|
- name: metrics
|
||||||
containerPort: 9550
|
containerPort: 9550
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
livenessProbe:
|
startupProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /scrape
|
path: /scrape
|
||||||
port: metrics
|
port: metrics
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 5
|
||||||
|
failureThreshold: 36
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: metrics
|
||||||
initialDelaySeconds: 60
|
initialDelaySeconds: 60
|
||||||
periodSeconds: 30
|
periodSeconds: 30
|
||||||
timeoutSeconds: 10
|
timeoutSeconds: 10
|
||||||
failureThreshold: 3
|
failureThreshold: 6
|
||||||
readinessProbe:
|
readinessProbe:
|
||||||
httpGet:
|
httpGet:
|
||||||
path: /scrape
|
path: /scrape
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ spec:
|
|||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: pasarguard
|
app: pasarguard
|
||||||
replicas: 1
|
replicas: 2
|
||||||
strategy:
|
strategy:
|
||||||
type: RollingUpdate
|
type: RollingUpdate
|
||||||
template:
|
template:
|
||||||
@@ -34,7 +34,7 @@ spec:
|
|||||||
mountPath: /templates/subscription
|
mountPath: /templates/subscription
|
||||||
containers:
|
containers:
|
||||||
- name: pasarguard-web
|
- name: pasarguard-web
|
||||||
image: pasarguard/panel:v4.0.2
|
image: pasarguard/panel:v5.0.3
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
envFrom:
|
envFrom:
|
||||||
- secretRef:
|
- secretRef:
|
||||||
@@ -50,6 +50,10 @@ spec:
|
|||||||
value: "/app/tls/tls.crt"
|
value: "/app/tls/tls.crt"
|
||||||
- name: UVICORN_SSL_KEYFILE
|
- name: UVICORN_SSL_KEYFILE
|
||||||
value: "/app/tls/tls.key"
|
value: "/app/tls/tls.key"
|
||||||
|
- name: UVICORN_PROXY_HEADERS
|
||||||
|
value: "true"
|
||||||
|
- name: FORWARDED_ALLOW_IPS
|
||||||
|
value: "*"
|
||||||
- name: CUSTOM_TEMPLATES_DIRECTORY
|
- name: CUSTOM_TEMPLATES_DIRECTORY
|
||||||
value: "/code/app/templates/"
|
value: "/code/app/templates/"
|
||||||
- name: SUBSCRIPTION_PAGE_TEMPLATE
|
- name: SUBSCRIPTION_PAGE_TEMPLATE
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ spec:
|
|||||||
- name: pasarguard
|
- name: pasarguard
|
||||||
port: 80
|
port: 80
|
||||||
scheme: https
|
scheme: https
|
||||||
serversTransport: pasarguard-pasarguard-transport@kubernetescrd
|
serversTransport: pasarguard-transport
|
||||||
tls:
|
tls:
|
||||||
secretName: pasarguard-tls
|
secretName: pasarguard-tls
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ spec:
|
|||||||
labels:
|
labels:
|
||||||
app: teamspeak
|
app: teamspeak
|
||||||
spec:
|
spec:
|
||||||
|
nodeSelector:
|
||||||
|
kubernetes.io/hostname: music.tail2fe2d.ts.net
|
||||||
volumes:
|
volumes:
|
||||||
- name: data
|
- name: data
|
||||||
persistentVolumeClaim:
|
persistentVolumeClaim:
|
||||||
|
|||||||
@@ -43,10 +43,15 @@ spec:
|
|||||||
env:
|
env:
|
||||||
- name: RUST_LOG
|
- name: RUST_LOG
|
||||||
value: "info"
|
value: "info"
|
||||||
|
- name: WEB_PETTING_DEBUG
|
||||||
|
value: "false"
|
||||||
|
- name: WEB_PETTING_DATABASE_URL
|
||||||
|
valueFrom:
|
||||||
|
secretKeyRef:
|
||||||
|
name: web-petting-secrets
|
||||||
|
key: WEB_PETTING_DATABASE_URL
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
memory: "64Mi"
|
memory: "256Mi"
|
||||||
cpu: "50m"
|
|
||||||
limits:
|
limits:
|
||||||
memory: "128Mi"
|
memory: "1Gi"
|
||||||
cpu: "150m"
|
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
---
|
||||||
|
apiVersion: external-secrets.io/v1
|
||||||
|
kind: ExternalSecret
|
||||||
|
metadata:
|
||||||
|
name: web-petting-secrets
|
||||||
|
spec:
|
||||||
|
target:
|
||||||
|
name: web-petting-secrets
|
||||||
|
deletionPolicy: Delete
|
||||||
|
template:
|
||||||
|
type: Opaque
|
||||||
|
data:
|
||||||
|
WEB_PETTING_DATABASE_URL: |-
|
||||||
|
postgresql://web_petting:{{ .web_petting }}@psql.psql.svc:5432/web_petting
|
||||||
|
data:
|
||||||
|
- secretKey: web_petting
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
conversionStrategy: Default
|
||||||
|
decodingStrategy: None
|
||||||
|
metadataPolicy: None
|
||||||
|
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||||
|
property: fields[20].value
|
||||||
@@ -4,6 +4,7 @@ kind: Kustomization
|
|||||||
resources:
|
resources:
|
||||||
- app.yaml
|
- app.yaml
|
||||||
- deployment.yaml
|
- deployment.yaml
|
||||||
|
- external-secrets.yaml
|
||||||
- service.yaml
|
- service.yaml
|
||||||
- ingress.yaml
|
- ingress.yaml
|
||||||
- storage.yaml
|
- storage.yaml
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: argocd-dex-server
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
template:
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: dex-server
|
||||||
|
readinessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 5556
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
timeoutSeconds: 3
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
|
livenessProbe:
|
||||||
|
tcpSocket:
|
||||||
|
port: 5556
|
||||||
|
initialDelaySeconds: 10
|
||||||
|
periodSeconds: 30
|
||||||
|
timeoutSeconds: 3
|
||||||
|
successThreshold: 1
|
||||||
|
failureThreshold: 3
|
||||||
@@ -10,9 +10,11 @@ resources:
|
|||||||
helmCharts:
|
helmCharts:
|
||||||
- name: argo-cd
|
- name: argo-cd
|
||||||
repo: https://argoproj.github.io/argo-helm
|
repo: https://argoproj.github.io/argo-helm
|
||||||
version: 9.4.10
|
version: 10.2.1
|
||||||
releaseName: argocd
|
releaseName: argocd
|
||||||
namespace: argocd
|
namespace: argocd
|
||||||
valuesFile: values.yaml
|
valuesFile: values.yaml
|
||||||
includeCRDs: true
|
includeCRDs: true
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- path: dex-probes-patch.yaml
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
nfd:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
labeller:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
dp:
|
||||||
|
image:
|
||||||
|
repository: docker.io/rocm/k8s-device-plugin
|
||||||
|
tag: "1.31.0.9"
|
||||||
|
updateStrategy:
|
||||||
|
type: RollingUpdate
|
||||||
|
rollingUpdate:
|
||||||
|
maxUnavailable: 1
|
||||||
|
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
capabilities:
|
||||||
|
drop:
|
||||||
|
- ALL
|
||||||
|
|
||||||
|
tolerations:
|
||||||
|
- key: workload
|
||||||
|
operator: Equal
|
||||||
|
value: ai
|
||||||
|
effect: NoSchedule
|
||||||
|
|
||||||
|
node_selector_enabled: true
|
||||||
|
node_selector:
|
||||||
|
kubernetes.io/arch: amd64
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
@@ -13,3 +13,24 @@ helmCharts:
|
|||||||
namespace: gpu-system
|
namespace: gpu-system
|
||||||
valuesFile: values.yaml
|
valuesFile: values.yaml
|
||||||
includeCRDs: true
|
includeCRDs: true
|
||||||
|
- name: amd-gpu
|
||||||
|
repo: https://rocm.github.io/k8s-device-plugin/
|
||||||
|
version: 0.21.0
|
||||||
|
releaseName: amd-gpu-device-plugin
|
||||||
|
namespace: gpu-system
|
||||||
|
valuesFile: amd-gpu-values.yaml
|
||||||
|
includeCRDs: true
|
||||||
|
|
||||||
|
patches:
|
||||||
|
- target:
|
||||||
|
group: apps
|
||||||
|
version: v1
|
||||||
|
kind: DaemonSet
|
||||||
|
name: amd-gpu-device-plugin-daemonset
|
||||||
|
namespace: gpu-system
|
||||||
|
patch: |-
|
||||||
|
- op: replace
|
||||||
|
path: /spec/template/spec/nodeSelector
|
||||||
|
value:
|
||||||
|
kubernetes.io/arch: amd64
|
||||||
|
kubernetes.io/hostname: ai.tail2fe2d.ts.net
|
||||||
|
|||||||
@@ -66,11 +66,11 @@ ingress:
|
|||||||
|
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 200m
|
cpu: 500m
|
||||||
memory: 512Mi
|
|
||||||
limits:
|
|
||||||
cpu: "1"
|
|
||||||
memory: 1Gi
|
memory: 1Gi
|
||||||
|
limits:
|
||||||
|
cpu: "3"
|
||||||
|
memory: 2Gi
|
||||||
|
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
kubernetes.io/hostname: master.tail2fe2d.ts.net
|
kubernetes.io/hostname: master.tail2fe2d.ts.net
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ resources:
|
|||||||
- nfs-storage.yaml
|
- nfs-storage.yaml
|
||||||
- coredns-internal-resolve.yaml
|
- coredns-internal-resolve.yaml
|
||||||
- https-middleware.yaml
|
- https-middleware.yaml
|
||||||
|
- node-external-ip-labeler.yaml
|
||||||
|
|
||||||
helmCharts:
|
helmCharts:
|
||||||
- name: csi-driver-nfs
|
- name: csi-driver-nfs
|
||||||
@@ -15,4 +16,3 @@ helmCharts:
|
|||||||
namespace: kube-system
|
namespace: kube-system
|
||||||
#valuesFile: values.yaml
|
#valuesFile: values.yaml
|
||||||
includeCRDs: true
|
includeCRDs: true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRole
|
||||||
|
metadata:
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: ["nodes"]
|
||||||
|
verbs: ["get", "list", "patch", "update"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: ClusterRoleBinding
|
||||||
|
metadata:
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: ClusterRole
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
rules:
|
||||||
|
- apiGroups: ["batch"]
|
||||||
|
resources: ["jobs"]
|
||||||
|
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
namespace: kube-system
|
||||||
|
---
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: CronJob
|
||||||
|
metadata:
|
||||||
|
name: node-external-ip-labeler
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
spec:
|
||||||
|
schedule: "17 3 * * *"
|
||||||
|
concurrencyPolicy: Forbid
|
||||||
|
successfulJobsHistoryLimit: 3
|
||||||
|
failedJobsHistoryLimit: 3
|
||||||
|
jobTemplate:
|
||||||
|
spec:
|
||||||
|
backoffLimit: 1
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
spec:
|
||||||
|
serviceAccountName: node-external-ip-labeler
|
||||||
|
restartPolicy: Never
|
||||||
|
tolerations:
|
||||||
|
- operator: Exists
|
||||||
|
containers:
|
||||||
|
- name: fanout
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
clean_name() {
|
||||||
|
echo "$1" \
|
||||||
|
| tr '[:upper:]' '[:lower:]' \
|
||||||
|
| tr -c 'a-z0-9-' '-' \
|
||||||
|
| sed 's/^-*//;s/-*$//' \
|
||||||
|
| cut -c1-45
|
||||||
|
}
|
||||||
|
|
||||||
|
for NODE_NAME in $(kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}'); do
|
||||||
|
NODE_CLEAN="$(clean_name "${NODE_NAME}")"
|
||||||
|
JOB_NAME="node-external-ip-${NODE_CLEAN}"
|
||||||
|
|
||||||
|
kubectl delete job "${JOB_NAME}" -n kube-system --ignore-not-found=true --wait=true --timeout=60s
|
||||||
|
|
||||||
|
cat <<EOF | kubectl apply -f -
|
||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: ${JOB_NAME}
|
||||||
|
namespace: kube-system
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
target-node: "${NODE_CLEAN}"
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 86400
|
||||||
|
backoffLimit: 2
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: node-external-ip-labeler
|
||||||
|
target-node: "${NODE_CLEAN}"
|
||||||
|
spec:
|
||||||
|
serviceAccountName: node-external-ip-labeler
|
||||||
|
nodeName: "${NODE_NAME}"
|
||||||
|
hostNetwork: true
|
||||||
|
dnsPolicy: ClusterFirstWithHostNet
|
||||||
|
restartPolicy: Never
|
||||||
|
tolerations:
|
||||||
|
- operator: Exists
|
||||||
|
containers:
|
||||||
|
- name: label-node
|
||||||
|
image: bitnami/kubectl:latest
|
||||||
|
imagePullPolicy: IfNotPresent
|
||||||
|
env:
|
||||||
|
- name: NODE_NAME
|
||||||
|
value: "${NODE_NAME}"
|
||||||
|
command:
|
||||||
|
- /bin/bash
|
||||||
|
- -lc
|
||||||
|
args:
|
||||||
|
- |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
json_ip() {
|
||||||
|
sed -n 's/.*"ip"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p'
|
||||||
|
}
|
||||||
|
|
||||||
|
IPV4="\$(curl -fsS --connect-timeout 10 --max-time 30 'https://api.ipify.org?format=json' | json_ip)"
|
||||||
|
IP64="\$(curl -fsS --connect-timeout 10 --max-time 30 'https://api64.ipify.org?format=json' | json_ip || true)"
|
||||||
|
|
||||||
|
if [ -z "\${IPV4}" ]; then
|
||||||
|
echo "Unable to detect external IPv4 for node ${NODE_NAME}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
kubectl label node "${NODE_NAME}" external-ipv4="\${IPV4}" --overwrite
|
||||||
|
kubectl annotate node "${NODE_NAME}" homelab.hexor.cy/external-ipv4="\${IPV4}" --overwrite
|
||||||
|
|
||||||
|
if echo "\${IP64}" | grep -q ':'; then
|
||||||
|
kubectl annotate node "${NODE_NAME}" homelab.hexor.cy/external-ipv6="\${IP64}" --overwrite
|
||||||
|
elif [ -n "\${IP64}" ]; then
|
||||||
|
kubectl annotate node "${NODE_NAME}" homelab.hexor.cy/external-ipv4-api64="\${IP64}" --overwrite
|
||||||
|
fi
|
||||||
|
EOF
|
||||||
|
done
|
||||||
@@ -6,7 +6,7 @@ metadata:
|
|||||||
spec:
|
spec:
|
||||||
project: core
|
project: core
|
||||||
destination:
|
destination:
|
||||||
namespace: longhorn
|
namespace: longhorn-system
|
||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
source:
|
source:
|
||||||
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ kind: Kustomization
|
|||||||
helmCharts:
|
helmCharts:
|
||||||
- name: longhorn
|
- name: longhorn
|
||||||
repo: https://charts.longhorn.io
|
repo: https://charts.longhorn.io
|
||||||
version: 1.11.0
|
version: 1.12.0
|
||||||
releaseName: longhorn
|
releaseName: longhorn
|
||||||
namespace: longhorn
|
namespace: longhorn-system
|
||||||
valuesFile: values.yaml
|
valuesFile: values.yaml
|
||||||
includeCRDs: true
|
includeCRDs: true
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,56 @@
|
|||||||
|
global:
|
||||||
|
tolerations:
|
||||||
|
- key: "workload"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoExecute"
|
||||||
|
|
||||||
|
longhornManager:
|
||||||
|
tolerations:
|
||||||
|
- key: "workload"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoExecute"
|
||||||
|
|
||||||
|
longhornDriver:
|
||||||
|
tolerations:
|
||||||
|
- key: "workload"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoExecute"
|
||||||
|
|
||||||
longhornUI:
|
longhornUI:
|
||||||
replicas: 1
|
replicas: 1
|
||||||
|
tolerations:
|
||||||
|
- key: "workload"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoSchedule"
|
||||||
|
- key: "node.kubernetes.io/unreachable"
|
||||||
|
operator: "Exists"
|
||||||
|
effect: "NoExecute"
|
||||||
|
|
||||||
|
defaultSettings:
|
||||||
|
taintToleration: "workload=ai:NoSchedule; workload=desktop:NoSchedule; node.kubernetes.io/unreachable:NoSchedule; node.kubernetes.io/unreachable:NoExecute"
|
||||||
|
# Keep new instance-manager pods schedulable on nodes with high CPU requests.
|
||||||
|
guaranteedInstanceManagerCPU: '{"v1":"6","v2":"6"}'
|
||||||
|
|
||||||
persistence:
|
persistence:
|
||||||
reclaimPolicy: "Retain"
|
reclaimPolicy: "Retain"
|
||||||
|
|||||||
@@ -140,6 +140,10 @@ spec:
|
|||||||
{{ .furumi_dev }}
|
{{ .furumi_dev }}
|
||||||
USER_keycloak: |-
|
USER_keycloak: |-
|
||||||
{{ .keycloak }}
|
{{ .keycloak }}
|
||||||
|
USER_amnezia_fellow: |-
|
||||||
|
{{ .amnezia_fellow }}
|
||||||
|
USER_web_petting: |-
|
||||||
|
{{ .web_petting }}
|
||||||
data:
|
data:
|
||||||
- secretKey: authentik
|
- secretKey: authentik
|
||||||
sourceRef:
|
sourceRef:
|
||||||
@@ -339,3 +343,25 @@ spec:
|
|||||||
metadataPolicy: None
|
metadataPolicy: None
|
||||||
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||||
property: fields[18].value
|
property: fields[18].value
|
||||||
|
- secretKey: amnezia_fellow
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
conversionStrategy: Default
|
||||||
|
decodingStrategy: None
|
||||||
|
metadataPolicy: None
|
||||||
|
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||||
|
property: fields[19].value
|
||||||
|
- secretKey: web_petting
|
||||||
|
sourceRef:
|
||||||
|
storeRef:
|
||||||
|
name: vaultwarden-login
|
||||||
|
kind: ClusterSecretStore
|
||||||
|
remoteRef:
|
||||||
|
conversionStrategy: Default
|
||||||
|
decodingStrategy: None
|
||||||
|
metadataPolicy: None
|
||||||
|
key: 2a9deb39-ef22-433e-a1be-df1555625e22
|
||||||
|
property: fields[20].value
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ resources:
|
|||||||
helmCharts:
|
helmCharts:
|
||||||
- name: pgadmin4
|
- name: pgadmin4
|
||||||
repo: https://helm.runix.net
|
repo: https://helm.runix.net
|
||||||
version: 1.50.0
|
version: 1.64.0
|
||||||
releaseName: pgmanager
|
releaseName: pgmanager
|
||||||
namespace: psql
|
namespace: psql
|
||||||
valuesFile: pgadmin4-values.yaml
|
valuesFile: pgadmin4-values.yaml
|
||||||
|
|||||||
@@ -0,0 +1,504 @@
|
|||||||
|
apiVersion: v1
|
||||||
|
kind: ConfigMap
|
||||||
|
metadata:
|
||||||
|
name: furumi-dashboard
|
||||||
|
labels:
|
||||||
|
grafana_dashboard: "1"
|
||||||
|
data:
|
||||||
|
furumi.json: |-
|
||||||
|
{
|
||||||
|
"annotations": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"builtIn": 1,
|
||||||
|
"datasource": { "type": "grafana", "uid": "-- Grafana --" },
|
||||||
|
"enable": true,
|
||||||
|
"hide": true,
|
||||||
|
"iconColor": "rgba(0, 211, 255, 1)",
|
||||||
|
"name": "Annotations & Alerts",
|
||||||
|
"type": "dashboard"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"editable": true,
|
||||||
|
"fiscalYearStartMonth": 0,
|
||||||
|
"graphTooltip": 1,
|
||||||
|
"id": null,
|
||||||
|
"links": [],
|
||||||
|
"liveNow": false,
|
||||||
|
"panels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Build Version",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 4, "x": 0, "y": 0 },
|
||||||
|
"targets": [
|
||||||
|
{
|
||||||
|
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
||||||
|
"expr": "max by (version) (furumusic_build_info{namespace=~\"$namespace\"})",
|
||||||
|
"legendFormat": "{{version}}",
|
||||||
|
"refId": "A"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"options": { "colorMode": "none", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "textMode": "name" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 2,
|
||||||
|
"title": "HTTP RPS",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 4, "x": 4, "y": 0 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "reqps", "decimals": 2 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_http_requests_total{namespace=~\"$namespace\"}[$__rate_interval]))", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "textMode": "auto" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 3,
|
||||||
|
"title": "5xx Error Ratio",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 4, "x": 8, "y": 0 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "percentunit", "decimals": 2, "thresholds": { "mode": "absolute", "steps": [ { "color": "green", "value": null }, { "color": "yellow", "value": 0.01 }, { "color": "red", "value": 0.05 } ] } }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_http_requests_total{namespace=~\"$namespace\",status=~\"5..\"}[$__rate_interval])) / clamp_min(sum(rate(furumusic_http_requests_total{namespace=~\"$namespace\"}[$__rate_interval])), 0.001)", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "textMode": "auto" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 4,
|
||||||
|
"title": "HTTP p95 Latency",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 4, "x": 12, "y": 0 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "s", "decimals": 3 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le) (rate(furumusic_http_request_duration_seconds_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "textMode": "auto" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 5,
|
||||||
|
"title": "Active Users 15m",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 4, "x": 16, "y": 0 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short", "decimals": 0 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(furumusic_active_users{namespace=~\"$namespace\",window=\"15m\"})", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "textMode": "auto" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 6,
|
||||||
|
"title": "Stream Throughput",
|
||||||
|
"type": "stat",
|
||||||
|
"gridPos": { "h": 4, "w": 4, "x": 20, "y": 0 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "Bps", "decimals": 1 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_stream_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "textMode": "auto" }
|
||||||
|
},
|
||||||
|
{ "id": 10, "title": "HTTP", "type": "row", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 4 }, "collapsed": false, "panels": [] },
|
||||||
|
{
|
||||||
|
"id": 11,
|
||||||
|
"title": "Request Rate by Route and Status",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 0, "y": 5 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (route, status) (rate(furumusic_http_requests_total{namespace=~\"$namespace\",route=~\"$route\"}[$__rate_interval]))", "legendFormat": "{{route}} {{status}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 12,
|
||||||
|
"title": "Request Duration Quantiles",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 8, "w": 12, "x": 12, "y": 5 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "s", "decimals": 3 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.50, sum by (le) (rate(furumusic_http_request_duration_seconds_bucket{namespace=~\"$namespace\",route=~\"$route\"}[$__rate_interval])))", "legendFormat": "p50", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le) (rate(furumusic_http_request_duration_seconds_bucket{namespace=~\"$namespace\",route=~\"$route\"}[$__rate_interval])))", "legendFormat": "p95", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.99, sum by (le) (rate(furumusic_http_request_duration_seconds_bucket{namespace=~\"$namespace\",route=~\"$route\"}[$__rate_interval])))", "legendFormat": "p99", "refId": "C" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 13,
|
||||||
|
"title": "In-flight Requests",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 13 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (route) (furumusic_http_in_flight_requests{namespace=~\"$namespace\",route=~\"$route\"})", "legendFormat": "{{route}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 14,
|
||||||
|
"title": "HTTP Body Throughput",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 13 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "Bps" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_http_request_body_bytes_total{namespace=~\"$namespace\",route=~\"$route\"}[$__rate_interval]))", "legendFormat": "request", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_http_response_body_bytes_total{namespace=~\"$namespace\",route=~\"$route\"}[$__rate_interval]))", "legendFormat": "response", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 15,
|
||||||
|
"title": "Status Code Mix",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 13 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (status) (rate(furumusic_http_requests_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "{{status}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{ "id": 20, "title": "Auth and Users", "type": "row", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 20 }, "collapsed": false, "panels": [] },
|
||||||
|
{
|
||||||
|
"id": 21,
|
||||||
|
"title": "Users by Role",
|
||||||
|
"type": "bargauge",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 21 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short", "decimals": 0 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (role) (furumusic_users_total{namespace=~\"$namespace\"})", "legendFormat": "{{role}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "displayMode": "gradient", "orientation": "horizontal", "reduceOptions": { "calc": "lastNotNull", "fields": "", "values": false }, "showUnfilled": true }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 22,
|
||||||
|
"title": "Active Users",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 21 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short", "decimals": 0 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (window) (furumusic_active_users{namespace=~\"$namespace\"})", "legendFormat": "{{window}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 23,
|
||||||
|
"title": "Auth Events",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 21 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (method, outcome, reason) (rate(furumusic_auth_login_attempts_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "login {{method}} {{outcome}} {{reason}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (method) (rate(furumusic_auth_sessions_created_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "session {{method}}", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (kind) (rate(furumusic_auth_denied_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "denied {{kind}}", "refId": "C" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{ "id": 30, "title": "Playback and Streaming", "type": "row", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 28 }, "collapsed": false, "panels": [] },
|
||||||
|
{
|
||||||
|
"id": 31,
|
||||||
|
"title": "Listens Rate",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 29 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (completed) (rate(furumusic_listens_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "completed={{completed}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 32,
|
||||||
|
"title": "Listen Completion Ratio",
|
||||||
|
"description": "Share of listens completed during the rolling 1h window. The wider window avoids 0/100% jumps when traffic is sparse.",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 29 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "percentunit", "decimals": 2, "min": 0, "max": 1 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(increase(furumusic_listens_total{namespace=~\"$namespace\",completed=\"true\"}[1h])) / clamp_min(sum(increase(furumusic_listens_total{namespace=~\"$namespace\"}[1h])), 1)", "legendFormat": "completed / total", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 33,
|
||||||
|
"title": "Streaming",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 29 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "Bps" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_stream_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "bytes", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (range) (rate(furumusic_stream_requests_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "requests range={{range}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 34,
|
||||||
|
"title": "Listened Hours and Play History Activity",
|
||||||
|
"description": "Per-bucket listening time and newly written play-history rows. This shows activity instead of the ever-growing history counter.",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 12, "x": 0, "y": 36 },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "h", "decimals": 2 },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "unit", "value": "short" }, { "id": "decimals", "value": 0 }, { "id": "custom.axisPlacement", "value": "right" } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(increase(furumusic_listened_seconds_total{namespace=~\"$namespace\"}[$__rate_interval])) / 3600", "legendFormat": "listened hours per interval", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(increase(furumusic_play_history_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "history rows per interval", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{ "id": 40, "title": "Library and Storage", "type": "row", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 43 }, "collapsed": false, "panels": [] },
|
||||||
|
{
|
||||||
|
"id": 41,
|
||||||
|
"title": "Library Inventory",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 44 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short", "decimals": 0, "custom": { "scaleDistribution": { "type": "log", "log": 10 } } }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(furumusic_library_tracks_total{namespace=~\"$namespace\"})", "legendFormat": "tracks", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(furumusic_library_releases_total{namespace=~\"$namespace\"})", "legendFormat": "releases", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(furumusic_library_artists_total{namespace=~\"$namespace\"})", "legendFormat": "artists", "refId": "C" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(furumusic_library_playlists_total{namespace=~\"$namespace\"})", "legendFormat": "playlists", "refId": "D" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 42,
|
||||||
|
"title": "Media Bytes by Type",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 44 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "bytes", "custom": { "scaleDistribution": { "type": "log", "log": 10 } } }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (type) (furumusic_media_file_bytes_total{namespace=~\"$namespace\"})", "legendFormat": "{{type}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 43,
|
||||||
|
"title": "Storage Used Ratio",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 44 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "percentunit", "decimals": 2, "min": 0, "max": 1 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "1 - (sum by (path_kind) (furumusic_storage_free_bytes{namespace=~\"$namespace\"}) / sum by (path_kind) (furumusic_storage_total_bytes{namespace=~\"$namespace\"}))", "legendFormat": "{{path_kind}}", "refId": "A" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{ "id": 50, "title": "AI Agent", "type": "row", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 51 }, "collapsed": false, "panels": [] },
|
||||||
|
{
|
||||||
|
"id": 51,
|
||||||
|
"title": "Agent Queue and Reviews",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 52 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short", "decimals": 0, "custom": { "scaleDistribution": { "type": "log", "log": 10 } } }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (status) (furumusic_agent_queue_depth{namespace=~\"$namespace\"})", "legendFormat": "queue {{status}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (status) (furumusic_agent_reviews_total{namespace=~\"$namespace\"})", "legendFormat": "reviews {{status}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 52,
|
||||||
|
"title": "Agent Processing and Failures",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 52 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (outcome, decision) (rate(furumusic_agent_files_processed_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "processed {{outcome}} {{decision}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (stage) (rate(furumusic_agent_failed_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "failed {{stage}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 53,
|
||||||
|
"title": "Agent Confidence",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 52 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "percentunit", "decimals": 2, "min": 0, "max": 1 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.50, sum by (le) (rate(furumusic_agent_confidence_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "legendFormat": "p50", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le) (rate(furumusic_agent_confidence_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "legendFormat": "p95", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 54,
|
||||||
|
"title": "Discover Runs and Duration",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 12, "x": 0, "y": 59 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (outcome) (rate(furumusic_agent_discover_runs_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "runs {{outcome}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le, outcome) (rate(furumusic_agent_discover_duration_seconds_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "legendFormat": "p95 {{outcome}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 55,
|
||||||
|
"title": "Discover File Flow",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 12, "x": 12, "y": 59 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_agent_discover_files_seen_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "seen", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum(rate(furumusic_agent_discover_files_queued_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "queued", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (reason) (rate(furumusic_agent_discover_files_skipped_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "skipped {{reason}}", "refId": "C" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 56,
|
||||||
|
"title": "RAG and LLM Requests",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 66 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (kind, outcome) (rate(furumusic_agent_rag_queries_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "rag {{kind}} {{outcome}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (model, outcome) (rate(furumusic_agent_llm_requests_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "llm {{model}} {{outcome}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 57,
|
||||||
|
"title": "RAG and LLM Latency p95",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 66 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "s", "decimals": 2 }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le, kind, outcome) (rate(furumusic_agent_rag_duration_seconds_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "legendFormat": "rag {{kind}} {{outcome}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le, model, outcome) (rate(furumusic_agent_llm_duration_seconds_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "legendFormat": "llm {{model}} {{outcome}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 58,
|
||||||
|
"title": "LLM Token Rate, Batch Splits and Parse Failures",
|
||||||
|
"description": "Token series are tokens per second by model and token type. Batch splits and parse failures are operational events per second and use the right axis.",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 66 },
|
||||||
|
"fieldConfig": {
|
||||||
|
"defaults": { "unit": "suffix: tok/s" },
|
||||||
|
"overrides": [
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "B" }, "properties": [ { "id": "unit", "value": "ops" }, { "id": "custom.axisPlacement", "value": "right" } ] },
|
||||||
|
{ "matcher": { "id": "byFrameRefID", "options": "C" }, "properties": [ { "id": "unit", "value": "ops" }, { "id": "custom.axisPlacement", "value": "right" } ] }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (model, type) (rate(furumusic_agent_llm_tokens_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "tokens {{model}} {{type}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (reason) (rate(furumusic_agent_llm_batch_splits_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "splits {{reason}}", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (model) (rate(furumusic_agent_llm_parse_failures_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "parse failures {{model}}", "refId": "C" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 59,
|
||||||
|
"title": "Cover Pipeline",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 12, "x": 0, "y": 73 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (source, outcome) (rate(furumusic_agent_cover_lookup_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "lookup {{source}} {{outcome}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (variant, outcome) (rate(furumusic_agent_cover_variant_generation_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "variant {{variant}} {{outcome}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{ "id": 70, "title": "Scheduler and Torrents", "type": "row", "gridPos": { "h": 1, "w": 24, "x": 0, "y": 80 }, "collapsed": false, "panels": [] },
|
||||||
|
{
|
||||||
|
"id": 71,
|
||||||
|
"title": "Scheduler Jobs",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 0, "y": 81 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (job) (furumusic_scheduler_job_running{namespace=~\"$namespace\"})", "legendFormat": "running {{job}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (job) (furumusic_scheduler_job_enabled{namespace=~\"$namespace\"})", "legendFormat": "enabled {{job}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 72,
|
||||||
|
"title": "Scheduler Runs and Duration p95",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 8, "y": 81 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "ops" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (job, trigger, outcome) (rate(furumusic_scheduler_job_runs_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "{{job}} {{trigger}} {{outcome}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "histogram_quantile(0.95, sum by (le, job, trigger, outcome) (rate(furumusic_scheduler_job_duration_seconds_bucket{namespace=~\"$namespace\"}[$__rate_interval])))", "legendFormat": "p95 {{job}} {{outcome}}", "refId": "B" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 73,
|
||||||
|
"title": "Torrents",
|
||||||
|
"type": "timeseries",
|
||||||
|
"gridPos": { "h": 7, "w": 8, "x": 16, "y": 81 },
|
||||||
|
"fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] },
|
||||||
|
"targets": [
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (status) (furumusic_torrent_sessions_total{namespace=~\"$namespace\"})", "legendFormat": "sessions {{status}}", "refId": "A" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (outcome) (rate(furumusic_torrent_downloads_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "downloads {{outcome}}", "refId": "B" },
|
||||||
|
{ "datasource": { "type": "prometheus", "uid": "${datasource}" }, "expr": "sum by (outcome) (rate(furumusic_torrent_selected_bytes_total{namespace=~\"$namespace\"}[$__rate_interval]))", "legendFormat": "bytes {{outcome}}", "refId": "C" }
|
||||||
|
],
|
||||||
|
"options": { "legend": { "displayMode": "list", "placement": "bottom", "showLegend": true }, "tooltip": { "mode": "multi", "sort": "desc" } }
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"refresh": "30s",
|
||||||
|
"schemaVersion": 38,
|
||||||
|
"style": "dark",
|
||||||
|
"tags": [ "furumi", "furumusic", "music" ],
|
||||||
|
"templating": {
|
||||||
|
"list": [
|
||||||
|
{
|
||||||
|
"current": { "selected": false, "text": "Prometheus", "value": "Prometheus" },
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": false,
|
||||||
|
"multi": false,
|
||||||
|
"name": "datasource",
|
||||||
|
"options": [],
|
||||||
|
"query": "prometheus",
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"type": "datasource"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allValue": ".*",
|
||||||
|
"current": { "selected": true, "text": "All", "value": "$__all" },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
||||||
|
"definition": "label_values(furumusic_build_info, namespace)",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": true,
|
||||||
|
"multi": true,
|
||||||
|
"name": "namespace",
|
||||||
|
"options": [],
|
||||||
|
"query": { "query": "label_values(furumusic_build_info, namespace)", "refId": "PrometheusVariableQueryEditor-VariableQuery" },
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 1,
|
||||||
|
"type": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"allValue": ".*",
|
||||||
|
"current": { "selected": true, "text": "All", "value": "$__all" },
|
||||||
|
"datasource": { "type": "prometheus", "uid": "${datasource}" },
|
||||||
|
"definition": "label_values(furumusic_http_requests_total{namespace=~\"$namespace\"}, route)",
|
||||||
|
"hide": 0,
|
||||||
|
"includeAll": true,
|
||||||
|
"multi": true,
|
||||||
|
"name": "route",
|
||||||
|
"options": [],
|
||||||
|
"query": { "query": "label_values(furumusic_http_requests_total{namespace=~\"$namespace\"}, route)", "refId": "PrometheusVariableQueryEditor-VariableQuery" },
|
||||||
|
"refresh": 1,
|
||||||
|
"regex": "",
|
||||||
|
"skipUrlSync": false,
|
||||||
|
"sort": 1,
|
||||||
|
"type": "query"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"time": { "from": "now-6h", "to": "now" },
|
||||||
|
"timepicker": {},
|
||||||
|
"timezone": "",
|
||||||
|
"title": "Furumi Music",
|
||||||
|
"uid": "furumi-music",
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
@@ -1,647 +0,0 @@
|
|||||||
{
|
|
||||||
"annotations": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"builtIn": 1,
|
|
||||||
"datasource": {
|
|
||||||
"type": "grafana",
|
|
||||||
"uid": "-- Grafana --"
|
|
||||||
},
|
|
||||||
"enable": true,
|
|
||||||
"hide": true,
|
|
||||||
"iconColor": "rgba(0, 211, 255, 1)",
|
|
||||||
"name": "Annotations & Alerts",
|
|
||||||
"type": "dashboard"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"editable": true,
|
|
||||||
"fiscalYearStartMonth": 0,
|
|
||||||
"graphTooltip": 0,
|
|
||||||
"id": null,
|
|
||||||
"links": [],
|
|
||||||
"liveNow": false,
|
|
||||||
"panels": [
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 4,
|
|
||||||
"w": 6,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 1,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calc": "lastNotNull",
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "furumi_active_streams",
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Active Streams",
|
|
||||||
"type": "stat"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 4,
|
|
||||||
"w": 6,
|
|
||||||
"x": 6,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 2,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calc": "rate",
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "rate(furumi_bytes_read_total[$__rate_interval])",
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Bytes Read / Sec",
|
|
||||||
"type": "stat",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 0,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": false,
|
|
||||||
"lineInterpolation": "linear",
|
|
||||||
"lineWidth": 1,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "auto",
|
|
||||||
"spanNulls": false,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "Bps"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 4,
|
|
||||||
"w": 6,
|
|
||||||
"x": 12,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 3,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calc": "lastNotNull",
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "sum(increase(furumi_file_open_errors_total[$__rate_interval]))",
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "File Open Errors (Rate)",
|
|
||||||
"type": "stat",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "thresholds"
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{ "color": "green", "value": null },
|
|
||||||
{ "color": "red", "value": 1 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 4,
|
|
||||||
"w": 6,
|
|
||||||
"x": 18,
|
|
||||||
"y": 0
|
|
||||||
},
|
|
||||||
"id": 4,
|
|
||||||
"options": {
|
|
||||||
"colorMode": "value",
|
|
||||||
"graphMode": "area",
|
|
||||||
"justifyMode": "auto",
|
|
||||||
"orientation": "auto",
|
|
||||||
"reduceOptions": {
|
|
||||||
"calc": "lastNotNull",
|
|
||||||
"fields": "",
|
|
||||||
"values": false
|
|
||||||
},
|
|
||||||
"textMode": "auto"
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "sum(increase(furumi_auth_failures_total[$__rate_interval]))",
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Auth Failures (Rate)",
|
|
||||||
"type": "stat",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "thresholds"
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{ "color": "green", "value": null },
|
|
||||||
{ "color": "red", "value": 1 }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 8,
|
|
||||||
"w": 12,
|
|
||||||
"x": 0,
|
|
||||||
"y": 4
|
|
||||||
},
|
|
||||||
"id": 5,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "sum by (method, status) (rate(furumi_grpc_requests_total[$__rate_interval]))",
|
|
||||||
"legendFormat": "{{method}} - {{status}}",
|
|
||||||
"refId": "A"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "gRPC Request Rate by Method & Status",
|
|
||||||
"type": "timeseries",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 10,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": false,
|
|
||||||
"lineInterpolation": "linear",
|
|
||||||
"lineWidth": 2,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "auto",
|
|
||||||
"spanNulls": false,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "reqps"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 8,
|
|
||||||
"w": 12,
|
|
||||||
"x": 12,
|
|
||||||
"y": 4
|
|
||||||
},
|
|
||||||
"id": 6,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "histogram_quantile(0.95, sum(rate(furumi_grpc_request_duration_seconds_bucket[$__rate_interval])) by (le, method))",
|
|
||||||
"legendFormat": "p95 {{method}}",
|
|
||||||
"refId": "A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "histogram_quantile(0.99, sum(rate(furumi_grpc_request_duration_seconds_bucket[$__rate_interval])) by (le, method))",
|
|
||||||
"legendFormat": "p99 {{method}}",
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "gRPC Request Duration (p95, p99)",
|
|
||||||
"type": "timeseries",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 0,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": false,
|
|
||||||
"lineInterpolation": "linear",
|
|
||||||
"lineWidth": 2,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "auto",
|
|
||||||
"spanNulls": false,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"color": "green",
|
|
||||||
"value": null
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "s"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 8,
|
|
||||||
"w": 12,
|
|
||||||
"x": 0,
|
|
||||||
"y": 12
|
|
||||||
},
|
|
||||||
"id": 7,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "sum(process_resident_memory_bytes) / 1024 / 1024",
|
|
||||||
"legendFormat": "Resident Memory",
|
|
||||||
"refId": "A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "sum(process_virtual_memory_bytes) / 1024 / 1024",
|
|
||||||
"legendFormat": "Virtual Memory",
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Process Memory Usage",
|
|
||||||
"type": "timeseries",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 15,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": false,
|
|
||||||
"lineInterpolation": "linear",
|
|
||||||
"lineWidth": 2,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "auto",
|
|
||||||
"spanNulls": false,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{ "color": "green", "value": null }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "megbytes"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"gridPos": {
|
|
||||||
"h": 8,
|
|
||||||
"w": 12,
|
|
||||||
"x": 12,
|
|
||||||
"y": 12
|
|
||||||
},
|
|
||||||
"id": 8,
|
|
||||||
"options": {
|
|
||||||
"legend": {
|
|
||||||
"calcs": [],
|
|
||||||
"displayMode": "list",
|
|
||||||
"placement": "bottom",
|
|
||||||
"showLegend": true
|
|
||||||
},
|
|
||||||
"tooltip": {
|
|
||||||
"mode": "single",
|
|
||||||
"sort": "none"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pluginVersion": "10.0.0",
|
|
||||||
"targets": [
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "process_open_fds",
|
|
||||||
"legendFormat": "Open FDs",
|
|
||||||
"refId": "A"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"datasource": {
|
|
||||||
"type": "prometheus",
|
|
||||||
"uid": "${datasource}"
|
|
||||||
},
|
|
||||||
"expr": "process_max_fds",
|
|
||||||
"legendFormat": "Max FDs",
|
|
||||||
"refId": "B"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"title": "Process File Descriptors",
|
|
||||||
"type": "timeseries",
|
|
||||||
"fieldConfig": {
|
|
||||||
"defaults": {
|
|
||||||
"color": {
|
|
||||||
"mode": "palette-classic"
|
|
||||||
},
|
|
||||||
"custom": {
|
|
||||||
"axisBorderShow": false,
|
|
||||||
"axisCenteredZero": false,
|
|
||||||
"axisColorMode": "text",
|
|
||||||
"axisLabel": "",
|
|
||||||
"axisPlacement": "auto",
|
|
||||||
"barAlignment": 0,
|
|
||||||
"drawStyle": "line",
|
|
||||||
"fillOpacity": 10,
|
|
||||||
"gradientMode": "none",
|
|
||||||
"hideFrom": {
|
|
||||||
"legend": false,
|
|
||||||
"tooltip": false,
|
|
||||||
"viz": false
|
|
||||||
},
|
|
||||||
"insertNulls": false,
|
|
||||||
"lineInterpolation": "linear",
|
|
||||||
"lineWidth": 2,
|
|
||||||
"pointSize": 5,
|
|
||||||
"scaleDistribution": {
|
|
||||||
"type": "linear"
|
|
||||||
},
|
|
||||||
"showPoints": "auto",
|
|
||||||
"spanNulls": false,
|
|
||||||
"stacking": {
|
|
||||||
"group": "A",
|
|
||||||
"mode": "none"
|
|
||||||
},
|
|
||||||
"thresholdsStyle": {
|
|
||||||
"mode": "off"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"mappings": [],
|
|
||||||
"thresholds": {
|
|
||||||
"mode": "absolute",
|
|
||||||
"steps": [
|
|
||||||
{ "color": "green", "value": null }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"unit": "short"
|
|
||||||
},
|
|
||||||
"overrides": []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"refresh": "10s",
|
|
||||||
"schemaVersion": 38,
|
|
||||||
"style": "dark",
|
|
||||||
"tags": [
|
|
||||||
"furumi-server",
|
|
||||||
"grpc"
|
|
||||||
],
|
|
||||||
"templating": {
|
|
||||||
"list": [
|
|
||||||
{
|
|
||||||
"current": {
|
|
||||||
"selected": false,
|
|
||||||
"text": "Prometheus",
|
|
||||||
"value": "Prometheus"
|
|
||||||
},
|
|
||||||
"hide": 0,
|
|
||||||
"includeAll": false,
|
|
||||||
"multi": false,
|
|
||||||
"name": "datasource",
|
|
||||||
"options": [],
|
|
||||||
"query": "prometheus",
|
|
||||||
"refresh": 1,
|
|
||||||
"regex": "",
|
|
||||||
"skipUrlSync": false,
|
|
||||||
"type": "datasource"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"time": {
|
|
||||||
"from": "now-1h",
|
|
||||||
"to": "now"
|
|
||||||
},
|
|
||||||
"timepicker": {},
|
|
||||||
"timezone": "",
|
|
||||||
"title": "Furumi Server Metrics",
|
|
||||||
"uid": "furumi-metrics",
|
|
||||||
"version": 1
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: Middleware
|
||||||
|
metadata:
|
||||||
|
name: auth-proxy
|
||||||
|
spec:
|
||||||
|
forwardAuth:
|
||||||
|
address: http://auth-proxy.auth-proxy.svc:80/auth
|
||||||
|
trustForwardHeader: true
|
||||||
|
authResponseHeaders:
|
||||||
|
- X-Auth-Request-User
|
||||||
|
- X-Auth-Request-Email
|
||||||
|
- X-Auth-Request-Groups
|
||||||
|
---
|
||||||
|
apiVersion: traefik.io/v1alpha1
|
||||||
|
kind: IngressRoute
|
||||||
|
metadata:
|
||||||
|
name: prometheus
|
||||||
|
annotations:
|
||||||
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
|
spec:
|
||||||
|
entryPoints:
|
||||||
|
- websecure
|
||||||
|
routes:
|
||||||
|
- match: Host(`prom.hexor.cy`)
|
||||||
|
kind: Rule
|
||||||
|
middlewares:
|
||||||
|
- name: auth-proxy
|
||||||
|
services:
|
||||||
|
- name: prometheus-kube-prometheus-prometheus
|
||||||
|
port: 9090
|
||||||
|
tls:
|
||||||
|
secretName: prometheus-tls
|
||||||
|
---
|
||||||
|
apiVersion: cert-manager.io/v1
|
||||||
|
kind: Certificate
|
||||||
|
metadata:
|
||||||
|
name: prometheus-tls
|
||||||
|
spec:
|
||||||
|
secretName: prometheus-tls
|
||||||
|
issuerRef:
|
||||||
|
name: letsencrypt
|
||||||
|
kind: ClusterIssuer
|
||||||
|
dnsNames:
|
||||||
|
- prom.hexor.cy
|
||||||
@@ -4,10 +4,12 @@ kind: Kustomization
|
|||||||
resources:
|
resources:
|
||||||
- persistentVolume.yaml
|
- persistentVolume.yaml
|
||||||
- external-secrets.yaml
|
- external-secrets.yaml
|
||||||
|
- ingress.yaml
|
||||||
- grafana-alerting-configmap.yaml
|
- grafana-alerting-configmap.yaml
|
||||||
- alertmanager-config.yaml
|
- alertmanager-config.yaml
|
||||||
- dashboards/telemt-dashboard-cm.yaml
|
- dashboards/telemt-dashboard-cm.yaml
|
||||||
- dashboards/auth-proxy-dashboard-cm.yaml
|
- dashboards/auth-proxy-dashboard-cm.yaml
|
||||||
|
- dashboards/furumi-dashboard-cm.yaml
|
||||||
|
|
||||||
helmCharts:
|
helmCharts:
|
||||||
- name: kube-prometheus-stack
|
- name: kube-prometheus-stack
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
alertmanager:
|
alertmanager:
|
||||||
config:
|
config:
|
||||||
global:
|
global:
|
||||||
@@ -25,7 +24,7 @@ alertmanager:
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
@@ -46,7 +45,7 @@ alertmanager:
|
|||||||
|
|
||||||
prometheus:
|
prometheus:
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: false
|
||||||
ingressClassName: traefik
|
ingressClassName: traefik
|
||||||
annotations:
|
annotations:
|
||||||
cert-manager.io/cluster-issuer: letsencrypt
|
cert-manager.io/cluster-issuer: letsencrypt
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
apiVersion: argoproj.io/v1alpha1
|
||||||
|
kind: Application
|
||||||
|
metadata:
|
||||||
|
name: reloader
|
||||||
|
namespace: argocd
|
||||||
|
spec:
|
||||||
|
project: core
|
||||||
|
destination:
|
||||||
|
namespace: reloader
|
||||||
|
server: https://kubernetes.default.svc
|
||||||
|
source:
|
||||||
|
repoURL: ssh://git@gt.hexor.cy:30022/ab/homelab.git
|
||||||
|
targetRevision: HEAD
|
||||||
|
path: k8s/core/reloader
|
||||||
|
syncPolicy:
|
||||||
|
automated:
|
||||||
|
selfHeal: true
|
||||||
|
prune: true
|
||||||
|
syncOptions:
|
||||||
|
- CreateNamespace=true
|
||||||
|
- ServerSideApply=true
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
|
||||||
|
resources:
|
||||||
|
- app.yaml
|
||||||
|
|
||||||
|
helmCharts:
|
||||||
|
- name: reloader
|
||||||
|
repo: https://stakater.github.io/stakater-charts
|
||||||
|
version: 2.2.12
|
||||||
|
releaseName: reloader
|
||||||
|
namespace: reloader
|
||||||
|
valuesFile: values.yaml
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
reloader:
|
||||||
|
watchGlobally: true
|
||||||
|
autoReloadAll: true
|
||||||
|
reloadOnCreate: true
|
||||||
|
reloadOnDelete: false
|
||||||
|
reloadStrategy: annotations
|
||||||
|
ignoreConfigMaps: false
|
||||||
|
ignoreSecrets: false
|
||||||
|
ignoreJobs: false
|
||||||
|
ignoreCronJobs: false
|
||||||
|
enableHA: true
|
||||||
|
syncAfterRestart: true
|
||||||
|
logLevel: info
|
||||||
|
rbac:
|
||||||
|
enabled: true
|
||||||
|
deployment:
|
||||||
|
replicas: 2
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 10m
|
||||||
|
memory: 128Mi
|
||||||
|
limits:
|
||||||
|
cpu: 150m
|
||||||
|
memory: 512Mi
|
||||||
@@ -36,7 +36,7 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
nodeSelector:
|
nodeSelector:
|
||||||
#kubernetes.io/hostname: home.homenet
|
#kubernetes.io/hostname: home.homenet
|
||||||
kubernetes.io/hostname: master.tail2fe2d.ts.net
|
kubernetes.io/hostname: music.tail2fe2d.ts.net
|
||||||
terminationGracePeriodSeconds: 10
|
terminationGracePeriodSeconds: 10
|
||||||
containers:
|
containers:
|
||||||
- name: prom-a2s-exporter
|
- name: prom-a2s-exporter
|
||||||
|
|||||||
Generated
-44
@@ -1,44 +0,0 @@
|
|||||||
# This file is maintained automatically by "terraform init".
|
|
||||||
# Manual edits may be lost in future updates.
|
|
||||||
|
|
||||||
provider "registry.terraform.io/goauthentik/authentik" {
|
|
||||||
version = "2025.12.1"
|
|
||||||
constraints = ">= 2023.10.0, 2025.12.1"
|
|
||||||
hashes = [
|
|
||||||
"h1:p9AGeRqK50wTHEIp7z7O4MUP83cs+lt7wPajZ9m9TB8=",
|
|
||||||
"zh:0e856d3b13614bc32346a236a8e84ba55ecd17238c2008d4b3e71aa8cb49f515",
|
|
||||||
"zh:2dcc44cd499c18ebbc4f763eff97a7b725763c8ac8fbb5d69c935413ccdc4962",
|
|
||||||
"zh:434100fc75ec7cd6b64cc9497e8273e79325fa8d285e9fd9d341c1a67421643b",
|
|
||||||
"zh:483484f66d2e8ce6fa4bfd91e824ceebf07d10acb5df5f366397c55227c4ae91",
|
|
||||||
"zh:596743a6f1c77a6f103b06ef8d932fe8f2376793b92478853dc84571d17c429f",
|
|
||||||
"zh:5ed2d5eb7db13229baaf042c725d5c64b58ffdcc641370175e0a88900af94bf1",
|
|
||||||
"zh:8aecd4cf782c82bee01098f72fe4ffff83707516007b32a01c7fcb19a9260338",
|
|
||||||
"zh:928c05ecac309287ff7d73ed6e478350fe3003557658ae5dc2be817a4268dba7",
|
|
||||||
"zh:9b9fd36dfb3e75da8b4478485272505ae9a3c67b10db173e1d2d76cfe2b637b8",
|
|
||||||
"zh:ab7cd8c61ab67a045854e32f0be1940a92746770dbf3c17bbe923e0259c4f897",
|
|
||||||
"zh:bb1360ec19a4fc1095d0ef1b7b6c5c3c1a91daac7cd1957d43a4cdbb7356a2e3",
|
|
||||||
"zh:d2186f4063aa1a547b52a53745d472e43f5343bc1674f2bbb91421c61b0fab50",
|
|
||||||
"zh:d74bbb67a77951b18ffd7b2863954e70ac03450ad2023cc305c66a5ff25d8d18",
|
|
||||||
"zh:f5970569ea0a479bbfbf2d452f5962e1c9bd472b82756db822d0e951363daa25",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
provider "registry.terraform.io/hashicorp/random" {
|
|
||||||
version = "3.8.1"
|
|
||||||
constraints = ">= 3.5.0"
|
|
||||||
hashes = [
|
|
||||||
"h1:u8AKlWVDTH5r9YLSeswoVEjiY72Rt4/ch7U+61ZDkiQ=",
|
|
||||||
"zh:08dd03b918c7b55713026037c5400c48af5b9f468f483463321bd18e17b907b4",
|
|
||||||
"zh:0eee654a5542dc1d41920bbf2419032d6f0d5625b03bd81339e5b33394a3e0ae",
|
|
||||||
"zh:229665ddf060aa0ed315597908483eee5b818a17d09b6417a0f52fd9405c4f57",
|
|
||||||
"zh:2469d2e48f28076254a2a3fc327f184914566d9e40c5780b8d96ebf7205f8bc0",
|
|
||||||
"zh:37d7eb334d9561f335e748280f5535a384a88675af9a9eac439d4cfd663bcb66",
|
|
||||||
"zh:741101426a2f2c52dee37122f0f4a2f2d6af6d852cb1db634480a86398fa3511",
|
|
||||||
"zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3",
|
|
||||||
"zh:a902473f08ef8df62cfe6116bd6c157070a93f66622384300de235a533e9d4a9",
|
|
||||||
"zh:b85c511a23e57a2147355932b3b6dce2a11e856b941165793a0c3d7578d94d05",
|
|
||||||
"zh:c5172226d18eaac95b1daac80172287b69d4ce32750c82ad77fa0768be4ea4b8",
|
|
||||||
"zh:dab4434dba34aad569b0bc243c2d3f3ff86dd7740def373f2a49816bd2ff819b",
|
|
||||||
"zh:f49fd62aa8c5525a5c17abd51e27ca5e213881d58882fd42fec4a545b53c9699",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# Authentik Terraform Configuration
|
|
||||||
|
|
||||||
Root Terraform configuration for managing Authentik SSO — applications (OAuth2/OIDC, Proxy, SAML), groups, outposts, flows, certificates, and property mappings.
|
|
||||||
|
|
||||||
State is stored in Terraform Cloud (organization `ultradesu`, workspace `Authentik`).
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.
|
|
||||||
├── main.tf # Resources: groups, outposts, policy bindings, module calls
|
|
||||||
├── variables.tf # Input variable definitions
|
|
||||||
├── outputs.tf # Outputs (app details, groups, flows, wiki data)
|
|
||||||
├── providers.tf # Authentik provider (goauthentik/authentik 2025.12.1)
|
|
||||||
├── state.tf # Terraform Cloud backend
|
|
||||||
├── terraform.tfvars # General settings: authentik_url, outposts, flows, tags
|
|
||||||
├── oauth2-apps.auto.tfvars # OAuth2/OIDC application definitions
|
|
||||||
├── proxy-apps.auto.tfvars # Proxy application definitions
|
|
||||||
├── groups.auto.tfvars # Group definitions
|
|
||||||
└── modules/
|
|
||||||
├── oauth-provider/ # OAuth2/OIDC provider + application
|
|
||||||
├── proxy-provider/ # Proxy provider + application
|
|
||||||
└── saml-provider/ # SAML provider + application
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Set the API token
|
|
||||||
export TF_VAR_authentik_token="..."
|
|
||||||
|
|
||||||
terraform init
|
|
||||||
terraform plan
|
|
||||||
terraform apply
|
|
||||||
```
|
|
||||||
|
|
||||||
All `*.auto.tfvars` files are loaded automatically — no `-var-file` flags needed.
|
|
||||||
|
|
||||||
## Adding applications
|
|
||||||
|
|
||||||
OAuth2/OIDC — add to `oauth2-apps.auto.tfvars`:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
oauth_applications = {
|
|
||||||
"my-app" = {
|
|
||||||
name = "My App"
|
|
||||||
slug = "my-app"
|
|
||||||
group = "Tools"
|
|
||||||
redirect_uris = ["https://my-app.example.com/callback"]
|
|
||||||
create_group = true
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Proxy — add to `proxy-apps.auto.tfvars`:
|
|
||||||
|
|
||||||
```hcl
|
|
||||||
proxy_applications = {
|
|
||||||
"my-proxy" = {
|
|
||||||
name = "My Proxy"
|
|
||||||
slug = "my-proxy"
|
|
||||||
group = "Tools"
|
|
||||||
external_host = "https://my-proxy.example.com"
|
|
||||||
internal_host = "http://my-service.namespace.svc:80"
|
|
||||||
outpost = "kubernetes-outpost"
|
|
||||||
create_group = true
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## CI/CD
|
|
||||||
|
|
||||||
Managed via Gitea Actions (`.gitea/workflows/authentik-apps.yaml`). Runs `terraform apply` on push to `main` when files in `terraform/authentik/` change. Also generates a wiki page with the applications list.
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
- Terraform >= 1.0
|
|
||||||
- goauthentik/authentik provider 2025.12.1
|
|
||||||
- Authentik API token with admin permissions
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
groups = {
|
|
||||||
"admins" = {
|
|
||||||
name = "Administrators"
|
|
||||||
is_superuser = true
|
|
||||||
attributes = {
|
|
||||||
notes = "Managed by Terraform"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
data "authentik_flow" "default_authorization_flow" {
|
|
||||||
slug = var.default_authorization_flow
|
|
||||||
}
|
|
||||||
|
|
||||||
data "authentik_flow" "default_authentication_flow" {
|
|
||||||
slug = var.default_authentication_flow
|
|
||||||
}
|
|
||||||
|
|
||||||
data "authentik_flow" "default_invalidation_flow" {
|
|
||||||
slug = var.default_invalidation_flow
|
|
||||||
}
|
|
||||||
|
|
||||||
# Root groups (without parent)
|
|
||||||
resource "authentik_group" "root_groups" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.groups : k => v
|
|
||||||
if v.parent == null
|
|
||||||
}
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
is_superuser = each.value.is_superuser
|
|
||||||
attributes = jsonencode(each.value.attributes)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Child groups (with parent)
|
|
||||||
resource "authentik_group" "child_groups" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.groups : k => v
|
|
||||||
if v.parent != null
|
|
||||||
}
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
is_superuser = each.value.is_superuser
|
|
||||||
parents = authentik_group.root_groups[each.value.parent].id
|
|
||||||
attributes = jsonencode(each.value.attributes)
|
|
||||||
|
|
||||||
depends_on = [authentik_group.root_groups]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Auto-created groups for proxy applications
|
|
||||||
resource "authentik_group" "proxy_app_groups" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.proxy_applications : k => v
|
|
||||||
if v.create_group == true
|
|
||||||
}
|
|
||||||
|
|
||||||
name = "TF-${each.value.name} Users"
|
|
||||||
is_superuser = false
|
|
||||||
attributes = jsonencode({
|
|
||||||
notes = "Auto-created for ${each.value.name} application"
|
|
||||||
app_slug = each.value.slug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
# Auto-created groups for OAuth applications
|
|
||||||
resource "authentik_group" "oauth_app_groups" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.oauth_applications : k => v
|
|
||||||
if v.create_group == true
|
|
||||||
}
|
|
||||||
|
|
||||||
name = "TF-${each.value.name} Users"
|
|
||||||
is_superuser = false
|
|
||||||
attributes = jsonencode({
|
|
||||||
notes = "Auto-created for ${each.value.name} application"
|
|
||||||
app_slug = each.value.slug
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_certificate_key_pair" "certificates" {
|
|
||||||
for_each = var.certificates
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
certificate_data = each.value.certificate_data
|
|
||||||
key_data = each.value.key_data
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
data "authentik_service_connection_kubernetes" "local_k8s" {
|
|
||||||
name = "Local Kubernetes Cluster"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_flow" "flows" {
|
|
||||||
for_each = var.flows
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
title = each.value.title
|
|
||||||
slug = each.value.slug
|
|
||||||
designation = each.value.designation
|
|
||||||
policy_engine_mode = each.value.policy_engine_mode
|
|
||||||
compatibility_mode = each.value.compatibility_mode
|
|
||||||
layout = each.value.layout
|
|
||||||
denied_action = each.value.denied_action
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_property_mapping_provider_scope" "oidc_mappings" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.property_mappings : k => v
|
|
||||||
if v.oidc_scope != null
|
|
||||||
}
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
scope_name = each.value.oidc_scope
|
|
||||||
expression = each.value.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_property_mapping_provider_saml" "saml_mappings" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.property_mappings : k => v
|
|
||||||
if v.saml_name != null
|
|
||||||
}
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
saml_name = each.value.saml_name
|
|
||||||
expression = each.value.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
module "oauth_applications" {
|
|
||||||
source = "./modules/oauth-provider"
|
|
||||||
|
|
||||||
for_each = var.oauth_applications
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
app_name = each.value.name
|
|
||||||
app_slug = each.value.slug
|
|
||||||
app_group = each.value.group
|
|
||||||
client_id = each.value.client_id
|
|
||||||
authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id)
|
|
||||||
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
|
|
||||||
redirect_uris = each.value.redirect_uris
|
|
||||||
client_type = each.value.client_type
|
|
||||||
include_claims_in_id_token = each.value.include_claims_in_id_token
|
|
||||||
access_code_validity = each.value.access_code_validity
|
|
||||||
access_token_validity = each.value.access_token_validity
|
|
||||||
refresh_token_validity = each.value.refresh_token_validity
|
|
||||||
property_mappings = each.value.property_mappings
|
|
||||||
signing_key = each.value.signing_key
|
|
||||||
policy_engine_mode = each.value.policy_engine_mode
|
|
||||||
meta_description = each.value.meta_description
|
|
||||||
meta_launch_url = each.value.meta_launch_url
|
|
||||||
meta_icon = each.value.meta_icon
|
|
||||||
scope_mappings = each.value.scope_mappings
|
|
||||||
|
|
||||||
# Access control - only pass explicitly defined groups
|
|
||||||
access_groups = [
|
|
||||||
for group_key in each.value.access_groups :
|
|
||||||
try(
|
|
||||||
authentik_group.root_groups[group_key].id,
|
|
||||||
authentik_group.child_groups[group_key].id
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
module "proxy_applications" {
|
|
||||||
source = "./modules/proxy-provider"
|
|
||||||
|
|
||||||
for_each = var.proxy_applications
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
app_name = each.value.name
|
|
||||||
app_slug = each.value.slug
|
|
||||||
app_group = each.value.group
|
|
||||||
external_host = each.value.external_host
|
|
||||||
internal_host = each.value.internal_host
|
|
||||||
internal_host_ssl_validation = each.value.internal_host_ssl_validation
|
|
||||||
authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id)
|
|
||||||
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
|
|
||||||
mode = each.value.mode
|
|
||||||
intercept_header_auth = each.value.intercept_header_auth
|
|
||||||
basic_auth_enabled = each.value.basic_auth_enabled
|
|
||||||
basic_auth_user_attribute = each.value.basic_auth_username_attribute
|
|
||||||
basic_auth_password_attribute = each.value.basic_auth_password_attribute
|
|
||||||
cookie_domain = each.value.cookie_domain
|
|
||||||
skip_path_regex = each.value.skip_path_regex
|
|
||||||
policy_engine_mode = each.value.policy_engine_mode
|
|
||||||
meta_description = each.value.meta_description
|
|
||||||
meta_launch_url = each.value.meta_launch_url
|
|
||||||
meta_icon = each.value.meta_icon
|
|
||||||
|
|
||||||
# Access control - only pass explicitly defined groups
|
|
||||||
access_groups = [
|
|
||||||
for group_key in each.value.access_groups :
|
|
||||||
try(
|
|
||||||
authentik_group.root_groups[group_key].id,
|
|
||||||
authentik_group.child_groups[group_key].id
|
|
||||||
)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Binding auto-created groups to their applications
|
|
||||||
resource "authentik_policy_binding" "auto_group_bindings" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.proxy_applications : k => v
|
|
||||||
if v.create_group == true
|
|
||||||
}
|
|
||||||
|
|
||||||
target = module.proxy_applications[each.key].application_uuid
|
|
||||||
group = authentik_group.proxy_app_groups[each.key].id
|
|
||||||
order = 100
|
|
||||||
|
|
||||||
depends_on = [
|
|
||||||
module.proxy_applications,
|
|
||||||
authentik_group.proxy_app_groups
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Binding auto-created groups to their OAuth applications
|
|
||||||
resource "authentik_policy_binding" "oauth_auto_group_bindings" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.oauth_applications : k => v
|
|
||||||
if v.create_group == true
|
|
||||||
}
|
|
||||||
|
|
||||||
target = module.oauth_applications[each.key].application_uuid
|
|
||||||
group = authentik_group.oauth_app_groups[each.key].id
|
|
||||||
order = 100
|
|
||||||
|
|
||||||
depends_on = [
|
|
||||||
module.oauth_applications,
|
|
||||||
authentik_group.oauth_app_groups
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
module "saml_applications" {
|
|
||||||
source = "./modules/saml-provider"
|
|
||||||
|
|
||||||
for_each = var.saml_applications
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
app_name = each.value.name
|
|
||||||
app_slug = each.value.slug
|
|
||||||
app_group = each.value.group
|
|
||||||
authorization_flow = try(authentik_flow.flows[each.value.authorization_flow].id, data.authentik_flow.default_authorization_flow.id)
|
|
||||||
invalidation_flow = data.authentik_flow.default_invalidation_flow.id
|
|
||||||
acs_url = each.value.acs_url
|
|
||||||
issuer = each.value.issuer
|
|
||||||
audience = each.value.audience
|
|
||||||
sp_binding = each.value.sp_binding
|
|
||||||
signing_key = each.value.signing_key
|
|
||||||
property_mappings = [for pm in each.value.property_mappings : authentik_property_mapping_provider_saml.saml_mappings[pm].id]
|
|
||||||
name_id_mapping = each.value.name_id_mapping != null ? authentik_property_mapping_provider_saml.saml_mappings[each.value.name_id_mapping].id : null
|
|
||||||
assertion_valid_not_before = each.value.assertion_valid_not_before
|
|
||||||
assertion_valid_not_on_or_after = each.value.assertion_valid_not_on_or_after
|
|
||||||
session_valid_not_on_or_after = each.value.session_valid_not_on_or_after
|
|
||||||
policy_engine_mode = each.value.policy_engine_mode
|
|
||||||
meta_description = each.value.meta_description
|
|
||||||
meta_launch_url = each.value.meta_launch_url
|
|
||||||
meta_icon = each.value.meta_icon
|
|
||||||
}
|
|
||||||
|
|
||||||
locals {
|
|
||||||
oauth_outpost_assignments = {
|
|
||||||
for app_key, app in var.oauth_applications : app_key => app.outpost
|
|
||||||
if app.outpost != null
|
|
||||||
}
|
|
||||||
|
|
||||||
proxy_outpost_assignments = {
|
|
||||||
for app_key, app in var.proxy_applications : app_key => app.outpost
|
|
||||||
if app.outpost != null
|
|
||||||
}
|
|
||||||
|
|
||||||
outpost_providers = {
|
|
||||||
for outpost_key, outpost in var.outposts : outpost_key => concat(
|
|
||||||
[for app_key, app_outpost in local.oauth_outpost_assignments :
|
|
||||||
module.oauth_applications[app_key].provider_id if app_outpost == outpost_key],
|
|
||||||
[for app_key, app_outpost in local.proxy_outpost_assignments :
|
|
||||||
module.proxy_applications[app_key].provider_id if app_outpost == outpost_key]
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_outpost" "outposts" {
|
|
||||||
for_each = {
|
|
||||||
for k, v in var.outposts : k => v
|
|
||||||
if length(lookup(local.outpost_providers, k, [])) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
name = each.value.name
|
|
||||||
type = "proxy"
|
|
||||||
protocol_providers = local.outpost_providers[each.key]
|
|
||||||
service_connection = data.authentik_service_connection_kubernetes.local_k8s.id
|
|
||||||
config = jsonencode({
|
|
||||||
log_level = "info"
|
|
||||||
docker_labels = null
|
|
||||||
authentik_host = var.authentik_url
|
|
||||||
docker_network = null
|
|
||||||
container_image = null
|
|
||||||
docker_map_ports = true
|
|
||||||
refresh_interval = "minutes=5"
|
|
||||||
kubernetes_replicas = 1
|
|
||||||
kubernetes_namespace = "authentik"
|
|
||||||
authentik_host_browser = ""
|
|
||||||
object_naming_template = "ak-outpost-%(name)s"
|
|
||||||
authentik_host_insecure = false
|
|
||||||
kubernetes_json_patches = {
|
|
||||||
deployment = [
|
|
||||||
{
|
|
||||||
op = "add"
|
|
||||||
path = "/spec/template/spec/containers/0/env/-"
|
|
||||||
value = {
|
|
||||||
name = "AUTHENTIK_POSTGRESQL__HOST"
|
|
||||||
value = "psql.psql.svc"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op = "add"
|
|
||||||
path = "/spec/template/spec/containers/0/env/-"
|
|
||||||
value = {
|
|
||||||
name = "AUTHENTIK_POSTGRESQL__PORT"
|
|
||||||
value = "5432"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op = "add"
|
|
||||||
path = "/spec/template/spec/containers/0/env/-"
|
|
||||||
value = {
|
|
||||||
name = "AUTHENTIK_POSTGRESQL__NAME"
|
|
||||||
value = "authentik"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op = "add"
|
|
||||||
path = "/spec/template/spec/containers/0/env/-"
|
|
||||||
value = {
|
|
||||||
name = "AUTHENTIK_POSTGRESQL__USER"
|
|
||||||
valueFrom = {
|
|
||||||
secretKeyRef = {
|
|
||||||
name = "authentik-creds"
|
|
||||||
key = "AUTHENTIK_POSTGRESQL__USER"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
op = "add"
|
|
||||||
path = "/spec/template/spec/containers/0/env/-"
|
|
||||||
value = {
|
|
||||||
name = "AUTHENTIK_POSTGRESQL__PASSWORD"
|
|
||||||
valueFrom = {
|
|
||||||
secretKeyRef = {
|
|
||||||
name = "authentik-creds"
|
|
||||||
key = "AUTHENTIK_POSTGRESQL__PASSWORD"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
kubernetes_service_type = "ClusterIP"
|
|
||||||
kubernetes_image_pull_secrets = []
|
|
||||||
kubernetes_ingress_class_name = null
|
|
||||||
kubernetes_disabled_components = []
|
|
||||||
kubernetes_ingress_annotations = {}
|
|
||||||
kubernetes_ingress_secret_name = "idm-tls"
|
|
||||||
})
|
|
||||||
|
|
||||||
depends_on = [
|
|
||||||
module.oauth_applications,
|
|
||||||
module.proxy_applications
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
authentik = {
|
|
||||||
source = "goauthentik/authentik"
|
|
||||||
version = ">= 2023.10.0"
|
|
||||||
}
|
|
||||||
random = {
|
|
||||||
source = "hashicorp/random"
|
|
||||||
version = ">= 3.5.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all available scope mappings
|
|
||||||
data "authentik_property_mapping_provider_scope" "all_scopes" {
|
|
||||||
managed_list = [
|
|
||||||
"goauthentik.io/providers/oauth2/scope-email",
|
|
||||||
"goauthentik.io/providers/oauth2/scope-openid",
|
|
||||||
"goauthentik.io/providers/oauth2/scope-profile"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Filter scope mappings based on requested scopes
|
|
||||||
locals {
|
|
||||||
scope_name_mapping = {
|
|
||||||
"openid" = "goauthentik.io/providers/oauth2/scope-openid"
|
|
||||||
"profile" = "goauthentik.io/providers/oauth2/scope-profile"
|
|
||||||
"email" = "goauthentik.io/providers/oauth2/scope-email"
|
|
||||||
}
|
|
||||||
|
|
||||||
selected_scope_ids = [
|
|
||||||
for scope in var.scope_mappings :
|
|
||||||
data.authentik_property_mapping_provider_scope.all_scopes.ids[index(data.authentik_property_mapping_provider_scope.all_scopes.managed_list, local.scope_name_mapping[scope])]
|
|
||||||
if contains(keys(local.scope_name_mapping), scope)
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "random_password" "client_secret" {
|
|
||||||
count = var.client_secret == null ? 1 : 0
|
|
||||||
length = 40
|
|
||||||
special = true
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_provider_oauth2" "provider" {
|
|
||||||
name = var.name
|
|
||||||
client_id = var.client_id != null ? var.client_id : random_id.client_id[0].hex
|
|
||||||
client_secret = var.client_secret != null ? var.client_secret : random_password.client_secret[0].result
|
|
||||||
client_type = var.client_type
|
|
||||||
authorization_flow = var.authorization_flow
|
|
||||||
invalidation_flow = var.invalidation_flow
|
|
||||||
include_claims_in_id_token = var.include_claims_in_id_token
|
|
||||||
access_code_validity = var.access_code_validity
|
|
||||||
access_token_validity = var.access_token_validity
|
|
||||||
refresh_token_validity = var.refresh_token_validity
|
|
||||||
signing_key = var.signing_key
|
|
||||||
|
|
||||||
allowed_redirect_uris = [
|
|
||||||
for uri in var.redirect_uris : {
|
|
||||||
matching_mode = "strict"
|
|
||||||
url = uri
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
property_mappings = length(var.property_mappings) > 0 ? var.property_mappings : local.selected_scope_ids
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "random_id" "client_id" {
|
|
||||||
count = var.client_id == null ? 1 : 0
|
|
||||||
byte_length = 20
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_application" "app" {
|
|
||||||
name = var.app_name
|
|
||||||
slug = var.app_slug
|
|
||||||
protocol_provider = authentik_provider_oauth2.provider.id
|
|
||||||
group = var.app_group
|
|
||||||
policy_engine_mode = var.policy_engine_mode
|
|
||||||
meta_description = var.meta_description
|
|
||||||
meta_launch_url = var.meta_launch_url
|
|
||||||
meta_icon = var.meta_icon
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_policy_binding" "app_access" {
|
|
||||||
for_each = var.access_policies
|
|
||||||
|
|
||||||
target = authentik_application.app.id
|
|
||||||
policy = each.value.policy_id
|
|
||||||
order = each.value.order
|
|
||||||
|
|
||||||
enabled = lookup(each.value, "enabled", true)
|
|
||||||
timeout = lookup(each.value, "timeout", 30)
|
|
||||||
negate = lookup(each.value, "negate", false)
|
|
||||||
failure_result = lookup(each.value, "failure_result", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Binding groups to the application
|
|
||||||
resource "authentik_policy_binding" "group_bindings" {
|
|
||||||
for_each = { for idx, group_id in var.access_groups : idx => group_id }
|
|
||||||
|
|
||||||
target = authentik_application.app.uuid
|
|
||||||
group = each.value
|
|
||||||
order = 10 + each.key
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
output "provider_id" {
|
|
||||||
description = "ID of the OAuth2 provider"
|
|
||||||
value = authentik_provider_oauth2.provider.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_id" {
|
|
||||||
description = "ID of the application"
|
|
||||||
value = authentik_application.app.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_uuid" {
|
|
||||||
description = "UUID of the application"
|
|
||||||
value = authentik_application.app.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
output "client_id" {
|
|
||||||
description = "OAuth2 Client ID"
|
|
||||||
value = authentik_provider_oauth2.provider.client_id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "client_secret" {
|
|
||||||
description = "OAuth2 Client Secret"
|
|
||||||
value = authentik_provider_oauth2.provider.client_secret
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_slug" {
|
|
||||||
description = "Application slug"
|
|
||||||
value = authentik_application.app.slug
|
|
||||||
}
|
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
variable "name" {
|
|
||||||
description = "Name of the OAuth2 provider"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_name" {
|
|
||||||
description = "Name of the application"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_slug" {
|
|
||||||
description = "Slug of the application"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_group" {
|
|
||||||
description = "Group for the application"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "client_id" {
|
|
||||||
description = "OAuth2 Client ID"
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "client_secret" {
|
|
||||||
description = "OAuth2 Client Secret"
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
sensitive = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "client_type" {
|
|
||||||
description = "OAuth2 Client type (confidential or public)"
|
|
||||||
type = string
|
|
||||||
default = "confidential"
|
|
||||||
|
|
||||||
validation {
|
|
||||||
condition = contains(["confidential", "public"], var.client_type)
|
|
||||||
error_message = "Client type must be either 'confidential' or 'public'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "authorization_flow" {
|
|
||||||
description = "Authorization flow UUID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "invalidation_flow" {
|
|
||||||
description = "Invalidation flow UUID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "redirect_uris" {
|
|
||||||
description = "List of allowed redirect URIs"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "access_code_validity" {
|
|
||||||
description = "Access code validity duration"
|
|
||||||
type = string
|
|
||||||
default = "minutes=1"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "access_token_validity" {
|
|
||||||
description = "Access token validity duration"
|
|
||||||
type = string
|
|
||||||
default = "minutes=5"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "refresh_token_validity" {
|
|
||||||
description = "Refresh token validity duration"
|
|
||||||
type = string
|
|
||||||
default = "days=30"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "include_claims_in_id_token" {
|
|
||||||
description = "Include claims in ID token"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "signing_key" {
|
|
||||||
description = "Signing key UUID"
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "property_mappings" {
|
|
||||||
description = "List of property mapping UUIDs"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "policy_engine_mode" {
|
|
||||||
description = "Policy engine mode"
|
|
||||||
type = string
|
|
||||||
default = "all"
|
|
||||||
|
|
||||||
validation {
|
|
||||||
condition = contains(["all", "any"], var.policy_engine_mode)
|
|
||||||
error_message = "Policy engine mode must be either 'all' or 'any'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_description" {
|
|
||||||
description = "Application meta description"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_launch_url" {
|
|
||||||
description = "Application launch URL"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_icon" {
|
|
||||||
description = "Application icon URL"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "access_policies" {
|
|
||||||
description = "Access policies for the application"
|
|
||||||
type = map(object({
|
|
||||||
policy_id = string
|
|
||||||
order = number
|
|
||||||
enabled = optional(bool, true)
|
|
||||||
timeout = optional(number, 30)
|
|
||||||
negate = optional(bool, false)
|
|
||||||
failure_result = optional(bool, true)
|
|
||||||
}))
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "access_groups" {
|
|
||||||
description = "List of group IDs that have access to the application"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "scope_mappings" {
|
|
||||||
description = "List of scope mappings for the OAuth provider"
|
|
||||||
type = list(string)
|
|
||||||
default = ["openid", "profile", "email"]
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
authentik = {
|
|
||||||
source = "goauthentik/authentik"
|
|
||||||
version = ">= 2023.10.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_provider_proxy" "provider" {
|
|
||||||
name = var.name
|
|
||||||
external_host = var.external_host
|
|
||||||
internal_host = var.internal_host
|
|
||||||
internal_host_ssl_validation = var.internal_host_ssl_validation
|
|
||||||
authorization_flow = var.authorization_flow
|
|
||||||
invalidation_flow = var.invalidation_flow
|
|
||||||
mode = var.mode
|
|
||||||
cookie_domain = var.cookie_domain
|
|
||||||
skip_path_regex = var.skip_path_regex
|
|
||||||
intercept_header_auth = var.intercept_header_auth
|
|
||||||
basic_auth_enabled = var.basic_auth_enabled
|
|
||||||
basic_auth_password_attribute = var.basic_auth_password_attribute
|
|
||||||
|
|
||||||
property_mappings = var.property_mappings
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_application" "app" {
|
|
||||||
name = var.app_name
|
|
||||||
slug = var.app_slug
|
|
||||||
protocol_provider = authentik_provider_proxy.provider.id
|
|
||||||
group = var.app_group
|
|
||||||
policy_engine_mode = var.policy_engine_mode
|
|
||||||
meta_description = var.meta_description
|
|
||||||
meta_launch_url = var.meta_launch_url
|
|
||||||
meta_icon = var.meta_icon
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_policy_binding" "app_access" {
|
|
||||||
for_each = var.access_policies
|
|
||||||
|
|
||||||
target = authentik_application.app.id
|
|
||||||
policy = each.value.policy_id
|
|
||||||
order = each.value.order
|
|
||||||
|
|
||||||
enabled = lookup(each.value, "enabled", true)
|
|
||||||
timeout = lookup(each.value, "timeout", 30)
|
|
||||||
negate = lookup(each.value, "negate", false)
|
|
||||||
failure_result = lookup(each.value, "failure_result", true)
|
|
||||||
}
|
|
||||||
|
|
||||||
# Binding groups to the application
|
|
||||||
resource "authentik_policy_binding" "group_bindings" {
|
|
||||||
for_each = { for idx, group_id in var.access_groups : idx => group_id }
|
|
||||||
|
|
||||||
target = authentik_application.app.uuid
|
|
||||||
group = each.value
|
|
||||||
order = 10 + each.key
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
output "provider_id" {
|
|
||||||
description = "ID of the Proxy provider"
|
|
||||||
value = authentik_provider_proxy.provider.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_id" {
|
|
||||||
description = "ID of the application"
|
|
||||||
value = authentik_application.app.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_uuid" {
|
|
||||||
description = "UUID of the application"
|
|
||||||
value = authentik_application.app.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_slug" {
|
|
||||||
description = "Application slug"
|
|
||||||
value = authentik_application.app.slug
|
|
||||||
}
|
|
||||||
|
|
||||||
output "launch_url" {
|
|
||||||
description = "Application launch URL"
|
|
||||||
value = authentik_application.app.meta_launch_url
|
|
||||||
}
|
|
||||||
|
|
||||||
output "external_host" {
|
|
||||||
description = "External host URL"
|
|
||||||
value = authentik_provider_proxy.provider.external_host
|
|
||||||
}
|
|
||||||
|
|
||||||
output "internal_host" {
|
|
||||||
description = "Internal host URL"
|
|
||||||
value = authentik_provider_proxy.provider.internal_host
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
variable "name" {
|
|
||||||
description = "Name of the Proxy provider"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_name" {
|
|
||||||
description = "Name of the application"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_slug" {
|
|
||||||
description = "Slug of the application"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_group" {
|
|
||||||
description = "Group for the application"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "external_host" {
|
|
||||||
description = "External hostname for the proxy"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "internal_host" {
|
|
||||||
description = "Internal hostname for the proxy"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "internal_host_ssl_validation" {
|
|
||||||
description = "Enable SSL validation for internal host"
|
|
||||||
type = bool
|
|
||||||
default = true
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "authorization_flow" {
|
|
||||||
description = "Authorization flow UUID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "invalidation_flow" {
|
|
||||||
description = "Invalidation flow UUID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "mode" {
|
|
||||||
description = "Proxy mode (proxy, forward_single, forward_domain)"
|
|
||||||
type = string
|
|
||||||
default = "proxy"
|
|
||||||
|
|
||||||
validation {
|
|
||||||
condition = contains(["proxy", "forward_single", "forward_domain"], var.mode)
|
|
||||||
error_message = "Mode must be one of: proxy, forward_single, forward_domain."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
variable "cookie_domain" {
|
|
||||||
description = "Cookie domain for the proxy"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
variable "skip_path_regex" {
|
|
||||||
description = "Regular expression for paths to skip authentication"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "intercept_header_auth" {
|
|
||||||
description = "Intercept header authentication"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "basic_auth_enabled" {
|
|
||||||
description = "Enable basic authentication"
|
|
||||||
type = bool
|
|
||||||
default = false
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "basic_auth_password_attribute" {
|
|
||||||
description = "Attribute for basic auth password"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "basic_auth_user_attribute" {
|
|
||||||
description = "Attribute for basic auth username"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "property_mappings" {
|
|
||||||
description = "List of property mapping UUIDs"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "policy_engine_mode" {
|
|
||||||
description = "Policy engine mode"
|
|
||||||
type = string
|
|
||||||
default = "all"
|
|
||||||
|
|
||||||
validation {
|
|
||||||
condition = contains(["all", "any"], var.policy_engine_mode)
|
|
||||||
error_message = "Policy engine mode must be either 'all' or 'any'."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_description" {
|
|
||||||
description = "Application meta description"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_launch_url" {
|
|
||||||
description = "Application launch URL"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_icon" {
|
|
||||||
description = "Application icon URL"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
variable "access_policies" {
|
|
||||||
description = "Access policies for the application"
|
|
||||||
type = map(object({
|
|
||||||
policy_id = string
|
|
||||||
order = number
|
|
||||||
enabled = optional(bool, true)
|
|
||||||
timeout = optional(number, 30)
|
|
||||||
negate = optional(bool, false)
|
|
||||||
failure_result = optional(bool, true)
|
|
||||||
}))
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "access_groups" {
|
|
||||||
description = "List of group IDs that have access to the application"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
terraform {
|
|
||||||
required_providers {
|
|
||||||
authentik = {
|
|
||||||
source = "goauthentik/authentik"
|
|
||||||
version = ">= 2023.10.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data "authentik_certificate_key_pair" "default" {
|
|
||||||
name = "authentik Self-signed Certificate"
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_provider_saml" "provider" {
|
|
||||||
name = var.name
|
|
||||||
authorization_flow = var.authorization_flow
|
|
||||||
invalidation_flow = var.invalidation_flow
|
|
||||||
acs_url = var.acs_url
|
|
||||||
issuer = var.issuer
|
|
||||||
audience = var.audience
|
|
||||||
sp_binding = var.sp_binding
|
|
||||||
signing_kp = var.signing_key != null ? var.signing_key : data.authentik_certificate_key_pair.default.id
|
|
||||||
property_mappings = var.property_mappings
|
|
||||||
name_id_mapping = var.name_id_mapping
|
|
||||||
|
|
||||||
assertion_valid_not_before = var.assertion_valid_not_before
|
|
||||||
assertion_valid_not_on_or_after = var.assertion_valid_not_on_or_after
|
|
||||||
session_valid_not_on_or_after = var.session_valid_not_on_or_after
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_application" "app" {
|
|
||||||
name = var.app_name
|
|
||||||
slug = var.app_slug
|
|
||||||
protocol_provider = authentik_provider_saml.provider.id
|
|
||||||
group = var.app_group
|
|
||||||
policy_engine_mode = var.policy_engine_mode
|
|
||||||
meta_description = var.meta_description
|
|
||||||
meta_launch_url = var.meta_launch_url
|
|
||||||
meta_icon = var.meta_icon
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "authentik_policy_binding" "app_access" {
|
|
||||||
for_each = var.access_policies
|
|
||||||
|
|
||||||
target = authentik_application.app.id
|
|
||||||
policy = each.value.policy_id
|
|
||||||
order = each.value.order
|
|
||||||
|
|
||||||
enabled = lookup(each.value, "enabled", true)
|
|
||||||
timeout = lookup(each.value, "timeout", 30)
|
|
||||||
negate = lookup(each.value, "negate", false)
|
|
||||||
failure_result = lookup(each.value, "failure_result", true)
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
output "provider_id" {
|
|
||||||
description = "ID of the SAML provider"
|
|
||||||
value = authentik_provider_saml.provider.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "application_id" {
|
|
||||||
description = "ID of the application"
|
|
||||||
value = authentik_application.app.id
|
|
||||||
}
|
|
||||||
|
|
||||||
output "provider_name" {
|
|
||||||
description = "Name of the SAML provider"
|
|
||||||
value = authentik_provider_saml.provider.name
|
|
||||||
}
|
|
||||||
|
|
||||||
output "acs_url" {
|
|
||||||
description = "Assertion Consumer Service URL"
|
|
||||||
value = authentik_provider_saml.provider.acs_url
|
|
||||||
}
|
|
||||||
|
|
||||||
output "issuer" {
|
|
||||||
description = "SAML Issuer"
|
|
||||||
value = authentik_provider_saml.provider.issuer
|
|
||||||
}
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
variable "name" {
|
|
||||||
description = "Name of the SAML provider"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_name" {
|
|
||||||
description = "Name of the application"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_slug" {
|
|
||||||
description = "Slug of the application"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "app_group" {
|
|
||||||
description = "Group of the application"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "authorization_flow" {
|
|
||||||
description = "Authorization flow ID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "invalidation_flow" {
|
|
||||||
description = "Invalidation flow ID"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "acs_url" {
|
|
||||||
description = "Assertion Consumer Service URL"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "issuer" {
|
|
||||||
description = "SAML Issuer"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "audience" {
|
|
||||||
description = "SAML Audience"
|
|
||||||
type = string
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "sp_binding" {
|
|
||||||
description = "Service Provider binding (post or redirect)"
|
|
||||||
type = string
|
|
||||||
default = "post"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "signing_key" {
|
|
||||||
description = "Certificate key pair ID for signing"
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "property_mappings" {
|
|
||||||
description = "List of property mapping IDs"
|
|
||||||
type = list(string)
|
|
||||||
default = []
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "name_id_mapping" {
|
|
||||||
description = "Property mapping ID for NameID"
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "assertion_valid_not_before" {
|
|
||||||
description = "Assertion valid not before"
|
|
||||||
type = string
|
|
||||||
default = "minutes=-5"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "assertion_valid_not_on_or_after" {
|
|
||||||
description = "Assertion valid not on or after"
|
|
||||||
type = string
|
|
||||||
default = "minutes=5"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "session_valid_not_on_or_after" {
|
|
||||||
description = "Session valid not on or after"
|
|
||||||
type = string
|
|
||||||
default = "minutes=86400"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "policy_engine_mode" {
|
|
||||||
description = "Policy engine mode"
|
|
||||||
type = string
|
|
||||||
default = "all"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_description" {
|
|
||||||
description = "Application description"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_launch_url" {
|
|
||||||
description = "Application launch URL"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "meta_icon" {
|
|
||||||
description = "Application icon URL"
|
|
||||||
type = string
|
|
||||||
default = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "access_policies" {
|
|
||||||
description = "Access policies for the application"
|
|
||||||
type = map(object({
|
|
||||||
policy_id = string
|
|
||||||
order = number
|
|
||||||
enabled = optional(bool, true)
|
|
||||||
timeout = optional(number, 30)
|
|
||||||
negate = optional(bool, false)
|
|
||||||
failure_result = optional(bool, true)
|
|
||||||
}))
|
|
||||||
default = {}
|
|
||||||
}
|
|
||||||
@@ -1,232 +0,0 @@
|
|||||||
oauth_applications = {
|
|
||||||
"paperless" = {
|
|
||||||
name = "Paperless-NGX"
|
|
||||||
slug = "paperless"
|
|
||||||
group = "Tools"
|
|
||||||
meta_description = "Document management system"
|
|
||||||
meta_icon = "https://img.icons8.com/fluency/48/documents.png"
|
|
||||||
redirect_uris = ["https://docs.hexor.cy/accounts/oidc/authentik/login/callback/"]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
create_group = true
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
|
|
||||||
"gitea" = {
|
|
||||||
name = "Gitea"
|
|
||||||
slug = "gitea"
|
|
||||||
group = "Tools"
|
|
||||||
meta_description = "Git repository hosting"
|
|
||||||
meta_icon = "https://img.icons8.com/?size=100&id=20906&format=png&color=000000"
|
|
||||||
redirect_uris = ["https://gt.hexor.cy/user/oauth2/Authentik/callback"]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=10"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
|
|
||||||
"jellyfin" = {
|
|
||||||
name = "Jellyfin"
|
|
||||||
slug = "jellyfin"
|
|
||||||
group = "Media and Storage"
|
|
||||||
meta_description = "Media streaming server"
|
|
||||||
meta_icon = "https://img.icons8.com/plasticine/100/jellyfin.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"https://jf.hexor.cy/sso/OID/r/authentik",
|
|
||||||
"https://jf.hexor.cy/sso/OID/redirect/authentik"
|
|
||||||
]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=10"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
|
|
||||||
"argocd" = {
|
|
||||||
name = "ArgoCD"
|
|
||||||
slug = "argocd"
|
|
||||||
group = "Core"
|
|
||||||
meta_description = "GitOps deployment tool"
|
|
||||||
meta_icon = "https://img.icons8.com/color-glass/48/octopus.png"
|
|
||||||
redirect_uris = ["https://ag.hexor.cy/auth/callback"]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
|
|
||||||
"grafana" = {
|
|
||||||
name = "Grafana"
|
|
||||||
slug = "grafana"
|
|
||||||
group = "Core"
|
|
||||||
meta_description = "Monitoring and observability"
|
|
||||||
meta_icon = "https://img.icons8.com/fluency/48/grafana.png"
|
|
||||||
redirect_uris = ["https://gf.hexor.cy/login/generic_oauth"]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
}
|
|
||||||
|
|
||||||
"immich" = {
|
|
||||||
name = "Immich"
|
|
||||||
slug = "immich"
|
|
||||||
group = "Media and Storage"
|
|
||||||
meta_description = "Photo and video management"
|
|
||||||
meta_icon = "https://img.icons8.com/fluency/48/photos.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"https://photos.hexor.cy/auth/login",
|
|
||||||
"https://photos.hexor.cy/user-settings",
|
|
||||||
"app.immich:///oauth-callback",
|
|
||||||
"http://photos.homenet:30283/auth/login",
|
|
||||||
"http://photos.homenet:30283/user-settings"
|
|
||||||
]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
access_groups = ["admins"]
|
|
||||||
create_group = true
|
|
||||||
}
|
|
||||||
|
|
||||||
"pgadmin" = {
|
|
||||||
name = "Postgres WEB Admin"
|
|
||||||
slug = "pgadmin"
|
|
||||||
group = "Core"
|
|
||||||
meta_description = "PostgreSQL WEB administration"
|
|
||||||
meta_icon = "https://img.icons8.com/?size=100&id=JRnxU7ZWP4mi&format=png&color=000000"
|
|
||||||
redirect_uris = ["https://pg.hexor.cy/oauth2/authorize"]
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
}
|
|
||||||
|
|
||||||
"home-assistant-lms" = {
|
|
||||||
name = "Home Assistant LMS"
|
|
||||||
slug = "home-assistant-lms"
|
|
||||||
group = "Internal"
|
|
||||||
meta_description = "Home Assistant Limassol"
|
|
||||||
meta_icon = "https://img.icons8.com/stickers/100/smart-home-automation.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"http://ha-lms:8123/auth/oidc/callback",
|
|
||||||
"http://ha-lms.homenet:8123/auth/oidc/callback",
|
|
||||||
]
|
|
||||||
meta_launch_url = "http://ha-lms:8123/auth/oidc/welcome"
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
create_group = true
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
}
|
|
||||||
"home-assistant-london" = {
|
|
||||||
name = "Home Assistant London"
|
|
||||||
slug = "home-assistant-london"
|
|
||||||
group = "Internal"
|
|
||||||
meta_description = "Home Assistant London"
|
|
||||||
meta_icon = "https://img.icons8.com/stickers/100/smart-home-automation.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"http://ha-london:8123/auth/oidc/callback",
|
|
||||||
"http://ha-london.tail2fe2d.ts.net:8123/auth/oidc/callback",
|
|
||||||
]
|
|
||||||
meta_launch_url = "http://ha-london:8123/auth/oidc/welcome"
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
create_group = true
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
}
|
|
||||||
|
|
||||||
"openwebui" = {
|
|
||||||
name = "OpenWeb UI"
|
|
||||||
slug = "openwebui"
|
|
||||||
group = "Tools"
|
|
||||||
meta_description = "OpenWeb UI"
|
|
||||||
meta_icon = "https://ollama.com/public/ollama.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"https://ai.hexor.cy/oauth/oidc/callback",
|
|
||||||
]
|
|
||||||
meta_launch_url = "https://ai.hexor.cy"
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = ["admins"]
|
|
||||||
create_group = true
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
}
|
|
||||||
"matrix" = {
|
|
||||||
name = "Matrix Chat"
|
|
||||||
slug = "matrix"
|
|
||||||
group = "Tools"
|
|
||||||
meta_description = "Matrix Chat"
|
|
||||||
meta_icon = "https://img.icons8.com/ios/100/40C057/matrix-logo.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"https://auth.matrix.hexor.cy/upstream/callback/001KKV4EKY7KG98W2M9T806K6A",
|
|
||||||
]
|
|
||||||
meta_launch_url = "https://chat.matrix.hexor.cy"
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = []
|
|
||||||
create_group = false
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
}
|
|
||||||
"furumi-ng-web" = {
|
|
||||||
name = "Furumi Web Player"
|
|
||||||
slug = "furumi-ng-web"
|
|
||||||
group = "Tools"
|
|
||||||
meta_description = "Furumi Web Player"
|
|
||||||
meta_icon = "https://img.icons8.com/pulsar-color/48/music.png"
|
|
||||||
redirect_uris = [
|
|
||||||
"https://music.hexor.cy/auth/callback",
|
|
||||||
]
|
|
||||||
meta_launch_url = "https://music.hexor.cy"
|
|
||||||
client_type = "confidential"
|
|
||||||
include_claims_in_id_token = true
|
|
||||||
access_code_validity = "minutes=1"
|
|
||||||
access_token_validity = "minutes=5"
|
|
||||||
refresh_token_validity = "days=30"
|
|
||||||
scope_mappings = ["openid", "profile", "email"]
|
|
||||||
access_groups = []
|
|
||||||
create_group = true
|
|
||||||
signing_key = "1b1b5bec-034a-4d96-871a-133f11322360"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user