Claude_Homelab/114_koillection_deployment.md

450 lines
14 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

# 114 — Koillection Deployment Guide
> Status: **IN PROGRESS** — CT created 2026-08-11, app stack deployed and running, nginx/SSL/DNS/Authelia pending (Phase 6+)
> CT ID: 114 · IP: 192.168.1.114
> Domain: `collections.spendlik.sk`
> Last updated: 2026-08-11
---
## Overview
Koillection is a self-hosted collection manager for tracking physical collections of any kind. No pre-built metadata scrapers — metadata is added freely per item, with custom fields via templates. MIT licensed.
**Collections planned for this instance:**
- 🚗 Hot Wheels (series, year, colour, condition, variants)
- 🧱 LEGO (set number, theme, piece count, minifigures, completion status)
- 🦇 Batmobiles (source media, scale, manufacturer, condition)
- 📚 Comics (title, issue, publisher, language, condition)
- 📄 Paper Models (designer, scale, subject, format — physical printed copies)
Stack: `koillection/koillection` (PHP/Symfony + Vue.js, served via FrankenPHP/Caddy) + PostgreSQL 16. Uploads (item photos) bind-mounted to NAS for data safety.
---
## Resource Allocation
| Resource | Allocation |
|---|---|
| **CT ID** | 114 |
| **IP** | 192.168.1.114 |
| **CPUs** | 1 |
| **RAM** | 512 MB |
| **Disk** | 8 GB (app + DB only; photos on NAS) |
| **Template** | Debian 13 (trixie) — created from `13.1-2` (current template at time of creation; `13.6-1` is now the default for new deployments, but this doesn't matter post-creation — see `Proxmox LXC Templates.md`) |
| **Privileged** | Yes (Docker requires it) |
| **Nesting** | Enabled (`features: nesting=1`) |
---
## NAS Mount Planning
Photos uploaded to Koillection land in `/uploads` inside the container. This will be bind-mounted from the NAS at:
```
/volume1/proxmox/data/koillection/uploads
```
Create this directory on the NAS before deployment:
```bash
# On the Synology NAS (SSH or File Station)
mkdir -p /volume1/proxmox/data/koillection/uploads
```
> ⚠️ The NAS path follows the same convention as Paperless (`/volume1/proxmox/data/<service>`). Be consistent.
>
> **Host-side mount**: verified 2026-08-11 — the Proxmox storage ID is `spendlik-nas`, mounted at `/mnt/pve/spendlik-nas` on the host (confirmed via live `ls /mnt/pve/`). CT 111 (Paperless) uses `/mnt/pve/spendlik-nas/data/paperless` as its exact host-side bind-mount path — Koillection follows the identical pattern in Phase 4 below.
---
## Phase 1 — Create LXC Container ✅ DONE (2026-08-11)
```bash
pct create 114 local:vztmpl/debian-13-standard_13.1-2_amd64.tar.zst \
--hostname koillection \
--cores 1 \
--memory 512 \
--swap 512 \
--rootfs local-lvm:8 \
--net0 name=eth0,bridge=vmbr0,ip=192.168.1.114/24,gw=192.168.1.1 \
--unprivileged 0 \
--features nesting=1 \
--ostype debian \
--start 1
```
Enter the container:
```bash
pct enter 114
```
---
## Phase 2 — Base Setup ✅ DONE (2026-08-11)
```bash
apt update && apt upgrade -y
apt install -y nano curl ca-certificates gnupg lsb-release
```
---
## Phase 3 — Install Docker ✅ DONE (2026-08-11)
```bash
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/debian \
$(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
```
Verify:
```bash
docker run --rm hello-world
```
---
## Phase 4 — NAS Bind Mount ✅ DONE (2026-08-11)
Add the NAS uploads path as a Proxmox bind mount. Exit the container first:
```bash
exit
```
On the Proxmox host:
```bash
pct set 114 --mp0 /mnt/pve/spendlik-nas/data/koillection/uploads,mp=/uploads,shared=1
```
> ✅ Verified 2026-08-11 against live `ls /mnt/pve/` (only `spendlik-nas` present) and cross-checked against CT 111 (Paperless)'s actual documented host mount (`/mnt/pve/spendlik-nas/data/paperless`) — this replaces an earlier placeholder path in this guide that would have failed (wrong storage name).
Re-enter the container and verify the mount is visible:
```bash
pct enter 114
ls -la /uploads
```
Confirmed: `/uploads` mounted, `777 nobody:nogroup`, empty — ready for the app to write to.
---
## Phase 5 — Deploy Koillection ✅ DONE (2026-08-11)
```bash
mkdir -p /opt/koillection
cd /opt/koillection
nano .env
```
Paste (fill in a strong password for `DB_PASSWORD`):
```env
DB_DRIVER=pdo_pgsql
DB_NAME=koillection
DB_HOST=db
DB_PORT=5432
DB_USER=koillection
DB_PASSWORD=CHANGE_ME
DB_VERSION=16
APP_ENV=prod
APP_DEBUG=0
APP_SECRET=CHANGE_ME_32CHAR_RANDOM_STRING
PHP_TZ=Europe/Bratislava
HTTPS_ENABLED=0
```
> Generate `APP_SECRET` with: `openssl rand -hex 16`
```bash
nano docker-compose.yml
```
Paste:
```yaml
services:
koillection:
image: koillection/koillection:latest
container_name: koillection
restart: unless-stopped
ports:
- "8080:80"
env_file:
- .env
volumes:
- /uploads:/uploads
depends_on:
db:
condition: service_healthy
db:
image: postgres:16
container_name: koillection-db
restart: unless-stopped
env_file:
- .env
environment:
- POSTGRES_DB=${DB_NAME}
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- ./volumes/postgresql:/var/lib/postgresql/data
healthcheck:
test: ["CMD", "pg_isready", "-q", "-d", "koillection", "-U", "koillection"]
timeout: 45s
interval: 10s
retries: 10
```
Start:
```bash
docker compose up -d
docker compose logs -f
```
Wait until the koillection container logs settle (Symfony/FrankenPHP startup — you'll see deprecation.INFO notices about API Platform `#[ApiResource]` shortName deduplication; these are harmless upstream framework warnings on this image version, not errors). Then verify with a proper status check, **not** a text grep against the homepage (the homepage is just a redirect, so grepping for "koillection" in it will always come back empty and looks like a false failure):
```bash
docker compose ps
curl -sv http://localhost:8080
```
Expect both containers `healthy`, and the curl to show `HTTP/1.1 302 Found` with `Location: /first-connection` — that's Koillection's normal first-run redirect, confirming the app is up and reachable.
> `chown: Invalid argument` lines for `/uploads` in the startup logs are expected and harmless — same NAS-bind-mount ownership limitation already known from Paperless. Doesn't affect functionality.
---
## Phase 6 — nginx Reverse Proxy (CT 101)
Enter CT 101 — this must be done **on CT 101**, not CT 114. nginx does not and should not exist on CT 114 itself.
```bash
pct enter 101
nano /etc/nginx/sites-available/collections.spendlik.sk
```
> ✅ Verified 2026-08-11 against live `ls /etc/nginx/sites-available/` on CT 101 — every existing vhost is named by full domain (`paperless.spendlik.sk`, `jellyfin.spendlik.sk`, `vault.spendlik.sk`, etc.), not by short service name. Use `collections.spendlik.sk` as the filename here, not `koillection`.
Paste:
```nginx
server {
listen 80;
server_name collections.spendlik.sk;
location / {
proxy_pass http://192.168.1.114:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 20M;
}
}
```
> `client_max_body_size 20M` — item photos can be large. Adjust upward if needed.
Enable and reload:
```bash
ln -s /etc/nginx/sites-available/collections.spendlik.sk /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
```
`nginx -t` should print `syntax is ok` / `test is successful` — confirm that before the reload takes effect.
---
## Phase 7 — SSL Certificate
Still in CT 101:
```bash
certbot --nginx -d collections.spendlik.sk
```
> ⚠️ Always inspect the config after certbot:
```bash
cat /etc/nginx/sites-available/collections.spendlik.sk
```
Check for duplicate `server_name` directives and missing closing braces. Fix manually if needed.
Also set `HTTPS_ENABLED=1` in `/opt/koillection/.env` in CT 114, then restart:
```bash
# In CT 114
cd /opt/koillection
nano .env # set HTTPS_ENABLED=1
docker compose restart koillection
```
---
## Phase 8 — DNS Record
> **Updated 2026-08-10**: All `*.spendlik.sk` subdomains are now **CNAME** records pointing at the root `spendlik.sk`. Only the root `spendlik.sk` A record holds an IP — WebSupport rejects any duplicate IP value elsewhere in the zone. Do **not** create an A record for this subdomain.
In WebSupport admin panel:
1. Add **CNAME** record: `collections``spendlik.sk`
2. Check both DNS management pages
3. Note the numeric record ID
4. Add to `00_index.md` DNS table
> ✅ No DDNS updater step is needed for this subdomain. `ddns-update.sh` on CT 108 only updates the root A record on IP change; this CNAME resolves through automatically.
---
## Phase 9 — Authelia Protection (CT 102)
Enter CT 102, edit `/etc/authelia/configuration.yml`. Add to `access_control.rules`:
```yaml
- domain: collections.spendlik.sk
policy: two_factor
```
> Koillection has its own internal login system. Authelia adds a second layer before users even reach the login page. Since this is a personal single-user instance, you may prefer Authelia bypass and rely on Koillection's own login instead — your call.
Restart Authelia after editing:
```bash
docker compose restart
```
Add the Authelia middleware to the nginx vhost in CT 101 (follow the pattern from other protected services).
---
## Phase 10 — First Login & Initial Setup
Open `https://collections.spendlik.sk` from mobile data (hairpin NAT — never test from LAN).
On first load, Koillection will prompt you to create an admin account. Do so, then:
1. Set your timezone to `Europe/Bratislava` in profile settings
2. Set the display currency if tracking purchase values
3. Set visibility defaults (private by default is fine for a personal instance)
---
## Phase 11 — Collection Setup
Recommended collection structure. Create each as a top-level Collection:
### 🚗 Hot Wheels
Suggested item fields (via Template):
- Series / Line
- Year of release
- Colour
- Casting name
- Country of manufacture
- Condition (Mint / Good / Played)
- Treasure Hunt (yes/no)
### 🧱 LEGO
Suggested item fields:
- Set number
- Theme
- Sub-theme
- Piece count
- Minifigure count
- Year
- Completion status (Sealed / Built / Parts only)
- Instruction booklet present (yes/no)
> 💡 The set number field makes cross-referencing with kocka-novinky.sk and Brickset API straightforward.
### 🦇 Batmobiles
Suggested item fields:
- Source (Film / TV / Comics / Game)
- Year of appearance
- Manufacturer (Hot Wheels / Corgi / LEGO / custom)
- Scale
- Condition
### 📚 Comics
Suggested item fields:
- Title / Series
- Issue number
- Publisher
- Language
- Year
- Condition (Mint / Very Good / Good / Fair)
- Story arc
### 📄 Paper Models (physical hardcopy)
Suggested item fields:
- Designer / Publisher
- Subject (aircraft, ship, building…)
- Scale
- Format (magazine supplement / standalone / kit)
- Build status (Unbuilt / Built / Display)
> 💡 Tags are cross-collection in Koillection — tag items with `#display`, `#wishlist`, `#for-sale` etc. to group across all five collections at once.
---
## Backup
The only things that need backing up:
1. **PostgreSQL database** — contains all collection metadata
2. **NAS uploads directory** — contains all item photos (already on NAS, covered by NAS backup)
Add a daily DB dump to cron in CT 114:
```bash
crontab -e
```
Add:
```cron
0 3 * * * docker exec koillection-db pg_dump -U koillection koillection > /opt/koillection/backups/koillection-$(date +\%Y\%m\%d).sql 2>/dev/null
```
```bash
mkdir -p /opt/koillection/backups
```
> ⚠️ Always back up the database before upgrading Koillection — the developer notes that data migrations can occasionally have edge cases.
---
## Gotchas
| Issue | Fix |
|---|---|
| Photos not saving | Verify `/uploads` bind mount is writable inside the container |
| App not starting | Check `docker compose logs koillection` — usually a DB connection issue on first boot |
| certbot corrupts nginx config | Always inspect after issuance |
| Large photo uploads rejected | Increase `client_max_body_size` in nginx vhost |
| HTTPS redirect loop | Set `HTTPS_ENABLED=1` in `.env` and restart the koillection container after SSL is in place |
| DNS record type | Use CNAME → `spendlik.sk`, never a per-subdomain A record (see Phase 8) |
| Wrong template filename | Verify exact template string with `pveam list local` before `pct create` — versions bump periodically |
| Wrong NAS host mount path | Storage ID is `spendlik-nas`, mounted at `/mnt/pve/spendlik-nas` — verify with `ls /mnt/pve/` before trusting any guide's hardcoded path |
| `curl \| grep koillection` shows nothing | Not a failure — the app root just 302-redirects to `/first-connection`, whose HTML doesn't contain the word "koillection". Use `docker compose ps` (expect `healthy`) and `curl -sv` (expect `302` + `Location: /first-connection`) instead |
| `deprecation.INFO` API Platform log spam on startup | Harmless upstream framework warnings (duplicate `#[ApiResource]` shortName), not errors — ignore |
| `nginx: command not found` when following Phase 6 | You're inside CT 114 (koillection), not CT 101 (reverse-proxy). Check the shell prompt — nginx work always happens on CT 101, never on the app container itself |
| Wrong nginx vhost filename | Use the full domain as the filename (`collections.spendlik.sk`), matching every other vhost on CT 101 — not the short service name |