Reorganize repos into kitestacks-homelab + plain-English doc rewrite

- Rewrote RUNBOOK.md and DEBUG-DOCUMENTATION.md in simple 5th-grade language
  with real-world analogies for every technical concept
- Updated README.md with current service inventory and folder map
- Added cloud-migration/ subdirectory (from kitestacks-cloud-migration repo)
- Added autosync/ subdirectory (from kitestacks-homelab-autosync-test repo)
- Added osticket/ subdirectory (from OSTicketSystem repo)
- Added cloud/ placeholder for future cloud configs
- Excluded binary DB/postgres files from autosync subdirectory

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
kenpat 2026-06-18 18:37:58 -05:00
parent f79478158d
commit fb822d5142
75 changed files with 11711 additions and 338 deletions

View file

@ -1,121 +1,120 @@
# KiteStacks Homelab — Debug Documentation
# KiteStacks Homelab — Problems We've Seen and How We Fixed Them
All known incidents, root causes, and fixes. Most recent first.
Newest problems at the top.
---
## 2026-06-18 — kscloud1 SSH Key Lost / Cannot SSH
## 2026-06-18 — Can't SSH into kscloud1
**Symptom:** `Permission denied (publickey,password)` connecting to kscloud1.
**What happened:** Trying to connect to the cloud machine (kscloud1) gave a
"Permission denied" error. The SSH key was missing from the machine.
**Root cause:** SSH public key was removed from kscloud1's `authorized_keys`.
**How we found it:** The error message said `publickey,password` — meaning it tried
the SSH key first and then tried a password, both failed.
**Fix:**
1. Open Hetzner Cloud console → VNC terminal → log in as `root`
2. On monk, serve the public key temporarily:
**How we fixed it:**
1. Used Hetzner's browser console (like a TV remote for the server) to log in as root
2. Served the SSH public key from monk as a temporary download:
```bash
# On monk — share the key file over a mini web server
cat ~/.ssh/id_ed25519_kscloud1.pub > ~/key.txt
python3 -m http.server 7777 --directory ~/
```
3. In Hetzner console, type:
3. Downloaded it from the Hetzner console:
```bash
curl http://<MONK_TAILSCALE_IP>:7777/key.txt > /root/.ssh/authorized_keys
curl http://MONK_TAILSCALE_IP:7777/key.txt > /root/.ssh/authorized_keys
```
4. If root SSH login was disabled:
4. If the machine had root SSH login disabled:
```bash
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart ssh
```
**Note:** Hetzner VNC console does not support clipboard paste for long strings.
Serving the key via HTTP from monk's Tailscale IP is the reliable workaround.
**Why this works:** The Hetzner console bypasses SSH entirely — it's like plugging a
keyboard and monitor directly into the server. So even when SSH is broken, you can still
type commands.
---
## 2026-06-18 — BookStack SSO "An Error Occurred / An unknown error occurred"
## 2026-06-18 — BookStack Login Said "An Error Occurred"
**Symptom:** Clicking "Login with authentik" on BookStack shows a generic error page.
No stack trace even with `APP_DEBUG=true`. `laravel.log` is 0 bytes.
**What happened:** Clicking "Login with Authentik" on the wiki showed a generic error.
No details, no clues — just "An unknown error occurred."
**Root cause (3 compounding issues):**
**Why it happened (three problems at once):**
**Issue 1 — Wrong `OIDC_ISSUER_DISCOVER` default**
BookStack defaults to `OIDC_ISSUER_DISCOVER=false`. Without it set to `true`, BookStack
does not auto-discover endpoints from Authentik and cannot verify JWT tokens.
**Problem 1 — Missing setting in BookStack**
BookStack needs `OIDC_ISSUER_DISCOVER=true` to automatically find all the login
endpoints from Authentik. Without it, BookStack can't verify login tokens.
**Issue 2 — Authentik `issuer_mode=global` breaks discovery**
When `OIDC_ISSUER=https://auth.kitestacks.com/` (the global URL), BookStack tries to
fetch the discovery doc at `https://auth.kitestacks.com/.well-known/openid-configuration`.
Authentik's global URL returns an HTML login page, not JSON.
The app crashes silently trying to parse HTML as JSON.
**Problem 2 — Authentik was using the wrong login URL format**
Authentik can either use one shared URL for all apps or a unique URL per app.
BookStack expects a per-app URL. When the wrong type was set, BookStack tried to
download login instructions from a URL that returned an HTML page instead of data,
and then crashed trying to read it.
**Issue 3 — Root-owned cache directory blocks write**
Running `php artisan` commands inside the container as root creates cache subdirectories
owned by `root:root`. BookStack's PHP process runs as `abc` (UID 1000) and cannot write
to these directories, causing a `Permission denied` on the first OIDC login attempt.
This exception is caught by BookStack's generic handler → "An unknown error occurred".
**Problem 3 — File permission error hidden by BookStack**
Running a setup command inside the BookStack container as root created some folders
that only root could write to. When the normal BookStack process tried to save
a login session, it couldn't — and BookStack showed a generic error instead of
the real one.
**Fix:**
**How we fixed it:**
Step 1 — Change Authentik bookstack provider to `per_provider` issuer mode:
Step 1 — Change Authentik to use per-app URLs (run this once):
```bash
docker run --rm --network host \
-e PGPASSWORD="<REDACTED>" \
postgres:16 psql -h <KSCLOUD1_TAILSCALE_IP> -U authentik authentik -c \
"UPDATE authentik_providers_oauth2_oauth2provider SET issuer_mode='per_provider' WHERE provider_ptr_id=<ID>;"
-e PGPASSWORD="YOUR_DB_PASSWORD" \
postgres:16 psql -h KSCLOUD1_TAILSCALE_IP -U authentik authentik -c \
"UPDATE authentik_providers_oauth2_oauth2provider SET issuer_mode='per_provider' WHERE provider_ptr_id=PROVIDER_ID;"
```
Step 2 — Update BookStack compose env vars:
```yaml
- OIDC_ISSUER=https://auth.kitestacks.com/application/o/bookstack/
- OIDC_ISSUER_DISCOVER=true
Step 2 — Make sure BookStack's settings include:
```
OIDC_ISSUER=https://auth.kitestacks.com/application/o/bookstack/
OIDC_ISSUER_DISCOVER=true
```
Step 3 — Fix cache permissions:
Step 3 — Fix the file permission problem:
```bash
docker exec bookstack chown -R abc:users /config/www/framework/cache/
```
Step 4 — Restart BookStack and test:
Step 4 — Restart BookStack:
```bash
docker compose up -d
# Verify OIDC redirect works
curl -sc /tmp/c.txt http://localhost:6875/login -o /tmp/l.html
CSRF=$(grep -oP 'name="_token" value="\K[^"]+' /tmp/l.html | head -1)
curl -v -b /tmp/c.txt -X POST http://localhost:6875/oidc/login -d "_token=$CSRF" --max-redirs 0 2>&1 | grep "Location:"
# Should show: Location: https://auth.kitestacks.com/application/o/authorize/?...
```
**Key insight:** When Authentik's `issuer_mode=per_provider`, the discovery doc at
`https://auth.kitestacks.com/application/o/bookstack/.well-known/openid-configuration`
returns `issuer: https://auth.kitestacks.com/application/o/bookstack/` — this must match
`OIDC_ISSUER` exactly for JWT validation to pass.
---
## 2026-06-18 — Portainer OAuth Users Can't See Environments
## 2026-06-18 — Portainer OAuth Login Couldn't See Any Servers
**Symptom:** After logging in via Authentik SSO, Portainer shows no environments.
**What happened:** Logged in through Authentik, got into Portainer, but no environments
(no servers, nothing to manage) were visible.
**Root cause:** Portainer CE creates OAuth users as Role:2 (regular user). Regular users
have no access to environments by default — only admins do.
**Why it happened:** Portainer creates new SSO users as "regular users." Regular users
can't see environments — only admins can. The fix is to create the user as an admin
**before** they log in for the first time.
**Fix:** Pre-create the OAuth user as Role:1 (admin) via API *before* their first login:
**How we fixed it:**
Create the user as admin before first login:
```bash
# Get a temporary auth token
TOKEN=$(curl -sk -X POST https://portainer.kitestacks.com/api/auth \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<REDACTED>"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['jwt'])")
-d '{"username":"admin","password":"YOUR_PASSWORD"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['jwt'])")
# Note: do NOT include "Password" field for OAuth users
# Create the user with admin role (role 1 = admin)
curl -sk -X POST "https://portainer.kitestacks.com/api/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"user@example.com","role":1}'
```
If the user already logged in as Role:2, promote them via API:
If they already logged in as a regular user, promote them:
```bash
curl -sk -X PUT "https://portainer.kitestacks.com/api/users/<USER_ID>" \
curl -sk -X PUT "https://portainer.kitestacks.com/api/users/USER_ID" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"role":1}'
@ -123,110 +122,88 @@ curl -sk -X PUT "https://portainer.kitestacks.com/api/users/<USER_ID>" \
---
## 2026-06-17 — Cloudflare Tunnel Phantom 3rd Connector
## 2026-06-17 — Three Cloudflare Connectors Instead of Two
**Symptom:** `cloudflared tunnel info` shows 3 connectors instead of 2.
Authentik OAuth codes fail with `invalid_grant` intermittently.
**What happened:** The Cloudflare dashboard was showing 3 tunnel connectors when there
should only be 2 (one from monk, one from kscloud1). This caused Authentik logins to
fail randomly — about half the time, the code from the login form would reach the wrong
connector and get rejected.
**Root cause:** The native cloudflared systemd service on monk was running alongside
the Docker container — two connectors from the same host, causing session/auth split.
**Why it happened:** The system's built-in cloudflared service was still running on monk,
alongside the Docker container version. So monk was connecting to Cloudflare twice.
**Fix:**
**How we fixed it:**
```bash
sudo systemctl disable --now cloudflared
```
Verify only 2 connectors remain in Cloudflare Zero Trust → Networks → Tunnels.
That stopped the duplicate. Now only the Docker container runs.
**Also fixed:** Authentik OAuth2 code TTL bumped from 1 min → 10 min to tolerate
reconnect windows when monk comes back online.
After fixing: verified only 2 connectors in Cloudflare Zero Trust → Networks → Tunnels.
---
## 2026-06-17 — BookStack MariaDB Crash Loop ("Table 'mysql.db' doesn't exist")
## 2026-06-17 — BookStack Database Kept Crashing
**Symptom:** `bookstack-db` container in crash loop, logs show:
`Table 'mysql.db' doesn't exist`
**What happened:** The BookStack database container (bookstack-db) kept restarting
and never stayed running. Logs showed: `Table 'mysql.db' doesn't exist`
**Root cause:** Stale/corrupt data in `./db/` from a previous partial MariaDB initialization.
**Why it happened:** The database's data folder had leftover files from a previous
incomplete setup. When MariaDB started, it saw partial old data and crashed trying
to use it.
**Fix:** Wipe the data directory (files are root-owned inside the container):
**How we fixed it:**
```bash
# Wipe the broken database files (they're owned by root inside the container)
docker run --rm -v $(pwd)/db:/db alpine sh -c 'rm -rf /db/*'
# Start fresh
docker compose up -d
```
---
## 2026-06-17 — BookStack "Name does not resolve" for bookstack-db
## 2026-06-17 — BookStack Said It Couldn't Find the Database
**Symptom:** BookStack Laravel log shows DB hostname resolution failure on first boot.
**What happened:** BookStack started but immediately errored saying it couldn't connect
to the database (bookstack-db).
**Root cause:** Race condition — BookStack ran DB migrations before MariaDB was fully
initialized and registered with Docker's embedded DNS (127.0.0.11).
**Why it happened:** BookStack was too fast. It started before the database was fully
ready, and when it tried to find `bookstack-db` on the internal network, Docker hadn't
finished registering it yet.
**Fix:** Wait for `bookstack-db` to be healthy, then restart the BookStack container:
**How we fixed it:**
```bash
# Just wait a few seconds and restart BookStack
docker restart bookstack
```
---
## 2026-06-09 — Root CHANGELOG Permission Issue
**Symptom:** CHANGELOG.md could not be read/written by the normal user.
**Root cause:** CHANGELOG.md was owned by root with 600 permissions.
**Fix:**
```bash
sudo chown kenpat:kenpat CHANGELOG.md
chmod 644 CHANGELOG.md
```
That's it — the database had finished starting up by then.
---
## 2026-06-09 — Repo Folder Ownership Issue
**Symptom:** Could not create new files in the kitestacks-homelab repo directory.
**Root cause:** Repo root folder was owned by root.
**Fix:**
```bash
sudo chown -R kenpat:kenpat /opt/kitestacks-autosync/kitestacks-homelab
```
---
## Diagnostic Quick Reference
## Quick Diagnostic Commands
```bash
# Check which container is causing issues
# See which containers are running (and which are crashing)
docker ps --format "table {{.Names}}\t{{.Status}}"
# Tail any service log
docker logs <container> --tail 50 -f
# Follow the live logs of any service
docker logs CONTAINER_NAME --tail 50 -f
# BookStack PHP log
# Read BookStack's PHP error log
docker exec bookstack cat /app/www/storage/logs/laravel.log | tail -50
# Test BookStack OIDC flow directly
# Test if BookStack's login redirect works
curl -sc /tmp/c.txt http://localhost:6875/login -o /tmp/l.html && \
CSRF=$(grep -oP 'name="_token" value="\K[^"]+' /tmp/l.html | head -1) && \
curl -v -b /tmp/c.txt -X POST http://localhost:6875/oidc/login \
-d "_token=$CSRF" --max-redirs 0 2>&1 | grep -E "HTTP|Location"
# Should show: Location: https://auth.kitestacks.com/application/o/authorize/?...
# Test Authentik discovery document
curl -s https://auth.kitestacks.com/application/o/<slug>/.well-known/openid-configuration | python3 -m json.tool
# Check Cloudflare tunnel connector count
docker exec cloudflared cloudflared tunnel info <TUNNEL_ID>
# Check Tailscale connectivity
# Check Tailscale connections between machines
tailscale status
# PostgreSQL connectivity check (from monk)
docker run --rm --network host -e PGPASSWORD="<REDACTED>" \
postgres:16 psql -h <KSCLOUD1_TAILSCALE_IP> -U authentik authentik -c "\l"
# See if both Cloudflare connectors are working
docker exec cloudflared cloudflared tunnel info TUNNEL_ID
```

View file

