1. Removing distribution/distribution from the Hold Service (biggest change) The hold service previously used distribution's StorageDriver interface for all blob operations. This replaces it with direct AWS SDK v2 calls through ATCR's own pkg/s3.S3Service: - New S3Service methods: Stat(), PutBytes(), Move(), Delete(), WalkBlobs(), ListPrefix() added to pkg/s3/types.go - Pull zone fix: Presigned URLs are now generated against the real S3 endpoint, then the host is swapped to the CDN URL post-signing (previously the CDN URL was set as the endpoint, which broke SigV4 signatures) - All hold subsystems migrated: GC, OCI uploads, XRPC handlers, profile uploads, scan broadcaster, manifest posts — all now use *s3.S3Service instead of storagedriver.StorageDriver - Config simplified: Removed configuration.Storage type and buildStorageConfigFromFields(); replaced with a simple S3Params() method - Mock expanded: MockS3Client gains an in-memory object store + 5 new methods, replacing duplicate mockStorageDriver implementations in tests (~160 lines deleted from each test file) 2. Vulnerability Scan UI in AppView (new feature) Displays scan results from the hold's PDS on the repository page: - New lexicon: io/atcr/hold/scan.json with vulnReportBlob field for storing full Grype reports - Two new HTMX endpoints: /api/scan-result (badge) and /api/vuln-details (modal with CVE table) - New templates: vuln-badge.html (severity count chips) and vuln-details.html (full CVE table with NVD/GHSA links) - Repository page: Lazy-loads scan badges per manifest via HTMX - Tests: ~590 lines of test coverage for both handlers 3. S3 Diagnostic Tool New cmd/s3-test/main.go (418 lines) — tests S3 connectivity with both SDK v1 and v2, including presigned URL generation, pull zone host swapping, and verbose signing debug output. 4. Deployment Tooling - New syncServiceUnit() for comparing/updating systemd units on servers - Update command now syncs config keys (adds missing keys from template) and service units with daemon-reload 5. DB Migration 0011_fix_captain_successor_column.yaml — rebuilds hold_captain_records to add the successor column that was missed in a previous migration. 6. Documentation - APPVIEW-UI-FUTURE.md rewritten as a status-tracked feature inventory - DISTRIBUTION.md renamed to CREDENTIAL_HELPER.md - New REMOVING_DISTRIBUTION.md — 480-line analysis of fully removing distribution from the appview side 7. go.mod aws-sdk-go v1 moved from indirect to direct (needed by cmd/s3-test).
Database Migrations
This directory contains database migrations for the ATCR AppView database.
Schema vs Migrations
schema.sql (in parent directory) contains the complete base schema for fresh database installations. It includes all tables, indexes, and constraints.
Migrations (this directory) handle changes to existing databases. They are only for:
ALTER TABLEstatements (add/modify/drop columns)UPDATEstatements (data transformations)DELETEstatements (data cleanup)- Creating/modifying indexes on existing tables
NEW TABLES go in schema.sql, NOT in migrations.
Migration Format
Each migration is a YAML file with the following structure:
description: Optional human-readable description of what this migration does
query: |
SQL commands to apply the migration
Version and name are parsed from the filename, so you don't need to specify them in the YAML.
Naming Convention
Migration files must be named: {version:04d}_{migration_name}.yaml
The filename determines:
- Version: Numeric prefix (e.g.,
0001→ version 1) - Name: Everything after first underscore (e.g.,
add_repository_labels→ "add repository labels")
Examples:
0001_remove_star_count_from_repository_stats.yaml→ version 1, name "remove star count from repository stats"0002_add_repository_labels.yaml→ version 2, name "add repository labels"0003_create_webhooks_table.yaml→ version 3, name "create webhooks table"
Creating a New Migration
- Choose the next version number - Look at existing migrations and increment by 1
- Create a new YAML file with format
000N_descriptive_name.yaml - Add description (optional) - Explain what the migration does
- Write your SQL in
query- Use the|block scalar for clean multi-line SQL - Use
IF EXISTS/IF NOT EXISTSwhere possible for idempotency
Examples
Adding a column to existing table:
Filename: 0007_add_readme_url_to_manifests.yaml
description: Add readme_url column to manifests table for storing io.atcr.readme annotation
query: |
ALTER TABLE manifests ADD COLUMN readme_url TEXT;
IMPORTANT: After creating this migration, also add the column to schema.sql so fresh installations include it!
Data transformation migration:
Filename: 0005_normalize_hold_endpoint_to_did.yaml
description: Normalize hold_endpoint column to store DIDs instead of URLs
query: |
-- Convert HTTPS URLs to did:web: format
UPDATE manifests
SET hold_endpoint = 'did:web:' || substr(hold_endpoint, 9)
WHERE hold_endpoint LIKE 'https://%';
-- Convert HTTP URLs to did:web: format
UPDATE manifests
SET hold_endpoint = 'did:web:' || substr(hold_endpoint, 8)
WHERE hold_endpoint LIKE 'http://%';
Adding an index to existing table:
Filename: 0008_add_repository_description_index.yaml
description: Add index on manifests description field for faster searches
query: |
CREATE INDEX IF NOT EXISTS idx_manifests_description ON manifests(description);
How Migrations Run
- Migrations are loaded from this directory on startup
- Sorted by version number (ascending)
- Each migration is checked against the
schema_migrationstable - Only unapplied migrations are executed
- After successful execution, the version is recorded in
schema_migrations
Important Notes
- Never modify existing migrations - Once applied, they're immutable
- Test migrations before committing - Ensure they work on existing databases
- Version numbers must be unique - The migration system will fail if duplicates exist
- Migrations run automatically on
InitDB()- Schema first, then migrations - CRITICAL: Update
schema.sqlfor structural changes - When you ALTER a table or add columns, update both the migration ANDschema.sqlso fresh installations have the same structure - New tables go in
schema.sqlonly - Don't create migration files for new tables