A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add deployment scripts

+1127
+167
deploy/.env.prod.template
··· 1 + # ATCR Production Environment Configuration 2 + # Copy this file to .env and fill in your values 3 + # 4 + # Usage: 5 + # 1. cp deploy/.env.prod.template .env 6 + # 2. Edit .env with your configuration 7 + # 3. systemctl restart atcr 8 + # 9 + # NOTE: This file is loaded by docker-compose.prod.yml 10 + 11 + # ============================================================================== 12 + # Domain Configuration 13 + # ============================================================================== 14 + 15 + # Main AppView domain (registry API + web UI) 16 + # REQUIRED: Update with your domain 17 + APPVIEW_DOMAIN=atcr.io 18 + 19 + # Hold service domain (presigned URL generator) 20 + # REQUIRED: Update with your domain 21 + HOLD_DOMAIN=hold01.atcr.io 22 + 23 + # ============================================================================== 24 + # Hold Service Configuration 25 + # ============================================================================== 26 + 27 + # Your ATProto DID (REQUIRED for hold registration) 28 + # Get your DID from: https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social 29 + # Example: did:plc:abc123xyz789 30 + HOLD_OWNER=did:plc:pddp4xt5lgnv2qsegbzzs4xg 31 + 32 + # Allow public blob reads (pulls) without authentication 33 + # - true: Anyone can pull images (read-only) 34 + # - false: Only authenticated users can pull 35 + # Default: false (private) 36 + HOLD_PUBLIC=false 37 + 38 + # ============================================================================== 39 + # S3/UpCloud Object Storage Configuration 40 + # ============================================================================== 41 + 42 + # Storage driver type 43 + # Options: s3, filesystem 44 + # Default: s3 45 + STORAGE_DRIVER=s3 46 + 47 + # S3 Access Credentials 48 + # Get these from UpCloud Object Storage console 49 + AWS_ACCESS_KEY_ID= 50 + AWS_SECRET_ACCESS_KEY= 51 + 52 + # S3 Region 53 + # UpCloud regions: us-chi1, us-nyc1, de-fra1, uk-lon1, sg-sin1, etc. 54 + # Default: us-chi1 55 + AWS_REGION=us-chi1 56 + 57 + # S3 Bucket Name 58 + # Create this bucket in UpCloud Object Storage 59 + # Example: atcr-blobs 60 + S3_BUCKET=atcr 61 + 62 + # S3 Endpoint (for custom domain or UpCloud endpoint) 63 + # If using custom domain (blobs.atcr.io): 64 + # S3_ENDPOINT=https://blobs.atcr.io 65 + # If using UpCloud default endpoint: 66 + # S3_ENDPOINT=https://s3.us-chi1.upcloudobjects.com 67 + # 68 + # IMPORTANT: If using custom domain, create CNAME: 69 + # blobs.atcr.io → [bucket].us-chi1.upcloudobjects.com 70 + # (with Cloudflare proxy DISABLED - gray cloud) 71 + S3_ENDPOINT=https://blobs.atcr.io 72 + 73 + # S3 Region Endpoint (alternative to S3_ENDPOINT) 74 + # Use this if your S3 driver requires region-specific endpoint format 75 + # Example: s3.us-chi1.upcloudobjects.com 76 + # S3_REGION_ENDPOINT= 77 + 78 + # ============================================================================== 79 + # AppView Configuration 80 + # ============================================================================== 81 + 82 + # JWT token expiration in seconds 83 + # Default: 300 (5 minutes) 84 + ATCR_TOKEN_EXPIRATION=300 85 + 86 + # Enable web UI 87 + # Default: true 88 + ATCR_UI_ENABLED=true 89 + 90 + # ============================================================================== 91 + # Logging Configuration 92 + # ============================================================================== 93 + 94 + # Log level: debug, info, warn, error 95 + # Default: info 96 + ATCR_LOG_LEVEL=info 97 + 98 + # Log formatter: text, json 99 + # Default: text 100 + ATCR_LOG_FORMATTER=text 101 + 102 + # ============================================================================== 103 + # Jetstream Configuration (ATProto event streaming) 104 + # ============================================================================== 105 + 106 + # Jetstream WebSocket URL for real-time ATProto events 107 + # Default: wss://jetstream2.us-west.bsky.network/subscribe 108 + JETSTREAM_URL=wss://jetstream2.us-west.bsky.network/subscribe 109 + 110 + # Enable backfill worker to sync historical records 111 + # Default: true (recommended for production) 112 + ATCR_BACKFILL_ENABLED=true 113 + 114 + # ATProto relay endpoint for backfill sync API 115 + # Default: https://relay1.us-east.bsky.network 116 + ATCR_RELAY_ENDPOINT=https://relay1.us-east.bsky.network 117 + 118 + # Backfill interval 119 + # Examples: 30m, 1h, 2h, 24h 120 + # Default: 1h 121 + ATCR_BACKFILL_INTERVAL=1h 122 + 123 + # ============================================================================== 124 + # Optional: Filesystem Storage (alternative to S3) 125 + # ============================================================================== 126 + 127 + # If using filesystem storage instead of S3: 128 + # 1. Uncomment these lines 129 + # 2. Comment out all S3 variables above 130 + # 3. Set STORAGE_DRIVER=filesystem 131 + 132 + # STORAGE_DRIVER=filesystem 133 + # STORAGE_ROOT_DIR=/var/lib/atcr/hold 134 + 135 + # ============================================================================== 136 + # Advanced Configuration 137 + # ============================================================================== 138 + 139 + # Override service name (defaults to APPVIEW_DOMAIN) 140 + # ATCR_SERVICE_NAME=atcr.io 141 + 142 + # Debug listen address (optional - for pprof debugging) 143 + # ATCR_DEBUG_ADDR=:5001 144 + 145 + # ============================================================================== 146 + # CHECKLIST 147 + # ============================================================================== 148 + # 149 + # Before starting ATCR, ensure you have: 150 + # 151 + # ☐ Set APPVIEW_DOMAIN (e.g., atcr.io) 152 + # ☐ Set HOLD_DOMAIN (e.g., hold01.atcr.io) 153 + # ☐ Set HOLD_OWNER (your ATProto DID) 154 + # ☐ Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY 155 + # ☐ Set S3_BUCKET (created in UpCloud Object Storage) 156 + # ☐ Set S3_ENDPOINT (UpCloud endpoint or custom domain) 157 + # ☐ Configured DNS records: 158 + # - A record: atcr.io → server IP 159 + # - A record: hold01.atcr.io → server IP 160 + # - CNAME: blobs.atcr.io → [bucket].us-chi1.upcloudobjects.com 161 + # ☐ Disabled Cloudflare proxy (gray cloud, not orange) 162 + # ☐ Waited for DNS propagation (check with: dig atcr.io) 163 + # 164 + # After starting: 165 + # ☐ Complete hold OAuth registration (run: /opt/atcr/get-hold-oauth.sh) 166 + # ☐ Test registry: docker pull atcr.io/test/image 167 + # ☐ Monitor logs: /opt/atcr/logs.sh
+503
deploy/README.md
··· 1 + # ATCR UpCloud Deployment Guide 2 + 3 + This guide walks you through deploying ATCR on UpCloud with Rocky Linux. 4 + 5 + ## Architecture 6 + 7 + - **AppView** (atcr.io) - OCI registry API + web UI 8 + - **Hold Service** (hold01.atcr.io) - Presigned URL generator for blob storage 9 + - **Caddy** - Reverse proxy with automatic HTTPS 10 + - **UpCloud Object Storage** (blobs.atcr.io) - S3-compatible blob storage 11 + 12 + ## Prerequisites 13 + 14 + ### 1. UpCloud Account 15 + - Active UpCloud account 16 + - Object Storage enabled 17 + - Billing configured 18 + 19 + ### 2. Domain Names 20 + You need three DNS records: 21 + - `atcr.io` (or your domain) - AppView 22 + - `hold01.atcr.io` - Hold service 23 + - `blobs.atcr.io` - S3 storage (CNAME) 24 + 25 + ### 3. ATProto Account 26 + - Bluesky/ATProto account 27 + - Your DID (get from: `https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social`) 28 + 29 + ### 4. UpCloud Object Storage Bucket 30 + Create an S3 bucket in UpCloud Object Storage: 31 + 1. Go to UpCloud Console → Storage → Object Storage 32 + 2. Create new bucket (e.g., `atcr-blobs`) 33 + 3. Note the region (e.g., `us-chi1`) 34 + 4. Generate access credentials (Access Key ID + Secret) 35 + 5. Note the endpoint (e.g., `s3.us-chi1.upcloudobjects.com`) 36 + 37 + ## Deployment Steps 38 + 39 + ### Step 1: Configure DNS 40 + 41 + Set up DNS records (using Cloudflare or your DNS provider): 42 + 43 + ``` 44 + Type Name Value Proxy 45 + ──────────────────────────────────────────────────────────────────────────── 46 + A atcr.io [your-upcloud-ip] ☁️ DISABLED 47 + A hold01.atcr.io [your-upcloud-ip] ☁️ DISABLED 48 + CNAME blobs.atcr.io atcr-blobs.us-chi1.upcloudobjects.com ☁️ DISABLED 49 + ``` 50 + 51 + **IMPORTANT:** 52 + - **DISABLE Cloudflare proxy** (gray cloud, not orange) for all three domains 53 + - Proxied connections break Docker registry protocol and presigned URLs 54 + - You'll still get HTTPS via Caddy's Let's Encrypt integration 55 + 56 + Wait for DNS propagation (5-30 minutes). Verify with: 57 + ```bash 58 + dig atcr.io 59 + dig hold01.atcr.io 60 + dig blobs.atcr.io 61 + ``` 62 + 63 + ### Step 2: Create UpCloud Server 64 + 65 + 1. Go to UpCloud Console → Servers → Deploy a new server 66 + 2. Select location (match your S3 region if possible) 67 + 3. Select **Rocky Linux 9** operating system 68 + 4. Choose plan (minimum: 2 GB RAM, 1 CPU) 69 + 5. Configure hostname: `atcr` 70 + 6. Enable IPv4 public networking 71 + 7. **Optional:** Enable IPv6 72 + 8. **User data:** Paste contents of `deploy/init-upcloud.sh` 73 + - Update `ATCR_REPO` variable with your git repository URL 74 + - Or leave empty and manually copy files later 75 + 9. Create SSH key or use password authentication 76 + 10. Click **Deploy** 77 + 78 + ### Step 3: Wait for Initialization 79 + 80 + The init script will: 81 + - Update system packages (~2-5 minutes) 82 + - Install Docker and Docker Compose 83 + - Configure firewall 84 + - Clone repository (if ATCR_REPO configured) 85 + - Create systemd service 86 + - Create helper scripts 87 + 88 + Monitor progress: 89 + ```bash 90 + # SSH into server 91 + ssh root@[your-upcloud-ip] 92 + 93 + # Check cloud-init logs 94 + tail -f /var/log/cloud-init-output.log 95 + ``` 96 + 97 + Wait for the completion message in the logs. 98 + 99 + ### Step 4: Configure Environment 100 + 101 + Edit the environment configuration: 102 + 103 + ```bash 104 + # SSH into server 105 + ssh root@[your-upcloud-ip] 106 + 107 + # Edit environment file 108 + cd /opt/atcr 109 + nano .env 110 + ``` 111 + 112 + **Required configuration:** 113 + 114 + ```bash 115 + # Domains 116 + APPVIEW_DOMAIN=atcr.io 117 + HOLD_DOMAIN=hold01.atcr.io 118 + 119 + # Your ATProto DID 120 + HOLD_OWNER=did:plc:your-did-here 121 + 122 + # UpCloud S3 credentials 123 + AWS_ACCESS_KEY_ID=your-access-key-id 124 + AWS_SECRET_ACCESS_KEY=your-secret-access-key 125 + AWS_REGION=us-chi1 126 + S3_BUCKET=atcr-blobs 127 + 128 + # S3 endpoint (choose one): 129 + # Option 1: Custom domain (recommended) 130 + S3_ENDPOINT=https://blobs.atcr.io 131 + # Option 2: Direct UpCloud endpoint 132 + # S3_ENDPOINT=https://s3.us-chi1.upcloudobjects.com 133 + 134 + # Public access (optional) 135 + HOLD_PUBLIC=false # Set to true to allow anonymous pulls 136 + ``` 137 + 138 + Save and exit (Ctrl+X, Y, Enter). 139 + 140 + ### Step 5: Start ATCR 141 + 142 + ```bash 143 + # Start services 144 + systemctl start atcr 145 + 146 + # Check status 147 + systemctl status atcr 148 + 149 + # Verify containers are running 150 + docker ps 151 + ``` 152 + 153 + You should see three containers: 154 + - `atcr-caddy` 155 + - `atcr-appview` 156 + - `atcr-hold` 157 + 158 + ### Step 6: Complete Hold OAuth Registration 159 + 160 + The hold service needs to register itself with your PDS: 161 + 162 + ```bash 163 + # Get OAuth URL from logs 164 + /opt/atcr/get-hold-oauth.sh 165 + ``` 166 + 167 + Look for output like: 168 + ``` 169 + Visit this URL to authorize: https://bsky.social/oauth/authorize?... 170 + ``` 171 + 172 + 1. Copy the URL and open in your browser 173 + 2. Log in with your ATProto account 174 + 3. Authorize the hold service 175 + 4. Return to terminal 176 + 177 + The hold service will create records in your PDS: 178 + - `io.atcr.hold` - Hold definition 179 + - `io.atcr.hold.crew` - Your membership as captain 180 + 181 + Verify registration: 182 + ```bash 183 + docker logs atcr-hold | grep -i "success\|registered\|created" 184 + ``` 185 + 186 + ### Step 7: Test the Registry 187 + 188 + #### Test 1: Check endpoints 189 + 190 + ```bash 191 + # AppView (should return {}) 192 + curl https://atcr.io/v2/ 193 + 194 + # Hold service (should return {"status":"ok"}) 195 + curl https://hold01.atcr.io/health 196 + ``` 197 + 198 + #### Test 2: Configure Docker client 199 + 200 + On your local machine: 201 + 202 + ```bash 203 + # Install credential helper 204 + # (Build from source or download release) 205 + go install atcr.io/cmd/docker-credential-atcr@latest 206 + 207 + # Configure Docker 208 + docker-credential-atcr configure 209 + 210 + # Enter your ATProto handle when prompted 211 + # Complete OAuth flow in browser 212 + ``` 213 + 214 + #### Test 3: Push a test image 215 + 216 + ```bash 217 + # Tag an image 218 + docker tag alpine:latest atcr.io/yourhandle/test:latest 219 + 220 + # Push to ATCR 221 + docker push atcr.io/yourhandle/test:latest 222 + 223 + # Pull from ATCR 224 + docker pull atcr.io/yourhandle/test:latest 225 + ``` 226 + 227 + ### Step 8: Monitor and Maintain 228 + 229 + #### View logs 230 + 231 + ```bash 232 + # All services 233 + /opt/atcr/logs.sh 234 + 235 + # Specific service 236 + /opt/atcr/logs.sh atcr-appview 237 + /opt/atcr/logs.sh atcr-hold 238 + /opt/atcr/logs.sh atcr-caddy 239 + 240 + # Or use docker directly 241 + docker logs -f atcr-appview 242 + ``` 243 + 244 + #### Restart services 245 + 246 + ```bash 247 + # Restart all 248 + systemctl restart atcr 249 + 250 + # Or use docker-compose 251 + cd /opt/atcr 252 + docker compose -f deploy/docker-compose.prod.yml restart 253 + ``` 254 + 255 + #### Rebuild after code changes 256 + 257 + ```bash 258 + /opt/atcr/rebuild.sh 259 + ``` 260 + 261 + #### Update configuration 262 + 263 + ```bash 264 + # Edit environment 265 + nano /opt/atcr/.env 266 + 267 + # Restart services 268 + systemctl restart atcr 269 + ``` 270 + 271 + ## Architecture Details 272 + 273 + ### Service Communication 274 + 275 + ``` 276 + Internet 277 + 278 + Caddy (443) ───────────┐ 279 + ├─→ atcr-appview:5000 (Registry API + Web UI) 280 + └─→ atcr-hold:8080 (Presigned URL generator) 281 + 282 + UpCloud S3 (blobs.atcr.io) 283 + ``` 284 + 285 + ### Data Flow: Push 286 + 287 + ``` 288 + 1. docker push atcr.io/user/image:tag 289 + 2. AppView ← Docker client (manifest + blob metadata) 290 + 3. AppView → ATProto PDS (store manifest record) 291 + 4. Hold ← Docker client (request presigned URL) 292 + 5. Hold → UpCloud S3 API (generate presigned URL) 293 + 6. Hold → Docker client (return presigned URL) 294 + 7. UpCloud S3 ← Docker client (upload blob directly) 295 + ``` 296 + 297 + ### Data Flow: Pull 298 + 299 + ``` 300 + 1. docker pull atcr.io/user/image:tag 301 + 2. AppView ← Docker client (get manifest) 302 + 3. AppView → ATProto PDS (fetch manifest record) 303 + 4. AppView → Docker client (return manifest with holdEndpoint) 304 + 5. Hold ← Docker client (request presigned URL) 305 + 6. Hold → UpCloud S3 API (generate presigned URL) 306 + 7. Hold → Docker client (return presigned URL) 307 + 8. UpCloud S3 ← Docker client (download blob directly) 308 + ``` 309 + 310 + **Key insight:** The hold service only generates presigned URLs. Actual data transfer happens directly between Docker clients and S3, minimizing bandwidth costs. 311 + 312 + ## Troubleshooting 313 + 314 + ### Issue: "Cannot connect to registry" 315 + 316 + **Check DNS:** 317 + ```bash 318 + dig atcr.io 319 + dig hold01.atcr.io 320 + ``` 321 + 322 + **Check Caddy logs:** 323 + ```bash 324 + docker logs atcr-caddy 325 + ``` 326 + 327 + **Check firewall:** 328 + ```bash 329 + firewall-cmd --list-all 330 + ``` 331 + 332 + ### Issue: "Certificate errors" 333 + 334 + **Verify DNS is propagated:** 335 + ```bash 336 + curl -I https://atcr.io 337 + ``` 338 + 339 + **Check Caddy is obtaining certificates:** 340 + ```bash 341 + docker logs atcr-caddy | grep -i certificate 342 + ``` 343 + 344 + **Common causes:** 345 + - DNS not propagated (wait 30 minutes) 346 + - Cloudflare proxy enabled (must be disabled) 347 + - Port 80/443 blocked by firewall 348 + 349 + ### Issue: "Presigned URLs fail" 350 + 351 + **Check S3 endpoint configuration:** 352 + ```bash 353 + docker exec atcr-hold env | grep S3 354 + ``` 355 + 356 + **Verify custom domain CNAME:** 357 + ```bash 358 + dig blobs.atcr.io CNAME 359 + ``` 360 + 361 + **Test S3 connectivity:** 362 + ```bash 363 + docker exec atcr-hold wget -O- https://blobs.atcr.io/ 364 + ``` 365 + 366 + **Common causes:** 367 + - Cloudflare proxy enabled on blobs.atcr.io 368 + - S3_ENDPOINT misconfigured 369 + - AWS credentials invalid 370 + 371 + ### Issue: "Hold registration fails" 372 + 373 + **Check hold owner DID:** 374 + ```bash 375 + docker exec atcr-hold env | grep HOLD_OWNER 376 + ``` 377 + 378 + **Verify OAuth flow:** 379 + ```bash 380 + /opt/atcr/get-hold-oauth.sh 381 + ``` 382 + 383 + **Manual registration:** 384 + ```bash 385 + # Get fresh OAuth URL 386 + docker restart atcr-hold 387 + docker logs -f atcr-hold 388 + ``` 389 + 390 + ### Issue: "High bandwidth usage" 391 + 392 + Presigned URLs should eliminate hold bandwidth. If seeing high usage: 393 + 394 + **Verify presigned URLs are enabled:** 395 + ```bash 396 + docker logs atcr-hold | grep -i presigned 397 + ``` 398 + 399 + **Check S3 driver:** 400 + ```bash 401 + docker exec atcr-hold env | grep STORAGE_DRIVER 402 + # Should be: s3 (not filesystem) 403 + ``` 404 + 405 + **Verify direct S3 access:** 406 + ```bash 407 + # Push should show 307 redirects in logs 408 + docker logs -f atcr-hold 409 + # Then push an image 410 + ``` 411 + 412 + ## Security Hardening 413 + 414 + ### Firewall 415 + 416 + ```bash 417 + # Allow only necessary ports 418 + firewall-cmd --permanent --remove-service=cockpit 419 + firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="your-ip" service name="ssh" accept' 420 + firewall-cmd --reload 421 + ``` 422 + 423 + ### Automatic Updates 424 + 425 + ```bash 426 + # Install automatic updates 427 + dnf install -y dnf-automatic 428 + 429 + # Enable timer 430 + systemctl enable --now dnf-automatic.timer 431 + ``` 432 + 433 + ### Monitoring 434 + 435 + ```bash 436 + # Install monitoring tools 437 + dnf install -y htop iotop nethogs 438 + 439 + # Monitor resources 440 + htop 441 + 442 + # Monitor Docker 443 + docker stats 444 + ``` 445 + 446 + ### Backups 447 + 448 + Critical data to backup: 449 + - `/opt/atcr/.env` - Configuration 450 + - Docker volumes: 451 + - `atcr-appview-data` - Auth keys, UI database, OAuth tokens 452 + - `caddy_data` - TLS certificates 453 + 454 + ```bash 455 + # Backup volumes 456 + docker run --rm \ 457 + -v atcr-appview-data:/data \ 458 + -v /backup:/backup \ 459 + alpine tar czf /backup/atcr-appview-data.tar.gz /data 460 + ``` 461 + 462 + ## Scaling Considerations 463 + 464 + ### Single Server (Current Setup) 465 + - Suitable for: 100-1000 users 466 + - Bottleneck: AppView CPU (manifest queries) 467 + - Storage: Unlimited (S3) 468 + 469 + ### Multi-Server (Future) 470 + - Multiple AppView instances behind load balancer 471 + - Shared Redis for hold cache (replace in-memory cache) 472 + - PostgreSQL for UI database (replace SQLite) 473 + - Multiple hold services (geo-distributed) 474 + 475 + ## Cost Estimation 476 + 477 + **UpCloud Server:** 478 + - 2 GB RAM / 1 CPU: ~$15/month 479 + - 4 GB RAM / 2 CPU: ~$30/month 480 + 481 + **UpCloud Object Storage:** 482 + - Storage: $0.01/GB/month 483 + - Egress: $0.01/GB (first 1TB free in some regions) 484 + 485 + **Example monthly cost:** 486 + - Server: $15 487 + - Storage (100GB): $1 488 + - Transfer (500GB): $5 489 + - **Total: ~$21/month** 490 + 491 + **Bandwidth optimization:** 492 + - Presigned URLs mean hold service uses minimal bandwidth 493 + - Most costs are S3 storage + transfer (not server bandwidth) 494 + 495 + ## Support 496 + 497 + - Documentation: https://tangled.org/@evan.jarrett.net/at-container-registry 498 + - Issues: https://github.com/your-org/atcr.io/issues 499 + - Bluesky: @yourhandle.bsky.social 500 + 501 + ## License 502 + 503 + MIT
+194
deploy/docker-compose.prod.yml
··· 1 + # ATCR Production Deployment with Caddy 2 + # For UpCloud Rocky Linux deployment 3 + # 4 + # Usage: 5 + # 1. Copy .env.prod.template to .env and fill in your values 6 + # 2. docker compose -f deploy/docker-compose.prod.yml up -d 7 + # 8 + # Domains: 9 + # - atcr.io → AppView (registry API + web UI) 10 + # - hold01.atcr.io → Hold service (presigned URL generator) 11 + # - blobs.atcr.io → S3 object storage (CNAME to UpCloud S3) 12 + 13 + services: 14 + caddy: 15 + image: caddy:2-alpine 16 + container_name: atcr-caddy 17 + restart: unless-stopped 18 + ports: 19 + - "80:80" 20 + - "443:443" 21 + - "443:443/udp" # HTTP/3 22 + environment: 23 + APPVIEW_DOMAIN: ${APPVIEW_DOMAIN:-atcr.io} 24 + HOLD_DOMAIN: ${HOLD_DOMAIN:-hold01.atcr.io} 25 + volumes: 26 + - caddy_data:/data 27 + - caddy_config:/config 28 + configs: 29 + - source: caddyfile 30 + target: /etc/caddy/Caddyfile 31 + networks: 32 + - atcr-network 33 + healthcheck: 34 + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:2019/metrics"] 35 + interval: 30s 36 + timeout: 10s 37 + retries: 3 38 + start_period: 10s 39 + 40 + atcr-appview: 41 + build: 42 + context: .. 43 + dockerfile: Dockerfile.appview 44 + image: atcr-appview:latest 45 + container_name: atcr-appview 46 + restart: unless-stopped 47 + environment: 48 + # Server configuration 49 + ATCR_HTTP_ADDR: :5000 50 + ATCR_BASE_URL: https://${APPVIEW_DOMAIN:-atcr.io} 51 + ATCR_SERVICE_NAME: ${APPVIEW_DOMAIN:-atcr.io} 52 + 53 + # Storage configuration 54 + ATCR_DEFAULT_HOLD: https://${HOLD_DOMAIN:-hold01.atcr.io} 55 + 56 + # Authentication 57 + ATCR_AUTH_KEY_PATH: /var/lib/atcr/auth/private-key.pem 58 + ATCR_AUTH_CERT_PATH: /var/lib/atcr/auth/private-key.crt 59 + ATCR_TOKEN_EXPIRATION: ${ATCR_TOKEN_EXPIRATION:-300} 60 + 61 + # UI configuration 62 + ATCR_UI_ENABLED: ${ATCR_UI_ENABLED:-true} 63 + ATCR_UI_DATABASE_PATH: /var/lib/atcr/ui.db 64 + 65 + # Logging 66 + ATCR_LOG_LEVEL: ${ATCR_LOG_LEVEL:-info} 67 + ATCR_LOG_FORMATTER: ${ATCR_LOG_FORMATTER:-text} 68 + 69 + # Jetstream configuration 70 + JETSTREAM_URL: ${JETSTREAM_URL:-wss://jetstream2.us-west.bsky.network/subscribe} 71 + ATCR_BACKFILL_ENABLED: ${ATCR_BACKFILL_ENABLED:-true} 72 + ATCR_RELAY_ENDPOINT: ${ATCR_RELAY_ENDPOINT:-https://relay1.us-east.bsky.network} 73 + ATCR_BACKFILL_INTERVAL: ${ATCR_BACKFILL_INTERVAL:-1h} 74 + volumes: 75 + # Persistent data: auth keys, UI database, OAuth tokens, Jetstream cache 76 + - atcr-appview-data:/var/lib/atcr 77 + networks: 78 + - atcr-network 79 + healthcheck: 80 + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:5000/v2/"] 81 + interval: 30s 82 + timeout: 10s 83 + retries: 3 84 + start_period: 30s 85 + 86 + atcr-hold: 87 + build: 88 + context: .. 89 + dockerfile: Dockerfile.hold 90 + image: atcr-hold:latest 91 + container_name: atcr-hold 92 + restart: unless-stopped 93 + environment: 94 + # Hold service configuration 95 + HOLD_PUBLIC_URL: https://${HOLD_DOMAIN:-hold01.atcr.io} 96 + HOLD_SERVER_ADDR: :8080 97 + HOLD_PUBLIC: ${HOLD_PUBLIC:-false} 98 + HOLD_OWNER: ${HOLD_OWNER} 99 + 100 + # Storage driver 101 + STORAGE_DRIVER: ${STORAGE_DRIVER:-s3} 102 + 103 + # S3/UpCloud Object Storage configuration 104 + AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID} 105 + AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY} 106 + AWS_REGION: ${AWS_REGION:-us-chi1} 107 + S3_BUCKET: ${S3_BUCKET:-atcr-blobs} 108 + S3_ENDPOINT: ${S3_ENDPOINT} 109 + S3_REGION_ENDPOINT: ${S3_REGION_ENDPOINT} 110 + 111 + # Optional: Filesystem storage (comment out S3 vars above) 112 + # STORAGE_DRIVER: filesystem 113 + # STORAGE_ROOT_DIR: /var/lib/atcr/hold 114 + volumes: 115 + # Only needed for filesystem driver 116 + # - atcr-hold-data:/var/lib/atcr/hold 117 + # OAuth token storage for hold registration 118 + - atcr-hold-tokens:/root/.atcr 119 + networks: 120 + - atcr-network 121 + healthcheck: 122 + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] 123 + interval: 30s 124 + timeout: 10s 125 + retries: 3 126 + start_period: 30s 127 + 128 + networks: 129 + atcr-network: 130 + driver: bridge 131 + ipam: 132 + config: 133 + - subnet: 172.28.0.0/24 134 + 135 + volumes: 136 + caddy_data: 137 + driver: local 138 + caddy_config: 139 + driver: local 140 + atcr-appview-data: 141 + driver: local 142 + atcr-hold-data: 143 + driver: local 144 + atcr-hold-tokens: 145 + driver: local 146 + 147 + configs: 148 + caddyfile: 149 + content: | 150 + # ATCR AppView - Main registry + web UI 151 + {$APPVIEW_DOMAIN} { 152 + # Reverse proxy to AppView container 153 + reverse_proxy atcr-appview:5000 { 154 + # Preserve original host header 155 + header_up Host {host} 156 + header_up X-Real-IP {remote_host} 157 + header_up X-Forwarded-For {remote_host} 158 + header_up X-Forwarded-Proto {scheme} 159 + } 160 + 161 + # Enable compression 162 + encode gzip 163 + 164 + # Logging 165 + log { 166 + output file /data/logs/appview.log { 167 + roll_size 100mb 168 + roll_keep 10 169 + } 170 + } 171 + } 172 + 173 + # ATCR Hold Service - Storage presigned URL generator 174 + {$HOLD_DOMAIN} { 175 + # Reverse proxy to Hold service container 176 + reverse_proxy atcr-hold:8080 { 177 + # Preserve original host header 178 + header_up Host {host} 179 + header_up X-Real-IP {remote_host} 180 + header_up X-Forwarded-For {remote_host} 181 + header_up X-Forwarded-Proto {scheme} 182 + } 183 + 184 + # Enable compression 185 + encode gzip 186 + 187 + # Logging 188 + log { 189 + output file /data/logs/hold.log { 190 + roll_size 100mb 191 + roll_keep 10 192 + } 193 + } 194 + }
+263
deploy/init-upcloud.sh
··· 1 + #!/bin/bash 2 + # 3 + # ATCR UpCloud Initialization Script for Rocky Linux 4 + # 5 + # This script sets up ATCR on a fresh Rocky Linux instance. 6 + # Paste this into UpCloud's "User data" field when creating a server. 7 + # 8 + # What it does: 9 + # - Updates system packages 10 + # - Installs Docker and Docker Compose 11 + # - Configures firewall (ports 80, 443, 22) 12 + # - Creates directory structure 13 + # - Clones ATCR repository 14 + # - Creates systemd service for auto-start 15 + # - Builds and starts containers 16 + # 17 + # Post-deployment: 18 + # 1. Edit /opt/atcr/.env with your configuration 19 + # 2. Run: systemctl restart atcr 20 + # 3. Check logs: docker logs atcr-hold (for OAuth URL) 21 + # 4. Complete hold registration via OAuth 22 + 23 + set -euo pipefail 24 + 25 + # Configuration 26 + ATCR_DIR="/opt/atcr" 27 + ATCR_REPO="https://tangled.org/@evan.jarrett.net/at-container-registry" # UPDATE THIS 28 + ATCR_BRANCH="main" 29 + 30 + # Simple logging without colors (for cloud-init log compatibility) 31 + log_info() { 32 + echo "[INFO] $1" 33 + } 34 + 35 + log_warn() { 36 + echo "[WARN] $1" 37 + } 38 + 39 + log_error() { 40 + echo "[ERROR] $1" 41 + } 42 + 43 + # Function to check if command exists 44 + command_exists() { 45 + command -v "$1" >/dev/null 2>&1 46 + } 47 + 48 + log_info "Starting ATCR deployment on Rocky Linux..." 49 + 50 + # Update system packages 51 + log_info "Updating system packages..." 52 + dnf update -y 53 + 54 + # Install required packages 55 + log_info "Installing prerequisites..." 56 + dnf install -y \ 57 + git \ 58 + wget \ 59 + curl \ 60 + nano \ 61 + vim 62 + 63 + log_info "Required ports: HTTP (80), HTTPS (443), SSH (22)" 64 + 65 + # Install Docker 66 + if ! command_exists docker; then 67 + log_info "Installing Docker..." 68 + 69 + # Add Docker repository 70 + dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo 71 + 72 + # Install Docker 73 + dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 74 + 75 + # Start and enable Docker 76 + systemctl enable --now docker 77 + 78 + log_info "Docker installed successfully" 79 + else 80 + log_info "Docker already installed" 81 + fi 82 + 83 + # Verify Docker Compose 84 + if ! docker compose version >/dev/null 2>&1; then 85 + log_error "Docker Compose plugin not found. Please install manually." 86 + exit 1 87 + fi 88 + 89 + log_info "Docker Compose version: $(docker compose version)" 90 + 91 + # Create ATCR directory 92 + log_info "Creating ATCR directory: $ATCR_DIR" 93 + mkdir -p "$ATCR_DIR" 94 + cd "$ATCR_DIR" 95 + 96 + # Clone repository or create minimal structure 97 + if [ -n "$ATCR_REPO" ] && [ "$ATCR_REPO" != "https://tangled.org/@evan.jarrett.net/at-container-registry" ]; then 98 + log_info "Cloning ATCR repository..." 99 + git clone -b "$ATCR_BRANCH" "$ATCR_REPO" . 100 + else 101 + log_warn "ATCR_REPO not configured. You'll need to manually copy files to $ATCR_DIR" 102 + log_warn "Required files:" 103 + log_warn " - deploy/docker-compose.prod.yml" 104 + log_warn " - deploy/.env.prod.template" 105 + log_warn " - Dockerfile.appview" 106 + log_warn " - Dockerfile.hold" 107 + fi 108 + 109 + # Create .env file from template if it doesn't exist 110 + if [ -f "deploy/.env.prod.template" ] && [ ! -f "$ATCR_DIR/.env" ]; then 111 + log_info "Creating .env file from template..." 112 + cp deploy/.env.prod.template "$ATCR_DIR/.env" 113 + log_warn "IMPORTANT: Edit $ATCR_DIR/.env with your configuration!" 114 + fi 115 + 116 + # Create systemd service 117 + log_info "Creating systemd service..." 118 + cat > /etc/systemd/system/atcr.service <<'EOF' 119 + [Unit] 120 + Description=ATCR Container Registry 121 + Requires=docker.service 122 + After=docker.service network-online.target 123 + Wants=network-online.target 124 + 125 + [Service] 126 + Type=oneshot 127 + RemainAfterExit=yes 128 + WorkingDirectory=/opt/atcr 129 + EnvironmentFile=/opt/atcr/.env 130 + 131 + # Start containers 132 + ExecStart=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml up -d 133 + 134 + # Stop containers 135 + ExecStop=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml down 136 + 137 + # Restart containers 138 + ExecReload=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml restart 139 + 140 + # Always restart on failure 141 + Restart=on-failure 142 + RestartSec=10 143 + 144 + [Install] 145 + WantedBy=multi-user.target 146 + EOF 147 + 148 + # Reload systemd 149 + log_info "Reloading systemd daemon..." 150 + systemctl daemon-reload 151 + 152 + # Enable service (but don't start yet - user needs to configure .env) 153 + systemctl enable atcr.service 154 + 155 + log_info "Systemd service created and enabled" 156 + 157 + # Create helper scripts 158 + log_info "Creating helper scripts..." 159 + 160 + # Script to rebuild and restart 161 + cat > "$ATCR_DIR/rebuild.sh" <<'EOF' 162 + #!/bin/bash 163 + set -e 164 + cd /opt/atcr 165 + docker compose -f deploy/docker-compose.prod.yml build 166 + docker compose -f deploy/docker-compose.prod.yml up -d 167 + docker compose -f deploy/docker-compose.prod.yml logs -f 168 + EOF 169 + chmod +x "$ATCR_DIR/rebuild.sh" 170 + 171 + # Script to view logs 172 + cat > "$ATCR_DIR/logs.sh" <<'EOF' 173 + #!/bin/bash 174 + cd /opt/atcr 175 + docker compose -f deploy/docker-compose.prod.yml logs -f "$@" 176 + EOF 177 + chmod +x "$ATCR_DIR/logs.sh" 178 + 179 + # Script to get hold OAuth URL 180 + cat > "$ATCR_DIR/get-hold-oauth.sh" <<'EOF' 181 + #!/bin/bash 182 + echo "Checking atcr-hold logs for OAuth registration URL..." 183 + docker logs atcr-hold 2>&1 | grep -i "oauth\|authorization\|visit\|http" | tail -20 184 + EOF 185 + chmod +x "$ATCR_DIR/get-hold-oauth.sh" 186 + 187 + log_info "Helper scripts created in $ATCR_DIR" 188 + 189 + # Print completion message 190 + cat <<'EOF' 191 + 192 + ================================================================================ 193 + ATCR Installation Complete! 194 + ================================================================================ 195 + 196 + NEXT STEPS: 197 + 198 + 1. Configure environment variables: 199 + nano /opt/atcr/.env 200 + 201 + Required settings: 202 + - AWS_ACCESS_KEY_ID (UpCloud S3 credentials) 203 + - AWS_SECRET_ACCESS_KEY 204 + 205 + Pre-configured (verify these are correct): 206 + - APPVIEW_DOMAIN=atcr.io 207 + - HOLD_DOMAIN=hold01.atcr.io 208 + - HOLD_OWNER=did:plc:pddp4xt5lgnv2qsegbzzs4xg 209 + - S3_BUCKET=atcr 210 + - S3_ENDPOINT=https://blobs.atcr.io 211 + 212 + 2. Configure UpCloud Cloud Firewall (in control panel): 213 + Allow: TCP 22 (SSH) 214 + Allow: TCP 80 (HTTP) 215 + Allow: TCP 443 (HTTPS) 216 + Drop: Everything else 217 + 218 + 3. Configure DNS (Cloudflare - DNS-only mode): 219 + EOF 220 + 221 + echo " A atcr.io → $(curl -s ifconfig.me || echo '[server-ip]') (gray cloud)" 222 + echo " A hold01.atcr.io → $(curl -s ifconfig.me || echo '[server-ip]') (gray cloud)" 223 + echo " CNAME blobs.atcr.io → atcr.us-chi1.upcloudobjects.com (gray cloud)" 224 + 225 + cat <<'EOF' 226 + 227 + 4. Start ATCR: 228 + systemctl start atcr 229 + 230 + 5. Complete Hold OAuth registration: 231 + /opt/atcr/get-hold-oauth.sh 232 + 233 + Visit the OAuth URL in your browser to authorize the hold service. 234 + 235 + 6. Check status: 236 + systemctl status atcr 237 + docker ps 238 + /opt/atcr/logs.sh 239 + 240 + Helper Scripts: 241 + /opt/atcr/rebuild.sh - Rebuild and restart containers 242 + /opt/atcr/logs.sh [service] - View logs (e.g., logs.sh atcr-hold) 243 + /opt/atcr/get-hold-oauth.sh - Get hold OAuth URL 244 + 245 + Service Management: 246 + systemctl start atcr - Start ATCR 247 + systemctl stop atcr - Stop ATCR 248 + systemctl restart atcr - Restart ATCR 249 + systemctl status atcr - Check status 250 + 251 + Documentation: 252 + https://tangled.org/@evan.jarrett.net/at-container-registry 253 + 254 + IMPORTANT: 255 + - Edit /opt/atcr/.env with S3 credentials before starting! 256 + - Configure UpCloud cloud firewall (see step 2) 257 + - DNS must be configured and propagated 258 + - Cloudflare proxy must be DISABLED (gray cloud) 259 + - Complete hold OAuth registration before first push 260 + 261 + EOF 262 + 263 + log_info "Installation complete. Follow the next steps above."