updated TASK for refactoring
This commit is contained in:
@@ -1,486 +1,257 @@
|
|||||||
# TASK: Infrastructure FileBrowser + Orphan Stack Handling + Catalog Fixes
|
# TASK.md — Controller Refactoring: Template Split, Server Decomposition, Domain Rename
|
||||||
|
|
||||||
**Priority order:** Task 1 → Task 2 → Task 3 (Task 3 is independent, can be done anytime)
|
> **Goal:** Improve code organization for maintainability and Claude Code efficiency.
|
||||||
|
> No new features — purely structural refactoring + one config rename.
|
||||||
**Repositories involved:**
|
>
|
||||||
- `deploy-felhom-compose` — controller Go code, docker-setup.sh, hdd-setup.sh
|
> **Version bump:** v0.3.0 (structural refactor milestone)
|
||||||
- `app-catalog-felhom.eu` — template catalog
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Task 1: FileBrowser Quantum as Infrastructure Service
|
## Task 1: Split templates.go → go:embed (HIGH PRIORITY)
|
||||||
|
|
||||||
### Context
|
The `templates.go` file contains ALL HTML templates and CSS as Go string constants.
|
||||||
|
The file itself says: *"As the UI grows, switch to go:embed for easier editing."*
|
||||||
|
With 7 templates + full CSS, it's time.
|
||||||
|
|
||||||
FileBrowser Quantum (`gtstef/filebrowser:latest`) becomes a **mandatory infrastructure service**
|
### 1.1 — Create template files directory
|
||||||
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
|
Create `controller/internal/web/templates/` with individual files:
|
||||||
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}/
|
internal/web/templates/
|
||||||
├── media/
|
├── layout.html ← from layoutTmpl const
|
||||||
│ ├── downloads/complete/
|
├── dashboard.html ← from dashboardTmpl const
|
||||||
│ ├── downloads/incomplete/
|
├── stacks.html ← from stacksTmpl const (the stacks list page, NOT the old dashboard list)
|
||||||
│ ├── movies/
|
├── login.html ← from loginTmpl const
|
||||||
│ ├── series/
|
├── logs.html ← from logsTmpl const
|
||||||
│ ├── music/
|
├── deploy.html ← from deployTmpl const
|
||||||
│ └── books/
|
├── app_info.html ← from appInfoTmpl const
|
||||||
├── storage/
|
└── style.css ← from cssTemplate const
|
||||||
│ ├── immich/
|
|
||||||
│ ├── nextcloud/
|
|
||||||
│ ├── filebrowser/ ← REMOVE (filebrowser is now infra, doesn't need storage/)
|
|
||||||
│ ├── backups/local/
|
|
||||||
│ └── backups/appdata/
|
|
||||||
└── appdata/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Updated structure:**
|
Each `.html` file should contain ONLY the template content (the `{{define "name"}}...{{end}}` block).
|
||||||
```
|
Keep the existing template names (`layout_start`, `layout_end`, `dashboard`, `stacks`, `login`, etc.).
|
||||||
${HDD_PATH}/
|
|
||||||
├── Dokumentumok/ ← NEW: user documents (OnlyOffice, general files)
|
### 1.2 — Create embed.go
|
||||||
├── media/
|
|
||||||
│ ├── downloads/complete/
|
Create `controller/internal/web/embed.go`:
|
||||||
│ ├── downloads/incomplete/
|
|
||||||
│ ├── movies/
|
```go
|
||||||
│ ├── series/
|
package web
|
||||||
│ ├── music/
|
|
||||||
│ └── books/
|
import "embed"
|
||||||
├── storage/
|
|
||||||
│ ├── immich/
|
//go:embed templates/*.html templates/*.css
|
||||||
│ ├── nextcloud/
|
var templateFS embed.FS
|
||||||
│ ├── backups/local/
|
|
||||||
│ └── backups/appdata/
|
|
||||||
└── appdata/
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Changes to `hdd-setup.sh`:
|
### 1.3 — Update template loading in server.go (or the new funcmap.go, see Task 2)
|
||||||
- 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)
|
Replace the current `loadTemplates()` method that parses the `allTemplates` const:
|
||||||
|
|
||||||
Create `/opt/docker/stacks/filebrowser/docker-compose.yml` during `docker-setup.sh` execution.
|
```go
|
||||||
|
func (s *Server) loadTemplates() {
|
||||||
|
funcMap := template.FuncMap{ /* ... existing funcs ... */ }
|
||||||
|
|
||||||
**Mount strategy — three tiers with different permissions:**
|
s.tmpl = template.Must(
|
||||||
|
template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.html"),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
| HDD Path | Container Mount | Access | Rationale |
|
CSS serving: Instead of the `cssTemplate` const, read from `templateFS`:
|
||||||
|---|---|---|---|
|
|
||||||
| `${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:**
|
```go
|
||||||
|
func (s *Server) serveCSSHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data, err := templateFS.ReadFile("templates/style.css")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "CSS not found", 500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/css; charset=utf-8")
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=3600")
|
||||||
|
w.Write(data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Register this handler for `/static/style.css` in `ServeHTTP` (replace the current inline CSS serving).
|
||||||
|
|
||||||
|
### 1.4 — Delete old string constants
|
||||||
|
|
||||||
|
Remove from `templates.go`:
|
||||||
|
- `const allTemplates = ...`
|
||||||
|
- `const layoutTmpl = ...`
|
||||||
|
- `const dashboardTmpl = ...`
|
||||||
|
- `const stacksTmpl = ...`
|
||||||
|
- `const loginTmpl = ...`
|
||||||
|
- `const logsTmpl = ...`
|
||||||
|
- `const deployTmpl = ...`
|
||||||
|
- `const appInfoTmpl = ...`
|
||||||
|
- `const cssTemplate = ...`
|
||||||
|
|
||||||
|
After this, `templates.go` should either be empty (delete it) or contain only the
|
||||||
|
felhom logo SVG constant if that's still embedded as a string (keep that one — it's small).
|
||||||
|
|
||||||
|
### 1.5 — Verify the build
|
||||||
|
|
||||||
|
- `go build ./cmd/controller/` must succeed
|
||||||
|
- `go:embed` requires Go 1.16+ (we're on 1.22, fine)
|
||||||
|
- Templates are still compiled into the binary — zero runtime file dependencies (same as before)
|
||||||
|
- Verify that the HTML files actually include the `{{define "name"}}...{{end}}` wrappers
|
||||||
|
(ParseFS needs them to register template names)
|
||||||
|
|
||||||
|
### Important notes
|
||||||
|
|
||||||
|
- The `<link rel="stylesheet" href="/static/style.css">` in layout.html already exists,
|
||||||
|
so CSS loading via the `/static/style.css` route should already work — just make sure
|
||||||
|
the handler reads from embed.FS instead of serving the const.
|
||||||
|
- The felhom logo SVG can stay as a Go const (it's small) or move to `templates/felhom-logo.svg`
|
||||||
|
and be served from embed.FS too. Either approach is fine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 2: Split server.go into focused files (MEDIUM PRIORITY)
|
||||||
|
|
||||||
|
Currently `server.go` handles: Server struct, auth/sessions, page handlers, template FuncMap,
|
||||||
|
asset serving, and HTTP routing. Split into:
|
||||||
|
|
||||||
|
### 2.1 — Create `auth.go`
|
||||||
|
|
||||||
|
Move from `server.go` to `internal/web/auth.go`:
|
||||||
|
- `type session struct`
|
||||||
|
- `const sessionCookieName`, `const sessionMaxAge`
|
||||||
|
- `RequireAuth()` middleware method
|
||||||
|
- `loginHandler()`, `loginPostHandler()`, `logoutHandler()`
|
||||||
|
- `createSession()`, `isValidSession()`, `cleanupSessions()`
|
||||||
|
- `renderLogin()` helper
|
||||||
|
|
||||||
|
### 2.2 — Create `handlers.go`
|
||||||
|
|
||||||
|
Move from `server.go` to `internal/web/handlers.go`:
|
||||||
|
- `baseData()` helper
|
||||||
|
- `dashboardHandler()`
|
||||||
|
- `stacksHandler()`
|
||||||
|
- `deployPageHandler()`
|
||||||
|
- `deployPagePostHandler()` (if it exists as separate handler)
|
||||||
|
- `appDetailHandler()`
|
||||||
|
- `logsPageHandler()`
|
||||||
|
|
||||||
|
### 2.3 — Create `funcmap.go`
|
||||||
|
|
||||||
|
Move from `server.go` to `internal/web/funcmap.go`:
|
||||||
|
- The entire `template.FuncMap` definition from `loadTemplates()`
|
||||||
|
- Extract it as a standalone function: `func (s *Server) templateFuncMap() template.FuncMap`
|
||||||
|
- Then `loadTemplates()` becomes a clean 3-liner calling `templateFuncMap()` + `ParseFS`
|
||||||
|
|
||||||
|
### 2.4 — Keep in server.go
|
||||||
|
|
||||||
|
After the split, `server.go` should contain only:
|
||||||
|
- `type Server struct`
|
||||||
|
- `func NewServer()`
|
||||||
|
- `func (s *Server) loadTemplates()` (now a 3-liner)
|
||||||
|
- `func (s *Server) ServeHTTP()` (HTTP routing dispatch)
|
||||||
|
- `func (s *Server) render()` helper
|
||||||
|
- Static file/asset serving handlers (`serveStaticFile`, `serveCSSHandler`, `serveLogoHandler`)
|
||||||
|
|
||||||
|
### 2.5 — Verify the split
|
||||||
|
|
||||||
|
All files are in `package web` — no import changes needed within the package.
|
||||||
|
The `Server` struct and all its methods are accessible across files in the same package.
|
||||||
|
|
||||||
|
Run `go build ./cmd/controller/` to verify everything compiles.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Task 3: Rename controller domain from `dashboard.*` to `felhom.*` (LOW PRIORITY)
|
||||||
|
|
||||||
|
### 3.1 — Update controller's docker-compose.yml
|
||||||
|
|
||||||
|
In `controller/docker-compose.yml`, change the Traefik label:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
# FileBrowser Quantum — Infrastructure file manager
|
# OLD:
|
||||||
# Domain: files.${DOMAIN}
|
- "traefik.http.routers.controller.rule=Host(`dashboard.${DOMAIN}`)"
|
||||||
# Deployed by docker-setup.sh — do NOT remove
|
# NEW:
|
||||||
#
|
- "traefik.http.routers.controller.rule=Host(`felhom.${DOMAIN}`)"
|
||||||
# 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).
|
### 3.2 — Update docker-setup.sh
|
||||||
|
|
||||||
**NOTE:** The healthcheck endpoint `/health` should be verified against FileBrowser Quantum docs.
|
In `controller/scripts/docker-setup.sh`, update `print_summary()` output:
|
||||||
If it doesn't exist, fall back to `wget --spider -q http://localhost:80/`.
|
- Any reference to `dashboard.${BASE_DOMAIN}` → `felhom.${BASE_DOMAIN}`
|
||||||
|
|
||||||
### 1.3 — Deploy in docker-setup.sh
|
Also check install_controller() if it generates compose files or prints URLs.
|
||||||
|
|
||||||
Add a new step in `docker-setup.sh` that deploys FileBrowser **after** Traefik is running
|
### 3.3 — Update controller.yaml.example
|
||||||
and **after** HDD is mounted (HDD_PATH must be known).
|
|
||||||
|
|
||||||
**Implementation notes:**
|
If there's any reference to `dashboard.*` in the example config or comments, update to `felhom.*`.
|
||||||
- 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()`
|
### 3.4 — Update documentation
|
||||||
|
|
||||||
### 1.4 — Add to Protected Stacks
|
In CLAUDE.md build/deploy workflow sections, update any `dashboard.` references to `felhom.`.
|
||||||
|
|
||||||
Update `controller.yaml.example` to include filebrowser in the protected list:
|
### 3.5 — Cloudflare Tunnel public hostname (MANUAL — not code)
|
||||||
|
|
||||||
```yaml
|
**Reminder for Viktor:** After deploying, manually update the Cloudflare Tunnel
|
||||||
stacks:
|
public hostname in the Zero Trust dashboard:
|
||||||
protected:
|
- Old: `dashboard.demo-felhom.eu` → Traefik
|
||||||
- "traefik"
|
- New: `felhom.demo-felhom.eu` → Traefik
|
||||||
- "cloudflared"
|
|
||||||
- "felhom-controller"
|
|
||||||
- "filebrowser" # ← ADD
|
|
||||||
```
|
|
||||||
|
|
||||||
Also update any hardcoded protected stack references in documentation/README.
|
### 3.6 — Pi-hole DNS (MANUAL — not code)
|
||||||
|
|
||||||
### 1.5 — Remove FileBrowser from App Catalog
|
**Reminder for Viktor:** If there's a pi-hole local DNS record for `dashboard.demo-felhom.eu`,
|
||||||
|
update it to `felhom.demo-felhom.eu` (or rely on the wildcard `*.demo-felhom.eu` record).
|
||||||
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
|
## Task 4: Update documentation
|
||||||
|
|
||||||
### Context
|
### 4.1 — README.md
|
||||||
|
|
||||||
When an app is removed from the catalog (e.g., Stirling-PDF replaced by BentoPDF), its stack
|
- Update directory structure to show `internal/web/templates/` directory
|
||||||
directory may still exist in `/opt/docker/stacks/` with containers still deployed. These
|
- Update "Tech stack" section: "Templates: go:embed HTML files" instead of "Go string constants"
|
||||||
"orphaned" stacks need to be visible on the dashboard with a clear state and deletable by the user.
|
- Mention the `felhom.*` subdomain for the controller
|
||||||
|
- Update file tree showing the new files (embed.go, auth.go, handlers.go, funcmap.go)
|
||||||
|
|
||||||
Because FileBrowser (Task 1) gives users permanent access to their HDD data, the delete flow
|
### 4.2 — CLAUDE.md
|
||||||
can safely remove Docker volumes while informing users their HDD files are still accessible.
|
|
||||||
|
|
||||||
### 2.1 — New Stack State: `orphaned`
|
- Update workspace layout to reflect the new `internal/web/` file structure
|
||||||
|
- Update "Tech stack" section
|
||||||
|
- Update any `dashboard.*` references to `felhom.*`
|
||||||
|
- Note the go:embed pattern for future template additions
|
||||||
|
|
||||||
Add a new constant in the stacks package:
|
### 4.3 — CONTEXT.md
|
||||||
|
|
||||||
```go
|
- Add session entry documenting the refactoring
|
||||||
StateOrphaned ContainerState = "orphaned"
|
- Note: templates moved from Go string constants to go:embed HTML files
|
||||||
```
|
- Note: server.go split into auth.go, handlers.go, funcmap.go
|
||||||
|
- Note: controller domain changed from dashboard.* to felhom.*
|
||||||
|
- Update version to v0.3.0
|
||||||
|
|
||||||
An orphaned stack is defined as:
|
### 4.4 — BUILDING.md
|
||||||
- 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()
|
- Update the structure check in build.sh verification section if needed
|
||||||
|
(the `internal/web/templates/` directory should exist now)
|
||||||
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
|
## Implementation order
|
||||||
|
|
||||||
These are independent template fixes in the `app-catalog-felhom.eu` repository.
|
1. **Task 1** (templates.go → go:embed) — do this first, biggest impact
|
||||||
|
2. **Task 2** (server.go split) — do this second, leverages the cleaner templates
|
||||||
|
3. **Task 3** (domain rename) — small, do last
|
||||||
|
4. **Task 4** (docs) — update after all code changes
|
||||||
|
|
||||||
### 3.1 — BentoPDF: Change Subdomain to pdf.*
|
## Verification checklist
|
||||||
|
|
||||||
<should be done already, verify>
|
- [ ] `go build ./cmd/controller/` compiles successfully
|
||||||
|
- [ ] All 7 HTML templates render correctly (login, dashboard, stacks, deploy, app_info, logs, layout)
|
||||||
### 3.2 — Calibre-Web → Calibre-Web-Automated
|
- [ ] CSS loads at `/static/style.css`
|
||||||
|
- [ ] Felhom logo SVG loads at `/static/felhom-logo.svg`
|
||||||
<should be done already, verify>
|
- [ ] App logos/screenshots still serve from `/assets/`
|
||||||
|
- [ ] Auth (login/logout/session) works unchanged
|
||||||
### 3.3 — FileBrowser → FileBrowser Quantum (Catalog Removal)
|
- [ ] Stack operations (start/stop/deploy) work unchanged
|
||||||
|
- [ ] Controller accessible at `felhom.demo-felhom.eu` (after CF tunnel update)
|
||||||
Since FileBrowser is now infrastructure (Task 1), **remove it from the catalog entirely:**
|
- [ ] No broken links or template errors in browser console
|
||||||
|
- [ ] Build + push via build.sh works
|
||||||
- **Delete** `templates/filebrowser/` directory
|
- [ ] Deploy on demo-felhom works
|
||||||
- **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
|
|
||||||
Reference in New Issue
Block a user