add deployment scripts

This commit is contained in:
Evan Jarrett
2025-10-10 14:14:35 -05:00
parent 3fad473910
commit fa59c35bef
4 changed files with 1127 additions and 0 deletions
+167
View File
@@ -0,0 +1,167 @@
# ATCR Production Environment Configuration
# Copy this file to .env and fill in your values
#
# Usage:
# 1. cp deploy/.env.prod.template .env
# 2. Edit .env with your configuration
# 3. systemctl restart atcr
#
# NOTE: This file is loaded by docker-compose.prod.yml
# ==============================================================================
# Domain Configuration
# ==============================================================================
# Main AppView domain (registry API + web UI)
# REQUIRED: Update with your domain
APPVIEW_DOMAIN=atcr.io
# Hold service domain (presigned URL generator)
# REQUIRED: Update with your domain
HOLD_DOMAIN=hold01.atcr.io
# ==============================================================================
# Hold Service Configuration
# ==============================================================================
# Your ATProto DID (REQUIRED for hold registration)
# Get your DID from: https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social
# Example: did:plc:abc123xyz789
HOLD_OWNER=did:plc:pddp4xt5lgnv2qsegbzzs4xg
# Allow public blob reads (pulls) without authentication
# - true: Anyone can pull images (read-only)
# - false: Only authenticated users can pull
# Default: false (private)
HOLD_PUBLIC=false
# ==============================================================================
# S3/UpCloud Object Storage Configuration
# ==============================================================================
# Storage driver type
# Options: s3, filesystem
# Default: s3
STORAGE_DRIVER=s3
# S3 Access Credentials
# Get these from UpCloud Object Storage console
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
# S3 Region
# UpCloud regions: us-chi1, us-nyc1, de-fra1, uk-lon1, sg-sin1, etc.
# Default: us-chi1
AWS_REGION=us-chi1
# S3 Bucket Name
# Create this bucket in UpCloud Object Storage
# Example: atcr-blobs
S3_BUCKET=atcr
# S3 Endpoint (for custom domain or UpCloud endpoint)
# If using custom domain (blobs.atcr.io):
# S3_ENDPOINT=https://blobs.atcr.io
# If using UpCloud default endpoint:
# S3_ENDPOINT=https://s3.us-chi1.upcloudobjects.com
#
# IMPORTANT: If using custom domain, create CNAME:
# blobs.atcr.io → [bucket].us-chi1.upcloudobjects.com
# (with Cloudflare proxy DISABLED - gray cloud)
S3_ENDPOINT=https://blobs.atcr.io
# S3 Region Endpoint (alternative to S3_ENDPOINT)
# Use this if your S3 driver requires region-specific endpoint format
# Example: s3.us-chi1.upcloudobjects.com
# S3_REGION_ENDPOINT=
# ==============================================================================
# AppView Configuration
# ==============================================================================
# JWT token expiration in seconds
# Default: 300 (5 minutes)
ATCR_TOKEN_EXPIRATION=300
# Enable web UI
# Default: true
ATCR_UI_ENABLED=true
# ==============================================================================
# Logging Configuration
# ==============================================================================
# Log level: debug, info, warn, error
# Default: info
ATCR_LOG_LEVEL=info
# Log formatter: text, json
# Default: text
ATCR_LOG_FORMATTER=text
# ==============================================================================
# Jetstream Configuration (ATProto event streaming)
# ==============================================================================
# Jetstream WebSocket URL for real-time ATProto events
# Default: wss://jetstream2.us-west.bsky.network/subscribe
JETSTREAM_URL=wss://jetstream2.us-west.bsky.network/subscribe
# Enable backfill worker to sync historical records
# Default: true (recommended for production)
ATCR_BACKFILL_ENABLED=true
# ATProto relay endpoint for backfill sync API
# Default: https://relay1.us-east.bsky.network
ATCR_RELAY_ENDPOINT=https://relay1.us-east.bsky.network
# Backfill interval
# Examples: 30m, 1h, 2h, 24h
# Default: 1h
ATCR_BACKFILL_INTERVAL=1h
# ==============================================================================
# Optional: Filesystem Storage (alternative to S3)
# ==============================================================================
# If using filesystem storage instead of S3:
# 1. Uncomment these lines
# 2. Comment out all S3 variables above
# 3. Set STORAGE_DRIVER=filesystem
# STORAGE_DRIVER=filesystem
# STORAGE_ROOT_DIR=/var/lib/atcr/hold
# ==============================================================================
# Advanced Configuration
# ==============================================================================
# Override service name (defaults to APPVIEW_DOMAIN)
# ATCR_SERVICE_NAME=atcr.io
# Debug listen address (optional - for pprof debugging)
# ATCR_DEBUG_ADDR=:5001
# ==============================================================================
# CHECKLIST
# ==============================================================================
#
# Before starting ATCR, ensure you have:
#
# ☐ Set APPVIEW_DOMAIN (e.g., atcr.io)
# ☐ Set HOLD_DOMAIN (e.g., hold01.atcr.io)
# ☐ Set HOLD_OWNER (your ATProto DID)
# ☐ Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
# ☐ Set S3_BUCKET (created in UpCloud Object Storage)
# ☐ Set S3_ENDPOINT (UpCloud endpoint or custom domain)
# ☐ Configured DNS records:
# - A record: atcr.io → server IP
# - A record: hold01.atcr.io → server IP
# - CNAME: blobs.atcr.io → [bucket].us-chi1.upcloudobjects.com
# ☐ Disabled Cloudflare proxy (gray cloud, not orange)
# ☐ Waited for DNS propagation (check with: dig atcr.io)
#
# After starting:
# ☐ Complete hold OAuth registration (run: /opt/atcr/get-hold-oauth.sh)
# ☐ Test registry: docker pull atcr.io/test/image
# ☐ Monitor logs: /opt/atcr/logs.sh
+503
View File
@@ -0,0 +1,503 @@
# ATCR UpCloud Deployment Guide
This guide walks you through deploying ATCR on UpCloud with Rocky Linux.
## Architecture
- **AppView** (atcr.io) - OCI registry API + web UI
- **Hold Service** (hold01.atcr.io) - Presigned URL generator for blob storage
- **Caddy** - Reverse proxy with automatic HTTPS
- **UpCloud Object Storage** (blobs.atcr.io) - S3-compatible blob storage
## Prerequisites
### 1. UpCloud Account
- Active UpCloud account
- Object Storage enabled
- Billing configured
### 2. Domain Names
You need three DNS records:
- `atcr.io` (or your domain) - AppView
- `hold01.atcr.io` - Hold service
- `blobs.atcr.io` - S3 storage (CNAME)
### 3. ATProto Account
- Bluesky/ATProto account
- Your DID (get from: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social`)
### 4. UpCloud Object Storage Bucket
Create an S3 bucket in UpCloud Object Storage:
1. Go to UpCloud Console → Storage → Object Storage
2. Create new bucket (e.g., `atcr-blobs`)
3. Note the region (e.g., `us-chi1`)
4. Generate access credentials (Access Key ID + Secret)
5. Note the endpoint (e.g., `s3.us-chi1.upcloudobjects.com`)
## Deployment Steps
### Step 1: Configure DNS
Set up DNS records (using Cloudflare or your DNS provider):
```
Type Name Value Proxy
────────────────────────────────────────────────────────────────────────────
A atcr.io [your-upcloud-ip] ☁️ DISABLED
A hold01.atcr.io [your-upcloud-ip] ☁️ DISABLED
CNAME blobs.atcr.io atcr-blobs.us-chi1.upcloudobjects.com ☁️ DISABLED
```
**IMPORTANT:**
- **DISABLE Cloudflare proxy** (gray cloud, not orange) for all three domains
- Proxied connections break Docker registry protocol and presigned URLs
- You'll still get HTTPS via Caddy's Let's Encrypt integration
Wait for DNS propagation (5-30 minutes). Verify with:
```bash
dig atcr.io
dig hold01.atcr.io
dig blobs.atcr.io
```
### Step 2: Create UpCloud Server
1. Go to UpCloud Console → Servers → Deploy a new server
2. Select location (match your S3 region if possible)
3. Select **Rocky Linux 9** operating system
4. Choose plan (minimum: 2 GB RAM, 1 CPU)
5. Configure hostname: `atcr`
6. Enable IPv4 public networking
7. **Optional:** Enable IPv6
8. **User data:** Paste contents of `deploy/init-upcloud.sh`
- Update `ATCR_REPO` variable with your git repository URL
- Or leave empty and manually copy files later
9. Create SSH key or use password authentication
10. Click **Deploy**
### Step 3: Wait for Initialization
The init script will:
- Update system packages (~2-5 minutes)
- Install Docker and Docker Compose
- Configure firewall
- Clone repository (if ATCR_REPO configured)
- Create systemd service
- Create helper scripts
Monitor progress:
```bash
# SSH into server
ssh root@[your-upcloud-ip]
# Check cloud-init logs
tail -f /var/log/cloud-init-output.log
```
Wait for the completion message in the logs.
### Step 4: Configure Environment
Edit the environment configuration:
```bash
# SSH into server
ssh root@[your-upcloud-ip]
# Edit environment file
cd /opt/atcr
nano .env
```
**Required configuration:**
```bash
# Domains
APPVIEW_DOMAIN=atcr.io
HOLD_DOMAIN=hold01.atcr.io
# Your ATProto DID
HOLD_OWNER=did:plc:your-did-here
# UpCloud S3 credentials
AWS_ACCESS_KEY_ID=your-access-key-id
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_REGION=us-chi1
S3_BUCKET=atcr-blobs
# S3 endpoint (choose one):
# Option 1: Custom domain (recommended)
S3_ENDPOINT=https://blobs.atcr.io
# Option 2: Direct UpCloud endpoint
# S3_ENDPOINT=https://s3.us-chi1.upcloudobjects.com
# Public access (optional)
HOLD_PUBLIC=false # Set to true to allow anonymous pulls
```
Save and exit (Ctrl+X, Y, Enter).
### Step 5: Start ATCR
```bash
# Start services
systemctl start atcr
# Check status
systemctl status atcr
# Verify containers are running
docker ps
```
You should see three containers:
- `atcr-caddy`
- `atcr-appview`
- `atcr-hold`
### Step 6: Complete Hold OAuth Registration
The hold service needs to register itself with your PDS:
```bash
# Get OAuth URL from logs
/opt/atcr/get-hold-oauth.sh
```
Look for output like:
```
Visit this URL to authorize: https://bsky.social/oauth/authorize?...
```
1. Copy the URL and open in your browser
2. Log in with your ATProto account
3. Authorize the hold service
4. Return to terminal
The hold service will create records in your PDS:
- `io.atcr.hold` - Hold definition
- `io.atcr.hold.crew` - Your membership as captain
Verify registration:
```bash
docker logs atcr-hold | grep -i "success\|registered\|created"
```
### Step 7: Test the Registry
#### Test 1: Check endpoints
```bash
# AppView (should return {})
curl https://atcr.io/v2/
# Hold service (should return {"status":"ok"})
curl https://hold01.atcr.io/health
```
#### Test 2: Configure Docker client
On your local machine:
```bash
# Install credential helper
# (Build from source or download release)
go install atcr.io/cmd/docker-credential-atcr@latest
# Configure Docker
docker-credential-atcr configure
# Enter your ATProto handle when prompted
# Complete OAuth flow in browser
```
#### Test 3: Push a test image
```bash
# Tag an image
docker tag alpine:latest atcr.io/yourhandle/test:latest
# Push to ATCR
docker push atcr.io/yourhandle/test:latest
# Pull from ATCR
docker pull atcr.io/yourhandle/test:latest
```
### Step 8: Monitor and Maintain
#### View logs
```bash
# All services
/opt/atcr/logs.sh
# Specific service
/opt/atcr/logs.sh atcr-appview
/opt/atcr/logs.sh atcr-hold
/opt/atcr/logs.sh atcr-caddy
# Or use docker directly
docker logs -f atcr-appview
```
#### Restart services
```bash
# Restart all
systemctl restart atcr
# Or use docker-compose
cd /opt/atcr
docker compose -f deploy/docker-compose.prod.yml restart
```
#### Rebuild after code changes
```bash
/opt/atcr/rebuild.sh
```
#### Update configuration
```bash
# Edit environment
nano /opt/atcr/.env
# Restart services
systemctl restart atcr
```
## Architecture Details
### Service Communication
```
Internet
Caddy (443) ───────────┐
├─→ atcr-appview:5000 (Registry API + Web UI)
└─→ atcr-hold:8080 (Presigned URL generator)
UpCloud S3 (blobs.atcr.io)
```
### Data Flow: Push
```
1. docker push atcr.io/user/image:tag
2. AppView ← Docker client (manifest + blob metadata)
3. AppView → ATProto PDS (store manifest record)
4. Hold ← Docker client (request presigned URL)
5. Hold → UpCloud S3 API (generate presigned URL)
6. Hold → Docker client (return presigned URL)
7. UpCloud S3 ← Docker client (upload blob directly)
```
### Data Flow: Pull
```
1. docker pull atcr.io/user/image:tag
2. AppView ← Docker client (get manifest)
3. AppView → ATProto PDS (fetch manifest record)
4. AppView → Docker client (return manifest with holdEndpoint)
5. Hold ← Docker client (request presigned URL)
6. Hold → UpCloud S3 API (generate presigned URL)
7. Hold → Docker client (return presigned URL)
8. UpCloud S3 ← Docker client (download blob directly)
```
**Key insight:** The hold service only generates presigned URLs. Actual data transfer happens directly between Docker clients and S3, minimizing bandwidth costs.
## Troubleshooting
### Issue: "Cannot connect to registry"
**Check DNS:**
```bash
dig atcr.io
dig hold01.atcr.io
```
**Check Caddy logs:**
```bash
docker logs atcr-caddy
```
**Check firewall:**
```bash
firewall-cmd --list-all
```
### Issue: "Certificate errors"
**Verify DNS is propagated:**
```bash
curl -I https://atcr.io
```
**Check Caddy is obtaining certificates:**
```bash
docker logs atcr-caddy | grep -i certificate
```
**Common causes:**
- DNS not propagated (wait 30 minutes)
- Cloudflare proxy enabled (must be disabled)
- Port 80/443 blocked by firewall
### Issue: "Presigned URLs fail"
**Check S3 endpoint configuration:**
```bash
docker exec atcr-hold env | grep S3
```
**Verify custom domain CNAME:**
```bash
dig blobs.atcr.io CNAME
```
**Test S3 connectivity:**
```bash
docker exec atcr-hold wget -O- https://blobs.atcr.io/
```
**Common causes:**
- Cloudflare proxy enabled on blobs.atcr.io
- S3_ENDPOINT misconfigured
- AWS credentials invalid
### Issue: "Hold registration fails"
**Check hold owner DID:**
```bash
docker exec atcr-hold env | grep HOLD_OWNER
```
**Verify OAuth flow:**
```bash
/opt/atcr/get-hold-oauth.sh
```
**Manual registration:**
```bash
# Get fresh OAuth URL
docker restart atcr-hold
docker logs -f atcr-hold
```
### Issue: "High bandwidth usage"
Presigned URLs should eliminate hold bandwidth. If seeing high usage:
**Verify presigned URLs are enabled:**
```bash
docker logs atcr-hold | grep -i presigned
```
**Check S3 driver:**
```bash
docker exec atcr-hold env | grep STORAGE_DRIVER
# Should be: s3 (not filesystem)
```
**Verify direct S3 access:**
```bash
# Push should show 307 redirects in logs
docker logs -f atcr-hold
# Then push an image
```
## Security Hardening
### Firewall
```bash
# Allow only necessary ports
firewall-cmd --permanent --remove-service=cockpit
firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="your-ip" service name="ssh" accept'
firewall-cmd --reload
```
### Automatic Updates
```bash
# Install automatic updates
dnf install -y dnf-automatic
# Enable timer
systemctl enable --now dnf-automatic.timer
```
### Monitoring
```bash
# Install monitoring tools
dnf install -y htop iotop nethogs
# Monitor resources
htop
# Monitor Docker
docker stats
```
### Backups
Critical data to backup:
- `/opt/atcr/.env` - Configuration
- Docker volumes:
- `atcr-appview-data` - Auth keys, UI database, OAuth tokens
- `caddy_data` - TLS certificates
```bash
# Backup volumes
docker run --rm \
-v atcr-appview-data:/data \
-v /backup:/backup \
alpine tar czf /backup/atcr-appview-data.tar.gz /data
```
## Scaling Considerations
### Single Server (Current Setup)
- Suitable for: 100-1000 users
- Bottleneck: AppView CPU (manifest queries)
- Storage: Unlimited (S3)
### Multi-Server (Future)
- Multiple AppView instances behind load balancer
- Shared Redis for hold cache (replace in-memory cache)
- PostgreSQL for UI database (replace SQLite)
- Multiple hold services (geo-distributed)
## Cost Estimation
**UpCloud Server:**
- 2 GB RAM / 1 CPU: ~$15/month
- 4 GB RAM / 2 CPU: ~$30/month
**UpCloud Object Storage:**
- Storage: $0.01/GB/month
- Egress: $0.01/GB (first 1TB free in some regions)
**Example monthly cost:**
- Server: $15
- Storage (100GB): $1
- Transfer (500GB): $5
- **Total: ~$21/month**
**Bandwidth optimization:**
- Presigned URLs mean hold service uses minimal bandwidth
- Most costs are S3 storage + transfer (not server bandwidth)
## Support
- Documentation: https://tangled.org/@evan.jarrett.net/at-container-registry
- Issues: https://github.com/your-org/atcr.io/issues
- Bluesky: @yourhandle.bsky.social
## License
MIT
+194
View File
@@ -0,0 +1,194 @@
# ATCR Production Deployment with Caddy
# For UpCloud Rocky Linux deployment
#
# Usage:
# 1. Copy .env.prod.template to .env and fill in your values
# 2. docker compose -f deploy/docker-compose.prod.yml up -d
#
# Domains:
# - atcr.io → AppView (registry API + web UI)
# - hold01.atcr.io → Hold service (presigned URL generator)
# - blobs.atcr.io → S3 object storage (CNAME to UpCloud S3)
services:
caddy:
image: caddy:2-alpine
container_name: atcr-caddy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
environment:
APPVIEW_DOMAIN: ${APPVIEW_DOMAIN:-atcr.io}
HOLD_DOMAIN: ${HOLD_DOMAIN:-hold01.atcr.io}
volumes:
- caddy_data:/data
- caddy_config:/config
configs:
- source: caddyfile
target: /etc/caddy/Caddyfile
networks:
- atcr-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:2019/metrics"]
interval: 30s
timeout: 10s
retries: 3
start_period: 10s
atcr-appview:
build:
context: ..
dockerfile: Dockerfile.appview
image: atcr-appview:latest
container_name: atcr-appview
restart: unless-stopped
environment:
# Server configuration
ATCR_HTTP_ADDR: :5000
ATCR_BASE_URL: https://${APPVIEW_DOMAIN:-atcr.io}
ATCR_SERVICE_NAME: ${APPVIEW_DOMAIN:-atcr.io}
# Storage configuration
ATCR_DEFAULT_HOLD: https://${HOLD_DOMAIN:-hold01.atcr.io}
# Authentication
ATCR_AUTH_KEY_PATH: /var/lib/atcr/auth/private-key.pem
ATCR_AUTH_CERT_PATH: /var/lib/atcr/auth/private-key.crt
ATCR_TOKEN_EXPIRATION: ${ATCR_TOKEN_EXPIRATION:-300}
# UI configuration
ATCR_UI_ENABLED: ${ATCR_UI_ENABLED:-true}
ATCR_UI_DATABASE_PATH: /var/lib/atcr/ui.db
# Logging
ATCR_LOG_LEVEL: ${ATCR_LOG_LEVEL:-info}
ATCR_LOG_FORMATTER: ${ATCR_LOG_FORMATTER:-text}
# Jetstream configuration
JETSTREAM_URL: ${JETSTREAM_URL:-wss://jetstream2.us-west.bsky.network/subscribe}
ATCR_BACKFILL_ENABLED: ${ATCR_BACKFILL_ENABLED:-true}
ATCR_RELAY_ENDPOINT: ${ATCR_RELAY_ENDPOINT:-https://relay1.us-east.bsky.network}
ATCR_BACKFILL_INTERVAL: ${ATCR_BACKFILL_INTERVAL:-1h}
volumes:
# Persistent data: auth keys, UI database, OAuth tokens, Jetstream cache
- atcr-appview-data:/var/lib/atcr
networks:
- atcr-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/v2/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
atcr-hold:
build:
context: ..
dockerfile: Dockerfile.hold
image: atcr-hold:latest
container_name: atcr-hold
restart: unless-stopped
environment:
# Hold service configuration
HOLD_PUBLIC_URL: https://${HOLD_DOMAIN:-hold01.atcr.io}
HOLD_SERVER_ADDR: :8080
HOLD_PUBLIC: ${HOLD_PUBLIC:-false}
HOLD_OWNER: ${HOLD_OWNER}
# Storage driver
STORAGE_DRIVER: ${STORAGE_DRIVER:-s3}
# S3/UpCloud Object Storage configuration
AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
AWS_REGION: ${AWS_REGION:-us-chi1}
S3_BUCKET: ${S3_BUCKET:-atcr-blobs}
S3_ENDPOINT: ${S3_ENDPOINT}
S3_REGION_ENDPOINT: ${S3_REGION_ENDPOINT}
# Optional: Filesystem storage (comment out S3 vars above)
# STORAGE_DRIVER: filesystem
# STORAGE_ROOT_DIR: /var/lib/atcr/hold
volumes:
# Only needed for filesystem driver
# - atcr-hold-data:/var/lib/atcr/hold
# OAuth token storage for hold registration
- atcr-hold-tokens:/root/.atcr
networks:
- atcr-network
healthcheck:
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
networks:
atcr-network:
driver: bridge
ipam:
config:
- subnet: 172.28.0.0/24
volumes:
caddy_data:
driver: local
caddy_config:
driver: local
atcr-appview-data:
driver: local
atcr-hold-data:
driver: local
atcr-hold-tokens:
driver: local
configs:
caddyfile:
content: |
# ATCR AppView - Main registry + web UI
{$APPVIEW_DOMAIN} {
# Reverse proxy to AppView container
reverse_proxy atcr-appview:5000 {
# Preserve original host header
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
# Enable compression
encode gzip
# Logging
log {
output file /data/logs/appview.log {
roll_size 100mb
roll_keep 10
}
}
}
# ATCR Hold Service - Storage presigned URL generator
{$HOLD_DOMAIN} {
# Reverse proxy to Hold service container
reverse_proxy atcr-hold:8080 {
# Preserve original host header
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
# Enable compression
encode gzip
# Logging
log {
output file /data/logs/hold.log {
roll_size 100mb
roll_keep 10
}
}
}
+263
View File
@@ -0,0 +1,263 @@
#!/bin/bash
#
# ATCR UpCloud Initialization Script for Rocky Linux
#
# This script sets up ATCR on a fresh Rocky Linux instance.
# Paste this into UpCloud's "User data" field when creating a server.
#
# What it does:
# - Updates system packages
# - Installs Docker and Docker Compose
# - Configures firewall (ports 80, 443, 22)
# - Creates directory structure
# - Clones ATCR repository
# - Creates systemd service for auto-start
# - Builds and starts containers
#
# Post-deployment:
# 1. Edit /opt/atcr/.env with your configuration
# 2. Run: systemctl restart atcr
# 3. Check logs: docker logs atcr-hold (for OAuth URL)
# 4. Complete hold registration via OAuth
set -euo pipefail
# Configuration
ATCR_DIR="/opt/atcr"
ATCR_REPO="https://tangled.org/@evan.jarrett.net/at-container-registry" # UPDATE THIS
ATCR_BRANCH="main"
# Simple logging without colors (for cloud-init log compatibility)
log_info() {
echo "[INFO] $1"
}
log_warn() {
echo "[WARN] $1"
}
log_error() {
echo "[ERROR] $1"
}
# Function to check if command exists
command_exists() {
command -v "$1" >/dev/null 2>&1
}
log_info "Starting ATCR deployment on Rocky Linux..."
# Update system packages
log_info "Updating system packages..."
dnf update -y
# Install required packages
log_info "Installing prerequisites..."
dnf install -y \
git \
wget \
curl \
nano \
vim
log_info "Required ports: HTTP (80), HTTPS (443), SSH (22)"
# Install Docker
if ! command_exists docker; then
log_info "Installing Docker..."
# Add Docker repository
dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# Install Docker
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Start and enable Docker
systemctl enable --now docker
log_info "Docker installed successfully"
else
log_info "Docker already installed"
fi
# Verify Docker Compose
if ! docker compose version >/dev/null 2>&1; then
log_error "Docker Compose plugin not found. Please install manually."
exit 1
fi
log_info "Docker Compose version: $(docker compose version)"
# Create ATCR directory
log_info "Creating ATCR directory: $ATCR_DIR"
mkdir -p "$ATCR_DIR"
cd "$ATCR_DIR"
# Clone repository or create minimal structure
if [ -n "$ATCR_REPO" ] && [ "$ATCR_REPO" != "https://tangled.org/@evan.jarrett.net/at-container-registry" ]; then
log_info "Cloning ATCR repository..."
git clone -b "$ATCR_BRANCH" "$ATCR_REPO" .
else
log_warn "ATCR_REPO not configured. You'll need to manually copy files to $ATCR_DIR"
log_warn "Required files:"
log_warn " - deploy/docker-compose.prod.yml"
log_warn " - deploy/.env.prod.template"
log_warn " - Dockerfile.appview"
log_warn " - Dockerfile.hold"
fi
# Create .env file from template if it doesn't exist
if [ -f "deploy/.env.prod.template" ] && [ ! -f "$ATCR_DIR/.env" ]; then
log_info "Creating .env file from template..."
cp deploy/.env.prod.template "$ATCR_DIR/.env"
log_warn "IMPORTANT: Edit $ATCR_DIR/.env with your configuration!"
fi
# Create systemd service
log_info "Creating systemd service..."
cat > /etc/systemd/system/atcr.service <<'EOF'
[Unit]
Description=ATCR Container Registry
Requires=docker.service
After=docker.service network-online.target
Wants=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/opt/atcr
EnvironmentFile=/opt/atcr/.env
# Start containers
ExecStart=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml up -d
# Stop containers
ExecStop=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml down
# Restart containers
ExecReload=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml restart
# Always restart on failure
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
# Reload systemd
log_info "Reloading systemd daemon..."
systemctl daemon-reload
# Enable service (but don't start yet - user needs to configure .env)
systemctl enable atcr.service
log_info "Systemd service created and enabled"
# Create helper scripts
log_info "Creating helper scripts..."
# Script to rebuild and restart
cat > "$ATCR_DIR/rebuild.sh" <<'EOF'
#!/bin/bash
set -e
cd /opt/atcr
docker compose -f deploy/docker-compose.prod.yml build
docker compose -f deploy/docker-compose.prod.yml up -d
docker compose -f deploy/docker-compose.prod.yml logs -f
EOF
chmod +x "$ATCR_DIR/rebuild.sh"
# Script to view logs
cat > "$ATCR_DIR/logs.sh" <<'EOF'
#!/bin/bash
cd /opt/atcr
docker compose -f deploy/docker-compose.prod.yml logs -f "$@"
EOF
chmod +x "$ATCR_DIR/logs.sh"
# Script to get hold OAuth URL
cat > "$ATCR_DIR/get-hold-oauth.sh" <<'EOF'
#!/bin/bash
echo "Checking atcr-hold logs for OAuth registration URL..."
docker logs atcr-hold 2>&1 | grep -i "oauth\|authorization\|visit\|http" | tail -20
EOF
chmod +x "$ATCR_DIR/get-hold-oauth.sh"
log_info "Helper scripts created in $ATCR_DIR"
# Print completion message
cat <<'EOF'
================================================================================
ATCR Installation Complete!
================================================================================
NEXT STEPS:
1. Configure environment variables:
nano /opt/atcr/.env
Required settings:
- AWS_ACCESS_KEY_ID (UpCloud S3 credentials)
- AWS_SECRET_ACCESS_KEY
Pre-configured (verify these are correct):
- APPVIEW_DOMAIN=atcr.io
- HOLD_DOMAIN=hold01.atcr.io
- HOLD_OWNER=did:plc:pddp4xt5lgnv2qsegbzzs4xg
- S3_BUCKET=atcr
- S3_ENDPOINT=https://blobs.atcr.io
2. Configure UpCloud Cloud Firewall (in control panel):
Allow: TCP 22 (SSH)
Allow: TCP 80 (HTTP)
Allow: TCP 443 (HTTPS)
Drop: Everything else
3. Configure DNS (Cloudflare - DNS-only mode):
EOF
echo " A atcr.io → $(curl -s ifconfig.me || echo '[server-ip]') (gray cloud)"
echo " A hold01.atcr.io → $(curl -s ifconfig.me || echo '[server-ip]') (gray cloud)"
echo " CNAME blobs.atcr.io → atcr.us-chi1.upcloudobjects.com (gray cloud)"
cat <<'EOF'
4. Start ATCR:
systemctl start atcr
5. Complete Hold OAuth registration:
/opt/atcr/get-hold-oauth.sh
Visit the OAuth URL in your browser to authorize the hold service.
6. Check status:
systemctl status atcr
docker ps
/opt/atcr/logs.sh
Helper Scripts:
/opt/atcr/rebuild.sh - Rebuild and restart containers
/opt/atcr/logs.sh [service] - View logs (e.g., logs.sh atcr-hold)
/opt/atcr/get-hold-oauth.sh - Get hold OAuth URL
Service Management:
systemctl start atcr - Start ATCR
systemctl stop atcr - Stop ATCR
systemctl restart atcr - Restart ATCR
systemctl status atcr - Check status
Documentation:
https://tangled.org/@evan.jarrett.net/at-container-registry
IMPORTANT:
- Edit /opt/atcr/.env with S3 credentials before starting!
- Configure UpCloud cloud firewall (see step 2)
- DNS must be configured and propagated
- Cloudflare proxy must be DISABLED (gray cloud)
- Complete hold OAuth registration before first push
EOF
log_info "Installation complete. Follow the next steps above."