Sync workspace with Gitea: merge June/July session docs

- README + siklos-docker-services: add authentik/searxng/beszel rows, photos VM section,
  2026-06-24 cleanup notes, 2026-07-03 Portainer conversion note (jgpc naming preserved)
- Add: authentik setup docs, frigate docs/config, OCIS migration docs, AdventureLog bug
  report, install-native-apps.sh, workstation-disk-analysis.md
- Remove obsolete: cgitta-nextcloud-zip-analysis.md, kopia-restore-guide.md
- .gitignore: ocis-credentials.md (secrets stay local), *.bak
This commit is contained in:
2026-07-08 14:12:05 -05:00
parent bab19cd790
commit 7cb2ebed2d
27 changed files with 1611 additions and 595 deletions
+3
View File
@@ -2,3 +2,6 @@ credentials.md
kopia-docker-compose-fixed.yml kopia-docker-compose-fixed.yml
*.secret *.secret
*.env *.env
ocis-credentials.md
*.bak
*.bak2
@@ -0,0 +1,71 @@
# [BUG] Selecting a location search result overwrites the correct name with a reverse-geocoded POI name (e.g. "39")
🛑 **Note**: I searched existing issues before filing and did not find a match.
## 🐞 Bug Description
When adding a location, the search box returns the correct place from OpenStreetMap (e.g. **"Elk Rock State Park"**) with the correct address. But as soon as I **click that search result**, the app discards the result's name and replaces the location **Name** field with an unrelated value — in my case the bare number **"39"**.
The root cause is that selecting a search result does not keep the name the search already provided. Instead the app takes only the result's coordinates and performs a **reverse geocode** on them, then uses that reverse-geocode's `location_name` as the location Name. At the park's center point, the nearest named OSM object is a campsite tagged `tourism=39`, so the Name becomes "39". The coordinates and region (Marion County, Iowa) are correct — only the Name is wrong.
This means the app is throwing away good data it already has.
## 🔄 Steps to Reproduce
1. Add a new Location (e.g. inside a Collection).
2. In "Search for a location", type `Elk Rock State Park`.
3. The dropdown correctly shows **"Elk Rock State Park — Elk Rock State Park, Knoxville Township, Marion County, Iowa, United States"**.
4. Click that result.
5. The Name field is populated with **"39"** instead of "Elk Rock State Park".
## ✅ Expected Behavior
When a user selects a search result, the location **Name** should be set from that result's `name` (e.g. "Elk Rock State Park"). Reverse geocoding is fine for filling region/city/country, but it should **not overwrite** the name the user just selected from search.
## 📸 Evidence (server-side reproduction)
Running the app's own geocoding functions on the backend (`v0.12.1`, OSM/Nominatim, no Google key):
**Step A — what the search dropdown returns (`search_osm('elk rock state park')[0]`):**
```
name = 'Elk Rock State Park'
lat, lon = 41.4008688, -93.0776561
display_name = 'Elk Rock State Park, Knoxville Township, Marion County, Iowa, United States'
```
**Step B — what the app does on selection (`reverse_geocode(41.4008688, -93.0776561, user)`):**
```
location_name = '39'
display_name = 'Marion County, Iowa, US'
city / region = Marion County / Iowa
```
Raw Nominatim reverse lookup for those coordinates confirms the source of "39":
```
name: '39'
display_name: '39, Hayes Drive, Knoxville Township, Marion County, Iowa, 50138, United States'
address: { "tourism": "39", "road": "Hayes Drive", "county": "Marion County", ... }
```
So the correct name ("Elk Rock State Park") from Step A is replaced by the reverse-geocode `location_name` ("39") from Step B.
## 🔧 Suggested Fix
In the frontend "add location" flow, when a search result is chosen, keep that result's `name` for the location Name field. Continue to call `reverse_geocode` for region/city/country enrichment, but do not let its `location_name` overwrite a name that came from an explicit search-result selection. (Only fall back to the reverse-geocoded name when the location was placed by clicking the map, where no search name exists.)
Relevant backend code for reference:
- `adventures/views/reverse_geocode_view.py``search` (returns the correct `name`)
- `adventures/geocoding.py``search_osm` (passes OSM `name` through) and `reverse_geocode` / `reverse_geocode_osm` (returns `location_name` from the nearest OSM object, which can be a bare number)
## 🐳 Environment Details
- **Host:** Self-hosted (Proxmox VM, Docker via Portainer)
- **Install Method:** Docker Compose
- **AdventureLog Version:** v0.12.1
- **Reverse Proxy:** Caddy
- **Geocoding provider:** OpenStreetMap / Nominatim (no `GOOGLE_MAPS_API_KEY` set)
- **Browser:** <fill in, e.g. Firefox / Chrome + version>
## 📎 Additional Context
The reverse-geocoded region data (Marion County, Iowa, United States) is correct — this bug is specifically about the **Name** field being overwritten after a search-result selection. Users can manually retype the Name as a workaround, but it defeats the purpose of searching.
+14 -5
View File
@@ -26,6 +26,7 @@ All stacks are managed via **Portainer** at http://192.168.88.27:9000 (or https:
| siklos | 192.168.88.27 | Main Docker server (VM 112 on Proxmox) | 3 (local) | 26.1.5 | | siklos | 192.168.88.27 | Main Docker server (VM 112 on Proxmox) | 3 (local) | 26.1.5 |
| jellyfin | 192.168.88.10 | Media server | 4 (agent) | 26.1.5 | | jellyfin | 192.168.88.10 | Media server | 4 (agent) | 26.1.5 |
| jgpc | 192.168.88.41 | Workstation / Ollama / AI | 5 (agent) | 29.4.2 | | jgpc | 192.168.88.41 | Workstation / Ollama / AI | 5 (agent) | 29.4.2 |
| photos | 192.168.88.32 | Photos VM (VM 113) — Immich | 8 (agent) | — |
## Stack Inventory ## Stack Inventory
@@ -34,7 +35,7 @@ All stacks are managed via **Portainer** at http://192.168.88.27:9000 (or https:
| Stack | Port(s) | Data Path | URL | Notes | | Stack | Port(s) | Data Path | URL | Notes |
|---|---|---|---|---| |---|---|---|---|---|
| actual-budget | 5006 | /srv/docker/actual-budget | budget.jgitta.com | Budget tracking | | actual-budget | 5006 | /srv/docker/actual-budget | budget.jgitta.com | Budget tracking |
| beszel | 8085 | /srv/docker/beszel | beszel.jgitta.com | Beszel hub — all-in-one monitoring (replaced Prometheus/Grafana/cAdvisor) | | authentik | 9011 | /srv/docker/authentik | auth.jgitta.com | SSO / identity provider (stack 58; secrets stored as Portainer env vars) |
| dashboard | 8096 | /srv/docker/dashboard | dashboard.jgitta.com | Custom homepage | | dashboard | 8096 | /srv/docker/dashboard | dashboard.jgitta.com | Custom homepage |
| dashy | 8081 | /srv/docker/dashy | dashy.jgitta.com | Dashboard | | dashy | 8081 | /srv/docker/dashy | dashy.jgitta.com | Dashboard |
| gitea | 3002, 2222 | /srv/docker/gitea/data | gitea.jgitta.com | Git server — this repo | | gitea | 3002, 2222 | /srv/docker/gitea/data | gitea.jgitta.com | Git server — this repo |
@@ -47,13 +48,21 @@ All stacks are managed via **Portainer** at http://192.168.88.27:9000 (or https:
| linkwarden | 3015 | /srv/docker/linkwarden/pgdata | links.jgitta.com | Link manager | | linkwarden | 3015 | /srv/docker/linkwarden/pgdata | links.jgitta.com | Link manager |
| litellm | 4000 | /srv/docker/litellm | — | LLM API gateway | | litellm | 4000 | /srv/docker/litellm | — | LLM API gateway |
| meshcentral | 444 | /srv/docker/meshcentral | mesh.jgitta.com | Remote management | | meshcentral | 444 | /srv/docker/meshcentral | mesh.jgitta.com | Remote management |
| beszel | 8085 | /srv/docker/beszel | beszel.jgitta.com | Beszel hub — all-in-one monitoring (replaced Prometheus/Grafana/cAdvisor) |
| onlyoffice | 8880 | /srv/docker/onlyoffice | office.jgitta.com | Office document server (replaced Collabora) | | onlyoffice | 8880 | /srv/docker/onlyoffice | office.jgitta.com | Office document server (replaced Collabora) |
| pihole | host network | /srv/docker/pihole | pihole.jgitta.com | DNS ad blocker | | pihole | host network | /srv/docker/pihole | pihole.jgitta.com | DNS ad blocker + faster-whisper + open-webui |
| portainer | 9000, 9443 | /srv/docker/portainer | portainer.jgitta.com | Container management UI | | portainer | 9000, 9443 | /srv/docker/portainer | portainer.jgitta.com | Container management UI |
| uptime-kuma | 3001 | /srv/docker/uptime-kuma/data | status.jgitta.com | Uptime monitoring | | uptime-kuma | 3001 | /srv/docker/uptime-kuma/data | status.jgitta.com | Uptime monitoring |
| searxng | 8092 | /srv/docker/searxng | search.jgitta.com | Private search engine |
| watchtower | — | — | — | Auto-updates containers | | watchtower | — | — | — | Auto-updates containers |
| wordpress | 8095 | /srv/docker/wordpress | jgitta.com | WordPress site | | wordpress | 8095 | /srv/docker/wordpress | jgitta.com | WordPress site |
### photos (192.168.88.32) — 1 stack
| Stack | Port(s) | Data Path | URL | Notes |
|---|---|---|---|---|
| immich | 2283 | /mnt/immich-photos (uploads), /srv/docker/immich/postgres (DB) | pictures.jgitta.com | Stack 45; postgres needs `shared_preload_libraries=vectors.so,vchord.so` (unquoted, list-form command) |
### jellyfin (192.168.88.10) — 3 stacks ### jellyfin (192.168.88.10) — 3 stacks
| Stack | Port(s) | Data Path | Notes | | Stack | Port(s) | Data Path | Notes |
@@ -62,14 +71,13 @@ All stacks are managed via **Portainer** at http://192.168.88.27:9000 (or https:
| beszel-agent | 45876 | — | Beszel agent (systemd binary, not Docker) | | beszel-agent | 45876 | — | Beszel agent (systemd binary, not Docker) |
| portainer-agent | 9001 | — | Portainer agent (fully managed) | | portainer-agent | 9001 | — | Portainer agent (fully managed) |
### jgpc (192.168.88.41) — 4 stacks ### jgpc (192.168.88.41) — 3 stacks
| Stack | Port(s) | Data Path | Notes | | Stack | Port(s) | Data Path | Notes |
|---|---|---|---| |---|---|---|---|
| frigate | 5000, 8554, 8555 | /srv/docker/frigate | NVR / camera AI | | frigate | 5000, 8554, 8555 | /srv/docker/frigate | NVR / camera AI |
| open-webui | 3000 | /srv/docker/open-webui | Ollama web UI | | open-webui | 3000 | /srv/docker/open-webui | Ollama web UI |
| portainer-agent | 9001 | — | Portainer agent (shows "limited" — expected, see notes) | | portainer-agent | 9001 | — | Portainer agent (shows "limited" — expected, see notes) |
| stirling-pdf | 8081 | /srv/docker/stirling-pdf | PDF tools |
| beszel-agent | 45876 | — | Beszel agent (systemd binary, not Docker) | | beszel-agent | 45876 | — | Beszel agent (systemd binary, not Docker) |
## Caddy Reverse Proxy ## Caddy Reverse Proxy
@@ -111,6 +119,8 @@ All stack data is stored under `/srv/docker/<stack-name>/` on each host.
Old compose files were renamed to `.bak` after migration to Portainer. Old compose files were renamed to `.bak` after migration to Portainer.
**2026-07-03 — external stacks converted to Portainer:** authentik (stack 58), beszel (57), searxng (56) were created in Portainer; watchtower (18), karakeep (21), frigate (31), immich (45) were redeployed so Portainer owns the running containers. Frigate and immich stack definitions were updated to match the newer host compose files (frigate: cpuset pinning + 8G memory limit; immich: /mnt/immich-photos path + vectorchord postgres image). Host compose files renamed to `.bak`. Do not run `docker compose up` from /srv/docker — deploy via Portainer only.
## Kopia Backup Notes (updated 2026-05-07) ## Kopia Backup Notes (updated 2026-05-07)
Kopia backs up Nextcloud data (VM 103) and other sources to TrueNAS NFS. Kopia backs up Nextcloud data (VM 103) and other sources to TrueNAS NFS.
@@ -142,4 +152,3 @@ siklos to manage all three Docker hosts from one UI.
| OCIS VM | VM 114 (192.168.88.63) — cloud.jgitta.com | | OCIS VM | VM 114 (192.168.88.63) — cloud.jgitta.com |
| Docker server VM | VM 112 = siklos (192.168.88.27) | | Docker server VM | VM 112 = siklos (192.168.88.27) |
| Caddy proxy VM | 192.168.88.110 | | Caddy proxy VM | 192.168.88.110 |
+352
View File
@@ -0,0 +1,352 @@
# Authentik SSO — Setup Guide
Authentik is your central identity provider. Once set up, you log in once at `auth.jgitta.com` and all your services recognize you automatically.
---
## Phase 1: Deploy the Stack
### Step 1 — Copy files to siklos
SSH into siklos and create the directory:
```bash
ssh jgitta@192.168.88.27
sudo mkdir -p /srv/docker/authentik
```
Copy the files from this folder to siklos:
```bash
# Run these from your local machine (not from siklos)
scp docker-compose.yml jgitta@192.168.88.27:/srv/docker/authentik/
scp .env jgitta@192.168.88.27:/srv/docker/authentik/
```
### Step 2 — Deploy via Portainer
1. Open Portainer at `https://portainer.jgitta.com`
2. Click **Stacks****Add stack**
3. Name it `authentik`
4. Choose **Upload** and upload the `docker-compose.yml` from this folder
5. Scroll down to **Environment variables** → click **Load variables from .env file** → upload `.env`
6. Click **Deploy the stack**
Authentik takes about 6090 seconds to start on first run (it runs database migrations).
### Step 3 — Add auth.jgitta.com to Caddy
SSH into the Caddy VM and add the auth block:
```bash
ssh caddy # (root@192.168.88.110)
nano /etc/caddy/sites/infrastructure.caddy
```
Add this at the bottom of the file:
```
auth.jgitta.com {
import web_secure
reverse_proxy 192.168.88.27:9011
}
```
Then reload Caddy:
```bash
systemctl reload caddy
```
### Step 4 — Add DNS record
Add `auth.jgitta.com` to:
- **MikroTik** DNS: `auth.jgitta.com``192.168.88.110` (same as all other subdomains)
- **Cloudflare**: A record `auth` → your WAN IP, **grey cloud** (DNS only)
---
## Phase 2: First-Time Setup
1. Open `https://auth.jgitta.com/if/flow/initial-setup/`
2. Create your admin account (email + password)
3. You'll land on the Authentik admin dashboard
> **Tip:** The admin interface is at `https://auth.jgitta.com/if/admin/` — bookmark it.
---
## Phase 3: Service Integrations
### What each type means
- **Native OIDC**: The service has a built-in "Login with SSO" button. Best experience.
- **Forward Auth**: Caddy intercepts the request and checks Authentik before letting you in. Works on services with no login support at all.
---
### 3A — Native OIDC Services
For each service below, you create an **OAuth2/OIDC Provider** in Authentik, then configure the service to use it.
#### How to create an OIDC Provider in Authentik (do this for each service)
1. Go to `https://auth.jgitta.com/if/admin/`**Applications****Providers****Create**
2. Choose **OAuth2/OpenID Provider**
3. Fill in:
- **Name**: e.g. `Gitea`
- **Authorization flow**: `default-provider-authorization-explicit-consent`
- **Client type**: `Confidential`
- **Redirect URIs**: (see per-service table below)
4. Click **Finish** — copy the **Client ID** and **Client Secret**
5. Go to **Applications****Create**:
- **Name**: same as provider
- **Slug**: lowercase, e.g. `gitea`
- **Provider**: select the one you just created
---
#### Gitea (`gitea.jgitta.com`)
**Redirect URI**: `https://gitea.jgitta.com/user/oauth2/authentik/callback`
In Gitea → Site Administration → Authentication Sources → Add:
| Field | Value |
|---|---|
| Authentication type | OAuth2 |
| Name | `authentik` |
| OAuth2 provider | OpenID Connect |
| Client ID | (from Authentik) |
| Client Secret | (from Authentik) |
| OpenID Connect Auto Discovery URL | `https://auth.jgitta.com/application/o/gitea/.well-known/openid-configuration` |
---
#### Portainer (`portainer.jgitta.com`)
**Redirect URI**: `https://portainer.jgitta.com/`
In Portainer → Settings → Authentication → OAuth:
| Field | Value |
|---|---|
| Provider | Custom |
| Client ID | (from Authentik) |
| Client Secret | (from Authentik) |
| Authorization URL | `https://auth.jgitta.com/application/o/authorize/` |
| Access token URL | `https://auth.jgitta.com/application/o/token/` |
| Resource URL | `https://auth.jgitta.com/application/o/userinfo/` |
| Redirect URL | `https://portainer.jgitta.com/` |
| Logout URL | `https://auth.jgitta.com/application/o/portainer/end-session/` |
| User identifier | `preferred_username` |
| Scopes | `openid email profile` |
---
#### Linkwarden (`links.jgitta.com`)
**Redirect URI**: `https://links.jgitta.com/api/v1/auth/callback/authentik`
Add to the Linkwarden stack's environment variables in Portainer:
```
NEXTAUTH_URL=https://links.jgitta.com
AUTHENTIK_CUSTOM_NAME=Authentik
AUTHENTIK_ISSUER=https://auth.jgitta.com/application/o/linkwarden/
AUTHENTIK_CLIENT_ID=<client id>
AUTHENTIK_CLIENT_SECRET=<client secret>
```
Redeploy the stack after adding these.
---
#### Immich (VM113 — `192.168.88.32`)
**Redirect URI**: `https://immich.jgitta.com/auth/login` *(if you have this subdomain)* — or `app.immich.cloud://` for mobile app
In Immich → Administration → Authentication Settings:
| Field | Value |
|---|---|
| Enable OAuth | ✓ |
| Issuer URL | `https://auth.jgitta.com/application/o/immich/` |
| Client ID | (from Authentik) |
| Client Secret | (from Authentik) |
| Scope | `openid email profile` |
| Button text | `Login with Authentik` |
| Auto register | ✓ (optional — creates Immich user on first SSO login) |
---
#### OCIS / ownCloud (`cloud.jgitta.com`, VM114)
OCIS already uses OIDC. Update its config to point to Authentik instead of its built-in IDP.
**Redirect URI**: `https://cloud.jgitta.com/`
SSH into VM114 and edit the OCIS systemd environment or config file to set:
```
OCIS_OIDC_ISSUER=https://auth.jgitta.com/application/o/ocis/
PROXY_OIDC_ISSUER=https://auth.jgitta.com/application/o/ocis/
WEB_OIDC_CLIENT_ID=<client id>
```
> Note: OCIS with external OIDC is more involved — reach out if you want a dedicated guide for this one.
---
#### Home Assistant (`ha.jgitta.com`, VM106)
**Redirect URI**: `https://ha.jgitta.com/auth/oidc/callback`
In Home Assistant → `configuration.yaml`, add:
```yaml
homeassistant_cloud: # remove this if present
# In configuration.yaml:
http:
use_x_forwarded_for: true
trusted_proxies:
- 192.168.88.110 # Caddy VM
# Via HACS or built-in: install "OpenID Connect" (HACS → Integrations → search OIDC)
# Or use the built-in auth provider:
homeassistant:
auth_providers:
- type: homeassistant
- type: trusted_networks
trusted_networks:
- 192.168.88.0/24
```
Then add the `authentik` integration via Settings → Integrations → Add → search "OpenID Connect":
| Field | Value |
|---|---|
| Client ID | (from Authentik) |
| Client Secret | (from Authentik) |
| Metadata URL | `https://auth.jgitta.com/application/o/homeassistant/.well-known/openid-configuration` |
---
#### Open WebUI (`ai.jgitta.com`)
**Redirect URI**: `https://ai.jgitta.com/oauth/oidc/callback`
Add to the Open WebUI stack environment variables:
```
ENABLE_OAUTH_SIGNUP=true
OAUTH_MERGE_ACCOUNTS_BY_EMAIL=true
OAUTH_PROVIDER_NAME=Authentik
OPENID_PROVIDER_URL=https://auth.jgitta.com/application/o/openwebui/.well-known/openid-configuration
OAUTH_CLIENT_ID=<client id>
OAUTH_CLIENT_SECRET=<client secret>
OAUTH_SCOPES=openid email profile
```
---
#### Homarr (`homarr.jgitta.com`)
**Redirect URI**: `https://homarr.jgitta.com/api/auth/callback/oidc`
Add to Homarr stack environment:
```
AUTH_PROVIDER=oidc
AUTH_OIDC_CLIENT_ID=<client id>
AUTH_OIDC_CLIENT_SECRET=<client secret>
AUTH_OIDC_URI=https://auth.jgitta.com/application/o/homarr/
AUTH_OIDC_CLIENT_NAME=Authentik
```
---
#### Nextcloud (`next.jgitta.com`, VM103)
Install the `user_oidc` app in Nextcloud (Apps → Search "OpenID Connect user backend").
In Nextcloud → Administration → OpenID Connect:
| Field | Value |
|---|---|
| Identifier | `authentik` |
| Client ID | (from Authentik) |
| Client Secret | (from Authentik) |
| Discovery endpoint | `https://auth.jgitta.com/application/o/nextcloud/.well-known/openid-configuration` |
**Redirect URI** to enter in Authentik: `https://next.jgitta.com/apps/user_oidc/code`
---
### 3B — Forward Auth (Caddy Middleware)
These services have no native SSO. Caddy checks Authentik before granting access.
#### Step 1 — Create a Proxy Provider in Authentik
1. Authentik Admin → **Applications****Providers****Create**
2. Choose **Proxy Provider**
3. Set:
- **Name**: `Forward Auth`
- **Authorization flow**: `default-provider-authorization-implicit-consent`
- **Forward auth (single application)** → OR **Forward auth (domain level)**
- For domain-level (covers all subdomains): external host = `https://auth.jgitta.com`
4. Create an Application called `Forward Auth` linked to this provider
#### Step 2 — Deploy the Outpost
1. Authentik Admin → **Applications****Outposts****Create**
2. Type: **Proxy**
3. Applications: select `Forward Auth`
4. Integration: **Docker** (Authentik will auto-deploy the outpost container on siklos)
#### Step 3 — Add forward auth to Caddy
Edit `/etc/caddy/snippets.caddy` on the Caddy VM and add:
```
(authentik_forward_auth) {
forward_auth http://192.168.88.27:9000 {
uri /outpost.goauthentik.io/auth/caddy
copy_headers X-authentik-username X-authentik-groups X-authentik-email X-authentik-name X-authentik-uid
trusted_proxies private_ranges
}
}
```
Then add `import authentik_forward_auth` to any site block you want protected:
```
dashy.jgitta.com {
import web_secure
import authentik_forward_auth
reverse_proxy http://192.168.88.27:8081
}
```
**Services to protect with forward auth:**
| Service | Subdomain | Current Port |
|---|---|---|
| Dashy | dashy.jgitta.com | :8081 |
| SearXNG | search.jgitta.com | :8092 |
| Beszel | beszel.jgitta.com | :8085 |
| Uptime Kuma | status.jgitta.com | :3001 |
| Glances | glances.jgitta.com | :61208 |
| Actual Budget | budget.jgitta.com | :5006 |
| Guacamole | apache.jgitta.com | :8080 |
---
## Recommended Order
1. Deploy Authentik stack (Phase 1)
2. Complete initial setup (Phase 2)
3. Start with **Gitea** (simplest native OIDC, easy to test)
4. Add **Portainer** and **Homarr**
5. Set up **Forward Auth outpost** to protect Dashy, SearXNG, Beszel, etc.
6. Tackle Nextcloud, OCIS, Immich, Home Assistant individually
---
## Troubleshooting Tips
- **Can't reach auth.jgitta.com**: Check that Caddy reloaded (`systemctl reload caddy`) and MikroTik DNS has the record
- **Redirect URI mismatch error**: The redirect URI in Authentik must exactly match what the service sends — check for trailing slashes
- **"Invalid client"**: Client ID or Secret was copy-pasted with extra whitespace — re-enter manually
- **Forward auth loops**: Make sure the Authentik app URL itself (`auth.jgitta.com`) does NOT have `import authentik_forward_auth` — it would loop forever
+49
View File
@@ -0,0 +1,49 @@
# =============================================================================
# Authentik — add this block to /etc/caddy/sites/infrastructure.caddy
# on the Caddy VM (192.168.88.110)
# =============================================================================
# Main Authentik UI — login portal at https://auth.jgitta.com
auth.jgitta.com {
import web_secure
reverse_proxy 192.168.88.27:9011
}
# =============================================================================
# Forward Auth snippet — add to snippets.caddy on the Caddy VM
# =============================================================================
# This snippet protects any service that doesn't have its own login page.
# Add "import authentik_forward_auth" to any site block to require Authentik login.
#
# (authentik_forward_auth) {
# forward_auth http://192.168.88.27:9011 {
# uri /outpost.goauthentik.io/auth/caddy
# copy_headers X-authentik-username X-authentik-groups X-authentik-email X-authentik-name X-authentik-uid
# trusted_proxies private_ranges
# }
# }
# =============================================================================
# Example: protect Dashy with forward auth
# Replace your existing dashy block with this:
# =============================================================================
# dashy.jgitta.com {
# import web_secure
# import authentik_forward_auth
# reverse_proxy http://192.168.88.27:8081
# }
# =============================================================================
# Services to protect with forward auth (uncomment after Authentik is running):
# =============================================================================
# Add "import authentik_forward_auth" to these site blocks:
# - dashy.jgitta.com (:8081)
# - search.jgitta.com (:8092)
# - beszel.jgitta.com (:8085)
# - status.jgitta.com (:3001)
# - glances.jgitta.com (:61208)
# - budget.jgitta.com (:5006)
# - apache.jgitta.com (:8080) — Guacamole (also has native OIDC extension)
+92
View File
@@ -0,0 +1,92 @@
version: "3.4"
# Authentik — Identity Provider / SSO
# Host: siklos (192.168.88.27)
# Web UI: https://auth.jgitta.com (Caddy → port 9011)
# Data: /srv/docker/authentik/
services:
postgresql:
image: docker.io/library/postgres:16-alpine
container_name: authentik-postgres
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
start_period: 20s
interval: 30s
retries: 5
timeout: 5s
volumes:
- /srv/docker/authentik/postgres:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${PG_PASS}
POSTGRES_USER: ${PG_USER:-authentik}
POSTGRES_DB: ${PG_DB:-authentik}
redis:
image: docker.io/library/redis:alpine
container_name: authentik-redis
command: --save 60 1 --loglevel warning
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
start_period: 20s
interval: 30s
retries: 5
timeout: 3s
volumes:
- /srv/docker/authentik/redis:/data
server:
image: ghcr.io/goauthentik/server:latest
container_name: authentik-server
restart: unless-stopped
command: server
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
# Set this to your external URL — Authentik uses it for redirects
AUTHENTIK_AUTHENTIK__DEFAULT_TOKEN_DURATION: "days=30"
volumes:
- /srv/docker/authentik/media:/media
- /srv/docker/authentik/custom-templates:/templates
ports:
# Host port 9011 avoids conflict with Portainer's 9000
- "9011:9000"
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
worker:
image: ghcr.io/goauthentik/server:latest
container_name: authentik-worker
restart: unless-stopped
command: worker
environment:
AUTHENTIK_REDIS__HOST: redis
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY}
AUTHENTIK_ERROR_REPORTING__ENABLED: "false"
# Worker needs root to manage Docker socket for outpost deployments
user: root
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /srv/docker/authentik/media:/media
- /srv/docker/authentik/certs:/certs
- /srv/docker/authentik/custom-templates:/templates
depends_on:
postgresql:
condition: service_healthy
redis:
condition: service_healthy
-64
View File
@@ -1,64 +0,0 @@
# cgitta Nextcloud Storage Cleanup (Completed 2026-05-09)
**Starting size:** 418 GB
**Final size:** ~26 GB
**Total freed:** ~392 GB
---
## What Was Done
### 1. Corrupt / Unreadable Archives — Deleted (82.9 GB)
Both were first-segment-only multi-part zip archives with missing parts — completely unreadable.
| File | Size | Action |
|------|------|--------|
| `Documents.zip` | 75 GB | Deleted — content already in Documents/ folder |
| `OneDrive-2024-10-31.zip` | 7.9 GB | Deleted — unreadable, content verified elsewhere |
### 2. Google Takeout Zips — Deleted (75 GB)
8 takeout zip files from Feb 2023 export. Zip 001 (Google Photos/Keep/Drive/Contacts) and Zip 008 (Gmail mbox) were already fully extracted. Zips 002007 (Google Photos videos) were extracted then deleted.
All 8 zips removed after confirming extraction.
### 3. Duplicate Zips — Deleted (~293 MB)
- 9 exact duplicate `(1)`/`(2)` browser re-downloads removed
- `Photos.zip` and `Photos-001.zip` confirmed duplicated in Documents/Photos/ and removed
### 4. Gmail Backup
- Extracted `gmail-backup.tar.gz` (30 GB tar → 3.1 GB mbox) to `Backup Files/Gmail/`
- Deleted the original tar
### 5. Google Drive Content Consolidated
- Drive folders from 7 Takeout sub-archives merged into `Documents/Google Drive/` (4.2 GB)
- Duplicate files across takeout extracts deduplicated with fdupes
### 6. Photos Migrated to Immich (69 GB freed from Nextcloud)
- All 53,299 files (69 GB) from `cgitta/files/Photos/` rsynced to photos VM staging area
- fdupes comparison against Joe's Immich library (`/mnt/immich-photos/photos/joe/`):
- 3,243 files already in Joe's library → deleted
- 1,540 internal duplicates → deleted
- **48,676 unique files (55 GB) → moved to `/mnt/immich-photos/photos/cynthia/`**
- Immich external library rescanned — photos indexed under Cynthia's account
- `Photos/` folder deleted from Nextcloud
---
## Final Nextcloud State (cgitta account)
| Folder | Size |
|--------|------|
| Documents | 18 GB |
| zip drives (teaching materials) | 2.6 GB |
| Video | 463 MB |
| music | 2.6 MB |
| **Total** | **~26 GB** |
The `zip drives` folder (~2.6 GB) contains ~100 Teachers Pay Teachers curriculum zips — left intact for Cynthia to review.
---
## Notes
- Nextcloud `occ files:scan` run twice during cleanup — final scan: 1,612 folders / 36,306 files
- Immich external library ID: `54b6419e-eb04-47b0-a5bd-0ecdb52545d7`
- `id_nextcloud` SSH key on `next` VM is now authorized on `photos` VM for future transfers
Binary file not shown.
+40
View File
@@ -0,0 +1,40 @@
services:
frigate:
container_name: frigate
image: ghcr.io/blakeblackshear/frigate:stable-tensorrt
privileged: true
shm_size: "1024mb"
cpuset: "0-3,8-11" # Pin to NUMA node 0 — co-located with GTX 1050 Ti on PCIe
restart: always
runtime: nvidia
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,video,utility
- FRIGATE_RTSP_PASSWORD=restreampass
- GO2RTC_ALLOW_ARBITRARY_EXEC=true
secrets:
- frigate_jwt_secret
deploy:
resources:
limits:
memory: 8G # Increased from 4G — now have 62GB, give Frigate more headroom
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu, compute, video]
volumes:
- /srv/docker/frigate/config:/config
- /mnt/14TB-HD/frigate/media:/media/frigate
- /mnt/INTEL-SSD/frigate/db:/nvme_db
- /etc/localtime:/etc/localtime:ro
ports:
- "5000:5000"
- "8971:8971"
- "8554:8554"
- "8555:8555/tcp"
- "8555:8555/udp"
secrets:
frigate_jwt_secret:
file: /srv/docker/frigate/config/jwt_secret
+6 -21
View File
@@ -1,6 +1,6 @@
# Homelab Overview # Homelab Overview
**Last Updated:** June 13, 2026 **Last Updated:** May 11, 2026
This document provides a high-level summary of the entire homelab infrastructure — networking, physical/virtual hosts, Docker services, and how everything is connected. This document provides a high-level summary of the entire homelab infrastructure — networking, physical/virtual hosts, Docker services, and how everything is connected.
@@ -81,7 +81,7 @@ Proxmox runs all the VMs and LXC containers listed below. Backups are handled by
| Name | IP | Role | | Name | IP | Role |
|---|---|---| |---|---|---|
| jgpc | 192.168.88.41 | Workstation / AI / Ollama (bare metal, not a Proxmox guest) | | thinkstation-p710 | 192.168.88.41 | Workstation / AI / Ollama (bare metal, not a Proxmox guest) |
| TrueNAS | 192.168.88.24 | NAS — provides NFS storage for Kopia backups; separate physical machine | | TrueNAS | 192.168.88.24 | NAS — provides NFS storage for Kopia backups; separate physical machine |
--- ---
@@ -107,7 +107,7 @@ Config location on the Caddy VM: `/etc/caddy/`
## Docker Infrastructure ## Docker Infrastructure
Docker is managed centrally via **Portainer CE** at `http://192.168.88.27:9000` (external: `https://portainer.jgitta.com`). Portainer agents run on jellyfin and jgpc, allowing all three hosts to be managed from one UI. Docker is managed centrally via **Portainer CE** at `http://192.168.88.27:9000` (external: `https://portainer.jgitta.com`). Portainer agents run on jellyfin and thinkstation, allowing all three hosts to be managed from one UI.
All Docker Compose source files live in the **Gitea** repo `jgitta/homelab-configs` (this repo). All Docker Compose source files live in the **Gitea** repo `jgitta/homelab-configs` (this repo).
All stack data volumes are stored at `/srv/docker/<stack-name>/` on each host using **absolute paths**. All stack data volumes are stored at `/srv/docker/<stack-name>/` on each host using **absolute paths**.
@@ -120,7 +120,7 @@ All stack data volumes are stored at `/srv/docker/<stack-name>/` on each host us
|---|---|---|---|---| |---|---|---|---|---|
| Portainer | portainer/portainer-ce:latest | 9000, 9443 | portainer.jgitta.com | Docker management UI | | Portainer | portainer/portainer-ce:latest | 9000, 9443 | portainer.jgitta.com | Docker management UI |
| Pi-hole | pihole/pihole:latest | 53 (DNS), 8080 | pihole.jgitta.com | DNS ad blocker | | Pi-hole | pihole/pihole:latest | 53 (DNS), 8080 | pihole.jgitta.com | DNS ad blocker |
| Open WebUI | ghcr.io/open-webui/open-webui:main | 3000 | ai.jgitta.com | AI chat UI (connects to Ollama on jgpc) | | Open WebUI | ghcr.io/open-webui/open-webui:main | 3000 | ai.jgitta.com | AI chat UI (connects to Ollama on ThinkStation) |
| LiteLLM | litellm:latest | 4000 | — | Centralized LLM API gateway | | LiteLLM | litellm:latest | 4000 | — | Centralized LLM API gateway |
| OnlyOffice | onlyoffice/documentserver | 8880 | office.jgitta.com | Online document editing (used by Nextcloud) | | OnlyOffice | onlyoffice/documentserver | 8880 | office.jgitta.com | Online document editing (used by Nextcloud) |
| Homarr | homarr:latest | 7575 | homarr.jgitta.com | Dashboard | | Homarr | homarr:latest | 7575 | homarr.jgitta.com | Dashboard |
@@ -164,7 +164,7 @@ Media storage is mounted at `/mnt/media` on the jellyfin host and presented as `
--- ---
### jgpc — 192.168.88.41 — AI / Workstation Host ### thinkstation-p710 — 192.168.88.41 — AI / Workstation Host
| Service | Image | Port | Purpose | | Service | Image | Port | Purpose |
|---|---|---|---| |---|---|---|---|
@@ -174,20 +174,6 @@ Media storage is mounted at `/mnt/media` on the jellyfin host and presented as `
| Stirling PDF | frooodle/s-pdf:latest | 8081 | PDF tools web UI | | Stirling PDF | frooodle/s-pdf:latest | 8081 | PDF tools web UI |
| Portainer Agent | portainer/agent:latest | 9001 | Managed by siklos Portainer | | Portainer Agent | portainer/agent:latest | 9001 | Managed by siklos Portainer |
#### Printing (jgpc)
The jgpc is the print server for the HP PageWide Color Flow MFP 586.
| Item | Value |
|---|---|
| Printer | HP PageWide Color Flow MFP 586 |
| IP | 192.168.88.89 (static DHCP reservation, MAC: `ec:8e:b5:c0:d0:48`) |
| CUPS queue | `HP_586_MFP` (default) |
| Device URI | `ipps://192.168.88.89/ipp/print` |
| Driver | IPP Everywhere (driverless) |
`cups-browsed` is **disabled** — it was auto-recreating an unstable mDNS-based queue whenever the printer was online. The printer is permanently saved in `/etc/cups/printers.conf` with a static IP URI.
--- ---
### photos VM — 192.168.88.32 (VM 113) — Photo Services ### photos VM — 192.168.88.32 (VM 113) — Photo Services
@@ -204,7 +190,7 @@ Photo storage is on a local 200 GB SSD (`/mnt/immich-photos`) — migrated from
## Monitoring ## Monitoring
Prometheus scrapes **Node Exporter** (port 9100) from every host: siklos, proxmox, Nextcloud, jellyfin, PBS, caddy, and jgpc. **Grafana** at `grafana.jgitta.com` visualizes all metrics and fires alerts (via Telegram) when: Prometheus scrapes **Node Exporter** (port 9100) from every host: siklos, proxmox, Nextcloud, jellyfin, PBS, caddy, and ThinkStation. **Grafana** at `grafana.jgitta.com` visualizes all metrics and fires alerts (via Telegram) when:
- RAM > 90% for 5 minutes - RAM > 90% for 5 minutes
- Swap > 50% for 5 minutes - Swap > 50% for 5 minutes
@@ -242,4 +228,3 @@ Prometheus scrapes **Node Exporter** (port 9100) from every host: siklos, proxmo
| AI (Open WebUI) | https://ai.jgitta.com | | AI (Open WebUI) | https://ai.jgitta.com |
| Guacamole | https://apache.jgitta.com | | Guacamole | https://apache.jgitta.com |
| Kopia | https://kopia.jgitta.com | | Kopia | https://kopia.jgitta.com |
+213
View File
@@ -0,0 +1,213 @@
#!/bin/bash
# Replace Flatpak apps with native installs
# Migrates configs from Flatpak sandboxes to native locations
# Safe to re-run — won't overwrite existing native configs without prompting
set -e
BACKUP="$HOME/flatpak-config-backup"
TMPDIR="$HOME/Downloads/native-installs"
mkdir -p "$TMPDIR"
echo "============================================"
echo " Step 1: Install KeePassXC via apt"
echo "============================================"
sudo apt-get update -qq
sudo apt-get install -y keepassxc
echo "Done."
echo ""
echo "============================================"
echo " Step 1b: Install Telegram (official tarball)"
echo "============================================"
echo "Downloading Telegram..."
wget -q --show-progress -O "$TMPDIR/telegram.tar.xz" \
"https://telegram.org/dl/desktop/linux"
sudo mkdir -p /opt/telegram
sudo tar -xJf "$TMPDIR/telegram.tar.xz" -C /opt/telegram --strip-components=1
sudo ln -sf /opt/telegram/Telegram /usr/local/bin/telegram
mkdir -p ~/.local/share/applications
cat > ~/.local/share/applications/telegram-desktop.desktop << EOF
[Desktop Entry]
Name=Telegram Desktop
Comment=Official Telegram messaging app
Exec=/opt/telegram/Telegram -- %u
Icon=/opt/telegram/telegram.png
Terminal=false
Type=Application
Categories=Chat;Network;InstantMessaging;
MimeType=x-scheme-handler/tg;
StartupNotify=true
StartupWMClass=TelegramDesktop
EOF
update-desktop-database ~/.local/share/applications 2>/dev/null || true
echo "Telegram installed to /opt/telegram"
echo ""
echo "============================================"
echo " Step 2: Download Obsidian .deb"
echo "============================================"
wget -q --show-progress -O "$TMPDIR/obsidian.deb" \
"https://github.com/obsidianmd/obsidian-releases/releases/download/v1.12.7/obsidian_1.12.7_amd64.deb"
echo "Obsidian downloaded."
echo ""
echo "============================================"
echo " Step 3: Download FreeTube .deb (latest)"
echo "============================================"
FREETUBE_URL=$(curl -s https://api.github.com/repos/FreeTubeApp/FreeTube/releases/latest \
| grep -o '"browser_download_url": "[^"]*amd64\.deb"' \
| head -1 | cut -d'"' -f4)
if [ -z "$FREETUBE_URL" ]; then
echo "WARNING: Could not auto-detect FreeTube version. Check https://github.com/FreeTubeApp/FreeTube/releases"
echo "Download the *_amd64.deb file manually and run: sudo dpkg -i ~/Downloads/freetube_*.deb"
else
wget -q --show-progress -O "$TMPDIR/freetube.deb" "$FREETUBE_URL"
echo "FreeTube downloaded."
fi
echo ""
echo "============================================"
echo " Step 4: Install .deb packages"
echo "============================================"
sudo dpkg -i "$TMPDIR/obsidian.deb" 2>/dev/null || true
[ -f "$TMPDIR/freetube.deb" ] && sudo dpkg -i "$TMPDIR/freetube.deb" 2>/dev/null || true
sudo apt-get install -f -y # fix any missing dependencies
echo "Done."
echo ""
echo "============================================"
echo " Step 5: Install Zen Browser (native tar.xz)"
echo "============================================"
echo "Downloading Zen (this may take a minute — ~90MB)..."
wget -q --show-progress -O "$TMPDIR/zen.tar.xz" \
"https://github.com/zen-browser/desktop/releases/latest/download/zen.linux-x86_64.tar.xz"
sudo mkdir -p /opt/zen
sudo tar -xJf "$TMPDIR/zen.tar.xz" -C /opt/zen --strip-components=1
sudo chmod +x /opt/zen/zen
# Symlink so you can type 'zen' in the terminal
sudo ln -sf /opt/zen/zen /usr/local/bin/zen
# Find the icon (location varies by version)
ZEN_ICON=$(find /opt/zen -name "default128.png" 2>/dev/null | head -1)
[ -z "$ZEN_ICON" ] && ZEN_ICON="/opt/zen/browser/chrome/icons/default/default128.png"
# Create application menu entry
mkdir -p ~/.local/share/applications
cat > ~/.local/share/applications/zen-browser.desktop << EOF
[Desktop Entry]
Name=Zen Browser
Comment=Experience a calmer internet
Exec=/opt/zen/zen %u
Icon=$ZEN_ICON
Terminal=false
Type=Application
Categories=Network;WebBrowser;
MimeType=text/html;text/xml;application/xhtml+xml;x-scheme-handler/http;x-scheme-handler/https;
StartupNotify=true
StartupWMClass=zen-alpha
EOF
update-desktop-database ~/.local/share/applications 2>/dev/null || true
echo "Zen installed to /opt/zen"
echo ""
echo "============================================"
echo " Step 6: Migrate configs from Flatpak backups"
echo "============================================"
# KeePassXC — settings (recent files, window layout, etc.)
if [ -d "$BACKUP/org.keepassxc.KeePassXC/config/keepassxc" ]; then
mkdir -p ~/.config/keepassxc
cp -rn "$BACKUP/org.keepassxc.KeePassXC/config/keepassxc/." ~/.config/keepassxc/
echo "KeePassXC: config migrated (database stays at ~/passwords/kpdb.kdbx)"
else
echo "KeePassXC: no config found in backup, skipping"
fi
# Telegram — tdata contains your login session (no need to re-scan QR code)
if [ -d "$BACKUP/org.telegram.desktop/data/TelegramDesktop" ]; then
mkdir -p ~/.local/share/TelegramDesktop
cp -rn "$BACKUP/org.telegram.desktop/data/TelegramDesktop/." ~/.local/share/TelegramDesktop/
echo "Telegram: session data migrated (you should stay logged in)"
else
echo "Telegram: no data found in backup, you'll need to log in again"
fi
# FreeTube — subscriptions, history, settings
if [ -d "$BACKUP/io.freetubeapp.FreeTube/config/FreeTube" ]; then
mkdir -p ~/.config/FreeTube
# Copy only the actual data files, skip caches
for item in "$BACKUP/io.freetubeapp.FreeTube/config/FreeTube/"*; do
name=$(basename "$item")
case "$name" in
Cache|"Code Cache"|GPUCache|DawnGraphiteCache|DawnWebGPUCache|Crashpad)
echo "FreeTube: skipping cache: $name"
;;
*)
cp -rn "$item" ~/.config/FreeTube/ 2>/dev/null || true
;;
esac
done
echo "FreeTube: config migrated (subscriptions + history preserved)"
else
echo "FreeTube: no config found in backup, skipping"
fi
# Obsidian — app-level config (vault list, theme, plugins list)
if [ -d "$BACKUP/md.obsidian.Obsidian/config/obsidian" ]; then
mkdir -p ~/.config/obsidian
for item in "$BACKUP/md.obsidian.Obsidian/config/obsidian/"*; do
name=$(basename "$item")
case "$name" in
Cache|"Code Cache"|GPUCache|DawnGraphiteCache|DawnWebGPUCache|Crashpad)
echo "Obsidian: skipping cache: $name"
;;
*)
cp -rn "$item" ~/.config/obsidian/ 2>/dev/null || true
;;
esac
done
echo "Obsidian: config migrated (vaults are wherever you stored them, unchanged)"
else
echo "Obsidian: no config found in backup, skipping"
fi
# Zen — full browser profile (bookmarks, extensions, history, passwords, settings)
if [ -d "$BACKUP/app.zen_browser.zen/.zen" ]; then
echo "Zen: migrating browser profile (bookmarks, extensions, history)..."
mkdir -p ~/.zen
cp -rn "$BACKUP/app.zen_browser.zen/.zen/." ~/.zen/
echo "Zen: profile migrated"
else
echo "Zen: no profile found in backup, starting fresh"
fi
echo ""
echo "============================================"
echo " Step 7: Remove Flatpak versions"
echo "============================================"
flatpak remove -y \
app.zen_browser.zen \
org.keepassxc.KeePassXC \
md.obsidian.Obsidian \
org.telegram.desktop \
io.freetubeapp.FreeTube
echo "Flatpaks removed."
echo ""
echo "============================================"
echo " All done!"
echo "============================================"
echo ""
echo "Summary:"
echo " KeePass database : ~/passwords/kpdb.kdbx (unchanged)"
echo " Config backup : ~/flatpak-config-backup/ (safe to delete later)"
echo " Downloaded debs : ~/Downloads/native-installs/ (safe to delete)"
echo " Zen installed to : /opt/zen"
echo ""
echo "Next: open Zen and re-run 'firefoxpwa profile patch' to fix PWAs"
+15
View File
@@ -0,0 +1,15 @@
services:
node-exporter:
image: prom/node-exporter:latest
container_name: node-exporter
restart: unless-stopped
network_mode: host
pid: host
volumes:
- /proc:/host/proc:ro
- /sys:/host/sys:ro
- /:/rootfs:ro
command:
- '--path.procfs=/host/proc'
- '--path.sysfs=/host/sys'
- '--collector.filesystem.mount-points-exclude=^/(sys|proc|dev|host|etc)($$|/)'
-176
View File
@@ -1,176 +0,0 @@
# Frigate NVR & CCTV Setup
**Last Updated:** July 2026
**Host:** jgpc — `192.168.88.41`
---
## Hardware
| Component | Details |
|-----------|---------|
| Machine | Lenovo jgpc P710 |
| CPU | Intel Xeon E5-2623 v4 @ 2.60 GHz (4 cores / 8 threads, boost to 3.2 GHz) |
| RAM | 24 GB |
| GPU | NVIDIA GeForce GTX 1050 Ti (4 GB VRAM) |
| OS Disk | 234 GB SSD (`/dev/sda2`) |
| Media Storage | 14 TB HDD — mounted at `/mnt/14TB-HD/frigate/media` |
---
## Frigate Docker Setup
| Item | Value |
|------|-------|
| Image | `ghcr.io/blakeblackshear/frigate:stable-tensorrt` |
| Container name | `frigate` |
| Config path | `/srv/docker/frigate/config/` |
| Media path | `/mnt/14TB-HD/frigate/media` |
| Compose file | `jgpc/frigate/docker-compose.yml` (this repo) |
| Web UI | `http://192.168.88.41:5000``https://cameras.jgitta.com` |
| RTSP port | `8554` |
| WebRTC port | `8555` |
**GPU acceleration:** Container runs with `runtime: nvidia` and `NVIDIA_VISIBLE_DEVICES=all`. The GTX 1050 Ti handles:
- LPR (licence plate recognition) via GPU
- Birdseye re-stream encoding via `h264_nvenc`
- ONNX object detection via the `onnx` detector
**Shared memory:** `shm_size: 1024mb` — required for frame buffers across 6 cameras.
---
## Object Detection
| Item | Value |
|------|-------|
| Detector | ONNX (GPU-accelerated) |
| Model | YOLOv9-t-320 (`yolov9-t-320.onnx`) |
| Input size | 320 × 320 |
| Label map | COCO-80 |
| Model path | `/config/model_cache/onnx/yolov9-t-320.onnx` |
---
## Licence Plate Recognition (LPR)
| Item | Value |
|------|-------|
| Enabled | Yes |
| Device | GPU |
| Model size | Small |
**Known plates:**
| Name | Plate |
|------|-------|
| Joe | ABC1234 |
| Cynthia | ABC4567 |
| Wesley | XYZ9876 |
---
## Camera Inventory
All cameras are **Reolink** PoE units on the `192.168.88.0/24` subnet.
| Camera | IP | Model notes | Mount location |
|--------|----|-------------|----------------|
| front_driveway | 192.168.88.105 | Dual-lens panoramic (7680×2160 = two 4K sensors stitched) | Front driveway |
| front_yard | 192.168.88.104 | 4K 811A | Front yard |
| shop | 192.168.88.103 | 4K 811A (uses HTTPS for API) | Shop / outbuilding |
| deck | 192.168.88.101 | 4K 811A | Deck |
| back_yard | 192.168.88.102 | 4K 811A | Back yard |
| front_right | 192.168.88.100 | 4512×2512 sensor (non-standard resolution) | Front right |
---
## Camera Stream Settings
All cameras confirmed via Reolink API (May 2026):
| Camera | Main Resolution | Main Codec | Bitrate | FPS | I-frame | Sub Resolution | Sub Codec |
|--------|----------------|------------|---------|-----|---------|----------------|-----------|
| front_driveway (.105) | 7680×2160 | H.265 | 10240 Kbps | 20 | 1x | 1536×432 | H.264 |
| front_yard (.104) | 3840×2160 | H.265 | 6144 Kbps | 25 | 1x | 640×360 | H.264 |
| shop (.103) | 3840×2160 | H.265 | 6144 Kbps | 25 | 1x | 640×360 | H.264 |
| deck (.101) | 3840×2160 | H.265 | 6144 Kbps | 25 | 1x | 640×360 | H.264 |
| back_yard (.102) | 3840×2160 | H.265 | 6144 Kbps | 25 | 1x | 640×360 | H.264 |
| front_right (.100) | 4512×2512 | H.265 | 8192 Kbps | 20 | 1x | 896×512 | H.264 |
**I-frame interval** set to 1x on all cameras so Frigate can start/end recording clips cleanly without waiting for a keyframe.
---
## go2rtc Stream Architecture
Chrome/Firefox cannot play H.265 (HEVC) via MSE (Media Source Extensions), so each camera requires two go2rtc streams:
```
camera_h265 → native H.265 RTSP from camera
└─ consumed by Frigate ffmpeg for RECORDING (stream copy, no re-encode)
camera → ffmpeg:camera_h265#video=h264 (software H.265 → H.264 transcode)
└─ served to browser via MSE WebSocket for LIVE VIEW
camera_sub → native low-res H.264 RTSP sub-stream
└─ consumed by Frigate for OBJECT DETECTION only
```
**Key behaviour notes:**
- go2rtc uses **lazy initialization** — transcoders only start when a browser opens the live view, and shut down ~30 seconds after the last viewer leaves. This causes a 23 second gray screen on first view, which is expected and unavoidable.
- The `_h265` streams are kept alive continuously by Frigate's recording ffmpeg processes, so recording starts instantly.
- The `#hardware` flag for NVENC transcoding in go2rtc causes EOF crashes with Reolink cameras and **must not be used**. Software transcoding (`libx264`) is used instead.
- Reolink cameras drop RTSP connections every 520 minutes (firmware behaviour). go2rtc reconnects automatically; this produces a brief gray screen, not static.
### shop camera note
The shop camera (`192.168.88.103`) redirects HTTP → HTTPS. All API calls to shop must use `https://` with `-k` (skip cert verification).
---
## Frigate Camera Detection Zones
| Camera | Detect Resolution | Tracked Objects | Notes |
|--------|------------------|-----------------|-------|
| front_driveway | 1536×432 | person, car, dog, cat, license_plate | Has zone + LPR; alerts require `front_driveway_zone` |
| front_yard | 640×360 | person, car, dog, cat | — |
| shop | 640×360 | person, car, dog, cat | Motion mask on upper-right and mid-frame |
| deck | 640×360 | person, car, dog, cat | Two motion masks |
| back_yard | 640×360 | person, car, dog, cat | Two motion masks |
| front_right | 896×512 | person, car, dog, cat | Motion mask on upper-centre |
---
## Recording & Retention
| Type | Retention |
|------|-----------|
| Alert clips | 30 days |
| Detection clips | 30 days |
| Motion clips | 30 days |
| Continuous | Disabled |
| Snapshots | 30 days |
---
## Known Issues & Quirks
- **Reolink H.265 sub-stream firmware bug:** On some Reolink firmware versions, setting the sub-stream to H.265 produces a corrupted bitstream that go2rtc rejects with 404. Sub-streams should always remain H.264.
- **front_driveway panoramic:** The 7680×2160 resolution is two 4K sensors stitched side by side. The wide 1536×432 detect resolution in the Frigate config accounts for this.
- **front_right non-standard resolution:** Camera reports 4512×2512 rather than standard 4K. Model unknown — may be a 5MP variant or older sensor.
- **Swap pressure:** jgpc runs ~5.5 GB swap used under normal load. Monitor if adding more cameras or enabling Birdseye.
---
## Changelog
### 2026-07-07 — Live view fixes, audio, and two-way talk
- **Green-box fix (front_driveway High stream):** the exec transcode crash-looped because Reolink RTSP emits corrupt HEVC frames at stream-join ("PPS changed between slices"); ffmpeg fell back to software decode and the GPU-only `scale_cuda` filter failed. Replaced with `-hwaccel cuvid -c:v hevc_cuvid -resize 3840x1080` — decode + resize stay on the GPU, no filter graph. (Also: NVENC caps H.264 at 4096 px wide, so the 7680×2160 Duo 3 main stream must be scaled before encode.)
- **front_right:** main stream is native H.264 2560×1440 — replaced NVENC transcode with direct passthrough (frees a GPU encode session). Live label renamed "High (Native)".
- **Live stream selectors:** added `live: streams:` (High/Low) to all 6 cameras to pin stream choice per device.
- **Audio:** enabled on-camera mic audio for front_driveway (Duo 3) and shop (both were off in camera firmware); front_driveway transcode now passes AAC (`-c:a copy` instead of `-an`).
- **Two-way talk (all 6 cameras):** each live go2rtc stream now has 3 sources — primary video, `ffmpeg:<name>#audio=opus` (WebRTC audio), and `onvif://<camera>:8000` (backchannel). Added `webrtc: candidates: [192.168.88.41:8555, stun:8555]`. LAN-only unless 8555 tcp/udp is forwarded.
- **LPR:** removed `license_plate` from front_driveway tracked objects — the COCO-80 model has no such label (startup warning); LPR runs via Frigate's dedicated plate detector on `car` objects.
- **Camera-side:** Duo 3 interframe space set to 1x (gop 1) per Frigate live-view docs for faster stream startup/recovery.
- Full camera hardware specs: see `frigate-cameras.md` (this folder).
-153
View File
@@ -1,153 +0,0 @@
# Kopia Restore Guide
**Last Updated**: May 8, 2026
**Applies To**: Both Kopia repositories on siklos (192.168.88.27)
---
## ⚠️ Critical: What You Need to Restore
You need **two things** to restore from Backblaze B2. Without either one, data is unrecoverable:
1. **Repository password** — stored in `credentials.md`
2. **Backblaze B2 account access** — credentials in `credentials.md`
> **Do not store these only on siklos.** Keep them in a password manager or printed copy somewhere safe. If siklos dies and you haven't saved the password elsewhere, your backup is unreadable.
---
## 📦 Your Two Repositories
| Container | B2 Bucket | What It Backs Up | Port |
|-----------|-----------|-----------------|------|
| `Kopia` | jg-kopia-nextcloud | Nextcloud files + database | 51515 |
| `Kopia-Immich` | jg-kopia-immich | Immich photos + database | 51516 |
---
## 🔍 Step 1 — List Available Snapshots
SSH to siklos first:
```bash
ssh jgitta@siklos
```
**Nextcloud snapshots:**
```bash
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia snapshot list
```
**Immich snapshots:**
```bash
docker exec -e KOPIA_PASSWORD='<password>' Kopia-Immich kopia snapshot list
```
Each snapshot has an ID like `k8adffae645082989d7c0f4f051439332` and a timestamp. You can restore from any snapshot in history — not just the latest.
---
## 📁 Step 2 — Browse a Snapshot (Find Specific Files)
You can explore a snapshot like a folder before restoring anything:
```bash
# List the top-level contents of a snapshot
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia ls <snapshot-id>
# Drill into a subfolder
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia ls <snapshot-id>/jgitta/files/Documents
# Search for a specific filename across a snapshot
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia find <snapshot-id> --name "*.jpg"
```
This lets you confirm exactly what you want to restore before doing it.
---
## ♻️ Step 3 — Restore Options
### Restore a Single File
```bash
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia restore \
<snapshot-id>/path/to/filename.jpg \
/tmp/restore/filename.jpg
```
### Restore a Specific Folder
```bash
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia restore \
<snapshot-id>/jgitta/files/Photos \
/tmp/restore/Photos/
```
### Restore an Entire Snapshot
```bash
docker exec -e KOPIA_PASSWORD='<password>' Kopia kopia restore \
<snapshot-id> \
/tmp/restore/
```
> **Note:** Restored files land on siklos at `/tmp/restore/`. From there you can copy them to Nextcloud, Immich, or anywhere else via `scp` or `rsync`.
---
## 🚨 Emergency: Siklos Is Gone — Restore from Scratch
If siklos itself is dead, you can restore to any Linux machine:
### 1. Install Kopia
```bash
curl -s https://kopia.io/signing-key | sudo gpg --dearmor -o /usr/share/keyrings/kopia-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/kopia-keyring.gpg] http://packages.kopia.io/apt/ stable main" | sudo tee /etc/apt/sources.list.d/kopia.list
sudo apt update && sudo apt install kopia
```
### 2. Connect to Your B2 Repository
**For Nextcloud backup:**
```bash
kopia repository connect b2 \
--bucket=jg-kopia-nextcloud \
--key-id=00522b2471e5f090000000003 \
--key=K005wv6tlgknxkM9arOJV/tMeOInOQY \
--password=<repository-password>
```
**For Immich backup:**
```bash
kopia repository connect b2 \
--bucket=jg-kopia-immich \
--key-id=00522b2471e5f090000000004 \
--key=K005gSepxpWk9hWx/MjWnCKtOiNuKnQ \
--password=<repository-password>
```
### 3. List and Restore
```bash
kopia snapshot list
kopia restore <snapshot-id> /path/to/restore/
```
> The B2 key IDs and application keys above are in `credentials.md`. Replace `<repository-password>` with the password from `credentials.md`.
---
## 💡 Key Points
- **Backblaze "Browse Files"** shows encrypted chunks — this is normal and expected. Files are not human-readable in B2 directly.
- **Every snapshot is independent** — you can restore from last week's snapshot even if today's is corrupted.
- **Partial restores are fully supported** — restore one file, one folder, or everything.
- **Kopia is open source** — even if the project were abandoned, you can always download the binary from GitHub and reconnect to your B2 repository as long as you have your password.
---
## 📋 Restore Checklist
- [ ] Have repository password (from `credentials.md`)
- [ ] Have B2 credentials (from `credentials.md`)
- [ ] SSH to siklos (or install Kopia on alternate machine)
- [ ] Run `snapshot list` to find correct snapshot by date
- [ ] Use `kopia ls` to browse and confirm the right files
- [ ] Run `kopia restore` with specific path for targeted restore
- [ ] Copy restored files to final destination
+260
View File
@@ -0,0 +1,260 @@
# Nextcloud → ownCloud Infinite Scale (OCIS) Migration
**Date:** 2026-06-09
**Status:** 🟡 Migration in progress — data transfer running, cutover pending
**Author:** Joe Gitta
---
## What Was Done (Summary)
VM 114 (`ocis`, 192.168.88.63) was created on Proxmox and OCIS v8.0.4 was deployed as a native binary (systemd service). Both Nextcloud users (`jgitta` 89 GB / `cgitta` 210 GB) were recreated in OCIS and rclone migrations launched. Prometheus monitoring was added. Nextcloud (VM 103) remains running — no cutover yet.
---
## Deployed Architecture
```
Internet / LAN
Caddy (192.168.88.110)
├── next.jgitta.com → VM 103 (Nextcloud, 192.168.88.62) ← still running
└── cloud.jgitta.com → VM 114 (OCIS, 192.168.88.63:9200)
└── Local SSD: /mnt/ocis-data (500 GB, Proxmox SSD-2)
```
---
## VM 114 (ocis) Specs
| Item | Value |
|---|---|
| VM ID | 114 |
| VM Name | ocis |
| IP | 192.168.88.63 |
| SSH | `ssh jgitta@192.168.88.63` |
| vCPU | 4 cores |
| RAM | 8 GB |
| OS Disk | 64 GB (local SSD) |
| Data Disk | 500 GB SSD (Proxmox SSD-2) → `/mnt/ocis-data` |
| OS | Debian 13 (cloned from VM 9000 template) |
| OCIS Version | 8.0.4 |
| Binary | `/usr/local/bin/ocis` |
| Service | `systemctl {start|stop|restart|status} ocis` |
| Config | `/mnt/ocis-data/config/ocis.yaml` (auto-generated on first run) |
| Env file | `/mnt/ocis-data/config/ocis.env` |
| Systemd unit | `/etc/systemd/system/ocis.service` |
**Note:** Local disk was chosen over TrueNAS NFS so that PBS full-VM backups capture both OS and data together, providing an extra layer of redundancy.
---
## OCIS Configuration
`/mnt/ocis-data/config/ocis.env`:
```
OCIS_URL=https://cloud.jgitta.com
OCIS_BASE_DATA_PATH=/mnt/ocis-data
OCIS_CONFIG_DIR=/mnt/ocis-data/config
PROXY_TLS=false
OCIS_LOG_LEVEL=info
OCIS_LOG_COLOR=false
PROXY_ENABLE_BASIC_AUTH=true
```
> **Why `PROXY_ENABLE_BASIC_AUTH=true`?** OCIS v8 disables basic auth by default (OIDC only). This flag re-enables it so that rclone WebDAV and desktop/mobile sync clients can authenticate with username + password.
---
## Accounts
See `ocis-credentials.md` for full credential details.
| Username | Display Name | Use |
|---|---|---|
| admin | Admin | Administration only |
| jgitta | Joe Gitta | Primary account |
| cgitta | Cynthia Gitta | Secondary account |
Accounts were created via direct LDAP manipulation (OCIS v8 has no CLI user-create tool). The libregraph LDAP sysuser (`uid=libregraph,ou=sysusers,o=libregraph-idm`) was used for write access; passwords set with `ldappasswd` over LDAPS on `127.0.0.1:9235`.
---
## Data Migration Status
rclone is copying files directly from Nextcloud's filesystem on VM 103 (avoiding WebDAV overhead) and pushing to OCIS via WebDAV. Both processes run as background jobs on VM 103.
```bash
# Monitor from Nextcloud VM (192.168.88.62):
sudo tail -f /tmp/rclone-jgitta.log
sudo tail -f /tmp/rclone-cgitta.log
# Check if still running:
ps aux | grep rclone | grep -v grep
```
| User | Source | Data | Status |
|---|---|---|---|
| jgitta | `/mnt/nextcloud-data/jgitta/files/` | ~35 GB | 🔄 In progress |
| cgitta | `/mnt/nextcloud-data/cgitta/files/` | ~210 GB | 🔄 In progress (ETA many hours) |
**Expected errors:** Some files show `Failed to copy: object not found` — these are files present in Nextcloud's database but missing from disk (DB/filesystem desync in Nextcloud). Non-fatal; rclone skips and continues.
### rclone Config on VM 103
`/root/.config/rclone/rclone.conf` contains two WebDAV remotes:
- `ocis_jgitta``https://cloud.jgitta.com/remote.php/dav/files/jgitta/`
- `ocis_cgitta``https://cloud.jgitta.com/remote.php/dav/files/cgitta/`
### Re-run Migration (if needed)
```bash
# SSH to Nextcloud VM
ssh jgitta@192.168.88.62
# Re-run jgitta (rclone copy is idempotent — safe to re-run)
sudo nohup rclone copy /mnt/nextcloud-data/jgitta/files/ ocis_jgitta: \
--transfers 4 --checkers 8 \
--log-file /tmp/rclone-jgitta.log --log-level INFO --stats 120s &
# Re-run cgitta
sudo nohup rclone copy /mnt/nextcloud-data/cgitta/files/ ocis_cgitta: \
--transfers 4 --checkers 8 \
--log-file /tmp/rclone-cgitta.log --log-level INFO --stats 120s &
```
---
## Caddy Config
Added to `/etc/caddy/sites/media.caddy` on Caddy VM (192.168.88.110):
```caddy
cloud.jgitta.com {
import web_secure
reverse_proxy 192.168.88.63:9200 {
import proxy_timeouts
}
}
```
---
## Monitoring
- **node-exporter** running on OCIS VM as Docker container, port **9101** (port 9100 is taken by OCIS itself)
- **Prometheus** on siklos scrapes `192.168.88.63:9101`, labeled as instance `ocis`
- PBS backup job `backup-5d4b43bd-7988` uses `all=1` — VM 114 is automatically included, no changes needed
---
## Pending Steps (Manual)
### 1. Add Cloudflare DNS record ⚠️ REQUIRED for external access
Log into Cloudflare dashboard → jgitta.com DNS zone → Add A record:
- **Name:** `cloud`
- **Content:** your WAN IP
- **Proxy:** Grey cloud (DNS only)
Internal access via `cloud.jgitta.com` works already (MikroTik wildcard → Caddy).
### 2. Verify files after migration completes
Once rclone logs show completion:
1. Log into https://cloud.jgitta.com as `jgitta` and `cgitta`
2. Spot-check files in the web UI — confirm folder structure looks right
3. Check error count in logs: `sudo grep ERROR /tmp/rclone-jgitta.log | wc -l`
### 3. Change default passwords
Log into https://cloud.jgitta.com and change passwords for both accounts (see `ocis-credentials.md` for current values).
### 4. Reconnect desktop/mobile sync clients
- Desktop: Add account pointing to `https://cloud.jgitta.com` in ownCloud or Nextcloud desktop client
- Mobile: Add account in ownCloud app (iOS/Android) — same URL
### 5. OnlyOffice integration (optional, can defer)
OCIS uses WOPI for document editing. The existing OnlyOffice at `https://office.jgitta.com` supports WOPI.
In OCIS Admin Settings → Apps, configure the WOPI server URL.
---
## Cutover Procedure (when ready)
Once files are verified and clients reconnected:
1. **Enable Nextcloud maintenance mode** (prevents new uploads):
```bash
ssh jgitta@192.168.88.62
sudo -u www-data php /var/www/nextcloud/occ maintenance:mode --on
```
2. **Run a final rclone sync** to catch any files changed since the initial copy:
```bash
sudo rclone copy /mnt/nextcloud-data/jgitta/files/ ocis_jgitta: --transfers 4 --stats-log-level NOTICE
sudo rclone copy /mnt/nextcloud-data/cgitta/files/ ocis_cgitta: --transfers 4 --stats-log-level NOTICE
```
3. **Redirect Caddy** — update `next.jgitta.com` in Caddy to redirect to OCIS:
```caddy
next.jgitta.com {
redir https://cloud.jgitta.com{uri} permanent
}
```
4. **Stop VM 103** in Proxmox — do NOT delete it yet
5. **After 4 weeks with no issues** — delete VM 103
---
## Service Management Quick Reference
```bash
# OCIS status / logs
ssh jgitta@192.168.88.63 "sudo systemctl status ocis"
ssh jgitta@192.168.88.63 "sudo journalctl -u ocis -f"
# Restart OCIS
ssh jgitta@192.168.88.63 "sudo systemctl restart ocis"
# Check migration logs (run on Nextcloud VM)
ssh jgitta@192.168.88.62 "sudo tail -50 /tmp/rclone-jgitta.log"
ssh jgitta@192.168.88.62 "sudo tail -50 /tmp/rclone-cgitta.log"
# Check OCIS disk usage
ssh jgitta@192.168.88.63 "df -h /mnt/ocis-data"
```
---
## Checklist
- [x] VM 114 created on Proxmox (Debian 13, 4 vCPU, 8 GB RAM)
- [x] Static IP 192.168.88.63 configured
- [x] 500 GB data disk partitioned and mounted at `/mnt/ocis-data`
- [x] OCIS v8.0.4 binary installed at `/usr/local/bin/ocis`
- [x] OCIS systemd service configured and running
- [x] `cloud.jgitta.com` added to Caddy config and reloaded
- [x] OCIS users created: `admin`, `jgitta`, `cgitta`
- [x] rclone configured on VM 103 with both user remotes
- [x] Data migration launched (background, running)
- [x] node-exporter deployed on VM 114 (port 9101)
- [x] Prometheus scraping VM 114 confirmed (`up`)
- [x] PBS backup covers VM 114 automatically (`all=1`)
- [ ] **Cloudflare DNS A record for `cloud`** ← add manually
- [ ] Verify files in OCIS web UI after migration completes
- [ ] Change passwords from defaults
- [ ] Reconnect desktop/mobile sync clients
- [ ] OnlyOffice WOPI integration (optional)
- [ ] Final sync + enable Nextcloud maintenance mode
- [ ] Redirect `next.jgitta.com` → `cloud.jgitta.com` in Caddy
- [ ] Stop VM 103
- [ ] Delete VM 103 (after 4-week verification)
@@ -0,0 +1,185 @@
# Nextcloud → OCIS Migration File Comparison Report
**Date:** 2026-06-22
**Source:** Nextcloud VM 103 (192.168.88.62)
**Destination:** OCIS VM 114 (cloud.jgitta.com)
---
## Summary
| User | NC Files | OCIS Files | Missing | Reorganized (safe) | Truly Absent |
|------|----------|------------|---------|-------------------|--------------|
| jgitta | 55,015 | 54,541 | 4,575 raw | 4,025 InstantUpload date-reorganized | ~1,021 |
| cgitta | 464 | 491 | 63 raw | 5 Nextcloud defaults | 58 |
**"Breakfast Locations.md" status: ✅ PRESENT in OCIS at `Notes/General/Breakfast Locations.md`**
(Not visible yet because desktop sync client hasn't been reconnected.)
---
## jgitta — Non-InstantUpload Missing Files (19)
These are the files most worth recovering — documents and important files:
1Business/Business/Chrome Passwords.ods
1Business/Business/Coloff.CDB
1Business/Business/Ninja/1P002227017-119291-1587748816ScheduleC119291-AMMEND.pdf
1Business/Business/Ninja/ALL - Copy.csv
1Business/Business/Ninja/DisableSleep.bat
1Business/Business/Ninja/DisableSleep - Copy.bat
1Business/Business/Ninja/jgbusinessservicesmainoffice-5.3.2695-installer - Copy.deb
1Business/Business/Ninja/ninjaUninstallScript - Copy.ps1
1Business/Business/Ninja/ninjaUninstallScript.ps1
1Business/Business/OS ISO Files/AlmaLinux-9-latest-x86_64-boot.iso
1Business/Business/OS ISO Files/Windows.iso
1Business/Business/RMS-License Sample - Copy.jpg
1Business/Business/RMS licenses - Copy.pdf
1Business/Business/RMS Product Keys.pdf
Personal/Automation/Coinbase API keys.html
Personal/Automation/Coinbase API keys.odt
Personal/House/Oakcrest/FC0011599_0052220021552.pdf
Personal/House/Oakcrest/WMMortgagorAffidavit filled in.pdf
Systems/Dell/Dell-OS-Recovery-Tool_WFFJR_WIN64_2.4.2.2193_A00.EXE
---
## jgitta — Missing InstantUpload Photos (1,002 truly absent)
These are phone camera uploads that exist in Nextcloud but have no matching filename anywhere in OCIS.
The filenames use UUID/timestamp format (e.g. `1579148769626-f5524435-....jpg`), suggesting they are from
an older era before the auto-date-folder organization was introduced.
Full list saved separately. Sample (first 20):
InstantUpload/Camera/1579148769626-f5524435-0370-46e6-aeac-9d123639a275.jpg
InstantUpload/Camera/1579148814788-c91a92a7-c620-4436-b125-2807801301a5.jpg
InstantUpload/Camera/1579148844698-cd639f3a-6b2a-435d-aa6e-fc17d84ebd99.jpg
InstantUpload/Camera/1579148913181-ce4c026e-ad67-454c-859f-190ef4edc604.jpg
InstantUpload/Camera/1579148963218-5e6351a0-0638-49d6-9ccb-cd9019dcfed6.jpg
InstantUpload/Camera/1579149042550-b680921f-0b29-4076-adb1-5ab886f7d000.jpg
InstantUpload/Camera/1579149078795-13a6fe2b-3d38-4cd9-83cf-eabee42756f9.jpg
InstantUpload/Camera/1579149136687-16520269-f499-4127-aa6a-feca7c92ea0c.jpg
InstantUpload/Camera/1579149162662-81d1db7c-a7c6-4c35-b0ca-38d0678059a6.jpg
InstantUpload/Camera/1579149196301-0900112b-15d6-44f9-b242-d5c00bd5d0f3.jpg
InstantUpload/Camera/1608011136522~2.jpg
InstantUpload/Camera/1608011136522~3.jpg
InstantUpload/Camera/1608011136522.jpg
InstantUpload/Camera/20210715_120809.jpg
InstantUpload/Camera/20210715_120811.jpg
InstantUpload/Camera/20210805_213548.jpg
InstantUpload/Camera/20210805_230916.jpg
InstantUpload/Camera/20210805_230919.jpg
InstantUpload/Camera/20210805_230929.jpg
InstantUpload/Camera/20210805_230949.jpg
---
## jgitta — InstantUpload Files Reorganized (4,025 — safe, not lost)
These files exist in Nextcloud as flat files under `InstantUpload/Camera/` or `InstantUpload/BlueIris/`
but in OCIS they were placed into year/month subfolders (e.g. `InstantUpload/Camera/2019/2019-06/`).
They are NOT missing — just in a different path.
---
## cgitta — Missing Files (58)
Documents/4H/Photos/2010-2011/photos from phone April 2011 310.jpg
Documents/4H/Photos/2010-2011/photos from phone April 2011 315.jpg
Documents/4H/Photos/Oct 2008/October 010.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 012.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 015.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 020.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 034.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 041.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 042.jpg
Documents/4H/Photos/Sewing 2009/Sewing2009 044.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 045.jpg
Documents/4H/Photos/Sewing 2009/Sewing 2009 046.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 001.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 003.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 007.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 008.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 010.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 011.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 012.jpg
Documents/4H/Photos/We piano Dec 2009 4H Officer induction 2010 014.jpg
Documents/New House/17-6-1__1433 oakcrest dr, waterloo.PDF
Documents/New House/Photos/20170410_131249.jpg
Documents/New House/Photos/20170410_131254.jpg
Documents/New House/Photos/20170410_131406.jpg
Documents/New House/Photos/20170410_131411.jpg
Documents/New House/Photos/20170410_131425.jpg
Documents/New House/Photos/20170410_131501.jpg
Documents/New House/Photos/20170410_131508.jpg
Documents/New House/Photos/20170410_131527.jpg
Documents/New House/Photos/20170410_131542.jpg
Documents/New House/Photos/20170410_131547.jpg
Documents/New House/Photos/20170410_131558.jpg
Documents/New House/Photos/20170410_131947.jpg
Documents/New House/Photos/20170410_132027.jpg
Documents/New House/Photos/20170410_132038.jpg
Documents/New House/Photos/20170410_132052.jpg
Documents/New House/Photos/20170410_132120.jpg
Documents/New House/Photos/20170410_132131.jpg
Documents/New House/Photos/20170410_132142.jpg
Documents/Personal photos/Copy of IMG_0016.JPG
Documents/Personal photos/DSC_3566.JPG
Documents/Personal photos/DSCF0145.JPG
Documents/Personal photos/goofy wes face.jpg
Documents/Personal photos/IMG_20150923_075400869.jpg
Documents/Personal photos/photos from phone April 2011 038.jpg
Documents/Personal photos/photos from phone April 2011 039.jpg
Documents/Personal photos/photos from phone April 2011 040.jpg
Documents/Personal photos/photos from phone April 2011 042.jpg
Documents/Personal photos/photos from phone April 2011 043.jpg
Documents/Personal photos/photos from phone April 2011 314.jpg
Documents/Personal photos/photos from phone April 2011 315.jpg
InstantUpload/Attachments (2)/DSC01195.JPG
InstantUpload/Attachments (2)/DSC01202.JPG
InstantUpload/Attachments (2)/DSC01238.JPG
InstantUpload/Screenshots/Screenshot_20251128-084130.Facebook.png
InstantUpload/Screenshots/Screenshot_20260315-173818.Chrome.png
Video/IMG_20151020_124250944_HDR.jpg
Video/IMG_3977.JPG
---
## cgitta — Nextcloud Default Files (not migrated, not needed)
These 5 files are Nextcloud's built-in welcome files — safe to ignore:
- Nextcloud intro.mp4
- Nextcloud Manual.pdf
- Nextcloud.png
- Readme.md
- Reasons to use Nextcloud.pdf
---
## Recommended Actions
1. **Breakfast Locations.md** — Already in OCIS. Reconnect your desktop sync client to `https://cloud.jgitta.com` to see it.
2. **jgitta — 19 non-InstantUpload files** — These are business docs and personal files that should be re-uploaded manually.
Priority items: `Personal/Automation/Coinbase API keys.*`, `1Business/Business/RMS Product Keys.pdf`, mortgage docs.
3. **jgitta — 1,002 missing InstantUpload camera photos** — These are older photos (2020 era, UUID filenames).
Can be re-uploaded from Nextcloud via rclone re-run targeting just InstantUpload/Camera.
4. **cgitta — 58 missing files** — All photos (4H, New House, personal). Can be re-uploaded via rclone re-run.
---
## Re-run rclone for Missing Files (run on VM 103)
```bash
# Re-sync jgitta InstantUpload only
sudo rclone copy /mnt/nextcloud-data/jgitta/files/InstantUpload/ ocis_jgitta:InstantUpload/ \
--transfers 4 --log-file /tmp/rclone-resync-jgitta.log --log-level INFO
# Re-sync cgitta Documents
sudo rclone copy /mnt/nextcloud-data/cgitta/files/ ocis_cgitta: \
--transfers 4 --log-file /tmp/rclone-resync-cgitta.log --log-level INFO
```
Note: rclone copy is idempotent — already-transferred files are skipped.
-76
View File
@@ -1,76 +0,0 @@
# ownCloud Photo Organizer
## Overview
ownCloud's Android app does not support automatic year/month subfolder creation
during instant upload (unlike Nextcloud). This script compensates by running
nightly and sorting all uploaded photos and videos into a `YEAR/YEAR-MM/`
folder structure.
## Script
**Location on server:** `/usr/local/bin/organize_photos.sh`
**Source folder watched:**
`/mnt/INTEL-SSD/ownCloud - Joe Gitta@cloud.jgitta.com/Personal/InstantUpload`
**Subfolders processed:** `Camera`, `BlueIris`, `Facebook`, `scans`, `EufyVideoDir`
**Supported file types:** jpg, jpeg, png, gif, heic, webp, bmp, tiff, mp4, mov, pdf
## How It Works
The script reads the date from each filename using these patterns (in order):
1. `YYYYMMDD` embedded in the filename (e.g. `20231022_082107.jpg`)
2. `YYYY-MM-DD` or `YYYY_MM_DD` format
3. 13-digit Unix timestamp in milliseconds (e.g. `FB_IMG_1572815868839.jpg`)
4. `MM_DD_YYYY` prefix (scans folder format, e.g. `06_11_2026_7_28_16_AM.pdf`)
Files that don't match any pattern are moved to `_unsorted/` for manual review.
## Output Structure
```
InstantUpload/
└── Camera/
├── 2023/
│ ├── 2023-06/
│ └── 2023-12/
├── 2024/
│ └── 2024-03/
└── _unsorted/
```
## Cron Job
Runs nightly at 2:00 AM as root:
```
0 2 * * * /usr/local/bin/organize_photos.sh
```
To view or edit: `sudo crontab -e`
## Log File
Each run appends to `/var/log/organize_photos.log`:
```
=== 2026-06-14 02:00:01 Starting ===
Moved: 12 Unsorted: 0 Skipped: 0 Errors: 0
=== Done ===
```
Check the log anytime with:
```bash
cat /var/log/organize_photos.log
```
## Installation
```bash
sudo cp organize_photos.sh /usr/local/bin/organize_photos.sh
sudo chmod +x /usr/local/bin/organize_photos.sh
(sudo crontab -l; echo "0 2 * * * /usr/local/bin/organize_photos.sh") | sudo crontab -
```
-72
View File
@@ -1,72 +0,0 @@
#!/usr/bin/env bash
BASE="/mnt/INTEL-SSD/ownCloud - Joe Gitta@cloud.jgitta.com/Personal/InstantUpload"
UNSORTED="$BASE/_unsorted"
LOG="/var/log/organize_photos.log"
moved=0; skipped=0; unsorted=0; errors=0
echo "=== $(date +%Y-%m-%d\ %H:%M:%S) Starting ===" >> "$LOG"
ts_to_ym() {
local ts_ms="$1"
local ts_s=$(( ts_ms / 1000 ))
date -d "@$ts_s" "+%Y %m" 2>/dev/null
}
get_ym() {
local f="$1"
local b
b=$(basename "$f")
if [[ "$b" =~ (^|[^0-9])([0-9]{4})(0[1-9]|1[0-2])(0[1-9]|[12][0-9]|3[01]) ]]; then
echo "${BASH_REMATCH[2]} ${BASH_REMATCH[3]}"; return
fi
if [[ "$b" =~ ([0-9]{4})[-_](0[1-9]|1[0-2])[-_](0[1-9]|[12][0-9]|3[01]) ]]; then
echo "${BASH_REMATCH[1]} ${BASH_REMATCH[2]}"; return
fi
if [[ "$b" =~ (^|[^0-9])([0-9]{13})([^0-9]|$) ]]; then
ts_to_ym "${BASH_REMATCH[2]}"; return
fi
if [[ "$b" =~ ^([0-9]{2})_([0-9]{2})_([0-9]{4})_ ]]; then
echo "${BASH_REMATCH[3]} ${BASH_REMATCH[1]}"; return
fi
echo ""
}
move_file() {
local src="$1"
local root="$2"
local fname
fname=$(basename "$src")
local ym
ym=$(get_ym "$src")
if [[ -z "$ym" ]]; then
mkdir -p "$UNSORTED"
if [[ ! -e "$UNSORTED/$fname" ]]; then
mv "$src" "$UNSORTED/$fname" && ((unsorted++)) || ((errors++))
else
((skipped++))
fi
return
fi
local year month dest_dir dest
year=$(echo "$ym" | cut -d' ' -f1)
month=$(echo "$ym" | cut -d' ' -f2)
dest_dir="$root/$year/$year-$month"
dest="$dest_dir/$fname"
mkdir -p "$dest_dir"
if [[ -e "$dest" ]]; then
((skipped++)); return
fi
mv "$src" "$dest" && ((moved++)) || { echo " ERROR: $fname" >> "$LOG"; ((errors++)); }
}
for SUBDIR in Camera BlueIris Facebook scans EufyVideoDir; do
SUBPATH="$BASE/$SUBDIR"
[[ -d "$SUBPATH" ]] || continue
while IFS= read -r -d '' f; do
move_file "$f" "$SUBPATH"
done < <(find "$SUBPATH" -maxdepth 1 -type f -print0)
done
echo " Moved: $moved Unsorted: $unsorted Skipped: $skipped Errors: $errors" >> "$LOG"
echo "=== Done ===" >> "$LOG"
+14 -1
View File
@@ -8,7 +8,7 @@
- Docker compose files: `/srv/docker/<service>/docker-compose.yml` - Docker compose files: `/srv/docker/<service>/docker-compose.yml`
- Note: `vm.swappiness=10` set in `/etc/sysctl.conf` (April 2026) - Note: `vm.swappiness=10` set in `/etc/sysctl.conf` (April 2026)
## Running Containers (verified 2026-06-18) ## Running Containers (verified 2026-06-24, cleaned 2026-06-24)
| Container | Image | Port(s) | Compose Path | | Container | Image | Port(s) | Compose Path |
|---|---|---|---| |---|---|---|---|
@@ -33,6 +33,7 @@
| dashy | lissy93/dashy:latest | 8081 | /srv/docker/dashy/ | | dashy | lissy93/dashy:latest | 8081 | /srv/docker/dashy/ |
| dashboard | dashboard-dashboard | 8096 | /srv/docker/dashboard/ | | dashboard | dashboard-dashboard | 8096 | /srv/docker/dashboard/ |
| grav | linuxserver/grav:latest | 8585 | /srv/docker/grav/ | | grav | linuxserver/grav:latest | 8585 | /srv/docker/grav/ |
| searxng | searxng/searxng:latest | 8092 | /srv/docker/searxng/ |
| watchtower | containrrr/watchtower | — | /srv/docker/watchtower/ | | watchtower | containrrr/watchtower | — | /srv/docker/watchtower/ |
## Open WebUI ## Open WebUI
@@ -45,6 +46,18 @@
- **Immich** runs on VM113 (photos, 192.168.88.32) — migrated from siklos in April 2026 - **Immich** runs on VM113 (photos, 192.168.88.32) — migrated from siklos in April 2026
- **PhotoPrism** was removed from siklos in June 2026 — Immich chosen as the single photo management solution - **PhotoPrism** was removed from siklos in June 2026 — Immich chosen as the single photo management solution
## Cleanup (2026-06-24)
Removed stale Portainer stacks and /srv/docker directories:
- **kopia** — replaced by Proxmox PBS for VM backups
- **monitoring** — replaced by Beszel (Prometheus/Grafana/cAdvisor)
- **immich** dir — container lives on VM113 (Portainer endpoint 8)
- **litellm** dir — stack had already been removed; directory was leftover
- **faster-whisper** dir — service runs on jgpc (192.168.88.41)
- **onlyoffice** dir — duplicate; container managed via /srv/docker/media/
Caddy: removed broken `guacamole.jgitta.com` block (was pointing to port 8585); added `ai.jgitta.com` → port 3000 (open-webui).
DNS: removed collabora, kopia, headscale, headscale-ui from MikroTik + Cloudflare. Removed blue.jgitta.com, mail.jgitta.com from Cloudflare.
## Pi-hole ## Pi-hole
- Port: 53 (DNS), 8080 (web UI) - Port: 53 (DNS), 8080 (web UI)
- URL: `https://pihole.jgitta.com` - URL: `https://pihole.jgitta.com`
-27
View File
@@ -1,27 +0,0 @@
services:
beszel:
image: henrygd/beszel:latest
container_name: beszel
restart: always
environment:
APP_URL: https://beszel.jgitta.com
ports:
- "8085:8090"
volumes:
- ./beszel_data:/beszel_data
- ./beszel_socket:/beszel_socket
beszel-agent:
image: henrygd/beszel-agent:latest
container_name: beszel-agent
restart: always
network_mode: host
volumes:
- ./beszel_agent_data:/var/lib/beszel-agent
- ./beszel_socket:/beszel_socket
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
LISTEN: /beszel_socket/beszel.sock
HUB_URL: http://localhost:8085
TOKEN: LNlfwOORlQXrfFFjFzjb7qCDZ4LVn-lECgkLmmwbyq8
KEY: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIF3921TigvLCBlSDqgm1bIz2To1ONUT0ahXszPTxlngU"
+65
View File
@@ -0,0 +1,65 @@
services:
prometheus:
image: prom/prometheus:latest
container_name: prometheus
restart: unless-stopped
ports:
- "9090:9090"
volumes:
- /srv/docker/monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--storage.tsdb.retention.time=30d"
- "--web.enable-lifecycle"
- "--web.enable-admin-api"
grafana:
image: grafana/grafana:latest
container_name: grafana
restart: unless-stopped
ports:
- "3020:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=Jogiocsi1211+
- GF_USERS_ALLOW_SIGN_UP=false
- FTLCONF_dns_maxConcurrentQueries=300
depends_on:
- prometheus
cadvisor:
image: gcr.io/cadvisor/cadvisor:latest
container_name: cadvisor
restart: unless-stopped
privileged: true
ports:
- "8090:8080"
volumes:
- /:/rootfs:ro
- /var/run:/var/run:ro
- /sys:/sys:ro
- /var/lib/docker:/var/lib/docker:ro
- /dev/disk/:/dev/disk:ro
graphite-exporter:
image: prom/graphite-exporter:latest
container_name: graphite-exporter
restart: unless-stopped
ports:
- "9109:9109"
- "9109:9109/udp"
- "9108:9108"
volumes:
- /srv/docker/monitoring/graphite/graphite_mapping.conf:/tmp/graphite_mapping.conf:ro
command:
- "--graphite.mapping-config=/tmp/graphite_mapping.conf"
- "--graphite.listen-address=:9109"
- "--web.listen-address=:9108"
volumes:
prometheus_data:
grafana_data:
+232
View File
@@ -0,0 +1,232 @@
# Workstation OS Drive — Disk Analysis & Cleanup Plan
**Date:** 2026-05-27
**OS Drive:** `/dev/sda2` — 234 GB total | **190 GB used (86%)** | only 32 GB free ⚠️
**Target drive:** `/mnt/14TB-HD` (`/dev/sdb2`) — 12.7 TB total | 2.7 TB used | **9.4 TB free**
---
## Drive Layout Summary
| Mount Point | Drive | Size | Used | Free | % |
|---|---|---|---|---|---|
| `/` (OS) | /dev/sda2 | 234 GB | 190 GB | 32 GB | **86% ⚠️** |
| `/mnt/14TB-HD` | /dev/sdb2 | 12.7 TB | 2.7 TB | 9.4 TB | 23% ✅ |
| `/mnt/INTEL-SSD` | /dev/nvme0n1p1 | 469 GB | 449 MB | 445 GB | <1% ✅ |
| `/mnt/StorFly-SSD` | /dev/sdc1 | 1.8 TB | 1.4 TB | 312 GB | 82% |
| `/mnt/photos` (NFS) | TrueNAS NFS | 11 TB | 6.4 TB | 3.9 TB | 63% |
---
## What's Eating Your OS Drive
### Top-Level Breakdown
| Category | Location | Size |
|---|---|---|
| Home directory total | `/home/jgitta` | **65 GB** |
| System packages & libs | `/var`, `/usr` | **~12 GB** |
| Installed apps | `/opt` | **5.2 GB** |
### The Biggest Offenders (inside /home)
| Item | Path | Size | Notes |
|---|---|---|---|
| Google Chrome AI model | `~/.config/google-chrome/OptGuideOnDeviceModel` | **4.0 GB** | Regenerates automatically — safe to delete |
| Flatpak app data | `~/.var/app/` | **7.0 GB** | Brave (3.7G), Dolphin (1.4G), DigiKam (0.9G), Zen (0.7G) |
| DigiKam face database | `~/.local/share/digikam` | **5.5 GB** | Move to 14TB (photo metadata) |
| Gemini scratch space | `~/.gemini/antigravity/scratch` | **3.5 GB** | Temp AI workspace — can be purged or redirected |
| .cache (total) | `~/.cache/` | **7.1 GB** | Browser caches, thumbnails, package caches |
| .config (total) | `~/.config/` | **13 GB** | Chrome (5.1G incl. 4G AI model), Claude (2.4G), Brave (2.1G) |
| Downloads folder | `~/Downloads` | **6.9 GB** | Mostly old installers |
| debian-iso folder | `~/debian-iso` | **5.0 GB** | ISOs — move or delete |
| VS Code extensions | `~/.vscode` | **1.9 GB** | Can move to 14TB |
| Firefox profile | `~/.mozilla` | **1.5 GB** | Mostly cache |
| .rustup | `~/.rustup` | **1.4 GB** | Rust toolchain |
| .npm | `~/.npm` | **1.4 GB** | npm cache |
### Big items in /var and /opt
| Item | Path | Size | Notes |
|---|---|---|---|
| Flatpak system install | `/var/lib/flatpak` | **6.7 GB** | Runtimes, apps |
| System journal logs | `/var/log/journal` | **2.9 GB** | Can be capped to 500 MB |
| APT package cache | `/var/cache/apt` | **610 MB** | Safe to clear |
| Beekeeper Studio | `/opt/Beekeeper Studio` | **1.2 GB** | App install |
| Frigate config | `/opt/frigate/config` | **913 MB** | Move to 14TB |
| Netdata | `/opt/netdata` | **693 MB** | Move data dir to 14TB |
---
## PART 1 — Immediate Cleanup (Safe to Delete)
**Estimated savings: ~1822 GB — no data loss**
### Step 1: Empty the Trash (1.3 GB)
```bash
rm -rf ~/.local/share/Trash/files/*
rm -rf ~/.local/share/Trash/info/*
```
### Step 2: Delete Chrome's AI Optimization Model (4 GB!)
Chrome downloads a machine-learning model to optimize page loading. It **rebuilds itself automatically** the next time Chrome runs — you lose nothing permanent.
```bash
rm -rf ~/.config/google-chrome/OptGuideOnDeviceModel
```
### Step 3: Clear All Browser Caches (~5.5 GB)
Browsers rebuild their cache as you browse. You may notice slightly slower page loads for a day or two.
```bash
rm -rf ~/.cache/BraveSoftware/Brave-Browser/Default/Cache
rm -rf ~/.cache/google-chrome/Default/Cache
rm -rf ~/.cache/google-chrome/"Profile 1"/Cache
rm -rf ~/.cache/google-chrome/"Profile 2"/Cache
rm -rf ~/.cache/mozilla/firefox/*.default-release/cache2
rm -rf ~/.cache/browser-os/*/Cache
```
### Step 4: Clear Package Manager Caches (~1.8 GB)
```bash
sudo apt clean # APT downloaded .deb files (610 MB)
rm -rf ~/.cache/uv # Python uv cache (638 MB)
rm -rf ~/.cache/node-gyp # Node build cache (120 MB)
npm cache clean --force # npm cache (part of ~/.npm)
rm -rf ~/.cache/go-build # Go build cache (77 MB)
```
### Step 5: Trim System Journal Logs (saves ~2.4 GB)
Linux keeps a rolling log of everything happening on the system. 2.9 GB is excessive — capping it at 500 MB still keeps plenty of history for troubleshooting.
```bash
sudo journalctl --vacuum-size=500M
```
### Step 6: Clear Thumbnail Cache (~404 MB)
These are small preview images for your file manager. They regenerate automatically when you browse folders.
```bash
rm -rf ~/.cache/thumbnails/*
```
### Step 7: Remove Unused Flatpak Runtimes (~13 GB)
Flatpak keeps old runtime versions even after apps are updated. This command removes anything nothing depends on anymore.
```bash
flatpak uninstall --unused
```
### Step 8: Clear Playwright Test Browser Cache (~128 MB)
```bash
rm -rf ~/.cache/ms-playwright-go
```
### Step 9: Clear Gemini Temp Files (~64 MB)
```bash
rm -rf ~/.gemini/tmp/*
```
---
## PART 2 — Move Data to the 14TB Drive
**Estimated freed from OS drive: ~20 GB**
The strategy here is: move the folder to the 14TB drive, then create a **symlink** at the original location. Think of a symlink like a Windows shortcut — when a program goes looking for `~/Downloads`, Linux silently redirects it to the real folder on the 14TB drive. The program never knows anything changed.
> **Before running any move command**, make sure the 14TB is mounted:
> `ls /mnt/14TB-HD` — you should see files listed, not an empty folder.
### Move 1: Downloads Folder (6.9 GB)
```bash
mv ~/Downloads /mnt/14TB-HD/Downloads
ln -s /mnt/14TB-HD/Downloads ~/Downloads
```
### Move 2: Debian ISOs (5.0 GB)
These are just old installation images — move them to the 14TB for archiving, or delete if you no longer need them.
```bash
mv ~/debian-iso /mnt/14TB-HD/debian-iso
```
### Move 3: FreeDOS Custom Build (423 MB)
```bash
mv ~/freedos-custom /mnt/14TB-HD/freedos-custom
```
### Move 4: DigiKam Photo Database (5.5 GB)
DigiKam (your photo manager) stores its face recognition engine and metadata database in your home folder. Moving it to the 14TB frees up 5.5 GB.
```bash
mv ~/.local/share/digikam /mnt/14TB-HD/digikam-db
ln -s /mnt/14TB-HD/digikam-db ~/.local/share/digikam
```
After moving, open DigiKam and go to **Settings → Configure DigiKam → Database** to confirm it still sees the database correctly.
### Move 5: Frigate Config & Data (913 MB)
If Frigate is running, stop it first so files aren't in use:
```bash
sudo systemctl stop frigate # stop the service first
sudo mv /opt/frigate /mnt/14TB-HD/frigate
sudo ln -s /mnt/14TB-HD/frigate /opt/frigate
sudo systemctl start frigate
```
### Move 6: VS Code Extensions (1.9 GB — optional)
```bash
mv ~/.vscode /mnt/14TB-HD/vscode
ln -s /mnt/14TB-HD/vscode ~/.vscode
```
---
## PART 3 — Change Default Save Locations in Programs
### All Browsers — Change Download Folder
Set each browser's download location to `/mnt/14TB-HD/Downloads` so new downloads go there automatically:
- **Brave:** Settings → Downloads → Location
- **Chrome:** Settings → Downloads → Location
- **Firefox:** Settings → General → Downloads → Save files to
### System-Wide Default Download Location
This tells all XDG-aware Linux apps (file manager, etc.) where the Downloads folder is:
```bash
nano ~/.config/user-dirs.dirs
# Find this line:
# XDG_DOWNLOAD_DIR="$HOME/Downloads"
# Change it to:
# XDG_DOWNLOAD_DIR="/mnt/14TB-HD/Downloads"
# Save with Ctrl+O, then Ctrl+X to exit
```
---
## Summary: Expected Space Recovered
| Action | Estimated Savings |
|---|---|
| Empty Trash | 1.3 GB |
| Chrome AI model (OptGuide) | 4.0 GB |
| Browser caches (all) | 5.5 GB |
| Package manager caches | 1.8 GB |
| Journal logs (trim to 500 MB) | 2.4 GB |
| Thumbnail cache | 404 MB |
| Flatpak unused runtimes | ~13 GB |
| Gemini temp + Playwright cache | ~192 MB |
| **Total cleanup (no data loss)** | **~1822 GB** |
| Move Downloads to 14TB | 6.9 GB |
| Move debian-iso + freedos to 14TB | 5.4 GB |
| Move DigiKam DB to 14TB | 5.5 GB |
| Move Frigate to 14TB | 0.9 GB |
| Move VS Code extensions to 14TB | 1.9 GB |
| **Total moved to 14TB** | **~20 GB** |
| **GRAND TOTAL FREED FROM OS DRIVE** | **~3842 GB** |
After all steps, the OS drive should drop from **86% → roughly 5560% full**,
giving you ~5565 GB of breathing room.
---
## Bonus Tips
- **Your 469 GB Intel SSD at `/mnt/INTEL-SSD` is nearly empty** (only 449 MB used of 469 GB).
Consider using it for speed-sensitive data like VS Code, DigiKam DB, or your home `~/.config` folder — SSDs are much faster than the spinning 14TB hard drive.
- **Brave Browser appears twice:** you have it installed both as a native app (`/opt/brave.com`, 429 MB) and as a Flatpak (`~/.var/app/com.brave.Browser`, 3.7 GB). You are likely only using one. Running `flatpak uninstall com.brave.Browser` would free ~3.7 GB if you use the native version.
- **Make sure `/mnt/14TB-HD` auto-mounts at boot** so symlinks don't break. Check with:
```bash
grep 14TB /etc/fstab
```
If there's no entry, the drive may only mount when you log in — ask Claude to help add a permanent fstab entry.