mirror of
https://github.com/terraphim/gitea-infrastructure.git
synced 2026-07-16 01:00:33 +02:00
Automated fix from pre-commit hooks (trailing-whitespace, end-of-file-fixer). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
870 lines
27 KiB
Markdown
870 lines
27 KiB
Markdown
# Implementation Plan: Deploy Gitea Stack to Bigbox with 1Password and Caddy
|
|
|
|
**Status**: Draft
|
|
**Research Doc**: .docs/RESEARCH-gitea-bigbox-deployment.md
|
|
**Author**: Claude
|
|
**Date**: 2026-02-16
|
|
**Estimated Effort**: 3-4 hours
|
|
|
|
## Overview
|
|
|
|
### Summary
|
|
Deploy the Gitea development infrastructure stack to bigbox using 1Password for secrets management (`op inject`) and Caddy for reverse proxy/SSL termination. The deployment sources 1Password credentials via `op_zesticai_non_prod.sh` and uses template files for secure secret injection.
|
|
|
|
### Approach
|
|
1. Create 1Password vault items for all required secrets
|
|
2. Create template files (`.template`) with 1Password references
|
|
3. Configure Caddy as reverse proxy with automatic HTTPS
|
|
4. Deploy using `op inject` to populate secrets at runtime
|
|
5. Validate all services through Caddy endpoints
|
|
|
|
### Scope
|
|
|
|
**In Scope:**
|
|
- Create 1Password vault items for all credentials
|
|
- Create `docker-compose.yml.template` with 1Password references
|
|
- Create `s3_config.json.template` with 1Password references
|
|
- Configure Caddy reverse proxy with SSL
|
|
- Deploy with `op inject` workflow
|
|
- Update Caddy instance on bigbox
|
|
- Validate service accessibility via HTTPS
|
|
|
|
**Out of Scope:**
|
|
- Gitea runner registration (future work)
|
|
- Earthly Buildkit integration (future work)
|
|
- Automated backup configuration
|
|
- CI/CD pipeline setup
|
|
- Custom SSL certificates (Caddy handles automatically)
|
|
|
|
**Avoid At All Cost** (from 5/25 analysis):
|
|
- Hardcoded credentials in any deployed files
|
|
- Manual secret management
|
|
- Complex deployment automation
|
|
- Multiple Caddy instances
|
|
- Kubernetes migration
|
|
|
|
## Architecture
|
|
|
|
### Component Diagram
|
|
```
|
|
Public Internet Tailscale Network (100.106.66.0/8)
|
|
│ │
|
|
▼ ▼
|
|
┌──────────────┐ ┌─────────────────────┐
|
|
│ Caddy │ │ bigbox │
|
|
│ (existing) │ │ │
|
|
│ │ │ ┌─────────────────┐ │
|
|
│ │──443/HTTPS──────────────▶│ │ Gitea │ │
|
|
│ │ git.terraphim.cloud │ │ localhost:3000 │ │
|
|
└──────────────┘ │ └─────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ Prometheus │ │
|
|
│ │ 100.106.66.7 │ │
|
|
│ │ :9000 │ │
|
|
│ └─────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ S3 │ │
|
|
│ │ 100.106.66.7 │ │
|
|
│ │ :8333 │ │
|
|
│ └─────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────┐ │
|
|
│ │ SeaweedFS │ │
|
|
│ │ (internal) │ │
|
|
│ └─────────────────┘ │
|
|
└─────────────────────┘
|
|
```
|
|
|
|
### Secrets Management Flow
|
|
```
|
|
Developer Machine
|
|
│
|
|
├── Create/Update 1Password vault items
|
|
│
|
|
├── Create .template files with op:// references
|
|
│
|
|
└── Transfer files to bigbox
|
|
│
|
|
▼
|
|
bigbox Server
|
|
│
|
|
├── source ~/op_zesticai_non_prod.sh
|
|
│ └── Export OP_SERVICE_ACCOUNT_TOKEN
|
|
│
|
|
├── op inject --in-file docker-compose.yml.template \
|
|
│ --out-file docker-compose.yml
|
|
│ └── Resolves op:// references to actual secrets
|
|
│
|
|
└── docker compose up -d
|
|
└── Containers receive secrets as env vars
|
|
```
|
|
|
|
### Service Exposure
|
|
```
|
|
Public (via Caddy on terraphim.cloud):
|
|
git.terraphim.cloud ───────▶ localhost:3000 (Gitea Web)
|
|
|
|
Tailscale Network Only (100.106.66.7):
|
|
100.106.66.7:9000 ─────────▶ Prometheus
|
|
100.106.66.7:8333 ─────────▶ SeaweedFS S3 API
|
|
|
|
Internal Docker Network Only:
|
|
SeaweedFS Master, Volume, Filer (no external ports)
|
|
PostgreSQL (no external ports)
|
|
|
|
SSH (Port 222):
|
|
All interfaces - for Git over SSH
|
|
```
|
|
|
|
### Network Security
|
|
- **Public**: Only Gitea web interface via Caddy HTTPS
|
|
- **Tailscale**: Prometheus and S3 accessible only within VPN
|
|
- **Internal**: All other services on Docker network only
|
|
- **SSH**: Port 222 exposed for Git operations
|
|
|
|
### Key Design Decisions
|
|
| Decision | Rationale | Alternatives Rejected |
|
|
|----------|-----------|----------------------|
|
|
| 1Password + op inject | Enterprise-grade secrets management | Hardcoded secrets (security risk) |
|
|
| Caddy reverse proxy | Automatic HTTPS, simple config | Nginx (more complex SSL) |
|
|
| Service account auth | Non-interactive automation | Interactive CLI login |
|
|
| Template pattern | Clear separation of templates and rendered files | Direct file editing |
|
|
|
|
### Eliminated Options (Essentialism)
|
|
| Option Rejected | Why Rejected | Risk of Including |
|
|
|-----------------|--------------|-------------------|
|
|
| Hardcoded credentials | Security vulnerability | Credential exposure |
|
|
| Manual secret management | Error-prone, not auditable | Compliance issues |
|
|
| Kubernetes Secrets | Overkill for single host | Complexity explosion |
|
|
| HashiCorp Vault | Additional infrastructure | Operational burden |
|
|
|
|
### Simplicity Check
|
|
|
|
> "Minimum code that solves the problem. Nothing speculative."
|
|
|
|
**What if this could be easy?**
|
|
The simplest secure deployment is: create templates with `op://` references, source the 1Password script, run `op inject`, then `docker compose up`. Caddy handles SSL automatically. No complex automation, no hardcoded secrets, just secure defaults.
|
|
|
|
**Senior Engineer Test**: Would a senior engineer call this overcomplicated? No - this is production-grade security with minimal complexity.
|
|
|
|
**Nothing Speculative Checklist**:
|
|
- [x] No features the user didn't request
|
|
- [x] No abstractions "in case we need them later"
|
|
- [x] No flexibility "just in case"
|
|
- [x] No premature optimization
|
|
- [x] Security-first approach
|
|
|
|
## File Changes
|
|
|
|
### New Files
|
|
| File | Purpose |
|
|
|------|---------|
|
|
| `.docs/RESEARCH-gitea-bigbox-deployment.md` | Research findings (Phase 1) |
|
|
| `.docs/IMPLEMENTATION-gitea-bigbox-deployment.md` | This implementation plan (Phase 2) |
|
|
| `docker-compose.yml.template` | Template with 1Password references |
|
|
| `s3_config.json.template` | Template with 1Password references |
|
|
| `Caddyfile` | Caddy reverse proxy configuration |
|
|
| `.env.template` | Environment variables template |
|
|
|
|
### Modified Files
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `.gitignore` | Add `docker-compose.yml`, `s3_config.json`, `.env` (no hardcoded secrets in git) |
|
|
|
|
### Files to Exclude from Git
|
|
| File | Reason |
|
|
|------|--------|
|
|
| `docker-compose.yml` | Contains resolved secrets after op inject |
|
|
| `s3_config.json` | Contains resolved secrets after op inject |
|
|
| `.env` | Contains resolved secrets after op inject |
|
|
| `*.decrypted` | Any decrypted files |
|
|
|
|
## 1Password Vault Structure
|
|
|
|
### Required Vault Items
|
|
Create these items in the `zesticai-non-prod` vault (or update existing):
|
|
|
|
#### Item: gitea-postgres
|
|
```
|
|
Title: gitea-postgres
|
|
Username: gitea
|
|
Password: [generate strong password]
|
|
```
|
|
|
|
#### Item: gitea-s3
|
|
```
|
|
Title: gitea-s3
|
|
access-key: [generate random key]
|
|
secret-key: [generate random secret]
|
|
```
|
|
|
|
#### Item: gitea-server (optional)
|
|
```
|
|
Title: gitea-server
|
|
jwt-secret: [generate random string]
|
|
internal-token: [generate random string]
|
|
```
|
|
|
|
### 1Password References in Templates
|
|
```yaml
|
|
# docker-compose.yml.template
|
|
environment:
|
|
- GITEA__database__PASSWD=op://zesticai-non-prod/gitea-postgres/password
|
|
- GITEA__storage__MINIO_ACCESS_KEY_ID=op://zesticai-non-prod/gitea-s3/access-key
|
|
- GITEA__storage__MINIO_SECRET_ACCESS_KEY=op://zesticai-non-prod/gitea-s3/secret-key
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
### Step 1: Pre-Deployment Verification
|
|
**Description:** Verify bigbox is ready and 1Password is configured
|
|
**Tests:** SSH connectivity, Docker availability, 1Password CLI, service account
|
|
**Estimated:** 15 minutes
|
|
|
|
```bash
|
|
# Verify SSH access
|
|
ssh bigbox "whoami"
|
|
|
|
# Verify Docker
|
|
ssh bigbox "docker --version && docker compose version"
|
|
|
|
# Verify 1Password CLI
|
|
ssh bigbox "op --version"
|
|
|
|
# Verify service account script
|
|
ssh bigbox "cat ~/op_zesticai_non_prod.sh | head -5"
|
|
|
|
# Test 1Password authentication
|
|
ssh bigbox "source ~/op_zesticai_non_prod.sh && op vault list"
|
|
|
|
# Check Caddy
|
|
ssh bigbox "caddy version"
|
|
|
|
# Check available ports
|
|
ssh bigbox "sudo netstat -tlnp | grep -E ':80|:443' || echo 'Ports 80/443 available'"
|
|
```
|
|
|
|
### Step 2: Create 1Password Vault Items
|
|
**Description:** Create or update vault items with required secrets
|
|
**Tests:** All items exist with correct fields
|
|
**Dependencies:** Step 1
|
|
**Estimated:** 20 minutes
|
|
|
|
```bash
|
|
# Source 1Password credentials locally (if you have access)
|
|
source ~/op_zesticai_non_prod.sh
|
|
|
|
# Create gitea-postgres item (or update existing)
|
|
op item create \
|
|
--vault="zesticai-non-prod" \
|
|
--category=login \
|
|
--title="gitea-postgres" \
|
|
--generate-password=20,letters,digits \
|
|
username=gitea
|
|
|
|
# Create gitea-s3 item with access credentials
|
|
op item create \
|
|
--vault="zesticai-non-prod" \
|
|
--category=custom \
|
|
--title="gitea-s3" \
|
|
access-key="$(openssl rand -hex 20)" \
|
|
secret-key="$(openssl rand -hex 40)"
|
|
|
|
# Verify items created
|
|
op item list --vault="zesticai-non-prod" | grep gitea
|
|
```
|
|
|
|
### Step 3: Create Template Files
|
|
**Description:** Create `.template` files with 1Password references
|
|
**Tests:** Templates contain valid op:// references
|
|
**Dependencies:** Step 2
|
|
**Estimated:** 30 minutes
|
|
|
|
#### Create docker-compose.yml.template
|
|
```yaml
|
|
version: "3"
|
|
# Template file - use 'op inject' to generate docker-compose.yml
|
|
networks:
|
|
gitea:
|
|
external: false
|
|
|
|
services:
|
|
server:
|
|
image: gitea/gitea:1.22.6
|
|
container_name: gitea
|
|
environment:
|
|
- USER_UID=1000
|
|
- USER_GID=1000
|
|
- GITEA__database__DB_TYPE=postgres
|
|
- GITEA__database__HOST=db:5432
|
|
- GITEA__database__NAME=gitea
|
|
- GITEA__database__USER=gitea
|
|
- GITEA__database__PASSWD=op://zesticai-non-prod/gitea-postgres/password
|
|
- GITEA__actions__ENABLE=true
|
|
- GITEA__repository__LFS_START_SERVER=true
|
|
- GITEA__repository__LFS_CONTENT_PATH=/data/lfs
|
|
- GITEA__storage__type=minio
|
|
- GITEA__storage__MINIO_ACCESS_KEY_ID=op://zesticai-non-prod/gitea-s3/access-key
|
|
- GITEA__storage__MINIO_SECRET_ACCESS_KEY=op://zesticai-non-prod/gitea-s3/secret-key
|
|
- GITEA__storage__MINIO_BUCKET=gitea
|
|
- GITEA__storage__MINIO_LOCATION=us-east-1
|
|
- GITEA__storage__MINIO_ENDPOINT=http://s3:8333
|
|
- GITEA__storage__MINIO_INSECURE_SKIP_VERIFY=false
|
|
- GITEA__storage__MINIO_USE_SSL=false
|
|
- GITEA__storage__SERVE_DIRECT=true
|
|
user: "1000:1000"
|
|
restart: always
|
|
networks:
|
|
- gitea
|
|
volumes:
|
|
- ./gitea:/data
|
|
- /etc/timezone:/etc/timezone:ro
|
|
- /etc/localtime:/etc/localtime:ro
|
|
ports:
|
|
- "3000:3000"
|
|
- "222:22"
|
|
depends_on:
|
|
- db
|
|
- s3
|
|
|
|
db:
|
|
image: postgres:15
|
|
restart: always
|
|
environment:
|
|
- POSTGRES_USER=gitea
|
|
- POSTGRES_PASSWORD=op://zesticai-non-prod/gitea-postgres/password
|
|
- POSTGRES_DB=gitea
|
|
networks:
|
|
- gitea
|
|
volumes:
|
|
- ./postgres:/var/lib/postgresql/data
|
|
|
|
master:
|
|
image: chrislusf/seaweedfs
|
|
ports:
|
|
- 9333:9333
|
|
- 19333:19333
|
|
- 9324:9324
|
|
command: "master -ip=master -ip.bind=0.0.0.0 -metricsPort=9324"
|
|
|
|
volume:
|
|
image: chrislusf/seaweedfs
|
|
ports:
|
|
- 8080:8080
|
|
- 18080:18080
|
|
- 9325:9325
|
|
command: 'volume -mserver="master:9333" -ip.bind=0.0.0.0 -port=8080 -metricsPort=9325'
|
|
depends_on:
|
|
- master
|
|
volumes:
|
|
- ./seaweedfs:/data
|
|
|
|
filer:
|
|
image: chrislusf/seaweedfs
|
|
ports:
|
|
- 8888:8888
|
|
- 18888:18888
|
|
- 9326:9326
|
|
command: 'filer -master="master:9333" -ip.bind=0.0.0.0 -metricsPort=9326'
|
|
tty: true
|
|
stdin_open: true
|
|
depends_on:
|
|
- master
|
|
- volume
|
|
volumes:
|
|
- ./seaweedfs_filter:/data
|
|
|
|
s3:
|
|
image: chrislusf/seaweedfs
|
|
container_name: s3storage
|
|
hostname: s3storage
|
|
ports:
|
|
# Bind to Tailscale interface only (100.106.66.7)
|
|
# Accessible only within Tailscale network, not public internet
|
|
- "100.106.66.7:8333:8333"
|
|
- "100.106.66.7:9327:9327"
|
|
command: 's3 -config=/etc/seaweedfs/s3.json -filer="filer:8888" -ip.bind=0.0.0.0 -metricsPort=9327'
|
|
depends_on:
|
|
- master
|
|
- volume
|
|
- filer
|
|
volumes:
|
|
- ./s3_config.json:/etc/seaweedfs/s3.json
|
|
|
|
prometheus:
|
|
image: prom/prometheus:v2.21.0
|
|
ports:
|
|
# Bind to Tailscale interface only (100.106.66.7)
|
|
# Accessible only within Tailscale network, not public internet
|
|
- "100.106.66.7:9000:9090"
|
|
volumes:
|
|
- ./prometheus:/etc/prometheus
|
|
command: --web.enable-lifecycle --config.file=/etc/prometheus/prometheus.yml
|
|
depends_on:
|
|
- s3
|
|
```
|
|
|
|
#### Create s3_config.json.template
|
|
```json
|
|
{
|
|
"identities": [
|
|
{
|
|
"name": "anonymous",
|
|
"actions": [
|
|
"Read"
|
|
]
|
|
},
|
|
{
|
|
"name": "gitea",
|
|
"credentials": [
|
|
{
|
|
"accessKey": "op://zesticai-non-prod/gitea-s3/access-key",
|
|
"secretKey": "op://zesticai-non-prod/gitea-s3/secret-key"
|
|
}
|
|
],
|
|
"actions": [
|
|
"Admin",
|
|
"Read",
|
|
"List",
|
|
"Tagging",
|
|
"Write"
|
|
]
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Step 4: Create Caddy Configuration
|
|
**Description:** Add Gitea route to existing Caddy instance on bigbox
|
|
**Tests:** Caddy configuration is valid and can be reloaded
|
|
**Dependencies:** None
|
|
**Estimated:** 20 minutes
|
|
|
|
**Important:** Bigbox already has Caddy running with existing routes (terraphim.cloud, privacy1st.org). We will add Gitea to the existing configuration using Caddy's admin API or by reloading the config.
|
|
|
|
#### Create Caddyfile Snippet
|
|
```caddyfile
|
|
# Caddyfile snippet for Gitea on bigbox
|
|
# This adds Gitea to the existing Caddy instance
|
|
|
|
# Gitea Web Interface - Public via terraphim.cloud domain
|
|
git.terraphim.cloud {
|
|
reverse_proxy localhost:3000
|
|
|
|
# Logging
|
|
log {
|
|
output file /var/log/caddy/gitea-access.log
|
|
format json
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Deployment Options
|
|
|
|
**Option A: Add to existing Caddy via API (Recommended)**
|
|
```bash
|
|
# Add Gitea route to running Caddy instance via admin API
|
|
curl -X POST http://localhost:2019/config/apps/http/servers/srv0/routes \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"match": [{"host": ["git.terraphim.cloud"]}],
|
|
"handle": [{
|
|
"handler": "subroute",
|
|
"routes": [{
|
|
"handle": [{
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [{"dial": "localhost:3000"}]
|
|
}]
|
|
}]
|
|
}],
|
|
"terminal": true
|
|
}'
|
|
```
|
|
|
|
**Option B: Update Caddyfile and reload**
|
|
```bash
|
|
# Add the snippet to existing Caddyfile
|
|
sudo cat >> /etc/caddy/Caddyfile << 'EOF'
|
|
|
|
# Gitea Web Interface
|
|
git.terraphim.cloud {
|
|
reverse_proxy localhost:3000
|
|
log {
|
|
output file /var/log/caddy/gitea-access.log
|
|
format json
|
|
}
|
|
}
|
|
EOF
|
|
|
|
# Validate and reload
|
|
sudo caddy validate --config /etc/caddy/Caddyfile
|
|
sudo caddy reload --config /etc/caddy/Caddyfile
|
|
```
|
|
|
|
### Step 5: Update .gitignore
|
|
**Description:** Prevent committed secrets
|
|
**Tests:** .gitignore properly excludes sensitive files
|
|
**Dependencies:** Step 3
|
|
**Estimated:** 5 minutes
|
|
|
|
```bash
|
|
# Add to .gitignore
|
|
cat >> .gitignore << 'EOF'
|
|
|
|
# Secrets - Never commit these
|
|
docker-compose.yml
|
|
s3_config.json
|
|
.env
|
|
*.decrypted
|
|
*.secret
|
|
EOF
|
|
```
|
|
|
|
### Step 6: Transfer Files to Bigbox
|
|
**Description:** Transfer templates and configuration to bigbox
|
|
**Tests:** All files transferred successfully
|
|
**Dependencies:** Step 3, 4, 5
|
|
**Estimated:** 10 minutes
|
|
|
|
```bash
|
|
# Create remote directory
|
|
ssh bigbox "mkdir -p ~/gitea-stack"
|
|
|
|
# Transfer template files (not rendered files)
|
|
rsync -avz --progress \
|
|
--exclude='.git' \
|
|
--exclude='gitea' \
|
|
--exclude='postgres' \
|
|
--exclude='seaweedfs' \
|
|
--exclude='seaweedfs_filter' \
|
|
--exclude='docker-compose.yml' \
|
|
--exclude='s3_config.json' \
|
|
--exclude='.env' \
|
|
. bigbox:~/gitea-stack/
|
|
|
|
# Transfer Caddyfile separately
|
|
rsync -avz Caddyfile bigbox:~/gitea-stack/
|
|
|
|
# Verify transfer
|
|
ssh bigbox "ls -la ~/gitea-stack/"
|
|
```
|
|
|
|
### Step 7: Inject Secrets and Start Services
|
|
**Description:** Use `op inject` to populate secrets and start services
|
|
**Tests:** All containers start with correct environment variables
|
|
**Dependencies:** Step 6
|
|
**Estimated:** 15 minutes
|
|
|
|
```bash
|
|
# SSH to bigbox and execute deployment
|
|
ssh bigbox << 'REMOTESCRIPT'
|
|
|
|
# Change to project directory
|
|
cd ~/gitea-stack
|
|
|
|
# Source 1Password service account
|
|
echo "Loading 1Password credentials..."
|
|
source ~/op_zesticai_non_prod.sh
|
|
|
|
# Verify 1Password is working
|
|
echo "Verifying 1Password connection..."
|
|
op vault list
|
|
|
|
# Inject secrets into docker-compose.yml
|
|
echo "Injecting secrets into docker-compose.yml..."
|
|
op inject --in-file docker-compose.yml.template --out-file docker-compose.yml
|
|
|
|
# Inject secrets into s3_config.json
|
|
echo "Injecting secrets into s3_config.json..."
|
|
op inject --in-file s3_config.json.template --out-file s3_config.json
|
|
|
|
# Create data directories
|
|
echo "Creating data directories..."
|
|
mkdir -p gitea postgres seaweedfs seaweedfs_filter prometheus
|
|
|
|
# Set permissions for Gitea (runs as UID 1000)
|
|
chmod -R 1000:1000 gitea 2>/dev/null || sudo chown -R 1000:1000 gitea
|
|
|
|
# Start services
|
|
echo "Starting services..."
|
|
docker compose up -d
|
|
|
|
# Wait for startup
|
|
sleep 30
|
|
|
|
# Check status
|
|
echo "Checking service status..."
|
|
docker compose ps
|
|
|
|
REMOTESCRIPT
|
|
```
|
|
|
|
### Step 8: Configure Caddy (Add Gitea Route)
|
|
**Description:** Add Gitea route to existing Caddy instance on bigbox
|
|
**Tests:** Gitea route added and accessible via HTTPS
|
|
**Dependencies:** Step 7
|
|
**Estimated:** 15 minutes
|
|
|
|
**Important:** Bigbox already has Caddy running with existing services. We'll add the Gitea route without disrupting existing configuration.
|
|
|
|
```bash
|
|
ssh bigbox << 'REMOTESCRIPT'
|
|
|
|
# Create log directory
|
|
echo "Creating log directory..."
|
|
sudo mkdir -p /var/log/caddy
|
|
|
|
# Check if Caddy is running
|
|
echo "Checking Caddy status..."
|
|
if ! pgrep -x caddy > /dev/null; then
|
|
echo "ERROR: Caddy is not running on bigbox"
|
|
exit 1
|
|
fi
|
|
|
|
# Option 1: Add route via Caddy Admin API (recommended - no reload needed)
|
|
echo "Adding Gitea route to Caddy via Admin API..."
|
|
curl -s -X POST http://localhost:2019/config/apps/http/servers/srv0/routes \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"@id": "gitea_route",
|
|
"match": [{"host": ["git.terraphim.cloud"]}],
|
|
"handle": [{
|
|
"handler": "subroute",
|
|
"routes": [{
|
|
"handle": [{
|
|
"handler": "reverse_proxy",
|
|
"upstreams": [{"dial": "localhost:3000"}]
|
|
}]
|
|
}]
|
|
}],
|
|
"terminal": true
|
|
}'
|
|
|
|
# Verify route was added
|
|
echo "Verifying Caddy configuration..."
|
|
curl -s http://localhost:2019/config/apps/http/servers/srv0/routes | grep -q "git.terraphim.cloud" && echo "✓ Gitea route added successfully" || echo "✗ Failed to add Gitea route"
|
|
|
|
# Alternative: If API fails, backup to updating Caddyfile and reloading
|
|
# echo "Adding Gitea configuration to Caddyfile..."
|
|
# sudo tee -a /etc/caddy/Caddyfile > /dev/null << 'EOF'
|
|
#
|
|
# # Gitea Web Interface
|
|
# git.terraphim.cloud {
|
|
# reverse_proxy localhost:3000
|
|
# log {
|
|
# output file /var/log/caddy/gitea-access.log
|
|
# format json
|
|
# }
|
|
# }
|
|
# EOF
|
|
#
|
|
# # Validate and reload
|
|
# sudo caddy validate --config /etc/caddy/Caddyfile && sudo caddy reload --config /etc/caddy/Caddyfile
|
|
|
|
echo "Caddy configuration updated"
|
|
|
|
REMOTESCRIPT
|
|
```
|
|
|
|
### Step 9: Verify Deployment
|
|
**Description:** Validate all services are operational
|
|
**Tests:** Health checks pass for all services
|
|
**Dependencies:** Step 8
|
|
**Estimated:** 15 minutes
|
|
|
|
```bash
|
|
ssh bigbox << 'REMOTESCRIPT'
|
|
|
|
echo "=== Verifying Service Health ==="
|
|
|
|
# Test Gitea (via Caddy - public HTTPS)
|
|
echo "Testing Gitea via Caddy..."
|
|
curl -s -o /dev/null -w "Gitea HTTPS Status: %{http_code}\n" https://git.terraphim.cloud/api/healthz 2>/dev/null || echo "Gitea HTTPS not ready (DNS may need propagation)"
|
|
|
|
# Test Gitea (direct - localhost)
|
|
echo "Testing Gitea directly..."
|
|
curl -s -o /dev/null -w "Gitea HTTP Status: %{http_code}\n" http://localhost:3000/api/healthz || echo "Gitea not ready"
|
|
|
|
# Test S3 API (via Tailscale IP)
|
|
echo "Testing SeaweedFS S3 (Tailscale)..."
|
|
curl -s http://100.106.66.7:8333 > /dev/null && echo "S3 API (Tailscale): OK" || echo "S3 not ready"
|
|
|
|
# Test Prometheus (via Tailscale IP)
|
|
echo "Testing Prometheus (Tailscale)..."
|
|
curl -s http://100.106.66.7:9000/-/healthy > /dev/null && echo "Prometheus (Tailscale): OK" || echo "Prometheus not ready"
|
|
|
|
# Test SSH port
|
|
echo "Testing SSH port..."
|
|
nc -zv localhost 222 2>&1 | grep -q succeeded && echo "SSH port: OK" || echo "SSH port not responding"
|
|
|
|
# Check container logs for errors
|
|
echo ""
|
|
echo "=== Checking for errors in logs ==="
|
|
cd ~/gitea-stack
|
|
docker compose logs --tail=20 | grep -i error || echo "No errors found"
|
|
|
|
echo ""
|
|
echo "=== Container Status ==="
|
|
docker compose ps
|
|
|
|
REMOTESCRIPT
|
|
```
|
|
|
|
### Step 10: Document Access Endpoints
|
|
**Description:** Record access endpoints for the user
|
|
**Tests:** Documentation complete with all URLs
|
|
**Dependencies:** Step 9
|
|
**Estimated:** 10 minutes
|
|
|
|
**Public Access (via Caddy HTTPS):**
|
|
- Gitea Web: https://git.terraphim.cloud
|
|
- Gitea SSH: ssh://bigbox:222 (direct, not through Caddy)
|
|
|
|
**Tailscale Network Only (100.106.66.7):**
|
|
- Prometheus: http://100.106.66.7:9000
|
|
- SeaweedFS S3: http://100.106.66.7:8333
|
|
- SeaweedFS S3 Metrics: http://100.106.66.7:9327
|
|
|
|
**Internal Docker Network (no external access):**
|
|
- PostgreSQL: db:5432 (internal only)
|
|
- SeaweedFS Master: master:9333
|
|
- SeaweedFS Volume: volume:8080
|
|
- SeaweedFS Filer: filer:8888
|
|
|
|
**Local Development Access:**
|
|
- Gitea (direct): http://localhost:3000
|
|
|
|
**Important Notes:**
|
|
- Prometheus and S3 are NOT accessible from the public internet
|
|
- They are only accessible within the Tailscale VPN network
|
|
- Connect to Tailscale first: `tailscale up`
|
|
|
|
## Test Strategy
|
|
|
|
### Deployment Tests
|
|
| Test | Location | Purpose |
|
|
|------|----------|---------|
|
|
| SSH connectivity | Step 1 | Verify remote access |
|
|
| 1Password auth | Step 1 | Verify service account |
|
|
| Caddy validation | Step 8 | Config syntax correct |
|
|
| Secret injection | Step 7 | op:// references resolve |
|
|
| Service startup | Step 7 | All containers start |
|
|
| Caddy routing | Step 9 | HTTP requests routed correctly |
|
|
|
|
### Validation Commands
|
|
```bash
|
|
# Verify containers running
|
|
ssh bigbox "docker compose ps"
|
|
|
|
# Test Caddy routing
|
|
ssh bigbox "curl -s -o /dev/null -w '%{http_code}' http://localhost:3000"
|
|
|
|
# Verify no hardcoded secrets in templates
|
|
ssh bigbox "grep -r 'op://' ~/gitea-stack/*.template"
|
|
|
|
# Verify secrets resolved in compose file
|
|
ssh bigbox "grep -E 'PASSWORD|SECRET|KEY' ~/gitea-stack/docker-compose.yml | head -5"
|
|
|
|
# Test 1Password connectivity
|
|
ssh bigbox "source ~/op_zesticai_non_prod.sh && op item list"
|
|
```
|
|
|
|
## Rollback Plan
|
|
|
|
If deployment fails:
|
|
1. Stop services: `ssh bigbox "cd ~/gitea-stack && docker compose down"`
|
|
2. Stop Caddy: `ssh bigbox "sudo pkill caddy || sudo systemctl stop caddy"`
|
|
3. Remove data directories: `ssh bigbox "rm -rf ~/gitea-stack"`
|
|
4. Revert Caddy config: `ssh bigbox "sudo rm /etc/caddy/Caddyfile"`
|
|
5. Debug and retry
|
|
|
|
## Security Considerations
|
|
|
|
### Immediate Actions Post-Deployment
|
|
1. Change Gitea admin password after first login
|
|
2. Verify no secrets in git repository
|
|
3. Review Caddy access logs regularly
|
|
4. Set up firewall rules if needed
|
|
|
|
### Security Checklist
|
|
- [ ] No hardcoded secrets in git
|
|
- [ ] All secrets injected via 1Password
|
|
- [ ] Caddy automatically manages SSL certificates
|
|
- [ ] Direct service ports not exposed externally (except 222 for SSH)
|
|
- [ ] 1Password service account token secured on bigbox
|
|
|
|
### 1Password Security
|
|
- Service account token stored in `~/op_zesticai_non_prod.sh`
|
|
- Token has limited scope (zesticai-non-prod vault)
|
|
- File permissions should be restricted: `chmod 600 ~/op_zesticai_non_prod.sh`
|
|
|
|
## Open Items
|
|
|
|
| Item | Status | Owner |
|
|
|------|--------|-------|
|
|
| Create 1Password vault items | Pending | Deployer |
|
|
| Determine Caddy domain names | Pending | Deployer |
|
|
| Verify 80/443 port availability | Pending | Deployer |
|
|
| Set up DNS records | Pending | Deployer (if using real domain) |
|
|
|
|
## Deployment Commands Reference
|
|
|
|
### Quick Deploy (Full)
|
|
```bash
|
|
# 1. Create 1Password items locally
|
|
source ~/op_zesticai_non_prod.sh
|
|
op item create --vault="zesticai-non-prod" --category=login --title="gitea-postgres" --generate-password=20 username=gitea
|
|
op item create --vault="zesticai-non-prod" --category=custom --title="gitea-s3" access-key="$(openssl rand -hex 20)" secret-key="$(openssl rand -hex 40)"
|
|
|
|
# 2. Transfer files
|
|
rsync -avz --exclude='.git' --exclude='gitea' --exclude='postgres' \
|
|
--exclude='seaweedfs' --exclude='seaweedfs_filter' \
|
|
--exclude='docker-compose.yml' --exclude='s3_config.json' \
|
|
. bigbox:~/gitea-stack/
|
|
|
|
# 3. Deploy on bigbox
|
|
ssh bigbox "cd ~/gitea-stack && source ~/op_zesticai_non_prod.sh && op inject --in-file docker-compose.yml.template --out-file docker-compose.yml && op inject --in-file s3_config.json.template --out-file s3_config.json && docker compose up -d"
|
|
|
|
# 4. Configure Caddy
|
|
ssh bigbox "sudo cp ~/gitea-stack/Caddyfile /etc/caddy/Caddyfile && sudo caddy validate --config /etc/caddy/Caddyfile && sudo caddy run --config /etc/caddy/Caddyfile &"
|
|
```
|
|
|
|
### Status Check
|
|
```bash
|
|
ssh bigbox "cd ~/gitea-stack && docker compose ps"
|
|
ssh bigbox "curl -s http://localhost:3000/api/healthz && echo 'Gitea OK'"
|
|
ssh bigbox "sudo caddy list-modules 2>&1 | head -5"
|
|
```
|
|
|
|
### Stop Services
|
|
```bash
|
|
ssh bigbox "cd ~/gitea-stack && docker compose down"
|
|
ssh bigbox "sudo pkill caddy || sudo systemctl stop caddy"
|
|
```
|
|
|
|
### View Logs
|
|
```bash
|
|
ssh bigbox "cd ~/gitea-stack && docker compose logs -f"
|
|
ssh bigbox "sudo tail -f /var/log/caddy/gitea-access.log"
|
|
```
|
|
|
|
## Approval
|
|
|
|
- [ ] Research document reviewed
|
|
- [ ] Implementation plan reviewed
|
|
- [ ] 1Password vault structure defined
|
|
- [ ] Security considerations acknowledged
|
|
- [ ] Rollback plan understood
|
|
- [ ] Ready to proceed with deployment
|
|
|
|
---
|
|
|
|
**Next Steps After Approval:**
|
|
1. Create 1Password vault items
|
|
2. Create template files locally
|
|
3. Transfer to bigbox
|
|
4. Execute deployment
|
|
5. Validate all services via Caddy
|