Compare commits

...

40 Commits

Author SHA1 Message Date
90273fbbca feat(netlab): add MOTD configuration 2026-01-11 19:01:23 +00:00
13d97ff627 feat(proxy): add MOTD configuration 2026-01-11 19:00:39 +00:00
36e40211f6 refactor(netlab): use robust awk-based Tailscale FQDN extraction from seedbox 2026-01-11 18:51:40 +00:00
4935ae3c3e refactor(proxy): use robust awk-based Tailscale FQDN extraction from seedbox 2026-01-11 18:51:01 +00:00
52d7d024e5 refactor(netlab): align with proxy script - use Tailscale DNS hostname 2026-01-11 18:49:01 +00:00
727a29fed4 fix(sonarr): add missing serve.json for Tailscale proxy
All checks were successful
Deploy Seedbox / Deploy Seedbox Stacks (push) Successful in 35s
2026-01-04 17:38:37 +00:00
f9ce62d296 Update seedbox/install.sh
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Has been cancelled
2026-01-04 17:16:03 +00:00
bca52e345f Update seedbox/install.sh
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Has been cancelled
2026-01-04 17:01:10 +00:00
8680397c08 fix(ci): use rsync to sync only stacks/ without polluting /srv/seedbox
- Clone repo to temp directory
- Rsync only seedbox/stacks/ to /srv/seedbox/stacks/
- Preserve .env and volume data directories
2026-01-04 16:59:22 +00:00
4576df0773 fix(seedbox): simplify git clone with sparse checkout
All checks were successful
Deploy Seedbox / Deploy Seedbox Stacks (push) Successful in 36s
Only clone seedbox/ subdirectory into /srv/seedbox using git sparse-checkout
2026-01-04 16:28:00 +00:00
41f3f7224c Update README.md 2026-01-04 16:09:15 +00:00
036fd8b714 Update seedbox/install.sh
All checks were successful
Deploy Seedbox / Deploy Seedbox Stacks (push) Successful in 1m7s
2026-01-04 16:05:49 +00:00
69753e52e5 fix(seedbox): add --accept-routes and secure SSH via Tailnet only
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 27s
- Add --accept-routes flag to tailscale up for subnet routing
- Schedule SSH lockdown via 'at' to avoid cutting access during setup
- SSH will only be accessible via Tailscale interface after 2 minutes
2026-01-04 16:03:25 +00:00
7ccfa58dc1 fix(seedbox): disable Tailscale SSH management for CI compatibility
All checks were successful
Deploy Seedbox / Deploy Seedbox Stacks (push) Successful in 1m0s
- Remove --ssh flag from tailscale up
- Remove temporary SSH port 22 opening (not needed anymore)
- SSH now uses standard auth via Tailscale network
- CI can connect without Tailscale SSH check prompts
2026-01-04 15:58:55 +00:00
c7155d0555 feat(ci): add workflow_dispatch for manual trigger
Allows manual execution from Gitea Actions UI
2026-01-04 15:49:02 +00:00
97dbe32354 fix(ci): replace actions/checkout with git clone for Alpine compatibility
Alpine doesn't have Node.js by default, and actions/checkout requires it.
Using native git clone instead - simpler and KISS approach.
2026-01-04 15:13:54 +00:00
eb0720d0b1 fix(seedbox): update Sonarr README with correct /downloads path
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 3s
2026-01-04 13:46:51 +00:00
cc5b9be116 fix(seedbox): update Transmission README with correct /downloads path
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 3s
2026-01-04 13:46:32 +00:00
09da2f514b fix(seedbox): update README to reflect /downloads mount point
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 3s
- Storage section now shows /downloads (local RAID - 3.4T)
- Updated directory structure
- Fixed post-install verification commands
2026-01-04 13:44:52 +00:00
8237a425b1 fix(seedbox): update install.sh to reflect /downloads mount point
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 3s
- Remove /srv/seedbox/downloads creation (using /downloads RAID)
- Update MOTD to show correct storage paths
- Update final summary
2026-01-04 13:43:58 +00:00
3f23f418ea fix(seedbox): use /downloads mount point for Sonarr
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 3s
2026-01-04 13:43:06 +00:00
d5edc12e65 fix(seedbox): use /downloads mount point instead of /srv/seedbox/downloads
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 3s
2026-01-04 13:42:56 +00:00
7eaba56b44 ci(seedbox): remove misplaced workflow file
Some checks failed
Deploy Seedbox / Deploy Seedbox Stacks (push) Failing after 4s
Workflow moved to .gitea/workflows/deploy-seedbox.yml at repo root
2026-01-04 13:39:15 +00:00
0a9132b669 ci(seedbox): move workflow to repo root for Gitea discovery
Gitea only looks for workflows in .gitea/workflows/ at repository root
2026-01-04 13:38:54 +00:00
49d16f886e Merge pull request 'feat(seedbox): Docker + Tailscale sidecar architecture' (#1) from feature/seedbox-docker-tailscale into main
Reviewed-on: #1
2026-01-04 13:29:48 +00:00
3a39b7c4f2 docs(seedbox): add README for Sonarr stack 2026-01-04 12:30:29 +00:00
5f13ee47df docs(seedbox): add README for Prowlarr stack 2026-01-04 12:30:18 +00:00
90dfc9ec8a docs(seedbox): add README for Portainer stack 2026-01-04 12:30:08 +00:00
be0f6abaa2 docs(seedbox): add README for Transmission stack 2026-01-04 12:30:00 +00:00
979040a4c7 docs(seedbox): complete README rewrite for new architecture
- Document Docker + Tailscale sidecar architecture
- Add Gitea secrets configuration guide
- Add service management (add/remove/update)
- Add troubleshooting section
- Add Tailscale ACL configuration
2026-01-04 12:29:49 +00:00
1935d7fc99 feat(seedbox): add Sonarr stack with Tailscale sidecar
- TV series management
- Accessible via sonarr.taila5ad8.ts.net (HTTPS only)
- Mounts downloads and media volumes
2026-01-04 12:24:43 +00:00
6e13516d71 feat(seedbox): add Tailscale Serve config for Prowlarr 2026-01-04 12:24:37 +00:00
5928b5e990 feat(seedbox): add Prowlarr stack with Tailscale sidecar
- Indexer manager for *arr apps
- Accessible via prowlarr.taila5ad8.ts.net (HTTPS only)
2026-01-04 12:24:24 +00:00
4fe08a130b feat(seedbox): add Tailscale Serve config for Portainer 2026-01-04 12:24:18 +00:00
4473779d55 feat(seedbox): add Portainer stack with Tailscale sidecar
- Accessible via portainer.taila5ad8.ts.net (HTTPS only)
- Docker socket mounted for container management
2026-01-04 12:24:07 +00:00
333aa103c2 feat(seedbox): add Tailscale Serve config for Transmission 2026-01-04 12:24:00 +00:00
86441327b9 feat(seedbox): add Transmission stack with Tailscale sidecar
- WebUI accessible via transmission.taila5ad8.ts.net (HTTPS)
- Peer port 51413 exposed publicly for seeding
- Local downloads + NFS media mount
2026-01-04 12:23:54 +00:00
9afaf880a9 ci(seedbox): add Gitea Actions deployment pipeline
- Deploy on push to main and PR validation
- SSH deployment to seedbox via Tailscale
- Inject secrets from Gitea
- Deploy all stacks in stacks/ directory
2026-01-04 12:23:44 +00:00
c601e35930 docs(seedbox): add .env.example with secrets documentation 2026-01-04 12:23:25 +00:00
d137383936 refactor(seedbox): rewrite install.sh for Docker + Tailscale architecture
- Remove Transmission deployment (now via Gitea Actions)
- Add git clone for repo structure
- Simplify to server preparation only
- Keep NFS mount configuration
- Update MOTD for new architecture
2026-01-04 12:23:15 +00:00
19 changed files with 1142 additions and 329 deletions

View File

@@ -0,0 +1,103 @@
name: Deploy Seedbox
on:
push:
branches: [main]
paths:
- 'seedbox/**'
pull_request:
branches: [main]
paths:
- 'seedbox/**'
workflow_dispatch:
jobs:
deploy:
name: Deploy Seedbox Stacks
runs-on: self-hosted
container:
image: alpine:latest
steps:
- name: Install dependencies
run: apk add --no-cache openssh-client git
- name: Checkout repository
run: |
git clone --depth 1 --branch main https://gitea.arnodo.fr/Damien/infra-scripts.git .
- name: Setup SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SEEDBOX_SSH_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H seedbox.taila5ad8.ts.net >> ~/.ssh/known_hosts 2>/dev/null || true
- name: Validate compose files (PR only)
if: github.event_name == 'pull_request'
run: |
echo "Validating docker-compose files..."
for stack in seedbox/stacks/*/; do
if [ -f "${stack}docker-compose.yml" ]; then
echo "✓ ${stack}docker-compose.yml exists"
fi
done
echo "Validation complete."
- name: Deploy to seedbox
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
run: |
ssh -o StrictHostKeyChecking=accept-new debian@seedbox.taila5ad8.ts.net << 'ENDSSH'
set -e
cd /srv/seedbox
echo "=== Syncing stacks from repository ==="
# Clone to temp directory and sync only seedbox/ content
TEMP_DIR=$(mktemp -d)
git clone --depth 1 --branch main https://gitea.arnodo.fr/Damien/infra-scripts.git "$TEMP_DIR"
# Sync stacks directory (preserve .env and volumes)
rsync -av --delete \
--exclude='.env' \
--exclude='*/data/' \
--exclude='*/state/' \
"$TEMP_DIR/seedbox/stacks/" /srv/seedbox/stacks/
# Clean up temp directory
rm -rf "$TEMP_DIR"
echo "=== Creating .env file ==="
cat > .env << 'ENVEOF'
TS_AUTHKEY=${{ secrets.TS_AUTHKEY }}
TRANSMISSION_USER=${{ secrets.TRANSMISSION_USER }}
TRANSMISSION_PASS=${{ secrets.TRANSMISSION_PASS }}
ENVEOF
chmod 600 .env
echo "=== Deploying stacks ==="
for stack in stacks/*/; do
if [ -f "${stack}docker-compose.yml" ]; then
stack_name=$(basename "$stack")
echo "Deploying ${stack_name}..."
docker compose -f "${stack}docker-compose.yml" --env-file .env pull
docker compose -f "${stack}docker-compose.yml" --env-file .env up -d --remove-orphans
fi
done
echo "=== Cleanup unused images ==="
docker image prune -f
echo "=== Current status ==="
docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
ENDSSH
- name: Deployment summary
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
run: |
echo "✅ Deployment complete!"
echo ""
echo "Services should be available at:"
echo " • transmission.taila5ad8.ts.net"
echo " • portainer.taila5ad8.ts.net"
echo " • prowlarr.taila5ad8.ts.net"
echo " • sonarr.taila5ad8.ts.net"

View File

@@ -20,10 +20,6 @@ These scripts automate the deployment of personal infrastructure components. The
## Requirements ## Requirements
- Fresh Debian 11/12 installation - Fresh Debian 12/13 installation
- User with sudo privileges (do not run as root) - User with sudo privileges (do not run as root)
- Internet access - Internet access
## License
MIT

View File

@@ -107,8 +107,52 @@ EOF
log_warn " sudo ufw delete allow 22/tcp" log_warn " sudo ufw delete allow 22/tcp"
} }
# Get Tailscale IP for final message # Configure MOTD
TS_IP=$(tailscale ip -4) log_info "Configuring MOTD..."
sudo chmod -x /etc/update-motd.d/* 2>/dev/null || true
cat << 'MOTD' | sudo tee /etc/update-motd.d/00-netlab > /dev/null
#!/bin/bash
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
')
[[ -z "$TS_FQDN" ]] && TS_FQDN="$(hostname).ts.net"
# Get configured SSH port from sshd config
SSH_PORT=$(grep -h "^Port " /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk '{print $2}' | head -1)
[[ -z "$SSH_PORT" ]] && SSH_PORT="22"
echo ""
echo " _ _ _____ _____ _ _ ____"
echo "| \ | | ____|_ _| | / \ | __ )"
echo "| \| | _| | | | | / _ \ | _ \\"
echo "| |\ | |___ | | | |___ / ___ \| |_) |"
echo "|_| \_|_____| |_| |_____/_/ \_\____/"
echo ""
echo "ContainerLab Network Lab Server"
echo "─────────────────────────────────────────"
echo "Access:"
echo " • SSH (public) : port ${SSH_PORT}"
echo " • SSH (Tailscale) : ${TS_FQDN}"
echo ""
echo "Labs:"
containerlab inspect --all 2>/dev/null | head -20 || echo " No labs running"
echo ""
echo "Useful commands:"
echo " containerlab deploy -t <topology>.clab.yml"
echo " containerlab inspect --all"
echo " containerlab destroy -t <topology>.clab.yml"
echo "─────────────────────────────────────────"
echo ""
MOTD
sudo chmod +x /etc/update-motd.d/00-netlab
# Get Tailscale hostname for display
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
' || echo "${HOSTNAME}.ts.net")
echo "" echo ""
log_info "==========================================" log_info "=========================================="
@@ -117,7 +161,7 @@ EOF
echo "" echo ""
echo "Access:" echo "Access:"
echo " - Public SSH: ssh -p ${SSH_PORT} ${USER}@<public-ip>" echo " - Public SSH: ssh -p ${SSH_PORT} ${USER}@<public-ip>"
echo " - Tailscale SSH: ssh ${USER}@${TS_IP} (or use Tailscale SSH)" echo " - Tailscale SSH: ssh ${USER}@${TS_FQDN} (or use Tailscale SSH)"
echo "" echo ""
echo "ContainerLab is ready. Example usage:" echo "ContainerLab is ready. Example usage:"
echo " containerlab deploy -t mylab.clab.yml" echo " containerlab deploy -t mylab.clab.yml"

View File

@@ -118,15 +118,54 @@ EOF
log_info "Exposing NPM admin panel via Tailscale..." log_info "Exposing NPM admin panel via Tailscale..."
sudo tailscale serve --bg http://localhost:81 sudo tailscale serve --bg http://localhost:81
# Get Tailscale hostname for final message # Configure MOTD
TS_HOSTNAME=$(tailscale status --json | grep -o '"DNSName":"[^"]*' | head -1 | cut -d'"' -f4 | sed 's/\.$//') log_info "Configuring MOTD..."
sudo chmod -x /etc/update-motd.d/* 2>/dev/null || true
cat << 'MOTD' | sudo tee /etc/update-motd.d/00-proxy > /dev/null
#!/bin/bash
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
')
[[ -z "$TS_FQDN" ]] && TS_FQDN="$(hostname).ts.net"
echo ""
echo " ____ ____ _____ ____ __"
echo "| _ \| _ \ / _ \ \/ /\ \ / /"
echo "| |_) | |_) | | | \ / \ V /"
echo "| __/| _ <| |_| / \ | |"
echo "|_| |_| \_\\\\___/_/\_\ |_|"
echo ""
echo "Nginx Proxy Manager Server"
echo "─────────────────────────────────────────"
echo "Access:"
echo " • Admin panel : https://${TS_FQDN} (Tailscale)"
echo " • HTTP/HTTPS : Public ports 80/443"
echo ""
echo "Services:"
docker ps --format ' • {{.Names}} : {{.Status}}' 2>/dev/null || echo " Docker not running"
echo ""
echo "Useful commands:"
echo " cd ~/npm && docker compose logs -f"
echo " sudo tailscale serve status"
echo "─────────────────────────────────────────"
echo ""
MOTD
sudo chmod +x /etc/update-motd.d/00-proxy
# Get Tailscale hostname for display
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
' || echo "${HOSTNAME}.ts.net")
echo "" echo ""
log_info "==========================================" log_info "=========================================="
log_info "Deployment complete!" log_info "Deployment complete!"
log_info "==========================================" log_info "=========================================="
echo "" echo ""
echo "Access NPM admin panel at: https://${TS_HOSTNAME}" echo "Access NPM admin panel at: https://${TS_FQDN}"
echo "Default login: admin@example.com / changeme" echo "Default login: admin@example.com / changeme"
echo "" echo ""
echo "Note: Approve exit-node in Tailscale admin console if needed" echo "Note: Approve exit-node in Tailscale admin console if needed"

20
seedbox/.env.example Normal file
View File

@@ -0,0 +1,20 @@
# Gitea secrets required for seedbox deployment
#
# Configure these in: Gitea > Repository > Settings > Secrets and Variables > Actions
#
# TS_AUTHKEY - Tailscale OAuth client secret (recommended) or auth key
# Create at: https://login.tailscale.com/admin/settings/oauth
# Required scopes: devices:write
# Format: tskey-client-xxxxx-yyyyyyyy
#
# SEEDBOX_SSH_KEY - SSH private key for deployment
# Generate with: ssh-keygen -t ed25519 -f seedbox-deploy -N ""
# Add public key to seedbox: ~/.ssh/authorized_keys
#
# TRANSMISSION_USER - Transmission WebUI username (default: admin)
# TRANSMISSION_PASS - Transmission WebUI password
# Example .env file (DO NOT COMMIT WITH REAL VALUES)
TS_AUTHKEY=tskey-client-xxxxx-yyyyyyyy
TRANSMISSION_USER=admin
TRANSMISSION_PASS=changeme

View File

@@ -1,214 +1,351 @@
# Seedbox Server # Seedbox Server
Deploys a seedbox with Transmission for maintaining Linux ISO mirrors and OS images. Docker-based seedbox with Tailscale integration. Each service runs in its own container with a Tailscale sidecar for secure HTTPS access via your tailnet.
## Architecture
```
┌─────────────────────────────────────────────────────────────────┐
│ SEEDBOX │
│ │
│ Tailscale Host (SSH only) │
│ └─► seedbox.taila5ad8.ts.net │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Docker Stacks │ │
│ │ │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ Transmission│ │ Prowlarr │ │ Sonarr │ │ │
│ │ │ + ts-sidecar│ │ + ts-sidecar│ │ + ts-sidecar│ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ transmission. prowlarr. sonarr. │ │
│ │ taila5ad8.ts.net taila5ad8.ts.net taila5ad8.ts.net │ │
│ │ │ │
│ │ ┌─────────────┐ │ │
│ │ │ Portainer │ (optional, for monitoring) │ │
│ │ │ + ts-sidecar│ │ │
│ │ └─────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ portainer.taila5ad8.ts.net │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Storage: │
│ ├─ /downloads (local RAID - 3.4T) │
│ └─ /mnt/media (NFS from NAS) │
└─────────────────────────────────────────────────────────────────┘
```
## Quick Start ## Quick Start
### 1. Server Installation
```bash ```bash
NFS_SERVER=nas TRANSMISSION_PASS=MySecureP@ss123 \ # With NFS mount
NFS_SERVER=nas curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/seedbox/install.sh | bash
# Without NFS (configure later)
curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/seedbox/install.sh | bash curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/seedbox/install.sh | bash
``` ```
> **⚠️ Important:** Always set `TRANSMISSION_PASS` explicitly. If omitted, a random password is generated and displayed only once at the end of the installation. It cannot be recovered afterward without resetting the config. ### 2. Configure Gitea Secrets
## Components Go to **Gitea > Repository > Settings > Secrets and Variables > Actions** and add:
- **Transmission**: BitTorrent client with WebUI | Secret | Description | Example |
- **NFS**: Dual mount to NAS for downloads and media storage |--------|-------------|---------|
- **Tailscale**: Private access to WebUI via `tailscale serve` | `TS_AUTHKEY` | Tailscale OAuth client secret | `tskey-client-xxxxx-yyyyyyyy` |
- **Docker**: Container runtime | `SEEDBOX_SSH_KEY` | SSH private key (ed25519) | `-----BEGIN OPENSSH PRIVATE KEY-----...` |
- **UFW**: Firewall (only peer port exposed publicly) | `TRANSMISSION_USER` | Transmission WebUI username | `admin` |
- **fail2ban** + **unattended-upgrades**: Basic hardening | `TRANSMISSION_PASS` | Transmission WebUI password | `your-secure-password` |
## Environment Variables #### Creating Tailscale OAuth Client
| Variable | Default | Description | 1. Go to https://login.tailscale.com/admin/settings/oauth
|----------|---------|-------------| 2. Click "Generate OAuth Client"
| `NFS_SERVER` | *required* | NAS hostname/IP (Tailscale) | 3. Scopes: `devices:write`
| `NFS_SHARE_DOWNLOAD` | `/volume2/Downloads` | NFS export for downloads | 4. Copy the **Client Secret** (starts with `tskey-client-`)
| `NFS_SHARE_MEDIA` | `/volume2/Multimédia` | NFS export for media/ISOs |
| `NFS_MOUNT_DOWNLOAD` | `/mnt/download` | Local mount for downloads |
| `NFS_MOUNT_MEDIA` | `/mnt/media` | Local mount for media |
| `SEEDBOX_HOSTNAME` | `seedbox` | Server hostname |
| `PEER_PORT` | `51413` | BitTorrent peer port |
| `TRANSMISSION_USER` | `admin` | WebUI username |
| `TRANSMISSION_PASS` | *auto-generated* | WebUI password (⚠️ set explicitly!) |
| `TZ` | `Europe/Paris` | Timezone |
Example with custom settings: #### Creating SSH Deploy Key
```bash ```bash
NFS_SERVER=nas \ # Generate key pair
NFS_SHARE_DOWNLOAD=/volume1/torrents \ ssh-keygen -t ed25519 -f seedbox-deploy -N "" -C "gitea-deploy"
NFS_SHARE_MEDIA=/volume1/iso \
TRANSMISSION_USER=damien \ # Add public key to seedbox
TRANSMISSION_PASS=MySecurePassword \ ssh debian@seedbox.taila5ad8.ts.net "cat >> ~/.ssh/authorized_keys" < seedbox-deploy.pub
curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/seedbox/install.sh | bash
# Copy private key content to Gitea secret SEEDBOX_SSH_KEY
cat seedbox-deploy
``` ```
### Password Recovery #### Tailscale ACL Configuration
If you forgot to set `TRANSMISSION_PASS` and lost the auto-generated password: Add this tag to your Tailscale ACL policy (https://login.tailscale.com/admin/acls):
```json
{
"tagOwners": {
"tag:container": ["autogroup:admins"]
}
}
```
### 3. Deploy Services
Push to the `main` branch to trigger deployment:
```bash ```bash
# Option 1: Check the docker-compose.yml (password in clear text) git push origin main
grep PASS ~/transmission/docker-compose.yml ```
# Option 2: Reset by editing docker-compose.yml Or create a PR for validation first.
cd ~/transmission
sed -i 's/PASS=.*/PASS=NewPassword/' docker-compose.yml ## Services
docker compose down && docker compose up -d
| Service | URL | Port | Description |
|---------|-----|------|-------------|
| Transmission | `transmission.taila5ad8.ts.net` | 9091 | BitTorrent client |
| Prowlarr | `prowlarr.taila5ad8.ts.net` | 9696 | Indexer manager |
| Sonarr | `sonarr.taila5ad8.ts.net` | 8989 | TV series manager |
| Portainer | `portainer.taila5ad8.ts.net` | 9000 | Docker management UI |
## Directory Structure
```
/srv/seedbox/
├── stacks/
│ ├── transmission/
│ │ ├── docker-compose.yml
│ │ └── serve.json
│ ├── prowlarr/
│ │ ├── docker-compose.yml
│ │ └── serve.json
│ ├── sonarr/
│ │ ├── docker-compose.yml
│ │ └── serve.json
│ └── portainer/
│ ├── docker-compose.yml
│ └── serve.json
└── .env # Secrets (created by pipeline)
/downloads/ # Local RAID storage (3.4T)
/mnt/media/ # NFS mount from NAS
```
## Adding a New Service
1. Create a new directory in `stacks/`:
```bash
mkdir -p seedbox/stacks/myservice
```
2. Create `docker-compose.yml`:
```yaml
services:
ts-myservice:
image: tailscale/tailscale:latest
hostname: myservice
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json
- TS_USERSPACE=false
volumes:
- ts-state:/var/lib/tailscale
- ./serve.json:/config/serve.json:ro
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
restart: unless-stopped
myservice:
image: myservice/image:latest
container_name: myservice
network_mode: service:ts-myservice
depends_on:
- ts-myservice
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
volumes:
- config:/config
- /downloads:/downloads
- /mnt/media:/media
restart: unless-stopped
volumes:
ts-state:
config:
```
3. Create `serve.json`:
```json
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"myservice.taila5ad8.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:PORT"
}
}
}
}
}
```
4. Commit and push:
```bash
git add seedbox/stacks/myservice/
git commit -m "feat(seedbox): add myservice stack"
git push origin main
```
## Removing a Service
### Option A: Via Git (recommended)
1. Remove the stack directory:
```bash
rm -rf seedbox/stacks/myservice/
git add -A
git commit -m "feat(seedbox): remove myservice stack"
git push origin main
```
2. Manually stop containers on seedbox:
```bash
ssh debian@seedbox.taila5ad8.ts.net
cd /srv/seedbox/stacks/myservice
docker compose down -v
```
3. Remove Tailscale device from admin console
### Option B: Manual (emergency)
```bash
ssh debian@seedbox.taila5ad8.ts.net
cd /srv/seedbox/stacks/myservice
docker compose down -v
rm -rf /srv/seedbox/stacks/myservice
# Remove device from https://login.tailscale.com/admin/machines
```
## Updating Services
### Update Docker Images
The pipeline automatically pulls latest images on each deployment. To force an update:
```bash
# Trigger a new deployment by making any change
git commit --allow-empty -m "chore: trigger deployment"
git push origin main
```
### Manual Update
```bash
ssh debian@seedbox.taila5ad8.ts.net
cd /srv/seedbox/stacks/transmission
docker compose pull
docker compose up -d
``` ```
## Network Access ## Network Access
| Service | Public | Tailscale | | Service | Public | Tailscale |
|---------|--------|-----------| |---------|--------|-----------|
| BitTorrent peers | ✅ Port 51413 | ✅ | | SSH | ❌ | ✅ `seedbox.taila5ad8.ts.net` |
| Transmission WebUI | ❌ | ✅ HTTPS via `tailscale serve` | | Transmission WebUI | ❌ | ✅ HTTPS via sidecar |
| SSH | ❌ | ✅ Tailscale SSH | | Transmission Peer | ✅ Port 51413 | ✅ |
| NFS (to NAS) | ❌ | ✅ | | Prowlarr | ❌ | ✅ HTTPS via sidecar |
| Sonarr | ❌ | ✅ HTTPS via sidecar |
| Portainer | ❌ | ✅ HTTPS via sidecar |
### WebUI Access ## Troubleshooting
The WebUI is exposed via Tailscale Serve with automatic HTTPS: ### Check container status
```
https://seedbox.<your-tailnet>.ts.net
```
The WebUI binds to `localhost:9091` and is proxied through `tailscale serve`, ensuring it's never accessible from the public internet.
## Storage Architecture
```
NAS (via Tailscale) Seedbox LXC (70GB)
┌─────────────────────┐ ┌─────────────────────┐
│ /volume2/Downloads │◄──── NFS ────►│ /mnt/download │
│ (incomplete + temp) │ │ └► /downloads │
├─────────────────────┤ │ (in container) │
│ /volume2/Multimédia │◄──── NFS ────►│ /mnt/media │
│ (ISOs, VMDK, QCOW) │ │ └► /media │
└─────────────────────┘ │ (in container) │
└─────────────────────┘
```
### Transmission Paths
| Container Path | Host Path | NAS Path | Purpose |
|----------------|-----------|----------|---------|
| `/downloads` | `/mnt/download` | `/volume2/Downloads` | Incomplete + completed torrents |
| `/media` | `/mnt/media` | `/volume2/Multimédia` | Final ISOs, VMDK, QCOW images |
### Recommended Workflow
1. Torrents download to `/downloads` (on NAS via NFS)
2. Once complete, move ISOs to `/media/iso/<distro>/`
3. Proxmox mounts the same NAS share for VM templates
## What it does
1. Sets hostname
2. Installs base packages (vim, fail2ban, unattended-upgrades, nfs-common, at)
3. Installs and connects Tailscale
4. Installs Docker
5. Configures dual NFS mounts to NAS (same as Proxmox)
6. Deploys Transmission container with both mounts
7. Exposes WebUI via `tailscale serve` (HTTPS on tailnet)
8. Configures UFW (peer port public, WebUI via Tailscale only)
9. Temporarily opens SSH port 22 for 5 minutes (safety net)
## SSH Safety Net
During installation, SSH port 22 is temporarily opened for 5 minutes to prevent lockout if you're connected via public IP. After 5 minutes, it will be automatically closed and only Tailscale SSH will work.
```bash ```bash
# List scheduled jobs docker ps -a
sudo atq docker logs ts-transmission
docker logs transmission
# Cancel the scheduled SSH closure (replace N with job number)
sudo atrm N
# Manually close SSH port 22 if needed
sudo ufw delete allow 22/tcp
``` ```
## Directory Structure ### Check Tailscale sidecar
Organize your media by type:
```bash
docker exec ts-transmission tailscale status
docker exec ts-transmission tailscale serve status
``` ```
/mnt/media/
├── iso/ ### Restart a stack
│ ├── debian/
│ │ └── debian-12.7.0-amd64-netinst.iso ```bash
│ ├── ubuntu/ cd /srv/seedbox/stacks/transmission
│ │ └── ubuntu-24.04.1-live-server-amd64.iso docker compose restart
│ ├── rhel/ ```
│ │ └── rocky-9.4-x86_64-minimal.iso
│ └── proxmox/ ### View all Tailscale devices
│ └── proxmox-ve_8.2-1.iso
├── vmdk/ ```bash
│ └── windows-server-2022.vmdk tailscale status
└── qcow/ ```
└── cloud-init-debian-12.qcow2
### Force re-authentication of sidecar
```bash
cd /srv/seedbox/stacks/transmission
docker compose down
docker volume rm transmission_ts-state
docker compose up -d
``` ```
## NAS Configuration (Synology) ## NAS Configuration (Synology)
Ensure your NAS exports both shares via NFS: Ensure your NAS exports the media share via NFS:
1. Control Panel → Shared Folder → Edit → NFS Permissions 1. **Control Panel → Shared Folder → Edit → NFS Permissions**
2. For each share (`Downloads` and `Multimédia`), add rule: 2. Add rule:
- Hostname/IP: `*` or Tailscale IP of seedbox (e.g., `100.x.x.x`) - Hostname/IP: `100.64.0.0/10` (Tailscale subnet) or specific IP
- Privilege: Read/Write - Privilege: Read/Write
- Squash: No mapping - Squash: No mapping
- Security: sys - Security: sys
- Enable NFSv4.1: ✅ - Enable NFSv4.1: ✅
## Post-install ## Post-install Verification
```bash ```bash
# Check NFS mounts # Check downloads mount
df -h /mnt/download /mnt/media df -h /downloads
# View Transmission logs # Check NFS mount
cd ~/transmission && docker compose logs -f df -h /mnt/media
# Restart Transmission # Check Docker
cd ~/transmission && docker compose restart docker ps
# Check tailscale serve status # Check Tailscale devices
tailscale serve status tailscale status
# Move completed ISO to final location # Test service access
mv /mnt/download/debian-12.iso /mnt/media/iso/debian/ curl -k https://transmission.taila5ad8.ts.net
```
## Troubleshooting
### WebUI not accessible
```bash
# Check tailscale serve is running
tailscale serve status
# Restart if needed
sudo tailscale serve --bg http://localhost:9091
# Check container is running
docker ps | grep transmission
# Check container logs
cd ~/transmission && docker compose logs
```
### Reset Transmission credentials
```bash
cd ~/transmission
docker compose down
# Edit docker-compose.yml to change USER and PASS
vim docker-compose.yml
docker compose up -d
``` ```

View File

@@ -1,6 +1,9 @@
#!/bin/bash #!/bin/bash
# install.sh - Automated deployment of Seedbox Server with Transmission # install.sh - Seedbox Server Initial Setup
# Usage: NFS_SERVER=<nas-ip> curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/seedbox/install.sh | bash # Usage: curl -fsSL https://gitea.arnodo.fr/Damien/infra-scripts/raw/branch/main/seedbox/install.sh | bash
#
# This script prepares the server for Docker-based seedbox deployment.
# Services are deployed separately via Gitea Actions.
set -euo pipefail set -euo pipefail
@@ -14,6 +17,15 @@ log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Configuration (can be overridden via environment)
HOSTNAME="${SEEDBOX_HOSTNAME:-seedbox}"
NFS_SERVER="${NFS_SERVER:-}"
NFS_SHARE_MEDIA="${NFS_SHARE_MEDIA:-/volume2/Multimédia}"
NFS_MOUNT_MEDIA="${NFS_MOUNT_MEDIA:-/mnt/media}"
NFS_OPTS="defaults,_netdev,nofail,x-systemd.automount,x-systemd.mount-timeout=30s"
SEEDBOX_DIR="/srv/seedbox"
REPO_URL="${REPO_URL:-https://gitea.arnodo.fr/Damien/infra-scripts.git}"
# Pre-flight checks # Pre-flight checks
check_root() { check_root() {
if [[ $EUID -eq 0 ]]; then if [[ $EUID -eq 0 ]]; then
@@ -32,83 +44,61 @@ check_debian() {
fi fi
} }
check_required_vars() {
if [[ -z "${NFS_SERVER:-}" ]]; then
log_error "NFS_SERVER environment variable is required."
log_error "Usage: NFS_SERVER=<nas-ip-or-hostname> curl -fsSL ... | bash"
exit 1
fi
}
# Generate random password if not provided
# Note: uses head -c with || true to avoid SIGPIPE with pipefail
generate_password() {
head -c 100 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 16 || true
}
# Get Tailscale FQDN hostname from Self.DNSName
# Extracts DNSName from the Self object in tailscale status --json
get_tailscale_hostname() {
local dns_name
# Use awk to extract Self.DNSName specifically (not from peers)
# Looks for DNSName within the Self block
dns_name=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
' || true)
if [[ -z "$dns_name" ]]; then
# Fallback: use hostname + default tailnet domain hint
dns_name="${HOSTNAME}.ts.net"
log_warn "Could not determine Tailscale FQDN, using fallback: $dns_name"
fi
echo "$dns_name"
}
# Configuration variables (can be overridden via environment)
HOSTNAME="${SEEDBOX_HOSTNAME:-seedbox}"
# NFS shares configuration
NFS_SHARE_DOWNLOAD="${NFS_SHARE_DOWNLOAD:-/volume2/Downloads}"
NFS_SHARE_MEDIA="${NFS_SHARE_MEDIA:-/volume2/Multimédia}"
NFS_MOUNT_DOWNLOAD="${NFS_MOUNT_DOWNLOAD:-/mnt/download}"
NFS_MOUNT_MEDIA="${NFS_MOUNT_MEDIA:-/mnt/media}"
# NFS mount options (same as Proxmox)
NFS_OPTS="defaults,_netdev,nofail,x-systemd.automount,x-systemd.mount-timeout=30s"
PEER_PORT="${PEER_PORT:-51413}"
TIMEZONE="${TZ:-Europe/Paris}"
TRANSMISSION_USER="${TRANSMISSION_USER:-admin}"
TRANSMISSION_PASS="${TRANSMISSION_PASS:-$(generate_password)}"
TRANSMISSION_DIR="$HOME/transmission"
main() { main() {
log_info "=== Seedbox Server Deployment ===" log_info "=== Seedbox Server Setup ==="
check_root check_root
check_debian check_debian
check_required_vars
# Step 1: System update
log_info "Updating system..."
sudo apt-get update -qq
sudo apt-get upgrade -y -qq
# Step 2: Set hostname
log_info "Setting hostname to: $HOSTNAME" log_info "Setting hostname to: $HOSTNAME"
echo "$HOSTNAME" | sudo tee /etc/hostname > /dev/null echo "$HOSTNAME" | sudo tee /etc/hostname > /dev/null
sudo hostnamectl set-hostname "$HOSTNAME" sudo hostnamectl set-hostname "$HOSTNAME"
# Step 3: Install base packages
log_info "Installing base packages..." log_info "Installing base packages..."
sudo apt-get update -qq sudo apt-get install -y -qq \
sudo apt-get install -y -qq vim ca-certificates curl gnupg lsb-release fail2ban unattended-upgrades nfs-common ufw at > /dev/null vim \
ca-certificates \
curl \
gnupg \
lsb-release \
fail2ban \
unattended-upgrades \
nfs-common \
ufw \
at \
git \
rsync \
> /dev/null
# Ensure atd service is running (needed for delayed SSH lockdown)
sudo systemctl enable --now atd
# Step 4: Install Tailscale
log_info "Installing Tailscale..." log_info "Installing Tailscale..."
curl -fsSL https://tailscale.com/install.sh | sh curl -fsSL https://tailscale.com/install.sh | sh
log_info "Connecting to Tailscale..." log_info "Connecting to Tailscale..."
sudo tailscale up --ssh sudo tailscale up
# Get Tailscale hostname for display
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
' || echo "${HOSTNAME}.ts.net")
# Step 5: Install Docker
log_info "Installing Docker..." log_info "Installing Docker..."
# Use official Docker installation method (DEB822 format)
# See: https://docs.docker.com/engine/install/debian/
sudo install -m 0755 -d /etc/apt/keyrings sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc
# Add Docker repository using DEB822 format (.sources)
# shellcheck disable=SC1091 # shellcheck disable=SC1091
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb Types: deb
@@ -119,97 +109,103 @@ Signed-By: /etc/apt/keyrings/docker.asc
EOF EOF
sudo apt-get update -qq sudo apt-get update -qq
sudo apt-get install -y -qq docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin > /dev/null sudo apt-get install -y -qq \
docker-ce \
docker-ce-cli \
containerd.io \
docker-buildx-plugin \
docker-compose-plugin \
> /dev/null
# Step 6: Configure Docker for user
log_info "Adding current user to docker group..." log_info "Adding current user to docker group..."
sudo usermod -aG docker "$USER" sudo usermod -aG docker "$USER"
log_info "Configuring NFS mounts..." # Step 7: Configure UFW firewall (initial - SSH still open on public)
sudo mkdir -p "$NFS_MOUNT_DOWNLOAD" "$NFS_MOUNT_MEDIA" log_info "Configuring UFW firewall (initial setup)..."
# Add download share to fstab if not already present
if ! grep -q "${NFS_SERVER}:${NFS_SHARE_DOWNLOAD}" /etc/fstab; then
log_info "Adding NFS mount: ${NFS_SERVER}:${NFS_SHARE_DOWNLOAD} -> ${NFS_MOUNT_DOWNLOAD}"
echo "${NFS_SERVER}:${NFS_SHARE_DOWNLOAD} ${NFS_MOUNT_DOWNLOAD} nfs ${NFS_OPTS} 0 0" | sudo tee -a /etc/fstab > /dev/null
fi
# Add media share to fstab if not already present
if ! grep -q "${NFS_SERVER}:${NFS_SHARE_MEDIA}" /etc/fstab; then
log_info "Adding NFS mount: ${NFS_SERVER}:${NFS_SHARE_MEDIA} -> ${NFS_MOUNT_MEDIA}"
echo "${NFS_SERVER}:${NFS_SHARE_MEDIA} ${NFS_MOUNT_MEDIA} nfs ${NFS_OPTS} 0 0" | sudo tee -a /etc/fstab > /dev/null
fi
# Mount NFS shares
sudo mount -a || log_warn "NFS mount failed - ensure NAS is accessible via Tailscale"
log_info "Creating Transmission stack..."
mkdir -p "$TRANSMISSION_DIR"
cat > "$TRANSMISSION_DIR/docker-compose.yml" << EOF
services:
transmission:
image: linuxserver/transmission:latest
container_name: transmission
restart: unless-stopped
ports:
# Peer port - public for seeding
- "${PEER_PORT}:${PEER_PORT}"
- "${PEER_PORT}:${PEER_PORT}/udp"
# WebUI - bound to localhost only (exposed via tailscale serve)
- "127.0.0.1:9091:9091"
volumes:
- ./config:/config
# Downloads: incomplete and complete torrents
- ${NFS_MOUNT_DOWNLOAD}:/downloads
# Media: final destination for completed ISOs and images
- ${NFS_MOUNT_MEDIA}:/media
environment:
- PUID=1000
- PGID=1000
- TZ=${TIMEZONE}
- USER=${TRANSMISSION_USER}
- PASS=${TRANSMISSION_PASS}
- PEERPORT=${PEER_PORT}
EOF
log_info "Starting Transmission..."
cd "$TRANSMISSION_DIR"
# Use sg to run docker compose with the new docker group membership
sg docker -c "docker compose up -d"
log_info "Exposing Transmission WebUI via Tailscale..."
sudo tailscale serve --bg http://localhost:9091
# Get Tailscale hostname for final message
TS_HOSTNAME=$(get_tailscale_hostname)
log_info "Configuring UFW firewall..."
sudo ufw --force reset > /dev/null sudo ufw --force reset > /dev/null
sudo ufw default deny incoming > /dev/null sudo ufw default deny incoming > /dev/null
sudo ufw default allow outgoing > /dev/null sudo ufw default allow outgoing > /dev/null
# Allow BitTorrent peer port from public internet # SSH temporarily on all interfaces (will be locked down after Tailscale is confirmed)
sudo ufw allow ${PEER_PORT}/tcp > /dev/null sudo ufw allow 22/tcp > /dev/null
sudo ufw allow ${PEER_PORT}/udp > /dev/null # BitTorrent peer port (public)
sudo ufw allow 51413/tcp > /dev/null
sudo ufw allow 51413/udp > /dev/null
# Allow all traffic on Tailscale interface # Allow all traffic on Tailscale interface
sudo ufw allow in on tailscale0 > /dev/null sudo ufw allow in on tailscale0 > /dev/null
# Temporarily allow SSH during setup (safety net)
sudo ufw allow 22/tcp > /dev/null
sudo ufw --force enable > /dev/null sudo ufw --force enable > /dev/null
# Schedule SSH rule removal in 5 minutes # Step 8: Schedule SSH lockdown via 'at' (2 minutes delay for safety)
log_warn "SSH port 22 temporarily open for 5 minutes (safety net)." log_info "Scheduling SSH lockdown to Tailscale-only in 2 minutes..."
log_warn "Verify Tailscale SSH access works, then wait or run: sudo ufw delete allow 22/tcp" log_warn "IMPORTANT: Reconnect via Tailscale SSH within 2 minutes!"
echo "sudo ufw delete allow 22/tcp && logger 'UFW: SSH port 22 closed by scheduled task'" | sudo at now + 5 minutes 2>/dev/null || { log_warn " ssh ${USER}@${TS_FQDN}"
log_warn "Could not schedule automatic SSH cleanup. Run manually after verification:"
log_warn " sudo ufw delete allow 22/tcp" echo "sudo ufw delete allow 22/tcp" | at now + 2 minutes 2>/dev/null || {
log_warn "Failed to schedule SSH lockdown via 'at'. Manual lockdown required."
log_warn "Run manually after confirming Tailscale access: sudo ufw delete allow 22/tcp"
} }
# Step 9: Create directory structure
log_info "Creating directory structure..."
sudo mkdir -p "$SEEDBOX_DIR"
sudo chown -R "$USER:$USER" "$SEEDBOX_DIR"
# Step 10: Configure NFS mount (if NFS_SERVER provided)
if [[ -n "$NFS_SERVER" ]]; then
log_info "Configuring NFS mount..."
sudo mkdir -p "$NFS_MOUNT_MEDIA"
if ! grep -q "${NFS_SERVER}:${NFS_SHARE_MEDIA}" /etc/fstab; then
log_info "Adding NFS mount: ${NFS_SERVER}:${NFS_SHARE_MEDIA} -> ${NFS_MOUNT_MEDIA}"
echo "${NFS_SERVER}:${NFS_SHARE_MEDIA} ${NFS_MOUNT_MEDIA} nfs ${NFS_OPTS} 0 0" | sudo tee -a /etc/fstab > /dev/null
fi
sudo mount -a || log_warn "NFS mount failed - ensure NAS is accessible via Tailscale"
else
log_warn "NFS_SERVER not set. NFS mount skipped. Set it later if needed."
fi
# Step 11: Clone repository (sparse checkout for seedbox/ only)
log_info "Cloning seedbox configuration..."
if [[ -d "${SEEDBOX_DIR}/.git" ]]; then
cd "$SEEDBOX_DIR"
git pull origin main || log_warn "Git pull failed"
else
# Clean any existing content
rm -rf "${SEEDBOX_DIR:?}"/*
rm -rf "${SEEDBOX_DIR}"/.[!.]* 2>/dev/null || true
cd "$SEEDBOX_DIR"
git init
git remote add origin "$REPO_URL"
# Configure sparse checkout to only get seedbox/ directory
git sparse-checkout init --cone
git sparse-checkout set seedbox
# Fetch and checkout
git fetch origin main
git checkout main
# Move contents of seedbox/ to root and clean up
if [[ -d "${SEEDBOX_DIR}/seedbox" ]]; then
# Move all files including hidden ones
shopt -s dotglob
mv "${SEEDBOX_DIR}/seedbox"/* "${SEEDBOX_DIR}/" 2>/dev/null || true
shopt -u dotglob
rmdir "${SEEDBOX_DIR}/seedbox" 2>/dev/null || true
fi
# Disable sparse checkout now that we have the files
git sparse-checkout disable 2>/dev/null || true
fi
# Step 12: Configure MOTD
log_info "Configuring MOTD..." log_info "Configuring MOTD..."
sudo chmod -x /etc/update-motd.d/* 2>/dev/null || true sudo chmod -x /etc/update-motd.d/* 2>/dev/null || true
cat << 'MOTD' | sudo tee /etc/update-motd.d/00-seedbox > /dev/null cat << 'MOTD' | sudo tee /etc/update-motd.d/00-seedbox > /dev/null
#!/bin/bash #!/bin/bash
# Get Tailscale FQDN from Self.DNSName
TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' ' TS_FQDN=$(tailscale status --json 2>/dev/null | awk -F'"' '
/"Self"/ { in_self=1 } /"Self"/ { in_self=1 }
in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit } in_self && /"DNSName"/ { gsub(/\.$/, "", $4); print $4; exit }
@@ -223,49 +219,64 @@ echo "\___ \| _| | _| | | | | _ \| | | \ /"
echo " ___) | |___| |___| |_| | |_) | |_| / \\" echo " ___) | |___| |___| |_| | |_) | |_| / \\"
echo "|____/|_____|_____|____/|____/ \___/_/\_\\" echo "|____/|_____|_____|____/|____/ \___/_/\_\\"
echo "" echo ""
echo "ISO Seedbox Server - Transmission" echo "Docker Seedbox Server"
echo "─────────────────────────────────────────" echo "─────────────────────────────────────────"
echo "Access:" echo "Access:"
echo " • WebUI : https://${TS_FQDN}" echo " • SSH : ${TS_FQDN} (Tailscale only)"
echo " • SSH : Tailscale only" echo " • Seeding : Public port 51413"
echo " • Seeding : Public port 51413" echo ""
echo "Services: (via Tailscale)"
docker ps --format ' • {{.Names}} : {{.Status}}' 2>/dev/null || echo " Docker not running"
echo "" echo ""
echo "Storage:" echo "Storage:"
echo " • Downloads : /mnt/download" echo " • Downloads : /downloads (local RAID)"
echo " • Media : /mnt/media" echo " • Media : /mnt/media (NFS)"
echo "" echo ""
echo "Useful commands:" echo "Useful commands:"
echo " cd ~/transmission && docker compose logs -f" echo " cd /srv/seedbox && docker compose ls"
echo " df -h /mnt/download /mnt/media" echo " docker logs -f <container>"
echo "─────────────────────────────────────────" echo "─────────────────────────────────────────"
echo "" echo ""
MOTD MOTD
sudo chmod +x /etc/update-motd.d/00-seedbox sudo chmod +x /etc/update-motd.d/00-seedbox
# Final summary
echo "" echo ""
log_info "==========================================" log_info "==========================================="
log_info "Deployment complete!" log_info "Server setup complete!"
log_info "==========================================" log_info "==========================================="
echo "" echo ""
echo "Transmission WebUI:" log_warn "⚠️ SSH LOCKDOWN SCHEDULED IN 2 MINUTES!"
echo " URL : https://${TS_HOSTNAME}" log_warn " Reconnect NOW via Tailscale:"
echo " Username : ${TRANSMISSION_USER}"
echo " Password : ${TRANSMISSION_PASS}"
echo "" echo ""
echo "Storage (NFS mounts):" echo " ssh ${USER}@${TS_FQDN}"
echo " Downloads : ${NFS_SERVER}:${NFS_SHARE_DOWNLOAD} -> ${NFS_MOUNT_DOWNLOAD}"
echo " Media : ${NFS_SERVER}:${NFS_SHARE_MEDIA} -> ${NFS_MOUNT_MEDIA}"
echo "" echo ""
echo "Transmission paths:" echo "Server accessible at:"
echo " /downloads -> ${NFS_MOUNT_DOWNLOAD} (incomplete + complete torrents)" echo " SSH: ssh user@${TS_FQDN}"
echo " /media -> ${NFS_MOUNT_MEDIA} (final destination for ISOs)"
echo "" echo ""
echo "Peer port : ${PEER_PORT} (public)" echo "Directory structure:"
echo " ${SEEDBOX_DIR}/"
echo " ├── stacks/ # Docker Compose stacks"
echo " └── .env # Secrets (created by Gitea Actions)"
echo "" echo ""
log_warn "SSH port 22 will be closed in 5 minutes." echo "Storage:"
log_warn "To cancel: sudo atq (list jobs) then sudo atrm <job-number>" echo " • Downloads: /downloads (local RAID - 3.4T)"
echo " • Media: /mnt/media (NFS)"
echo "" echo ""
echo "Save these credentials! The password was auto-generated." echo "NFS mount:"
if [[ -n "$NFS_SERVER" ]]; then
echo " ${NFS_SERVER}:${NFS_SHARE_MEDIA} -> ${NFS_MOUNT_MEDIA}"
else
echo " Not configured. Run with NFS_SERVER=<ip> to configure."
fi
echo ""
echo "Next steps:"
echo " 1. Reconnect via Tailscale SSH IMMEDIATELY"
echo " 2. Configure Gitea secrets (see README.md)"
echo " 3. Push to main branch to trigger deployment"
echo " 4. Services will be available at <service>.taila5ad8.ts.net"
echo ""
log_info "SSH access via Tailscale: ssh user@${TS_FQDN}"
echo "" echo ""
} }

View File

@@ -0,0 +1,52 @@
# Portainer Stack
Docker management UI, accessible via Tailscale.
## Access
- **URL**: https://portainer.taila5ad8.ts.net
- **Initial setup**: Create admin account on first access
## Ports
| Port | Protocol | Exposure | Description |
|------|----------|----------|-------------|
| 9000 | TCP | Tailscale only | WebUI |
## Features
- View and manage all Docker containers
- View logs in real-time
- Execute commands in containers
- Manage Docker networks and volumes
- Deploy stacks from templates
## First Setup
1. Access https://portainer.taila5ad8.ts.net
2. Create an admin account
3. Select "Docker" as environment type
4. Connect to local Docker socket
## Troubleshooting
```bash
# Check logs
docker logs portainer
# Check Tailscale sidecar
docker exec ts-portainer tailscale status
# Restart stack
cd /srv/seedbox/stacks/portainer
docker compose restart
```
## Note
Portainer is optional and mainly useful for:
- Visual monitoring of containers
- Quick access to logs
- Emergency container management
All deployments should still go through Gitea Actions for proper version control.

View File

@@ -0,0 +1,33 @@
services:
ts-portainer:
image: tailscale/tailscale:latest
hostname: portainer
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json
- TS_USERSPACE=false
volumes:
- ts-state:/var/lib/tailscale
- ./serve.json:/config/serve.json:ro
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
restart: unless-stopped
portainer:
image: portainer/portainer-ce:latest
container_name: portainer
network_mode: service:ts-portainer
depends_on:
- ts-portainer
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- data:/data
restart: unless-stopped
volumes:
ts-state:
data:

View File

@@ -0,0 +1,16 @@
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"portainer.taila5ad8.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:9000"
}
}
}
}
}

View File

@@ -0,0 +1,61 @@
# Prowlarr Stack
Indexer manager for Sonarr, Radarr, and other *arr applications.
## Access
- **URL**: https://prowlarr.taila5ad8.ts.net
- **Initial setup**: Configure on first access (no default credentials)
## Ports
| Port | Protocol | Exposure | Description |
|------|----------|----------|-------------|
| 9696 | TCP | Tailscale only | WebUI |
## Features
- Centralized indexer management
- Automatic sync with Sonarr/Radarr
- Support for Usenet and Torrent indexers
- Built-in indexer testing
## Configuration
### Connect to Sonarr
1. In Prowlarr: **Settings → Apps → Add**
2. Select "Sonarr"
3. Prowlarr Server: `http://localhost:9696` (or use Tailscale hostname)
4. Sonarr Server: `http://sonarr.taila5ad8.ts.net` or internal Docker network
5. API Key: Get from Sonarr **Settings → General**
### Add Indexers
1. Go to **Indexers → Add**
2. Search for your preferred indexer
3. Configure credentials if required
4. Test and save
## Troubleshooting
```bash
# Check logs
docker logs prowlarr
# Check Tailscale sidecar
docker exec ts-prowlarr tailscale status
# Restart stack
cd /srv/seedbox/stacks/prowlarr
docker compose restart
# Check WebUI is responding
docker exec ts-prowlarr curl -s http://127.0.0.1:9696
```
## Integration Notes
Prowlarr can communicate with other *arr apps via:
1. **Tailscale hostnames** (e.g., `sonarr.taila5ad8.ts.net`) - Recommended
2. **Docker network** - Requires additional network configuration

View File

@@ -0,0 +1,36 @@
services:
ts-prowlarr:
image: tailscale/tailscale:latest
hostname: prowlarr
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json
- TS_USERSPACE=false
volumes:
- ts-state:/var/lib/tailscale
- ./serve.json:/config/serve.json:ro
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
restart: unless-stopped
prowlarr:
image: linuxserver/prowlarr:latest
container_name: prowlarr
network_mode: service:ts-prowlarr
depends_on:
- ts-prowlarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
volumes:
- config:/config
restart: unless-stopped
volumes:
ts-state:
config:

View File

@@ -0,0 +1,16 @@
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"prowlarr.taila5ad8.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:9696"
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
# Sonarr Stack
TV series management and automation.
## Access
- **URL**: https://sonarr.taila5ad8.ts.net
- **Initial setup**: Configure on first access (no default credentials)
## Ports
| Port | Protocol | Exposure | Description |
|------|----------|----------|-------------|
| 8989 | TCP | Tailscale only | WebUI |
## Volumes
| Path in container | Host path | Description |
|-------------------|-----------|-------------|
| `/config` | Docker volume | Sonarr configuration |
| `/downloads` | `/downloads` | Download directory (local RAID) |
| `/media` | `/mnt/media` | Media library (NFS) |
## Configuration
### Root Folders
1. Go to **Settings → Media Management → Root Folders**
2. Add `/media/TV` or your preferred path
### Download Client
1. Go to **Settings → Download Clients → Add**
2. Select "Transmission"
3. Host: `transmission.taila5ad8.ts.net`
4. Port: `443`
5. Use SSL: ✅
6. Username/Password: Your Transmission credentials
### Connect to Prowlarr
1. Go to **Settings → Indexers**
2. Prowlarr will auto-sync indexers if configured correctly
### Quality Profiles
Configure in **Settings → Profiles** based on your preferences.
## Troubleshooting
```bash
# Check logs
docker logs sonarr
# Check Tailscale sidecar
docker exec ts-sonarr tailscale status
# Restart stack
cd /srv/seedbox/stacks/sonarr
docker compose restart
# Check WebUI is responding
docker exec ts-sonarr curl -s http://127.0.0.1:8989
```
## Recommended Folder Structure
```
/mnt/media/
├── TV/
│ ├── Show Name (Year)/
│ │ ├── Season 01/
│ │ └── Season 02/
│ └── Another Show/
└── downloads/
└── completed/
```
## API Key
Find your API key in **Settings → General → Security**.
This is needed for Prowlarr integration.

View File

@@ -0,0 +1,38 @@
services:
ts-sonarr:
image: tailscale/tailscale:latest
hostname: sonarr
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json
- TS_USERSPACE=false
volumes:
- ts-state:/var/lib/tailscale
- ./serve.json:/config/serve.json:ro
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
restart: unless-stopped
sonarr:
image: linuxserver/sonarr:latest
container_name: sonarr
network_mode: service:ts-sonarr
depends_on:
- ts-sonarr
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
volumes:
- config:/config
- /downloads:/downloads
- /mnt/media:/media
restart: unless-stopped
volumes:
ts-state:
config:

View File

@@ -0,0 +1,16 @@
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"sonarr.taila5ad8.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:8989"
}
}
}
}
}

