···84 # Organization name for Terms of Service and Privacy Policy. Defaults to server.client_name.
85 company_name: ""
86 # Governing law jurisdiction for legal terms.
87- jurisdiction: State of Texas, United States
···84 # Organization name for Terms of Service and Privacy Policy. Defaults to server.client_name.
85 company_name: ""
86 # Governing law jurisdiction for legal terms.
87+ jurisdiction: ""
-216
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-# ==============================================================================
20-# Hold Service Configuration
21-# ==============================================================================
22-23-# Hold service domain (REQUIRED)
24-# The hostname where the hold service will be accessible
25-# Used by docker-compose.prod.yml to derive:
26-# - HOLD_PUBLIC_URL: https://${HOLD_DOMAIN}
27-# - ATCR_DEFAULT_HOLD_DID: did:web:${HOLD_DOMAIN}
28-# Example: hold01.atcr.io
29-HOLD_DOMAIN=hold01.atcr.io
30-31-# Your ATProto DID (REQUIRED for hold registration)
32-# Get your DID from: https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=yourhandle.bsky.social
33-# Example: did:plc:abc123xyz789
34-HOLD_OWNER=did:plc:pddp4xt5lgnv2qsegbzzs4xg
35-36-# Directory path for embedded PDS carstore (SQLite database)
37-# Default: /var/lib/atcr-hold
38-# If empty, embedded PDS is disabled
39-#
40-# Note: This should be a directory path, NOT a file path
41-# Carstore creates db.sqlite3 inside this directory
42-#
43-# The embedded PDS makes the hold a proper ATProto user with:
44-# - did:web identity (derived from HOLD_DOMAIN)
45-# - DID document at /.well-known/did.json
46-# - XRPC endpoints for crew management
47-# - ATProto blob endpoints (wraps existing presigned URL logic)
48-#
49-# Example: For HOLD_DOMAIN=hold01.atcr.io, the hold becomes did:web:hold01.atcr.io
50-HOLD_DATABASE_DIR=/var/lib/atcr-hold
51-52-# Path to signing key (auto-generated on first run if missing)
53-# Default: {HOLD_DATABASE_DIR}/signing.key
54-# HOLD_KEY_PATH=/var/lib/atcr-hold/signing.key
55-56-# Allow public blob reads (pulls) without authentication
57-# - true: Anyone can pull images (read-only)
58-# - false: Only authenticated users can pull
59-# Default: false (private)
60-HOLD_PUBLIC=false
61-62-# Allow all authenticated users to write to this hold
63-# This setting controls write permissions for authenticated ATCR users
64-#
65-# - true: Any authenticated ATCR user can push images (treat all as crew)
66-# Useful for shared/community holds where you want to allow
67-# multiple users to push without explicit crew membership.
68-# Users must still authenticate via ATProto OAuth.
69-#
70-# - false: Only hold owner and explicit crew members can push (default)
71-# Write access requires io.atcr.hold.crew record in owner's PDS.
72-# Most secure option for production holds.
73-#
74-# Read permissions are controlled by HOLD_PUBLIC (above).
75-#
76-# Security model:
77-# Read: HOLD_PUBLIC=true → anonymous + authenticated users
78-# HOLD_PUBLIC=false → authenticated users only
79-# Write: HOLD_ALLOW_ALL_CREW=true → all authenticated users
80-# HOLD_ALLOW_ALL_CREW=false → owner + crew only (verified via PDS)
81-#
82-# Use cases:
83-# - Public registry: HOLD_PUBLIC=true, HOLD_ALLOW_ALL_CREW=true
84-# - ATProto users only: HOLD_PUBLIC=false, HOLD_ALLOW_ALL_CREW=true
85-# - Private hold (default): HOLD_PUBLIC=false, HOLD_ALLOW_ALL_CREW=false
86-#
87-# Default: false
88-HOLD_ALLOW_ALL_CREW=false
89-90-# Enable Bluesky posts when manifests are pushed
91-# When enabled, the hold service creates Bluesky posts announcing new container
92-# image pushes. Posts include image name, tag, size, and layer count.
93-#
94-# - true: Create Bluesky posts for manifest uploads
95-# - false: Silent operation (no Bluesky posts)
96-#
97-# Note: This requires the hold owner to have OAuth credentials for posting.
98-# See docs/BLUESKY_MANIFEST_POSTS.md for setup instructions.
99-#
100-# Default: false
101-HOLD_BLUESKY_POSTS_ENABLED=true
102-103-# ==============================================================================
104-# S3/UpCloud Object Storage Configuration (REQUIRED)
105-# ==============================================================================
106-107-# S3 is the only supported storage backend. Presigned URLs are used for direct
108-# client ↔ S3 transfers, eliminating the hold service as a bandwidth bottleneck.
109-110-# S3 Access Credentials
111-# Get these from UpCloud Object Storage console
112-AWS_ACCESS_KEY_ID=
113-AWS_SECRET_ACCESS_KEY=
114-115-# S3 Region (for distribution S3 driver)
116-# For third-party S3 providers (UpCloud, Storj, Minio), this value is ignored
117-# when S3_ENDPOINT is set, but must be a valid AWS region to pass validation.
118-# Default: us-east-1
119-AWS_REGION=us-east-1
120-121-# S3 Bucket Name
122-# Create this bucket in UpCloud Object Storage
123-# Example: atcr-blobs
124-S3_BUCKET=atcr
125-126-# S3 Endpoint
127-# Get this from UpCloud Console → Storage → Object Storage → Your bucket → "S3 endpoint"
128-# Format: https://[bucket-id].upcloudobjects.com
129-# Example: https://6vmss.upcloudobjects.com
130-#
131-# NOTE: Use the bucket-specific endpoint, NOT a custom domain
132-# Custom domains break presigned URL generation
133-S3_ENDPOINT=https://6vmss.upcloudobjects.com
134-135-# ==============================================================================
136-# AppView Configuration
137-# ==============================================================================
138-139-# Default hold service DID (derived from HOLD_DOMAIN in docker-compose.prod.yml)
140-# Uncomment to override if you want to use a different hold service as the default
141-# ATCR_DEFAULT_HOLD_DID=did:web:some-other-hold.example.com
142-143-# OAuth client display name (shown in authorization screens)
144-# Default: AT Container Registry
145-# ATCR_CLIENT_NAME=AT Container Registry
146-147-# Short brand name for page titles and metadata
148-# Used in meta tags, page titles, and UI text
149-# Default: ATCR
150-# ATCR_CLIENT_SHORT_NAME=ATCR
151-152-# ==============================================================================
153-# Legal Page Customization
154-# ==============================================================================
155-156-# Company/organization name displayed in legal pages (Terms, Privacy)
157-# Default: AT Container Registry
158-ATCR_LEGAL_COMPANY_NAME=AT Container Registry
159-160-# Governing law jurisdiction for legal terms
161-# Default: State of Texas, United States
162-ATCR_LEGAL_JURISDICTION=State of Texas, United States
163-164-# ==============================================================================
165-# Logging Configuration
166-# ==============================================================================
167-168-# Log level: debug, info, warn, error
169-# Default: info
170-ATCR_LOG_LEVEL=debug
171-172-# Log formatter: text, json
173-# Default: text
174-ATCR_LOG_FORMATTER=text
175-176-# ==============================================================================
177-# Jetstream Configuration (ATProto event streaming)
178-# ==============================================================================
179-180-# Jetstream WebSocket URL for real-time ATProto events
181-# Default: wss://jetstream2.us-west.bsky.network/subscribe
182-JETSTREAM_URL=wss://jetstream2.us-west.bsky.network/subscribe
183-184-# Enable backfill worker to sync historical records
185-# Default: true (recommended for production)
186-ATCR_BACKFILL_ENABLED=true
187-188-# ATProto relay endpoint for backfill sync API
189-# Default: https://relay1.us-east.bsky.network
190-ATCR_RELAY_ENDPOINT=https://relay1.us-east.bsky.network
191-192-# ==============================================================================
193-# CHECKLIST
194-# ==============================================================================
195-#
196-# Before starting ATCR, ensure you have:
197-#
198-# ☐ Set APPVIEW_DOMAIN (e.g., atcr.io)
199-# ☐ Set HOLD_DOMAIN (e.g., hold01.atcr.io)
200-# ☐ Set HOLD_OWNER (your ATProto DID)
201-# ☐ Set HOLD_DATABASE_DIR (default: /var/lib/atcr-hold) - enables embedded PDS
202-# ☐ Set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY
203-# ☐ Set S3_BUCKET (created in UpCloud Object Storage)
204-# ☐ Set S3_ENDPOINT (UpCloud bucket endpoint, e.g., https://6vmss.upcloudobjects.com)
205-# ☐ Configured DNS records:
206-# - A record: atcr.io → server IP
207-# - A record: hold01.atcr.io → server IP
208-# - CNAME: blobs.atcr.io → [bucket].upcloudobjects.com
209-# ☐ Disabled Cloudflare proxy (gray cloud, not orange)
210-# ☐ Waited for DNS propagation (check with: dig atcr.io)
211-#
212-# After starting:
213-# ☐ Complete hold OAuth registration (run: /opt/atcr/get-hold-oauth.sh)
214-# ☐ Verify hold PDS: curl https://hold01.atcr.io/.well-known/did.json
215-# ☐ Test registry: docker pull atcr.io/test/image
216-# ☐ Monitor logs: /opt/atcr/logs.sh
···1-# ATCR Hold Service Quota Configuration
2-# Copy this file to quotas.yaml to enable quota enforcement.
3-# If quotas.yaml doesn't exist, quotas are disabled (unlimited for all users).
4-5-# Tiers define quota levels using nautical crew ranks.
6-# Each tier has a quota limit specified in human-readable format.
7-# Supported units: B, KB, MB, GB, TB, PB (case-insensitive)
8-tiers:
9- # Entry-level crew - starter tier for new users (free)
10- swabbie:
11- quota: 2GB
12-13- # Standard crew - for regular users
14- deckhand:
15- quota: 5GB
16-17- # Mid-level crew - for regular contributors
18- bosun:
19- quota: 10GB
20-21- # Senior crew - for power users or trusted contributors
22- #quartermaster:
23- # quota: 50GB
24-25- # You can add custom tiers with any name:
26- # admiral:
27- # quota: 1TB
28-29-defaults:
30- # Default tier assigned to new crew members who don't have an explicit tier.
31- # This tier must exist in the tiers section above.
32- new_crew_tier: swabbie
33-34-# Notes:
35-# - The hold captain (owner) always has unlimited quota regardless of tiers.
36-# - Crew members can be assigned a specific tier in their crew record.
37-# - If a crew member's tier doesn't exist in config, they fall back to the default.
38-# - Quota is calculated per-user by summing unique blob sizes (deduplicated).
39-# - Quota is checked when pushing manifests (after blobs are already uploaded).
40-# - Billing configuration (Stripe prices, descriptions) goes in a separate
41-# top-level "billing:" section. See billing documentation for details.
···00000000000000000000000000000000000000000
-55
deploy/request-crawl.sh
···1-#!/bin/bash
2-#
3-# Request crawl for a PDS from the Bluesky relay
4-#
5-# Usage: ./request-crawl.sh <hostname> [relay-url]
6-# Example: ./request-crawl.sh hold01.atcr.io
7-#
8-9-set -e
10-11-DEFAULT_RELAY="https://bsky.network/xrpc/com.atproto.sync.requestCrawl"
12-13-# Parse arguments
14-HOSTNAME="${1:-}"
15-RELAY_URL="${2:-$DEFAULT_RELAY}"
16-17-# Validate hostname
18-if [ -z "$HOSTNAME" ]; then
19- echo "Error: hostname is required" >&2
20- echo "" >&2
21- echo "Usage: $0 <hostname> [relay-url]" >&2
22- echo "Example: $0 hold01.atcr.io" >&2
23- echo "" >&2
24- echo "Options:" >&2
25- echo " hostname Hostname of the PDS to request crawl for (required)" >&2
26- echo " relay-url Relay URL to send crawl request to (default: $DEFAULT_RELAY)" >&2
27- exit 1
28-fi
29-30-# Log what we're doing
31-echo "Requesting crawl for hostname: $HOSTNAME"
32-echo "Sending to relay: $RELAY_URL"
33-34-# Make the request
35-RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$RELAY_URL" \
36- -H "Content-Type: application/json" \
37- -d "{\"hostname\":\"$HOSTNAME\"}")
38-39-# Split response and status code
40-HTTP_BODY=$(echo "$RESPONSE" | head -n -1)
41-HTTP_CODE=$(echo "$RESPONSE" | tail -n 1)
42-43-# Check response
44-if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then
45- echo "✅ Success! Crawl requested for $HOSTNAME"
46- if [ -n "$HTTP_BODY" ]; then
47- echo "Response: $HTTP_BODY"
48- fi
49-else
50- echo "❌ Failed with status $HTTP_CODE" >&2
51- if [ -n "$HTTP_BODY" ]; then
52- echo "Response: $HTTP_BODY" >&2
53- fi
54- exit 1
55-fi
···10 # Optional: Load from .env.appview file (create from .env.appview.example)
11 # env_file:
12 # - .env.appview
0013 environment:
14 # ATCR_SERVER_CLIENT_NAME: "Seamark"
15 # ATCR_SERVER_CLIENT_SHORT_NAME: "Seamark"
16- # Server configuration
17- ATCR_SERVER_ADDR: :5000
18 ATCR_SERVER_DEFAULT_HOLD_DID: did:web:172.28.0.3:8080
19- ATCR_JETSTREAM_BACKFILL_ENABLED: "true"
20- # Test mode - fallback to default hold when user's hold is unreachable
21 ATCR_SERVER_TEST_MODE: "true"
22- # Logging
23 ATCR_LOG_LEVEL: debug
24- # Log shipping (uncomment to enable)
25 LOG_SHIPPER_BACKEND: victoria
26 LOG_SHIPPER_URL: http://172.28.0.10:9428
27 # Limit local Docker logs - real logs go to Victoria Logs
···53 atcr-hold:
54 env_file:
55 - ../atcr-secrets.env # Load S3/Storj credentials from external file
0056 environment:
57- HOLD_ADMIN_ENABLED: true
58 HOLD_SERVER_PUBLIC_URL: http://172.28.0.3:8080
59 HOLD_REGISTRATION_OWNER_DID: did:plc:pddp4xt5lgnv2qsegbzzs4xg
60- HOLD_SERVER_PUBLIC: false
61 HOLD_REGISTRATION_ALLOW_ALL_CREW: true
62 HOLD_SERVER_TEST_MODE: true
63 # Stripe billing (only used with -tags billing)
64 STRIPE_SECRET_KEY: sk_test_
65 STRIPE_PUBLISHABLE_KEY: pk_test_
66 STRIPE_WEBHOOK_SECRET: whsec_
67- # Logging
68 HOLD_LOG_LEVEL: debug
69- # Log shipping (uncomment to enable)
70 LOG_SHIPPER_BACKEND: victoria
71 LOG_SHIPPER_URL: http://172.28.0.10:9428
72 # S3 storage config comes from env_file (AWS_*, S3_*)
···10 # Optional: Load from .env.appview file (create from .env.appview.example)
11 # env_file:
12 # - .env.appview
13+ # Base config: config-appview.example.yaml (passed via Air entrypoint)
14+ # Env vars below override config file values for local dev
15 environment:
16 # ATCR_SERVER_CLIENT_NAME: "Seamark"
17 # ATCR_SERVER_CLIENT_SHORT_NAME: "Seamark"
0018 ATCR_SERVER_DEFAULT_HOLD_DID: did:web:172.28.0.3:8080
0019 ATCR_SERVER_TEST_MODE: "true"
020 ATCR_LOG_LEVEL: debug
021 LOG_SHIPPER_BACKEND: victoria
22 LOG_SHIPPER_URL: http://172.28.0.10:9428
23 # Limit local Docker logs - real logs go to Victoria Logs
···49 atcr-hold:
50 env_file:
51 - ../atcr-secrets.env # Load S3/Storj credentials from external file
52+ # Base config: config-hold.example.yaml (passed via Air entrypoint)
53+ # Env vars below override config file values for local dev
54 environment:
055 HOLD_SERVER_PUBLIC_URL: http://172.28.0.3:8080
56 HOLD_REGISTRATION_OWNER_DID: did:plc:pddp4xt5lgnv2qsegbzzs4xg
057 HOLD_REGISTRATION_ALLOW_ALL_CREW: true
58 HOLD_SERVER_TEST_MODE: true
59 # Stripe billing (only used with -tags billing)
60 STRIPE_SECRET_KEY: sk_test_
61 STRIPE_PUBLISHABLE_KEY: pk_test_
62 STRIPE_WEBHOOK_SECRET: whsec_
063 HOLD_LOG_LEVEL: debug
064 LOG_SHIPPER_BACKEND: victoria
65 LOG_SHIPPER_URL: http://172.28.0.10:9428
66 # S3 storage config comes from env_file (AWS_*, S3_*)
+1-1
pkg/appview/config.go
···181182 // Legal defaults
183 v.SetDefault("legal.company_name", "")
184- v.SetDefault("legal.jurisdiction", "State of Texas, United States")
185186 // Log formatter (used by distribution config, not in Config struct)
187 v.SetDefault("log_formatter", "text")
···181182 // Legal defaults
183 v.SetDefault("legal.company_name", "")
184+ v.SetDefault("legal.jurisdiction", "")
185186 // Log formatter (used by distribution config, not in Config struct)
187 v.SetDefault("log_formatter", "text")