{"id":22004917,"url":"https://github.com/c0nsultant/homelab-as-code","last_synced_at":"2025-03-23T06:24:28.079Z","repository":{"id":246644671,"uuid":"821732409","full_name":"C0nsultant/homelab-as-code","owner":"C0nsultant","description":"IaC for going from empty disks to running HA homelab cluster managed using GitOps within 2(-ish) clicks","archived":false,"fork":false,"pushed_at":"2025-03-22T18:53:01.000Z","size":991,"stargazers_count":2,"open_issues_count":6,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-22T19:29:12.850Z","etag":null,"topics":["ansible","cloud-init","devops","flux","gitops","helm","homelab","k8s-at-home","kubernetes","netboot","pxe","selfhosted"],"latest_commit_sha":null,"homepage":"","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/C0nsultant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-06-29T09:24:54.000Z","updated_at":"2025-03-22T18:53:03.000Z","dependencies_parsed_at":"2024-12-24T19:15:21.005Z","dependency_job_id":"81845164-eb3c-42ce-b495-b5bf9359aedd","html_url":"https://github.com/C0nsultant/homelab-as-code","commit_stats":null,"previous_names":["c0nsultant/homelab-as-code"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C0nsultant%2Fhomelab-as-code","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C0nsultant%2Fhomelab-as-code/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C0nsultant%2Fhomelab-as-code/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/C0nsultant%2Fhomelab-as-code/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/C0nsultant","download_url":"https://codeload.github.com/C0nsultant/homelab-as-code/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245063314,"owners_count":20554913,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ansible","cloud-init","devops","flux","gitops","helm","homelab","k8s-at-home","kubernetes","netboot","pxe","selfhosted"],"created_at":"2024-11-30T00:17:30.691Z","updated_at":"2025-03-23T06:24:28.071Z","avatar_url":"https://github.com/C0nsultant.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n![alt text](logo.png \"Title\")\n\n\u003cimg src=\"https://uptime-kuma.kthxbye.cyou/api/badge/43/uptime?labelPrefix=Node+\" alt=\"Node Uptime\"\u003e\n\u003cimg src=\"https://uptime-kuma.kthxbye.cyou/api/badge/90/uptime?labelPrefix=Cluster+\" alt=\"Cluster Uptime\"\u003e\n\u003cimg src=\"https://uptime-kuma.kthxbye.cyou/api/badge/63/uptime?labelPrefix=Service+\" alt=\"Service Uptime\"\u003e\n\n\u003ch3\u003eBootstrap and GitOps sources to get my baremetal homelab set up consistently.\u003c/h3\u003e\n\n\u003c/div\u003e\n\n# 🏡 Homelab-as-Code (HaC™)\n\nThis repository was born out of the need to better manage an ever-growing homelab environment.\nAfter starting with a simple single-node Docker Compose setup, the increasing number of services began to make maintenance and updates more challenging.\n\nAs the complexity grew, it became clear that a more structured, Infrastructure-as-Code approach was needed to:\n- keep configurations versioned and thus better documented\n- make deployments more consistently repeatable and reliable\n- simplify the process of adding new services without losing track of the overall state\n- enable easier backup and disaster recovery that is centrally managed\n- provide better scalability and resilience beyond a single node\n\nI decided to take this opportunity to properly learn Kubernetes hands-on, embracing the complexity and \"feeling the pain\" that comes with it rather than _just_ having the theoretical knowledge.\nThis repo serves as both documentation of my setup as well as a real-world learning experience in managing infrastructure that I rely upon as code.\n\nPS: This setup is mature enough to be girlfriend-approved. 😉\n\n## 🔰 Overview\n\nAt the highest possible level, this repo and HaC workflow consists of three parts:\n- [cloud-init](cloud-init) contains the stage 1 bootstrapping for the cluster nodes.\n  This includes only the very basic OS-level configuration required for the others stages of this workflow.\n  The contained shell script creates all files required to install the OS via [network boot](https://ubuntu.com/server/docs/how-to-netboot-the-server-installer-on-amd64) and without user interaction.\n  _Triggering_ the network-boot installation is out-of-scope for the moment.\n  After completion of the cloud-init [autoinstall](https://canonical-subiquity.readthedocs-hosted.com/en/latest/intro-to-autoinstall.html), all nodes reboot and are ready to accept SSH connections.\n- [ansible](ansible) contains the stage 2 system configuration for the cluster nodes.\n  This includes a range of tasks including power management, networking setup, and most importantly bootstrapping the kubernetes cluster using [kubeadm](https://kubernetes.io/docs/reference/setup-tools/kubeadm/).\n  The contained ansible playbook and roles perform the required tasks on the nodes via an SSH and a dedicated ansible user created in the previous step.\n  After completion of this stage, the kubernetes cluster is set up with [HA](https://kube-vip.io/) control planes, joined worker nodes, dual-stack [CNI](https://www.tigera.io/tigera-products/calico/), almost working [OIDC authn](https://dexidp.io/), and last but not least a bootstrapped [GitOps](https://fluxcd.io/) setup that is ready to start reconciling.\n- [flux](flux) contains the final stage 3 GitOps cluster configuration.\n  This includes everything running _inside_ kubernetes in the cluster and ranges from basic system infrastructure like [load balancer](https://metallb.io/), [ingress](https://kubernetes.github.io/ingress-nginx/), and [CSI](https://longhorn.io/) to more user-style applications such as [password manager](https://bitwarden.com/) and [file management](https://nextcloud.com/) apps.\n  The contained flux kustomizations are automatically installed and/or reconciled on the cluster without* user interaction.\n  This process is staggered since there is an inherent dependency between some of the components.\n  After completion of this stage, the cluster is fully set up and ready for use.\n\n## 📐 Tech Stack\n\n| Component                                                            | Purpose                                | Notes                                                                              |\n| -------------------------------------------------------------------- | -------------------------------------- | ---------------------------------------------------------------------------------- |\n| [Ubuntu Server 24.04](https://ubuntu.com/server)                     | Base Operating System                  |                                                                                    |\n| [cloud-init](https://cloud-init.io/)                                 | Headless OS Installation               | see [cloud-init/README.md](cloud-init/README.md)                                   |\n| [Ansible](https://ansible.com/)                                      | OS Configuration                       |                                                                                    |\n| [kubeadm](https://kubernetes.io/docs/reference/setup-tools/kubeadm/) | k8s _Distribution_ / Install Mechanism | stacked HA controlplanes                                                           |\n| [containerd](https://containerd.io/)                                 | OCI Runtime                            |                                                                                    |\n| [Calico](https://www.tigera.io/tigera-products/calico/)              | CNI                                    | dual-stack nodes and services                                                      |\n| [kube-vip](https://kube-vip.io/)                                     | Virtual IP for controlplane Nodes      | used in L2/ARP mode                                                                |\n| [Flux2](https://fluxcd.io)                                           | GitOps Automation inside the Cluster   |                                                                                    |\n| [SOPS](https://getsops.io/)                                          | Secrets Management                     | [age](https://age-encryption.org/) rather than pgp, but not any more user-friendly |\n\n## 📱 Applications\n\n### 🤖 System-Level\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003cth\u003e\u003c/th\u003e\n        \u003cth\u003eName\u003c/th\u003e\n        \u003cth\u003ePurpose\u003c/th\u003e\n        \u003cth\u003eNotes\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/metallb/metallb/refs/heads/main/website/static/images/logo/metallb-blue.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://metallb.io/\"\u003emetallb\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCloud-Native Service LoadBalancer\u003c/td\u003e\n        \u003ctd\u003eused in L2/ARP mode, so only VIP rather than true LB\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/kubernetes-sigs/external-dns/refs/heads/master/docs/img/external-dns.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://kubernetes-sigs.github.io/external-dns/\"\u003eexternal-dns\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eDNS Management Automation\u003c/td\u003e\n        \u003ctd\u003esplit-horizon realized using \u003ca href=\"https://github.com/jobs62/opnsense_unbound_external-dns_webhook\"\u003eopnsense webhook\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/cert-manager/cert-manager/refs/heads/master/logo/logo.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://cert-manager.io/\"\u003ecert-manager\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eAutomated Certificate Management\u003c/td\u003e\n        \u003ctd\u003eLet's Encrypt via ACME DNS\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/nginx/nginx.org/refs/heads/main/img/ingress_logo.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://kubernetes.github.io/ingress-nginx/\"\u003eingress-nginx\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eIngress Controller\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/kyverno/kyverno/refs/heads/main/img/logo.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://kubernetes.github.io/ingress-nginx/\"\u003eKyverno\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003ePolicy Engine\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/spegel-org/website/refs/heads/main/static/favicon.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://spegel.dev/\"\u003eSpegel\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCluster-Internal P2P Container Image Distribution\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://spegel.dev/docs/guides/updating-latest-tag/\"\u003ebasically mandates\u003c/a\u003e the use of digests or good pinning\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/longhorn/website/refs/heads/master/static/img/logos/longhorn-icon-color.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://longhorn.io/\"\u003elonghorn\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCloud-Native Distributed Block Storage CSI\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://cdn.jsdelivr.net/gh/selfhst/icons/svg/truenas-core.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/democratic-csi/democratic-csi\"\u003edemocratic-csi\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCSI for Common External Storage Systems\u003c/td\u003e\n        \u003ctd\u003eusing the freenas-nfs implementation\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/renovatebot/renovate/refs/heads/main/docs/usage/assets/images/logo.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://www.mend.io/renovate/\"\u003eRenovate Bot\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eDependency Update Automation\u003c/td\u003e\n        \u003ctd\u003eused for multiple repos, not just this one\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/k8up-io/k8up/refs/heads/master/docs/modules/ROOT/assets/images/k8up-logo-square.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://k8up.io/\"\u003ek8up\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCloud-Native Backup/Restore\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg.github.io/refs/heads/main/assets/images/hero_image.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://cloudnative-pg.io/\"\u003eCloudNativePG\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCloud-Native PostgreSQL Operator\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/grafana/grafana/refs/heads/main/public/img/grafana_icon.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://grafana.com/grafana/\"\u003eGrafana\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eMontoring and Observability\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/prometheus/prometheus/refs/heads/main/documentation/images/prometheus-logo.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://prometheus.io/\"\u003ePrometheus\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eMetrics Aggregation and Storage\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/grafana/loki/refs/heads/main/docs/sources/logo.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://grafana.com/loki/\"\u003eLoki\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eLog Aggregation and Storage\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/AnalogJ/scrutiny/refs/heads/master/webapp/frontend/src/assets/images/logo/scrutiny-logo-dark.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/AnalogJ/scrutiny\"\u003eScrutiny\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eDrive Health Monitoring\u003c/td\u003e\n        \u003ctd\u003evia SMART\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://openmoji.org/data/color/svg/1F9E6.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/httptoolkit/docker-socks-tunnel/\"\u003emicrosocks\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eLightweight SOCKS5 Server\u003c/td\u003e\n        \u003ctd\u003epoor man's monitoring gateway, alternatives: OpenSSH, Isito\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg height=\"32\" width=\"32\" src=\"https://raw.githubusercontent.com/kubernetes-sigs/descheduler/refs/heads/master/assets/logo/descheduler-stacked-color.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://sigs.k8s.io/descheduler\"\u003edescheduler\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003ePod Eviction for Node Balancing\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/stakater/Reloader/refs/heads/master/assets/web/reloader-round-100px.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://docs.stakater.com/reloader/\"\u003ereloader\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eHot-Reload for ALL Workloads\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/dexidp/website/refs/heads/main/static/img/logos/dex-glyph-color.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://dexidp.io/\"\u003eDex\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eOIDC Provider\u003c/td\u003e\n        \u003ctd\u003eused for api-server authentication\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://kubernetes-sigs.github.io/metrics-server/\"\u003emetrics-server\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eMetrics API\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/FairwindsOps/goldilocks/refs/heads/master/pkg/dashboard/assets/images/favicon.ico\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://goldilocks.docs.fairwinds.com/\"\u003eGoldilocks\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eResource Recommendation Engine\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler\"\u003eVertical Pod Autoscaler\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eWorkload Resource Scaler\u003c/td\u003e\n        \u003ctd\u003eused exclusively for Goldilocks recommendations\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n### 👨‍💻 User-Level\n\n\u003ctable\u003e\n    \u003ctr\u003e\n        \u003cth\u003e\u003c/th\u003e\n        \u003cth\u003eName\u003c/th\u003e\n        \u003cth\u003ePurpose\u003c/th\u003e\n        \u003cth\u003eNotes\u003c/th\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/pi-hole/docs/refs/heads/master/docs/images/logo.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://pi-hole.net/\"\u003ePi-hole\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eFiltering DNS Proxy\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/nextcloud/server/refs/heads/master/core/img/favicon.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://nextcloud.com/\"\u003eNextcloud\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eFile Storage and Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/dani-garcia/vaultwarden/refs/heads/main/resources/vaultwarden-icon.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/dani-garcia/vaultwarden\"\u003eVaultwarden\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eAPI-compatible Password Manager\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/immich-app/immich/refs/heads/main/web/static/favicon.ico\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://immich.app/\"\u003eImmich\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003ePhoto/Video Storage and Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/refs/heads/dev/docs/assets/favicon.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://docs.paperless-ngx.com/\"\u003ePaperless-ngx\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eDocument Management System\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/firefly-iii/firefly-iii/refs/heads/main/public/favicon.ico\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://www.firefly-iii.org/\"\u003eFirefly III\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003ePersonal Finance Manager\u003c/td\u003e\n        \u003ctd\u003eincluding \u003ca href=\"https://github.com/firefly-iii/data-importer\"\u003eimporter\u003c/a\u003e and \u003ca href=\"https://github.com/cioraneanu/firefly-pico\"\u003epico\u003c/a\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/gethomepage/homepage/refs/heads/dev/public/homepage.ico\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://gethomepage.dev/\"\u003eHomepage\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eApplication Dashboard\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/FreshRSS/freshrss.org/refs/heads/main/static/favicon.ico\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://freshrss.org/index.html\"\u003eFresh-RSS\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eRSS Aggregator\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/RSS-Bridge/rss-bridge/refs/heads/master/static/favicon.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://rss-bridge.org/\"\u003eRSS-Bridge\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eUnofficial RSS Feeds of ANY Source\u003c/td\u003e\n        \u003ctd\u003e\u003ci\u003eany\u003c/i\u003e as long as you know some PHP\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://d21buns5ku92am.cloudfront.net/26628/documents/54546-1717072325-sc-logo-cloud-black-7412d7.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/C0nsultant/soundcloud-scraper\"\u003eSoundcloud Scraper\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eParser + Webhook for my Soundcloud Feed\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/docs/stirling.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://www.stirlingpdf.com/\"\u003eStirling PDF\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eSwiss-Army Knife for PDFs\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://cdn.jsdelivr.net/gh/selfhst/icons/svg/ubiquiti-unifi.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://ui.com/consoles\"\u003eUniFi Network Application\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eAP Administration and Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://cdn.jsdelivr.net/gh/selfhst/icons/svg/opnsense-v1.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/C0nsultant/opnsense-ipv6-prefix-update\"\u003eOPNsense Prefix Updater\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eUpdate Network Configs with Newest Non-Static IPv6\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/n8n-io/n8n-docs/refs/heads/main/docs/_images/favicon.ico\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://n8n.io/\"\u003en8n\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eWorkflow Automation\u003c/td\u003e\n        \u003ctd\u003efreemium/open core\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/jellyfin/jellyfin-web/refs/heads/master/src/assets/img/icon-transparent.png\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://jellyfin.org/\"\u003eJellyfin\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eMedia Streaming and Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/qdm12/gluetun/refs/heads/master/doc/logo.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/qdm12/gluetun/\"\u003eGluetun\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eVPN Gateway\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/qbittorrent/qBittorrent-website/refs/heads/master/src/favicon.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://www.qbittorrent.org/\"\u003eqBittorrent\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eTorrent Client\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/sabnzbd/sabnzbd/refs/heads/develop/interfaces/Config/templates/staticcfg/images/logo-small.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://sabnzbd.org/\"\u003eSABnzbd\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eUsenet Client\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/Prowlarr/Prowlarr/refs/heads/develop/Logo/Prowlarr.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://prowlarr.com/\"\u003eProwlarr\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eTorrent \u0026 Usenet Indexer Engine\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/Radarr/Radarr/refs/heads/develop/Logo/Radarr.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://radarr.video/\"\u003eRadarr\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eMovie Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/Sonarr/Sonarr/refs/heads/develop/Logo/Sonarr.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://sonarr.tv/\"\u003eSonarr\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eTV Show Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/Lidarr/Lidarr/refs/heads/develop/Logo/Lidarr.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://lidarr.audio/\"\u003eLidarr\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eMusic Management\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n        \u003ctd\u003e\u003cimg width=\"32\" src=\"https://raw.githubusercontent.com/FlareSolverr/FlareSolverr/refs/heads/master/resources/flaresolverr_logo.svg\"\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003ca href=\"https://github.com/FlareSolverr/FlareSolverr\"\u003eFlareSolverr\u003c/a\u003e\u003c/td\u003e\n        \u003ctd\u003eCloudflare Protection Bypass\u003c/td\u003e\n        \u003ctd\u003e\u003c/td\u003e\n    \u003c/tr\u003e\n\u003c/table\u003e\n\n## ☁️ Cloud Dependencies\n\nWhile the ultimate goal is to have as self-sufficient of a setup as possible, some external services are still required for proper operation.\n\n| Service                                                  | Purpose                                    | Notes                                                                    |\n| -------------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------ |\n| [GitHub](https://github.com/)                            | Git Repository Hosting, GitOps Source      |                                                                          |\n| [INWX](https://www.inwx.de/)                             | Domain Registrar                           |                                                                          |\n| [Cloudflare](https://www.cloudflare.com/)                | Public DNS Auth Hosting                    |                                                                          |\n| [netcup](https://www.netcup.de/)                         | Public Reverse-Proxy for Relevant Services | not _yet_ managed here since the number of public services is tiny       |\n| [BackBlaze](https://www.backblaze.com/)                  | Cloud Storage for Backups                  | the \"3\" in 3-2-1 for the really important data                           |\n| [TailScale](https://tailscale.com/)                      | Overlay VPN                                | used for split-horizon and a direct connection back home                 |\n| VPN Provider                                             | VPN Gateway                                | different external IP for all the Linux ISOs                             |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc0nsultant%2Fhomelab-as-code","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fc0nsultant%2Fhomelab-as-code","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fc0nsultant%2Fhomelab-as-code/lists"}