Compare commits
441 Commits
0b2b031d65
...
renovate/c
| Author | SHA1 | Date | |
|---|---|---|---|
| d0ef8f30b9 | |||
| 98f63b1576 | |||
| edba33b552 | |||
| 054df42d8b | |||
| 08db022d0d | |||
| e485a4fc7f | |||
| 9e74ed6a19 | |||
| 42e89c9bb7 | |||
| 99bc04b76a | |||
| 7ee77e33d4 | |||
| 8bdd5f2196 | |||
| 1d8cb85bd4 | |||
| eeb302b63b | |||
| 69b437ed3b | |||
| 54674a6e79 | |||
| a9da405326 | |||
| 264871bf68 | |||
| 6bcd0ba464 | |||
| cb53301926 | |||
| 110817b748 | |||
| 66cb3c9d82 | |||
| 42ae7af649 | |||
| cffcb1cc2d | |||
| a4a7dd6fe6 | |||
| 52b8ca79dc | |||
| 9a1fe1f740 | |||
| e996a60378 | |||
| 0ccd4d93f1 | |||
| d667c6c0fc | |||
| 4254ebc9ef | |||
| 8cf02fea0e | |||
| aa3c74d6a7 | |||
| 289089428e | |||
| a93f6ec36f | |||
| 1d85bf3a88 | |||
| f495debf25 | |||
| bfede17c87 | |||
| 08ca3f4c4e | |||
| 471c0ba62d | |||
| 261141f509 | |||
| 86d5751842 | |||
| 43e531a3ca | |||
| 9a0764268b | |||
| 7c88498756 | |||
| 8717526358 | |||
| b6a7e5092c | |||
| 27f7a5f29a | |||
| 9d0fd0981a | |||
| 51bc53dbbc | |||
| ce0b13ebb3 | |||
| 516e157d39 | |||
| 73d6d1f15a | |||
| c51fc2a5ef | |||
| 8d994e7aa1 | |||
| 5b551c6c6e | |||
| 7e7b3e3d71 | |||
| 9f315b38e3 | |||
| 3e1a806db1 | |||
| f7dba45165 | |||
| c8fac3201a | |||
| 82864a4738 | |||
| b54c05b956 | |||
| afdada25a0 | |||
| 79315d32db | |||
| a2a5cd72a9 | |||
| c2706a8af2 | |||
| 610ca0017e | |||
| 466932347a | |||
| afbcea4e82 | |||
| 20ad26ed31 | |||
| 7a2d1e0437 | |||
| 6b5929fb95 | |||
| 6b64f1a8b8 | |||
| 4b4cec10be | |||
| 1f319d607a | |||
| 7d90001f18 | |||
| 7948f53d1d | |||
| 829a5a3fd8 | |||
| cf28dcb5eb | |||
| 4f1764d192 | |||
| 49f88e4f96 | |||
| 4aca8daecd | |||
| 005b52dc4f | |||
| d39846422b | |||
| bc4f378df3 | |||
| db91415017 | |||
| 3c071b88df | |||
| c5ef5e2273 | |||
| c55c37f0ac | |||
| 493f939551 | |||
| 168f480c75 | |||
| c056d86da2 | |||
| 58634b82ba | |||
| 5d1ddd6e5d | |||
| 09a3251902 | |||
| 162f5529e2 | |||
| 75531925ef | |||
| 9fa7888799 | |||
| b0c4e17aa8 | |||
| 2d295d24e0 | |||
| e8efa9ddc1 | |||
| c88dd2899a | |||
| e2d2b32208 | |||
| 8d280bc9dc | |||
| f219abb74f | |||
| 0130991c74 | |||
| bbb57cc174 | |||
| 966d2c50c0 | |||
| fb4fcc7c12 | |||
| 1026beb722 | |||
| af737ab82b | |||
| 6dc09ec242 | |||
| 39fc38d62b | |||
| e72a79be8f | |||
| 4fda343b01 | |||
| 266ced7362 | |||
| 8a074839b1 | |||
| 42038207fc | |||
| 28cb53c031 | |||
| 88a73cbb41 | |||
| 46a7e24932 | |||
| cd7ebac6b9 | |||
| ba9db6ce41 | |||
| 6dd9a717e2 | |||
| c67b6f7ebe | |||
| 8d7cf402fd | |||
| 2a59555c3b | |||
| f236b89cca | |||
| 5f3f3d33ee | |||
| b22498c60f | |||
| 13aaae7620 | |||
| 1d7fba80d4 | |||
| 3fdad80b22 | |||
| 865a98ed97 | |||
| 78a81c5b72 | |||
| 2bb23c4ed0 | |||
| 8c29fc8018 | |||
| 2836542569 | |||
| 1e68450d8a | |||
| 0a57fdd22d | |||
| a0a7b85cc2 | |||
| 2c83eb26b3 | |||
| ec038d7154 | |||
| b61e3b5c08 | |||
| 59bf4a1aa6 | |||
| 63a8e2f7ac | |||
| 1ddef7951a | |||
| b431b9c038 | |||
| 6b0c50b104 | |||
| 9f55d67ffa | |||
| 3ffadc8628 | |||
| a138171c2f | |||
| a986aea9ed | |||
| 3939bc9138 | |||
| d8c380ac7c | |||
| 9d086645ad | |||
| 2cd866b33c | |||
| b72d2d93d6 | |||
| 8183285cc9 | |||
| 514568ae40 | |||
| f4294de967 | |||
| ec0b479ef2 | |||
| 0ca2136333 | |||
| 726e61b54a | |||
| d0bd54cde9 | |||
| 41d3629e8a | |||
| 0e756c46a8 | |||
| 17f7ee8515 | |||
| 596d54ae0c | |||
| 2290599f7e | |||
| a3f30873f9 | |||
| 96e5202e6d | |||
| 8b51286a28 | |||
| d210a340a7 | |||
| 93cd4605ad | |||
| 664268dbfe | |||
| 99d6c36e16 | |||
| 70ad1e0ab3 | |||
| 9d3dc4a5a2 | |||
| 28d485b7b2 | |||
| d7e3a77f73 | |||
| 96cb5e53b1 | |||
| 0951b5173b | |||
| acfebdef11 | |||
| d7dd1f73fc | |||
| 4c561cbcad | |||
| 976422c174 | |||
| fe1d3ca12a | |||
| 3144ccdb38 | |||
| ce8eb9ae13 | |||
| 673739e2c4 | |||
| 6bfc99d066 | |||
| a5d9082006 | |||
| b20194bc13 | |||
| ecf1327f53 | |||
| 038ffbf499 | |||
| 985a0dc3b1 | |||
| e344ba26e8 | |||
| 6ea969b44a | |||
| f2ef3fdb6a | |||
| 08a09ecb9d | |||
| 00d8236ad8 | |||
| a06700fd53 | |||
| 4e60185ade | |||
| e5cadafd19 | |||
| fe5ba29264 | |||
| b978c01af4 | |||
| 547c7d9b11 | |||
| 28fbd523aa | |||
| 3d58fb6724 | |||
| 5fdc621bc9 | |||
| ee23d02ec4 | |||
| e92150a5de | |||
| cc9c2bca52 | |||
| 61d43700e9 | |||
| 13cc582c7b | |||
| 24b600427e | |||
| 45a6944776 | |||
| 9f29aa7251 | |||
| 77904beb30 | |||
| 3bec27a13d | |||
| 6a64f6cb5a | |||
| 2d28c3aa21 | |||
| 8f13e38eae | |||
| 928136e7bf | |||
| ea55bf43ea | |||
| 72020c9f77 | |||
| 3714d5663c | |||
| 20b32f1ae0 | |||
| a3c6f85d1c | |||
| 9032060930 | |||
| 95879f05d7 | |||
| f13c3ae3e7 | |||
| 669beccc35 | |||
| 5eb7b7bb0c | |||
| 0b677d0faf | |||
| e3325670de | |||
| b9200d3a4c | |||
| 00ba40d168 | |||
| d3e00bfbc2 | |||
| 1db1394c6a | |||
| 7841f58b3d | |||
| a038f5aa8c | |||
| 9cefdefa75 | |||
| c116a30fe3 | |||
| d1a95c6001 | |||
| 8063cbaf80 | |||
| 77ebe2cc89 | |||
| 4d42cd2fd6 | |||
| 1137079fb6 | |||
| 049641cc6b | |||
| 86cae7f8eb | |||
| ee3323fa05 | |||
| 9ac289316c | |||
| f239b568c4 | |||
| b073db7438 | |||
| f7e9d6ee5b | |||
| 7af6905af2 | |||
| 84d553daa7 | |||
| 50066769cd | |||
| 2863587fc1 | |||
| 381aba63f1 | |||
| 00f3188f01 | |||
| 0ae32844c4 | |||
| 072d161be7 | |||
| 9544f4719f | |||
| d5e487f831 | |||
| 2c46e7789f | |||
| a38363662c | |||
| 36ab225f52 | |||
| 4347ceebeb | |||
| b5d27092b8 | |||
| 2543b43592 | |||
| 033214f219 | |||
| 6fb2cda000 | |||
| 2056e3be5a | |||
| 624aad4938 | |||
| eb4ac7acf4 | |||
| f447bf86fc | |||
| 5ad66355be | |||
| 8817f18aa3 | |||
| 4d16128b5d | |||
| 60fafe2a91 | |||
| e87c1df74b | |||
| e363113c5e | |||
| feaf805208 | |||
| 52c868a8dd | |||
| c47423632a | |||
| bac36e4c94 | |||
| 4ea09d6cdc | |||
| 355f05e733 | |||
| 3f989984ab | |||
| 7dc2ae7d87 | |||
| 862b411ff1 | |||
| f9a6c0faac | |||
| bf2dd44081 | |||
| 151d3528fb | |||
| 8565fb57a2 | |||
| 93855dc712 | |||
| 241dce4524 | |||
| 17805e6b31 | |||
| 4b0c2020b9 | |||
| c72d798549 | |||
| 41dc36a52a | |||
| f9a1cedc7e | |||
| 9d26ccff04 | |||
| 6f3e612dde | |||
| 853d01f4d4 | |||
| 8e39dafe00 | |||
| 224089fe16 | |||
| 0848057867 | |||
| fd83f896ee | |||
| 32eea7c3af | |||
| de3ef465f0 | |||
| fc8860f89a | |||
| 869cc79898 | |||
| 5813db75dc | |||
| f0dd38fc0b | |||
| 156598de64 | |||
| cad6d0a471 | |||
| e53623dbb5 | |||
| 8579ff451c | |||
| b892de6b34 | |||
| a922097081 | |||
| af6545444b | |||
| a724b3c727 | |||
| 3d8bf2d195 | |||
| ae7ca9f40d | |||
| 3ca6365ca4 | |||
| fe6dffff0e | |||
| b9b490d2ba | |||
| 4c5abfcd18 | |||
| 1b2ba62394 | |||
| 837b97b5be | |||
| 411797cb07 | |||
| e769ce747c | |||
| b0c0e0a577 | |||
| cdf031527f | |||
| 39ec796a2e | |||
| 5190457aa1 | |||
| c31f567d42 | |||
| 55d24aebb9 | |||
| 5f558c447e | |||
| 3f119c515c | |||
| 933929511e | |||
| 11409081fb | |||
| 0bb0b21a6e | |||
| 97a322c5c9 | |||
| dd5b7a5156 | |||
| 067cff0043 | |||
| 515c0c58ae | |||
| bb54cebe28 | |||
| 1b3f5df139 | |||
| 4a9aa5ca9e | |||
| a9bb43be24 | |||
| ed5f74c237 | |||
| 8202ee0d9f | |||
| 9b6dfe4efb | |||
| 05686a7913 | |||
| 76b44470b7 | |||
| 1db42b409a | |||
| 37bd3f615c | |||
| db5d67be37 | |||
| 693d8c820e | |||
| f670536eeb | |||
| 8251d8088a | |||
| c2e2e91931 | |||
| ae6dfee85e | |||
| 9cac367f07 | |||
| 45dfd864e0 | |||
| 37fdc4e939 | |||
| 84cba4378c | |||
| b45154cc47 | |||
| 9802eb1bcb | |||
| dabe3cf0bf | |||
| 0e18758068 | |||
| 13de92656d | |||
| 29ad46ced9 | |||
| 7d389c0a8a | |||
| dc7f1cc42b | |||
| 36b0b83b26 | |||
| ec9f32f901 | |||
| a85d98b5d6 | |||
| c7c5056562 | |||
| 54d5dec257 | |||
| 854e5fa7ae | |||
| 6671f60bde | |||
| 4bf7bce92b | |||
| dec8b8361f | |||
| b45a0f9263 | |||
| b4a883cff9 | |||
| 26a9f4a03d | |||
| 7c42307aa9 | |||
| d26b5ff485 | |||
| faf3ecfa6d | |||
| c1b8f2d9f3 | |||
| 883d705436 | |||
| e96f17230a | |||
| c4d7311a25 | |||
| de886071eb | |||
| b1d1197373 | |||
| 35cd6cad03 | |||
| da9a61c086 | |||
| e64ef24f11 | |||
| 52b0feec66 | |||
| 9a9c1a45db | |||
| 8ad179c72f | |||
| 432d03772a | |||
| 59703c8d12 | |||
| 88de916e22 | |||
| db4e79e3e6 | |||
| 2c30aaed8c | |||
| be103c322c | |||
| 1c4b540fdb | |||
| 535a70d85e | |||
| 1b6ba010fd | |||
| 81fd0c6d08 | |||
| af99a3566e | |||
| 1210865c54 | |||
| f5bc134dcf | |||
| 0386244e10 | |||
| 7e4a5fd170 | |||
| de211a74c6 | |||
| 853f1b14a3 | |||
| 465eb1cd5e | |||
| 5d0b6d1b99 | |||
| 0ad763649b | |||
| c5d4b70fd4 | |||
| d918a548fd | |||
| f832e58040 | |||
| f9d79ad402 | |||
| 461e2e0f01 | |||
| 4a4e646b0a | |||
| 4020b93dca | |||
| fb2d5cbcea | |||
| 177bfa0d1a | |||
| 066555c312 | |||
| d2854403cd | |||
| 0a715524fc | |||
| fb819fbd4a | |||
| d9a761c02a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ devenv.local.yaml
|
||||
|
||||
# pre-commit
|
||||
.pre-commit-config.yaml
|
||||
.opencode
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "openwrt/roles/ansible-openwrt"]
|
||||
path = openwrt/roles/ansible-openwrt
|
||||
url = https://github.com/gekmihesg/ansible-openwrt.git
|
||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -2,6 +2,7 @@
|
||||
"recommendations": [
|
||||
"jnoortheen.nix-ide",
|
||||
"detachhead.basedpyright",
|
||||
"mkhl.direnv"
|
||||
"mkhl.direnv",
|
||||
"mermaidchart.vscode-mermaid-chart"
|
||||
]
|
||||
}
|
||||
|
||||
20
Makefile
20
Makefile
@@ -1,3 +1,7 @@
|
||||
SHELL := /usr/bin/env bash
|
||||
|
||||
.PHONY: install-router gen-talos-config apply-talos-config get-kubeconfig garm-image-build garm-image-push garm-image-build-push
|
||||
|
||||
install-router:
|
||||
ansible-playbook ansible/playbook.yml -i ansible/hosts
|
||||
|
||||
@@ -23,3 +27,19 @@ apply-talos-config:
|
||||
|
||||
get-kubeconfig:
|
||||
talosctl -n anapistula-delrosalae kubeconfig talos/generated/kubeconfig
|
||||
|
||||
garm-image-build:
|
||||
set -euo pipefail; \
|
||||
source apps/garm/image-source.env; \
|
||||
docker build \
|
||||
-f docker/garm/Dockerfile \
|
||||
--build-arg GARM_COMMIT=$$GARM_COMMIT \
|
||||
-t $$GARM_IMAGE \
|
||||
.
|
||||
|
||||
garm-image-push:
|
||||
set -euo pipefail; \
|
||||
source apps/garm/image-source.env; \
|
||||
docker push $$GARM_IMAGE
|
||||
|
||||
garm-image-build-push: garm-image-build garm-image-push
|
||||
|
||||
73
README.md
73
README.md
@@ -141,7 +141,7 @@ Currently the k8s cluster consists of single node (hostname anapistula-delrosala
|
||||
|
||||
## Software stack
|
||||
|
||||
The cluster itself is based on [Talos Linux](https://www.talos.dev/) (which is also a Kubernetes distribution) and uses [Cilium](https://cilium.io/) as CNI, IPAM, kube-proxy replacement, Load Balancer, and BGP control plane. Persistent volumes are managed by [OpenEBS LVM LocalPV](https://openebs.io/docs/user-guides/local-storage-user-guide/local-pv-lvm/lvm-overview). Applications are deployed using GitOps (this repo) and reconciled on cluster using [Flux](https://fluxcd.io/). Git repository is hosted on [Gitea](https://gitea.io/) running on a cluster itself. Secets are kept in [OpenBao](https://openbao.org/) (HashiCorp Vault fork) running on a cluster and synced to cluster objects using [Vault Secrets Operator](https://github.com/hashicorp/vault-secrets-operator). Deployments are kept up to date using self hosted [Renovate](https://www.mend.io/renovate/) bot updating manifests in the Git repository. Incoming HTTP traffic is routed to cluster using [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/) and certificates are issued by [cert-manager](https://cert-manager.io/) with [Let's Encrypt](https://letsencrypt.org/) ACME issuer with [cert-manager-webhook-ovh](https://github.com/aureq/cert-manager-webhook-ovh) resolving DNS-01 challanges. Cluster also runs [CloudNativePG](https://cloudnative-pg.io/) operator for managing PostgreSQL databases. High level core cluster software architecture is shown on the diagram below.
|
||||
The cluster itself is based on [Talos Linux](https://www.talos.dev/) (which is also a Kubernetes distribution) and uses [Cilium](https://cilium.io/) as CNI, IPAM, kube-proxy replacement, Load Balancer, and BGP control plane. Persistent volumes are managed by [OpenEBS LVM LocalPV](https://openebs.io/docs/user-guides/local-storage-user-guide/local-pv-lvm/lvm-overview). Applications are deployed using GitOps (this repo) and reconciled on cluster using [Flux](https://fluxcd.io/). Git repository is hosted on [Gitea](https://gitea.io/) running on a cluster itself. Secets are kept in [OpenBao](https://openbao.org/) (HashiCorp Vault fork) running on a cluster and synced to cluster objects using [Vault Secrets Operator](https://github.com/hashicorp/vault-secrets-operator). Deployments are kept up to date using self hosted [Renovate](https://www.mend.io/renovate/) bot updating manifests in the Git repository. Incoming HTTP traffic is routed to cluster using [Nginx Ingress Controller](https://kubernetes.github.io/ingress-nginx/) and certificates are issued by [cert-manager](https://cert-manager.io/) with [Let's Encrypt](https://letsencrypt.org/) ACME issuer with [cert-manager-webhook-ovh](https://github.com/aureq/cert-manager-webhook-ovh) resolving DNS-01 challanges. Cluster also runs [CloudNativePG](https://cloudnative-pg.io/) operator for managing PostgreSQL databases. Router is running [Mikrotik RouterOS](https://help.mikrotik.com/docs/spaces/ROS/pages/328059/RouterOS) and its configuration is managed via [Ansible](https://docs.ansible.com/) playbook in this repo. High level core cluster software architecture is shown on the diagram below.
|
||||
|
||||
> Talos Linux is an immutable Linux distribution purpose-built for running Kubernetes. The OS is distributed as an OCI (Docker) image and does not contain any package manager, shell, SSH, or any other tools for managing the system. Instead, all operations are performed using API, which can be accessed using `talosctl` CLI tool.
|
||||
|
||||
@@ -187,23 +187,62 @@ flowchart TD
|
||||
|
||||
<!-- TODO: Backups, monitoring, logging, deployment with ansible etc -->
|
||||
|
||||
## Applications / Services
|
||||
## Software
|
||||
|
||||
### Infrastructure
|
||||
|
||||
### Operating systems
|
||||
|
||||
| Logo | Name | Description |
|
||||
|------|------|-------------|
|
||||
| <img src="docs/assets/talos.svg" alt="Talos Linux" height="50" width="50"> | Talos Linux | Kubernetes distribution and operating system for cluster nodes |
|
||||
| <img src="docs/assets/mikrotik.svg" alt="MikroTik RouterOS" height="50" width="50"> | MikroTik RouterOS | Router operating system for MikroTik devices |
|
||||
|
||||
### Configuration management
|
||||
|
||||
| Logo | Name | Description |
|
||||
|------|------|-------------|
|
||||
| <img src="docs/assets/flux.svg" alt="Flux CD" height="50" width="50"> | Flux CD | GitOps operator for reconciling cluster state with Git repository |
|
||||
| <img src="docs/assets/ansible.svg" alt="Ansible" height="50" width="50"> | Ansible | Configuration management and automation tool |
|
||||
| | Vault Secrets Operator | Kubernetes operator for syncing secrets from OpenBao/Vault to Kubernetes |
|
||||
|
||||
### Networking
|
||||
|
||||
| Logo | Name | Description |
|
||||
|------|------|-------------|
|
||||
| <img src="docs/assets/cilium.svg" alt="Cilium" height="50" width="50"> | Cilium | CNI, BGP control plane, kube-proxy replacement and Load Balancer for cluster networking |
|
||||
| <img src="docs/assets/nginx.svg" alt="Nginx" height="50" width="50"> | Nginx Ingress Controller | Ingress controller for routing external traffic to services in the cluster |
|
||||
| <img src="docs/assets/cert-manager.svg" alt="cert-manager" height="50" width="50"> | cert-manager | Automatic TLS certificate management |
|
||||
|
||||
### Storage
|
||||
|
||||
| Logo | Name | Description |
|
||||
|------|------|-------------|
|
||||
| <img src="docs/assets/openebs.svg" alt="OpenEBS" height="50" width="50"> | OpenEBS LVM LocalPV | Container Storage Interface for managing persistent volumes on local LVM pools |
|
||||
| <img src="docs/assets/openbao.svg" alt="OpenBao" height="50" width="50"> | OpenBao | Secret storage (HashiCorp Vault compatible) |
|
||||
| <img src="docs/assets/cloudnativepg.svg" alt="CloudNativePG" height="50" width="50"> | CloudNativePG | PostgreSQL operator for managing PostgreSQL instances |
|
||||
|
||||
### Development tools
|
||||
|
||||
| Logo | Name | Description |
|
||||
|------|------|-------------|
|
||||
| <img src="docs/assets/devenv.svg" alt="devenv" height="50" width="50"> | devenv | Tool for declarative managment of development environment using Nix |
|
||||
| <img src="docs/assets/renovate.svg" alt="Renovate" height="50" width="50"> | Renovate | Bot for keeping dependencies up to date |
|
||||
|
||||
### AI infrastructure
|
||||
|
||||
| Logo | Name | Address | Description |
|
||||
|------|------|---------|-------------|
|
||||
| <img src="docs/assets/flux.svg" alt="Flux CD" height="50" width="50"> | Flux CD | | GitOps operator for reconciling cluster state with Git repository |
|
||||
| <img src="docs/assets/cilium.svg" alt="Cilium" height="50" width="50"> | Cilium | | CNI, BGP control plane, kube-proxy replacement and Load Balancer for cluster networking |
|
||||
| <img src="docs/assets/openebs.svg" alt="OpenEBS" height="50" width="50"> | OpenEBS LVM LocalPV | | Container Storage Interface for managing persistent volumes on local LVM pools |
|
||||
| <img src="docs/assets/gitea.svg" alt="Gitea" height="50" width="50"> | Gitea | https://gitea.lumpiasty.xyz/ | Private Git repository hosting and artifact storage (Docker, Helm charts) |
|
||||
| <img src="docs/assets/openbao.svg" alt="OpenBao" height="50" width="50"> | OpenBao | https://openbao.lumpiasty.xyz:8200/ | Secret storage (HashiCorp Vault compatible) |
|
||||
| <img src="docs/assets/renovate.svg" alt="Renovate" height="50" width="50"> | Renovate | | Bot for keeping dependencies up to date |
|
||||
| <img src="docs/assets/cert-manager.svg" alt="cert-manager" height="50" width="50"> | cert-manager | | Automatic TLS certificate management |
|
||||
| <img src="docs/assets/nginx.svg" alt="Nginx" height="50" width="50"> | Nginx Ingress Controller | | Ingress controller for routing external traffic to services in the cluster |
|
||||
| <img src="docs/assets/cloudnativepg.svg" alt="CloudNativePG" height="50" width="50"> | CloudNativePG | | PostgreSQL operator for managing PostgreSQL instances |
|
||||
| <img src="docs/assets/immich.svg" alt="Immich" height="50" width="50"> | Immich | https://immich.lumpiasty.xyz/ | Self-hosted photo and video backup and streaming service |
|
||||
| <img src="docs/assets/teamspeak.svg" alt="iSpeak3" height="50" width="50"> | iSpeak3.pl | [ts3server://ispeak3.pl](ts3server://ispeak3.pl) | Public TeamSpeak 3 voice communication server |
|
||||
| <img src="docs/assets/llama-cpp.svg" alt="LLaMA.cpp" height="50" width="50"> | LLaMA.cpp | https://llama.lumpiasty.xyz/ | LLM inference server running local models with GPU acceleration |
|
||||
|
||||
### Applications/Services
|
||||
|
||||
| Logo | Name | Address | Description |
|
||||
|------|------|---------|-------------|
|
||||
| <img src="docs/assets/gitea.svg" alt="Gitea" height="50" width="50"> | Gitea | https://gitea.lumpiasty.xyz/ | Private Git repository hosting and artifact storage (Docker, Helm charts) |
|
||||
| <img src="docs/assets/open-webui.png" alt="Open WebUI" height="50" width="50"> | Open WebUI | https://openwebui.lumpiasty.xyz/ | Web UI for chatting with LLMs running on the cluster |
|
||||
| <img src="docs/assets/teamspeak.svg" alt="iSpeak3" height="50" width="50"> | iSpeak3.pl | [ts3server://ispeak3.pl](ts3server://ispeak3.pl) | Public TeamSpeak 3 voice communication server |
|
||||
| <img src="docs/assets/immich.svg" alt="Immich" height="50" width="50"> | Immich | https://immich.lumpiasty.xyz/ | Self-hosted photo and video backup and streaming service |
|
||||
| <img src="docs/assets/frigate.svg" alt="Frigate" height="50" width="50"> | Frigate | https://frigate.lumpiasty.xyz/ | NVR for camera system with AI object detection and classification |
|
||||
|
||||
|
||||
@@ -227,6 +266,14 @@ Talos config in this repo is stored as yaml patches under [talos/patches](talos/
|
||||
|
||||
To compile config, you need to have secrets file, which contains certificates and keys for cluster. Those secrets are then incorporated into final config files. That is also why we can not store full config in repo.
|
||||
|
||||
### Router config changes
|
||||
|
||||
Router config is stored as Ansible playbook under `ansible/` directory. To apply changes to router, run `ansible-playbook playbooks/routeros.yml` command in `ansible/` directory Before running playbook, you can check what changes will be applied to router using `--check` flag to `ansible-playbook` command, which will run playbook in "check mode" and show you the changes that would be applied without actually applying them. This is useful for verifying that your changes are correct before applying them to the router.
|
||||
|
||||
To run Ansible playbook, you need to have required Ansible collections installed. You can install them using `ansible-galaxy collection install -r ansible/requirements.yml` command. Configuring this in devenv is yet to be done, so you might need to install collections manually for now.
|
||||
|
||||
Secrets needed to access the router API are stored in OpenBao and loaded on demand when running playbook so you need to have access to appropriate secrets.
|
||||
|
||||
### Kube API access
|
||||
|
||||
To generate kubeconfig for accessing cluster API, run `make get-kubeconfig` command, which will generate kubeconfig under `talos/generated/kubeconfig` path. Devenv automatically sets `KUBECONFIG` enviornment variable to point to this file, so you can start using `kubectl` right away.
|
||||
|
||||
20
ansible/README.md
Normal file
20
ansible/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
## RouterOS Ansible
|
||||
|
||||
This directory contains the new Ansible automation for the MikroTik router.
|
||||
|
||||
- Transport: RouterOS API (`community.routeros` collection), not SSH CLI scraping.
|
||||
- Layout: one playbook (`playbooks/routeros.yml`) importing domain task files from `tasks/`.
|
||||
- Goal: idempotent convergence using `community.routeros.api_modify` for managed paths.
|
||||
|
||||
### Quick start
|
||||
|
||||
1. Install dependencies:
|
||||
- `ansible-galaxy collection install -r ansible/requirements.yml`
|
||||
- `python -m pip install librouteros hvac`
|
||||
2. Configure secret references in `ansible/vars/routeros-secrets.yml`.
|
||||
3. Store required fields in OpenBao under configured KV path.
|
||||
4. Export token (`OPENBAO_TOKEN` or `VAULT_TOKEN`).
|
||||
5. Run:
|
||||
- `ANSIBLE_CONFIG=ansible/ansible.cfg ansible-playbook ansible/playbooks/routeros.yml`
|
||||
|
||||
More details and design rationale: `docs/ansible/routeros-design.md`.
|
||||
5
ansible/ansible.cfg
Normal file
5
ansible/ansible.cfg
Normal file
@@ -0,0 +1,5 @@
|
||||
[defaults]
|
||||
inventory = inventory/hosts.yml
|
||||
host_key_checking = False
|
||||
retry_files_enabled = False
|
||||
result_format = yaml
|
||||
@@ -1,2 +0,0 @@
|
||||
[openwrt]
|
||||
2001:470:61a3:100:ffff:ffff:ffff:ffff ansible_scp_extra_args="-O"
|
||||
6
ansible/inventory/hosts.yml
Normal file
6
ansible/inventory/hosts.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
all:
|
||||
children:
|
||||
mikrotik:
|
||||
hosts:
|
||||
crs418:
|
||||
ansible_host: 192.168.255.10
|
||||
@@ -1,6 +0,0 @@
|
||||
- name: Configure router
|
||||
hosts: openwrt
|
||||
remote_user: root
|
||||
roles:
|
||||
- ansible-openwrt
|
||||
- router
|
||||
92
ansible/playbooks/routeros.yml
Normal file
92
ansible/playbooks/routeros.yml
Normal file
@@ -0,0 +1,92 @@
|
||||
---
|
||||
- name: Converge MikroTik RouterOS config
|
||||
hosts: mikrotik
|
||||
gather_facts: false
|
||||
connection: local
|
||||
|
||||
vars_files:
|
||||
- ../vars/routeros-secrets.yml
|
||||
|
||||
pre_tasks:
|
||||
- name: Load router secrets from OpenBao
|
||||
ansible.builtin.set_fact:
|
||||
routeros_api_username: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.hashi_vault.vault_kv2_get',
|
||||
openbao_fields.routeros_api.path,
|
||||
engine_mount_point=openbao_kv_mount
|
||||
).secret[openbao_fields.routeros_api.username_key]
|
||||
}}
|
||||
routeros_api_password: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.hashi_vault.vault_kv2_get',
|
||||
openbao_fields.routeros_api.path,
|
||||
engine_mount_point=openbao_kv_mount
|
||||
).secret[openbao_fields.routeros_api.password_key]
|
||||
}}
|
||||
routeros_pppoe_username: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.hashi_vault.vault_kv2_get',
|
||||
openbao_fields.wan_pppoe.path,
|
||||
engine_mount_point=openbao_kv_mount
|
||||
).secret[openbao_fields.wan_pppoe.username_key]
|
||||
}}
|
||||
routeros_pppoe_password: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.hashi_vault.vault_kv2_get',
|
||||
openbao_fields.wan_pppoe.path,
|
||||
engine_mount_point=openbao_kv_mount
|
||||
).secret[openbao_fields.wan_pppoe.password_key]
|
||||
}}
|
||||
routeros_tailscale_container_password: >-
|
||||
{{
|
||||
lookup(
|
||||
'community.hashi_vault.vault_kv2_get',
|
||||
openbao_fields.routeros_tailscale_container.path,
|
||||
engine_mount_point=openbao_kv_mount
|
||||
).secret[openbao_fields.routeros_tailscale_container.container_password_key]
|
||||
}}
|
||||
no_log: true
|
||||
|
||||
module_defaults:
|
||||
group/community.routeros.api:
|
||||
hostname: "{{ ansible_host }}"
|
||||
username: "{{ routeros_api_username }}"
|
||||
password: "{{ routeros_api_password }}"
|
||||
tls: true
|
||||
validate_certs: false
|
||||
validate_cert_hostname: false
|
||||
force_no_cert: true
|
||||
encoding: UTF-8
|
||||
|
||||
tasks:
|
||||
- name: Preflight checks
|
||||
ansible.builtin.import_tasks: ../tasks/preflight.yml
|
||||
|
||||
- name: Base network configuration
|
||||
ansible.builtin.import_tasks: ../tasks/base.yml
|
||||
|
||||
- name: WAN and tunnel interfaces
|
||||
ansible.builtin.import_tasks: ../tasks/wan.yml
|
||||
|
||||
- name: Hardware and platform tuning
|
||||
ansible.builtin.import_tasks: ../tasks/hardware.yml
|
||||
|
||||
- name: RouterOS container configuration
|
||||
ansible.builtin.import_tasks: ../tasks/containers.yml
|
||||
|
||||
- name: Addressing configuration
|
||||
ansible.builtin.import_tasks: ../tasks/addressing.yml
|
||||
|
||||
- name: Firewall configuration
|
||||
ansible.builtin.import_tasks: ../tasks/firewall.yml
|
||||
|
||||
- name: Routing configuration
|
||||
ansible.builtin.import_tasks: ../tasks/routing.yml
|
||||
|
||||
- name: System configuration
|
||||
ansible.builtin.import_tasks: ../tasks/system.yml
|
||||
5
ansible/requirements.yml
Normal file
5
ansible/requirements.yml
Normal file
@@ -0,0 +1,5 @@
|
||||
collections:
|
||||
- name: community.routeros
|
||||
version: ">=3.16.0"
|
||||
- name: community.hashi_vault
|
||||
version: ">=7.1.0"
|
||||
@@ -1,53 +0,0 @@
|
||||
# Would never work without this awesome blogpost
|
||||
# https://farcaller.net/2024/making-cilium-bgp-work-with-ipv6/
|
||||
|
||||
log "/tmp/bird.log" all;
|
||||
log syslog all;
|
||||
|
||||
#Router ID
|
||||
router id 192.168.1.1;
|
||||
|
||||
protocol kernel kernel4 {
|
||||
learn;
|
||||
scan time 10;
|
||||
merge paths yes;
|
||||
ipv4 {
|
||||
import none;
|
||||
export all;
|
||||
};
|
||||
}
|
||||
|
||||
protocol kernel kernel6 {
|
||||
learn;
|
||||
scan time 10;
|
||||
merge paths yes;
|
||||
ipv6 {
|
||||
import none;
|
||||
export all;
|
||||
};
|
||||
}
|
||||
|
||||
protocol device {
|
||||
scan time 10;
|
||||
}
|
||||
|
||||
protocol direct {
|
||||
interface "*";
|
||||
}
|
||||
|
||||
protocol bgp homelab {
|
||||
debug { events };
|
||||
passive;
|
||||
direct;
|
||||
local 2001:470:61a3:100:ffff:ffff:ffff:ffff as 65000;
|
||||
neighbor range 2001:470:61a3:100::/64 as 65000;
|
||||
ipv4 {
|
||||
extended next hop yes;
|
||||
import all;
|
||||
export all;
|
||||
};
|
||||
ipv6 {
|
||||
import all;
|
||||
export all;
|
||||
};
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
- name: Reload bird
|
||||
service:
|
||||
name: bird
|
||||
state: restarted
|
||||
enabled: true
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
- name: Install bird2
|
||||
opkg:
|
||||
name: "{{ item }}"
|
||||
state: present
|
||||
# Workaround for opkg module not handling multiple names at once well
|
||||
loop:
|
||||
- bird2
|
||||
- bird2c
|
||||
|
||||
- name: Set up bird.conf
|
||||
ansible.builtin.copy:
|
||||
src: bird.conf
|
||||
dest: /etc/bird.conf
|
||||
mode: "644"
|
||||
notify: Reload bird
|
||||
48
ansible/tasks/addressing.yml
Normal file
48
ansible/tasks/addressing.yml
Normal file
@@ -0,0 +1,48 @@
|
||||
---
|
||||
- name: Configure IPv4 addresses
|
||||
community.routeros.api_modify:
|
||||
path: ip address
|
||||
data:
|
||||
- address: 172.17.0.1/16
|
||||
interface: dockers
|
||||
network: 172.17.0.0
|
||||
- address: 192.168.4.1/24
|
||||
interface: lo
|
||||
network: 192.168.4.0
|
||||
- address: 192.168.100.20/24
|
||||
interface: sfp-sfpplus1
|
||||
network: 192.168.100.0
|
||||
- address: 192.168.255.10/24
|
||||
interface: bridge1
|
||||
network: 192.168.255.0
|
||||
- address: 192.168.0.1/24
|
||||
interface: vlan2
|
||||
network: 192.168.0.0
|
||||
- address: 192.168.1.1/24
|
||||
interface: vlan4
|
||||
network: 192.168.1.0
|
||||
- address: 192.168.3.1/24
|
||||
interface: vlan3
|
||||
network: 192.168.3.0
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure IPv6 addresses
|
||||
community.routeros.api_modify:
|
||||
path: ipv6 address
|
||||
data:
|
||||
- address: 2001:470:70:dd::2/64
|
||||
advertise: false
|
||||
interface: sit1
|
||||
- address: ::ffff:ffff:ffff:ffff/64
|
||||
from-pool: pool1
|
||||
interface: vlan2
|
||||
- address: 2001:470:61a3:500:ffff:ffff:ffff:ffff/64
|
||||
interface: dockers
|
||||
- address: 2001:470:61a3:100::1/64
|
||||
advertise: false
|
||||
interface: vlan4
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
226
ansible/tasks/base.yml
Normal file
226
ansible/tasks/base.yml
Normal file
@@ -0,0 +1,226 @@
|
||||
---
|
||||
- name: Configure bridges
|
||||
community.routeros.api_modify:
|
||||
path: interface bridge
|
||||
data:
|
||||
- name: bridge1
|
||||
vlan-filtering: true
|
||||
- name: dockers
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure VLAN interfaces
|
||||
community.routeros.api_modify:
|
||||
path: interface vlan
|
||||
data:
|
||||
- name: vlan2
|
||||
comment: LAN (PC, WIFI)
|
||||
interface: bridge1
|
||||
vlan-id: 2
|
||||
- name: vlan3
|
||||
comment: KAMERY
|
||||
interface: bridge1
|
||||
vlan-id: 3
|
||||
- name: vlan4
|
||||
comment: SERVER LAN
|
||||
interface: bridge1
|
||||
vlan-id: 4
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure interface lists
|
||||
community.routeros.api_modify:
|
||||
path: interface list
|
||||
data:
|
||||
- name: wan
|
||||
comment: contains interfaces facing internet
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure interface list members
|
||||
community.routeros.api_modify:
|
||||
path: interface list member
|
||||
data:
|
||||
- interface: pppoe-gpon
|
||||
list: wan
|
||||
- interface: lte1
|
||||
list: wan
|
||||
- interface: sit1
|
||||
list: wan
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure bridge ports
|
||||
community.routeros.api_modify:
|
||||
path: interface bridge port
|
||||
data:
|
||||
- bridge: dockers
|
||||
interface: veth1
|
||||
comment: Tailscale container interface
|
||||
- bridge: bridge1
|
||||
interface: ether1
|
||||
pvid: 2
|
||||
- bridge: bridge1
|
||||
interface: ether2
|
||||
pvid: 2
|
||||
- bridge: bridge1
|
||||
interface: ether8
|
||||
pvid: 4
|
||||
- bridge: bridge1
|
||||
interface: ether9
|
||||
pvid: 2
|
||||
- bridge: bridge1
|
||||
interface: ether10
|
||||
pvid: 3
|
||||
- bridge: bridge1
|
||||
interface: sfp-sfpplus2
|
||||
- bridge: bridge1
|
||||
interface: ether11
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure bridge VLAN membership
|
||||
community.routeros.api_modify:
|
||||
path: interface bridge vlan
|
||||
data:
|
||||
- bridge: bridge1
|
||||
tagged: sfp-sfpplus2
|
||||
untagged: ether1,ether2,ether9
|
||||
vlan-ids: 2
|
||||
- bridge: bridge1
|
||||
tagged: sfp-sfpplus2
|
||||
untagged: ether10
|
||||
vlan-ids: 3
|
||||
- bridge: bridge1
|
||||
untagged: ether8
|
||||
vlan-ids: 4
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure IPv4 pools
|
||||
community.routeros.api_modify:
|
||||
path: ip pool
|
||||
data:
|
||||
- name: dhcp_pool0
|
||||
ranges: 192.168.0.50-192.168.0.250
|
||||
comment: LAN DHCP pool
|
||||
- name: dhcp_pool1
|
||||
ranges: 192.168.255.1-192.168.255.9,192.168.255.11-192.168.255.254
|
||||
comment: MGMT DHCP pool
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure DHCP servers
|
||||
community.routeros.api_modify:
|
||||
path: ip dhcp-server
|
||||
data:
|
||||
- name: dhcp1
|
||||
address-pool: dhcp_pool0
|
||||
interface: vlan2
|
||||
lease-time: 30m
|
||||
comment: LAN
|
||||
- name: dhcp2
|
||||
address-pool: dhcp_pool1
|
||||
interface: bridge1
|
||||
lease-time: 30m
|
||||
comment: MGMT
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure DHCP networks
|
||||
community.routeros.api_modify:
|
||||
path: ip dhcp-server network
|
||||
data:
|
||||
- address: 192.168.0.0/24
|
||||
dns-server: 192.168.0.1
|
||||
gateway: 192.168.0.1
|
||||
- address: 192.168.255.0/24
|
||||
dns-none: true
|
||||
gateway: 192.168.255.10
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
# TODO: IPv6 pools are useful when we have dynamic prefix, but we don't
|
||||
# We can remove it now
|
||||
- name: Configure IPv6 pools
|
||||
community.routeros.api_modify:
|
||||
path: ipv6 pool
|
||||
data:
|
||||
- name: pool1
|
||||
prefix: 2001:470:61a3::/48
|
||||
prefix-length: 64
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure DNS
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: ip dns
|
||||
find: {}
|
||||
values:
|
||||
allow-remote-requests: true
|
||||
cache-size: 20480
|
||||
servers: 1.1.1.1,1.0.0.1,2606:4700:4700::1111,2606:4700:4700::1001
|
||||
|
||||
- name: Configure NAT-PMP global settings
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: ip nat-pmp
|
||||
find: {}
|
||||
values:
|
||||
enabled: true
|
||||
|
||||
- name: Configure NAT-PMP interfaces
|
||||
community.routeros.api_modify:
|
||||
path: ip nat-pmp interfaces
|
||||
data:
|
||||
- interface: dockers
|
||||
type: internal
|
||||
- interface: pppoe-gpon
|
||||
type: external
|
||||
- interface: vlan2
|
||||
type: internal
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure UPnP global settings
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: ip upnp
|
||||
find: {}
|
||||
values:
|
||||
enabled: true
|
||||
|
||||
- name: Configure UPnP interfaces
|
||||
community.routeros.api_modify:
|
||||
path: ip upnp interfaces
|
||||
data:
|
||||
- interface: dockers
|
||||
type: internal
|
||||
- interface: pppoe-gpon
|
||||
type: external
|
||||
- interface: vlan2
|
||||
type: internal
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure IPv6 ND defaults
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: ipv6 nd
|
||||
find:
|
||||
default: true
|
||||
values:
|
||||
advertise-dns: true
|
||||
66
ansible/tasks/containers.yml
Normal file
66
ansible/tasks/containers.yml
Normal file
@@ -0,0 +1,66 @@
|
||||
---
|
||||
- name: Configure container runtime defaults
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: container config
|
||||
find: {}
|
||||
values:
|
||||
registry-url: https://ghcr.io
|
||||
tmpdir: /tmp1/pull
|
||||
|
||||
- name: Configure container env lists
|
||||
community.routeros.api_modify:
|
||||
path: container envs
|
||||
data:
|
||||
- key: ADVERTISE_ROUTES
|
||||
list: tailscale
|
||||
value: 192.168.0.0/24,192.168.1.0/24,192.168.4.1/32,192.168.100.1/32,192.168.255.0/24,10.42.0.0/16,10.43.0.0/16,10.44.0.0/16,2001:470:61a3::/48
|
||||
- key: CONTAINER_GATEWAY
|
||||
list: tailscale
|
||||
value: 172.17.0.1
|
||||
- key: PASSWORD
|
||||
list: tailscale
|
||||
value: "{{ routeros_tailscale_container_password }}"
|
||||
- key: TAILSCALE_ARGS
|
||||
list: tailscale
|
||||
value: --accept-routes --advertise-exit-node --snat-subnet-routes=false
|
||||
- key: UPDATE_TAILSCALE
|
||||
list: tailscale
|
||||
value: y
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure container mounts
|
||||
community.routeros.api_modify:
|
||||
path: container mounts
|
||||
data:
|
||||
- dst: /var/lib/tailscale
|
||||
list: tailscale
|
||||
src: /usb1/tailscale
|
||||
- dst: /root
|
||||
list: tailscale-root
|
||||
src: /tmp1/tailscale-root
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure tailscale container
|
||||
community.routeros.api_modify:
|
||||
path: container
|
||||
data:
|
||||
- dns: 172.17.0.1
|
||||
envlists: tailscale
|
||||
hostname: mikrotik
|
||||
interface: veth1
|
||||
layer-dir: ""
|
||||
mountlists: tailscale
|
||||
name: tailscale-mikrotik:latest
|
||||
remote-image: fluent-networks/tailscale-mikrotik:latest
|
||||
root-dir: /usb1/containers/tailscale
|
||||
start-on-boot: true
|
||||
tmpfs: /tmp:67108864:01777
|
||||
workdir: /
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
480
ansible/tasks/firewall.yml
Normal file
480
ansible/tasks/firewall.yml
Normal file
@@ -0,0 +1,480 @@
|
||||
---
|
||||
- name: Configure IPv4 firewall filter rules
|
||||
community.routeros.api_modify:
|
||||
path: ip firewall filter
|
||||
data:
|
||||
- action: fasttrack-connection
|
||||
chain: forward
|
||||
connection-state: established,related
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow all already established connections
|
||||
connection-state: established,related
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow LTE modem management (next rule forbids it otherwise)
|
||||
dst-address: 192.168.8.1
|
||||
out-interface: lte1
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Forbid forwarding 192.168.0.0/16 to WAN
|
||||
dst-address: 192.168.0.0/16
|
||||
out-interface-list: wan
|
||||
reject-with: icmp-network-unreachable
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Forbid forwarding 10.0.0.0/8 to WAN
|
||||
dst-address: 10.0.0.0/8
|
||||
out-interface-list: wan
|
||||
reject-with: icmp-network-unreachable
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Forbid forwarding 172.16.0.0/12 to WAN
|
||||
dst-address: 172.16.0.0/12
|
||||
out-interface-list: wan
|
||||
reject-with: icmp-network-unreachable
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Forbid forwarding 100.64.0.0/10 to WAN
|
||||
dst-address: 100.64.0.0/10
|
||||
out-interface-list: wan
|
||||
reject-with: icmp-network-unreachable
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from LAN to everywhere
|
||||
in-interface: vlan2
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from SRV to internet
|
||||
in-interface: vlan4
|
||||
out-interface-list: wan
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from SRV to CAM
|
||||
in-interface: vlan4
|
||||
out-interface: vlan3
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from dockers to everywhere
|
||||
in-interface: dockers
|
||||
- action: jump
|
||||
chain: forward
|
||||
comment: Allow port forwards
|
||||
in-interface: pppoe-gpon
|
||||
jump-target: allow-ports
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Reject all remaining (port unreachable from WAN)
|
||||
in-interface-list: wan
|
||||
log-prefix: FORWARD REJECT
|
||||
reject-with: icmp-port-unreachable
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Reject all remaining (net prohibited from LAN)
|
||||
log-prefix: FORWARD REJECT
|
||||
reject-with: icmp-net-prohibited
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow all already established connections
|
||||
connection-state: established,related
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow HE tunnel
|
||||
in-interface: pppoe-gpon
|
||||
protocol: ipv6-encap
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow ICMP
|
||||
protocol: icmp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow Winbox
|
||||
dst-port: 8291
|
||||
log: true
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow SSH Mikrotik
|
||||
dst-port: 2137
|
||||
log: true
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow RouterOS API-SSL from MGMT
|
||||
dst-port: 8729
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow DNS from LAN
|
||||
dst-port: 53
|
||||
in-interface: vlan2
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
dst-port: 53
|
||||
in-interface: vlan2
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow DNS from SRV
|
||||
dst-port: 53
|
||||
in-interface: vlan4
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
dst-port: 53
|
||||
in-interface: vlan4
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow DNS from dockers
|
||||
dst-port: 53
|
||||
in-interface: dockers
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
dst-port: 53
|
||||
in-interface: dockers
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow BGP from SRV
|
||||
dst-port: 179
|
||||
in-interface: vlan4
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: NAT-PMP from LAN
|
||||
dst-port: 5351
|
||||
in-interface: vlan2
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: NAT-PMP from dockers (for tailscale)
|
||||
dst-port: 5351
|
||||
in-interface: dockers
|
||||
protocol: udp
|
||||
- action: reject
|
||||
chain: input
|
||||
comment: Reject all remaining
|
||||
log-prefix: INPUT REJECT
|
||||
reject-with: icmp-port-unreachable
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
comment: Allow TS3
|
||||
dst-port: 9987
|
||||
out-interface: vlan4
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
dst-port: 30033
|
||||
out-interface: vlan4
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
comment: Allow HTTP
|
||||
dst-port: 80
|
||||
out-interface: vlan4
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
comment: Allow HTTPS
|
||||
dst-port: 443
|
||||
out-interface: vlan4
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
comment: Allow SSH Gitea
|
||||
dst-port: 22
|
||||
out-interface: vlan4
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
comment: Allow anything udp to Tailscale
|
||||
dst-address: 172.17.0.2
|
||||
out-interface: dockers
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: allow-ports
|
||||
comment: Allow anything from GPON to LAN (NAT-PMP)
|
||||
dst-address: 192.168.0.0/24
|
||||
in-interface: pppoe-gpon
|
||||
out-interface: vlan2
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure IPv4 NAT rules
|
||||
community.routeros.api_modify:
|
||||
path: ip firewall nat
|
||||
data:
|
||||
- action: masquerade
|
||||
chain: srcnat
|
||||
comment: Masquerade to internet
|
||||
out-interface-list: wan
|
||||
- action: masquerade
|
||||
chain: srcnat
|
||||
comment: GPON ONT management
|
||||
dst-address: 192.168.100.1
|
||||
- action: masquerade
|
||||
chain: srcnat
|
||||
comment: LTE Modem management
|
||||
dst-address: 192.168.8.1
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: TS3
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 9987
|
||||
protocol: udp
|
||||
to-addresses: 10.44.0.0
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 30033
|
||||
protocol: tcp
|
||||
to-addresses: 10.44.0.0
|
||||
- action: src-nat
|
||||
chain: srcnat
|
||||
comment: src-nat from LAN to TS3 to some Greenland address
|
||||
dst-address: 10.44.0.0
|
||||
dst-port: 9987
|
||||
in-interface: '!pppoe-gpon'
|
||||
protocol: udp
|
||||
to-addresses: 128.0.70.5
|
||||
- action: src-nat
|
||||
chain: srcnat
|
||||
dst-address: 10.44.0.0
|
||||
dst-port: 30033
|
||||
in-interface: '!pppoe-gpon'
|
||||
protocol: tcp
|
||||
to-addresses: 128.0.70.5
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: HTTPS
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 443
|
||||
protocol: tcp
|
||||
to-addresses: 10.44.0.6
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: HTTP
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 80
|
||||
protocol: tcp
|
||||
to-addresses: 10.44.0.6
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: SSH Gitea
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 22
|
||||
protocol: tcp
|
||||
to-addresses: 10.44.0.6
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: sunshine
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 47984
|
||||
in-interface: pppoe-gpon
|
||||
protocol: tcp
|
||||
to-addresses: 192.168.0.67
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: sunshine
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 47989
|
||||
in-interface: pppoe-gpon
|
||||
protocol: tcp
|
||||
to-addresses: 192.168.0.67
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: sunshine
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 48010
|
||||
in-interface: pppoe-gpon
|
||||
protocol: tcp
|
||||
to-addresses: 192.168.0.67
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: sunshine
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 48010
|
||||
in-interface: pppoe-gpon
|
||||
protocol: udp
|
||||
to-addresses: 192.168.0.67
|
||||
- action: dst-nat
|
||||
chain: dstnat
|
||||
comment: sunshine
|
||||
dst-address: 139.28.40.212
|
||||
dst-port: 47998-48000
|
||||
in-interface: pppoe-gpon
|
||||
protocol: udp
|
||||
to-addresses: 192.168.0.67
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure IPv6 firewall filter rules
|
||||
community.routeros.api_modify:
|
||||
path: ipv6 firewall filter
|
||||
data:
|
||||
- action: fasttrack-connection
|
||||
chain: forward
|
||||
connection-state: established,related
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow all already established connections
|
||||
connection-state: established,related
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Forbid forwarding routed /48 from tunnelbroker to WAN
|
||||
dst-address: 2001:470:61a3::/48
|
||||
out-interface-list: wan
|
||||
reject-with: icmp-no-route
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Forbid forwarding routed /64 from tunnelbroker to WAN
|
||||
dst-address: 2001:470:71:dd::/64
|
||||
out-interface-list: wan
|
||||
reject-with: icmp-no-route
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from LAN to everywhere
|
||||
in-interface: vlan2
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow ICMPv6 from internet to LAN
|
||||
in-interface-list: wan
|
||||
out-interface: vlan2
|
||||
protocol: icmpv6
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from SRV to internet
|
||||
in-interface: vlan4
|
||||
out-interface-list: wan
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from internet to SRV nodes
|
||||
dst-address: 2001:470:61a3:100::/64
|
||||
in-interface-list: wan
|
||||
out-interface: vlan4
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from internet to homelab LB
|
||||
dst-address: 2001:470:61a3:400::/112
|
||||
in-interface-list: wan
|
||||
out-interface: vlan4
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from SRV to CAM
|
||||
in-interface: vlan4
|
||||
out-interface: vlan3
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from dockers to everywhere
|
||||
in-interface: dockers
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow from internet to dockers
|
||||
dst-address: 2001:470:61a3:500::/64
|
||||
in-interface-list: wan
|
||||
out-interface: dockers
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow tcp transmission port to LAN
|
||||
dst-port: 51413
|
||||
out-interface: vlan2
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: forward
|
||||
comment: Allow udp transmission port to LAN
|
||||
dst-port: 51413
|
||||
out-interface: vlan2
|
||||
protocol: udp
|
||||
- action: reject
|
||||
chain: forward
|
||||
comment: Reject all remaining
|
||||
reject-with: icmp-no-route
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow all already established connections
|
||||
connection-state: established,related
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow ICMPv6
|
||||
protocol: icmpv6
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow Winbox
|
||||
dst-port: 8291
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow SSH Mikrotik
|
||||
dst-port: 2137
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow DNS from LAN
|
||||
dst-port: 53
|
||||
in-interface: vlan2
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
dst-port: 53
|
||||
in-interface: vlan2
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow DNS from SRV
|
||||
dst-port: 53
|
||||
in-interface: vlan4
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
dst-port: 53
|
||||
in-interface: vlan4
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow DNS from dockers
|
||||
dst-port: 53
|
||||
in-interface: dockers
|
||||
protocol: udp
|
||||
- action: accept
|
||||
chain: input
|
||||
dst-port: 53
|
||||
in-interface: dockers
|
||||
protocol: tcp
|
||||
- action: accept
|
||||
chain: input
|
||||
comment: Allow BGP from SRV
|
||||
dst-port: 179
|
||||
in-interface: vlan4
|
||||
protocol: tcp
|
||||
src-address: 2001:470:61a3:100::/64
|
||||
- action: reject
|
||||
chain: input
|
||||
comment: Reject all remaining
|
||||
reject-with: icmp-admin-prohibited
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure IPv6 NAT rules
|
||||
community.routeros.api_modify:
|
||||
path: ipv6 firewall nat
|
||||
data:
|
||||
- action: src-nat
|
||||
chain: srcnat
|
||||
comment: src-nat tailnet to internet
|
||||
out-interface-list: wan
|
||||
src-address: fd7a:115c:a1e0::/48
|
||||
to-address: 2001:470:61a3:600::/64
|
||||
- action: masquerade
|
||||
chain: srcnat
|
||||
disabled: true
|
||||
in-interface: vlan2
|
||||
out-interface: vlan4
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
103
ansible/tasks/hardware.yml
Normal file
103
ansible/tasks/hardware.yml
Normal file
@@ -0,0 +1,103 @@
|
||||
---
|
||||
- name: Configure ethernet interface metadata and SFP options
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: interface ethernet
|
||||
find:
|
||||
default-name: "{{ item.default_name }}"
|
||||
values: "{{ item.config }}"
|
||||
loop:
|
||||
- default_name: ether1
|
||||
config:
|
||||
comment: Mój pc
|
||||
- default_name: ether2
|
||||
config:
|
||||
comment: Wifi środek
|
||||
- default_name: ether8
|
||||
config:
|
||||
comment: Serwer
|
||||
- default_name: ether9
|
||||
config:
|
||||
comment: Wifi góra
|
||||
- default_name: ether10
|
||||
config:
|
||||
comment: Kamera na domu
|
||||
- default_name: ether11
|
||||
config:
|
||||
comment: KVM serwer
|
||||
- default_name: sfp-sfpplus1
|
||||
config:
|
||||
auto-negotiation: false
|
||||
comment: GPON WAN
|
||||
speed: 2.5G-baseX
|
||||
- default_name: sfp-sfpplus2
|
||||
config:
|
||||
comment: GARAŻ
|
||||
loop_control:
|
||||
label: "{{ item.default_name }}"
|
||||
|
||||
- name: Configure LTE interface defaults
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: interface lte
|
||||
find:
|
||||
default-name: lte1
|
||||
values:
|
||||
apn-profiles: default-nodns
|
||||
comment: Backup LTE WAN
|
||||
|
||||
- name: Configure LTE APN profiles
|
||||
community.routeros.api_modify:
|
||||
path: interface lte apn
|
||||
data:
|
||||
- add-default-route: false
|
||||
apn: internet
|
||||
comment: default but without dns and default route
|
||||
ipv6-interface: lte1
|
||||
name: default-nodns
|
||||
use-network-apn: true
|
||||
use-peer-dns: false
|
||||
# Default APN we can't really remove yet I don't want to reconfigure it
|
||||
- add-default-route: true
|
||||
apn: internet
|
||||
authentication: none
|
||||
default-route-distance: 2
|
||||
ip-type: auto
|
||||
name: default
|
||||
use-network-apn: true
|
||||
use-peer-dns: true
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
|
||||
- name: Configure temporary disk for containers
|
||||
community.routeros.api_modify:
|
||||
path: disk
|
||||
data:
|
||||
- slot: tmp1
|
||||
type: tmpfs
|
||||
# This is not ideal, there's no unique identifier for usb disk,
|
||||
# after reinstall it might be assigned to another slot
|
||||
# Just adding disk with slot usb1 and not specifying anything else
|
||||
# so ansible doesn't touch it
|
||||
- slot: usb1
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
|
||||
- name: Configure switch settings
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: interface ethernet switch
|
||||
find:
|
||||
.id: "0"
|
||||
values:
|
||||
qos-hw-offloading: true
|
||||
# Enabling L3 offloading would cause all packets to skip firewall and NAT
|
||||
l3-hw-offloading: false
|
||||
|
||||
- name: Configure neighbor discovery settings
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: ip neighbor discovery-settings
|
||||
find: {}
|
||||
values:
|
||||
discover-interface-list: '!dynamic'
|
||||
46
ansible/tasks/preflight.yml
Normal file
46
ansible/tasks/preflight.yml
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
- name: Verify API connectivity and fetch basic facts
|
||||
community.routeros.api_facts:
|
||||
gather_subset:
|
||||
- default
|
||||
- hardware
|
||||
|
||||
- name: Show target identity
|
||||
ansible.builtin.debug:
|
||||
msg: "Managing {{ ansible_host }} ({{ ansible_facts['net_model'] | default('unknown model') }})"
|
||||
|
||||
- name: Assert expected router model
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts['net_model'] is defined
|
||||
- ansible_facts['net_model'] == "CRS418-8P-8G-2S+"
|
||||
fail_msg: "Unexpected router model: {{ ansible_facts['net_model'] | default('unknown') }}"
|
||||
success_msg: "Router model matches expected CRS418-8P-8G-2S+"
|
||||
|
||||
- name: Read RouterOS device-mode flags
|
||||
community.routeros.api:
|
||||
path: system/device-mode
|
||||
register: routeros_device_mode
|
||||
check_mode: false
|
||||
changed_when: false
|
||||
|
||||
- name: Assert container feature is enabled in device mode
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- not (routeros_device_mode.skipped | default(false))
|
||||
- (routeros_device_mode | to_nice_json | lower) is search('container[^a-z0-9]+(yes|true)')
|
||||
fail_msg: "RouterOS device-mode does not report container as enabled. Payload: {{ routeros_device_mode | to_nice_json }}"
|
||||
success_msg: "RouterOS device-mode confirms container=yes"
|
||||
|
||||
- name: Read configured disks
|
||||
community.routeros.api_info:
|
||||
path: disk
|
||||
register: routeros_disks
|
||||
check_mode: false
|
||||
|
||||
- name: Assert usb1 disk is present
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- (routeros_disks.result | selectattr('slot', 'equalto', 'usb1') | list | length) > 0
|
||||
fail_msg: "Required disk slot usb1 is not present on router."
|
||||
success_msg: "Required disk usb1 is present"
|
||||
99
ansible/tasks/routing.yml
Normal file
99
ansible/tasks/routing.yml
Normal file
@@ -0,0 +1,99 @@
|
||||
---
|
||||
- name: Configure IPv4 routes
|
||||
community.routeros.api_modify:
|
||||
path: ip route
|
||||
data:
|
||||
- comment: Tailnet
|
||||
disabled: false
|
||||
distance: 1
|
||||
dst-address: 100.64.0.0/10
|
||||
gateway: 172.17.0.2
|
||||
routing-table: main
|
||||
scope: 30
|
||||
suppress-hw-offload: false
|
||||
target-scope: 10
|
||||
- disabled: false
|
||||
distance: 1
|
||||
dst-address: 0.0.0.0/0
|
||||
gateway: pppoe-gpon
|
||||
routing-table: main
|
||||
scope: 30
|
||||
suppress-hw-offload: false
|
||||
target-scope: 10
|
||||
vrf-interface: pppoe-gpon
|
||||
- disabled: false
|
||||
distance: 2
|
||||
dst-address: 0.0.0.0/0
|
||||
gateway: 192.168.8.1
|
||||
routing-table: main
|
||||
scope: 30
|
||||
suppress-hw-offload: false
|
||||
target-scope: 10
|
||||
vrf-interface: lte1
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
|
||||
- name: Configure IPv6 routes
|
||||
community.routeros.api_modify:
|
||||
path: ipv6 route
|
||||
data:
|
||||
- disabled: false
|
||||
distance: 1
|
||||
dst-address: 2000::/3
|
||||
gateway: 2001:470:70:dd::1
|
||||
scope: 30
|
||||
target-scope: 10
|
||||
- comment: Tailnet
|
||||
disabled: false
|
||||
dst-address: fd7a:115c:a1e0::/48
|
||||
gateway: 2001:470:61a3:500::1
|
||||
pref-src: ""
|
||||
routing-table: main
|
||||
suppress-hw-offload: false
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
|
||||
- name: Configure BGP instance
|
||||
community.routeros.api_modify:
|
||||
path: routing bgp instance
|
||||
data:
|
||||
- name: bgp-homelab
|
||||
as: 65000
|
||||
disabled: false
|
||||
router-id: 192.168.1.1
|
||||
routing-table: main
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure BGP templates
|
||||
community.routeros.api_modify:
|
||||
path: routing bgp template
|
||||
data:
|
||||
- name: klaster
|
||||
afi: ip,ipv6
|
||||
as: 6500
|
||||
disabled: false
|
||||
# Default template
|
||||
- name: default
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
|
||||
- name: Configure BGP connections
|
||||
community.routeros.api_modify:
|
||||
path: routing bgp connection
|
||||
data:
|
||||
- name: bgp1
|
||||
afi: ip,ipv6
|
||||
as: 65000
|
||||
connect: true
|
||||
disabled: false
|
||||
instance: bgp-homelab
|
||||
listen: true
|
||||
local.role: ibgp
|
||||
remote.address: 2001:470:61a3:100::3/128
|
||||
routing-table: main
|
||||
templates: klaster
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
43
ansible/tasks/system.yml
Normal file
43
ansible/tasks/system.yml
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
- name: Configure system clock
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: system clock
|
||||
find: {}
|
||||
values:
|
||||
time-zone-name: Europe/Warsaw
|
||||
|
||||
- name: Configure dedicated Ansible management user
|
||||
community.routeros.api_modify:
|
||||
path: user
|
||||
data:
|
||||
- name: "{{ routeros_api_username }}"
|
||||
group: full
|
||||
password: "{{ routeros_api_password }}"
|
||||
disabled: false
|
||||
comment: "Ansible management user"
|
||||
handle_absent_entries: ignore
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
|
||||
- name: Configure service ports and service enablement
|
||||
community.routeros.api_find_and_modify:
|
||||
ignore_dynamic: false
|
||||
path: ip service
|
||||
find:
|
||||
name: "{{ item.name }}"
|
||||
values: "{{ item }}"
|
||||
loop:
|
||||
- name: ftp
|
||||
disabled: true
|
||||
- name: telnet
|
||||
disabled: true
|
||||
- name: www
|
||||
disabled: true
|
||||
- name: ssh
|
||||
port: 2137
|
||||
- name: api
|
||||
disabled: true
|
||||
- name: api-ssl
|
||||
disabled: false
|
||||
loop_control:
|
||||
label: "{{ item.name }}"
|
||||
44
ansible/tasks/wan.yml
Normal file
44
ansible/tasks/wan.yml
Normal file
@@ -0,0 +1,44 @@
|
||||
---
|
||||
- name: Configure PPPoE client
|
||||
community.routeros.api_modify:
|
||||
path: interface pppoe-client
|
||||
data:
|
||||
- disabled: false
|
||||
interface: sfp-sfpplus1
|
||||
keepalive-timeout: 2
|
||||
name: pppoe-gpon
|
||||
password: "{{ routeros_pppoe_password }}"
|
||||
use-peer-dns: true
|
||||
user: "{{ routeros_pppoe_username }}"
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure 6to4 tunnel interface
|
||||
community.routeros.api_modify:
|
||||
path: interface 6to4
|
||||
data:
|
||||
- comment: Hurricane Electric IPv6 Tunnel Broker
|
||||
local-address: 139.28.40.212
|
||||
mtu: 1472
|
||||
name: sit1
|
||||
remote-address: 216.66.80.162
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
|
||||
- name: Configure veth interface for containers
|
||||
community.routeros.api_modify:
|
||||
path: interface veth
|
||||
data:
|
||||
- address: 172.17.0.2/16,2001:470:61a3:500::1/64
|
||||
container-mac-address: 7E:7E:A1:B1:2A:7C
|
||||
dhcp: false
|
||||
gateway: 172.17.0.1
|
||||
gateway6: 2001:470:61a3:500:ffff:ffff:ffff:ffff
|
||||
mac-address: 7E:7E:A1:B1:2A:7B
|
||||
name: veth1
|
||||
comment: Tailscale container
|
||||
handle_absent_entries: remove
|
||||
handle_entries_content: remove_as_much_as_possible
|
||||
ensure_order: true
|
||||
19
ansible/vars/routeros-secrets.yml
Normal file
19
ansible/vars/routeros-secrets.yml
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
# Secret references only; actual values are loaded from OpenBao/Vault at runtime.
|
||||
|
||||
# KVv2 mount and secret path (full secret path is <mount>/data/<path>).
|
||||
openbao_kv_mount: secret
|
||||
|
||||
# Field names expected in the OpenBao secret.
|
||||
openbao_fields:
|
||||
routeros_api:
|
||||
path: routeros_api
|
||||
username_key: username
|
||||
password_key: password
|
||||
wan_pppoe:
|
||||
path: wan_pppoe
|
||||
username_key: username
|
||||
password_key: password
|
||||
routeros_tailscale_container:
|
||||
path: router_tailscale
|
||||
container_password_key: container_password
|
||||
8
apps/authentik/kustomization.yaml
Normal file
8
apps/authentik/kustomization.yaml
Normal file
@@ -0,0 +1,8 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- postgres-volume.yaml
|
||||
- postgres-cluster.yaml
|
||||
- secret.yaml
|
||||
- release.yaml
|
||||
4
apps/authentik/namespace.yaml
Normal file
4
apps/authentik/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: authentik
|
||||
23
apps/authentik/postgres-cluster.yaml
Normal file
23
apps/authentik/postgres-cluster.yaml
Normal file
@@ -0,0 +1,23 @@
|
||||
---
|
||||
apiVersion: postgresql.cnpg.io/v1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
name: authentik-postgresql-cluster-lvmhdd
|
||||
namespace: authentik
|
||||
spec:
|
||||
instances: 1
|
||||
|
||||
imageName: ghcr.io/cloudnative-pg/postgresql:17.4
|
||||
|
||||
bootstrap:
|
||||
initdb:
|
||||
database: authentik
|
||||
owner: authentik
|
||||
|
||||
storage:
|
||||
pvcTemplate:
|
||||
storageClassName: hdd-lvmpv
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
volumeName: authentik-postgresql-cluster-lvmhdd-1
|
||||
33
apps/authentik/postgres-volume.yaml
Normal file
33
apps/authentik/postgres-volume.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
apiVersion: local.openebs.io/v1alpha1
|
||||
kind: LVMVolume
|
||||
metadata:
|
||||
labels:
|
||||
kubernetes.io/nodename: anapistula-delrosalae
|
||||
name: authentik-postgresql-cluster-lvmhdd-1
|
||||
namespace: openebs
|
||||
spec:
|
||||
capacity: 10Gi
|
||||
ownerNodeID: anapistula-delrosalae
|
||||
shared: "yes"
|
||||
thinProvision: "no"
|
||||
vgPattern: ^openebs-hdd$
|
||||
volGroup: openebs-hdd
|
||||
---
|
||||
kind: PersistentVolume
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: authentik-postgresql-cluster-lvmhdd-1
|
||||
spec:
|
||||
capacity:
|
||||
storage: 10Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: hdd-lvmpv
|
||||
volumeMode: Filesystem
|
||||
csi:
|
||||
driver: local.csi.openebs.io
|
||||
fsType: btrfs
|
||||
volumeHandle: authentik-postgresql-cluster-lvmhdd-1
|
||||
---
|
||||
# PVCs are dynamically created by the Postgres operator
|
||||
61
apps/authentik/release.yaml
Normal file
61
apps/authentik/release.yaml
Normal file
@@ -0,0 +1,61 @@
|
||||
---
|
||||
apiVersion: source.toolkit.fluxcd.io/v1
|
||||
kind: HelmRepository
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
interval: 24h
|
||||
url: https://charts.goauthentik.io
|
||||
---
|
||||
apiVersion: helm.toolkit.fluxcd.io/v2
|
||||
kind: HelmRelease
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
interval: 30m
|
||||
chart:
|
||||
spec:
|
||||
chart: authentik
|
||||
version: 2026.2.1
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: authentik
|
||||
namespace: authentik
|
||||
interval: 12h
|
||||
values:
|
||||
authentik:
|
||||
postgresql:
|
||||
host: authentik-postgresql-cluster-lvmhdd-rw
|
||||
name: authentik
|
||||
user: authentik
|
||||
|
||||
global:
|
||||
env:
|
||||
- name: AUTHENTIK_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-secret
|
||||
key: secret_key
|
||||
- name: AUTHENTIK_POSTGRESQL__PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: authentik-postgresql-cluster-lvmhdd-app
|
||||
key: password
|
||||
|
||||
postgresql:
|
||||
enabled: false
|
||||
|
||||
server:
|
||||
ingress:
|
||||
enabled: true
|
||||
ingressClassName: nginx-ingress
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
hosts:
|
||||
- authentik.lumpiasty.xyz
|
||||
tls:
|
||||
- secretName: authentik-ingress
|
||||
hosts:
|
||||
- authentik.lumpiasty.xyz
|
||||
38
apps/authentik/secret.yaml
Normal file
38
apps/authentik/secret.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: authentik-secret
|
||||
namespace: authentik
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultAuth
|
||||
metadata:
|
||||
name: authentik
|
||||
namespace: authentik
|
||||
spec:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
kubernetes:
|
||||
role: authentik
|
||||
serviceAccount: authentik-secret
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultStaticSecret
|
||||
metadata:
|
||||
name: authentik-secret
|
||||
namespace: authentik
|
||||
spec:
|
||||
type: kv-v2
|
||||
|
||||
mount: secret
|
||||
path: authentik
|
||||
|
||||
destination:
|
||||
create: true
|
||||
name: authentik-secret
|
||||
type: Opaque
|
||||
transformation:
|
||||
excludeRaw: true
|
||||
|
||||
vaultAuthRef: authentik
|
||||
48
apps/crawl4ai-proxy/deployment.yaml
Normal file
48
apps/crawl4ai-proxy/deployment.yaml
Normal file
@@ -0,0 +1,48 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: crawl4ai-proxy
|
||||
namespace: crawl4ai
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: crawl4ai-proxy
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: crawl4ai-proxy
|
||||
spec:
|
||||
containers:
|
||||
- name: crawl4ai-proxy
|
||||
image: gitea.lumpiasty.xyz/lumpiasty/crawl4ai-proxy-fit:latest
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: LISTEN_PORT
|
||||
value: "8000"
|
||||
- name: CRAWL4AI_ENDPOINT
|
||||
value: http://crawl4ai.crawl4ai.svc.cluster.local:11235/crawl
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 8000
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 3
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
requests:
|
||||
cpu: 25m
|
||||
memory: 32Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
5
apps/crawl4ai-proxy/kustomization.yaml
Normal file
5
apps/crawl4ai-proxy/kustomization.yaml
Normal file
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
14
apps/crawl4ai-proxy/service.yaml
Normal file
14
apps/crawl4ai-proxy/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: crawl4ai-proxy
|
||||
namespace: crawl4ai
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: crawl4ai-proxy
|
||||
ports:
|
||||
- name: http
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
protocol: TCP
|
||||
62
apps/crawl4ai/deployment.yaml
Normal file
62
apps/crawl4ai/deployment.yaml
Normal file
@@ -0,0 +1,62 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: crawl4ai
|
||||
namespace: crawl4ai
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: crawl4ai
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: crawl4ai
|
||||
spec:
|
||||
containers:
|
||||
- name: crawl4ai
|
||||
image: unclecode/crawl4ai:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
env:
|
||||
- name: CRAWL4AI_API_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: crawl4ai-secret
|
||||
key: api_token
|
||||
optional: false
|
||||
- name: MAX_CONCURRENT_TASKS
|
||||
value: "5"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 11235
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
volumeMounts:
|
||||
- name: dshm
|
||||
mountPath: /dev/shm
|
||||
volumes:
|
||||
- name: dshm
|
||||
emptyDir:
|
||||
medium: Memory
|
||||
sizeLimit: 1Gi
|
||||
7
apps/crawl4ai/kustomization.yaml
Normal file
7
apps/crawl4ai/kustomization.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- secret.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
4
apps/crawl4ai/namespace.yaml
Normal file
4
apps/crawl4ai/namespace.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: crawl4ai
|
||||
38
apps/crawl4ai/secret.yaml
Normal file
38
apps/crawl4ai/secret.yaml
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: crawl4ai-secret
|
||||
namespace: crawl4ai
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultAuth
|
||||
metadata:
|
||||
name: crawl4ai
|
||||
namespace: crawl4ai
|
||||
spec:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
kubernetes:
|
||||
role: crawl4ai
|
||||
serviceAccount: crawl4ai-secret
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultStaticSecret
|
||||
metadata:
|
||||
name: crawl4ai-secret
|
||||
namespace: crawl4ai
|
||||
spec:
|
||||
type: kv-v2
|
||||
|
||||
mount: secret
|
||||
path: crawl4ai
|
||||
|
||||
destination:
|
||||
create: true
|
||||
name: crawl4ai-secret
|
||||
type: Opaque
|
||||
transformation:
|
||||
excludeRaw: true
|
||||
|
||||
vaultAuthRef: crawl4ai
|
||||
14
apps/crawl4ai/service.yaml
Normal file
14
apps/crawl4ai/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: crawl4ai
|
||||
namespace: crawl4ai
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: crawl4ai
|
||||
ports:
|
||||
- name: http
|
||||
port: 11235
|
||||
targetPort: 11235
|
||||
protocol: TCP
|
||||
49
apps/garm/README.md
Normal file
49
apps/garm/README.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# garm
|
||||
|
||||
This app deploys `garm` with external `garm-provider-k8s`.
|
||||
|
||||
- API/UI ingress: `https://garm.lumpiasty.xyz`
|
||||
- Internal service DNS: `http://garm.garm.svc.cluster.local:9997`
|
||||
|
||||
## Vault secret requirements
|
||||
|
||||
`VaultStaticSecret` reads `secret/data/garm` and expects at least:
|
||||
|
||||
- `jwt_auth_secret`
|
||||
- `database_passphrase` (must be 32 characters)
|
||||
|
||||
## Connect garm to Gitea
|
||||
|
||||
After Flux reconciles this app, initialize garm and add Gitea endpoint/credentials.
|
||||
|
||||
```bash
|
||||
# 1) Initialize garm (from your local devenv shell)
|
||||
garm-cli init \
|
||||
--name homelab \
|
||||
--url https://garm.lumpiasty.xyz \
|
||||
--username admin \
|
||||
--email admin@lumpiasty.xyz \
|
||||
--password '<STRONG_ADMIN_PASSWORD>' \
|
||||
--metadata-url http://garm.garm.svc.cluster.local:9997/api/v1/metadata \
|
||||
--callback-url http://garm.garm.svc.cluster.local:9997/api/v1/callbacks \
|
||||
--webhook-url http://garm.garm.svc.cluster.local:9997/webhooks
|
||||
|
||||
# 2) Add Gitea endpoint
|
||||
garm-cli gitea endpoint create \
|
||||
--name local-gitea \
|
||||
--description 'Cluster Gitea' \
|
||||
--base-url http://gitea-http.gitea.svc.cluster.local:80 \
|
||||
--api-base-url http://gitea-http.gitea.svc.cluster.local:80/api/v1
|
||||
|
||||
# 3) Add Gitea PAT credentials
|
||||
garm-cli gitea credentials add \
|
||||
--name gitea-pat \
|
||||
--description 'PAT for garm' \
|
||||
--endpoint local-gitea \
|
||||
--auth-type pat \
|
||||
--pat-oauth-token '<GITEA_PAT_WITH_write:repository,write:organization>'
|
||||
```
|
||||
|
||||
Then add repositories/orgs and create pools against provider `kubernetes_external`.
|
||||
|
||||
If Gitea refuses webhook installation to cluster-local URLs, set `gitea.config.webhook.ALLOWED_HOST_LIST` in `apps/gitea/release.yaml`.
|
||||
19
apps/garm/configmap.yaml
Normal file
19
apps/garm/configmap.yaml
Normal file
@@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: garm-provider-k8s-config
|
||||
namespace: garm
|
||||
data:
|
||||
provider-config.yaml: |
|
||||
kubeConfigPath: ""
|
||||
runnerNamespace: "garm-runners"
|
||||
podTemplate:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
flavors:
|
||||
default:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 2Gi
|
||||
106
apps/garm/deployment.yaml
Normal file
106
apps/garm/deployment.yaml
Normal file
@@ -0,0 +1,106 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: garm
|
||||
namespace: garm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: garm
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: garm
|
||||
spec:
|
||||
serviceAccountName: garm
|
||||
initContainers:
|
||||
- name: render-garm-config
|
||||
image: alpine:3.23
|
||||
env:
|
||||
- name: JWT_AUTH_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: garm-config
|
||||
key: jwt_auth_secret
|
||||
- name: DATABASE_PASSPHRASE
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: garm-config
|
||||
key: database_passphrase
|
||||
command:
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
cat <<EOF > /etc/garm/config.toml
|
||||
[default]
|
||||
enable_webhook_management = true
|
||||
|
||||
[logging]
|
||||
enable_log_streamer = true
|
||||
log_format = "text"
|
||||
log_level = "info"
|
||||
log_source = false
|
||||
|
||||
[metrics]
|
||||
enable = true
|
||||
disable_auth = false
|
||||
|
||||
[jwt_auth]
|
||||
secret = "${JWT_AUTH_SECRET}"
|
||||
time_to_live = "8760h"
|
||||
|
||||
[apiserver]
|
||||
bind = "0.0.0.0"
|
||||
port = 9997
|
||||
use_tls = false
|
||||
[apiserver.webui]
|
||||
enable = true
|
||||
|
||||
[database]
|
||||
backend = "sqlite3"
|
||||
passphrase = "${DATABASE_PASSPHRASE}"
|
||||
[database.sqlite3]
|
||||
db_file = "/data/garm.db"
|
||||
busy_timeout_seconds = 5
|
||||
|
||||
[[provider]]
|
||||
name = "kubernetes_external"
|
||||
description = "Kubernetes provider"
|
||||
provider_type = "external"
|
||||
[provider.external]
|
||||
config_file = "/etc/garm/provider-config.yaml"
|
||||
provider_executable = "/opt/garm/providers.d/garm-provider-k8s"
|
||||
environment_variables = ["KUBERNETES_"]
|
||||
EOF
|
||||
volumeMounts:
|
||||
- name: config-dir
|
||||
mountPath: /etc/garm
|
||||
containers:
|
||||
- name: garm
|
||||
image: gitea.lumpiasty.xyz/lumpiasty/garm-k8s:r1380
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/garm
|
||||
- --config
|
||||
- /etc/garm/config.toml
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 9997
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: config-dir
|
||||
mountPath: /etc/garm
|
||||
- name: provider-config
|
||||
mountPath: /etc/garm/provider-config.yaml
|
||||
subPath: provider-config.yaml
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: garm-lvmhdd
|
||||
- name: config-dir
|
||||
emptyDir: {}
|
||||
- name: provider-config
|
||||
configMap:
|
||||
name: garm-provider-k8s-config
|
||||
5
apps/garm/image-source.env
Normal file
5
apps/garm/image-source.env
Normal file
@@ -0,0 +1,5 @@
|
||||
# renovate: datasource=github-refs depName=cloudbase/garm versioning=git
|
||||
GARM_COMMIT=818a9dddccba5f2843f185e6a846770988f31fc5
|
||||
GARM_COMMIT_NUMBER=1380
|
||||
GARM_IMAGE_REPO=gitea.lumpiasty.xyz/lumpiasty/garm-k8s
|
||||
GARM_IMAGE=gitea.lumpiasty.xyz/lumpiasty/garm-k8s:r1380
|
||||
24
apps/garm/ingress.yaml
Normal file
24
apps/garm/ingress.yaml
Normal file
@@ -0,0 +1,24 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
namespace: garm
|
||||
name: garm
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
spec:
|
||||
ingressClassName: nginx-ingress
|
||||
rules:
|
||||
- host: garm.lumpiasty.xyz
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: garm
|
||||
port:
|
||||
number: 9997
|
||||
path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- hosts:
|
||||
- garm.lumpiasty.xyz
|
||||
secretName: garm-ingress
|
||||
11
apps/garm/kustomization.yaml
Normal file
11
apps/garm/kustomization.yaml
Normal file
@@ -0,0 +1,11 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- namespace.yaml
|
||||
- pvc.yaml
|
||||
- configmap.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- rbac.yaml
|
||||
- secret.yaml
|
||||
- deployment.yaml
|
||||
9
apps/garm/namespace.yaml
Normal file
9
apps/garm/namespace.yaml
Normal file
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: garm
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: garm-runners
|
||||
46
apps/garm/pvc.yaml
Normal file
46
apps/garm/pvc.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
---
|
||||
apiVersion: local.openebs.io/v1alpha1
|
||||
kind: LVMVolume
|
||||
metadata:
|
||||
labels:
|
||||
kubernetes.io/nodename: anapistula-delrosalae
|
||||
name: garm-lvmhdd
|
||||
namespace: openebs
|
||||
spec:
|
||||
capacity: 5Gi
|
||||
ownerNodeID: anapistula-delrosalae
|
||||
shared: "yes"
|
||||
thinProvision: "no"
|
||||
vgPattern: ^openebs-hdd$
|
||||
volGroup: openebs-hdd
|
||||
---
|
||||
kind: PersistentVolume
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: garm-lvmhdd
|
||||
spec:
|
||||
capacity:
|
||||
storage: 5Gi
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
persistentVolumeReclaimPolicy: Retain
|
||||
storageClassName: hdd-lvmpv
|
||||
volumeMode: Filesystem
|
||||
csi:
|
||||
driver: local.csi.openebs.io
|
||||
fsType: btrfs
|
||||
volumeHandle: garm-lvmhdd
|
||||
---
|
||||
kind: PersistentVolumeClaim
|
||||
apiVersion: v1
|
||||
metadata:
|
||||
name: garm-lvmhdd
|
||||
namespace: garm
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
storageClassName: hdd-lvmpv
|
||||
volumeName: garm-lvmhdd
|
||||
51
apps/garm/rbac.yaml
Normal file
51
apps/garm/rbac.yaml
Normal file
@@ -0,0 +1,51 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: garm
|
||||
namespace: garm
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: garm-provider-k8s
|
||||
namespace: garm-runners
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods", "pods/log", "configmaps", "secrets", "events"]
|
||||
verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: garm-provider-k8s
|
||||
namespace: garm-runners
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: garm
|
||||
namespace: garm
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: garm-provider-k8s
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: garm-namespace-manager
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["namespaces"]
|
||||
verbs: ["get"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: garm-namespace-manager
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: garm
|
||||
namespace: garm
|
||||
roleRef:
|
||||
kind: ClusterRole
|
||||
name: garm-namespace-manager
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
32
apps/garm/secret.yaml
Normal file
32
apps/garm/secret.yaml
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultAuth
|
||||
metadata:
|
||||
name: garm
|
||||
namespace: garm
|
||||
spec:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
kubernetes:
|
||||
role: garm
|
||||
serviceAccount: garm
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultStaticSecret
|
||||
metadata:
|
||||
name: garm-config
|
||||
namespace: garm
|
||||
spec:
|
||||
type: kv-v2
|
||||
|
||||
mount: secret
|
||||
path: garm
|
||||
|
||||
destination:
|
||||
create: true
|
||||
name: garm-config
|
||||
type: Opaque
|
||||
transformation:
|
||||
excludeRaw: true
|
||||
|
||||
vaultAuthRef: garm
|
||||
14
apps/garm/service.yaml
Normal file
14
apps/garm/service.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: garm
|
||||
namespace: garm
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: garm
|
||||
ports:
|
||||
- name: http
|
||||
port: 9997
|
||||
targetPort: 9997
|
||||
protocol: TCP
|
||||
@@ -72,6 +72,8 @@ spec:
|
||||
indexer:
|
||||
ISSUE_INDEXER_TYPE: bleve
|
||||
REPO_INDEXER_ENABLED: true
|
||||
webhook:
|
||||
ALLOWED_HOST_LIST: garm.garm.svc.cluster.local
|
||||
admin:
|
||||
username: GiteaAdmin
|
||||
email: gi@tea.com
|
||||
@@ -88,6 +90,11 @@ spec:
|
||||
# Requirement for sharing ip with other service
|
||||
externalTrafficPolicy: Cluster
|
||||
ipFamilyPolicy: RequireDualStack
|
||||
http:
|
||||
type: ClusterIP
|
||||
# We need the service to be at port 80 specifically
|
||||
# to work around bug of Actions Runner
|
||||
port: 80
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
@@ -95,6 +102,7 @@ spec:
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt
|
||||
acme.cert-manager.io/http01-edit-in-place: "true"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "1g"
|
||||
hosts:
|
||||
- host: gitea.lumpiasty.xyz
|
||||
paths:
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: immich
|
||||
version: 1.1.1
|
||||
version: 1.2.2
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: secustor
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- crawl4ai
|
||||
- crawl4ai-proxy
|
||||
- authentik
|
||||
- gitea
|
||||
- renovate
|
||||
- librechat
|
||||
@@ -11,3 +14,4 @@ resources:
|
||||
- searxng
|
||||
- ispeak3
|
||||
- openwebui
|
||||
- garm
|
||||
|
||||
@@ -4,12 +4,16 @@ logToStdout: "both" # proxy and upstream
|
||||
|
||||
macros:
|
||||
base_args: "--no-warmup --port ${PORT}"
|
||||
common_args: "--fit-target 1536 --fit-ctx 65536 --no-warmup --port ${PORT}"
|
||||
common_args: "--fit-target 1536 --no-warmup --port ${PORT}"
|
||||
gemma3_ctx_128k: "--ctx-size 131072"
|
||||
qwen35_ctx_128k: "--ctx-size 131072"
|
||||
qwen35_ctx_256k: "--ctx-size 262144"
|
||||
gemma_sampling: "--prio 2 --temp 1.0 --repeat-penalty 1.0 --min-p 0.00 --top-k 64 --top-p 0.95"
|
||||
qwen35_sampling: "--temp 0.6 --top-p 0.95 --top-k 20 --min-p 0.00 -ctk q4_0 -ctv q4_0"
|
||||
qwen35_35b_args: "--temp 1.0 --min-p 0.00 --top-p 0.95 --top-k 20 -ctk q4_0 -ctv q4_0"
|
||||
qwen35_sampling: "--temp 0.6 --top-p 0.95 --top-k 20 --min-p 0.00 -ctk q8_0 -ctv q8_0"
|
||||
qwen35_35b_args: "--temp 1.0 --min-p 0.00 --top-p 0.95 --top-k 20 -ctk q8_0 -ctv q8_0"
|
||||
qwen35_35b_heretic_mmproj: "--mmproj-url https://huggingface.co/unsloth/Qwen3.5-35B-A3B-GGUF/resolve/main/mmproj-F16.gguf --mmproj /root/.cache/llama.cpp/unsloth_Qwen3.5-35B-A3B-GGUF_mmproj-F16.gguf"
|
||||
qwen35_4b_heretic_mmproj: "--mmproj-url https://huggingface.co/unsloth/Qwen3.5-4B-GGUF/resolve/main/mmproj-F16.gguf --mmproj /root/.cache/llama.cpp/unsloth_Qwen3.5-4B-GGUF_mmproj-F16.gguf"
|
||||
glm47_flash_args: "--temp 0.7 --top-p 1.0 --min-p 0.01 --repeat-penalty 1.0"
|
||||
thinking_on: "--chat-template-kwargs '{\"enable_thinking\": true}'"
|
||||
thinking_off: "--chat-template-kwargs '{\"enable_thinking\": false}'"
|
||||
|
||||
@@ -38,6 +42,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/gemma-3-12b-it-GGUF:Q4_K_M
|
||||
${gemma3_ctx_128k}
|
||||
${gemma_sampling}
|
||||
${common_args}
|
||||
|
||||
@@ -45,6 +50,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/gemma-3-12b-it-GGUF:Q4_K_M
|
||||
${gemma3_ctx_128k}
|
||||
${gemma_sampling}
|
||||
--no-mmproj
|
||||
${common_args}
|
||||
@@ -53,6 +59,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/gemma-3-4b-it-GGUF:Q4_K_M
|
||||
${gemma3_ctx_128k}
|
||||
${gemma_sampling}
|
||||
${common_args}
|
||||
|
||||
@@ -60,6 +67,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/gemma-3-4b-it-GGUF:Q4_K_M
|
||||
${gemma3_ctx_128k}
|
||||
${gemma_sampling}
|
||||
--no-mmproj
|
||||
${common_args}
|
||||
@@ -75,13 +83,14 @@ models:
|
||||
--top-p 0.95
|
||||
--top-k 40
|
||||
--repeat-penalty 1.0
|
||||
-ctk q4_0 -ctv q4_0
|
||||
-ctk q8_0 -ctv q8_0
|
||||
${common_args}
|
||||
|
||||
"Qwen3.5-35B-A3B-GGUF:Q4_K_M":
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_35b_args}
|
||||
${common_args}
|
||||
|
||||
@@ -89,6 +98,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-35B-A3B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_35b_args}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -100,6 +110,7 @@ models:
|
||||
/app/llama-server
|
||||
-hf mradermacher/Qwen3.5-35B-A3B-heretic-GGUF:Q4_K_M
|
||||
${qwen35_35b_heretic_mmproj}
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_35b_args}
|
||||
${common_args}
|
||||
|
||||
@@ -108,6 +119,7 @@ models:
|
||||
/app/llama-server
|
||||
-hf mradermacher/Qwen3.5-35B-A3B-heretic-GGUF:Q4_K_M
|
||||
${qwen35_35b_heretic_mmproj}
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_35b_args}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -116,6 +128,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-0.8B-GGUF:Q4_K_XL
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${base_args}
|
||||
${thinking_on}
|
||||
@@ -133,6 +146,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-2B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_on}
|
||||
@@ -141,6 +155,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-2B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -149,6 +164,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-4B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_128k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_on}
|
||||
@@ -157,6 +173,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-4B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_128k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -166,6 +183,7 @@ models:
|
||||
/app/llama-server
|
||||
-hf mradermacher/Qwen3.5-4B-heretic-GGUF:Q4_K_M
|
||||
${qwen35_4b_heretic_mmproj}
|
||||
${qwen35_ctx_128k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_on}
|
||||
@@ -175,6 +193,7 @@ models:
|
||||
/app/llama-server
|
||||
-hf mradermacher/Qwen3.5-4B-heretic-GGUF:Q4_K_M
|
||||
${qwen35_4b_heretic_mmproj}
|
||||
${qwen35_ctx_128k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -183,6 +202,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-9B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_on}
|
||||
@@ -191,6 +211,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-9B-GGUF:Q4_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -199,6 +220,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-9B-GGUF:Q3_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_on}
|
||||
@@ -207,6 +229,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-9B-GGUF:Q3_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
@@ -215,6 +238,7 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-27B-GGUF:Q3_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_on}
|
||||
@@ -223,6 +247,14 @@ models:
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/Qwen3.5-27B-GGUF:Q3_K_M
|
||||
${qwen35_ctx_256k}
|
||||
${qwen35_sampling}
|
||||
${common_args}
|
||||
${thinking_off}
|
||||
|
||||
"GLM-4.7-Flash-GGUF:Q4_K_M":
|
||||
cmd: |
|
||||
/app/llama-server
|
||||
-hf unsloth/GLM-4.7-Flash-GGUF:Q4_K_M
|
||||
${glm47_flash_args}
|
||||
${common_args}
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
spec:
|
||||
containers:
|
||||
- name: llama-swap
|
||||
image: ghcr.io/mostlygeek/llama-swap:v197-vulkan-b8248
|
||||
image: ghcr.io/mostlygeek/llama-swap:v199-vulkan-b8576
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /app/llama-swap
|
||||
|
||||
@@ -4,5 +4,6 @@ resources:
|
||||
- namespace.yaml
|
||||
- pvc.yaml
|
||||
- pvc-pipelines.yaml
|
||||
- secret.yaml
|
||||
- release.yaml
|
||||
- ingress.yaml
|
||||
|
||||
@@ -18,7 +18,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: open-webui
|
||||
version: 12.10.0
|
||||
version: 12.13.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: open-webui
|
||||
@@ -44,3 +44,30 @@ spec:
|
||||
persistence:
|
||||
enabled: true
|
||||
existingClaim: openwebui-pipelines-lvmhdd
|
||||
|
||||
# SSO with Authentik
|
||||
extraEnvVars:
|
||||
- name: WEBUI_URL
|
||||
value: "https://openwebui.lumpiasty.xyz"
|
||||
- name: OAUTH_CLIENT_ID
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openwebui-authentik
|
||||
key: client_id
|
||||
- name: OAUTH_CLIENT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: openwebui-authentik
|
||||
key: client_secret
|
||||
- name: OAUTH_PROVIDER_NAME
|
||||
value: "authentik"
|
||||
- name: OPENID_PROVIDER_URL
|
||||
value: "https://authentik.lumpiasty.xyz/application/o/open-web-ui/.well-known/openid-configuration"
|
||||
- name: OPENID_REDIRECT_URI
|
||||
value: "https://openwebui.lumpiasty.xyz/oauth/oidc/callback"
|
||||
- name: ENABLE_OAUTH_SIGNUP
|
||||
value: "true"
|
||||
- name: ENABLE_LOGIN_FORM
|
||||
value: "false"
|
||||
- name: OAUTH_MERGE_ACCOUNTS_BY_EMAIL
|
||||
value: "true"
|
||||
|
||||
43
apps/openwebui/secret.yaml
Normal file
43
apps/openwebui/secret.yaml
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: openwebui-secret
|
||||
namespace: openwebui
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultAuth
|
||||
metadata:
|
||||
name: openwebui
|
||||
namespace: openwebui
|
||||
spec:
|
||||
method: kubernetes
|
||||
mount: kubernetes
|
||||
kubernetes:
|
||||
role: openwebui
|
||||
serviceAccount: openwebui-secret
|
||||
---
|
||||
apiVersion: secrets.hashicorp.com/v1beta1
|
||||
kind: VaultStaticSecret
|
||||
metadata:
|
||||
name: openwebui-authentik
|
||||
namespace: openwebui
|
||||
spec:
|
||||
type: kv-v2
|
||||
|
||||
mount: secret
|
||||
path: authentik/openwebui
|
||||
|
||||
destination:
|
||||
create: true
|
||||
name: openwebui-authentik
|
||||
type: Opaque
|
||||
transformation:
|
||||
excludeRaw: true
|
||||
templates:
|
||||
client_id:
|
||||
text: '{{ get .Secrets "client_id" }}'
|
||||
client_secret:
|
||||
text: '{{ get .Secrets "client_secret" }}'
|
||||
|
||||
vaultAuthRef: openwebui
|
||||
@@ -9,3 +9,4 @@ data:
|
||||
RENOVATE_ENDPOINT: https://gitea.lumpiasty.xyz/api/v1
|
||||
RENOVATE_PLATFORM: gitea
|
||||
RENOVATE_GIT_AUTHOR: Renovate Bot <renovate@lumpiasty.xyz>
|
||||
RENOVATE_ALLOWED_COMMANDS: '["^node utils/update-garm-cli-hash\\.mjs$", "^node utils/update-garm-image-pin\\.mjs$"]'
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
- name: renovate
|
||||
# Update this to the latest available and then enable Renovate on
|
||||
# the manifest
|
||||
image: renovate/renovate:43.64.2-full
|
||||
image: renovate/renovate:43.95.0-full
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: renovate-gitea-token
|
||||
|
||||
87
devenv.lock
87
devenv.lock
@@ -3,10 +3,11 @@
|
||||
"devenv": {
|
||||
"locked": {
|
||||
"dir": "src/modules",
|
||||
"lastModified": 1769881431,
|
||||
"lastModified": 1773504385,
|
||||
"narHash": "sha256-ANaeR+xVHxjGz36VI4qlZUbdhrlSE0xU7O7AUJKw3zU=",
|
||||
"owner": "cachix",
|
||||
"repo": "devenv",
|
||||
"rev": "72d5e66e2dd5112766ef4c9565872b51094b542d",
|
||||
"rev": "4bce49e6f60c69e99eeb643efbbf74125cefd329",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -16,27 +17,13 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-compat": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1767039857,
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"repo": "flake-compat",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
@@ -48,47 +35,6 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"git-hooks": {
|
||||
"inputs": {
|
||||
"flake-compat": "flake-compat",
|
||||
"gitignore": "gitignore",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769069492,
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"rev": "a1ef738813b15cf8ec759bdff5761b027e3e1d23",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "cachix",
|
||||
"repo": "git-hooks.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"gitignore": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"git-hooks",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1762808025,
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "gitignore.nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"krew2nix": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
@@ -99,10 +45,11 @@
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769904483,
|
||||
"lastModified": 1773451905,
|
||||
"narHash": "sha256-S/bukFEwbOYQbnR5UpciwYA42aEt1w5LK73GwARhsaA=",
|
||||
"owner": "a1994sc",
|
||||
"repo": "krew2nix",
|
||||
"rev": "17d6ad3375899bd3f7d4d298481536155f3ec13c",
|
||||
"rev": "bc779a8cf59ebf76ae60556bfe2d781a0a4cdbd9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -113,10 +60,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1769461804,
|
||||
"lastModified": 1773389992,
|
||||
"narHash": "sha256-wvfdLLWJ2I9oEpDd9PfMA8osfIZicoQ5MT1jIwNs9Tk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "bfc1b8a4574108ceef22f02bafcf6611380c100d",
|
||||
"rev": "c06b4ae3d6599a672a6210b7021d699c351eebda",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -129,17 +77,14 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"devenv": "devenv",
|
||||
"git-hooks": "git-hooks",
|
||||
"krew2nix": "krew2nix",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"pre-commit-hooks": [
|
||||
"git-hooks"
|
||||
]
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
@@ -154,6 +99,7 @@
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
@@ -173,10 +119,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769691507,
|
||||
"lastModified": 1773297127,
|
||||
"narHash": "sha256-6E/yhXP7Oy/NbXtf1ktzmU8SdVqJQ09HC/48ebEGBpk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "28b19c5844cc6e2257801d43f2772a4b4c050a1b",
|
||||
"rev": "71b125cd05fbfd78cab3e070b73544abe24c5016",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -188,4 +135,4 @@
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
}
|
||||
12
devenv.nix
12
devenv.nix
@@ -4,7 +4,10 @@ let
|
||||
# Python with hvac package
|
||||
python = pkgs.python313.withPackages (python-pkgs: with python-pkgs; [
|
||||
hvac
|
||||
librouteros
|
||||
]);
|
||||
|
||||
garm-cli = pkgs.callPackage ./nix/garm-cli.nix { };
|
||||
in
|
||||
{
|
||||
# Overlays - apply krew2nix to get kubectl with krew support
|
||||
@@ -35,12 +38,14 @@ in
|
||||
openebs
|
||||
browse-pvc
|
||||
]))
|
||||
ansible
|
||||
fluxcd
|
||||
restic
|
||||
openbao
|
||||
pv-migrate
|
||||
mermaid-cli
|
||||
opencode
|
||||
garm-cli
|
||||
tea
|
||||
];
|
||||
|
||||
# Scripts
|
||||
@@ -59,4 +64,9 @@ in
|
||||
echo "Running tests"
|
||||
git --version | grep --color=auto "${pkgs.git.version}"
|
||||
'';
|
||||
|
||||
languages.ansible.enable = true;
|
||||
# TODO: automatically manage collections from ansible/requirements.yml
|
||||
# For now, we need to manually install them with `ansible-galaxy collection install -r ansible/requirements.yml`
|
||||
# This is not implemented in devenv
|
||||
}
|
||||
|
||||
28
docker/garm/Dockerfile
Normal file
28
docker/garm/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
FROM golang:1.26-alpine AS build
|
||||
|
||||
ARG GARM_COMMIT
|
||||
ARG GARM_PROVIDER_K8S_VERSION=0.3.2
|
||||
|
||||
RUN apk add --no-cache ca-certificates git wget tar build-base util-linux-dev linux-headers
|
||||
|
||||
WORKDIR /src
|
||||
RUN git clone https://github.com/cloudbase/garm.git . && git checkout "${GARM_COMMIT}"
|
||||
|
||||
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 \
|
||||
go build -trimpath \
|
||||
-tags osusergo,netgo,sqlite_omit_load_extension \
|
||||
-ldflags="-linkmode external -extldflags '-static' -s -w" \
|
||||
-o /out/garm ./cmd/garm
|
||||
|
||||
RUN mkdir -p /out/providers.d \
|
||||
&& wget -qO /tmp/garm-provider-k8s.tar.gz "https://github.com/mercedes-benz/garm-provider-k8s/releases/download/v${GARM_PROVIDER_K8S_VERSION}/garm-provider-k8s_Linux_x86_64.tar.gz" \
|
||||
&& tar -xzf /tmp/garm-provider-k8s.tar.gz -C /out/providers.d \
|
||||
&& chmod 0755 /out/providers.d/garm-provider-k8s
|
||||
|
||||
FROM busybox
|
||||
|
||||
COPY --from=build /out/garm /bin/garm
|
||||
COPY --from=build /out/providers.d/garm-provider-k8s /opt/garm/providers.d/garm-provider-k8s
|
||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||
|
||||
ENTRYPOINT ["/bin/garm"]
|
||||
1
docs/assets/ansible.svg
Normal file
1
docs/assets/ansible.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill="#1A1918" d="M126 64c0 34.2-27.8 62-62 62S2 98.2 2 64 29.8 2 64 2s62 27.8 62 62"/><path fill="#FFF" d="M65 39.9l16 39.6-24.1-19.1L65 39.9zm28.5 48.7L68.9 29.2c-.7-1.7-2.1-2.6-3.8-2.6-1.7 0-3.2.9-3.9 2.6L34 94.3h9.3L54 67.5l32 25.9c1.3 1 2.2 1.5 3.4 1.5 2.4 0 4.5-1.8 4.5-4.4.1-.5-.1-1.2-.4-1.9z"/></svg>
|
||||
|
After Width: | Height: | Size: 377 B |
16
docs/assets/devenv.svg
Normal file
16
docs/assets/devenv.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<svg width="480" height="480" viewBox="0 0 480 480" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M245.308 31V110.692H325V31L245.308 31Z" fill="#425C82"/>
|
||||
<path d="M334.962 120.654V200.346H414.654V120.654H334.962Z" fill="#425C82"/>
|
||||
<path d="M245.308 120.654V200.346H325V120.654H245.308Z" fill="#425C82"/>
|
||||
<path d="M334.962 210.308V290H414.654V210.308H334.962Z" fill="#425C82"/>
|
||||
<path d="M245.308 210.308V290H325V210.308H245.308Z" fill="#101010"/>
|
||||
<path d="M155.654 210.308V290H235.346V210.308H155.654Z" fill="#101010"/>
|
||||
<path d="M66 210.308V290H145.692V210.308H66Z" fill="#101010"/>
|
||||
<path d="M155.654 120.654V200.346H235.346V120.654H155.654Z" fill="#101010"/>
|
||||
<path d="M104.25 416H100.125L93.5 406.812C91.875 408.271 90.1458 409.646 88.3125 410.938C86.5208 412.188 84.625 413.292 82.625 414.25C80.625 415.167 78.5625 415.896 76.4375 416.438C74.3542 416.979 72.2292 417.25 70.0625 417.25C65.3542 417.25 60.9167 416.375 56.75 414.625C52.625 412.833 49 410.375 45.875 407.25C42.7917 404.083 40.3542 400.354 38.5625 396.062C36.7708 391.729 35.875 387.021 35.875 381.938C35.875 376.896 36.7708 372.208 38.5625 367.875C40.3542 363.542 42.7917 359.792 45.875 356.625C49 353.458 52.625 350.979 56.75 349.188C60.9167 347.396 65.3542 346.5 70.0625 346.5C71.5625 346.5 73.1042 346.625 74.6875 346.875C76.3125 347.125 77.875 347.542 79.375 348.125C80.9167 348.667 82.3542 349.396 83.6875 350.312C85.0208 351.229 86.1458 352.354 87.0625 353.688V322.438H104.25V416ZM87.0625 381.938C87.0625 379.604 86.6042 377.354 85.6875 375.188C84.8125 372.979 83.6042 371.042 82.0625 369.375C80.5208 367.667 78.7083 366.312 76.625 365.312C74.5833 364.271 72.3958 363.75 70.0625 363.75C67.7292 363.75 65.5208 364.167 63.4375 365C61.3958 365.833 59.6042 367.042 58.0625 368.625C56.5625 370.167 55.375 372.062 54.5 374.312C53.625 376.562 53.1875 379.104 53.1875 381.938C53.1875 384.396 53.625 386.729 54.5 388.938C55.375 391.146 56.5625 393.083 58.0625 394.75C59.6042 396.417 61.3958 397.729 63.4375 398.688C65.5208 399.646 67.7292 400.125 70.0625 400.125C72.3958 400.125 74.5833 399.625 76.625 398.625C78.7083 397.583 80.5208 396.229 82.0625 394.562C83.6042 392.854 84.8125 390.917 85.6875 388.75C86.6042 386.542 87.0625 384.271 87.0625 381.938Z" fill="#101010"/>
|
||||
<path d="M143.938 399.625C144.604 399.833 145.271 399.979 145.938 400.062C146.604 400.104 147.271 400.125 147.938 400.125C149.604 400.125 151.208 399.896 152.75 399.438C154.292 398.979 155.729 398.333 157.062 397.5C158.438 396.625 159.646 395.583 160.688 394.375C161.771 393.125 162.646 391.75 163.312 390.25L175.812 402.812C174.229 405.062 172.396 407.083 170.312 408.875C168.271 410.667 166.042 412.188 163.625 413.438C161.25 414.688 158.729 415.625 156.062 416.25C153.438 416.917 150.729 417.25 147.938 417.25C143.229 417.25 138.792 416.375 134.625 414.625C130.5 412.875 126.875 410.438 123.75 407.312C120.667 404.188 118.229 400.479 116.438 396.188C114.646 391.854 113.75 387.104 113.75 381.938C113.75 376.646 114.646 371.812 116.438 367.438C118.229 363.062 120.667 359.333 123.75 356.25C126.875 353.167 130.5 350.771 134.625 349.062C138.792 347.354 143.229 346.5 147.938 346.5C150.729 346.5 153.458 346.833 156.125 347.5C158.792 348.167 161.312 349.125 163.688 350.375C166.104 351.625 168.354 353.167 170.438 355C172.521 356.792 174.354 358.812 175.938 361.062L143.938 399.625ZM152.688 364.438C151.896 364.146 151.104 363.958 150.312 363.875C149.562 363.792 148.771 363.75 147.938 363.75C145.604 363.75 143.396 364.188 141.312 365.062C139.271 365.896 137.479 367.104 135.938 368.688C134.438 370.271 133.25 372.188 132.375 374.438C131.5 376.646 131.062 379.146 131.062 381.938C131.062 382.562 131.083 383.271 131.125 384.062C131.208 384.854 131.312 385.667 131.438 386.5C131.604 387.292 131.792 388.062 132 388.812C132.208 389.562 132.479 390.229 132.812 390.812L152.688 364.438Z" fill="#101010"/>
|
||||
<path d="M202.688 416L177.188 349.062H196.625L211.25 390.812L225.812 349.062H245.312L219.812 416H202.688Z" fill="#101010"/>
|
||||
<path d="M276.438 399.625C277.104 399.833 277.771 399.979 278.438 400.062C279.104 400.104 279.771 400.125 280.438 400.125C282.104 400.125 283.708 399.896 285.25 399.438C286.792 398.979 288.229 398.333 289.562 397.5C290.938 396.625 292.146 395.583 293.188 394.375C294.271 393.125 295.146 391.75 295.812 390.25L308.312 402.812C306.729 405.062 304.896 407.083 302.812 408.875C300.771 410.667 298.542 412.188 296.125 413.438C293.75 414.688 291.229 415.625 288.562 416.25C285.938 416.917 283.229 417.25 280.438 417.25C275.729 417.25 271.292 416.375 267.125 414.625C263 412.875 259.375 410.438 256.25 407.312C253.167 404.188 250.729 400.479 248.938 396.188C247.146 391.854 246.25 387.104 246.25 381.938C246.25 376.646 247.146 371.812 248.938 367.438C250.729 363.062 253.167 359.333 256.25 356.25C259.375 353.167 263 350.771 267.125 349.062C271.292 347.354 275.729 346.5 280.438 346.5C283.229 346.5 285.958 346.833 288.625 347.5C291.292 348.167 293.812 349.125 296.188 350.375C298.604 351.625 300.854 353.167 302.938 355C305.021 356.792 306.854 358.812 308.438 361.062L276.438 399.625ZM285.188 364.438C284.396 364.146 283.604 363.958 282.812 363.875C282.062 363.792 281.271 363.75 280.438 363.75C278.104 363.75 275.896 364.188 273.812 365.062C271.771 365.896 269.979 367.104 268.438 368.688C266.938 370.271 265.75 372.188 264.875 374.438C264 376.646 263.562 379.146 263.562 381.938C263.562 382.562 263.583 383.271 263.625 384.062C263.708 384.854 263.812 385.667 263.938 386.5C264.104 387.292 264.292 388.062 264.5 388.812C264.708 389.562 264.979 390.229 265.312 390.812L285.188 364.438Z" fill="#101010"/>
|
||||
<path d="M334.125 416H317.062V349.062H321.188L326.812 355.562C329.562 353.062 332.667 351.146 336.125 349.812C339.625 348.438 343.271 347.75 347.062 347.75C351.146 347.75 355 348.542 358.625 350.125C362.25 351.667 365.417 353.812 368.125 356.562C370.833 359.271 372.958 362.458 374.5 366.125C376.083 369.75 376.875 373.625 376.875 377.75V416H359.812V377.75C359.812 376 359.479 374.354 358.812 372.812C358.146 371.229 357.229 369.854 356.062 368.688C354.896 367.521 353.542 366.604 352 365.938C350.458 365.271 348.812 364.938 347.062 364.938C345.271 364.938 343.583 365.271 342 365.938C340.417 366.604 339.042 367.521 337.875 368.688C336.708 369.854 335.792 371.229 335.125 372.812C334.458 374.354 334.125 376 334.125 377.75V416Z" fill="#101010"/>
|
||||
<path d="M405.312 416L379.812 349.062H399.25L413.875 390.812L428.438 349.062H447.938L422.438 416H405.312Z" fill="#101010"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.3 KiB |
1
docs/assets/mikrotik.svg
Normal file
1
docs/assets/mikrotik.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="610" height="610" version="1.2"><path fill-rule="evenodd" d="M586.8 193.4v222.5c0 13.8-1.7 25.6-5.5 34.3-.7 1.6-1.5 3.2-2.3 4.7-5.5 8.9-16.6 17.7-31.6 25.9L344.4 592c-12.6 6.9-24.2 11.4-34 12.7q-2.8.4-5.4.4-2.7 0-5.5-.4c-9.8-1.3-21.4-5.8-34-12.7L164 536.4 62.6 480.8c-15.1-8.2-26.2-17-31.6-25.9-5.5-9-7.9-22.5-7.9-39V193.4c0-13.8 1.7-25.5 5.5-34.2.7-1.7 1.5-3.3 2.4-4.7q1.3-2.2 3-4.3c6.1-7.5 16-14.7 28.6-21.7L164 72.9l101.5-55.6c15-8.2 28.6-13 39.5-13q2.6 0 5.4.4c9.8 1.2 21.4 5.7 34 12.6l101.5 55.6 101.5 55.6c12.6 7 22.4 14.2 28.5 21.7q1.8 2.1 3.1 4.3c.8 1.4 1.6 3 2.3 4.7 3.8 8.7 5.5 20.4 5.5 34.2m-102.5 33.2c0-9.8-5.3-18.8-13.8-23.4l-152.7-83.7c-8-4.4-17.7-4.4-25.7 0l-38.9 21.3c-4.6 2.6-4.6 9.2 0 11.7l116.4 63.8c4.6 2.6 4.6 9.2 0 11.7l-51.8 28.4c-8 4.4-17.7 4.4-25.7 0l-112-61.4c-8-4.4-17.7-4.4-25.7 0l-14.9 8.2c-8.6 4.7-13.9 13.6-13.9 23.4v7l135.5 74.3c8.6 4.6 13.9 13.6 13.9 23.3v141.4c0 4.8 2.6 9.3 6.9 11.7l10.2 5.6c8 4.4 17.7 4.4 25.7 0l10.3-5.6c4.2-2.4 6.9-6.9 6.9-11.7V331.2c0-9.7 5.3-18.7 13.9-23.3l65.5-36c4.5-2.4 9.9.8 9.9 5.9v142.4c0 5.1 5.4 8.3 9.9 5.9l36.3-19.9c8.5-4.7 13.8-13.7 13.8-23.4zm-298.7 78.2c0-4.8-2.6-9.3-6.9-11.7l-43.2-23.7c-4.5-2.4-9.9.8-9.9 5.9v107.5c0 9.7 5.3 18.7 13.9 23.4l36.3 19.9c4.4 2.4 9.8-.8 9.8-5.9z" style="fill:#263037"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
docs/assets/talos.svg
Normal file
1
docs/assets/talos.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><defs><linearGradient id="a" x1="101.85" x2="101.85" y1="-12.91" y2="224.04" gradientTransform="translate(6.318) scale(.56637)" gradientUnits="userSpaceOnUse"><stop offset="0" style="stop-color:#ffd200;stop-opacity:1"/><stop offset=".08" style="stop-color:#ffb500;stop-opacity:1"/><stop offset=".2" style="stop-color:#ff8c00;stop-opacity:1"/><stop offset=".3" style="stop-color:#ff7300;stop-opacity:1"/><stop offset=".36" style="stop-color:#ff6a00;stop-opacity:1"/><stop offset=".48" style="stop-color:#fc4f0e;stop-opacity:1"/><stop offset=".65" style="stop-color:#f92f1e;stop-opacity:1"/><stop offset=".79" style="stop-color:#f81b27;stop-opacity:1"/><stop offset=".89" style="stop-color:#f7142b;stop-opacity:1"/><stop offset="1" style="stop-color:#df162e;stop-opacity:1"/></linearGradient></defs><path d="M64.012 0c-1.617 0-3.227.078-4.825.2v127.624c1.594.117 3.196.2 4.817.203h.023c1.614 0 3.211-.086 4.79-.199V.2A62.973 62.973 0 0 0 64.011 0Zm27.515 6.23C82.16 27.34 76.36 49.79 76.36 63.926c0 13.68 6.282 36.379 15.82 57.601a64.209 64.209 0 0 0 8.407-4.968c-8.832-19.864-14.59-40.625-14.59-52.657 0-12.457 5.555-33.175 14.113-52.757a64.114 64.114 0 0 0-8.582-4.915Zm-55.191.04a64.114 64.114 0 0 0-8.457 4.894C36.453 30.73 42 51.454 42 63.902c0 12.028-5.762 32.782-14.59 52.625a64.62 64.62 0 0 0 8.406 4.965c9.547-21.215 15.813-43.898 15.813-57.59 0-14.207-6.031-36.785-15.293-57.632Zm80.207 21.16-1.379 1.375c-14.613 14.754-21.676 26.246-21.59 35.117.086 8.867 7.106 20.324 21.489 34.789l1.652 1.644a64.618 64.618 0 0 0 4.996-8.601c-9.953-10.102-18.441-21.008-18.508-27.918-.062-6.738 8.375-17.645 18.383-27.856a63.693 63.693 0 0 0-5.043-8.55Zm-105.059.023a64.028 64.028 0 0 0-5.054 8.574c16.664 16.93 18.418 25.094 18.398 27.832-.062 6.938-8.555 17.817-18.512 27.895a64.432 64.432 0 0 0 5.008 8.578c.567-.566 1.106-1.086 1.653-1.64 14.37-14.465 21.394-25.833 21.476-34.766.086-8.93-6.976-20.317-21.586-35.098Zm0 0" style="fill:url(#a)"/></svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -18,7 +18,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: cert-manager-webhook-ovh
|
||||
version: 0.9.3
|
||||
version: 0.9.5
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cert-manager-webhook-ovh
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: cert-manager
|
||||
version: v1.19.4
|
||||
version: v1.20.1
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cert-manager
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: cilium
|
||||
version: 1.19.1
|
||||
version: 1.19.2
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cilium
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: cloudnative-pg
|
||||
version: 0.27.1
|
||||
version: 0.28.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: cnpg
|
||||
|
||||
@@ -110,7 +110,7 @@ spec:
|
||||
kubernetes.io/os: linux
|
||||
containers:
|
||||
- name: coredns
|
||||
image: registry.k8s.io/coredns/coredns:v1.14.1
|
||||
image: registry.k8s.io/coredns/coredns:v1.14.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
args: ["-conf", "/etc/coredns/Corefile"]
|
||||
ports:
|
||||
|
||||
@@ -97,7 +97,7 @@ spec:
|
||||
env:
|
||||
- name: GOMEMLIMIT
|
||||
value: 161MiB
|
||||
image: registry.k8s.io/coredns/coredns:v1.14.1
|
||||
image: registry.k8s.io/coredns/coredns:v1.14.2
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
failureThreshold: 5
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: k8up
|
||||
version: 4.8.6
|
||||
version: 4.9.0
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: k8up-io
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: ingress-nginx
|
||||
version: 4.15.0
|
||||
version: 4.15.1
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: ingress-nginx
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
chart:
|
||||
spec:
|
||||
chart: openbao
|
||||
version: 0.25.6
|
||||
version: 0.26.2
|
||||
sourceRef:
|
||||
kind: HelmRepository
|
||||
name: openbao
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: tavily
|
||||
namespace: gpt-researcher
|
||||
stringData:
|
||||
TAVILY_API_KEY: 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
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: openrouter
|
||||
namespace: gpt-researcher
|
||||
stringData:
|
||||
OPENROUTER_API_KEY: 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
|
||||
45
nix/garm-cli.nix
Normal file
45
nix/garm-cli.nix
Normal file
@@ -0,0 +1,45 @@
|
||||
{ lib, buildGoModule, fetchFromGitHub, installShellFiles }:
|
||||
|
||||
buildGoModule rec {
|
||||
pname = "garm-cli";
|
||||
version = "r1380";
|
||||
garmCommit = "818a9dddccba5f2843f185e6a846770988f31fc5";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "cloudbase";
|
||||
repo = "garm";
|
||||
rev = garmCommit;
|
||||
hash = "sha256-CTqqabNYUMSrmnQVCWml1/vkDw+OP1uJo1KFhBSZpYY=";
|
||||
};
|
||||
|
||||
subPackages = [ "cmd/garm-cli" ];
|
||||
|
||||
nativeBuildInputs = [ installShellFiles ];
|
||||
|
||||
vendorHash = null;
|
||||
|
||||
ldflags = [
|
||||
"-s"
|
||||
"-w"
|
||||
"-X main.version=${version}"
|
||||
];
|
||||
|
||||
postInstall = ''
|
||||
# We need to set a temporary HOME for the completion scripts as workaround
|
||||
# because garm-cli tries to write config to the home directory
|
||||
# when generating the completion scripts
|
||||
export HOME="$(mktemp -d)"
|
||||
|
||||
installShellCompletion --cmd garm-cli \
|
||||
--bash <($out/bin/garm-cli completion bash) \
|
||||
--fish <($out/bin/garm-cli completion fish) \
|
||||
--zsh <($out/bin/garm-cli completion zsh)
|
||||
'';
|
||||
|
||||
meta = {
|
||||
description = "CLI for GitHub Actions Runner Manager";
|
||||
homepage = "https://github.com/cloudbase/garm";
|
||||
license = lib.licenses.asl20;
|
||||
mainProgram = "garm-cli";
|
||||
};
|
||||
}
|
||||
@@ -10,8 +10,57 @@
|
||||
"gotk-components\\.ya?ml$"
|
||||
]
|
||||
},
|
||||
"customManagers": [
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track garm-cli pinned main commit",
|
||||
"managerFilePatterns": ["^nix/garm-cli\\.nix$"],
|
||||
"matchStrings": ["garmCommit = \\\"(?<currentValue>[a-f0-9]{40})\\\";"],
|
||||
"depNameTemplate": "cloudbase/garm",
|
||||
"datasourceTemplate": "github-refs",
|
||||
"versioningTemplate": "git"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track garm-provider-k8s release in garm image Dockerfile",
|
||||
"managerFilePatterns": ["^docker/garm/Dockerfile$"],
|
||||
"matchStrings": ["ARG GARM_PROVIDER_K8S_VERSION=(?<currentValue>[0-9]+\\.[0-9]+\\.[0-9]+)"],
|
||||
"depNameTemplate": "mercedes-benz/garm-provider-k8s",
|
||||
"datasourceTemplate": "github-releases",
|
||||
"versioningTemplate": "semver"
|
||||
},
|
||||
{
|
||||
"customType": "regex",
|
||||
"description": "Track pinned garm main commit",
|
||||
"managerFilePatterns": ["^apps/garm/image-source\\.env$"],
|
||||
"matchStrings": ["GARM_COMMIT=(?<currentValue>[a-f0-9]{40})"],
|
||||
"depNameTemplate": "cloudbase/garm",
|
||||
"datasourceTemplate": "github-refs",
|
||||
"versioningTemplate": "git"
|
||||
}
|
||||
],
|
||||
"prHourlyLimit": 9,
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["custom.regex"],
|
||||
"matchDepNames": ["cloudbase/garm"],
|
||||
"matchFileNames": ["nix/garm-cli.nix"],
|
||||
"postUpgradeTasks": {
|
||||
"commands": ["node utils/update-garm-cli-hash.mjs"],
|
||||
"fileFilters": ["nix/garm-cli.nix"],
|
||||
"executionMode": "update"
|
||||
}
|
||||
},
|
||||
{
|
||||
"matchManagers": ["custom.regex"],
|
||||
"matchDepNames": ["cloudbase/garm"],
|
||||
"matchFileNames": ["apps/garm/image-source.env"],
|
||||
"postUpgradeTasks": {
|
||||
"commands": ["node utils/update-garm-image-pin.mjs"],
|
||||
"fileFilters": ["apps/garm/image-source.env", "apps/garm/deployment.yaml"],
|
||||
"executionMode": "update"
|
||||
}
|
||||
},
|
||||
{
|
||||
"matchDatasources": ["docker"],
|
||||
"matchPackageNames": ["ghcr.io/mostlygeek/llama-swap"],
|
||||
|
||||
320
utils/update-garm-cli-hash.mjs
Normal file
320
utils/update-garm-cli-hash.mjs
Normal file
@@ -0,0 +1,320 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import { Buffer } from "node:buffer";
|
||||
import fs from "node:fs";
|
||||
import https from "node:https";
|
||||
import zlib from "node:zlib";
|
||||
|
||||
const nixFile = "nix/garm-cli.nix";
|
||||
|
||||
function die(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function readText(filePath) {
|
||||
try {
|
||||
return fs.readFileSync(filePath, "utf8");
|
||||
} catch {
|
||||
die(`Missing ${filePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function extractVersion(text) {
|
||||
const match = text.match(/^\s*version\s*=\s*"([^"]+)";/m);
|
||||
if (!match) {
|
||||
die(`Unable to extract version from ${nixFile}`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
|
||||
function extractCommit(text) {
|
||||
const match = text.match(/^\s*garmCommit\s*=\s*"([a-f0-9]{40})";/m);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
function writeU64LE(hash, value) {
|
||||
const buf = Buffer.alloc(8);
|
||||
buf.writeBigUInt64LE(BigInt(value), 0);
|
||||
hash.update(buf);
|
||||
}
|
||||
|
||||
function writeNarString(hash, data) {
|
||||
writeU64LE(hash, data.length);
|
||||
hash.update(data);
|
||||
const pad = (8 - (data.length % 8)) % 8;
|
||||
if (pad) {
|
||||
hash.update(Buffer.alloc(pad));
|
||||
}
|
||||
}
|
||||
|
||||
function writeNarText(hash, text) {
|
||||
writeNarString(hash, Buffer.from(text, "utf8"));
|
||||
}
|
||||
|
||||
function parseOctal(field) {
|
||||
const clean = field.toString("ascii").replace(/\0.*$/, "").trim();
|
||||
if (!clean) {
|
||||
return 0;
|
||||
}
|
||||
return Number.parseInt(clean, 8);
|
||||
}
|
||||
|
||||
function parseTarHeader(block) {
|
||||
const name = block.subarray(0, 100).toString("utf8").replace(/\0.*$/, "");
|
||||
const mode = parseOctal(block.subarray(100, 108));
|
||||
const size = parseOctal(block.subarray(124, 136));
|
||||
const typeflagRaw = block[156];
|
||||
const typeflag = typeflagRaw === 0 ? "0" : String.fromCharCode(typeflagRaw);
|
||||
const linkname = block.subarray(157, 257).toString("utf8").replace(/\0.*$/, "");
|
||||
const prefix = block.subarray(345, 500).toString("utf8").replace(/\0.*$/, "");
|
||||
return {
|
||||
name: prefix ? `${prefix}/${name}` : name,
|
||||
mode,
|
||||
size,
|
||||
typeflag,
|
||||
linkname,
|
||||
};
|
||||
}
|
||||
|
||||
function parsePax(data) {
|
||||
const out = {};
|
||||
let i = 0;
|
||||
while (i < data.length) {
|
||||
let sp = i;
|
||||
while (sp < data.length && data[sp] !== 0x20) sp += 1;
|
||||
if (sp >= data.length) break;
|
||||
const len = Number.parseInt(data.subarray(i, sp).toString("utf8"), 10);
|
||||
if (!Number.isFinite(len) || len <= 0) break;
|
||||
const record = data.subarray(sp + 1, i + len).toString("utf8");
|
||||
const eq = record.indexOf("=");
|
||||
if (eq > 0) {
|
||||
const key = record.slice(0, eq);
|
||||
const value = record.slice(eq + 1).replace(/\n$/, "");
|
||||
out[key] = value;
|
||||
}
|
||||
i += len;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function parseTarEntries(archiveBuffer) {
|
||||
const gz = zlib.gunzipSync(archiveBuffer);
|
||||
const entries = [];
|
||||
let i = 0;
|
||||
let pendingPax = null;
|
||||
let longName = null;
|
||||
let longLink = null;
|
||||
|
||||
while (i + 512 <= gz.length) {
|
||||
const header = gz.subarray(i, i + 512);
|
||||
i += 512;
|
||||
|
||||
if (header.every((b) => b === 0)) {
|
||||
break;
|
||||
}
|
||||
|
||||
const h = parseTarHeader(header);
|
||||
const data = gz.subarray(i, i + h.size);
|
||||
const dataPad = (512 - (h.size % 512)) % 512;
|
||||
i += h.size + dataPad;
|
||||
|
||||
if (h.typeflag === "x") {
|
||||
pendingPax = parsePax(data);
|
||||
continue;
|
||||
}
|
||||
if (h.typeflag === "g") {
|
||||
continue;
|
||||
}
|
||||
if (h.typeflag === "L") {
|
||||
longName = data.toString("utf8").replace(/\0.*$/, "");
|
||||
continue;
|
||||
}
|
||||
if (h.typeflag === "K") {
|
||||
longLink = data.toString("utf8").replace(/\0.*$/, "");
|
||||
continue;
|
||||
}
|
||||
|
||||
const path = pendingPax?.path ?? longName ?? h.name;
|
||||
const linkpath = pendingPax?.linkpath ?? longLink ?? h.linkname;
|
||||
|
||||
entries.push({
|
||||
path,
|
||||
typeflag: h.typeflag,
|
||||
mode: h.mode,
|
||||
linkname: linkpath,
|
||||
data,
|
||||
});
|
||||
|
||||
pendingPax = null;
|
||||
longName = null;
|
||||
longLink = null;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function stripTopDir(path) {
|
||||
const cleaned = path.replace(/^\.?\//, "").replace(/\/$/, "");
|
||||
const idx = cleaned.indexOf("/");
|
||||
if (idx === -1) return "";
|
||||
return cleaned.slice(idx + 1);
|
||||
}
|
||||
|
||||
function ensureDir(root, relPath) {
|
||||
if (!relPath) return root;
|
||||
const parts = relPath.split("/").filter(Boolean);
|
||||
let cur = root;
|
||||
for (const part of parts) {
|
||||
let child = cur.children.get(part);
|
||||
if (!child) {
|
||||
child = { kind: "directory", children: new Map() };
|
||||
cur.children.set(part, child);
|
||||
}
|
||||
if (child.kind !== "directory") {
|
||||
die(`Path conflict while building tree at ${relPath}`);
|
||||
}
|
||||
cur = child;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
function buildTree(entries) {
|
||||
const root = { kind: "directory", children: new Map() };
|
||||
for (const entry of entries) {
|
||||
const rel = stripTopDir(entry.path);
|
||||
if (!rel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const parts = rel.split("/").filter(Boolean);
|
||||
const name = parts.pop();
|
||||
const parent = ensureDir(root, parts.join("/"));
|
||||
|
||||
if (entry.typeflag === "5") {
|
||||
const existing = parent.children.get(name);
|
||||
if (!existing) {
|
||||
parent.children.set(name, { kind: "directory", children: new Map() });
|
||||
} else if (existing.kind !== "directory") {
|
||||
die(`Path conflict at ${rel}`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.typeflag === "2") {
|
||||
parent.children.set(name, { kind: "symlink", target: entry.linkname });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.typeflag === "0") {
|
||||
parent.children.set(name, {
|
||||
kind: "regular",
|
||||
executable: (entry.mode & 0o111) !== 0,
|
||||
contents: Buffer.from(entry.data),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
function compareUtf8(a, b) {
|
||||
return Buffer.from(a, "utf8").compare(Buffer.from(b, "utf8"));
|
||||
}
|
||||
|
||||
function narDump(hash, node) {
|
||||
if (node.kind === "directory") {
|
||||
writeNarText(hash, "(");
|
||||
writeNarText(hash, "type");
|
||||
writeNarText(hash, "directory");
|
||||
const names = [...node.children.keys()].sort(compareUtf8);
|
||||
for (const name of names) {
|
||||
writeNarText(hash, "entry");
|
||||
writeNarText(hash, "(");
|
||||
writeNarText(hash, "name");
|
||||
writeNarString(hash, Buffer.from(name, "utf8"));
|
||||
writeNarText(hash, "node");
|
||||
narDump(hash, node.children.get(name));
|
||||
writeNarText(hash, ")");
|
||||
}
|
||||
writeNarText(hash, ")");
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.kind === "symlink") {
|
||||
writeNarText(hash, "(");
|
||||
writeNarText(hash, "type");
|
||||
writeNarText(hash, "symlink");
|
||||
writeNarText(hash, "target");
|
||||
writeNarString(hash, Buffer.from(node.target, "utf8"));
|
||||
writeNarText(hash, ")");
|
||||
return;
|
||||
}
|
||||
|
||||
writeNarText(hash, "(");
|
||||
writeNarText(hash, "type");
|
||||
writeNarText(hash, "regular");
|
||||
if (node.executable) {
|
||||
writeNarText(hash, "executable");
|
||||
writeNarText(hash, "");
|
||||
}
|
||||
writeNarText(hash, "contents");
|
||||
writeNarString(hash, node.contents);
|
||||
writeNarText(hash, ")");
|
||||
}
|
||||
|
||||
function fetchBuffer(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
https
|
||||
.get(url, (res) => {
|
||||
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
||||
const redirectUrl = new URL(res.headers.location, url).toString();
|
||||
res.resume();
|
||||
fetchBuffer(redirectUrl).then(resolve, reject);
|
||||
return;
|
||||
}
|
||||
if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
|
||||
reject(new Error(`Failed to fetch ${url}: ${res.statusCode ?? "unknown"}`));
|
||||
res.resume();
|
||||
return;
|
||||
}
|
||||
const chunks = [];
|
||||
res.on("data", (chunk) => chunks.push(chunk));
|
||||
res.on("end", () => resolve(Buffer.concat(chunks)));
|
||||
})
|
||||
.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
function computeSRIFromGitHubTar(ref) {
|
||||
const url = `https://github.com/cloudbase/garm/archive/${ref}.tar.gz`;
|
||||
return fetchBuffer(url).then((archive) => {
|
||||
const entries = parseTarEntries(archive);
|
||||
const root = buildTree(entries);
|
||||
const hash = createHash("sha256");
|
||||
writeNarText(hash, "nix-archive-1");
|
||||
narDump(hash, root);
|
||||
return `sha256-${hash.digest("base64")}`;
|
||||
});
|
||||
}
|
||||
|
||||
function updateHash(text, sri) {
|
||||
const pattern = /(^\s*hash\s*=\s*")sha256-[^"]+(";)/m;
|
||||
if (!pattern.test(text)) {
|
||||
die(`Unable to update hash in ${nixFile}`);
|
||||
}
|
||||
const next = text.replace(pattern, `$1${sri}$2`);
|
||||
return next;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const text = readText(nixFile);
|
||||
const version = extractVersion(text);
|
||||
const commit = extractCommit(text);
|
||||
const ref = commit ?? `v${version}`;
|
||||
const sri = await computeSRIFromGitHubTar(ref);
|
||||
const updated = updateHash(text, sri);
|
||||
fs.writeFileSync(nixFile, updated, "utf8");
|
||||
console.log(`Updated ${nixFile} hash to ${sri}`);
|
||||
}
|
||||
|
||||
main().catch((err) => die(err.message));
|
||||
91
utils/update-garm-image-pin.mjs
Normal file
91
utils/update-garm-image-pin.mjs
Normal file
@@ -0,0 +1,91 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { execFileSync } from "node:child_process";
|
||||
|
||||
const pinFile = "apps/garm/image-source.env";
|
||||
const deploymentFile = "apps/garm/deployment.yaml";
|
||||
|
||||
function fail(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function parseEnvFile(content) {
|
||||
const vars = {};
|
||||
for (const line of content.split(/\r?\n/)) {
|
||||
if (!line || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
const idx = line.indexOf("=");
|
||||
if (idx === -1) {
|
||||
continue;
|
||||
}
|
||||
const key = line.slice(0, idx).trim();
|
||||
const value = line.slice(idx + 1).trim();
|
||||
vars[key] = value;
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
|
||||
function updateOrAdd(content, key, value) {
|
||||
const pattern = new RegExp(`^${key}=.*$`, "m");
|
||||
if (pattern.test(content)) {
|
||||
return content.replace(pattern, `${key}=${value}`);
|
||||
}
|
||||
return `${content.trimEnd()}\n${key}=${value}\n`;
|
||||
}
|
||||
|
||||
function gitOut(args, options = {}) {
|
||||
return execFileSync("git", args, {
|
||||
encoding: "utf8",
|
||||
...options,
|
||||
}).trim();
|
||||
}
|
||||
|
||||
function gitRun(args, options = {}) {
|
||||
execFileSync("git", args, options);
|
||||
}
|
||||
|
||||
const pinContent = fs.readFileSync(pinFile, "utf8");
|
||||
const vars = parseEnvFile(pinContent);
|
||||
const commit = vars.GARM_COMMIT;
|
||||
const imageRepo = vars.GARM_IMAGE_REPO || "gitea.lumpiasty.xyz/lumpiasty/garm-k8s";
|
||||
|
||||
if (!commit || !/^[0-9a-f]{40}$/.test(commit)) {
|
||||
fail(`Invalid or missing GARM_COMMIT in ${pinFile}`);
|
||||
}
|
||||
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "garm-main-"));
|
||||
let commitNumber;
|
||||
try {
|
||||
gitRun(["clone", "--filter=blob:none", "https://github.com/cloudbase/garm.git", tmpDir], {
|
||||
stdio: "ignore",
|
||||
});
|
||||
commitNumber = gitOut(["-C", tmpDir, "rev-list", "--count", commit]);
|
||||
} finally {
|
||||
fs.rmSync(tmpDir, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
if (!/^\d+$/.test(commitNumber)) {
|
||||
fail(`Unable to resolve commit number for ${commit}`);
|
||||
}
|
||||
|
||||
const image = `${imageRepo}:r${commitNumber}`;
|
||||
|
||||
let nextPin = pinContent;
|
||||
nextPin = updateOrAdd(nextPin, "GARM_COMMIT_NUMBER", commitNumber);
|
||||
nextPin = updateOrAdd(nextPin, "GARM_IMAGE_REPO", imageRepo);
|
||||
nextPin = updateOrAdd(nextPin, "GARM_IMAGE", image);
|
||||
fs.writeFileSync(pinFile, nextPin, "utf8");
|
||||
|
||||
const deployment = fs.readFileSync(deploymentFile, "utf8");
|
||||
const imagePattern = /image:\s*(?:ghcr\.io\/cloudbase\/garm:[^\s]+|gitea\.lumpiasty\.xyz\/(?:Lumpiasty|lumpiasty)\/garm(?:-k8s)?:[^\s]+)/;
|
||||
if (!imagePattern.test(deployment)) {
|
||||
fail(`Unable to update garm image in ${deploymentFile}`);
|
||||
}
|
||||
|
||||
const updatedDeployment = deployment.replace(imagePattern, `image: ${image}`);
|
||||
|
||||
fs.writeFileSync(deploymentFile, updatedDeployment, "utf8");
|
||||
console.log(`Pinned garm image to ${image}`);
|
||||
6
vault/kubernetes-roles/authentik.yaml
Normal file
6
vault/kubernetes-roles/authentik.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
bound_service_account_names:
|
||||
- authentik-secret
|
||||
bound_service_account_namespaces:
|
||||
- authentik
|
||||
token_policies:
|
||||
- authentik
|
||||
6
vault/kubernetes-roles/crawl4ai.yaml
Normal file
6
vault/kubernetes-roles/crawl4ai.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
bound_service_account_names:
|
||||
- crawl4ai-secret
|
||||
bound_service_account_namespaces:
|
||||
- crawl4ai
|
||||
token_policies:
|
||||
- crawl4ai
|
||||
6
vault/kubernetes-roles/garm.yaml
Normal file
6
vault/kubernetes-roles/garm.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
bound_service_account_names:
|
||||
- garm
|
||||
bound_service_account_namespaces:
|
||||
- garm
|
||||
token_policies:
|
||||
- garm
|
||||
6
vault/kubernetes-roles/openwebui.yaml
Normal file
6
vault/kubernetes-roles/openwebui.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
bound_service_account_names:
|
||||
- openwebui-secret
|
||||
bound_service_account_namespaces:
|
||||
- openwebui
|
||||
token_policies:
|
||||
- openwebui
|
||||
3
vault/policy/authentik.hcl
Normal file
3
vault/policy/authentik.hcl
Normal file
@@ -0,0 +1,3 @@
|
||||
path "secret/data/authentik" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
3
vault/policy/crawl4ai.hcl
Normal file
3
vault/policy/crawl4ai.hcl
Normal file
@@ -0,0 +1,3 @@
|
||||
path "secret/data/crawl4ai" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
7
vault/policy/garm.hcl
Normal file
7
vault/policy/garm.hcl
Normal file
@@ -0,0 +1,7 @@
|
||||
path "secret/data/garm" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
|
||||
path "secret/data/backblaze" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
3
vault/policy/openwebui.hcl
Normal file
3
vault/policy/openwebui.hcl
Normal file
@@ -0,0 +1,3 @@
|
||||
path "secret/data/authentik/openwebui" {
|
||||
capabilities = ["read"]
|
||||
}
|
||||
Reference in New Issue
Block a user