153 lines
8.8 KiB
Markdown
153 lines
8.8 KiB
Markdown
---
|
|
name: website-management
|
|
description: "Manage SpeedyFoxAI site across staging and production LXCs."
|
|
version: 1.0.0
|
|
tags: [website, lxc, nginx, filezilla, sync, cleanup, music]
|
|
---
|
|
|
|
# website-management — SpeedyFoxAI Site Operations
|
|
|
|
Production LXC hosts the SpeedyFoxAI static site:
|
|
|
|
| Role | IP | OS | Notes |
|
|
|------|----|----|-------|
|
|
| Production | 10.0.0.39 | Debian 13 | Live site — all work goes here directly |
|
|
| Staging | 10.0.0.17 | Kali Linux | Shut down 2026-08-11, kept as backup only |
|
|
|
|
Production: n8n/passw0rd, nginx serving from `/root/html/` on port 80, filezilla on 3025-3026.
|
|
|
|
**SSH method**: use `sshpass` for all remote commands — `echo passw0rd | sudo -S` fails with password prompts on these hosts. Pattern:
|
|
```bash
|
|
sshpass -p 'passw0rd' ssh -o StrictHostKeyChecking=no [email protected] '<command>'
|
|
sshpass -p 'passw0rd' scp -o StrictHostKeyChecking=no <src> [email protected]:<dst>
|
|
```
|
|
|
|
## LXC Cleanup (run on both)
|
|
|
|
When cloning or resetting, strip to bare essentials:
|
|
|
|
1. Remove stale Docker artifacts:
|
|
```bash
|
|
docker stop filezilla 2>/dev/null; docker rm filezilla 2>/dev/null
|
|
docker rm nginx 2>/dev/null # stale container from original clone
|
|
docker rmi nginx:latest fauria/vsftpd:latest 2>/dev/null
|
|
docker volume prune -f; docker network prune -f
|
|
```
|
|
|
|
2. Clean `/root/html/` — keep live site files + one backup dir:
|
|
```bash
|
|
cd /root/html
|
|
rm -f .counter_data.txt counter.gif .counter_last_line .counter_total count.txt \
|
|
index.html.bak.13022026 update_count_persistent.sh
|
|
```
|
|
|
|
3. Clean `/root/html/backup/` — keep only `20260326_201727/`, remove loose files and nested backups:
|
|
```bash
|
|
cd /root/html/backup
|
|
rm -f clawdbot.json favicon.png "index (Copy 2).html" "index (Copy).html" \
|
|
InstructionsGrok.txt jarvis-memory-blueprint.tar.gz \
|
|
"LTX-2 FULL Guidelines.txt" openclaw.json yt_doc.md
|
|
rm -rf 20260326_201727/backup
|
|
```
|
|
|
|
4. Remove cruft from `/root/` and `/home/n8n/`:
|
|
```bash
|
|
rm -rf /root/backup /root/DocumentsUpload /root/docs /root/.projects /root/.projects-backup
|
|
rm -f /root/nginx-default.bak.13022026 /root/watcher.md
|
|
rm -rf ~/.projects ~/.main_projects ~/neuralstream-temp ~/filezilla-data
|
|
rm -f ~/site_log.md ~/ENDOFCARD ~/ENDOFFILE ~/EOF ~/HTML ~/SCRIPT_END
|
|
```
|
|
|
|
5. Re-deploy filezilla:
|
|
```bash
|
|
docker run -d --name filezilla --restart unless-stopped \
|
|
-p 3025:3000 -p 3026:3001 \
|
|
-v /root/html:/config/data:rw \
|
|
-e PUID=1000 -e PGID=1000 \
|
|
lscr.io/linuxserver/filezilla:latest
|
|
```
|
|
|
|
## Sync Staging → Production
|
|
|
|
After testing on .17, copy to .39:
|
|
|
|
```bash
|
|
# On .17: package the site
|
|
ssh [email protected] 'echo passw0rd | sudo -S tar czf /tmp/site.tar.gz -C /root/html .'
|
|
|
|
# Pull to local, push to .39
|
|
scp [email protected]:/tmp/site.tar.gz /tmp/
|
|
scp /tmp/site.tar.gz [email protected]:/tmp/
|
|
|
|
# On .39: extract
|
|
ssh [email protected] 'echo passw0rd | sudo -S tar xzf /tmp/site.tar.gz -C /root/html/ && echo passw0rd | sudo -S chown -R n8n:n8n /root/html/'
|
|
```
|
|
|
|
For music page specifically, package just the new files:
|
|
```bash
|
|
ssh [email protected] 'echo passw0rd | sudo -S tar czf /tmp/music_site.tar.gz -C /root/html music.html music/'
|
|
```
|
|
|
|
## Music Page Architecture
|
|
|
|
See `references/music-page.md` for full architecture — library.json manifest, genre folder structure, animated visualizer, Tailwind theme integration.
|
|
|
|
**Download counter**: see `references/download-counter.md` — two-URL play vs download tracking via nginx access log parsing, zero backend, cron-driven counts.json.
|
|
|
|
## Downloads Page Architecture
|
|
|
|
See `references/downloads-page.md` for full architecture — downloads.json manifest, type filter buttons, matching music page row style, download counter integration.
|
|
|
|
**Quick reference:**
|
|
- Manifest: `/root/html/backup/downloads.json` — `{items: [{id, title, description, type, src}]}`
|
|
- Types: Image, JSON, TXT, MD, Skill — filter buttons auto-generated from manifest
|
|
- Counter: same `counts.json` tracks `/backup/` paths under `downloads` key
|
|
- Styling: identical to music page — glass rows, type badge pill, centered count badge, download button on right
|
|
|
|
### Music Management Quick Reference
|
|
|
|
**Alphabetize after every change**: tracks in library.json MUST be sorted by title (case-insensitive). Use:
|
|
```bash
|
|
sshpass -p 'passw0rd' ssh -o StrictHostKeyChecking=no [email protected] 'python3 -c "
|
|
import json; data = json.load(open(\"/root/html/music/audio/library.json\"))
|
|
data[\"tracks\"].sort(key=lambda t: t[\"title\"].lower())
|
|
json.dump(data, open(\"/root/html/music/audio/library.json\",\"w\"), indent=2)
|
|
"'
|
|
```
|
|
|
|
**Move track between genres**: update both `genre` and `src` fields in library.json, then re-sort.
|
|
|
|
**Remove a genre**: delete the folder under `/root/html/music/audio/<genre>/`, remove all tracks with that genre from library.json. The filter button disappears automatically — no HTML edit needed.
|
|
|
|
**Validate all tracks playable**:
|
|
```bash
|
|
sshpass -p 'passw0rd' ssh -o StrictHostKeyChecking=no [email protected] 'python3 -c "
|
|
import json, os
|
|
data = json.load(open(\"/root/html/music/audio/library.json\"))
|
|
missing = [t[\"title\"] for t in data[\"tracks\"] if not os.path.exists(\"/root/html\" + t[\"src\"])]
|
|
print(f\"All {len(data[\"tracks\"])} present\" if not missing else f\"MISSING: \" + \", \".join(missing))
|
|
"'
|
|
```
|
|
|
|
**Sync missing files from NAS**: source files on TrueNAS at `smb://green.local/proxmoxbackup/SiteMusic/<genre>/`. Download via smbclient, scp to .39, copy into genre folder. See `references/music-page.md` for full workflow.
|
|
|
|
## Pitfalls
|
|
|
|
- **Kali vs Debian**: .17 runs Kali (rolling), .39 runs Debian 13 (stable). Package names may differ (e.g., `docker-compose` vs `docker-compose-plugin`). Prefer Debian for production.
|
|
- **No swap on either LXC**: 4.1GB RAM with no safety net. User explicitly declined swapfile — don't add without asking.
|
|
- **filezilla maps `/root/html`**: any file dropped via filezilla lands in the nginx root. Permissions must stay n8n:n8n.
|
|
- **nginx runs as systemd service, NOT Docker**: the nginx container is always stale — the live server is the native systemd unit.
|
|
- **index.html nav**: when adding a new page tab, insert between Downloads and Contact in the nav `<div class="hidden md:flex space-x-8">`.
|
|
- **Count badge visibility**: default gray-on-transparent styling (0.7rem, 500 weight, 0.7 opacity) is nearly invisible. Use indigo text (#6366f1), subtle indigo background pill, 600 weight, centered below title. See `references/download-counter.md` for the full CSS.
|
|
- **Sync from production to staging**: when .17 is behind, pull from .39 via local machine as relay — `ssh .39 tar → scp to local → scp to .17 → extract`. Direct .39→.17 scp may not work if they can't reach each other.
|
|
- **304 Not Modified not counted**: counter only matches status 200 and 206. Browser-cached replays return 304 and are invisible. A user who plays a track, refreshes, and plays again from cache generates only one count. Fix: add `'304'` to the status check in `parse_new_entries()`.
|
|
- **Query string pollution in counter**: access log captures query strings (`/backup/downloads.json?t=12345`). Regex captures the full string including `?t=...`, so `.json` exclusion checks fail. Fix: `.split('?', 1)[0]` on all captured paths before validation.
|
|
- **Leading slash mismatch in counter**: some log paths have leading `/` (e.g. `/electronic/Heavy-Weight.mp3`). Frontend looks up without slash. Fix: `.lstrip('/')` on all captured paths. Clean existing data too.
|
|
- **nginx types{} block overrides ALL MIME types**: adding `types {}` in a server/location context replaces inherited MIME types from `mime.types`, causing `.html` to serve as `application/octet-stream` (download prompt). Never use `types {}` in server blocks — add types to http-level config only.
|
|
- **Grok validation loop**: dispatch to Grok for adversarial validation after building, fix findings, re-validate. Grok caught range-request inflation, log rotation loss, invalid HTML, path traversal, query string pollution, leading slash mismatch, and types{} MIME override — all real bugs.
|
|
- **Counter script must strip query strings**: access log captures `?t=...` cache busters. Use `.split('?', 1)[0]` on all captured paths before validation and key storage.
|
|
- **Counter script must strip leading slashes**: some log paths have leading `/`. Use `.lstrip('/')` on all captured paths. Frontend lookup keys have no leading slash.
|
|
- **Backup path validation**: `is_valid_backup()` must exclude `.json` files and paths ending in `/` from download counts. Apply AFTER stripping query strings.
|
|
- **Persistent accumulation over full-log re-parse**: load existing counts.json, only process new entries via state file (inode+offset). Survives log rotation without re-parsing compressed logs.
|
|
- **nginx types{} block is dangerous**: adding `types {}` in a server/location context replaces ALL inherited MIME types from `mime.types`, causing `.html` to serve as `application/octet-stream` (download prompt). Never use `types {}` in server blocks.
|