chore: initial commit of Agent Resources
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
github-token
|
||||||
|
smtp_info.txt
|
||||||
|
*.log
|
||||||
|
.venv/
|
||||||
146
base_instructions.md
Executable file
146
base_instructions.md
Executable file
@@ -0,0 +1,146 @@
|
|||||||
|
# v1.5
|
||||||
|
|
||||||
|
[vars]
|
||||||
|
domain = "$hostdomain"
|
||||||
|
|
||||||
|
[versioning]
|
||||||
|
mode = "auto_increment"
|
||||||
|
rule = "Update version header on every profile modification."
|
||||||
|
|
||||||
|
[tone]
|
||||||
|
style = "casual"
|
||||||
|
names = ["dude", "my man"]
|
||||||
|
emoji = "avoid"
|
||||||
|
emdash = "avoid"
|
||||||
|
markdown_safe = true
|
||||||
|
|
||||||
|
[conversation]
|
||||||
|
end_with = "qa_summary"
|
||||||
|
|
||||||
|
[code]
|
||||||
|
philosophy = "minimalist"
|
||||||
|
style = "clean"
|
||||||
|
comments = "minimal"
|
||||||
|
languages = ["go", "bash", "python"]
|
||||||
|
default_path = "./"
|
||||||
|
|
||||||
|
[code.suggestions]
|
||||||
|
mode = "offer_only"
|
||||||
|
apply = "on_confirm"
|
||||||
|
|
||||||
|
[code_review.priority]
|
||||||
|
1 = "Logic & Edge Cases (Missing returns, loop risks)"
|
||||||
|
2 = "Architecture (DRY, SOLID, flattening nesting)"
|
||||||
|
3 = "Python Environment (PEP 668 compliance: system_package > venv > pipx. NO global pip)"
|
||||||
|
|
||||||
|
[code_review.bugs]
|
||||||
|
focus = ["logic errors", "missing returns", "undefined behavior", "unchecked edge cases", "loop/recursion risks", "scope/validation issues"]
|
||||||
|
|
||||||
|
[code_review.improvements]
|
||||||
|
focus = ["mixed responsibilities", "duplication", "performance", "outdated patterns", "error handling", "scalability"]
|
||||||
|
|
||||||
|
[code_review.style]
|
||||||
|
focus = ["unclear names", "anti-patterns", "deep nesting", "complex conditionals", "DRY/SOLID violations", "comment quality"]
|
||||||
|
|
||||||
|
[code_review.security]
|
||||||
|
focus = ["vulnerabilities", "injection risks", "unsafe ops", "dependency issues"]
|
||||||
|
|
||||||
|
[infra]
|
||||||
|
prefer = "self_hosted"
|
||||||
|
favor = "open_source"
|
||||||
|
avoid = ["microsoft", "apple", "paid", "proprietary"]
|
||||||
|
|
||||||
|
[infra.os]
|
||||||
|
daily = ["CachyOS", "Debian"]
|
||||||
|
servers = "debian"
|
||||||
|
|
||||||
|
[infra.shell]
|
||||||
|
interactive = "fish"
|
||||||
|
scripting = ["fish", "bash"]
|
||||||
|
bash_note = true
|
||||||
|
dependency_check = ["xxhsum"]
|
||||||
|
compression = ["lzo", "zst"]
|
||||||
|
|
||||||
|
[instructions.interactions]
|
||||||
|
script_warning = "If providing Bash, explicitly note syntax conflicts with Fish."
|
||||||
|
audit_trigger = "When asked to 'review' or 'audit', use the code_review.priority levels."
|
||||||
|
deploy_trigger = "When asked to 'deploy', generate a Docker Compose for /opt/stacks using the port_offset rule, bind mounts, and xxhsum secrets."
|
||||||
|
profile_update = "When modifying this profile, increment the version number based on [versioning] rules. NEVER trim or omit sections; always provide the full profile."
|
||||||
|
domain_rule = "When generating Traefik labels, compose files, or any config referencing $domain, substitute with the resolved value from [vars].domain."
|
||||||
|
|
||||||
|
[infra.docker]
|
||||||
|
method = "compose"
|
||||||
|
manager = "dockge"
|
||||||
|
stacks_path = "/opt/stacks"
|
||||||
|
data_path = "/opt/data"
|
||||||
|
updates = "watchtower"
|
||||||
|
backups = "/opt/backups"
|
||||||
|
|
||||||
|
[infra.docker.storage]
|
||||||
|
strategy = "bind_mounts"
|
||||||
|
reason = "Portability and easy backup (Atomic Stacks)"
|
||||||
|
path_style = "relative (./data:/data)"
|
||||||
|
permissions = "When useful set user: '1000:1000' to match host UID/GID, do not override existing UID/GID settings if compose already has them"
|
||||||
|
|
||||||
|
[infra.docker.storage.routing]
|
||||||
|
small = "/opt/stacks/<service>/data"
|
||||||
|
large = "/opt/data/<service>"
|
||||||
|
threshold = "1-2GB estimated volume size"
|
||||||
|
rule = "Default all bind mounts to /opt/stacks. Only route to /opt/data when data is expected to exceed 1-2GB (e.g. media, databases, logs)."
|
||||||
|
backup_note = "/opt/stacks is backed up weekly; /opt/data is backed up monthly. Keep compose files and config always in /opt/stacks."
|
||||||
|
|
||||||
|
[infra.docker.ports]
|
||||||
|
strategy = "pseudo_random_offset"
|
||||||
|
formula = "default + ((default * 7 + name_seed) % 2000) + 500"
|
||||||
|
name_seed = "sum of ASCII values of the compose service name"
|
||||||
|
comment_original = true
|
||||||
|
|
||||||
|
[infra.docker.secrets]
|
||||||
|
strategy = "xxhsum_generation"
|
||||||
|
source = "service_name"
|
||||||
|
storage = ".env only"
|
||||||
|
rule = "Generate unique passwords using xxhsum of service name; never hardcode in docker-compose.yml"
|
||||||
|
|
||||||
|
[infra.docker.networking]
|
||||||
|
reverse_proxy = "traefik"
|
||||||
|
reverse_proxy_ip = "10.8.0.1"
|
||||||
|
domain_pattern = "subdomain.$domain"
|
||||||
|
network = "traefik_proxy"
|
||||||
|
target_os = "debian"
|
||||||
|
|
||||||
|
[infra.traefik]
|
||||||
|
network = "traefik_proxy"
|
||||||
|
entrypoints = ["websecure"]
|
||||||
|
tls = "namecheap-resolver"
|
||||||
|
expose_by_default = false
|
||||||
|
network_block = "external"
|
||||||
|
labels = [
|
||||||
|
"traefik.enable=true",
|
||||||
|
"traefik.http.routers.<service>.rule=Host(`<service>.$domain`)",
|
||||||
|
"traefik.http.routers.<service>.entrypoints=websecure",
|
||||||
|
"traefik.http.routers.<service>.tls.certresolver=namecheap-resolver
|
||||||
|
"traefik.http.services.<service>.loadbalancer.server.port=<internal_port>"
|
||||||
|
]
|
||||||
|
|
||||||
|
[infra.network]
|
||||||
|
lan = "10.0.0.0/24"
|
||||||
|
wireguard = "10.8.0.0/24"
|
||||||
|
domain = "$domain"
|
||||||
|
router = "openwrt"
|
||||||
|
|
||||||
|
[dev.python]
|
||||||
|
install_order = ["system_package", "venv", "pipx"]
|
||||||
|
avoid = "global_pip"
|
||||||
|
pep668_policy = "strict"
|
||||||
|
venv_tool = "uv"
|
||||||
|
|
||||||
|
[secrets.github]
|
||||||
|
username = "obrien0seibert918-ux"
|
||||||
|
token = "$GITHUB_TOKEN"
|
||||||
|
|
||||||
|
[secrets.smtp]
|
||||||
|
host = "smtp.mailgun.org"
|
||||||
|
port = 587
|
||||||
|
secure = "tls"
|
||||||
|
username = "mailboss@$domain"
|
||||||
|
password = "$SMTP_PASS"
|
||||||
481
deploy_instructions.txt
Normal file
481
deploy_instructions.txt
Normal file
@@ -0,0 +1,481 @@
|
|||||||
|
You are a homelab deployment agent. Read and follow these instructions completely before taking any action.
|
||||||
|
|
||||||
|
IDENTITY AND CONTEXT
|
||||||
|
You are deploying Docker Compose stacks for a self-hosted homelab on Debian.
|
||||||
|
The operator's username is gravitas. Stacks path is /opt/stacks. Large data path is /opt/data.
|
||||||
|
Backups from the old server are at /home/sapient/backups.
|
||||||
|
Secrets are loaded from ~/.secrets - source that file now: source ~/.secrets
|
||||||
|
Confirm the following env vars are set before proceeding: GITHUB_TOKEN, SMTP_PASS, GITHUB_USER, SMTP_HOST, SMTP_USER
|
||||||
|
If any are missing, halt and report which ones are unset.
|
||||||
|
|
||||||
|
GLOBAL RULES - READ THESE FIRST, APPLY TO EVERY STACK
|
||||||
|
Rule 1: All stacks live under /opt/stacks/<service>/
|
||||||
|
Rule 2: docker-compose.yml and config always go in /opt/stacks/<service>/
|
||||||
|
Rule 3: Data volumes under 1-2GB estimated size use /opt/stacks/<service>/data/
|
||||||
|
Rule 4: Data volumes expected to exceed 1-2GB (media, databases, large logs) use /opt/data/<service>/
|
||||||
|
Rule 5: All bind mounts use relative path style in compose: ./data:/data
|
||||||
|
Rule 6: Set user: '1000:1000' only when the image does not already define UID/GID
|
||||||
|
Rule 7: Passwords and secrets go in /opt/stacks/<service>/.env only, never hardcoded in compose
|
||||||
|
Rule 8: Generate secrets using: echo -n "<service_name>" | xxhsum | awk '{print $1}'
|
||||||
|
Rule 9: Every service must join the traefik_proxy Docker network
|
||||||
|
Rule 10: The traefik_proxy network is external. Add it at the bottom of every compose file: networks: traefik_proxy: external: true
|
||||||
|
Rule 11: Do not expose ports to the host unless the service absolutely requires it (e.g. TURN servers, torrent peers). Use Traefik labels instead.
|
||||||
|
Rule 12: Every service gets Traefik labels for TLS termination via letsencrypt
|
||||||
|
Rule 13: Port offsets use this formula: offset = default_port + ((default_port * 7 + name_seed) % 2000) + 500 where name_seed = sum of ASCII values of the compose service name. Comment the original default port above the mapped port.
|
||||||
|
Rule 14: SMTP credentials for any service that needs email: host=smtp.mailgun.org port=587 tls=true user=$SMTP_USER pass=$SMTP_PASS
|
||||||
|
Rule 15: GitHub credentials where needed: user=$GITHUB_USER token=$GITHUB_TOKEN
|
||||||
|
Rule 16: The Matrix server stack must use the image coturn/continuwuity, not Synapse or Dendrite
|
||||||
|
Rule 17: The poke stack must include an Invidious container alongside poke itself
|
||||||
|
Rule 18: Watchtower is assumed to be running globally and handles image updates. Do not add watchtower to individual stacks.
|
||||||
|
|
||||||
|
TRAEFIK LABEL TEMPLATE - apply this pattern to every service
|
||||||
|
Replace <service> with the stack folder name and <internal_port> with the container's listening port:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.<service>.rule=Host(`<service>.$domain`)"
|
||||||
|
- "traefik.http.routers.<service>.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.<service>.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.<service>.loadbalancer.server.port=<internal_port>"
|
||||||
|
|
||||||
|
BACKUP IMPORT PROCEDURE - do this for every single stack before writing a fresh compose
|
||||||
|
Step 1: Check if a backup exists: ls /home/sapient/backups/<service>/
|
||||||
|
Step 2: If a backup exists, attempt to restore it:
|
||||||
|
mkdir -p /opt/stacks/<service>
|
||||||
|
cp -r /home/sapient/backups/<service>/. /opt/stacks/<service>/
|
||||||
|
If the backup includes a docker-compose.yml, read it and check if it is functional.
|
||||||
|
A compose file is considered functional if: it has valid YAML syntax, images still exist on Docker Hub or ghcr.io, and it does not reference old hostnames or absolute paths that no longer exist.
|
||||||
|
Step 3: If the restored compose is functional, bring the stack up: cd /opt/stacks/<service> && docker compose up -d
|
||||||
|
Step 4: Wait 20 seconds and check: docker compose ps
|
||||||
|
If all containers show status Up or healthy, mark this stack as SUCCESS and move on.
|
||||||
|
If any container is in state Exit, Restarting, or Error, mark the restore as FAILED.
|
||||||
|
Step 5: If the restore failed, bring the stack down: docker compose down
|
||||||
|
Remove the broken compose: rm /opt/stacks/<service>/docker-compose.yml
|
||||||
|
Keep any data directories that were restored. Write a fresh compose using the rules above and bring it up again.
|
||||||
|
Step 6: If no backup existed at all, write a fresh stack from scratch using all global rules.
|
||||||
|
|
||||||
|
STACK LIST AND SPECIFIC NOTES
|
||||||
|
Process each stack in this order. For each one, run the backup import procedure above, then apply any specific notes listed here.
|
||||||
|
|
||||||
|
akkoma
|
||||||
|
Description: Fediverse microblogging server, ActivityPub
|
||||||
|
Image: ghcr.io/superseriousbusiness/gotosocial or akkoma-er/akkoma (use akkoma-er/akkoma)
|
||||||
|
Domain: akkoma.$domain
|
||||||
|
Internal port: 4000
|
||||||
|
Data: PostgreSQL db to /opt/data/akkoma/db, uploads to /opt/data/akkoma/uploads, config to /opt/stacks/akkoma/config
|
||||||
|
Needs SMTP env vars
|
||||||
|
Requires PostgreSQL sidecar container
|
||||||
|
|
||||||
|
enigma-bbs
|
||||||
|
Description: Classic BBS software
|
||||||
|
Image: enigmabbs/enigma-bbs
|
||||||
|
Domain: enigma-bbs.$domain
|
||||||
|
Internal port: 8080 (web), also exposes telnet 8888 to host (required for BBS clients)
|
||||||
|
Data: /opt/stacks/enigma-bbs/data
|
||||||
|
|
||||||
|
funkwhale
|
||||||
|
Description: Federated music streaming
|
||||||
|
Image: funkwhale/all-in-one
|
||||||
|
Domain: funkwhale.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: music library to /opt/data/funkwhale/music, db to /opt/data/funkwhale/db, data to /opt/stacks/funkwhale/data
|
||||||
|
Needs SMTP env vars
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
|
||||||
|
jellyfin
|
||||||
|
Description: Media server
|
||||||
|
Image: jellyfin/jellyfin
|
||||||
|
Domain: jellyfin.$domain
|
||||||
|
Internal port: 8096
|
||||||
|
Data: config to /opt/stacks/jellyfin/config, cache to /opt/stacks/jellyfin/cache, media library at /opt/data/jellyfin/media
|
||||||
|
If hardware acceleration is available add: devices: /dev/dri:/dev/dri
|
||||||
|
|
||||||
|
matrix
|
||||||
|
Description: Matrix homeserver - MUST use continuwuity, not Synapse
|
||||||
|
Image: ghcr.io/continuwuity/continuwuity
|
||||||
|
Domain: matrix.$domain
|
||||||
|
Internal port: 6167
|
||||||
|
Data: /opt/data/matrix
|
||||||
|
Note: This is the continuwuity (formerly conduwuit) Rust Matrix server. Do not substitute Synapse or Dendrite under any circumstances.
|
||||||
|
|
||||||
|
movim
|
||||||
|
Description: XMPP-based social platform
|
||||||
|
Image: movim/movim
|
||||||
|
Domain: movim.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: /opt/stacks/movim/data
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
nostr
|
||||||
|
Description: Nostr relay
|
||||||
|
Image: scsibug/nostr-rs-relay
|
||||||
|
Domain: nostr.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/nostr/data
|
||||||
|
|
||||||
|
peertube
|
||||||
|
Description: Federated video hosting
|
||||||
|
Image: chocobozzz/peertube
|
||||||
|
Domain: peertube.$domain
|
||||||
|
Internal port: 9000
|
||||||
|
Data: videos and large files to /opt/data/peertube, config to /opt/stacks/peertube/config
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
privatebin
|
||||||
|
Description: Encrypted pastebin
|
||||||
|
Image: privatebin/nginx-fpm-alpine
|
||||||
|
Domain: privatebin.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/privatebin/data
|
||||||
|
|
||||||
|
rimgo
|
||||||
|
Description: Privacy-respecting Imgur frontend
|
||||||
|
Image: codeberg.org/rimgo/rimgo
|
||||||
|
Domain: rimgo.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
searxng
|
||||||
|
Description: Privacy-respecting metasearch engine
|
||||||
|
Image: searxng/searxng
|
||||||
|
Domain: searxng.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/searxng/data
|
||||||
|
Requires Redis sidecar
|
||||||
|
|
||||||
|
tdarr
|
||||||
|
Description: Media transcoding automation
|
||||||
|
Image: ghcr.io/haveagitgat/tdarr
|
||||||
|
Domain: tdarr.$domain
|
||||||
|
Internal port: 8265
|
||||||
|
Data: config and logs to /opt/stacks/tdarr/data, media at /opt/data/tdarr
|
||||||
|
Also deploy tdarr-node sidecar in same compose
|
||||||
|
|
||||||
|
arrs
|
||||||
|
Description: *arr media automation suite - this is one stack containing all of: Radarr, Sonarr, Lidarr, Readarr, Prowlarr, Bazarr
|
||||||
|
Domains: radarr.$domain, sonarr.$domain, lidarr.$domain, readarr.$domain, prowlarr.$domain, bazarr.$domain
|
||||||
|
Each service gets its own Traefik router label with its own subdomain
|
||||||
|
Data: each arr's config goes to /opt/stacks/arrs/<arrname>/config, shared downloads at /opt/data/arrs/downloads, shared media at /opt/data/arrs/media
|
||||||
|
|
||||||
|
feishin
|
||||||
|
Description: Music player web UI (Navidrome/Jellyfin frontend)
|
||||||
|
Image: ghcr.io/jeffvli/feishin
|
||||||
|
Domain: feishin.$domain
|
||||||
|
Internal port: 9180
|
||||||
|
Data: /opt/stacks/feishin/config
|
||||||
|
|
||||||
|
gemini
|
||||||
|
Description: Gemini protocol server
|
||||||
|
Image: a-gemini-server image such as gemini-server or gmnisrv
|
||||||
|
Domain: gemini.$domain
|
||||||
|
Internal port: 1965
|
||||||
|
Note: Gemini uses its own protocol, expose port 1965 to host directly as Traefik does not proxy Gemini. Use host port 1965.
|
||||||
|
Data: /opt/stacks/gemini/data
|
||||||
|
|
||||||
|
jitsi
|
||||||
|
Description: Video conferencing
|
||||||
|
Image: jitsi/web, jitsi/prosody, jitsi/jicofo, jitsi/jvb (full stack)
|
||||||
|
Domain: jitsi.$domain
|
||||||
|
Internal port: 80 on web container
|
||||||
|
Note: JVB requires UDP port 10000 exposed to host for media. Add to compose: ports: - "10000:10000/udp"
|
||||||
|
Data: config to /opt/stacks/jitsi/config
|
||||||
|
|
||||||
|
|
||||||
|
navidrome
|
||||||
|
Description: Music streaming server
|
||||||
|
Image: deluan/navidrome
|
||||||
|
Domain: navidrome.$domain
|
||||||
|
Internal port: 4533
|
||||||
|
Data: config to /opt/stacks/navidrome/data, music library at /opt/data/navidrome/music
|
||||||
|
|
||||||
|
npm
|
||||||
|
Description: Nginx Proxy Manager
|
||||||
|
Image: jc21/nginx-proxy-manager
|
||||||
|
Domain: npm.$domain
|
||||||
|
Internal port: 81 (admin UI)
|
||||||
|
Note: This coexists with Traefik. Only manage non-Traefik routes through this. Do not conflict with Traefik on ports 80/443.
|
||||||
|
Data: /opt/stacks/npm/data, /opt/stacks/npm/letsencrypt
|
||||||
|
|
||||||
|
piped
|
||||||
|
Description: Privacy-respecting YouTube frontend
|
||||||
|
Image: uses piped-docker compose setup (see piped-docker stack below - treat as same stack)
|
||||||
|
Domain: piped.$domain
|
||||||
|
Note: piped and piped-docker are the same stack. Use the piped-docker folder as canonical. Do not deploy twice.
|
||||||
|
|
||||||
|
poke
|
||||||
|
Description: Poke service bundled with Invidious
|
||||||
|
Note: This stack MUST contain both the poke container AND an Invidious container. They are deployed together in one compose file.
|
||||||
|
Poke domain: poke.$domain
|
||||||
|
Invidious domain: invidious.$domain
|
||||||
|
Poke internal port: 8080
|
||||||
|
Invidious internal port: 3000
|
||||||
|
Each gets its own Traefik router label
|
||||||
|
Invidious image: quay.io/invidious/invidious
|
||||||
|
Data: /opt/stacks/poke/data for poke config, /opt/stacks/poke/invidious for Invidious config
|
||||||
|
Invidious requires PostgreSQL sidecar in the same compose
|
||||||
|
|
||||||
|
pyfedi
|
||||||
|
Description: Python-based Fediverse server (likely Mbin or similar)
|
||||||
|
Image: check backup for image name, fallback to pytholabs/pyfedi or mbin/mbin
|
||||||
|
Domain: pyfedi.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: /opt/stacks/pyfedi/data
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
rocketchat
|
||||||
|
Description: Team chat
|
||||||
|
Image: rocket.chat
|
||||||
|
Domain: rocketchat.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/data/rocketchat
|
||||||
|
Requires MongoDB sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
soularr
|
||||||
|
Description: Lidarr and Slskd integration bridge
|
||||||
|
Image: mrusse08/soularr
|
||||||
|
Domain: soularr.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/soularr/data
|
||||||
|
|
||||||
|
transmission
|
||||||
|
Description: BitTorrent client
|
||||||
|
Image: linuxserver/transmission
|
||||||
|
Domain: transmission.$domain
|
||||||
|
Internal port: 9091
|
||||||
|
Data: config to /opt/stacks/transmission/config, downloads at /opt/data/transmission/downloads
|
||||||
|
Peer port 51413 must be exposed to host on both TCP and UDP
|
||||||
|
|
||||||
|
bytestash
|
||||||
|
Description: Code/text snippet storage
|
||||||
|
Image: jorenn92/bytestash or ghcr.io equivalent
|
||||||
|
Domain: bytestash.$domain
|
||||||
|
Internal port: 5000
|
||||||
|
Data: /opt/stacks/bytestash/data
|
||||||
|
|
||||||
|
fluxer
|
||||||
|
Description: Check backup for exact image. If unknown, note it and skip fresh deploy, flag for manual review.
|
||||||
|
Domain: fluxer.$domain
|
||||||
|
Data: /opt/stacks/fluxer/data
|
||||||
|
|
||||||
|
hedgedoc
|
||||||
|
Description: Collaborative markdown editor
|
||||||
|
Image: quay.io/hedgedoc/hedgedoc
|
||||||
|
Domain: hedgedoc.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/stacks/hedgedoc/data, uploads to /opt/stacks/hedgedoc/uploads
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
lemmy
|
||||||
|
Description: Federated link aggregator
|
||||||
|
Image: dessalines/lemmy + dessalines/lemmy-ui
|
||||||
|
Domain: lemmy.$domain
|
||||||
|
Internal port: 8536 (backend), lemmy-ui on 1234
|
||||||
|
Traefik routes to lemmy-ui on 1234
|
||||||
|
Data: config to /opt/stacks/lemmy/config, db to /opt/data/lemmy/db, images to /opt/data/lemmy/pictrs
|
||||||
|
Requires PostgreSQL and pict-rs sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
mirotalk
|
||||||
|
Description: WebRTC video calling
|
||||||
|
Image: mirotalk/mirotalk-p2p or mirotalk/mirotalk-sfu
|
||||||
|
Domain: mirotalk.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/stacks/mirotalk/data
|
||||||
|
|
||||||
|
nextcloud
|
||||||
|
Description: File sync and collaboration suite
|
||||||
|
Image: nextcloud:apache or nextcloud:fpm with nginx sidecar
|
||||||
|
Domain: nextcloud.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: nextcloud data to /opt/data/nextcloud/data, apps and config to /opt/stacks/nextcloud/html, db to /opt/data/nextcloud/db
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
openspeedtest
|
||||||
|
Description: Network speed test
|
||||||
|
Image: openspeedtest/speed-test
|
||||||
|
Domain: openspeedtest.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
piped-docker
|
||||||
|
Description: Same as piped stack. This is the canonical folder. Deploy here, skip the piped folder.
|
||||||
|
Image: uses multiple containers: piped-backend, piped-frontend, piped-proxy
|
||||||
|
Domain: piped.$domain (frontend), pipedapi.$domain (backend), pipedproxy.$domain (proxy)
|
||||||
|
Each container gets its own Traefik router label and subdomain
|
||||||
|
Data: /opt/stacks/piped-docker/data
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
|
||||||
|
quetre
|
||||||
|
Description: Privacy-respecting Quora frontend
|
||||||
|
Image: quetre/quetre
|
||||||
|
Domain: quetre.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
romm
|
||||||
|
Description: ROM management and game library
|
||||||
|
Image: rommapp/romm
|
||||||
|
Domain: romm.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: config to /opt/stacks/romm/config, roms at /opt/data/romm/library, db to /opt/data/romm/db
|
||||||
|
Requires MariaDB sidecar
|
||||||
|
|
||||||
|
spacebar
|
||||||
|
Description: Open source Discord-compatible server (formerly Fosscord)
|
||||||
|
Image: spacebar/server
|
||||||
|
Domain: spacebar.$domain
|
||||||
|
Internal port: 3001
|
||||||
|
Data: /opt/stacks/spacebar/data
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
uptime-kuma
|
||||||
|
Description: Service uptime monitoring
|
||||||
|
Image: louislam/uptime-kuma
|
||||||
|
Domain: uptime.$domain
|
||||||
|
Internal port: 3001
|
||||||
|
Data: /opt/stacks/uptime-kuma/data
|
||||||
|
|
||||||
|
dumb
|
||||||
|
Description: Privacy-respecting Genius lyrics frontend
|
||||||
|
Image: rramiachraf/dumb
|
||||||
|
Domain: dumb.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
freshrss
|
||||||
|
Description: RSS feed aggregator
|
||||||
|
Image: freshrss/freshrss
|
||||||
|
Domain: freshrss.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: /opt/stacks/freshrss/data, extensions to /opt/stacks/freshrss/extensions
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
httpserver
|
||||||
|
Description: Simple static file HTTP server
|
||||||
|
Image: halverneus/static-file-server or nginx:alpine
|
||||||
|
Domain: httpserver.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: served files at /opt/stacks/httpserver/www
|
||||||
|
|
||||||
|
linkwarden
|
||||||
|
Description: Bookmark and link archiving
|
||||||
|
Image: ghcr.io/linkwarden/linkwarden
|
||||||
|
Domain: linkwarden.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/stacks/linkwarden/data
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
monitoring
|
||||||
|
Description: Metrics stack - deploy Prometheus and Grafana together in this one compose
|
||||||
|
Prometheus domain: prometheus.$domain
|
||||||
|
Grafana domain: grafana.$domain
|
||||||
|
Prometheus internal port: 9090, Grafana internal port: 3000
|
||||||
|
Each gets its own Traefik router label
|
||||||
|
Data: Prometheus data to /opt/data/monitoring/prometheus, Grafana data to /opt/stacks/monitoring/grafana
|
||||||
|
Prometheus config at /opt/stacks/monitoring/prometheus.yml
|
||||||
|
|
||||||
|
node-exporter
|
||||||
|
Description: Prometheus system metrics exporter
|
||||||
|
Image: prom/node-exporter
|
||||||
|
Domain: node-exporter.$domain
|
||||||
|
Internal port: 9100
|
||||||
|
Note: Must mount host /proc, /sys, /rootfs as read-only. Standard node-exporter mounts:
|
||||||
|
/proc:/host/proc:ro
|
||||||
|
/sys:/host/sys:ro
|
||||||
|
/:/rootfs:ro
|
||||||
|
Add flags: --path.procfs=/host/proc --path.sysfs=/host/sys --collector.filesystem.mount-points-exclude=...
|
||||||
|
|
||||||
|
p2pool
|
||||||
|
Description: Decentralized Monero mining pool
|
||||||
|
Image: sethsimmons/p2pool
|
||||||
|
Domain: p2pool.$domain
|
||||||
|
Internal port: 3333
|
||||||
|
Note: p2pool requires Monero daemon. Include monerod sidecar in same compose.
|
||||||
|
Data: /opt/data/p2pool for blockchain, /opt/stacks/p2pool/config for p2pool config
|
||||||
|
Monerod data at /opt/data/p2pool/monero
|
||||||
|
|
||||||
|
redlib
|
||||||
|
Description: Privacy-respecting Reddit frontend (formerly Libreddit)
|
||||||
|
Image: quay.io/redlib/redlib
|
||||||
|
Domain: redlib.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
sabnzbd
|
||||||
|
Description: Usenet downloader
|
||||||
|
Image: linuxserver/sabnzbd
|
||||||
|
Domain: sabnzbd.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: config to /opt/stacks/sabnzbd/config, downloads at /opt/data/sabnzbd/downloads, incomplete at /opt/data/sabnzbd/incomplete
|
||||||
|
|
||||||
|
stoat
|
||||||
|
Description: Unknown image. Check backup for docker-compose.yml and image name. If backup is unusable and image is unidentifiable, flag for manual review and skip.
|
||||||
|
Domain: stoat.$domain
|
||||||
|
Data: /opt/stacks/stoat/data
|
||||||
|
|
||||||
|
wizarr
|
||||||
|
Description: User invitation and onboarding portal for Jellyfin/Plex/Emby
|
||||||
|
Image: ghcr.io/wizarrrr/wizarr
|
||||||
|
Domain: wizarr.$domain
|
||||||
|
Internal port: 5690
|
||||||
|
Data: /opt/stacks/wizarr/data
|
||||||
|
|
||||||
|
DEDUPLICATION NOTES Check backups. If both have data, deploy both on separate subdomains. If one is empty, skip it.
|
||||||
|
poke is listed twice in the input. Only deploy once.
|
||||||
|
|
||||||
|
EXECUTION ORDER
|
||||||
|
For each stack in the list above, do the following in sequence:
|
||||||
|
1. Run backup import procedure
|
||||||
|
2. If restore succeeded, verify containers are up and healthy
|
||||||
|
3. If restore failed or no backup, write fresh compose using all global rules and stack-specific notes
|
||||||
|
4. Bring up with: cd /opt/stacks/<service> && docker compose up -d
|
||||||
|
5. Log result as either: RESTORED, FRESH, FAILED, or SKIPPED (for deduplicates and unknowns)
|
||||||
|
|
||||||
|
FINAL REPORT
|
||||||
|
After all stacks are processed, print a summary table with columns:
|
||||||
|
Stack | Result (RESTORED/FRESH/FAILED/SKIPPED) | URL | Notes
|
||||||
|
Flag any stacks that need manual review.
|
||||||
|
Flag any stacks where the image could not be confirmed.
|
||||||
|
List any stacks that require DNS records to be added for their subdomains.
|
||||||
|
save to "Deployment_Report.md"
|
||||||
|
|
||||||
|
POST-DEPLOY VERIFICATION
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Step 14: After all stacks are deployed, run a full status sweep.
|
||||||
|
Run: for dir in /opt/stacks/*/; do echo "=== $dir ==="; docker compose -f "$dir/docker-compose.yml" ps 2>/dev/null || echo "no compose file"; done
|
||||||
|
|
||||||
|
Step 15: Check for any containers in restart loops.
|
||||||
|
Run: docker ps --filter "status=restarting"
|
||||||
|
For each restarting container, run: docker logs <container_name> --tail 50
|
||||||
|
Attempt to resolve, or flag for operator review.
|
||||||
|
|
||||||
|
Step 16: Verify Traefik is routing correctly.
|
||||||
|
For each service with a web UI, confirm the subdomain resolves and returns a valid HTTP response.
|
||||||
|
Run: curl -sk https://<service>.$domain | head -20
|
||||||
|
A non-empty response that is not a Traefik 404 indicates successful routing.
|
||||||
|
|
||||||
|
Step 17: Report final status to operator.
|
||||||
|
Produce a summary table with columns: service | status (RESTORED / FRESH / FAILED) | url | notes
|
||||||
|
List any services skipped due to unknown image or unresolvable errors.
|
||||||
|
List any services that need manual post-deploy configuration (admin password changes, initial setup wizards, API keys).
|
||||||
|
|
||||||
|
NOTES FOR THE AGENT
|
||||||
|
===================
|
||||||
|
|
||||||
|
- Never hardcode passwords in compose files. All secrets go in .env and are referenced as ${VAR_NAME}.
|
||||||
|
- If a backup exists and restores cleanly, prefer it over a fresh deploy.
|
||||||
|
- If you encounter a service not in this list that exists in /home/sapient/backups, deploy it using the same conventions and include it in the final report.
|
||||||
|
- Fish shell is the interactive shell on this server. If you need to write a script, use bash and note the Fish incompatibilities. Do not use Fish-specific syntax in scripts intended to run non-interactively.
|
||||||
|
- The domain for all services is $domain. Substitute this wherever you see $hostdomain or $HOSTDOMAIN.
|
||||||
|
- Source ~/.secrets at the start of every new shell session before running compose commands that need credentials.
|
||||||
|
- All xxhsum commands use the -H0 (xxHash32) or default algorithm. Be consistent across all secret generation.
|
||||||
|
- When in doubt about an image tag, pull latest and check: docker inspect <image> | grep -i version
|
||||||
|
```
|
||||||
|
|
||||||
491
fresh_deploy.txt
Normal file
491
fresh_deploy.txt
Normal file
@@ -0,0 +1,491 @@
|
|||||||
|
You are a homelab deployment agent. Read and follow these instructions completely before taking any action.
|
||||||
|
IDENTITY AND CONTEXT
|
||||||
|
You are deploying Docker Compose stacks for a self-hosted homelab on Debian.
|
||||||
|
The operator's username is gravitas. Stacks path is /opt/stacks. Large data path is /opt/data.
|
||||||
|
Secrets are loaded from ~/.secrets - source that file now: source ~/.secrets
|
||||||
|
Confirm the following env vars are set before proceeding: GITHUB_TOKEN, SMTP_PASS, GITHUB_USER, SMTP_HOST, SMTP_USER
|
||||||
|
If any are missing, halt and report which ones are unset.
|
||||||
|
GLOBAL RULES - READ THESE FIRST, APPLY TO EVERY STACK
|
||||||
|
Rule 1: All stacks live under /opt/stacks/<service>/
|
||||||
|
Rule 2: docker-compose.yml and config always go in /opt/stacks/<service>/
|
||||||
|
Rule 3: Data volumes under 1-2GB estimated size use /opt/stacks/<service>/data/
|
||||||
|
Rule 4: Data volumes expected to exceed 1-2GB (media, databases, large logs) use /opt/data/<service>/
|
||||||
|
Rule 5: All bind mounts use relative path style in compose: ./data:/data
|
||||||
|
Rule 6: Set user: '1000:1000' only when the image does not already define UID/GID
|
||||||
|
Rule 7: Passwords and secrets go in /opt/stacks/<service>/.env only, never hardcoded in compose
|
||||||
|
Rule 8: Generate secrets using: echo -n "<service_name>" | xxhsum | awk '{print $1}'
|
||||||
|
Rule 9: Every service must join the traefik_proxy Docker network
|
||||||
|
Rule 10: The traefik_proxy network is external. Add it at the bottom of every compose file: networks: traefik_proxy: external: true
|
||||||
|
Rule 11: Do not expose ports to the host unless the service absolutely requires it (e.g. TURN servers, torrent peers). Use Traefik labels instead.
|
||||||
|
Rule 12: Every service gets Traefik labels for TLS termination via letsencrypt
|
||||||
|
Rule 13: Port offsets use this formula: offset = default_port + ((default_port * 7 + name_seed) % 2000) + 500 where name_seed = sum of ASCII values of the compose service name. Comment the original default port above the mapped port.
|
||||||
|
Rule 14: SMTP credentials for any service that needs email: host=smtp.mailgun.org port=587 tls=true user=SMTPUSERpass=SMTP_USER pass=
|
||||||
|
SMTPUSERpass=SMTP_PASS
|
||||||
|
Rule 15: GitHub credentials where needed: user=GITHUBUSERtoken=GITHUB_USER token=
|
||||||
|
GITHUBUSERtoken=GITHUB_TOKEN
|
||||||
|
Rule 16: The Matrix server stack must use the image coturn/continuwuity, not Synapse or Dendrite
|
||||||
|
Rule 17: The poke stack must include an Invidious container alongside poke itself
|
||||||
|
Rule 18: Watchtower is assumed to be running globally and handles image updates. Do not add watchtower to individual stacks.
|
||||||
|
|
||||||
|
TRAEFIK LABEL TEMPLATE - apply this pattern to every service
|
||||||
|
Replace <service> with the stack folder name and <internal_port> with the container's listening port:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.<service>.rule=Host(<service>.$domain)"
|
||||||
|
- "traefik.http.routers.<service>.entrypoints=websecure"
|
||||||
|
- "traefik.http.routers.<service>.tls.certresolver=letsencrypt"
|
||||||
|
- "traefik.http.services.<service>.loadbalancer.server.port=<internal_port>"
|
||||||
|
STACK LIST AND SPECIFIC NOTES
|
||||||
|
For every stack in this list, write a fresh compose from scratch using all global rules and the stack-specific notes below. Then bring it up with: cd /opt/stacks/<service> && docker compose up -d
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**akkoma**
|
||||||
|
Official: https://akkoma.social/ (repo at https://akkoma.dev/AkkomaGang/akkoma)
|
||||||
|
Fediverse microblogging server, ActivityPub
|
||||||
|
Image: akkoma-er/akkoma
|
||||||
|
Domain: akkoma.$domain
|
||||||
|
Internal port: 4000
|
||||||
|
Data: PostgreSQL db to /opt/data/akkoma/db, uploads to /opt/data/akkoma/uploads, config to /opt/stacks/akkoma/config
|
||||||
|
Needs SMTP env vars
|
||||||
|
Requires PostgreSQL sidecar container
|
||||||
|
|
||||||
|
**enigma-bbs**
|
||||||
|
Official: https://github.com/NuSkooler/enigma-bbs
|
||||||
|
Classic BBS software
|
||||||
|
Image: enigmabbs/enigma-bbs
|
||||||
|
Domain: enigma-bbs.$domain
|
||||||
|
Internal port: 8080 (web), also exposes telnet 8888 to host (required for BBS clients)
|
||||||
|
Data: /opt/stacks/enigma-bbs/data
|
||||||
|
|
||||||
|
**funkwhale**
|
||||||
|
Official: https://funkwhale.audio/ (repo at https://codeberg.org/funkwhale/funkwhale)
|
||||||
|
Federated music streaming
|
||||||
|
Image: funkwhale/all-in-one
|
||||||
|
Domain: funkwhale.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: music library to /opt/data/funkwhale/music, db to /opt/data/funkwhale/db, data to /opt/stacks/funkwhale/data
|
||||||
|
Needs SMTP env vars
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
|
||||||
|
**jellyfin**
|
||||||
|
Official: https://github.com/jellyfin/jellyfin
|
||||||
|
Media server
|
||||||
|
Image: jellyfin/jellyfin
|
||||||
|
Domain: jellyfin.$domain
|
||||||
|
Internal port: 8096
|
||||||
|
Data: config to /opt/stacks/jellyfin/config, cache to /opt/stacks/jellyfin/cache, media library at /opt/data/jellyfin/media
|
||||||
|
If hardware acceleration is available add: devices: /dev/dri:/dev/dri
|
||||||
|
|
||||||
|
**matrix**
|
||||||
|
Official: https://github.com/continuwuity/continuwuity
|
||||||
|
Matrix homeserver - MUST use continuwuity, not Synapse
|
||||||
|
Image: ghcr.io/continuwuity/continuwuity
|
||||||
|
Domain: matrix.$domain
|
||||||
|
Internal port: 6167
|
||||||
|
Data: /opt/data/matrix
|
||||||
|
Note: This is the continuwuity (formerly conduwuit) Rust Matrix server. Do not substitute Synapse or Dendrite under any circumstances.
|
||||||
|
|
||||||
|
**movim**
|
||||||
|
Official: https://github.com/movim/movim
|
||||||
|
XMPP-based social platform
|
||||||
|
Image: movim/movim
|
||||||
|
Domain: movim.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: /opt/stacks/movim/data
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**nostr**
|
||||||
|
Official: https://github.com/scsibug/nostr-rs-relay (mirror; primary at https://sr.ht/~gheartsfield/nostr-rs-relay)
|
||||||
|
Nostr relay
|
||||||
|
Image: scsibug/nostr-rs-relay
|
||||||
|
Domain: nostr.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/nostr/data
|
||||||
|
|
||||||
|
**peertube**
|
||||||
|
Official: https://github.com/Chocobozzz/PeerTube
|
||||||
|
Federated video hosting
|
||||||
|
Image: chocobozzz/peertube
|
||||||
|
Domain: peertube.$domain
|
||||||
|
Internal port: 9000
|
||||||
|
Data: videos and large files to /opt/data/peertube, config to /opt/stacks/peertube/config
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**privatebin**
|
||||||
|
Official: https://github.com/PrivateBin/PrivateBin
|
||||||
|
Encrypted pastebin
|
||||||
|
Image: privatebin/nginx-fpm-alpine
|
||||||
|
Domain: privatebin.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/privatebin/data
|
||||||
|
|
||||||
|
**rimgo**
|
||||||
|
Official: https://codeberg.org/rimgo/rimgo
|
||||||
|
Privacy-respecting Imgur frontend
|
||||||
|
Image: codeberg.org/rimgo/rimgo
|
||||||
|
Domain: rimgo.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
**searxng**
|
||||||
|
Official: https://github.com/searxng/searxng
|
||||||
|
Privacy-respecting metasearch engine
|
||||||
|
Image: searxng/searxng
|
||||||
|
Domain: searxng.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/searxng/data
|
||||||
|
Requires Redis sidecar
|
||||||
|
|
||||||
|
**tdarr**
|
||||||
|
Official: https://github.com/HaveAGitGat/Tdarr
|
||||||
|
Media transcoding automation
|
||||||
|
Image: ghcr.io/haveagitgat/tdarr
|
||||||
|
Domain: tdarr.$domain
|
||||||
|
Internal port: 8265
|
||||||
|
Data: config and logs to /opt/stacks/tdarr/data, media at /opt/data/tdarr
|
||||||
|
Also deploy tdarr-node sidecar in same compose
|
||||||
|
|
||||||
|
**arrs**
|
||||||
|
Official: Radarr https://github.com/Radarr/Radarr, Sonarr https://github.com/Sonarr/Sonarr, etc. (each *arr has own repo)
|
||||||
|
*arr media automation suite - this is one stack containing all of: Radarr, Sonarr, Lidarr, Readarr, Prowlarr, Bazarr
|
||||||
|
Domains: radarr.$domain, sonarr.$domain, lidarr.$domain, readarr.$domain, prowlarr.$domain, bazarr.$domain
|
||||||
|
Each service gets its own Traefik router label with its own subdomain
|
||||||
|
Data: each arr's config goes to /opt/stacks/arrs/<arrname>/config, shared downloads at /opt/data/arrs/downloads, shared media at /opt/data/arrs/media
|
||||||
|
|
||||||
|
**feishin**
|
||||||
|
Official: https://github.com/jeffvli/feishin
|
||||||
|
Music player web UI (Navidrome/Jellyfin frontend)
|
||||||
|
Image: ghcr.io/jeffvli/feishin
|
||||||
|
Domain: feishin.$domain
|
||||||
|
Internal port: 9180
|
||||||
|
Data: /opt/stacks/feishin/config
|
||||||
|
|
||||||
|
**gemini**
|
||||||
|
Official: Varies (e.g. https://sr.ht/~sircmpwn/gmnisrv or similar)
|
||||||
|
Gemini protocol server
|
||||||
|
Image: a-gemini-server image such as gemini-server or gmnisrv
|
||||||
|
Domain: gemini.$domain
|
||||||
|
Internal port: 1965
|
||||||
|
Note: Gemini uses its own protocol, expose port 1965 to host directly as Traefik does not proxy Gemini. Use host port 1965.
|
||||||
|
Data: /opt/stacks/gemini/data
|
||||||
|
|
||||||
|
**jitsi**
|
||||||
|
Official: https://github.com/jitsi
|
||||||
|
Video conferencing
|
||||||
|
Image: jitsi/web, jitsi/prosody, jitsi/jicofo, jitsi/jvb (full stack)
|
||||||
|
Domain: jitsi.$domain
|
||||||
|
Internal port: 80 on web container
|
||||||
|
Note: JVB requires UDP port 10000 exposed to host for media. Add to compose: ports: - "10000:10000/udp"
|
||||||
|
Data: config to /opt/stacks/jitsi/config
|
||||||
|
|
||||||
|
**navidrome**
|
||||||
|
Official: https://github.com/navidrome/navidrome
|
||||||
|
Music streaming server
|
||||||
|
Image: deluan/navidrome
|
||||||
|
Domain: navidrome.$domain
|
||||||
|
Internal port: 4533
|
||||||
|
Data: config to /opt/stacks/navidrome/data, music library at /opt/data/navidrome/music
|
||||||
|
|
||||||
|
**npm**
|
||||||
|
Official: https://github.com/jc21/nginx-proxy-manager
|
||||||
|
Nginx Proxy Manager
|
||||||
|
Image: jc21/nginx-proxy-manager
|
||||||
|
Domain: npm.$domain
|
||||||
|
Internal port: 81 (admin UI)
|
||||||
|
Note: This coexists with Traefik. Only manage non-Traefik routes through this. Do not conflict with Traefik on ports 80/443.
|
||||||
|
Data: /opt/stacks/npm/data, /opt/stacks/npm/letsencrypt
|
||||||
|
|
||||||
|
**piped-docker**
|
||||||
|
Official: https://github.com/TeamPiped/Piped
|
||||||
|
Privacy-respecting YouTube frontend. This is the canonical stack. Do not deploy a separate piped stack.
|
||||||
|
Image: uses multiple containers: piped-backend, piped-frontend, piped-proxy
|
||||||
|
Domain: piped.$domain (frontend), pipedapi.$domain (backend), pipedproxy.$domain (proxy)
|
||||||
|
Each container gets its own Traefik router label and subdomain
|
||||||
|
Data: /opt/stacks/piped-docker/data
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
|
||||||
|
**poke**
|
||||||
|
Official: Invidious https://github.com/iv-org/invidious (poke bundled)
|
||||||
|
Poke service bundled with Invidious. Deploy once only.
|
||||||
|
Note: This stack MUST contain both the poke container AND an Invidious container. They are deployed together in one compose file.
|
||||||
|
Poke domain: poke.$domain
|
||||||
|
Invidious domain: invidious.$domain
|
||||||
|
Poke internal port: 8080
|
||||||
|
Invidious internal port: 3000
|
||||||
|
Each gets its own Traefik router label
|
||||||
|
Invidious image: quay.io/invidious/invidious
|
||||||
|
Data: /opt/stacks/poke/data for poke config, /opt/stacks/poke/invidious for Invidious config
|
||||||
|
Invidious requires PostgreSQL sidecar in the same compose
|
||||||
|
|
||||||
|
**pyfedi**
|
||||||
|
Official: Varies (likely Mbin https://github.com/MbinOrg/mbin)
|
||||||
|
Python-based Fediverse server (likely Mbin or similar)
|
||||||
|
Image: pytholabs/pyfedi or mbin/mbin
|
||||||
|
Domain: pyfedi.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: /opt/stacks/pyfedi/data
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**rocketchat**
|
||||||
|
Official: https://github.com/RocketChat/Rocket.Chat
|
||||||
|
Team chat
|
||||||
|
Image: rocket.chat
|
||||||
|
Domain: rocketchat.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/data/rocketchat
|
||||||
|
Requires MongoDB sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**soularr**
|
||||||
|
Official: https://github.com/mrusse/soularr
|
||||||
|
Lidarr and Slskd integration bridge
|
||||||
|
Image: mrusse08/soularr
|
||||||
|
Domain: soularr.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: /opt/stacks/soularr/data
|
||||||
|
|
||||||
|
**transmission**
|
||||||
|
Official: https://github.com/transmission/transmission
|
||||||
|
BitTorrent client
|
||||||
|
Image: linuxserver/transmission
|
||||||
|
Domain: transmission.$domain
|
||||||
|
Internal port: 9091
|
||||||
|
Data: config to /opt/stacks/transmission/config, downloads at /opt/data/transmission/downloads
|
||||||
|
Peer port 51413 must be exposed to host on both TCP and UDP
|
||||||
|
|
||||||
|
**bytestash**
|
||||||
|
Official: https://github.com/jorenn92/bytestash
|
||||||
|
Code/text snippet storage
|
||||||
|
Image: jorenn92/bytestash or ghcr.io equivalent
|
||||||
|
Domain: bytestash.$domain
|
||||||
|
Internal port: 5000
|
||||||
|
Data: /opt/stacks/bytestash/data
|
||||||
|
|
||||||
|
**fluxer**
|
||||||
|
Official: Unknown
|
||||||
|
Image unknown. Flag for manual review and skip fresh deploy.
|
||||||
|
Domain: fluxer.$domain
|
||||||
|
Data: /opt/stacks/fluxer/data
|
||||||
|
|
||||||
|
**hedgedoc**
|
||||||
|
Official: https://github.com/hedgedoc/hedgedoc
|
||||||
|
Collaborative markdown editor
|
||||||
|
Image: quay.io/hedgedoc/hedgedoc
|
||||||
|
Domain: hedgedoc.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/stacks/hedgedoc/data, uploads to /opt/stacks/hedgedoc/uploads
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**lemmy**
|
||||||
|
Official: https://github.com/LemmyNet/lemmy
|
||||||
|
Federated link aggregator
|
||||||
|
Image: dessalines/lemmy + dessalines/lemmy-ui
|
||||||
|
Domain: lemmy.$domain
|
||||||
|
Internal port: 8536 (backend), lemmy-ui on 1234
|
||||||
|
Traefik routes to lemmy-ui on 1234
|
||||||
|
Data: config to /opt/stacks/lemmy/config, db to /opt/data/lemmy/db, images to /opt/data/lemmy/pictrs
|
||||||
|
Requires PostgreSQL and pict-rs sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**mirotalk**
|
||||||
|
Official: https://github.com/miroslavpejic85/mirotalk
|
||||||
|
WebRTC video calling
|
||||||
|
Image: mirotalk/mirotalk-p2p or mirotalk/mirotalk-sfu
|
||||||
|
Domain: mirotalk.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/stacks/mirotalk/data
|
||||||
|
|
||||||
|
**nextcloud**
|
||||||
|
Official: https://github.com/nextcloud/server
|
||||||
|
File sync and collaboration suite
|
||||||
|
Image: nextcloud:apache or nextcloud:fpm with nginx sidecar
|
||||||
|
Domain: nextcloud.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: nextcloud data to /opt/data/nextcloud/data, apps and config to /opt/stacks/nextcloud/html, db to /opt/data/nextcloud/db
|
||||||
|
Requires PostgreSQL and Redis sidecars
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**openspeedtest**
|
||||||
|
Official: https://github.com/libre-devie/openspeedtest
|
||||||
|
Network speed test
|
||||||
|
Image: openspeedtest/speed-test
|
||||||
|
Domain: openspeedtest.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
**quetre**
|
||||||
|
Official: https://github.com/Quetre/Quetre
|
||||||
|
Privacy-respecting Quora frontend
|
||||||
|
Image: quetre/quetre
|
||||||
|
Domain: quetre.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
**romm**
|
||||||
|
Official: https://github.com/zurdi15/romm
|
||||||
|
ROM management and game library
|
||||||
|
Image: rommapp/romm
|
||||||
|
Domain: romm.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: config to /opt/stacks/romm/config, roms at /opt/data/romm/library, db to /opt/data/romm/db
|
||||||
|
Requires MariaDB sidecar
|
||||||
|
|
||||||
|
**spacebar**
|
||||||
|
Official: https://github.com/spacebarchat/server
|
||||||
|
Open source Discord-compatible server (formerly Fosscord)
|
||||||
|
Image: spacebar/server
|
||||||
|
Domain: spacebar.$domain
|
||||||
|
Internal port: 3001
|
||||||
|
Data: /opt/stacks/spacebar/data
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**uptime-kuma**
|
||||||
|
Official: https://github.com/louislam/uptime-kuma
|
||||||
|
Service uptime monitoring
|
||||||
|
Image: louislam/uptime-kuma
|
||||||
|
Domain: uptime.$domain
|
||||||
|
Internal port: 3001
|
||||||
|
Data: /opt/stacks/uptime-kuma/data
|
||||||
|
|
||||||
|
**dumb**
|
||||||
|
Official: https://github.com/rramiachraf/dumb
|
||||||
|
Privacy-respecting Genius lyrics frontend
|
||||||
|
Image: rramiachraf/dumb
|
||||||
|
Domain: dumb.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
**freshrss**
|
||||||
|
Official: https://github.com/FreshRSS/FreshRSS
|
||||||
|
RSS feed aggregator
|
||||||
|
Image: freshrss/freshrss
|
||||||
|
Domain: freshrss.$domain
|
||||||
|
Internal port: 80
|
||||||
|
Data: /opt/stacks/freshrss/data, extensions to /opt/stacks/freshrss/extensions
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**httpserver**
|
||||||
|
Official: Varies (e.g. https://github.com/halverneus/static-file-server)
|
||||||
|
Simple static file HTTP server
|
||||||
|
Image: halverneus/static-file-server or nginx:alpine
|
||||||
|
Domain: httpserver.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: served files at /opt/stacks/httpserver/www
|
||||||
|
|
||||||
|
**linkwarden**
|
||||||
|
Official: https://github.com/linkwarden/linkwarden
|
||||||
|
Bookmark and link archiving
|
||||||
|
Image: ghcr.io/linkwarden/linkwarden
|
||||||
|
Domain: linkwarden.$domain
|
||||||
|
Internal port: 3000
|
||||||
|
Data: /opt/stacks/linkwarden/data
|
||||||
|
Requires PostgreSQL sidecar
|
||||||
|
Needs SMTP env vars
|
||||||
|
|
||||||
|
**monitoring**
|
||||||
|
Official: Prometheus https://github.com/prometheus/prometheus, Grafana https://github.com/grafana/grafana
|
||||||
|
Metrics stack - deploy Prometheus and Grafana together in this one compose
|
||||||
|
Prometheus domain: prometheus.$domain
|
||||||
|
Grafana domain: grafana.$domain
|
||||||
|
Prometheus internal port: 9090, Grafana internal port: 3000
|
||||||
|
Each gets its own Traefik router label
|
||||||
|
Data: Prometheus data to /opt/data/monitoring/prometheus, Grafana data to /opt/stacks/monitoring/grafana
|
||||||
|
Prometheus config at /opt/stacks/monitoring/prometheus.yml
|
||||||
|
|
||||||
|
**node-exporter**
|
||||||
|
Official: https://github.com/prometheus/node_exporter
|
||||||
|
Prometheus system metrics exporter
|
||||||
|
Image: prom/node-exporter
|
||||||
|
Domain: node-exporter.$domain
|
||||||
|
Internal port: 9100
|
||||||
|
Note: Must mount host /proc, /sys, /rootfs as read-only. Standard node-exporter mounts: /proc:/host/proc:ro /sys:/host/sys:ro /:/rootfs:ro
|
||||||
|
Add flags: --path.procfs=/host/proc --path.sysfs=/host/sys --collector.filesystem.mount-points-exclude=...
|
||||||
|
|
||||||
|
**p2pool**
|
||||||
|
Official: https://github.com/SChernykh/p2pool
|
||||||
|
Decentralized Monero mining pool
|
||||||
|
Image: sethsimmons/p2pool
|
||||||
|
Domain: p2pool.$domain
|
||||||
|
Internal port: 3333
|
||||||
|
Note: p2pool requires Monero daemon. Include monerod sidecar in same compose.
|
||||||
|
Data: /opt/data/p2pool for blockchain, /opt/stacks/p2pool/config for p2pool config
|
||||||
|
Monerod data at /opt/data/p2pool/monero
|
||||||
|
|
||||||
|
**redlib**
|
||||||
|
Official: https://github.com/redlib-org/redlib
|
||||||
|
Privacy-respecting Reddit frontend (formerly Libreddit)
|
||||||
|
Image: quay.io/redlib/redlib
|
||||||
|
Domain: redlib.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: none needed
|
||||||
|
|
||||||
|
**sabnzbd**
|
||||||
|
Official: https://github.com/sabnzbd/sabnzbd
|
||||||
|
Usenet downloader
|
||||||
|
Image: linuxserver/sabnzbd
|
||||||
|
Domain: sabnzbd.$domain
|
||||||
|
Internal port: 8080
|
||||||
|
Data: config to /opt/stacks/sabnzbd/config, downloads at /opt/data/sabnzbd/downloads, incomplete at /opt/data/sabnzbd/incomplete
|
||||||
|
|
||||||
|
**stoat**
|
||||||
|
Official: Unknown
|
||||||
|
Image is unidentifiable, flag for manual review and skip.
|
||||||
|
Domain: stoat.$domain
|
||||||
|
Data: /opt/stacks/stoat/data
|
||||||
|
|
||||||
|
**wizarr**
|
||||||
|
Official: https://github.com/Wizarrrr/wizarr
|
||||||
|
User invitation and onboarding portal for Jellyfin/Plex/Emby
|
||||||
|
Image: ghcr.io/wizarrrr/wizarr
|
||||||
|
Domain: wizarr.$domain
|
||||||
|
Internal port: 5690
|
||||||
|
Data: /opt/stacks/wizarr/data
|
||||||
|
|
||||||
|
EXECUTION ORDER
|
||||||
|
For each stack in the list above:
|
||||||
|
|
||||||
|
Write a fresh compose from scratch using all global rules and the stack-specific notes above
|
||||||
|
Bring up with: cd /opt/stacks/<service> && docker compose up -d
|
||||||
|
Wait 20 seconds and check: docker compose ps
|
||||||
|
If all containers show status Up or healthy, mark as FRESH
|
||||||
|
If any container is in state Exit, Restarting, or Error, mark as FAILED and log the error
|
||||||
|
Flag stacks with unknown images (fluxer, stoat) as SKIPPED
|
||||||
|
|
||||||
|
FINAL REPORT
|
||||||
|
After all stacks are processed, print a summary table with columns:
|
||||||
|
Stack | Result (FRESH/FAILED/SKIPPED) | URL | Notes
|
||||||
|
Flag any stacks that need manual review.
|
||||||
|
Flag any stacks where the image could not be confirmed.
|
||||||
|
List any stacks that require DNS records to be added for their subdomains.
|
||||||
|
Save to "Deployment_Report.md"
|
||||||
|
POST-DEPLOY VERIFICATION
|
||||||
|
Step 1: After all stacks are deployed, run a full status sweep.
|
||||||
|
Run: for dir in /opt/stacks/*/; do echo "=== dir===";dockercompose−f"dir ==="; docker compose -f "
|
||||||
|
dir===";dockercompose−f"dir/docker-compose.yml" ps 2>/dev/null || echo "no compose file"; done
|
||||||
|
|
||||||
|
Step 2: Check for any containers in restart loops.
|
||||||
|
Run: docker ps --filter "status=restarting"
|
||||||
|
For each restarting container, run: docker logs <container_name> --tail 50
|
||||||
|
Attempt to resolve, or flag for operator review.
|
||||||
|
Step 3: Verify Traefik is routing correctly.
|
||||||
|
For each service with a web UI, confirm the subdomain resolves and returns a valid HTTP response.
|
||||||
|
Run: curl -sk https://<service>.$domain | head -20
|
||||||
|
A non-empty response that is not a Traefik 404 indicates successful routing.
|
||||||
|
Step 4: Report final status to operator.
|
||||||
|
Produce a summary table with columns: service | status (FRESH / FAILED / SKIPPED) | url | notes
|
||||||
|
List any services skipped due to unknown image or unresolvable errors.
|
||||||
|
List any services that need manual post-deploy configuration (admin password changes, initial setup wizards, API keys).
|
||||||
|
AGENT NOTES
|
||||||
|
|
||||||
|
Never hardcode passwords in compose files. All secrets go in .env and are referenced as ${VAR_NAME}.
|
||||||
|
Fish shell is the interactive shell on this server. If you need to write a script, use bash and note the Fish incompatibilities. Do not use Fish-specific syntax in scripts intended to run non-interactively.
|
||||||
|
The domain for all services is $domain. Substitute this wherever you see $hostdomain or $HOSTDOMAIN.
|
||||||
|
Source ~/.secrets at the start of every new shell session before running compose commands that need credentials.
|
||||||
|
All xxhsum commands use the default algorithm. Be consistent across all secret generation.
|
||||||
|
When in doubt about an image tag, pull latest and check: docker inspect <image> | grep -i version
|
||||||
|
Share
|
||||||
Reference in New Issue
Block a user