View File

@@ -0,0 +1,52 @@
# Transmission Stack
BitTorrent client with web interface, accessible via Tailscale.
## Access
- **URL**: https://transmission.taila5ad8.ts.net
- **Default credentials**: Set via `TRANSMISSION_USER` and `TRANSMISSION_PASS` secrets
## Ports
| Port | Protocol | Exposure | Description |
|------|----------|----------|-------------|
| 9091 | TCP | Tailscale only | WebUI |
| 51413 | TCP/UDP | Public | BitTorrent peer port |
## Volumes
| Path in container | Host path | Description |
|-------------------|-----------|-------------|
| `/config` | Docker volume | Transmission configuration |
| `/downloads` | `/downloads` | Download directory (local RAID - 3.4T) |
| `/media` | `/mnt/media` | Media library (NFS) |
## Configuration
### Download Paths
In Transmission settings:
- **Download to**: `/downloads`
- **Move completed to**: `/media/downloads` (optional)
### Peer Port
The peer port 51413 is exposed publicly for optimal seeding performance. Ensure your firewall/router allows this port.
## Troubleshooting
```bash
# Check logs
docker logs transmission
# Check Tailscale sidecar
docker exec ts-transmission tailscale status
# Restart stack
cd /srv/seedbox/stacks/transmission
docker compose restart
# Check WebUI is responding
docker exec ts-transmission curl -s http://127.0.0.1:9091
```

