{"id":28410780,"url":"https://github.com/k0rventen/lampone","last_synced_at":"2025-07-24T16:05:15.441Z","repository":{"id":292581358,"uuid":"932995174","full_name":"k0rventen/lampone","owner":"k0rventen","description":"My self hosted cloud, running on some raspberries.","archived":false,"fork":false,"pushed_at":"2025-06-24T11:38:07.000Z","size":5986,"stargazers_count":4,"open_issues_count":4,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-24T12:28:50.580Z","etag":null,"topics":["fluxcd","gitops","homelab","k3s","kubernetes","selfhosted"],"latest_commit_sha":null,"homepage":"https://cocointhe.cloud","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/k0rventen.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2025-02-14T22:59:18.000Z","updated_at":"2025-06-24T11:38:11.000Z","dependencies_parsed_at":null,"dependency_job_id":"1789e01b-ac00-4607-abeb-d39f7e2f52eb","html_url":"https://github.com/k0rventen/lampone","commit_stats":null,"previous_names":["k0rventen/lampone"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/k0rventen/lampone","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0rventen%2Flampone","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0rventen%2Flampone/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0rventen%2Flampone/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0rventen%2Flampone/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k0rventen","download_url":"https://codeload.github.com/k0rventen/lampone/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k0rventen%2Flampone/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261797887,"owners_count":23211074,"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":["fluxcd","gitops","homelab","k3s","kubernetes","selfhosted"],"created_at":"2025-06-02T12:38:15.635Z","updated_at":"2025-07-24T16:05:15.417Z","avatar_url":"https://github.com/k0rventen.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"\n\n\u003cdiv align=\"center\"\u003e\n\n![logo](./resources/logo.png)\n\n\u003ch1\u003elampone\u003c/h1\u003e\n\nMy self hosted cloud, available at [cocointhe.cloud](https://cocointhe.cloud).\n\n\u003cbr\u003e\n\n\n![sli](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Favailability_sli\u0026style=for-the-badge\u0026)\n![cluster uptime](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fcluster_uptime_days\u0026style=for-the-badge\u0026color=blue)\n\u003cbr\u003e\n![cluster version](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fkubernetes_version\u0026style=for-the-badge\u0026color=blue)\n![nodes](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fnodes_count\u0026style=for-the-badge\u0026color=purple)\n![pods](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fpods_count\u0026style=for-the-badge\u0026color=purple)\n![cluster power](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fcluster_power_draw\u0026style=for-the-badge\u0026color=ffda1e)\n\u003cbr\u003e\n![cluster cpu](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fcluster_cpu_usage\u0026style=for-the-badge)\n![cluster ram](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fcluster_memory_usage\u0026style=for-the-badge)\n![nfs disk](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fnfs_disk_usage\u0026style=for-the-badge)\n![cluster temp](https://img.shields.io/endpoint?url=https%3A%2F%2Fstats.cocointhe.cloud%2Fcluster_temperature\u0026style=for-the-badge)\n\n\u003c/div\u003e\n\n\n## Hardware\n\nThis is what the cluster looks like:\n\n\u003cdiv align=\"center\"\u003e\n\n![cluster](./resources/cluster.gif)\n\u003c/div\u003e\n\nWhat it's made of:\n\n- 3 [raspberry pi 4 (8Go)](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/)\n- 1 [gigabit ethernet 5 ports switch](https://www.tp-link.com/home-networking/soho-switch/tl-sg105/)\n- 1 [1To lexar ES3 usb SSD](https://www.lexar.com/products/Lexar-ES3-Portable-SSD/)\n- 1 [80mm fan](https://www.thermalright.com/product/tl-8015w/)\n- 3 very short cat6 ethernet cables\n- a [3d printed rack](https://github.com/k0rventen/lampone/tree/main/resources/3d)\n- some m3 threaded inserts and screws\n\nThe rack is a remix of [this one](https://makerworld.com/en/models/180806-raspberry-pi-4-5-mini-server-rack-case). I've included the stls that I remixed/designed, aka the vented sleds for the PI4 and the SSD, and the side fan mount.\n\n\n## Software\n\nHere is a top view diagram of the main components:\n\n![architecture](./resources/arch.png)\n\nThis is the repo that governs almost all the cluster. The bootstrapping is done using ansible, from 3 ssh-available machines (pi4 in this case).\n\nFrom here, Flux will create everything that is declared in `k8s/`, decrypt what's secret using a private key, and keep the stack in sync.\n\nIn `k8s/` there are 2 main folders:\n- `infra` that represents what's needed for the cluster to function:\n  - a NFS provisionner as a storageclass,\n  - an IngressController with [Traefik](https://github.com/traefik/traefik), one private (listens on local lan), one \"public\" (routes specific subdomains from cloudflare),\n  - [cert-manager](https://github.com/cert-manager/cert-manager) for certificates management of my domain,\n  - [cloudflare tunnel](https://github.com/cloudflare/cloudflared) for exposing part of my services to the outside world,\n  - [tailscale-operator](https://github.com/tailscale/tailscale/tree/main/cmd/k8s-operator/deploy) for accessing my private services from wherever (using a subnet route) and for my cluster services to access my offsite backup server\n  - [system-upgrade-controller](https://github.com/rancher/system-upgrade-controller) for managing k8s upgrades directly in the cluster using CRDs.\n  - [renovate](https://github.com/renovatebot/renovate) cronjob to create PR for components updates (w/ auto merging when it's a patch level update)\n  - [restic](https://github.com/restic/restic) cronjob that create the local backup (if an app fails and borks its files) and remote backup (if the server catches fire)\n\n\n- `apps`, the actual services running on the cluster:\n  - [adguard](https://github.com/AdguardTeam/AdGuardHome) for DNS/DHCP\n  - [gitea](https://github.com/go-gitea/gitea) for local git and CI/CD\n  - [paperless-ngx](https://github.com/paperless-ngx/paperless-ngx) for my important files\n  - [immich](https://github.com/immich-app/immich) for photos backups and sync\n  - [vaultwarden](https://github.com/dani-garcia/vaultwarden) as my passwords manager\n  - [filebrowser](https://github.com/gtsteffaniak/filebrowser) for file sharing\n  - [glance](https://github.com/glanceapp/glance) as my internet homepage\n  - [kromgo](https://github.com/kashalls/kromgo) for exposing prom stats publicly\n  - [octoprint](https://github.com/OctoPrint/OctoPrint) for controlling my 3D printer\n  - [pocketid](https://github.com/pocket-id/pocket-id) as an OIDC provider\n  - [atuin](https://github.com/atuinsh/atuin) for my centralized shell history\n  - [grafana](https://github.com/grafana/grafana) + [prometheus](https://github.com/prometheus/prometheus) + [loki](https://github.com/grafana/loki) for monitoring\n  - and some other stuff like a blog , static sites, etc..\n\n- there is also an `appchart` folder. It's a Helm chart that ease the deployment of simple services.\n\n\n\n## deployment\n\nI try to adhere to gitops/automation principles.\nSome things aren't automated but it's mainly toil (one-time-things during setup).\n95% of the infrastructure should be deployable by following these instructions (assuming data and encryption keys are known).\n\nRequirements and basic stack:\n- [ansible](https://docs.ansible.com/): infrastructure automation\n- [flux](https://fluxcd.io/flux/): cluster state mgmt\n- [sops](https://github.com/getsops/sops) + [age](https://github.com/FiloSottile/age/): encryption\n- [git](https://git-scm.com/): change management\n\n```\nbrew install git ansible fluxcd/tap/flux sops age\n```\n\n### SOPS setup\n\nThis assume you have the decryption key `age.agekey`, and the env var configured:\n\n```\nSOPS_AGE_KEY_FILE=age.agekey\n```\n\nIf you want to encrypt an already created file (eg a k8s Secret spec):\n\n```\nsops encrypt -i \u003cfile.yaml\u003e\n```\n\nIf you want to edit inline a encrypted file (eg modify a value in a encrypted Secret/Configmap) using $EDITOR:\n\n```\nsops \u003cfile.yaml\u003e\n```\n\n\n### Creating the cluster\n\nIt is assumed that a ssh key auth is configured on the nodes (ssh-copy-id \u003cip\u003e),\nwith passwordless sudo (`\u003cuser\u003e ALL=(ALL) NOPASSWD: ALL` in visudo).\n\n```\ncd ansible\nansible-playbook -i inventory.yaml -l lampone cluster-install.yaml\n```\n\n\n## Deploying the stack\n\n1. Get a github token and set an env var:\n\n```fish\nexport GITHUB_TOKEN=xxx\n```\n\n2. Enter some commands\n```fish\n# pre create the decryption key\nkubectl create ns flux-system\nkubectl create secret generic sops-age --namespace=flux-system --from-file=age.agekey\n\n# bootstrap flux\nflux bootstrap github \\\n              --owner=k0rventen \\\n              --repository=lampone \\\n              --branch=main \\\n              --path=./k8s/flux\n```\n\n3. Things should start to deploy ! :magic:\n\n\n## backup strategy\n\nI try to follow a 3-2-1 backup rule. The 'live' data is on the nfs ssd.\nIt's backed up daily onto the same ssd (mainly for rollbacks and potential local re-deployments).\nFor disaster-recovery situations, it's also backed up daily onto a HDD offsite, which can be accessed through my tailnet.\n\nThe backup tool is [restic](https://restic.net/) . It's deployed as a cronjob in the cluster. The image used runs a custom script that runs both the local restic backup as well as the remote one (which requires commands before and after to mount the external disk.). Here are the commands used to create the restic repos before deploying the cronjob:\n\n1. local repo\n\n```\ncd /nfs\nrestic init nfs-backups\n```\n\n2. remote repo\n\nCreate a `mnt-backup.mount` systemd service on the remote server to mount/umount the backup disk\n```\ncoco@remote_server:~ $ cat /etc/systemd/system/mnt-backup.mount\n[Unit]\nDescription=Restic Backup External Disk mount\n\n[Mount]\nWhat=/dev/disk/by-label/backup\nWhere=/mnt/backup\nType=ext4\nOptions=defaults\n\n[Install]\nWantedBy=multi-user.target\n```\n\nInit the repo from the nfs server (this assumes passwordless ssh auth):\n```\nrestic init -r sftp:\u003cremote_server_ip\u003e:/mnt/backup/nfs-backups\n```\n\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ch3\u003e Staging / tests env (WIP)\u003c/h3\u003e\u003c/summary\u003e\n\nA staging environment can be deployed using [vagrant](https://developer.hashicorp.com/vagrant/downloads):\n```\nbrew tap hashicorp/tap\nbrew install hashicorp/tap/vagrant\nsudo apt install virtualbox vagrant --no-install-recommends\n```\n\nThen create the staging env:\n```sh\n# launch\nvagrant up\n\n# add the nodes ssh config\nvagrant ssh-config \u003e\u003e $HOME/.ssh/config\n\n# deploy the cluster\ncd ansible\nansible-playbook -i inventory.yaml -l staging cluster-install.yaml\n\n# get the kubectl config\ncd ..\nvagrant ssh -c \"kubectl config view --raw\" staging-master \u003e $HOME/.kube/configs/staging\n\n# test\nkubectl get nodes\n```\n\nThen bootstrap the cluster using flux from [this section](#deploying-the-services), ideally using a develop branch.\n\n\u003c/details\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk0rventen%2Flampone","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk0rventen%2Flampone","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk0rventen%2Flampone/lists"}