A container registry that uses the AT Protocol for manifest storage and S3 for blob storage. atcr.io
docker container atproto go
80
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."