Files
deploy-felhom-compose/TASK.md
T
2026-02-15 09:39:50 +01:00

487 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TASK: Infrastructure FileBrowser + Orphan Stack Handling + Catalog Fixes
**Priority order:** Task 1 → Task 2 → Task 3 (Task 3 is independent, can be done anytime)
**Repositories involved:**
- `deploy-felhom-compose` — controller Go code, docker-setup.sh, hdd-setup.sh
- `app-catalog-felhom.eu` — template catalog
---
## Task 1: FileBrowser Quantum as Infrastructure Service
### Context
FileBrowser Quantum (`gtstef/filebrowser:latest`) becomes a **mandatory infrastructure service**
deployed alongside Traefik, Cloudflared, and felhom-controller. It provides the customer with
permanent web-based access to their HDD data — this is critical for the orphan deletion workflow
(Task 2), where users need to retrieve data from deleted apps.
FileBrowser is **removed from the app catalog** and instead deployed by `docker-setup.sh` during
initial server setup, just like Traefik.
### 1.1 — HDD Folder Structure Update (hdd-setup.sh)
Add new user-facing folders to the HDD folder structure arrays in `hdd-setup.sh`:
**Current structure:**
```
${HDD_PATH}/
├── media/
│ ├── downloads/complete/
│ ├── downloads/incomplete/
│ ├── movies/
│ ├── series/
│ ├── music/
│ └── books/
├── storage/
│ ├── immich/
│ ├── nextcloud/
│ ├── filebrowser/ ← REMOVE (filebrowser is now infra, doesn't need storage/)
│ ├── backups/local/
│ └── backups/appdata/
└── appdata/
```
**Updated structure:**
```
${HDD_PATH}/
├── Dokumentumok/ ← NEW: user documents (OnlyOffice, general files)
├── media/
│ ├── downloads/complete/
│ ├── downloads/incomplete/
│ ├── movies/
│ ├── series/
│ ├── music/
│ └── books/
├── storage/
│ ├── immich/
│ ├── nextcloud/
│ ├── backups/local/
│ └── backups/appdata/
└── appdata/
```
Changes to `hdd-setup.sh`:
- Remove `"storage/filebrowser"` from `STORAGE_DIRS` array
- Add `"Dokumentumok"` as a new top-level entry (add a new `USER_DIRS` array, or add to existing)
- Ownership: same 1000:1000 as other dirs
### 1.2 — FileBrowser Docker Compose (Infrastructure)
Create `/opt/docker/stacks/filebrowser/docker-compose.yml` during `docker-setup.sh` execution.
**Mount strategy — three tiers with different permissions:**
| HDD Path | Container Mount | Access | Rationale |
|---|---|---|---|
| `${HDD_PATH}/storage/` | `/srv/storage` | **read-only** | App data, prevent accidental deletion |
| `${HDD_PATH}/media/` | `/srv/media` | **read-write** | User adds movies, music, books |
| `${HDD_PATH}/Dokumentumok/` | `/srv/Dokumentumok` | **read-write** | User documents (docx, xlsx, etc.) |
**Docker Compose template:**
```yaml
# FileBrowser Quantum — Infrastructure file manager
# Domain: files.${DOMAIN}
# Deployed by docker-setup.sh — do NOT remove
#
# Mount permissions:
# /srv/storage/ → HDD storage/ (READ-ONLY — app data)
# /srv/media/ → HDD media/ (read-write — user media)
# /srv/Dokumentumok/ → HDD Dokumentumok/ (read-write — user documents)
services:
filebrowser:
image: gtstef/filebrowser:latest
container_name: filebrowser
restart: unless-stopped
environment:
- TZ=Europe/Budapest
volumes:
- filebrowser_data:/home/filebrowser/data
- ${HDD_PATH}/storage:/srv/storage:ro
- ${HDD_PATH}/media:/srv/media
- ${HDD_PATH}/Dokumentumok:/srv/Dokumentumok
networks:
- traefik-public
deploy:
resources:
limits:
memory: 256M
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:80/health"]
interval: 30s
timeout: 5s
retries: 3
start_period: 15s
labels:
- "traefik.enable=true"
- "traefik.http.routers.filebrowser.rule=Host(`files.${DOMAIN}`)"
- "traefik.http.routers.filebrowser.entrypoints=websecure"
- "traefik.http.routers.filebrowser.tls=true"
- "traefik.http.routers.filebrowser.tls.certresolver=letsencrypt"
- "traefik.http.services.filebrowser.loadbalancer.server.port=80"
- "traefik.docker.network=traefik-public"
volumes:
filebrowser_data:
networks:
traefik-public:
external: true
```
**Default credentials:** admin / admin (user should change on first login).
**NOTE:** The healthcheck endpoint `/health` should be verified against FileBrowser Quantum docs.
If it doesn't exist, fall back to `wget --spider -q http://localhost:80/`.
### 1.3 — Deploy in docker-setup.sh
Add a new step in `docker-setup.sh` that deploys FileBrowser **after** Traefik is running
and **after** HDD is mounted (HDD_PATH must be known).
**Implementation notes:**
- The step should come after `install_traefik()` and after HDD detection/mount
- Requires `HDD_PATH` to be set — if no HDD is configured, **skip FileBrowser deployment**
and log a warning: "FileBrowser skipped — no HDD path configured. Deploy manually after HDD setup."
- Create the compose file from template (substitute `${DOMAIN}` and `${HDD_PATH}`)
- Create a `.env` file in the filebrowser stack dir with `DOMAIN` and `HDD_PATH`
- `docker compose up -d`
- Verify container is running
**New function:** `install_filebrowser()`
### 1.4 — Add to Protected Stacks
Update `controller.yaml.example` to include filebrowser in the protected list:
```yaml
stacks:
protected:
- "traefik"
- "cloudflared"
- "felhom-controller"
- "filebrowser" # ← ADD
```
Also update any hardcoded protected stack references in documentation/README.
### 1.5 — Remove FileBrowser from App Catalog
In `app-catalog-felhom.eu` repository:
- **Delete** `templates/filebrowser/` directory (docker-compose.yml + .felhom.yml)
- **Delete** `existing-appinfo/filebrowser-appinfo.yml` if it exists
- FileBrowser should no longer appear in the "Alkalmazások" catalog on the dashboard
### 1.6 — Dashboard UI for Infrastructure Services
Currently the dashboard shows catalog apps. Infrastructure services (traefik, cloudflared,
controller, filebrowser) are hidden. Consider adding a small "Rendszer" (System) section
at the bottom of the sidebar or dashboard that shows infrastructure service status.
**This is optional / future work** — not blocking. The controller already knows about
protected stacks via `IsProtectedStack()`. The UI just needs to render them differently
if this section is added.
---
## Task 2: Orphan Stack Detection and Deletion
### Context
When an app is removed from the catalog (e.g., Stirling-PDF replaced by BentoPDF), its stack
directory may still exist in `/opt/docker/stacks/` with containers still deployed. These
"orphaned" stacks need to be visible on the dashboard with a clear state and deletable by the user.
Because FileBrowser (Task 1) gives users permanent access to their HDD data, the delete flow
can safely remove Docker volumes while informing users their HDD files are still accessible.
### 2.1 — New Stack State: `orphaned`
Add a new constant in the stacks package:
```go
StateOrphaned ContainerState = "orphaned"
```
An orphaned stack is defined as:
- Has a `docker-compose.yml` in `/opt/docker/stacks/<name>/`
- Has `app.yaml` with `deployed: true`
- Does **NOT** have a matching template in the synced catalog
### 2.2 — Orphan Detection in ScanStacks()
After the existing scan loop in `ScanStacks()`, add orphan detection:
```go
// After scanning all stack dirs, check which deployed stacks have no catalog template
catalogTemplates := m.getCatalogTemplateSlugs() // returns set of slugs from synced catalog
for name, stack := range m.stacks {
if stack.Protected {
continue // infrastructure stacks are never orphaned
}
if !stack.Deployed {
continue // not deployed = just an available template, not orphaned
}
if !catalogTemplates[name] {
stack.Orphaned = true
}
}
```
**Add `Orphaned` field to Stack struct:**
```go
type Stack struct {
Name string `json:"name"`
Meta Metadata `json:"meta"`
ComposePath string `json:"compose_path"`
State ContainerState `json:"state"`
Deployed bool `json:"deployed"`
Protected bool `json:"protected"`
Orphaned bool `json:"orphaned"` // ← ADD
Containers []ContainerInfo `json:"containers"`
AppConfig *AppConfig `json:"app_config,omitempty"`
LastUpdated time.Time `json:"last_updated"`
}
```
**`getCatalogTemplateSlugs()`** needs to read the synced catalog directory and return
a `map[string]bool` of all template slugs that have a `docker-compose.yml`. The synced
catalog lives at the path configured in `git.local_path` or wherever the catalog sync
stores templates after git pull. Check the existing `catalogsync` package for the exact path.
### 2.3 — Dashboard UI for Orphaned Stacks
Orphaned stacks appear in the deployed apps list with distinct visual treatment:
**Visual styling:**
- Left border: amber/yellow (instead of green for running or gray for stopped)
- Badge: `Elavult` (Deprecated) — amber background, dark text
- App name still shown from `.felhom.yml` metadata (if available) or directory name
- Show current state (running/stopped) alongside the orphan badge
**Available actions for orphaned stacks:**
- ✅ Start / Stop (normal controls — user may need to run it briefly)
- ✅ View logs
-**Törlés** (Delete) button — NEW, only shown for orphaned stacks
- ❌ No "Frissítés" (Update) — no catalog template to update from
- ❌ No "Beállítások" (Settings) — no deploy_fields to configure
### 2.4 — Delete API Endpoint
**Endpoint:** `DELETE /api/stacks/{name}`
**Request body:**
```json
{
"remove_hdd_data": false
}
```
**Preconditions (return 409 Conflict if violated):**
- Stack must be **stopped** (State != running). Force the user to stop first.
- Stack must be **orphaned** (for now — catalog apps cannot be deleted, only stopped).
In the future this could be relaxed, but for safety, start with orphan-only deletion.
**Execution steps:**
```
1. Verify preconditions (stopped + orphaned)
2. Read docker-compose.yml to identify:
a. Named Docker volumes (from `volumes:` top-level section)
b. HDD bind mounts (paths starting with ${HDD_PATH})
3. Run: docker compose down --rmi local --volumes
- This removes containers, local images, AND named Docker volumes (SSD data)
- Named volumes (configs, databases, caches) are always removed — they're useless
without the app and are the #1 cause of "Docker ate my disk space"
4. If remove_hdd_data == true:
a. For each HDD bind mount found in step 2:
- Calculate size: du -sh <path>
- Remove: rm -rf <path>
- Log: "[INFO] Removed HDD data: <path> (<size>)"
b. WARNING: Never rm -rf ${HDD_PATH} itself or ${HDD_PATH}/media/ or
${HDD_PATH}/Dokumentumok/ — only remove app-specific subdirectories
like ${HDD_PATH}/storage/paperless/ or ${HDD_PATH}/storage/immich/
5. Remove stack directory: rm -rf /opt/docker/stacks/<name>/
6. Log the complete delete action with timestamp
7. Trigger ScanStacks() to refresh dashboard
8. Return 200 OK with summary
```
**Response body:**
```json
{
"deleted": "stirling-pdf",
"volumes_removed": ["stirling_pdf_data"],
"hdd_paths_removed": [],
"hdd_paths_preserved": ["/mnt/hdd_1/storage/stirling-pdf (245 MB)"]
}
```
**Safety guards:**
- Protected stacks can never be deleted (check `IsProtectedStack()`)
- Running stacks can never be deleted (must stop first)
- Only orphaned stacks can be deleted (for now)
- HDD data deletion is opt-in (default false)
- Never delete top-level HDD directories (media/, storage/, Dokumentumok/)
- Log every delete action with full details
### 2.5 — HDD Data Discovery for Delete Dialog
The delete confirmation dialog needs to show what HDD data exists and its size.
**New endpoint:** `GET /api/stacks/{name}/hdd-data`
Parses the stack's `docker-compose.yml` to find HDD bind mounts, checks if paths exist
on disk, and returns size info:
```json
{
"stack": "stirling-pdf",
"hdd_paths": [
{
"path": "/mnt/hdd_1/storage/stirling-pdf",
"size_bytes": 256901120,
"size_human": "245 MB",
"exists": true
}
],
"has_hdd_data": true
}
```
If no HDD bind mounts exist (SSD-only app like Vaultwarden, Mealie), return:
```json
{
"stack": "vaultwarden",
"hdd_paths": [],
"has_hdd_data": false
}
```
### 2.6 — Delete Confirmation Dialog (UI)
**Full dialog (when HDD data exists):**
```
┌──────────────────────────────────────────────────────┐
│ Stirling-PDF törlése │
│ │
│ ⚠ Ez az alkalmazás már nem érhető el a │
│ katalógusban. │
│ │
│ Az alkalmazás eltávolítása magában foglalja a │
│ konténereket, beállításokat és belső adatbázist. │
│ │
│ ☐ Felhasználói adatok törlése │
│ 📁 /srv/storage/stirling-pdf (245 MB) │
Ha nem törli, a Fájlkezelőben továbbra is │
│ elérheti ezeket a fájlokat. │
│ │
│ [Mégse] [Törlés] │
└──────────────────────────────────────────────────────┘
```
Notes:
- Show the FileBrowser-relative path (`/srv/storage/...`) not the system path — this is
what the user sees in FileBrowser
- The "Felhasználói adatok törlése" checkbox is **unchecked by default**
- The info hint reminds users about FileBrowser access (Task 1 must be completed first)
- "Törlés" button should be red/destructive styling
**Simple dialog (no HDD data — SSD-only apps):**
```
┌──────────────────────────────────────────────────────┐
│ Vaultwarden törlése │
│ │
│ ⚠ Ez az alkalmazás már nem érhető el a │
│ katalógusban. │
│ │
│ Az alkalmazás és minden adata véglegesen törlődik. │
│ │
│ [Mégse] [Törlés] │
└──────────────────────────────────────────────────────┘
```
No checkbox needed — there's nothing optional to preserve.
### 2.7 — Router Registration
Add to the API router:
```go
r.HandleFunc("/api/stacks/{name}/hdd-data", r.getStackHDDData).Methods("GET")
r.HandleFunc("/api/stacks/{name}", r.deleteStack).Methods("DELETE")
```
Both require authentication (same as existing stack endpoints).
---
## Task 3: App Catalog Fixes
These are independent template fixes in the `app-catalog-felhom.eu` repository.
### 3.1 — BentoPDF: Change Subdomain to pdf.*
<should be done already, verify>
### 3.2 — Calibre-Web → Calibre-Web-Automated
<should be done already, verify>
### 3.3 — FileBrowser → FileBrowser Quantum (Catalog Removal)
Since FileBrowser is now infrastructure (Task 1), **remove it from the catalog entirely:**
- **Delete** `templates/filebrowser/` directory
- **Delete** `existing-appinfo/filebrowser-appinfo.yml`
- The existing `filebrowser` catalog entry in any customer's deployed stacks will become
orphaned (Task 2 handles this gracefully)
**Note:** If a customer already has the old catalog-based FileBrowser deployed, it will show
as orphaned after catalog sync. They can delete it via the orphan workflow. The infrastructure
FileBrowser (Task 1) will already be running at `files.${DOMAIN}`.
---
## Implementation Checklist
### deploy-felhom-compose repository
- [ ] **hdd-setup.sh**: Add `Dokumentumok/` to folder structure, remove `storage/filebrowser`
- [ ] **docker-setup.sh**: Add `install_filebrowser()` function
- [ ] **controller.yaml.example**: Add `filebrowser` to `stacks.protected` list
- [ ] **stacks/manager.go** (or equivalent):
- [ ] Add `Orphaned` field to `Stack` struct
- [ ] Add `StateOrphaned` constant
- [ ] Add orphan detection in `ScanStacks()`
- [ ] Add `getCatalogTemplateSlugs()` helper
- [ ] **stacks/delete.go** (new file or add to manager):
- [ ] `DeleteStack()` method with volume + HDD cleanup
- [ ] `GetStackHDDData()` method for size discovery
- [ ] HDD path parsing from docker-compose.yml
- [ ] Safety guards (protected, running, top-level dir protection)
- [ ] **api/router.go**:
- [ ] `DELETE /api/stacks/{name}` endpoint
- [ ] `GET /api/stacks/{name}/hdd-data` endpoint
- [ ] **templates/dashboard.html** (or relevant UI template):
- [ ] Orphan badge styling (amber)
- [ ] Delete button for orphaned stacks
- [ ] Delete confirmation dialog with HDD data info
- [ ] FileBrowser hint in delete dialog
- [ ] **README.md**: Update protected stacks list, document delete flow
### app-catalog-felhom.eu repository
- [ ] Delete `templates/stirling-pdf/` (if exists)
- [ ] Delete `templates/filebrowser/` (moved to infra)
- [ ] Delete `existing-appinfo/filebrowser-appinfo.yml`
- [ ] Update `templates/bentopdf/` — subdomain `bento.*``pdf.*`
- [ ] Replace `templates/calibre-web/` with calibre-web-automated version
- [ ] Verify all YAML files parse without errors
- [ ] **README.md**: Update accordingly