View File

@@ -0,0 +1,45 @@
services:
ts-transmission:
image: tailscale/tailscale:latest
hostname: transmission
environment:
- TS_AUTHKEY=${TS_AUTHKEY}
- TS_EXTRA_ARGS=--advertise-tags=tag:container
- TS_STATE_DIR=/var/lib/tailscale
- TS_SERVE_CONFIG=/config/serve.json
- TS_USERSPACE=false
volumes:
- ts-state:/var/lib/tailscale
- ./serve.json:/config/serve.json:ro
devices:
- /dev/net/tun:/dev/net/tun
cap_add:
- net_admin
ports:
# BitTorrent peer port - exposed publicly for seeding
- "51413:51413"
- "51413:51413/udp"
restart: unless-stopped
transmission:
image: linuxserver/transmission:latest
container_name: transmission
network_mode: service:ts-transmission
depends_on:
- ts-transmission
environment:
- PUID=1000
- PGID=1000
- TZ=Europe/Paris
- USER=${TRANSMISSION_USER:-admin}
- PASS=${TRANSMISSION_PASS}
- PEERPORT=51413
volumes:
- config:/config
- /downloads:/downloads
- /mnt/media:/media
restart: unless-stopped
volumes:
ts-state:
config:

View File

@@ -0,0 +1,16 @@
{
"TCP": {
"443": {
"HTTPS": true
}
},
"Web": {
"transmission.taila5ad8.ts.net:443": {
"Handlers": {
"/": {
"Proxy": "http://127.0.0.1:9091"
}
}
}
}
}