@ -1,37 +1,55 @@
# KiteStacks Homelab
<!-- version: 1.3.922 -->
Everything needed to run, fix, and understand the KiteStacks homelab lives here.
Private GitOps repository for the KiteStacks homelab.
## What is KiteStacks?
## Cluster
KiteStacks is a personal homelab — a set of useful web apps that run on two computers
(monk at home, kscloud1 in Germany). All the websites are accessible over the internet
through Cloudflare without exposing any home IP addresses.
- K3s
- FluxCD (planned)
- Longhorn (planned)
## How to Read This Repo
## Applications
| File / Folder | What it is |
|--------------|------------|
| `RUNBOOK.md` | **Start here.** Plain-English guide to how everything works and how to do common tasks |
| `DEBUG-DOCUMENTATION.md` | Every problem we've hit and how we solved it |
| `docs/` | Detailed setup guides for specific services (Authentik SSO, etc.) |
| `apps/` | Docker Compose files for each service |
| `clusters/` | Infrastructure-level configs |
| `projects/` | Active project notes |
| `cloud/` | Cloud-specific configurations (kscloud1) |
| `cloud-migration/` | Archive of cloud migration work and volume backups |
| `autosync/` | Auto-sync scripts that keep the repo up to date automatically |
| `osticket/` | OSTicket help-desk project notes |
- Homepage
- Authentik (SSO identity provider)
- Grafana
- Open WebUI (Kite AI)
- Forgejo
- BookStack
- OpenProject
- Kavita
- Raindrop.io (cloud, bookmark manager)
- Uptime Kuma
- LiteLLM
- Linkding
- Prometheus (monitoring, no SSO)
- Portainer (admin, no SSO)
## Services Running Right Now
## SSO
| Service | Website | What it does |
|---------|---------|--------------|
| Authentik | auth.kitestacks.com | Single login for all services |
| Portainer | portainer.kitestacks.com | Manage all Docker containers |
| Forgejo | gitforge.kitestacks.com | Git server (code + scripts) |
| BookStack | wiki.kitestacks.com | Wiki and notes |
| Grafana | grafana.kitestacks.com | Server health charts |
| Karakeep | links.kitestacks.com | Bookmark manager |
| Kavita | kavita.kitestacks.com | Ebook reader |
| OSTicket | tasks.kitestacks.com | Help desk / ticket system |
| Open WebUI | ai.kitestacks.com | AI chat (GPT, Claude, local) |
| Uptime Kuma | status.kitestacks.com | Service monitor |
| Portal | www.kitestacks.com | Homepage |
All services use [Authentik](https://auth.kitestacks.com) as the identity provider.
Setup guide: [docs/authentik-sso-setup.md](docs/authentik-sso-setup.md)
## Quick Reference
## Documentation
```bash
# Check all running containers
docker ps --format "table {{.Names}}\t{{.Status}}"
docs/KiteStacks-Homelab-Documentation-v1.3.884.md
# Restart a service
cd ~/kitestacks-live/docker/SERVICE_NAME && docker compose restart
# View live logs
docker logs CONTAINER_NAME --tail 50 -f
```
All usernames and passwords go through Authentik at `https://auth.kitestacks.com`.

View file

@ -1,221 +1,174 @@
# KiteStacks Homelab — Complete Setup Runbook
# KiteStacks Homelab — How Everything Works
**Last Updated:** 2026-06-18
**Status:** Production (monk primary, kscloud1 Hetzner cloud replica)
**Maintainer:** kenpat
**Status:** Up and running
**Owner:** kenpat
---
## Architecture Overview
## The Big Picture
KiteStacks is a personal homelab — a small set of programs (called "services") that run
on two computers. One computer sits at home (called **monk**), and one rents space in
a data center in Germany (called **kscloud1**).
People on the internet can reach every website without knowing where the computers are,
because all traffic goes through **Cloudflare** — a free service that acts like a secret
post-office. Cloudflare knows the address; the rest of the world doesn't.
```
Internet
You (browser)
└── Cloudflare (DNS + Tunnel)
│ Active-Active across 2 connectors
├── cloudflared on monk (primary home machine, Docker container)
└── cloudflared on kscloud1 (Hetzner VPS, <KSCLOUD1_PUBLIC_IP>)
Tailscale overlay network (VPN mesh):
monk <MONK_TAILSCALE_IP>
kscloud1 <KSCLOUD1_TAILSCALE_IP> ← hosts shared Authentik Postgres + Redis
└─► Cloudflare (the post office)
├─► monk (home machine, runs most services)
└─► kscloud1 (cloud backup machine in Germany)
```
**Public subdomains** route through the same Cloudflare Tunnel token.
Both monk and kscloud1 are connectors so the site stays up if either goes offline.
| Subdomain | Service | Port |
|-----------|---------|------|
| auth.kitestacks.com | Authentik | 9000 |
| portainer.kitestacks.com | Portainer | 9443 |
| wiki.kitestacks.com | BookStack | 6875 (monk) / 6877 (kscloud1) |
| grafana.kitestacks.com | Grafana | 3000 |
| gitforge.kitestacks.com | Forgejo | 3006 |
| links.kitestacks.com | Karakeep | 3100 |
| status.kitestacks.com | Uptime Kuma | 3001 |
| tasks.kitestacks.com | OSTicket | 8080 |
| flux.kitestacks.com | FluxCD | — |
If monk goes offline, kscloud1 keeps serving the sites — Cloudflare automatically
switches traffic over. This is called **active-active** (both doors are always open).
---
## Service Inventory
## What Runs Where
### Running on monk
authentik, authentik-worker, authentik-ldap, authentik-ldap-proxy,
bookstack, bookstack-db, cloudflared, flux, forgejo, grafana,
karakeep, karakeep-chrome, karakeep-meilisearch, kavita,
kite-litellm, kite-openwebui, kitestacks-metrics-api, kitestacks-portal,
node-exporter, ntfy, osticket, osticket-app, osticket-db,
portainer, prometheus, uptime-kuma, blackbox-exporter
### Services on monk
| What it is | What it does | Website |
|------------|--------------|---------|
| Authentik | Login manager — handles all usernames and passwords | auth.kitestacks.com |
| Portainer | Dashboard to manage all the Docker containers | portainer.kitestacks.com |
| Forgejo | Git — stores all the code and scripts | gitforge.kitestacks.com |
| BookStack | Wiki — where all the notes and guides live | wiki.kitestacks.com |
| Grafana | Charts showing how healthy the servers are | grafana.kitestacks.com |
| Karakeep | Saves and organizes bookmarks | links.kitestacks.com |
| Kavita | Reads ebooks and manga | kavita.kitestacks.com |
| OSTicket | Help-desk ticket system | tasks.kitestacks.com |
| Open WebUI | Chat with AI models (GPT, Claude, local models) | ai.kitestacks.com |
| Uptime Kuma | Watches every service and alerts if one goes down | status.kitestacks.com |
| KiteStacks Portal | The main homepage with links to everything | www.kitestacks.com |
### Running on kscloud1 (extras)
bookstack, bookstack-db-ks, kite-monitor, osticket-app-118,
osticket-db-118, www-backup, homepage-backup, cloudflared,
authentik-postgresql, authentik-redis
### Shared infrastructure on kscloud1
- PostgreSQL `:5432` — Authentik DB used by both hosts (Tailscale only)
- Redis `:6379` — Authentik session cache (Tailscale only)
### Services on kscloud1 (cloud backup)
- A copy of BookStack
- A copy of the main Portal
- The login database (PostgreSQL) and session memory (Redis) that Authentik uses
- The Cloudflare connector (so the site keeps running if monk is off)
---
## Cloudflare Tunnel
## Cloudflare Tunnel (the secret post office)
### How it works
Both monk and kscloud1 run `cloudflared` as Docker containers using the **same tunnel token**. Cloudflare load-balances across both connectors (active-active). The tunnel token is stored in:
- monk: `~/kitestacks-live/docker/cloudflared/.env``TUNNEL_TOKEN`
- kscloud1: `/opt/kitestacks/docker/cloudflared/.env``TUNNEL_TOKEN`
### Why it exists
Normal websites need a router setting called "port forwarding" and a fixed home IP address.
Cloudflare Tunnel removes both requirements — monk connects **out** to Cloudflare, and
Cloudflare forwards visitor traffic back in. Your home address is never exposed.
### Fix: Phantom 3rd Replica
If `cloudflared tunnel info` shows 3 connectors instead of 2, the native cloudflared systemd service on monk is running alongside the Docker container.
### How to check it's healthy
Go to Cloudflare Zero Trust → Networks → Tunnels. You should see **2 healthy connectors**
(one from monk, one from kscloud1).
```bash
# Check systemd cloudflared on monk
systemctl status cloudflared
# Disable it — Docker container is the correct one
sudo systemctl disable --now cloudflared
```
### Adding a new hostname route
### Adding a new website
In Cloudflare Zero Trust → Networks → Tunnels → your tunnel → Edit → Public Hostname:
- Subdomain: `newservice`
- Domain: `kitestacks.com`
- Service: `http://container-name:port`
- Service URL: `http://container-name:port`
Both monk and kscloud1 must have the container running on the same port.
Both monk and kscloud1 need to be running that container on the same port.
---
### Fix: If you see 3 connectors instead of 2
The old cloudflared system service on monk is probably running alongside the Docker one.
Run this on monk to fix it:
## Authentik SSO
### Architecture
Authentik uses a **shared database** hosted on kscloud1. monk's Authentik containers connect via Tailscale.
- monk containers: `authentik`, `authentik-worker`, `authentik-ldap`, `authentik-ldap-proxy`
- DB: PostgreSQL on kscloud1 at `<KSCLOUD1_TAILSCALE_IP>:5432`
- Redis: kscloud1 at `<KSCLOUD1_TAILSCALE_IP>:6379`
### Adding OIDC SSO for a new app
1. In Authentik admin (`https://auth.kitestacks.com/if/admin/`):
- **Providers** → Create → OAuth2/OpenID Provider
- Name the provider after the app (e.g. `bookstack`)
- Set `issuer_mode` based on the app's requirements (see Debug doc)
- Note the Client ID and Client Secret
2. **Application** → Create → link to the provider
3. **Policy Binding** → bind the `default-authentication-flow` to the application
4. Configure the app with:
- `OIDC_ISSUER` = discovery base URL
- `OIDC_CLIENT_ID` / `OIDC_CLIENT_SECRET`
- Callback URL = `https://yourapp.kitestacks.com/auth/callback`
### Checking OIDC discovery URL
```bash
# Per-provider (issuer_mode=per_provider)
curl -s https://auth.kitestacks.com/application/o/<slug>/.well-known/openid-configuration | python3 -m json.tool
# Global (issuer_mode=global)
# Note: global issuer URL does NOT serve a JSON discovery doc at /.well-known/
# Use per-provider mode for apps that auto-discover endpoints (BookStack, etc.)
```
### Changing provider issuer_mode via SQL
```bash
docker run --rm --network host \
-e PGPASSWORD="<REDACTED>" \
postgres:16 psql -h <KSCLOUD1_TAILSCALE_IP> -U authentik authentik -c \
"UPDATE authentik_providers_oauth2_oauth2provider SET issuer_mode='per_provider' WHERE provider_ptr_id=<ID>;"
sudo systemctl disable --now cloudflared
```
---
## Portainer
## Authentik (the login manager)
### OAuth setup (Authentik)
Portainer CE uses AuthenticationMethod=3 (OAuth). Configured via the BoltDB.
### What it does
Every website on KiteStacks uses Authentik for login. Instead of each website having its
own username and password, Authentik is the one source of truth. You log in once and
all the websites trust that login. This system is called **SSO** (Single Sign-On).
Key settings:
- `OAuthLoginURI`: `https://auth.kitestacks.com/application/o/authorize/`
- `OAuthTokenURI`: `https://auth.kitestacks.com/application/o/token/`
- `OAuthUserURI`: `https://auth.kitestacks.com/application/o/userinfo/`
- `OAuthClientID`: `portainer`
- `OAuthRedirectURI`: `https://portainer.kitestacks.com`
- `OAuthAutoCreateUsers`: `true`
- `OAuthDefaultTeamID`: `0`
### Where the database lives
Authentik's user database lives on **kscloud1** (not on monk). Both machines share it
through a private encrypted network called **Tailscale**.
### Pre-creating an admin user before first OAuth login
OAuth auto-created users default to Role:2 (regular user) and can't see environments.
Pre-create them as Role:1 (admin) via the API before they log in:
### Adding a new app to SSO
1. Go to `https://auth.kitestacks.com/if/admin/`
2. **Providers** → Create → OAuth2/OpenID Provider
3. Name it after the app (e.g., `myapp`)
4. Note the Client ID and Client Secret
5. **Application** → Create → link it to the provider
6. Set up the app with:
- Login URL (your app's OIDC issuer URL)
- Client ID and Client Secret
- Callback URL: `https://yourapp.kitestacks.com/auth/callback`
---
## Portainer (the container dashboard)
### What it does
Portainer is a web dashboard that shows all running Docker containers. Think of Docker
containers like small self-contained boxes — each one runs one program. Portainer lets
you start, stop, restart, and view logs for all the boxes without typing commands.
### If you get locked out
```bash
# Stop Portainer
docker stop portainer
# Reset the password (the command will print a new temporary password)
docker run --rm -v portainer_data:/data portainer/helper-reset-password
# Start it again
docker start portainer
```
### First-time OAuth login issue
When someone logs into Portainer through Authentik for the first time, they get created
as a regular user (not admin). They won't be able to see any servers. To fix this,
create their account as admin **before** their first login:
```bash
# Get auth token
# Step 1: Get a login token
TOKEN=$(curl -sk -X POST https://portainer.kitestacks.com/api/auth \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"<REDACTED>"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['jwt'])")
-d '{"username":"admin","password":"YOUR_PASSWORD"}' | python3 -c "import sys,json; print(json.load(sys.stdin)['jwt'])")
# Create user as admin (Role:1), no password needed for OAuth users
# Step 2: Create the user as admin (role 1 = admin)
curl -sk -X POST "https://portainer.kitestacks.com/api/users" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"username":"user@example.com","role":1}'
```
### Reset admin password (if locked out)
```bash
# Stop Portainer
docker stop portainer
# Reset password (shows new temp password)
docker run --rm -v portainer_data:/data portainer/helper-reset-password
# Restart
docker start portainer
```
---
## BookStack
## BookStack (the wiki)
### Setup (both monk and kscloud1)
Location:
- monk: `~/kitestacks-live/docker/bookstack/docker-compose.yml`
- kscloud1: `/opt/kitestacks/docker/bookstack/docker-compose.yml`
### What it does
BookStack is a self-hosted wiki — like a private Wikipedia just for this homelab.
All notes, runbooks, and guides live here.
Key environment variables:
```yaml
- APP_URL=https://wiki.kitestacks.com
- DB_HOST=bookstack-db
- AUTH_METHOD=oidc
- OIDC_ISSUER=https://auth.kitestacks.com/application/o/bookstack/
- OIDC_ISSUER_DISCOVER=true
- OIDC_CLIENT_ID=bookstack
- OIDC_CLIENT_SECRET=<REDACTED>
- OIDC_USER_ATTRIBUTE=email
- APP_KEY=<REDACTED>
```
### Important settings
BookStack uses Authentik for login. Two settings must be correct:
### Generate APP_KEY
```bash
docker run --rm --entrypoint /bin/bash lscr.io/linuxserver/bookstack:latest appkey
```
- `OIDC_ISSUER_DISCOVER=true` — tells BookStack to automatically find all login endpoints
- `OIDC_ISSUER` — must point to the per-app Authentik URL, like:
`https://auth.kitestacks.com/application/o/bookstack/`
### OIDC Configuration
BookStack uses `OIDC_ISSUER_DISCOVER=true` to auto-discover all endpoints from Authentik.
The `OIDC_ISSUER` must match the per-app discovery URL base (not the global Authentik URL).
### Fix: If cache breaks after running a PHP command
Sometimes running admin commands inside the container breaks file permissions:
The Authentik bookstack provider must have `issuer_mode='per_provider'` so its discovery
document returns the correct per-app issuer URL. See Debug doc for full troubleshooting.
### Fix cache permissions after artisan runs
Running `php artisan` as root creates root-owned cache dirs that block the app:
```bash
docker exec bookstack chown -R abc:users /config/www/framework/cache/
```
### Clear Laravel config/cache
### Clear BookStack's config cache
```bash
docker exec bookstack php /app/www/artisan config:clear
docker exec bookstack php /app/www/artisan cache:clear
@ -223,27 +176,27 @@ docker exec bookstack php /app/www/artisan cache:clear
---
## kscloud1 Access
## kscloud1 (the cloud backup machine)
### SSH
### SSH access
```bash
ssh -i ~/.ssh/id_ed25519_kscloud1 root@<KSCLOUD1_TAILSCALE_IP>
ssh -i ~/.ssh/id_ed25519_kscloud1 root@KSCLOUD1_TAILSCALE_IP
```
### If SSH key is lost / not working
1. Open Hetzner Cloud console: `console.hetzner.cloud` → your server → Console tab
2. Log in as `root` (Linux user password)
3. Serve the key from monk over Tailscale:
### If you can't SSH in (key was lost)
1. Open Hetzner Cloud console → your server → **Console** tab (this is like a TV remote for the server)
2. Log in as `root` using the Linux root password
3. On monk, share your public SSH key temporarily:
```bash
# On monk — start temporary HTTP server
cat ~/.ssh/id_ed25519_kscloud1.pub > ~/key.txt
python3 -m http.server 7777 --directory ~/
```
4. In Hetzner console, type:
4. In the Hetzner console, type:
```bash
curl http://<MONK_TAILSCALE_IP>:7777/key.txt > /root/.ssh/authorized_keys
curl http://MONK_TAILSCALE_IP:7777/key.txt > /root/.ssh/authorized_keys
```
5. Enable root SSH (if needed):
5. If root SSH is disabled:
```bash
sed -i 's/^#*PermitRootLogin.*/PermitRootLogin prohibit-password/' /etc/ssh/sshd_config
systemctl restart ssh
@ -251,43 +204,67 @@ ssh -i ~/.ssh/id_ed25519_kscloud1 root@<KSCLOUD1_TAILSCALE_IP>
---
## OSTicket SMTP
## OSTicket (help desk)
**Config:** smtp.gmail.com:587, STARTTLS
**From:** `kitestacks.helpdesk@gmail.com` (app password stored in DB)
OSTicket is the ticket/task system at `tasks.kitestacks.com`.
Emails sent to `kitestacks.helpdesk@gmail.com` become tickets automatically.
To test email delivery: Admin Panel → Diagnostics → Send Test Email
To test that email is working: Admin Panel → Diagnostics → Send Test Email
---
## Forgejo
## Forgejo (code storage)
Runs on monk at `localhost:3006` (port 2222 for SSH git).
Forgejo is the Git server — all scripts, configs, and docs live here.
### Generate API token for automation
### Create an API token for automation
```bash
docker exec -u git forgejo forgejo admin user generate-access-token \
--username kenpat --token-name "my-token" --raw \
--username kenpat \
--token-name "my-token" \
--raw \
--scopes "read:user,write:user,read:repository,write:repository"
```
Note: SSH to gitforge.kitestacks.com only works from inside the local network,
not through Cloudflare (Cloudflare blocks non-HTTPS ports).
For git operations from monk, use `ssh://git@localhost:2222/kenpat/repo.git`.
---
## Common Docker Operations
## Everyday Docker Commands
```bash
# View logs for a service
docker logs <container> --tail 50 -f
# See all running containers and their status
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
# View recent logs for a service
docker logs CONTAINER_NAME --tail 50 -f
# Restart a service
cd ~/kitestacks-live/docker/<service> && docker compose restart
cd ~/kitestacks-live/docker/SERVICE_NAME
docker compose restart
# Full stack restart
# Stop and restart a service (harder reset)
docker compose down && docker compose up -d
# Update a container image
# Pull latest image and restart
docker compose pull && docker compose up -d
# Check all running containers
docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
```
---
## Tailscale (the private tunnel between machines)
Tailscale creates an encrypted private network between monk and kscloud1.
Nothing on this network is visible to the public internet.
Used for:
- monk connecting to kscloud1's PostgreSQL and Redis (for Authentik)
- SSH from monk to kscloud1
- Prometheus on monk scraping metrics from kscloud1
To check connection status:
```bash
tailscale status
```

9
autosync/.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
apps/authentik/postgres/
apps/forgejo/data/gitea/gitea.db
apps/kavita/config/*.db
apps/kavita/config/*.db-wal
apps/kavita/config/*.db-shm
apps/linkding/
*.sqlite3
*.sqlite3-wal
*.sqlite3-shm

78
autosync/CHANGELOG.md Normal file
View file

@ -0,0 +1,78 @@
# Changelog
All notable changes to KiteStacks Homelab are documented here.
## [v1.3.7] — 2026-06-06 05:08:38
### Changed
- [autosync] 2026-06-06 05:08:38 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:08:38 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- apps/forgejo/data/gitea/gitea.db
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
## [v1.3.6] — 2026-06-06 05:08:20
### Changed
- [autosync] 2026-06-06 05:08:20 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:08:20 INFO Removed: apps/kavita/config/kavita.db-shm
- [autosync] 2026-06-06 05:08:20 INFO Synced: apps/kavita/config/kavita.db
- [autosync] 2026-06-06 05:08:20 INFO Removed: apps/kavita/config/kavita.db-wal
- [autosync] 2026-06-06 05:08:20 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- [autosync] 2026-06-06 05:08:20 INFO Synced: apps/linkding/data/tasks.sqlite3-wal
- apps/forgejo/data/gitea/gitea.db
- DELETED: apps/kavita/config/kavita.db-shm
- apps/kavita/config/kavita.db
- DELETED: apps/kavita/config/kavita.db-wal
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
- apps/linkding/data/tasks.sqlite3-wal
## [v1.3.5] — 2026-06-06 05:07:39
### Changed
- [autosync] 2026-06-06 05:07:39 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:07:39 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- apps/forgejo/data/gitea/gitea.db
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
## [v1.3.4] — 2026-06-06 05:07:19
### Changed
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17715
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/pg_wal/000000010000000000000002
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17733
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17734
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17735
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/pg_xact/0000
- [autosync] 2026-06-06 05:07:19 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/linkding/data/tasks.sqlite3-wal
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17722
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17720
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/global/pg_control
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/pg_logical/replorigin_checkpoint
- apps/forgejo/data/gitea/gitea.db
- apps/authentik/postgres/base/16384/17715
- apps/authentik/postgres/pg_wal/000000010000000000000002
- apps/authentik/postgres/base/16384/17733
- apps/authentik/postgres/base/16384/17734
- apps/authentik/postgres/base/16384/17735
- apps/authentik/postgres/pg_xact/0000
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
- apps/linkding/data/tasks.sqlite3-wal
- apps/authentik/postgres/base/16384/17722
- apps/authentik/postgres/base/16384/17720
- apps/authentik/postgres/global/pg_control
- apps/authentik/postgres/pg_logical/replorigin_checkpoint
## [v1.3.3] — 2026-06-06 04:54:24
### Changed
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/authentik/postgres/pg_wal/000000010000000000000002
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/kavita/config/kavita.db-wal
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/linkding/data/tasks.sqlite3-wal
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/kavita/config/cache.db-wal
- apps/authentik/postgres/pg_wal/000000010000000000000002
- apps/kavita/config/kavita.db-wal
- apps/linkding/data/tasks.sqlite3-wal
- apps/kavita/config/cache.db-wal

157
autosync/README.md Normal file
View file

@ -0,0 +1,157 @@
╔══════════════════════════════════════════════════════════════════╗
║ KiteStacks Homelab — Auto-Sync Setup Guide ║
╚══════════════════════════════════════════════════════════════════╝
WHAT THIS DOES
──────────────
Watches your Docker app directories for any file changes.
On each change it automatically:
1. Copies the changed file into the correct repo folder
~/docker/homepage/ → apps/homepage/
~/docker/<app>/ → apps/<app>/
/etc/kitestacks/ → clusters/assassin/
2. Bumps the version in README.md
<!-- version: 1.3.7 -->
3. Creates a new versioned doc file
docs/KiteStacks-Homelab-Documentation-v1.3.3.md
4. Appends an entry to CHANGELOG.md
5. Commits with your existing style:
"Automated update: 2026-06-06 03:36:00"
6. Pushes to Forgejo at gitforge.kitestacks.com
It pushes to a TEST repo first so you can verify before
flipping to kenpat/kitestacks-homelab.
FILES
─────
setup.sh ← Run first (as root)
promote.sh ← Run to switch test → main repo
status.sh ← Health check
config/
settings.conf.example
settings.conf ← Your config (fill this in first)
scripts/
kitestacks-watcher.sh ← The daemon (started by systemd)
STEP-BY-STEP
════════════
1. GET A FORGEJO TOKEN
gitforge.kitestacks.com → User Settings → Applications
→ Generate New Token → name it "autosync"
→ Scopes: ✓ repository (read + write)
Copy it — you only see it once.
2. CONFIGURE
cp config/settings.conf.example config/settings.conf
nano config/settings.conf
Required changes:
FORGEJO_TOKEN="your-token-here"
WATCH_DIRS="/home/kenpat/docker /etc/kitestacks"
↑ adjust to match where your app files live
3. RUN SETUP
sudo bash setup.sh
This will:
• Install git, inotify-tools, curl, jq
• Create kitestacks-homelab-autosync-test on Forgejo
• Clone it to /opt/kitestacks-autosync/
• Bootstrap README.md and docs/ at v1.3.0
• Install and start the systemd service
4. VERIFY
bash status.sh
journalctl -u kitestacks-autosync -f
Trigger a test change:
touch /home/kenpat/docker/homepage/test-autosync.txt
Check gitforge.kitestacks.com/kenpat/kitestacks-homelab-autosync-test
You should see:
• A new commit: "Automated update: 2026-06-06 ..."
• apps/homepage/test-autosync.txt added
• README.md version bumped: 1.3.0 → 1.3.1
• docs/KiteStacks-Homelab-Documentation-v1.3.1.md created
• CHANGELOG.md entry added
Clean up the test file:
rm /home/kenpat/docker/homepage/test-autosync.txt
5. PROMOTE TO MAIN REPO
sudo bash promote.sh
Type "promote" to confirm. From this point on, all changes
go to kenpat/kitestacks-homelab.
HOW THE README.md VERSIONING WORKS
════════════════════════════════════
The script tracks versions using an HTML comment tag that is
invisible when the README renders:
# KiteStacks Homelab
<!-- version: 1.3.7 -->
Private GitOps repository for the KiteStacks homelab.
...
This tag is injected automatically on first run (it won't
change how your README looks in the browser).
The docs/ reference line is also kept current:
docs/KiteStacks-Homelab-Documentation-v1.3.4.md
DIRECTORY MAPPING
═════════════════
When a file changes, it's placed in the repo like this:
Server path Repo path
───────────────────────────────── ─────────────────────────────────
~/docker/homepage/config/… apps/homepage/config/…
~/docker/kavita-docker-automation/… apps/kavita-docker-automation/…
/etc/kitestacks/… clusters/assassin/…
MANAGING THE SERVICE
════════════════════
bash status.sh # health check
journalctl -u kitestacks-autosync -f # live logs
sudo systemctl restart kitestacks-autosync # restart
sudo systemctl stop kitestacks-autosync # stop
DEBOUNCE
════════
Default: 15 seconds after the last change before committing.
This means if you save 10 files in 5 seconds, you get ONE commit
not 10. Adjust DEBOUNCE_SECONDS in settings.conf.
TROUBLESHOOTING
═══════════════
Push fails with auth error
→ Check FORGEJO_TOKEN is correct in settings.conf
→ Re-run: sudo bash setup.sh
"Config not found" on start
→ sudo bash setup.sh (it will copy the example for you)
Service crashes
→ journalctl -u kitestacks-autosync -n 50
→ Check WATCH_DIRS path(s) exist
Files not appearing in apps/ subfolder
→ Check WATCH_DIRS in settings.conf matches your actual
Docker compose directory (e.g. /home/kenpat/docker)

View file

@ -0,0 +1,20 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: homepage
namespace: homepage
spec:
replicas: 1
selector:
matchLabels:
app: homepage
template:
metadata:
labels:
app: homepage
spec:
containers:
- name: homepage
image: ghcr.io/yourusername/homepage:latest
ports:
- containerPort: 80

View file

@ -0,0 +1,13 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: homepage
namespace: flux-system
spec:
interval: 1m
path: ./apps/homepage
prune: true
sourceRef:
kind: GitRepository
name: kitestacks-homelab
targetNamespace: homepage

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: homepage

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: homepage
namespace: homepage
spec:
selector:
app: homepage
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

View file

@ -0,0 +1,32 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: kavita-docker-auto-restart
namespace: automation
spec:
schedule: "*/15 * * * *"
successfulJobsHistoryLimit: 2
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
restartPolicy: OnFailure
containers:
- name: restart-kavita
image: docker:27-cli
command:
- /bin/sh
- -c
- |
echo "Restarting Kavita Docker container..."
docker restart kavita
echo "Kavita restart completed."
volumeMounts:
- name: docker-sock
mountPath: /var/run/docker.sock
volumes:
- name: docker-sock
hostPath:
path: /var/run/docker.sock
type: Socket

View file

@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- kavita-docker-restart-cronjob.yaml

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: automation

View file

@ -0,0 +1,20 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: homepage
namespace: homepage
spec:
replicas: 1
selector:
matchLabels:
app: homepage
template:
metadata:
labels:
app: homepage
spec:
containers:
- name: homepage
image: nginx:latest
ports:
- containerPort: 80

View file

@ -0,0 +1,12 @@
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
name: kitestacks-homelab
namespace: flux-system
spec:
interval: 1m
url: https://gitforge.kitestacks.com/kenpat/kitestacks-homelab.git
ref:
branch: main
secretRef:
name: forgejo-auth

View file

@ -0,0 +1,13 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: kitestacks-homelab
namespace: flux-system
spec:
interval: 1m
path: ./apps/kavita-docker-automation
prune: true
sourceRef:
kind: GitRepository
name: kitestacks-homelab
targetNamespace: automation

View file

@ -0,0 +1,13 @@
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: homepage
namespace: flux-system
spec:
interval: 1m
path: ./apps/homepage
prune: true
sourceRef:
kind: GitRepository
name: kitestacks-homelab

View file

@ -0,0 +1,6 @@
apiVersion: kustomize.config.k8s.io/v1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml

View file

@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: homepage

View file

@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: homepage
namespace: homepage
spec:
selector:
app: homepage
ports:
- protocol: TCP
port: 80
targetPort: 80
type: ClusterIP

View file

@ -0,0 +1,32 @@
# KiteStacks Homelab Documentation v1.2
## Current Architecture
KiteStacks Homelab is transitioning from Docker Compose to Kubernetes using K3s.
## Current Server
Hostname: Assassin
Role: Homelab server and Kubernetes control plane
Kubernetes: K3s
Status: Ready
## Current Docker Services
- Homepage
- Kavita
- Forgejo
- Linkding
- Grafana
- Prometheus
- Other supporting services
## Kubernetes Milestone
K3s has been installed successfully.
Verified command:
```bash
kubectl get nodes
assassin Ready control-plane

View file

@ -0,0 +1,48 @@
# KiteStacks Homelab Documentation v1.3.3
**Version:** 1.3.3
**Updated:** 2026-06-06 04:54:24
**Previous:** [v1.3.2 docs](KiteStacks-Homelab-Documentation-v1.3.2.md)
---
## Change Summary
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/authentik/postgres/pg_wal/000000010000000000000002
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/kavita/config/kavita.db-wal
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/linkding/data/tasks.sqlite3-wal
- [autosync] 2026-06-06 04:54:24 INFO Synced: apps/kavita/config/cache.db-wal
- apps/authentik/postgres/pg_wal/000000010000000000000002
- apps/kavita/config/kavita.db-wal
- apps/linkding/data/tasks.sqlite3-wal
- apps/kavita/config/cache.db-wal
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
## All Documentation Versions
- [v1.2](KiteStacks-Homelab-Documentation-v1.2.md)
- [v1.3](KiteStacks-Homelab-Documentation-v1.3.md)
- [v1.3.3](KiteStacks-Homelab-Documentation-v1.3.3.md)

View file

@ -0,0 +1,67 @@
# KiteStacks Homelab Documentation v1.3.4
**Version:** 1.3.4
**Updated:** 2026-06-06 05:07:19
**Previous:** [v1.3.3 docs](KiteStacks-Homelab-Documentation-v1.3.3.md)
---
## Change Summary
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17715
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/pg_wal/000000010000000000000002
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17733
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17734
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17735
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/pg_xact/0000
- [autosync] 2026-06-06 05:07:19 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/linkding/data/tasks.sqlite3-wal
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17722
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/base/16384/17720
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/global/pg_control
- [autosync] 2026-06-06 05:07:19 INFO Synced: apps/authentik/postgres/pg_logical/replorigin_checkpoint
- apps/forgejo/data/gitea/gitea.db
- apps/authentik/postgres/base/16384/17715
- apps/authentik/postgres/pg_wal/000000010000000000000002
- apps/authentik/postgres/base/16384/17733
- apps/authentik/postgres/base/16384/17734
- apps/authentik/postgres/base/16384/17735
- apps/authentik/postgres/pg_xact/0000
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
- apps/linkding/data/tasks.sqlite3-wal
- apps/authentik/postgres/base/16384/17722
- apps/authentik/postgres/base/16384/17720
- apps/authentik/postgres/global/pg_control
- apps/authentik/postgres/pg_logical/replorigin_checkpoint
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
## All Documentation Versions
- [v1.2](KiteStacks-Homelab-Documentation-v1.2.md)
- [v1.3](KiteStacks-Homelab-Documentation-v1.3.md)
- [v1.3.3](KiteStacks-Homelab-Documentation-v1.3.3.md)
- [v1.3.4](KiteStacks-Homelab-Documentation-v1.3.4.md)

View file

@ -0,0 +1,46 @@
# KiteStacks Homelab Documentation v1.3.5
**Version:** 1.3.5
**Updated:** 2026-06-06 05:07:39
**Previous:** [v1.3.4 docs](KiteStacks-Homelab-Documentation-v1.3.4.md)
---
## Change Summary
- [autosync] 2026-06-06 05:07:39 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:07:39 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- apps/forgejo/data/gitea/gitea.db
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
## All Documentation Versions
- [v1.2](KiteStacks-Homelab-Documentation-v1.2.md)
- [v1.3](KiteStacks-Homelab-Documentation-v1.3.md)
- [v1.3.3](KiteStacks-Homelab-Documentation-v1.3.3.md)
- [v1.3.4](KiteStacks-Homelab-Documentation-v1.3.4.md)
- [v1.3.5](KiteStacks-Homelab-Documentation-v1.3.5.md)

View file

@ -0,0 +1,55 @@
# KiteStacks Homelab Documentation v1.3.6
**Version:** 1.3.6
**Updated:** 2026-06-06 05:08:20
**Previous:** [v1.3.5 docs](KiteStacks-Homelab-Documentation-v1.3.5.md)
---
## Change Summary
- [autosync] 2026-06-06 05:08:20 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:08:20 INFO Removed: apps/kavita/config/kavita.db-shm
- [autosync] 2026-06-06 05:08:20 INFO Synced: apps/kavita/config/kavita.db
- [autosync] 2026-06-06 05:08:20 INFO Removed: apps/kavita/config/kavita.db-wal
- [autosync] 2026-06-06 05:08:20 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- [autosync] 2026-06-06 05:08:20 INFO Synced: apps/linkding/data/tasks.sqlite3-wal
- apps/forgejo/data/gitea/gitea.db
- DELETED: apps/kavita/config/kavita.db-shm
- apps/kavita/config/kavita.db
- DELETED: apps/kavita/config/kavita.db-wal
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
- apps/linkding/data/tasks.sqlite3-wal
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
## All Documentation Versions
- [v1.2](KiteStacks-Homelab-Documentation-v1.2.md)
- [v1.3](KiteStacks-Homelab-Documentation-v1.3.md)
- [v1.3.3](KiteStacks-Homelab-Documentation-v1.3.3.md)
- [v1.3.4](KiteStacks-Homelab-Documentation-v1.3.4.md)
- [v1.3.5](KiteStacks-Homelab-Documentation-v1.3.5.md)
- [v1.3.6](KiteStacks-Homelab-Documentation-v1.3.6.md)

View file

@ -0,0 +1,48 @@
# KiteStacks Homelab Documentation v1.3.7
**Version:** 1.3.7
**Updated:** 2026-06-06 05:08:38
**Previous:** [v1.3.6 docs](KiteStacks-Homelab-Documentation-v1.3.6.md)
---
## Change Summary
- [autosync] 2026-06-06 05:08:38 INFO Synced: apps/forgejo/data/gitea/gitea.db
- [autosync] 2026-06-06 05:08:38 INFO Removed: apps/forgejo/data/gitea/gitea.db-journal
- apps/forgejo/data/gitea/gitea.db
- DELETED: apps/forgejo/data/gitea/gitea.db-journal
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
## All Documentation Versions
- [v1.2](KiteStacks-Homelab-Documentation-v1.2.md)
- [v1.3](KiteStacks-Homelab-Documentation-v1.3.md)
- [v1.3.3](KiteStacks-Homelab-Documentation-v1.3.3.md)
- [v1.3.4](KiteStacks-Homelab-Documentation-v1.3.4.md)
- [v1.3.5](KiteStacks-Homelab-Documentation-v1.3.5.md)
- [v1.3.6](KiteStacks-Homelab-Documentation-v1.3.6.md)
- [v1.3.7](KiteStacks-Homelab-Documentation-v1.3.7.md)

View file

@ -0,0 +1,339 @@
# KiteStacks Homelab Documentation v1.3
## Overview
KiteStacks Homelab is a self-hosted infrastructure platform designed around Kubernetes, GitOps, observability, AI services, digital libraries, productivity applications, and identity management.
The homelab is currently operating in a hybrid state:
* Existing applications continue running in Docker
* Kubernetes (K3s) has been deployed successfully
* Forgejo serves as the private Git platform
* GitOps repository has been established
* Migration to Kubernetes will occur incrementally
---
# Infrastructure
## Primary Server
Hostname:
```text
assassin
```
Role:
```text
Primary Homelab Server
Kubernetes Control Plane
Docker Host
GitOps Management Node
```
Operating System:
```text
Debian 13
```
---
# Kubernetes
## Distribution
```text
K3s
```
Version:
```text
v1.35.5+k3s1
```
Status:
```text
Ready
```
Verification:
```bash
kubectl get nodes
```
Expected Result:
```text
assassin Ready control-plane
```
---
## Kubernetes Components
Currently Running:
* CoreDNS
* Metrics Server
* Local Path Provisioner
* Traefik Ingress Controller
Future Components:
* FluxCD
* Longhorn
* Cert-Manager
* Cloudflare Tunnel
* Authentik
* Prometheus Operator
---
# GitOps
## Git Platform
```text
Forgejo
```
Repository:
```text
https://gitforge.kitestacks.com/kenpat/kitestacks-homelab.git
```
Visibility:
```text
Private
```
Purpose:
* Infrastructure as Code
* Kubernetes manifests
* Documentation
* Application deployment
* Disaster recovery
---
## Repository Structure
```text
kitestacks-homelab
├── apps
├── clusters
│ └── assassin
├── docs
├── infrastructure
└── media
```
---
# Current Docker Applications
## Dashboard
* Homepage
## Library Services
* Kavita
## Knowledge Management
* Linkding
## Source Control
* Forgejo
## Monitoring
* Grafana
* Prometheus
---
# Planned Services
## Identity
* Authentik
## AI
* Open WebUI
* LiteLLM
## Productivity
* Plane
* OpenProject (evaluation)
## Media
* Audiobookshelf
---
# Kavita
## Library Location
```text
/home/kenpat/library/books
```
## Current Status
Running in Docker.
Future migration planned to Kubernetes.
---
# Cloudflare
## Domain
```text
kitestacks.com
```
Current usage:
* Reverse proxy
* Public application access
* DNS management
Future usage:
* Kubernetes ingress
* Cloudflare Tunnel
* Zero Trust
---
# Monitoring Strategy
Primary Monitoring:
* Grafana
Metrics Collection:
* Prometheus
Future Monitoring:
* Kubernetes cluster metrics
* Longhorn metrics
* Application metrics
* Node metrics
---
# Migration Roadmap
## Phase 1 (Completed)
* Docker Homelab
* Forgejo Deployment
* K3s Installation
* GitOps Repository Creation
## Phase 2 (Current)
* FluxCD Installation
* GitOps Bootstrap
* Longhorn Deployment
## Phase 3
* Homepage Migration
* Linkding Migration
## Phase 4
* Kavita Migration
* Forgejo Migration
## Phase 5
* Grafana Migration
* Prometheus Migration
## Phase 6
* Authentik Deployment
* Open WebUI Deployment
* LiteLLM Deployment
---
# Backup Strategy
Planned:
* Git repository backups
* Application volume backups
* Longhorn snapshots
* Off-site backups
---
# Version History
## Version 1.1
Initial Homelab Documentation
## Version 1.2
K3s Installation Milestone
## Version 1.3
Forgejo GitOps Repository Established
K3s Operational
Documentation Baseline Created
Ready for FluxCD Deployment
---
# Current Status
Homelab State:
```text
Operational
```
Kubernetes State:
```text
Ready
```
GitOps State:
```text
Repository Created
Awaiting FluxCD Integration
```
Next Milestone:
```text
FluxCD Installation and Forgejo Integration
```

102
autosync/promote.sh Normal file
View file

@ -0,0 +1,102 @@
#!/bin/bash
# =============================================================================
# promote.sh
# Flips the watcher from TEST repo → kenpat/kitestacks-homelab (main).
# Run after verifying the test repo looks correct in Forgejo.
# =============================================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config/settings.conf"
STATE_FILE="/opt/kitestacks-autosync/.active_target"
WORK_DIR="/opt/kitestacks-autosync"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
[[ $EUID -ne 0 ]] && error "Run as root: sudo bash promote.sh"
source "$CONFIG_FILE"
CURRENT="$(cat "$STATE_FILE" 2>/dev/null || echo "test")"
if [[ "$CURRENT" == "main" ]]; then
warn "Already targeting kenpat/kitestacks-homelab. Nothing to do."
exit 0
fi
echo ""
warn "┌──────────────────────────────────────────────────────────────┐"
warn "│ PROMOTE: test repo → kenpat/kitestacks-homelab │"
warn "│ │"
warn "│ From: ${FORGEJO_URL}/${FORGEJO_USER}/${TEST_REPO}"
warn "│ To : ${FORGEJO_URL}/${FORGEJO_USER}/${MAIN_REPO}"
warn "│ │"
warn "│ All future auto-commits will go to the MAIN repo. │"
warn "└──────────────────────────────────────────────────────────────┘"
echo ""
read -rp "Have you verified the test repo looks correct? Type 'promote' to confirm: " CONFIRM
[[ "$CONFIRM" != "promote" ]] && { info "Aborted."; exit 0; }
# ── Set up remote prefix ──────────────────────────────────────────────────────
if [[ "$AUTH_METHOD" == "ssh" ]]; then
REMOTE_BASE="git@gitforge.kitestacks.com:${FORGEJO_USER}"
else
REMOTE_BASE="https://gitforge.kitestacks.com/${FORGEJO_USER}"
fi
# ── Clone main repo if not already present ────────────────────────────────────
MAIN_DIR="$WORK_DIR/$MAIN_REPO"
if [[ ! -d "$MAIN_DIR/.git" ]]; then
info "Cloning kenpat/kitestacks-homelab..."
git clone "${REMOTE_BASE}/${MAIN_REPO}.git" "$MAIN_DIR"
fi
cd "$MAIN_DIR"
git config user.email "$GIT_EMAIL"
git config user.name "$GIT_NAME"
git pull --rebase origin HEAD 2>/dev/null || true
# ── Add version tag to main README.md if not already there ───────────────────
if ! grep -q '<!-- version:' "$MAIN_DIR/README.md" 2>/dev/null; then
info "Injecting version tag into main README.md..."
# Get the latest version from the test repo
TEST_VER=$(grep -oP '(?<=version:\s)[\d.]+' "$WORK_DIR/$TEST_REPO/README.md" 2>/dev/null | head -1 || echo "1.3.0")
# Inject after the first H1
sed -i "0,/^# /{s|^# \(.*\)|# \1\n\n<!-- version: $TEST_VER -->|}" "$MAIN_DIR/README.md"
# Update the docs reference line
sed -i "s|docs/KiteStacks-Homelab-Documentation-v.*\.md|docs/KiteStacks-Homelab-Documentation-v${TEST_VER}.md|" "$MAIN_DIR/README.md" || true
# Copy CHANGELOG.md and docs from test repo if main doesn't have them
if [[ ! -f "$MAIN_DIR/CHANGELOG.md" ]]; then
cp "$WORK_DIR/$TEST_REPO/CHANGELOG.md" "$MAIN_DIR/CHANGELOG.md" 2>/dev/null || true
fi
if [[ ! -d "$MAIN_DIR/docs" ]]; then
cp -r "$WORK_DIR/$TEST_REPO/docs" "$MAIN_DIR/docs" 2>/dev/null || true
fi
git add -A
git commit -m "chore: inject autosync version tracking — promoted from test"
git push origin HEAD
info "Main repo prepared for autosync."
fi
# ── Flip state ────────────────────────────────────────────────────────────────
echo "main" > "$STATE_FILE"
info "Active target → main (kenpat/kitestacks-homelab)"
# ── Restart service ───────────────────────────────────────────────────────────
info "Restarting kitestacks-autosync service..."
systemctl restart kitestacks-autosync
sleep 2
systemctl status kitestacks-autosync --no-pager
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
info "Promotion complete!"
info " Now watching → ${FORGEJO_URL}/${FORGEJO_USER}/${MAIN_REPO}"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"

View file

@ -0,0 +1,319 @@
#!/bin/bash
# =============================================================================
# kitestacks-watcher.sh
# Runs as a systemd service. Watches configured directories, then on change:
# 1. Pulls latest from Forgejo (avoid conflicts)
# 2. Copies changed files into the correct apps/ or clusters/ folder
# 3. Bumps the version in README.md and the docs/ changelog file
# 4. Commits with a descriptive message
# 5. Pushes to active repo (test first, then main after promote.sh)
# =============================================================================
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config/settings.conf"
STATE_FILE="/opt/kitestacks-autosync/.active_target"
WORK_DIR="/opt/kitestacks-autosync"
log() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') INFO $*"; }
warn() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') WARN $*"; }
err() { echo "[autosync] $(date '+%Y-%m-%d %H:%M:%S') ERROR $*" >&2; }
# ── Load config ──────────────────────────────────────────────────────────────
[[ ! -f "$CONFIG_FILE" ]] && { err "Config not found: $CONFIG_FILE"; exit 1; }
source "$CONFIG_FILE"
# ── Helpers ───────────────────────────────────────────────────────────────────
active_target() { cat "$STATE_FILE" 2>/dev/null || echo "test"; }
active_repo() {
[[ "$(active_target)" == "main" ]] && echo "$MAIN_REPO" || echo "$TEST_REPO"
}
repo_dir() { echo "$WORK_DIR/$(active_repo)"; }
# ── Version management ────────────────────────────────────────────────────────
read_version() {
local readme="$(repo_dir)/README.md"
# Look for a line like: <!-- version: 1.3.2 --> or **Version:** 1.3.2
local ver
ver=$(grep -oP '(?<=version:\s)[\d.]+' "$readme" 2>/dev/null | head -1)
[[ -z "$ver" ]] && ver=$(grep -oP '\d+\.\d+\.\d+' "$readme" 2>/dev/null | head -1)
echo "${ver:-$VERSION_SEED}"
}
bump_version() {
local ver="$1"
IFS='.' read -ra p <<< "$ver"
local maj="${p[0]:-1}" min="${p[1]:-3}" pat="${p[2]:-0}"
case "${VERSION_BUMP:-patch}" in
major) echo "$((maj+1)).0.0" ;;
minor) echo "${maj}.$((min+1)).0" ;;
*) echo "${maj}.${min}.$((pat+1))" ;;
esac
}
# ── README.md update ──────────────────────────────────────────────────────────
# Maintains the KiteStacks README.md format with version tracking.
update_readme() {
local dir="$1" new_ver="$2" old_ver="$3"
local readme="$dir/README.md"
local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
# If version comment tag exists, update it; otherwise append one after title
if grep -q '<!-- version:' "$readme" 2>/dev/null; then
sed -i "s|<!-- version:.*-->|<!-- version: $new_ver -->|" "$readme"
else
# Inject version tag after the first H1 line
sed -i "0,/^# /{s|^# \(.*\)|# \1\n\n<!-- version: $new_ver -->|}" "$readme"
fi
log "README.md version tag: $old_ver → $new_ver"
}
# ── Docs changelog update ─────────────────────────────────────────────────────
# Updates docs/KiteStacks-Homelab-Documentation-v<version>.md
# Creates a new versioned doc file and a CHANGELOG.md entry.
update_docs() {
local dir="$1" new_ver="$2" changed_files="$3"
local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')"
local docs_dir="$dir/docs"
mkdir -p "$docs_dir"
# ── New versioned doc file ────────────────────────────────────────────────
local doc_file="$docs_dir/KiteStacks-Homelab-Documentation-v${new_ver}.md"
cat > "$doc_file" <<EOF
# KiteStacks Homelab Documentation v${new_ver}
**Version:** ${new_ver}
**Updated:** ${ts}
**Previous:** [v${2%.*}.$(( ${new_ver##*.} - 1 )) docs](KiteStacks-Homelab-Documentation-v${2%.*}.$(( ${new_ver##*.} - 1 )).md)
---
## Change Summary
$(echo "$changed_files" | sed 's/^/- /')
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
## All Documentation Versions
$(ls "$docs_dir"/KiteStacks-Homelab-Documentation-v*.md 2>/dev/null \
| sort -V \
| while read f; do
v=$(basename "$f" .md | grep -oP '[\d.]+$')
echo "- [v${v}]($(basename "$f"))"
done)
EOF
log "Created doc file: $(basename "$doc_file")"
# ── CHANGELOG.md ─────────────────────────────────────────────────────────
local changelog="$dir/CHANGELOG.md"
if [[ ! -f "$changelog" ]]; then
echo -e "# Changelog\n\nAll notable changes to KiteStacks Homelab are documented here.\n" > "$changelog"
fi
# Prepend new entry after the header (after line 3)
local entry
entry=$(cat <<EOF
## [v${new_ver}] — ${ts}
### Changed
$(echo "$changed_files" | sed 's/^/- /')
EOF
)
# Insert after line 3 of the changelog
local tmp; tmp=$(mktemp)
head -3 "$changelog" > "$tmp"
echo "$entry" >> "$tmp"
tail -n +4 "$changelog" >> "$tmp"
mv "$tmp" "$changelog"
log "CHANGELOG.md updated with v${new_ver}"
}
# ── Map a filesystem path to a repo subfolder ─────────────────────────────────
# Files from ~/docker/homepage → apps/homepage/
# Files from ~/docker/ → apps/<dirname>/
# Files from /etc/kitestacks/ → clusters/assassin/
map_to_repo_path() {
local src="$1"
local rel=""
for watch in $WATCH_DIRS; do
if [[ "$src" == "$watch"* ]]; then
rel="${src#$watch/}"
# Top-level dir under watch becomes the app folder
local top; top=$(echo "$rel" | cut -d'/' -f1)
if [[ "$watch" == *docker* ]]; then
echo "apps/${rel}"
else
echo "clusters/assassin/${rel}"
fi
return
fi
done
# Fallback
echo "server-files/${src#/}"
}
# ── Sync changed files into the workspace ─────────────────────────────────────
sync_files() {
local repo="$1"; shift
local files=("$@")
local synced=()
for src in "${files[@]}"; do
local dest_rel; dest_rel="$(map_to_repo_path "$src")"
local dest="$repo/$dest_rel"
mkdir -p "$(dirname "$dest")"
if [[ -f "$src" ]]; then
cp -p "$src" "$dest"
synced+=("$dest_rel")
log "Synced: $dest_rel"
else
rm -f "$dest"
synced+=("DELETED: $dest_rel")
log "Removed: $dest_rel"
fi
done
printf '%s\n' "${synced[@]}"
}
# ── Commit and push ───────────────────────────────────────────────────────────
commit_and_push() {
local dir="$1" version="$2" file_count="$3"
cd "$dir"
git add -A
if git diff --cached --quiet; then
log "Nothing to commit."
return 0
fi
local target; target="$(active_target)"
local msg="Automated update: $(date '+%Y-%m-%d %H:%M:%S')"
# Match the commit style already in your repo: "Automated update: YYYY-MM-DD HH:MM:SS"
git commit -m "$msg"
log "Pushing to ${target} repo ($(active_repo))..."
if git push origin HEAD 2>&1; then
log "✓ Push OK — v${version}"
else
err "Push failed. Will retry on next change."
return 1
fi
}
# ── Should this path be skipped? ─────────────────────────────────────────────
is_excluded() {
local path="$1"
for pat in $EXCLUDE_PATTERNS; do
case "$path" in $pat) return 0 ;; esac
done
return 1
}
# ════════════════════════════════════════════════════════════════════════════
# MAIN
# ════════════════════════════════════════════════════════════════════════════
mkdir -p "$WORK_DIR"
log "KiteStacks AutoSync starting up"
log "Active target : $(active_target) → $(active_repo)"
log "Watching : $WATCH_DIRS"
log "Debounce : ${DEBOUNCE_SECONDS}s"
# Pull latest before we start watching
RDIR="$(repo_dir)"
if [[ -d "$RDIR/.git" ]]; then
cd "$RDIR"
git pull --rebase origin HEAD 2>/dev/null && log "Pulled latest from remote." || warn "Pull failed — continuing anyway."
fi
declare -A PENDING
LAST_EVENT=0
# Build the inotifywait argument list from WATCH_DIRS
IFS=' ' read -ra WATCH_ARRAY <<< "$WATCH_DIRS"
inotifywait -m -r \
-e modify,create,delete,moved_to,moved_from \
--format '%w%f' \
--exclude '\.git' \
"${WATCH_ARRAY[@]}" 2>/dev/null |
while IFS= read -r changed; do
is_excluded "$changed" && continue
PENDING["$changed"]=1
LAST_EVENT=$(date +%s)
# ── Debounce loop ─────────────────────────────────────────────────────────
while true; do
sleep 1
ELAPSED=$(( $(date +%s) - LAST_EVENT ))
[[ $ELAPSED -ge ${DEBOUNCE_SECONDS:-15} ]] && break
# Drain any additional events that arrived during sleep
while IFS= read -r -t 0.1 extra; do
is_excluded "$extra" && continue
PENDING["$extra"]=1
LAST_EVENT=$(date +%s)
done
done
[[ ${#PENDING[@]} -eq 0 ]] && continue
log "━━ Batch of ${#PENDING[@]} change(s) detected ━━"
RDIR="$(repo_dir)"
OLD_VER="$(read_version)"
NEW_VER="$(bump_version "$OLD_VER")"
# Pull before committing
cd "$RDIR"
git pull --rebase origin HEAD 2>/dev/null || warn "Pre-commit pull failed."
# Sync files and capture the list of repo-relative paths
CHANGED_LIST="$(sync_files "$RDIR" "${!PENDING[@]}")"
update_readme "$RDIR" "$NEW_VER" "$OLD_VER"
update_docs "$RDIR" "$NEW_VER" "$CHANGED_LIST"
commit_and_push "$RDIR" "$NEW_VER" "${#PENDING[@]}"
# Clear batch
unset PENDING
declare -A PENDING
done

244
autosync/setup.sh Normal file
View file

@ -0,0 +1,244 @@
#!/bin/bash
# =============================================================================
# KiteStacks HomelaB — Auto-Sync Setup
# Run once as root: sudo bash setup.sh
# =============================================================================
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config/settings.conf"
WORK_DIR="/opt/kitestacks-autosync"
STATE_FILE="$WORK_DIR/.active_target"
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
info() { echo -e "${GREEN}[INFO]${NC} $1"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
error() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
step() { echo -e "\n${CYAN}══ $1 ══${NC}"; }
[[ $EUID -ne 0 ]] && error "Run as root: sudo bash setup.sh"
# ── Config check ─────────────────────────────────────────────────────────────
if [[ ! -f "$CONFIG_FILE" ]]; then
info "Copying example config..."
cp "$SCRIPT_DIR/config/settings.conf.example" "$CONFIG_FILE"
warn "Please edit $CONFIG_FILE and set FORGEJO_TOKEN and WATCH_DIRS, then re-run."
exit 0
fi
source "$CONFIG_FILE"
[[ "$FORGEJO_TOKEN" == "PASTE_YOUR_TOKEN_HERE" ]] && \
error "You haven't set FORGEJO_TOKEN in config/settings.conf yet."
[[ -z "$FORGEJO_TOKEN" ]] && error "FORGEJO_TOKEN is empty in config/settings.conf."
# ── Dependencies ─────────────────────────────────────────────────────────────
step "Installing dependencies"
apt-get update -qq
apt-get install -y -qq git inotify-tools curl jq
info "Dependencies installed."
# ── Configure git credentials ─────────────────────────────────────────────────
step "Configuring git credentials"
if [[ "$AUTH_METHOD" == "ssh" ]]; then
if [[ ! -f "$SSH_KEY_PATH" ]]; then
info "Generating SSH key at $SSH_KEY_PATH..."
ssh-keygen -t ed25519 -C "kitestacks-autosync@$(hostname)" -f "$SSH_KEY_PATH" -N ""
echo ""
warn "Add this public key to Forgejo:"
warn " gitforge.kitestacks.com → User Settings → SSH / GPG Keys → Add Key"
echo "────────────────────────────────────────────────────────"
cat "${SSH_KEY_PATH}.pub"
echo "────────────────────────────────────────────────────────"
read -rp "Press Enter after adding the key to Forgejo..."
fi
REMOTE_BASE="git@gitforge.kitestacks.com:${FORGEJO_USER}"
else
# HTTPS token auth
git config --global credential.helper store
# Write credential (idempotent — grep prevents duplicates)
local CRED_LINE="https://${FORGEJO_USER}:${FORGEJO_TOKEN}@gitforge.kitestacks.com"
grep -qF "$CRED_LINE" ~/.git-credentials 2>/dev/null || echo "$CRED_LINE" >> ~/.git-credentials
REMOTE_BASE="https://gitforge.kitestacks.com/${FORGEJO_USER}"
fi
info "Credentials configured (method: $AUTH_METHOD)"
# ── Verify Forgejo API ────────────────────────────────────────────────────────
step "Verifying Forgejo connectivity"
HTTP=$(curl -s -o /dev/null -w "%{http_code}" --max-time 8 \
-H "Authorization: token $FORGEJO_TOKEN" \
"${FORGEJO_URL}/api/v1/user")
[[ "$HTTP" != "200" ]] && error "Forgejo API returned HTTP $HTTP — check FORGEJO_URL and FORGEJO_TOKEN."
info "Forgejo API reachable ✓"
# ── Create test repo on Forgejo if needed ─────────────────────────────────────
step "Setting up test repo: $TEST_REPO"
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Authorization: token $FORGEJO_TOKEN" \
"${FORGEJO_URL}/api/v1/repos/${FORGEJO_USER}/${TEST_REPO}")
if [[ "$HTTP" == "404" ]]; then
info "Creating test repo on Forgejo..."
RESULT=$(curl -s -X POST \
-H "Authorization: token $FORGEJO_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"${TEST_REPO}\",
\"description\": \"AutoSync test — KiteStacks Homelab\",
\"private\": true,
\"auto_init\": true,
\"default_branch\": \"main\"
}" \
"${FORGEJO_URL}/api/v1/user/repos")
URL=$(echo "$RESULT" | jq -r '.html_url // empty')
[[ -n "$URL" ]] && info "Test repo created: $URL" || warn "Repo creation response was unexpected — check Forgejo."
else
info "Test repo already exists."
fi
# ── Clone repos ───────────────────────────────────────────────────────────────
step "Cloning repos into $WORK_DIR"
mkdir -p "$WORK_DIR"
# Test repo
TEST_DIR="$WORK_DIR/$TEST_REPO"
if [[ ! -d "$TEST_DIR/.git" ]]; then
info "Cloning test repo..."
git clone "${REMOTE_BASE}/${TEST_REPO}.git" "$TEST_DIR"
else
info "Test repo already cloned."
fi
cd "$TEST_DIR"
git config user.email "$GIT_EMAIL"
git config user.name "$GIT_NAME"
# ── Bootstrap README.md from main repo content ───────────────────────────────
step "Bootstrapping README.md in test repo"
if [[ ! -f "$TEST_DIR/README.md" ]] || ! grep -q 'version:' "$TEST_DIR/README.md" 2>/dev/null; then
cat > "$TEST_DIR/README.md" <<'EOF'
# KiteStacks Homelab
<!-- version: 1.3.0 -->
Private GitOps repository for the KiteStacks homelab.
## Cluster
- K3s
- FluxCD (planned)
- Longhorn (planned)
## Applications
- Homepage
- Kavita
- Linkding
- Forgejo
- Grafana
- Prometheus
- Authentik
- Open WebUI
- LiteLLM
## Documentation
docs/KiteStacks-Homelab-Documentation-v1.3.0.md
EOF
mkdir -p "$TEST_DIR/docs"
cat > "$TEST_DIR/docs/KiteStacks-Homelab-Documentation-v1.3.0.md" <<'EOF'
# KiteStacks Homelab Documentation v1.3.0
**Version:** 1.3.0
**Updated:** Initial autosync bootstrap
---
## Cluster
| Component | Status |
|-----------|--------|
| K3s | Active |
| FluxCD | Planned |
| Longhorn | Planned |
## Applications
| App | Path |
|-----|------|
| Homepage | apps/homepage/ |
| Kavita | apps/kavita-docker-automation/ |
| Linkding | apps/linkding/ |
| Forgejo | apps/forgejo/ |
| Grafana | apps/grafana/ |
| Prometheus | apps/prometheus/ |
| Authentik | apps/authentik/ |
| Open WebUI | apps/open-webui/ |
| LiteLLM | apps/litellm/ |
EOF
cat > "$TEST_DIR/CHANGELOG.md" <<'EOF'
# Changelog
All notable changes to KiteStacks Homelab are documented here.
## [v1.3.0] — Initial autosync bootstrap
- Automated sync system installed
EOF
git add -A
git commit -m "chore: bootstrap autosync README and docs v1.3.0"
git push origin HEAD
info "Test repo bootstrapped with v1.3.0 content."
else
info "README.md already has version tag — skipping bootstrap."
fi
# ── Set initial state to test ─────────────────────────────────────────────────
echo "test" > "$STATE_FILE"
# ── Install systemd service ───────────────────────────────────────────────────
step "Installing systemd service"
cat > /etc/systemd/system/kitestacks-autosync.service <<EOF
[Unit]
Description=KiteStacks HomelaB Auto-Sync
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/bin/bash ${SCRIPT_DIR}/scripts/kitestacks-watcher.sh
Restart=on-failure
RestartSec=15
StandardOutput=journal
StandardError=journal
SyslogIdentifier=kitestacks-autosync
Environment=HOME=/root
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable kitestacks-autosync
systemctl restart kitestacks-autosync
sleep 2
info "Service installed and started."
systemctl status kitestacks-autosync --no-pager || true
echo ""
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
info "Setup complete!"
echo ""
info " Test repo : ${FORGEJO_URL}/${FORGEJO_USER}/${TEST_REPO}"
info " Watching : $WATCH_DIRS"
info " Live logs : journalctl -u kitestacks-autosync -f"
info " Status : bash status.sh"
echo ""
warn "Next step: trigger a test change, then check the test repo."
warn "Once verified, run: sudo bash promote.sh"
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"

85
autosync/status.sh Normal file
View file

@ -0,0 +1,85 @@
#!/bin/bash
# =============================================================================
# status.sh — KiteStacks AutoSync health check
# =============================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/config/settings.conf"
STATE_FILE="/opt/kitestacks-autosync/.active_target"
WORK_DIR="/opt/kitestacks-autosync"
BOLD='\033[1m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
RED='\033[0;31m'; CYAN='\033[0;36m'; NC='\033[0m'
ok() { echo -e " ${GREEN}${NC} $1"; }
fail() { echo -e " ${RED}${NC} $1"; }
info() { echo -e " ${CYAN}·${NC} $1"; }
source "$CONFIG_FILE" 2>/dev/null || { echo "Config not found at $CONFIG_FILE"; exit 1; }
echo -e "\n${BOLD}╔══ KiteStacks AutoSync Status ════════════════════════════════╗${NC}"
# ── Service ───────────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Service${NC}"
if systemctl is-active --quiet kitestacks-autosync 2>/dev/null; then
ok "kitestacks-autosync is running"
else
fail "kitestacks-autosync is NOT running"
echo " → sudo systemctl start kitestacks-autosync"
fi
systemctl is-enabled --quiet kitestacks-autosync 2>/dev/null \
&& ok "Enabled at boot" || info "Not enabled at boot"
# ── Active target ─────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Active Target${NC}"
TARGET="$(cat "$STATE_FILE" 2>/dev/null || echo "test")"
if [[ "$TARGET" == "main" ]]; then
ok "→ MAIN: ${FORGEJO_URL}/${FORGEJO_USER}/${MAIN_REPO}"
else
info "→ TEST: ${FORGEJO_URL}/${FORGEJO_USER}/${TEST_REPO}"
info " (run 'sudo bash promote.sh' when you're happy with the test repo)"
fi
# ── Workspace ─────────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Workspace${NC}"
RNAME="$([[ "$TARGET" == "main" ]] && echo "$MAIN_REPO" || echo "$TEST_REPO")"
RDIR="$WORK_DIR/$RNAME"
if [[ -d "$RDIR/.git" ]]; then
ok "Cloned: $RDIR"
VER=$(grep -oP '(?<=version:\s)[\d.]+' "$RDIR/README.md" 2>/dev/null | head -1 || echo "unknown")
info "Current version : $VER"
LAST=$(cd "$RDIR" && git log -1 --format="%h %s (%ar)" 2>/dev/null)
info "Last commit : $LAST"
DOC_COUNT=$(ls "$RDIR/docs/"KiteStacks-Homelab-Documentation-v*.md 2>/dev/null | wc -l)
info "Doc versions : $DOC_COUNT file(s) in docs/"
else
fail "Workspace not found — run setup.sh first"
fi
# ── Forgejo API ───────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Forgejo Connectivity${NC}"
HTTP=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 \
-H "Authorization: token $FORGEJO_TOKEN" \
"${FORGEJO_URL}/api/v1/user" 2>/dev/null)
[[ "$HTTP" == "200" ]] && ok "API reachable ($FORGEJO_URL)" || fail "API returned HTTP $HTTP"
# ── Watch dirs ────────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Watched Directories${NC}"
for d in $WATCH_DIRS; do
if [[ -d "$d" ]]; then
COUNT=$(find "$d" -type f 2>/dev/null | wc -l)
ok "$d ($COUNT files)"
else
fail "$d (does not exist)"
fi
done
# ── Recent logs ───────────────────────────────────────────────────────────────
echo -e "\n${BOLD}Recent Log Entries (last 10)${NC}"
journalctl -u kitestacks-autosync -n 10 --no-pager -q 2>/dev/null \
| sed 's/^/ /' \
|| info "No journal entries yet."
echo -e "\n${BOLD}╚══════════════════════════════════════════════════════════════╝${NC}"
echo -e " Live logs: ${CYAN}journalctl -u kitestacks-autosync -f${NC}\n"

2
cloud-migration/.gitattributes vendored Normal file
View file

@ -0,0 +1,2 @@
archives/*.tar.gz filter=lfs diff=lfs merge=lfs -text
volume-exports/*.tar.gz filter=lfs diff=lfs merge=lfs -text

42
cloud-migration/README.md Normal file
View file

@ -0,0 +1,42 @@
# KiteStacks Cloud Migration Backup
Created for migrating the current KiteStacks host to Hetzner Cloud.
## Contents
- `archives/docker-bind-data.tar.gz` - `/home/kenpat/docker` bind-mounted service folders, including compose files, `.env` files, and bind-mounted app data.
- `archives/syncthing-shared.tar.gz` - `/home/kenpat/SyncthingShared`.
- `archives/kitestacks-scripts.tar.gz` - local KiteStacks automation/script folders.
- `archives/host-etc-subset.tar.gz` - selected host `/etc` configuration needed for migration context.
- `volume-exports/*.tar.gz` - Docker named volume exports.
- `inventory/*` - Docker, network, disk, config, and host inventory.
- `restore/RESTORE.md` - first-pass restore procedure.
- `SHA256SUMS` - checksums for all files in this backup.
## Restoring Claude's Context on a New Machine
This repo includes a snapshot of Claude Code's persistent memory in `claude-memory/`
(taken from `~/.claude/projects/-home-kenpat/memory/` on the original host). It
captures the homelab setup, this migration plan/status, and your working
preferences, so a fresh Claude session on the new machine doesn't start blank.
To restore it:
1. Clone this repo on the new machine.
2. Copy the contents of `claude-memory/` into Claude Code's memory directory for
your new home path, e.g.:
```sh
mkdir -p ~/.claude/projects/-home-<youruser>/memory
cp claude-memory/*.md ~/.claude/projects/-home-<youruser>/memory/
```
3. Start a Claude Code session in that project. It will automatically load
`MEMORY.md` and the linked memory files, and can pick up the migration
(and any other tracked work) where it left off.
4. Update `claude-memory/project-cloud-migration.md` as migration phases
progress, and re-copy it back into this repo so future restores stay current.
## Important
This repository contains secrets: `.env` files, tunnel tokens, app database data, and service credentials. Keep it private.
The Docker bind-data archive was created from a live host. For databases with strict consistency requirements, prefer restoring service-native dumps when available, or stop services before taking a final cutover backup.

View file

@ -0,0 +1,31 @@
f188190cf1a5655916edc2192ceb778330a041082613ba3f8573a5f3bbfe64a0 ./README.md
d2f2128eed132b9e8a43f35f6f4e4bfdba3254c5117a0318a6bf137f55f41b1f ./archives/docker-bind-data.tar.gz
f865584c6d3548c073b5299cd6ba41b478610d221683238b4cd83b1420030549 ./archives/host-etc-subset.tar.gz
8748291a60e092bd389b8c50d16ae7613b6909138c4bdd06852eb41dc76dc07f ./archives/kitestacks-scripts.tar.gz
748e3f8decd1234dfbd1f9f81cb44222c40d3cfd827a9c46d1d3337b1643b58f ./archives/syncthing-shared.tar.gz
8a685bef70fd7a71cf439b8088e0930bfab93956a0e8fe9b3d17888c118814c2 ./inventory/config-files.txt
189c4fa043c3d7b67d1e2e5c923b10022977c84a751b6526a0b2ab3c89ce5c6b ./inventory/crontab.stderr
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./inventory/crontab.txt
a71267fb60fd62a9924b9eed404e5f2c217cdf4c604962fc195c8db8050ec51d ./inventory/df-h.txt
a1f2d23e0f2b0a5b9bb76820ff2dbeaadcd059362d3791a90090fdb624f4a5e8 ./inventory/docker-container-inspect.json
15f1abf2e548b2fdce4852ff6da034b75d3664446c23672ddd00135b83a1afce ./inventory/docker-folder-sizes.txt
9d28d36f5ae776ad10080aa6b44292500b4963588c901847861890098a997803 ./inventory/docker-networks.txt
08939f14b26a7c0918dcc6cf4f852435f19ee05da34bd37a5e04e8c3e87efab3 ./inventory/docker-running.txt
0e58c8f5d09d2319ea38eee6b0a6ec923d621f187c51d2cf6381dadcccc8ffe9 ./inventory/docker-volume-inspect.json
9d3067d74156ce24c16822686f94c8897ec7d877df25eff643cf19937b89d2e4 ./inventory/docker-volumes.txt
659e639b4a93e08e868e133ab7c97898d419aec3056fcda012df686d52231f9e ./inventory/host-etc-tar-warnings.txt
dc6d9f9163deacf7ff8ca239c47d039581ef079172577ee418bd6fcb72ddc07a ./inventory/ip-addr.txt
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 ./inventory/kitestacks-scripts-tar-warnings.txt
00c76796663c6b278a91730c15531a559ed552ff65dea34424129fdb12b92587 ./inventory/uname.txt
dd93d026ddf76ea16896bf5faab9a2462275983e8c69ea6c07dc351864dce81c ./restore/RESTORE.md
2a5392e54eed3edb677ad52f419a27b1c1de387d3c1c512585c09cbd399ab08b ./volume-exports/95f721b2e8b90b4e17a3675d4905837933bc366e12c15d16bce1bd9d166c43f0.tar.gz
d37a2b35bd294d36aa0c3f96e477ee0203bc491f66d6d3cf122c81c29ba4b2c5 ./volume-exports/b11ac7c9fe060195954a46980f7ed85da9a62fea48d7cca5990aa54ec3d4cd8a.tar.gz
668ae10a5bb4a44864b788063610b43def6fdad2964181b5838e50388d8b4fbf ./volume-exports/b1a99e9e271f6dbb1e693aabd2508fbae9973a82c5c1778fdad099054754f111.tar.gz
44730b582fe275ae4a5daa7a03b5ae6ba77d55022df5b41e1183475d7a6e29fb ./volume-exports/b303d482950f666b37acae69c790f0f37e9d28735a4ea0b98a7961a8a400fa75.tar.gz
c492df2ba24d0f77ac6b9684de110a2cc96a843f6b1b4422473af6361407c68b ./volume-exports/kite-ai_open-webui.tar.gz
9b6410c6b71cd1f96157f3780de604a9c453abc2e5e23f4e5b2950f87098446e ./volume-exports/openproject_openproject_assets.tar.gz
bae148a7b41896039d6cab807d27860b2d8d7333b03f79a6c16f7770179e4a6e ./volume-exports/openproject_openproject_db_data.tar.gz
ba708e82a287f5c16f53afc1a885ca9837c0dfc44f92ab5ae2691090222dfdf1 ./volume-exports/openproject_openproject_logs.tar.gz
ae85ee17213163cbea636c5270a0a9152361ba2e26bb0781d0274fe9c6379b4f ./volume-exports/openproject_openproject_pgdata.tar.gz
7d7039b4dd710016ba2a447cb4ae82b24778544f6bdbd8fff07dc6ba87e5377c ./volume-exports/portainer_data.tar.gz
a6fa0fea172711ef20d9ac4bd8362c2a9544f93c5dd435c62abfc7cfe18b88bd ./volume-exports/uptime-kuma.tar.gz

BIN
cloud-migration/archives/docker-bind-data.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

BIN
cloud-migration/archives/host-etc-subset.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

BIN
cloud-migration/archives/kitestacks-scripts.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

BIN
cloud-migration/archives/syncthing-shared.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,7 @@
# Memory Index
- [User Profile](user-profile.md) — kenpat, homelab operator, kitestacks.com
- [Project: KiteStacks Homelab](project-kitestacks.md) — Docker Compose homelab, Cloudflare tunnels, autosync to Forgejo
- [Project: Authentik SSO](project-sso.md) — SSO setup in progress; what's done, what's pending
- [Feedback: Tool Confirmation](feedback_tool_confirmation.md) — don't pause for tool confirmation, keep going autonomously
- [Project: Cloud Migration](project-cloud-migration.md) — Phase 1 backup→Forgejo (done), Phase 2 Oracle VPS (planned), LFS/Cloudflare 413 gotcha

View file

@ -0,0 +1,13 @@
---
name: feedback_tool_confirmation
description: "User preference: don't pause for tool confirmation — keep going autonomously"
metadata:
node_type: memory
type: feedback
originSessionId: c435573b-6aff-4e43-a184-c17aa96ce348
---
Don't pause and wait for the user to hit enter or confirm tool calls. Keep executing until the task is complete.
**Why:** User wants uninterrupted autonomous execution.
**How to apply:** Only stop when the user must do something manually (UI action, testing) or when a decision genuinely requires their input. Never pause mid-task just to confirm a command.

View file

@ -0,0 +1,28 @@
---
name: project-cloud-migration
description: "KiteStacks server migration plan — Phase 1 (Forgejo backup, restore to new desktop) and Phase 2 (Oracle Cloud VPS, always-on)"
metadata:
node_type: memory
type: project
originSessionId: 1d92780e-77c5-41ac-8887-daca0ea55e8b
---
**Phase 1 — STATUS: backup pushed & verified, restore not yet performed**
Goal: be able to pull the backup repo onto a new (desktop) machine and physically relaunch the homelab from it.
- Full server backup lives in Forgejo repo `kenpat/kitestacks-cloud-migration` (private), clone url `http://100.90.13.55:3006/kenpat/kitestacks-cloud-migration.git` (also reachable at `gitforge.kitestacks.com` for small files).
- Source backup: `/home/kenpat/kitestacks-cloud` (git repo, 1 commit `6ffcbea`, ~4.3GB). Contains `archives/*.tar.gz` (docker bind-data, syncthing-shared, scripts, host /etc subset), `volume-exports/*.tar.gz` (named Docker volumes), `inventory/`, `restore/RESTORE.md`, `SHA256SUMS`.
- 14 large files (~2.3GB) are tracked via **Git LFS** — required `apt-get install git-lfs` (no passwordless sudo on this host, user ran it manually) and `git lfs migrate import --include="archives/*.tar.gz,volume-exports/*.tar.gz"`.
- **Known gotcha**: Forgejo's public hostname `gitforge.kitestacks.com` (Cloudflare Tunnel) returns HTTP 413 for any LFS object >~100MB. The 3 big files (docker-bind-data.tar.gz ~967MB, kite-ai_open-webui.tar.gz ~963MB, syncthing-shared.tar.gz ~165MB) had to be PUT directly to `http://100.90.13.55:3006/kenpat/kitestacks-cloud-migration.git/info/lfs/objects/<oid>/<size>` (local Tailscale IP, bypasses Cloudflare). `git lfs pull` for these 3 files will need the same workaround — clone/pull via the local IP, not the public hostname.
- Verified: local HEAD == remote HEAD, all 15/15 LFS objects present server-side with sizes matching `SHA256SUMS` OIDs.
- Documented in `/home/kenpat/forgejo-repos/kitestacks-homelab/docs/KiteStacks-Homelab-Documentation-v1.3.922.md`.
**Phase 1 remaining work**: actual restore-to-new-desktop has NOT been done/tested yet. When the user pulls a Claude session in on the new desktop, the workflow is: clone `kitestacks-cloud-migration` (via local IP for LFS), follow `restore/RESTORE.md`, restore Docker volumes from `volume-exports/`, restore bind-mounts from `archives/docker-bind-data.tar.gz`, bring stacks up via the compose files, re-point Cloudflare Tunnel.
**Why**: User wants to migrate the KiteStacks homelab off the current desktop onto new hardware first (Phase 1), then to an always-on Oracle Cloud VPS (Phase 2) so services don't go down when the desktop sleeps/is off.
**How to apply**: When the user says they're moving to a new desktop / starting the restore, walk through `restore/RESTORE.md` using this memory's notes (especially the LFS/Cloudflare gotcha). Once services are confirmed running on the new desktop, mark Phase 1 complete in this file, notify the user explicitly, and begin Phase 2 (Oracle Cloud VPS setup) — update this file with Phase 2 progress the same way.
**Phase 2 — STATUS: not started**
Plan: provision an Oracle Cloud VPS, migrate the same stack there for always-on hosting (independent of desktop power state). No infra details yet — to be filled in once started.

View file

@ -0,0 +1,19 @@
---
name: project-kitestacks
description: "KiteStacks homelab infrastructure — Docker Compose services, networking, autosync, Forgejo repo"
metadata:
node_type: memory
type: project
originSessionId: 301d23e2-6920-42b0-a27d-eba4e667b7f7
---
All services run as Docker Compose stacks in `/home/kenpat/docker/<app>/`.
All containers join the `kitestacks` external Docker bridge network (172.18.0.0/16) — cloudflared uses container names as hostnames to route traffic.
Cloudflare Tunnel (token-based) routes `*.kitestacks.com` → containers on the kitestacks network. Tunnel ingress rules are configured in the Cloudflare Zero Trust dashboard (not in local files).
**Autosync system:** A systemd service (`kitestacks-autosync`) watches `/home/kenpat/docker/` via inotify and pushes any file change to the Forgejo repo `kenpat/kitestacks-homelab` at `gitforge.kitestacks.com`. Working repo lives at `/opt/kitestacks-autosync/kitestacks-homelab/`. User's local clone is at `/home/kenpat/forgejo-repos/kitestacks-homelab/` (remote: `http://100.90.13.55:3006/kenpat/kitestacks-homelab.git`). Both point to the same Forgejo repo.
**Versioned docs:** Autosync auto-creates `docs/KiteStacks-Homelab-Documentation-v1.3.NNN.md` on each change. Manual/feature docs use the next version number. Current version as of 2026-06-08: 1.3.884.
**Why:** User always wants changes documented and pushed to Forgejo at the end of any task.
**How to apply:** After making file changes, write docs to `/home/kenpat/forgejo-repos/kitestacks-homelab/docs/`, update CHANGELOG.md and README.md version tag, commit, and push. Use direct IP remote (no TLS issues).

View file

@ -0,0 +1,32 @@
---
name: project-sso
description: "Authentik SSO setup status for kitestacks.com — what's done vs pending"
metadata:
node_type: memory
type: project
originSessionId: 301d23e2-6920-42b0-a27d-eba4e667b7f7
---
Authentik SSO configured 2026-06-08 to cover all kitestacks.com services.
Full reference: `docs/authentik-sso-setup.md` in the Forgejo repo.
**Config files updated (done):**
- `apps/authentik/docker-compose.yml` — kitestacks network declared
- `apps/kavita/config/appsettings.json` — OIDC enabled, Authority set
- BookStack retired — not used, all books on Kavita
- `apps/openproject/docker-compose.yml` — OIDC env vars + network
- `apps/openproject/.env` — OPENPROJECT_OIDC_SECRET placeholder
- Grafana and OpenWebUI already had OIDC env vars (just need Authentik apps created)
**Pending manual steps:**
1. Create Authentik OAuth2/OIDC providers + applications in admin UI for: Grafana, OpenWebUI, Kavita, OpenProject, Forgejo
2. Create Authentik Proxy Providers for: Shaarli, Uptime Kuma, LiteLLM; assign to Embedded Outpost
3. Configure Forgejo OAuth2 source via Forgejo admin UI (Site Admin → Auth Sources)
4. Fill client secrets in `.env` files and restart containers
5. Update Cloudflare tunnel routes: links.kitestacks.com → authentik:9000, status.kitestacks.com → authentik:9000, llm.kitestacks.com → authentik:9000
6. After OpenProject container recreation (v13→v15 upgrade), update tunnel: tasks.kitestacks.com → openproject:80
**Excluded from SSO:** Portainer, Prometheus, Node Exporter, OpenRouter, BookStack (retired)
**Why:** User requested Authentik SSO for all services; OpenRouter/Prometheus/node-exporter/Portainer excluded by user request.
**How to apply:** When user asks about SSO, check this memory for current status before suggesting next steps.

View file

@ -0,0 +1,10 @@
---
name: user-profile
description: "kenpat's role, preferences, and homelab context"
metadata:
node_type: memory
type: user
originSessionId: 301d23e2-6920-42b0-a27d-eba4e667b7f7
---
kenpat runs a self-hosted homelab at kitestacks.com. All services exposed via Cloudflare Tunnels. Comfortable with Docker Compose, Git/Forgejo. Wants Claude to document changes and push them to Forgejo as part of completing any task.

View file

@ -0,0 +1,57 @@
/home/kenpat/docker/kitestacks-portal/nginx.conf
/home/kenpat/docker/kitestacks-portal/docker-compose.yml
/home/kenpat/docker/openproject/docker-compose.yml
/home/kenpat/docker/openproject/.env
/home/kenpat/docker/authentik/docker-compose.yml
/home/kenpat/docker/authentik/.env
/home/kenpat/docker/cloudflared/docker-compose.yml
/home/kenpat/docker/prometheus/prometheus.yml
/home/kenpat/docker/prometheus/docker-compose.yml
/home/kenpat/docker/zammad/docker-compose.old.yml
/home/kenpat/docker/bookstack/docker-compose.yml
/home/kenpat/docker/kavita/config/appsettings.json
/home/kenpat/docker/kite-ai/docker-compose.yml
/home/kenpat/docker/kite-ai/litellm_config.yaml
/home/kenpat/docker/kite-ai/.env
/home/kenpat/docker/kitestacks-portal-test/nginx.conf
/home/kenpat/docker/kitestacks-portal-test/docker-compose.yml
/home/kenpat/docker/grafana/docker-compose.yml
/home/kenpat/docker/grafana/grafana-networkpolicy.yaml
/home/kenpat/docker/grafana/.env
/home/kenpat/docker/homepage/config-test/kubernetes.yaml
/home/kenpat/docker/homepage/config-test/settings.yaml
/home/kenpat/docker/karakeep/docker-compose.yml
/home/kenpat/docker/karakeep/.env
/home/kenpat/docker/forgejo/cronjob.yaml
/home/kenpat/docker/forgejo/configmap.yaml
/home/kenpat/docker/forgejo/docker-compose.yml
/home/kenpat/docker/forgejo/uptime-kuma/kitestacks-monitors.json
/home/kenpat/docker/forgejo/uptime-kuma/uptime-kuma-deployment.yaml
/home/kenpat/docker/forgejo/uptime-kuma/kitestacks-cron.yaml
/home/kenpat/docker/forgejo/uptime-kuma/uptime-kuma-ingress.yaml
/home/kenpat/docker/forgejo/uptime-kuma/configmap.yaml
/home/kenpat/docker/forgejo/uptime-kuma/gitrepository.yaml
/home/kenpat/docker/forgejo/gitrepository.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/kubernetes.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/bookmarks.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/proxmox.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/services.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/settings.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/widgets-live.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/settings-live.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/services-live.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/widgets.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config/docker.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/docker-compose.test.yml
/home/kenpat/docker/homepage-archived-2026-06-07/docker-compose.yml
/home/kenpat/docker/homepage-archived-2026-06-07/services.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/kubernetes.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/bookmarks.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/proxmox.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/services.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/settings.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/widgets-live.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/settings-live.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/services-live.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/widgets.yaml
/home/kenpat/docker/homepage-archived-2026-06-07/config-test/docker.yaml

View file

@ -0,0 +1 @@
crontabs/kenpat/: fopen: Permission denied

View file

View file

@ -0,0 +1,17 @@
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p2 226G 69G 146G 33% /
tmpfs 3.8G 0 3.8G 0% /dev
tmpfs 761M 6.5M 755M 1% /run
tmpfs 5.0M 12K 5.0M 1% /run/lock
tmpfs 1.0M 0 1.0M 0% /run/credentials/systemd-journald.service
tmpfs 761M 4.1M 757M 1% /run/user/1000
efivarfs 246K 130K 112K 54% /sys/firmware/efi/efivars
tmpfs 3.8G 952K 3.8G 1% /tmp
/dev/nvme0n1p1 975M 8.8M 966M 1% /boot/efi
/dev/nvme0n1p2 226G 69G 146G 33% /home/kenpat
udev 3.7G 0 3.7G 0% /dev/tty
tmpfs 3.8G 0 3.8G 0% /tmp/.git
tmpfs 3.8G 0 3.8G 0% /tmp/.agents
tmpfs 3.8G 0 3.8G 0% /tmp/.codex
tmpfs 3.8G 0 3.8G 0% /home/kenpat/.git
tmpfs 3.8G 0 3.8G 0% /home/kenpat/.agents

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
4.0K /home/kenpat/docker/audiobookshelf
4.0K /home/kenpat/docker/plane
4.0K /home/kenpat/docker/portainer
4.0K /home/kenpat/docker/postgres
4.0K /home/kenpat/docker/uptime-kuma
8.0K /home/kenpat/docker/zammad
12K /home/kenpat/docker/openproject
16K /home/kenpat/docker/kite-ai
16K /home/kenpat/docker/prometheus
20K /home/kenpat/docker/cloudflared
24K /home/kenpat/docker/homepage
32K /home/kenpat/docker/authentik
2.5M /home/kenpat/docker/homepage-backup-pre-cyberpunk-2026-06-07-0152.tar.gz
2.7M /home/kenpat/docker/homepage-archived-2026-06-07
3.1M /home/kenpat/docker/kitestacks-portal
3.2M /home/kenpat/docker/kitestacks-portal-test
4.4M /home/kenpat/docker/linkding
42M /home/kenpat/docker/kavita
63M /home/kenpat/docker/karakeep
155M /home/kenpat/docker/bookstack
156M /home/kenpat/docker/grafana
839M /home/kenpat/docker/forgejo

View file

@ -0,0 +1,19 @@
NAME DRIVER SCOPE
authentik_default bridge local
bookstack_default bridge local
bridge bridge local
calibre-web_default bridge local
cloudflared_default bridge local
forgejo_default bridge local
grafana_default bridge local
homepage_default bridge local
host host local
karakeep_internal bridge local
kite-ai_default bridge local
kitestacks bridge local
kitestacks-portal-test_default bridge local
kitestacks-portal_default bridge local
none null local
openproject_default bridge local
openproject_openproject-net bridge local
prometheus_default bridge local

View file

@ -0,0 +1,25 @@
NAMES IMAGE STATUS PORTS
karakeep ghcr.io/karakeep-app/karakeep:release Up About an hour (healthy) 3000/tcp
karakeep-meilisearch getmeili/meilisearch:v1.41.0 Up 2 hours 7700/tcp
karakeep-chrome gcr.io/zenika-hub/alpine-chrome:124 Up 2 hours
openproject openproject/openproject:15 Up 18 hours 0.0.0.0:80->80/tcp, :::80->80/tcp
forgejo codeberg.org/forgejo/forgejo:11 Up 16 hours 0.0.0.0:2222->22/tcp, :::2222->22/tcp, 0.0.0.0:3006->3000/tcp, :::3006->3000/tcp
kite-openwebui ghcr.io/open-webui/open-webui:main Up 32 hours (healthy) 0.0.0.0:3100->8080/tcp, :::3100->8080/tcp
grafana grafana/grafana-oss Up 32 hours 0.0.0.0:3150->3000/tcp, :::3150->3000/tcp
cloudflared cloudflare/cloudflared:latest Up 32 hours
kitestacks-metrics-api kitestacks-portal-test-metrics-api:latest Up 32 hours
homepage nginx:alpine Up 32 hours 80/tcp, 0.0.0.0:3005->3000/tcp, :::3005->3000/tcp
kitestacks-portal-test nginx:alpine Up 32 hours 0.0.0.0:3008->80/tcp, :::3008->80/tcp
homepage-test ghcr.io/gethomepage/homepage:latest Up 32 hours (healthy) 0.0.0.0:3007->3000/tcp, :::3007->3000/tcp
kite-litellm ghcr.io/berriai/litellm:main-latest Up 32 hours 0.0.0.0:4000->4000/tcp, :::4000->4000/tcp
bookstack lscr.io/linuxserver/bookstack:latest Up 32 hours 443/tcp, 0.0.0.0:6875->80/tcp, :::6875->80/tcp
bookstack-db mariadb:11 Up 32 hours 3306/tcp
authentik ghcr.io/goauthentik/server:latest Up 32 hours (healthy) 0.0.0.0:9001->9000/tcp, :::9001->9000/tcp
authentik-worker ghcr.io/goauthentik/server:latest Up 32 hours (healthy)
authentik-postgres postgres:16-alpine Up 32 hours 5432/tcp
authentik-redis redis:alpine Up 32 hours 6379/tcp
kavita ghcr.io/kareadita/kavita:latest Up 8 minutes (healthy) 0.0.0.0:5000->5000/tcp, :::5000->5000/tcp
portainer portainer/portainer-ce:latest Up 32 hours 8000/tcp, 9000/tcp, 0.0.0.0:9443->9443/tcp, :::9443->9443/tcp
prometheus prom/prometheus Up 32 hours 0.0.0.0:9090->9090/tcp, :::9090->9090/tcp
node-exporter prom/node-exporter Up 32 hours 0.0.0.0:9100->9100/tcp, :::9100->9100/tcp
uptime-kuma louislam/uptime-kuma:latest Up 32 hours (healthy) 0.0.0.0:3001->3001/tcp, :::3001->3001/tcp

View file

@ -0,0 +1,439 @@
[
{
"CreatedAt": "2026-06-09T13:36:40-05:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/4e935c6eb337c5ace7ae2d4ac76bf4cd7fba2d6733b7a3ecd647e53052965524/_data",
"Name": "4e935c6eb337c5ace7ae2d4ac76bf4cd7fba2d6733b7a3ecd647e53052965524",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-06T02:50:40-05:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/95f721b2e8b90b4e17a3675d4905837933bc366e12c15d16bce1bd9d166c43f0/_data",
"Name": "95f721b2e8b90b4e17a3675d4905837933bc366e12c15d16bce1bd9d166c43f0",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-04T11:22:48-05:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/b1a99e9e271f6dbb1e693aabd2508fbae9973a82c5c1778fdad099054754f111/_data",
"Name": "b1a99e9e271f6dbb1e693aabd2508fbae9973a82c5c1778fdad099054754f111",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-06T02:50:40-05:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/b11ac7c9fe060195954a46980f7ed85da9a62fea48d7cca5990aa54ec3d4cd8a/_data",
"Name": "b11ac7c9fe060195954a46980f7ed85da9a62fea48d7cca5990aa54ec3d4cd8a",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-05T14:51:34-05:00",
"Driver": "local",
"Labels": {
"com.docker.volume.anonymous": ""
},
"Mountpoint": "/var/lib/docker/volumes/b303d482950f666b37acae69c790f0f37e9d28735a4ea0b98a7961a8a400fa75/_data",
"Name": "b303d482950f666b37acae69c790f0f37e9d28735a4ea0b98a7961a8a400fa75",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-04T23:47:29-05:00",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "kite-ai",
"com.docker.compose.version": "2.26.1",
"com.docker.compose.volume": "open-webui"
},
"Mountpoint": "/var/lib/docker/volumes/kite-ai_open-webui/_data",
"Name": "kite-ai_open-webui",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-04T21:54:45-05:00",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "openproject",
"com.docker.compose.version": "2.26.1",
"com.docker.compose.volume": "openproject_assets"
},
"Mountpoint": "/var/lib/docker/volumes/openproject_openproject_assets/_data",
"Name": "openproject_openproject_assets",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-05T14:38:15-05:00",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "openproject",
"com.docker.compose.version": "2.26.1",
"com.docker.compose.volume": "openproject_db_data"
},
"Mountpoint": "/var/lib/docker/volumes/openproject_openproject_db_data/_data",
"Name": "openproject_openproject_db_data",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-05T14:38:15-05:00",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "openproject",
"com.docker.compose.version": "2.26.1",
"com.docker.compose.volume": "openproject_logs"
},
"Mountpoint": "/var/lib/docker/volumes/openproject_openproject_logs/_data",
"Name": "openproject_openproject_logs",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-04T21:54:45-05:00",
"Driver": "local",
"Labels": {
"com.docker.compose.project": "openproject",
"com.docker.compose.version": "2.26.1",
"com.docker.compose.volume": "openproject_pgdata"
},
"Mountpoint": "/var/lib/docker/volumes/openproject_openproject_pgdata/_data",
"Name": "openproject_openproject_pgdata",
"Options": null,
"Scope": "local"
},
{
"CreatedAt": "2026-06-04T11:27:46-05:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/portainer_data/_data",
"Name": "portainer_data",
"Options": null,
"Scope": "local"
},
{
"Id": "9a8ff52b78b4009bfd1871f7ecabc87a03048027aeb928a93795c49cc63ecc8c",
"Created": "2026-06-04T15:51:50.956146487Z",
"Path": "/usr/bin/dumb-init",
"Args": [
"--",
"extra/entrypoint.sh",
"node",
"server/server.js"
],
"State": {
"Status": "running",
"Running": true,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 5263,
"ExitCode": 0,
"Error": "",
"StartedAt": "2026-06-08T10:20:52.463566079Z",
"FinishedAt": "2026-06-08T10:20:43.666763869Z",
"Health": {
"Status": "healthy",
"FailingStreak": 0,
"Log": [
{
"Start": "2026-06-09T13:32:39.230864414-05:00",
"End": "2026-06-09T13:32:39.293596386-05:00",
"ExitCode": 0,
"Output": "2026/06/09 18:32:39 Checking http://127.0.0.1:3001\n2026/06/09 18:32:39 Health Check OK [Res Code: 200]\n"
},
{
"Start": "2026-06-09T13:33:39.294974636-05:00",
"End": "2026-06-09T13:33:39.386020711-05:00",
"ExitCode": 0,
"Output": "2026/06/09 18:33:39 Checking http://127.0.0.1:3001\n2026/06/09 18:33:39 Health Check OK [Res Code: 200]\n"
},
{
"Start": "2026-06-09T13:34:39.387516537-05:00",
"End": "2026-06-09T13:34:39.582395564-05:00",
"ExitCode": 0,
"Output": "2026/06/09 18:34:39 Checking http://127.0.0.1:3001\n2026/06/09 18:34:39 Health Check OK [Res Code: 200]\n"
},
{
"Start": "2026-06-09T13:35:39.596511098-05:00",
"End": "2026-06-09T13:35:39.727123444-05:00",
"ExitCode": 0,
"Output": "2026/06/09 18:35:39 Checking http://127.0.0.1:3001\n2026/06/09 18:35:39 Health Check OK [Res Code: 200]\n"
},
{
"Start": "2026-06-09T13:36:39.728420401-05:00",
"End": "2026-06-09T13:36:39.827964251-05:00",
"ExitCode": 0,
"Output": "2026/06/09 18:36:39 Checking http://127.0.0.1:3001\n2026/06/09 18:36:39 Health Check OK [Res Code: 200]\n"
}
]
}
},
"Image": "sha256:f48d816cb7460cd3b7bb15ed393968b0ae0da4c690443b778b6a5db6b09f527e",
"ResolvConfPath": "/var/lib/docker/containers/9a8ff52b78b4009bfd1871f7ecabc87a03048027aeb928a93795c49cc63ecc8c/resolv.conf",
"HostnamePath": "/var/lib/docker/containers/9a8ff52b78b4009bfd1871f7ecabc87a03048027aeb928a93795c49cc63ecc8c/hostname",
"HostsPath": "/var/lib/docker/containers/9a8ff52b78b4009bfd1871f7ecabc87a03048027aeb928a93795c49cc63ecc8c/hosts",
"LogPath": "/var/lib/docker/containers/9a8ff52b78b4009bfd1871f7ecabc87a03048027aeb928a93795c49cc63ecc8c/9a8ff52b78b4009bfd1871f7ecabc87a03048027aeb928a93795c49cc63ecc8c-json.log",
"Name": "/uptime-kuma",
"RestartCount": 0,
"Driver": "overlay2",
"Platform": "linux",
"MountLabel": "",
"ProcessLabel": "",
"AppArmorProfile": "docker-default",
"ExecIDs": null,
"HostConfig": {
"Binds": [
"uptime-kuma:/app/data"
],
"ContainerIDFile": "",
"LogConfig": {
"Type": "json-file",
"Config": {}
},
"NetworkMode": "bridge",
"PortBindings": {
"3001/tcp": [
{
"HostIp": "",
"HostPort": "3001"
}
]
},
"RestartPolicy": {
"Name": "unless-stopped",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"ConsoleSize": [
26,
80
],
"CapAdd": null,
"CapDrop": null,
"CgroupnsMode": "private",
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": null,
"GroupAdd": null,
"IpcMode": "private",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": null,
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 67108864,
"Runtime": "runc",
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": [],
"BlkioDeviceReadBps": [],
"BlkioDeviceWriteBps": [],
"BlkioDeviceReadIOps": [],
"BlkioDeviceWriteIOps": [],
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DeviceCgroupRules": null,
"DeviceRequests": null,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": null,
"OomKillDisable": null,
"PidsLimit": null,
"Ulimits": [],
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0,
"MaskedPaths": [
"/proc/asound",
"/proc/acpi",
"/proc/kcore",
"/proc/keys",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
"/sys/devices/virtual/powercap"
],
"ReadonlyPaths": [
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger"
]
},
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/a9ccf55f6f463815b92b71dcf70142bc29e46f15a9f379793794f9e2dc9202e2-init/diff:/var/lib/docker/overlay2/484ebda4c7653a5aece44f036a066cf6a8eb822181ba22bae6908d16a629819c/diff:/var/lib/docker/overlay2/6f26b9d89fa702369832639624a9ed0ea1c5b47ca6fb349bc8c401df7eaf6d54/diff:/var/lib/docker/overlay2/5fece3c11be6f920ed0ac2bca96519fcc6b37a8c7e1d4012a9aef5989df017ee/diff:/var/lib/docker/overlay2/59e052c7119808723ca72a872dc04903cf46eae560e6cb078abeb99cb09cde7b/diff:/var/lib/docker/overlay2/a15d41f7bc54190c511f40a5677a79744aafe316725054e6dbf25a52372b756b/diff:/var/lib/docker/overlay2/252278bc5048f2b29878b86d0f585a399f13abec6902c3399d50f40245ce458e/diff:/var/lib/docker/overlay2/8b63000845eefb70e35349605be767cc7bc122d530605a042caabb249a61c16a/diff:/var/lib/docker/overlay2/83089265605c9e5dc2cee6afb5c6d65fb018f3b7b699e4a88c874b29d8582394/diff:/var/lib/docker/overlay2/5e82af76259ba2a3d365c0a46804925e2cbe0bfdaa4bf7c6bd174c6c95748321/diff:/var/lib/docker/overlay2/43d5816b7d853b6c652fdf2805bae68e7cc3881058052a3a8ca2a0ea912decf3/diff:/var/lib/docker/overlay2/3f2031c2ab4999a3c459c490542ef5bb9de6568f6ee4ba3d2a4e951e5627ecba/diff:/var/lib/docker/overlay2/9bbabaccb55a7612c7d2c275644d80565cb7f20b22f9243a78b3732d002a1b60/diff:/var/lib/docker/overlay2/6360813a2de43dd1d216d9a17945d9836403a018bdeb91754458768731788736/diff",
"MergedDir": "/var/lib/docker/overlay2/a9ccf55f6f463815b92b71dcf70142bc29e46f15a9f379793794f9e2dc9202e2/merged",
"UpperDir": "/var/lib/docker/overlay2/a9ccf55f6f463815b92b71dcf70142bc29e46f15a9f379793794f9e2dc9202e2/diff",
"WorkDir": "/var/lib/docker/overlay2/a9ccf55f6f463815b92b71dcf70142bc29e46f15a9f379793794f9e2dc9202e2/work"
},
"Name": "overlay2"
},
"Mounts": [
{
"Type": "volume",
"Name": "uptime-kuma",
"Source": "/var/lib/docker/volumes/uptime-kuma/_data",
"Destination": "/app/data",
"Driver": "local",
"Mode": "z",
"RW": true,
"Propagation": ""
}
],
"Config": {
"Hostname": "9a8ff52b78b4",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"ExposedPorts": {
"3001/tcp": {}
},
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"NODE_VERSION=18.20.3",
"YARN_VERSION=1.22.19",
"UPTIME_KUMA_IS_CONTAINER=1"
],
"Cmd": [
"node",
"server/server.js"
],
"Healthcheck": {
"Test": [
"CMD-SHELL",
"extra/healthcheck"
],
"Interval": 60000000000,
"Timeout": 30000000000,
"StartPeriod": 180000000000,
"Retries": 5
},
"Image": "louislam/uptime-kuma:latest",
"Volumes": {
"/app/data": {}
},
"WorkingDir": "/app",
"Entrypoint": [
"/usr/bin/dumb-init",
"--",
"extra/entrypoint.sh"
],
"OnBuild": null,
"Labels": {}
},
"NetworkSettings": {
"Bridge": "",
"SandboxID": "be33e50dd494b136bd0c4093411afca8264ab29e20b3c9bde92c87db15cb5383",
"SandboxKey": "/var/run/docker/netns/be33e50dd494",
"Ports": {
"3001/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "3001"
},
{
"HostIp": "::",
"HostPort": "3001"
}
]
},
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "febf33e9a0277267dae9c51c79c42e824dbb520c8fe0a471374398d6024788bb",
"Gateway": "172.17.0.1",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"MacAddress": "02:42:ac:11:00:03",
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"MacAddress": "02:42:ac:11:00:03",
"NetworkID": "f4d4e2fb72e14fd40db0f04cfe43e79b4afdd228a2593c24a0e36c832349ba48",
"EndpointID": "febf33e9a0277267dae9c51c79c42e824dbb520c8fe0a471374398d6024788bb",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.3",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DriverOpts": null,
"DNSNames": null
},
"kitestacks": {
"IPAMConfig": {},
"Links": null,
"Aliases": [],
"MacAddress": "02:42:ac:12:00:0d",
"NetworkID": "72143bca8d6382130e8e1698bf60fb53ff93f944102372d2cc7dc8e32aee0758",
"EndpointID": "6c0340601e5b36aa8af525c8a8dd1048be951be5cec6f802273dd3d13f8e2470",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.13",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"DriverOpts": {},
"DNSNames": [
"uptime-kuma",
"9a8ff52b78b4"
]
}
}
}
}
]

View file

@ -0,0 +1,11 @@
95f721b2e8b90b4e17a3675d4905837933bc366e12c15d16bce1bd9d166c43f0
b1a99e9e271f6dbb1e693aabd2508fbae9973a82c5c1778fdad099054754f111
b11ac7c9fe060195954a46980f7ed85da9a62fea48d7cca5990aa54ec3d4cd8a
b303d482950f666b37acae69c790f0f37e9d28735a4ea0b98a7961a8a400fa75
kite-ai_open-webui
openproject_openproject_assets
openproject_openproject_db_data
openproject_openproject_logs
openproject_openproject_pgdata
portainer_data
uptime-kuma

View file

@ -0,0 +1,2 @@
tar: etc/systemd/system/k3s.service.env: Cannot open: Permission denied
tar: Exiting with failure status due to previous errors

View file

@ -0,0 +1,350 @@
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: enp0s31f6: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
link/ether 54:05:db:c5:c4:4a brd ff:ff:ff:ff:ff:ff
altname enx5405dbc5c44a
3: wlp0s20f3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 8c:8d:28:c4:e1:9f brd ff:ff:ff:ff:ff:ff
altname wlx8c8d28c4e19f
inet 192.168.1.205/24 brd 192.168.1.255 scope global dynamic noprefixroute wlp0s20f3
valid_lft 57571sec preferred_lft 57571sec
inet6 2600:1702:5730:50b0:af17:2be8:9ef7:f714/64 scope global temporary dynamic
valid_lft 3523sec preferred_lft 3523sec
inet6 2600:1702:5730:50b0::40/128 scope global dynamic noprefixroute
valid_lft 2878sec preferred_lft 2878sec
inet6 2600:1702:5730:50b0:9c92:ac42:9751:b5c0/64 scope global temporary deprecated dynamic
valid_lft 3523sec preferred_lft 0sec
inet6 2600:1702:5730:50b0:8e8d:28ff:fec4:e19f/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 3523sec preferred_lft 3523sec
inet6 fe80::8e8d:28ff:fec4:e19f/64 scope link noprefixroute
valid_lft forever preferred_lft forever
4: tailscale0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN group default qlen 500
link/none
inet 100.90.13.55/32 scope global tailscale0
valid_lft forever preferred_lft forever
inet6 fd7a:115c:a1e0::b139:d38/128 scope global
valid_lft forever preferred_lft forever
inet6 fe80::3a3d:6f43:b3d1:6e3a/64 scope link stable-privacy proto kernel_ll
valid_lft forever preferred_lft forever
5: br-5a4a35faa802: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:19:cd:38:a4 brd ff:ff:ff:ff:ff:ff
inet 192.168.160.1/20 brd 192.168.175.255 scope global br-5a4a35faa802
valid_lft forever preferred_lft forever
inet6 fe80::42:19ff:fecd:38a4/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
6: br-9e267d3240c9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:bc:f6:1a:bd brd ff:ff:ff:ff:ff:ff
inet 172.22.0.1/16 brd 172.22.255.255 scope global br-9e267d3240c9
valid_lft forever preferred_lft forever
inet6 fe80::42:bcff:fef6:1abd/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
7: br-fa41b0d19f8e: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:56:3c:ac:24 brd ff:ff:ff:ff:ff:ff
inet 192.168.48.1/20 brd 192.168.63.255 scope global br-fa41b0d19f8e
valid_lft forever preferred_lft forever
inet6 fe80::42:56ff:fe3c:ac24/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
8: br-70ba1bd90717: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:13:4a:a6 brd ff:ff:ff:ff:ff:ff
inet 172.26.0.1/16 brd 172.26.255.255 scope global br-70ba1bd90717
valid_lft forever preferred_lft forever
inet6 fe80::42:acff:fe13:4aa6/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
9: br-72143bca8d63: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:91:a3:2d:1f brd ff:ff:ff:ff:ff:ff
inet 172.18.0.1/16 brd 172.18.255.255 scope global br-72143bca8d63
valid_lft forever preferred_lft forever
inet6 fe80::42:91ff:fea3:2d1f/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
11: br-bffe9441e56d: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:66:ef:dd:4f brd ff:ff:ff:ff:ff:ff
inet 172.19.0.1/16 brd 172.19.255.255 scope global br-bffe9441e56d
valid_lft forever preferred_lft forever
inet6 fe80::42:66ff:feef:dd4f/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
12: br-2a6267f1d942: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:7b:5b:ed:9e brd ff:ff:ff:ff:ff:ff
inet 172.21.0.1/16 brd 172.21.255.255 scope global br-2a6267f1d942
valid_lft forever preferred_lft forever
inet6 fe80::42:7bff:fe5b:ed9e/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
13: br-7530729ebfae: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:b7:e0:17:4f brd ff:ff:ff:ff:ff:ff
inet 192.168.32.1/20 brd 192.168.47.255 scope global br-7530729ebfae
valid_lft forever preferred_lft forever
inet6 fe80::42:b7ff:fee0:174f/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
14: br-a8e208276d2f: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN group default
link/ether 02:42:59:9f:c6:22 brd ff:ff:ff:ff:ff:ff
inet 172.20.0.1/16 brd 172.20.255.255 scope global br-a8e208276d2f
valid_lft forever preferred_lft forever
15: br-bb1a116b6f77: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:69:eb:0c:b9 brd ff:ff:ff:ff:ff:ff
inet 172.25.0.1/16 brd 172.25.255.255 scope global br-bb1a116b6f77
valid_lft forever preferred_lft forever
inet6 fe80::42:69ff:feeb:cb9/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
17: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:37:8a:ab:2f brd ff:ff:ff:ff:ff:ff
inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
valid_lft forever preferred_lft forever
inet6 fe80::42:37ff:fe8a:ab2f/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
18: br-be1a91b20dc8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:cd:7e:51:db brd ff:ff:ff:ff:ff:ff
inet 172.29.0.1/16 brd 172.29.255.255 scope global br-be1a91b20dc8
valid_lft forever preferred_lft forever
inet6 fe80::42:cdff:fe7e:51db/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
19: br-30777ab296b8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:af:5f:6f:f2 brd ff:ff:ff:ff:ff:ff
inet 172.27.0.1/16 brd 172.27.255.255 scope global br-30777ab296b8
valid_lft forever preferred_lft forever
inet6 fe80::42:afff:fe5f:6ff2/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
20: br-49d3d5da502c: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:9f:ae:96:94 brd ff:ff:ff:ff:ff:ff
inet 172.30.0.1/16 brd 172.30.255.255 scope global br-49d3d5da502c
valid_lft forever preferred_lft forever
inet6 fe80::42:9fff:feae:9694/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
24: vethf4816dc@if23: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-fa41b0d19f8e state UP group default
link/ether b6:a6:ed:49:bd:fe brd ff:ff:ff:ff:ff:ff link-netnsid 16
inet6 fe80::b4a6:edff:fe49:bdfe/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
26: veth1987749@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-9e267d3240c9 state UP group default
link/ether 36:c0:50:3d:b1:96 brd ff:ff:ff:ff:ff:ff link-netnsid 13
inet6 fe80::34c0:50ff:fe3d:b196/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
30: veth85fe678@if29: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-30777ab296b8 state UP group default
link/ether 8a:f3:42:ef:e1:6b brd ff:ff:ff:ff:ff:ff link-netnsid 4
inet6 fe80::88f3:42ff:feef:e16b/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
32: vethd5b37cc@if31: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-bffe9441e56d state UP group default
link/ether 3e:99:51:e7:ec:ef brd ff:ff:ff:ff:ff:ff link-netnsid 10
inet6 fe80::3c99:51ff:fee7:ecef/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
34: vetha6e4b9a@if33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-5a4a35faa802 state UP group default
link/ether ba:e5:82:e3:18:d4 brd ff:ff:ff:ff:ff:ff link-netnsid 21
inet6 fe80::b8e5:82ff:fee3:18d4/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
36: veth58ed7ba@if35: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 2a:09:5b:4b:75:ef brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet6 fe80::2809:5bff:fe4b:75ef/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
38: vethec42d32@if37: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-be1a91b20dc8 state UP group default
link/ether 3e:47:cf:ef:85:dd brd ff:ff:ff:ff:ff:ff link-netnsid 3
inet6 fe80::3c47:cfff:feef:85dd/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
40: vethbfda3d6@if39: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-70ba1bd90717 state UP group default
link/ether 8e:b3:04:4c:20:24 brd ff:ff:ff:ff:ff:ff link-netnsid 9
inet6 fe80::8cb3:4ff:fe4c:2024/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
42: veth3b113fe@if41: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-49d3d5da502c state UP group default
link/ether 02:10:8b:04:57:f2 brd ff:ff:ff:ff:ff:ff link-netnsid 1
inet6 fe80::10:8bff:fe04:57f2/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
44: vethfaaa84d@if43: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-7530729ebfae state UP group default
link/ether 32:39:e5:7c:f2:c8 brd ff:ff:ff:ff:ff:ff link-netnsid 2
inet6 fe80::3039:e5ff:fe7c:f2c8/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
46: veth34136f6@if45: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-49d3d5da502c state UP group default
link/ether 2e:db:db:40:b3:f3 brd ff:ff:ff:ff:ff:ff link-netnsid 15
inet6 fe80::2cdb:dbff:fe40:b3f3/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
48: vethd05d87a@if47: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-be1a91b20dc8 state UP group default
link/ether 4e:8c:a7:c5:c2:86 brd ff:ff:ff:ff:ff:ff link-netnsid 14
inet6 fe80::4c8c:a7ff:fec5:c286/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
50: vethd0baa49@if49: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 06:13:ff:e9:a6:60 brd ff:ff:ff:ff:ff:ff link-netnsid 19
inet6 fe80::413:ffff:fee9:a660/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
52: vethdceb901@if51: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-be1a91b20dc8 state UP group default
link/ether 5e:4a:e7:57:f9:74 brd ff:ff:ff:ff:ff:ff link-netnsid 6
inet6 fe80::5c4a:e7ff:fe57:f974/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
54: vethff5574b@if53: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-5a4a35faa802 state UP group default
link/ether 56:35:0f:70:e5:bf brd ff:ff:ff:ff:ff:ff link-netnsid 18
inet6 fe80::5435:fff:fe70:e5bf/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
62: vethce624b6@if61: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether ce:38:5d:fb:bc:ce brd ff:ff:ff:ff:ff:ff link-netnsid 8
inet6 fe80::cc38:5dff:fefb:bcce/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
66: vethe7b822b@if65: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 2a:eb:3b:03:d5:35 brd ff:ff:ff:ff:ff:ff link-netnsid 12
inet6 fe80::28eb:3bff:fe03:d535/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
70: vethb0d7c1a@if69: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-bffe9441e56d state UP group default
link/ether 16:32:ce:d0:92:8d brd ff:ff:ff:ff:ff:ff link-netnsid 8
inet6 fe80::1432:ceff:fed0:928d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
72: vethdf62a27@if71: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether e2:f4:b0:c1:a2:49 brd ff:ff:ff:ff:ff:ff link-netnsid 10
inet6 fe80::e0f4:b0ff:fec1:a249/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
74: vethb919b92@if73: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 62:7b:50:e0:bb:ea brd ff:ff:ff:ff:ff:ff link-netnsid 9
inet6 fe80::607b:50ff:fee0:bbea/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
76: veth0737587@if75: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-be1a91b20dc8 state UP group default
link/ether ae:a1:02:ef:6e:09 brd ff:ff:ff:ff:ff:ff link-netnsid 12
inet6 fe80::aca1:2ff:feef:6e09/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
78: veth8613011@if77: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 3a:54:4d:77:fa:63 brd ff:ff:ff:ff:ff:ff link-netnsid 13
inet6 fe80::3854:4dff:fe77:fa63/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
80: veth6c24951@if79: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 9a:4e:c2:9d:5b:5d brd ff:ff:ff:ff:ff:ff link-netnsid 14
inet6 fe80::984e:c2ff:fe9d:5b5d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
82: veth621784b@if81: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 76:b9:09:4c:ea:5b brd ff:ff:ff:ff:ff:ff link-netnsid 15
inet6 fe80::74b9:9ff:fe4c:ea5b/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
84: vethb8d844d@if83: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 26:5b:4a:c1:06:99 brd ff:ff:ff:ff:ff:ff link-netnsid 16
inet6 fe80::245b:4aff:fec1:699/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
86: vethe73ee17@if85: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 32:3c:06:a1:7b:c3 brd ff:ff:ff:ff:ff:ff link-netnsid 21
inet6 fe80::303c:6ff:fea1:7bc3/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
88: vetheb0e29b@if87: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 2a:ed:74:96:5b:5b brd ff:ff:ff:ff:ff:ff link-netnsid 19
inet6 fe80::28ed:74ff:fe96:5b5b/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
90: veth5614959@if89: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 6e:ad:c1:cb:fd:62 brd ff:ff:ff:ff:ff:ff link-netnsid 18
inet6 fe80::6cad:c1ff:fecb:fd62/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
95: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN group default
link/ether 86:22:0a:4d:90:00 brd ff:ff:ff:ff:ff:ff
inet 10.42.0.0/32 scope global flannel.1
valid_lft forever preferred_lft forever
inet6 fe80::8422:aff:fe4d:9000/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
96: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default qlen 1000
link/ether 12:c6:0d:7d:dd:9c brd ff:ff:ff:ff:ff:ff
inet 10.42.0.1/24 brd 10.42.0.255 scope global cni0
valid_lft forever preferred_lft forever
inet6 fe80::10c6:dff:fe7d:dd9c/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
97: veth445f8110@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 4a:31:e4:e9:f7:a3 brd ff:ff:ff:ff:ff:ff link-netns cni-81ae1f26-1017-c5d6-4493-ed9bb16fb501
inet6 fe80::4831:e4ff:fee9:f7a3/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
98: veth736cde43@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 02:df:78:e8:c2:27 brd ff:ff:ff:ff:ff:ff link-netns cni-c2221a39-e9f7-e155-eeb0-39b9fd4c1d66
inet6 fe80::df:78ff:fee8:c227/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
99: vethc04aa9e2@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether fa:33:2f:6a:c4:f2 brd ff:ff:ff:ff:ff:ff link-netns cni-6c852b61-9f8e-638e-1071-aef368406551
inet6 fe80::f833:2fff:fe6a:c4f2/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
100: veth033b45e8@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether de:90:ff:6b:d3:94 brd ff:ff:ff:ff:ff:ff link-netns cni-e2f9d769-bab1-5b91-4110-f56e53d38d37
inet6 fe80::dc90:ffff:fe6b:d394/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
101: veth4ad49488@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 56:33:f1:1d:fd:48 brd ff:ff:ff:ff:ff:ff link-netns cni-39c366c4-cbab-952a-bb8b-b611d518dab2
inet6 fe80::5433:f1ff:fe1d:fd48/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
102: vethbf1d0e0b@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether ae:05:8b:90:7d:d7 brd ff:ff:ff:ff:ff:ff link-netns cni-1f683513-3a91-70e3-87a9-c4292acaedb9
inet6 fe80::ac05:8bff:fe90:7dd7/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
103: vethbdf19ef0@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 26:07:7e:9c:d3:77 brd ff:ff:ff:ff:ff:ff link-netns cni-a42e0f60-d58d-f377-8811-001b64d3726d
inet6 fe80::2407:7eff:fe9c:d377/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
105: veth7feaf739@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 26:7d:bc:ba:a8:ca brd ff:ff:ff:ff:ff:ff link-netns cni-41f4ceb5-6cb8-8f92-953b-95864e9e7cf1
inet6 fe80::247d:bcff:feba:a8ca/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
106: veth0d598c9d@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether be:31:2d:cb:aa:84 brd ff:ff:ff:ff:ff:ff link-netns cni-ec30b8aa-5ede-4f67-9c31-77aebc74579c
inet6 fe80::bc31:2dff:fecb:aa84/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
108: veth612e159c@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 6e:ec:c7:4c:0d:e3 brd ff:ff:ff:ff:ff:ff link-netns cni-0ec5bdaa-3575-8063-bb3b-08ac819b3189
inet6 fe80::6cec:c7ff:fe4c:de3/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
109: veth93eeb514@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 4e:79:3f:e1:81:48 brd ff:ff:ff:ff:ff:ff link-netns cni-cb3547f5-7c92-bb53-0589-3f4e3d74bd27
inet6 fe80::4c79:3fff:fee1:8148/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
110: veth1bd4228f@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether 1a:29:c8:84:d9:c5 brd ff:ff:ff:ff:ff:ff link-netns cni-ff5b8f46-6411-8fd9-b1e6-512e439595df
inet6 fe80::1829:c8ff:fe84:d9c5/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
111: veth18e1f9d7@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether b2:d2:3c:7f:a8:8c brd ff:ff:ff:ff:ff:ff link-netns cni-279ef32e-3c53-9c4f-519d-47333ad178fc
inet6 fe80::b0d2:3cff:fe7f:a88c/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
572: br-2c51db061b99: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:9e:6a:3e:9b brd ff:ff:ff:ff:ff:ff
inet 172.24.0.1/16 brd 172.24.255.255 scope global br-2c51db061b99
valid_lft forever preferred_lft forever
inet6 fe80::42:9eff:fe6a:3e9b/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
574: vethdb48521@if573: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 36:6b:7a:5b:6f:ad brd ff:ff:ff:ff:ff:ff link-netnsid 5
inet6 fe80::346b:7aff:fe5b:6fad/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
576: vethdb77b7c@if575: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-2c51db061b99 state UP group default
link/ether 9e:93:de:92:ff:8b brd ff:ff:ff:ff:ff:ff link-netnsid 5
inet6 fe80::9c93:deff:fe92:ff8b/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
636: veth77e5e8e@if635: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 76:bd:07:8c:8d:f1 brd ff:ff:ff:ff:ff:ff link-netnsid 11
inet6 fe80::74bd:7ff:fe8c:8df1/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
638: veth01be747@if637: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-bb1a116b6f77 state UP group default
link/ether 12:3c:22:cc:04:d7 brd ff:ff:ff:ff:ff:ff link-netnsid 11
inet6 fe80::103c:22ff:fecc:4d7/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1100: br-fefc2ce50c94: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:03:65:11:bf brd ff:ff:ff:ff:ff:ff
inet 172.28.0.1/16 brd 172.28.255.255 scope global br-fefc2ce50c94
valid_lft forever preferred_lft forever
inet6 fe80::42:3ff:fe65:11bf/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1102: veth5d48e8b@if1101: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-fefc2ce50c94 state UP group default
link/ether 2e:36:d3:de:71:70 brd ff:ff:ff:ff:ff:ff link-netnsid 32
inet6 fe80::2c36:d3ff:fede:7170/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1106: veth813ef74@if1105: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-fefc2ce50c94 state UP group default
link/ether ca:bd:7b:23:ee:7d brd ff:ff:ff:ff:ff:ff link-netnsid 29
inet6 fe80::c8bd:7bff:fe23:ee7d/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1160: vethe7aa1c2@if1159: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether 46:c8:27:6d:21:aa brd ff:ff:ff:ff:ff:ff link-netnsid 7
inet6 fe80::44c8:27ff:fe6d:21aa/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1162: veth5b67937@if1161: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-fefc2ce50c94 state UP group default
link/ether 02:4a:ca:26:ff:10 brd ff:ff:ff:ff:ff:ff link-netnsid 7
inet6 fe80::4a:caff:fe26:ff10/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1208: veth3c71fd4@if1207: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether b6:43:00:76:43:22 brd ff:ff:ff:ff:ff:ff link-netnsid 17
inet6 fe80::b443:ff:fe76:4322/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1210: veth8f5c212@if1209: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-72143bca8d63 state UP group default
link/ether f6:b0:30:80:4e:52 brd ff:ff:ff:ff:ff:ff link-netnsid 17
inet6 fe80::f4b0:30ff:fe80:4e52/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1215: veth2cd00be8@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP group default
link/ether de:83:da:54:0d:ad brd ff:ff:ff:ff:ff:ff link-netns cni-0b8ed978-d1dc-2353-3e39-95be7b63e661
inet6 fe80::dc83:daff:fe54:dad/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever
1225: veth0e83a05@if1224: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP group default
link/ether 6e:ce:17:da:09:69 brd ff:ff:ff:ff:ff:ff link-netnsid 20
inet6 fe80::6cce:17ff:feda:969/64 scope link proto kernel_ll
valid_lft forever preferred_lft forever

View file

@ -0,0 +1 @@
Linux Assassin 6.12.90+deb13.1-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.12.90-2 (2026-05-27) x86_64 GNU/Linux

View file

@ -0,0 +1,77 @@
# Restore Outline
This is the high-level restore path for a new Hetzner Cloud host.
## 1. Prepare Host
Install Docker and Docker Compose plugin.
```bash
sudo apt update
sudo apt install -y docker.io docker-compose-plugin git tar gzip
sudo usermod -aG docker "$USER"
```
Log out and back in after adding the Docker group.
## 2. Clone Backup
```bash
git clone https://gitforge.kitestacks.com/kenpat/kitestacks-cloud.git
cd kitestacks-cloud
sha256sum -c SHA256SUMS
```
## 3. Restore Bind-Mounted Data
```bash
mkdir -p /home/kenpat
tar -xzf archives/docker-bind-data.tar.gz -C /home/kenpat
tar -xzf archives/syncthing-shared.tar.gz -C /home/kenpat
tar -xzf archives/kitestacks-scripts.tar.gz -C /home/kenpat
```
## 4. Restore Named Docker Volumes
Create the external network used by the stack:
```bash
docker network create kitestacks
```
For each file in `volume-exports`, create and restore the volume:
```bash
for archive in volume-exports/*.tar.gz; do
volume="$(basename "$archive" .tar.gz)"
docker volume create "$volume"
docker run --rm \
-v "$volume:/volume" \
-v "$PWD/volume-exports:/backup:ro" \
alpine \
sh -c "tar -xzf /backup/$volume.tar.gz -C /volume"
done
```
## 5. Start Services
Start core services in dependency order. Example:
```bash
cd /home/kenpat/docker/authentik && docker compose up -d
cd /home/kenpat/docker/forgejo && docker compose up -d
cd /home/kenpat/docker/cloudflared && docker compose up -d
```
Then start the remaining service folders under `/home/kenpat/docker`.
## 6. DNS and Tunnel Cutover
Review:
- `inventory/docker-running.txt`
- `inventory/docker-networks.txt`
- `/home/kenpat/docker/cloudflared/docker-compose.yml`
- Cloudflare tunnel credentials under the restored cloudflared folder
Move DNS/tunnel targets to the Hetzner host after services are healthy.

BIN
cloud-migration/volume-exports/kite-ai_open-webui.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
cloud-migration/volume-exports/portainer_data.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

BIN
cloud-migration/volume-exports/uptime-kuma.tar.gz (Stored with Git LFS) Normal file

Binary file not shown.

5
cloud/README.md Normal file
View file

@ -0,0 +1,5 @@
# KiteStacks Cloud
This folder is reserved for cloud-specific configurations.
For the main homelab setup, see the root of this repository.

7
osticket/CHANGELOG.md Normal file
View file

@ -0,0 +1,7 @@
# Changelog
This file explains what changed in simple words.
## 2026-06-08
- Started Phase 1: OSTicketSystem.
- Created separate files for runbook, debug notes, and change history.

View file

@ -0,0 +1,3 @@
# Debug Documentation
This file is for errors, fixes, broken commands, and troubleshooting notes.

5
osticket/README.md Normal file
View file

@ -0,0 +1,5 @@
# OSTicketSystem
This is Phase 1 of the training project.
Goal: Build a custom ticket system where classmates can log in, claim tickets, complete tasks, and close tickets.

5
osticket/RUNBOOK.md Normal file
View file

@ -0,0 +1,5 @@
# OSTicketSystem Runbook
This file explains how to build and use the project from start to finish.
No debugging notes go here.