A container registry that uses the AT Protocol for manifest storage and S3 for blob storage.
atcr.io
docker
container
atproto
go
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# - Creates 2GB swap file (for 1GB RAM instances)
11# - Installs Docker and Docker Compose
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
23set -euo pipefail
24
25# Configuration
26ATCR_DIR="/opt/atcr"
27ATCR_REPO="https://tangled.org/@evan.jarrett.net/at-container-registry" # UPDATE THIS
28ATCR_BRANCH="main"
29
30# Simple logging without colors (for cloud-init log compatibility)
31log_info() {
32 echo "[INFO] $1"
33}
34
35log_warn() {
36 echo "[WARN] $1"
37}
38
39log_error() {
40 echo "[ERROR] $1"
41}
42
43# Function to check if command exists
44command_exists() {
45 command -v "$1" >/dev/null 2>&1
46}
47
48log_info "Starting ATCR deployment on Rocky Linux..."
49
50# Update system packages
51log_info "Updating system packages..."
52dnf update -y
53
54# Install required packages
55log_info "Installing prerequisites..."
56dnf install -y \
57 git \
58 wget \
59 curl \
60 nano \
61 vim
62
63log_info "Required ports: HTTP (80), HTTPS (443), SSH (22)"
64
65# Create swap file for instances with limited RAM
66if [ ! -f /swapfile ]; then
67 log_info "Creating 2GB swap file (allows builds on 1GB RAM instances)..."
68 dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
69 chmod 600 /swapfile
70 mkswap /swapfile
71 swapon /swapfile
72
73 # Make swap permanent
74 echo '/swapfile none swap sw 0 0' >> /etc/fstab
75
76 log_info "Swap file created and enabled"
77 free -h
78else
79 log_info "Swap file already exists"
80fi
81
82# Install Docker
83if ! command_exists docker; then
84 log_info "Installing Docker..."
85
86 # Add Docker repository
87 dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
88
89 # Install Docker
90 dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
91
92 # Start and enable Docker
93 systemctl enable --now docker
94
95 log_info "Docker installed successfully"
96else
97 log_info "Docker already installed"
98fi
99
100# Verify Docker Compose
101if ! docker compose version >/dev/null 2>&1; then
102 log_error "Docker Compose plugin not found. Please install manually."
103 exit 1
104fi
105
106log_info "Docker Compose version: $(docker compose version)"
107
108# Create ATCR directory
109log_info "Creating ATCR directory: $ATCR_DIR"
110mkdir -p "$ATCR_DIR"
111cd "$ATCR_DIR"
112
113# Clone repository or create minimal structure
114if [ -n "$ATCR_REPO" ] && [ "$ATCR_REPO" != "https://tangled.org/@evan.jarrett.net/at-container-registry" ]; then
115 log_info "Cloning ATCR repository..."
116 git clone -b "$ATCR_BRANCH" "$ATCR_REPO" .
117else
118 log_warn "ATCR_REPO not configured. You'll need to manually copy files to $ATCR_DIR"
119 log_warn "Required files:"
120 log_warn " - deploy/docker-compose.prod.yml"
121 log_warn " - deploy/.env.prod.template"
122 log_warn " - Dockerfile.appview"
123 log_warn " - Dockerfile.hold"
124fi
125
126# Create .env file from template if it doesn't exist
127if [ -f "deploy/.env.prod.template" ] && [ ! -f "$ATCR_DIR/.env" ]; then
128 log_info "Creating .env file from template..."
129 cp deploy/.env.prod.template "$ATCR_DIR/.env"
130 log_warn "IMPORTANT: Edit $ATCR_DIR/.env with your configuration!"
131fi
132
133# Create systemd service
134log_info "Creating systemd service..."
135cat > /etc/systemd/system/atcr.service <<'EOF'
136[Unit]
137Description=ATCR Container Registry
138Requires=docker.service
139After=docker.service network-online.target
140Wants=network-online.target
141
142[Service]
143Type=oneshot
144RemainAfterExit=yes
145WorkingDirectory=/opt/atcr
146EnvironmentFile=/opt/atcr/.env
147
148# Start containers
149ExecStart=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml up -d
150
151# Stop containers
152ExecStop=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml down
153
154# Restart containers
155ExecReload=/usr/bin/docker compose -f /opt/atcr/deploy/docker-compose.prod.yml restart
156
157# Always restart on failure
158Restart=on-failure
159RestartSec=10
160
161[Install]
162WantedBy=multi-user.target
163EOF
164
165# Reload systemd
166log_info "Reloading systemd daemon..."
167systemctl daemon-reload
168
169# Enable service (but don't start yet - user needs to configure .env)
170systemctl enable atcr.service
171
172log_info "Systemd service created and enabled"
173
174# Create helper scripts
175log_info "Creating helper scripts..."
176
177# Script to rebuild and restart
178cat > "$ATCR_DIR/rebuild.sh" <<'EOF'
179#!/bin/bash
180set -e
181cd /opt/atcr
182docker compose -f deploy/docker-compose.prod.yml build
183docker compose -f deploy/docker-compose.prod.yml up -d
184docker compose -f deploy/docker-compose.prod.yml logs -f
185EOF
186chmod +x "$ATCR_DIR/rebuild.sh"
187
188# Script to view logs
189cat > "$ATCR_DIR/logs.sh" <<'EOF'
190#!/bin/bash
191cd /opt/atcr
192docker compose -f deploy/docker-compose.prod.yml logs -f "$@"
193EOF
194chmod +x "$ATCR_DIR/logs.sh"
195
196# Script to get hold OAuth URL
197cat > "$ATCR_DIR/get-hold-oauth.sh" <<'EOF'
198#!/bin/bash
199echo "Checking atcr-hold logs for OAuth registration URL..."
200docker logs atcr-hold 2>&1 | grep -i "oauth\|authorization\|visit\|http" | tail -20
201EOF
202chmod +x "$ATCR_DIR/get-hold-oauth.sh"
203
204log_info "Helper scripts created in $ATCR_DIR"
205
206# Print completion message
207cat <<'EOF'
208
209================================================================================
210ATCR Installation Complete!
211================================================================================
212
213NEXT STEPS:
214
2151. Configure environment variables:
216 nano /opt/atcr/.env
217
218 Required settings:
219 - AWS_ACCESS_KEY_ID (UpCloud S3 credentials)
220 - AWS_SECRET_ACCESS_KEY
221
222 Pre-configured (verify these are correct):
223 - APPVIEW_DOMAIN=atcr.io
224 - HOLD_DOMAIN=hold01.atcr.io
225 - HOLD_OWNER=did:plc:pddp4xt5lgnv2qsegbzzs4xg
226 - S3_BUCKET=atcr
227 - S3_ENDPOINT=https://blobs.atcr.io
228
2292. Configure UpCloud Cloud Firewall (in control panel):
230 Allow: TCP 22 (SSH)
231 Allow: TCP 80 (HTTP)
232 Allow: TCP 443 (HTTPS)
233 Drop: Everything else
234
2353. Configure DNS (Cloudflare - DNS-only mode):
236EOF
237
238echo " A atcr.io → $(curl -s ifconfig.me || echo '[server-ip]') (gray cloud)"
239echo " A hold01.atcr.io → $(curl -s ifconfig.me || echo '[server-ip]') (gray cloud)"
240echo " CNAME blobs.atcr.io → atcr.us-chi1.upcloudobjects.com (gray cloud)"
241
242cat <<'EOF'
243
2444. Start ATCR:
245 systemctl start atcr
246
2475. Complete Hold OAuth registration:
248 /opt/atcr/get-hold-oauth.sh
249
250 Visit the OAuth URL in your browser to authorize the hold service.
251
2526. Check status:
253 systemctl status atcr
254 docker ps
255 /opt/atcr/logs.sh
256
257Helper Scripts:
258 /opt/atcr/rebuild.sh - Rebuild and restart containers
259 /opt/atcr/logs.sh [service] - View logs (e.g., logs.sh atcr-hold)
260 /opt/atcr/get-hold-oauth.sh - Get hold OAuth URL
261
262Service Management:
263 systemctl start atcr - Start ATCR
264 systemctl stop atcr - Stop ATCR
265 systemctl restart atcr - Restart ATCR
266 systemctl status atcr - Check status
267
268Documentation:
269 https://tangled.org/@evan.jarrett.net/at-container-registry
270
271IMPORTANT:
272 - Edit /opt/atcr/.env with S3 credentials before starting!
273 - Configure UpCloud cloud firewall (see step 2)
274 - DNS must be configured and propagated
275 - Cloudflare proxy must be DISABLED (gray cloud)
276 - Complete hold OAuth registration before first push
277
278EOF
279
280log_info "Installation complete. Follow the next steps above."