···123123# ATProto relay endpoint for backfill sync API
124124# Default: https://relay1.us-east.bsky.network
125125# ATCR_RELAY_ENDPOINT=https://relay1.us-east.bsky.network
126126+127127+# ==============================================================================
128128+# Legal Page Customization (for self-hosted instances)
129129+# ==============================================================================
130130+131131+# Company/organization name displayed in legal pages (Terms, Privacy)
132132+# Default: AT Container Registry
133133+# ATCR_LEGAL_COMPANY_NAME=AT Container Registry
134134+135135+# Governing law jurisdiction for legal terms
136136+# Default: State of Texas, United States
137137+# ATCR_LEGAL_JURISDICTION=State of Texas, United States
···147147# ATCR_CLIENT_NAME=AT Container Registry
148148149149# ==============================================================================
150150+# Legal Page Customization
151151+# ==============================================================================
152152+153153+# Company/organization name displayed in legal pages (Terms, Privacy)
154154+# Default: AT Container Registry
155155+ATCR_LEGAL_COMPANY_NAME=AT Container Registry
156156+157157+# Governing law jurisdiction for legal terms
158158+# Default: State of Texas, United States
159159+ATCR_LEGAL_JURISDICTION=State of Texas, United States
160160+161161+# ==============================================================================
150162# Logging Configuration
151163# ==============================================================================
152164
+16
pkg/appview/config.go
···2929 Jetstream JetstreamConfig `yaml:"jetstream"`
3030 Auth AuthConfig `yaml:"auth"`
3131 CredentialHelper CredentialHelperConfig `yaml:"credential_helper"`
3232+ Legal LegalConfig `yaml:"legal"`
3233 Distribution *configuration.Configuration `yaml:"-"` // Wrapped distribution config for compatibility
3334}
3435···129130 TangledRepo string `yaml:"tangled_repo"`
130131}
131132133133+// LegalConfig defines legal page customization for self-hosted instances
134134+type LegalConfig struct {
135135+ // CompanyName is the company/organization name displayed in legal pages
136136+ // (from env: ATCR_LEGAL_COMPANY_NAME, default: "AT Container Registry")
137137+ CompanyName string `yaml:"company_name"`
138138+139139+ // Jurisdiction is the governing law jurisdiction for legal terms
140140+ // (from env: ATCR_LEGAL_JURISDICTION, default: "State of Texas, United States")
141141+ Jurisdiction string `yaml:"jurisdiction"`
142142+}
143143+132144// LoadConfigFromEnv builds a complete configuration from environment variables
133145// This follows the same pattern as the hold service (no config files, only env vars)
134146func LoadConfigFromEnv() (*Config, error) {
···187199188200 // Credential helper configuration (hardcoded - no env vars needed)
189201 cfg.CredentialHelper.TangledRepo = "https://tangled.org/@evan.jarrett.net/at-container-registry"
202202+203203+ // Legal page configuration (for self-hosted instances)
204204+ cfg.Legal.CompanyName = getEnvOrDefault("ATCR_LEGAL_COMPANY_NAME", "AT Container Registry")
205205+ cfg.Legal.Jurisdiction = getEnvOrDefault("ATCR_LEGAL_JURISDICTION", "State of Texas, United States")
190206191207 // Build distribution configuration for compatibility with distribution library
192208 distConfig, err := buildDistributionConfig(cfg)
+95-56
pkg/appview/db/queries.go
···128128 return pushes, total, nil
129129}
130130131131-// SearchPushes searches for pushes matching the query across handles, DIDs, repositories, and annotations
132132-func SearchPushes(db *sql.DB, query string, limit, offset int, currentUserDID string) ([]Push, int, error) {
131131+// SearchRepositories searches for repositories matching the query across handles, DIDs, repositories, and annotations
132132+// Returns RepoCardData (one per repository) instead of individual pushes/tags
133133+func SearchRepositories(db *sql.DB, query string, limit, offset int, currentUserDID string) ([]RepoCardData, int, error) {
133134 // Escape LIKE wildcards so they're treated literally
134135 query = escapeLikePattern(query)
135136···137138 searchPattern := "%" + query + "%"
138139139140 sqlQuery := `
140140- SELECT DISTINCT
141141- u.did,
142142- u.handle,
143143- t.repository,
144144- t.tag,
145145- t.digest,
146146- COALESCE((SELECT value FROM repository_annotations WHERE did = u.did AND repository = t.repository AND key = 'org.opencontainers.image.title'), ''),
147147- COALESCE((SELECT value FROM repository_annotations WHERE did = u.did AND repository = t.repository AND key = 'org.opencontainers.image.description'), ''),
148148- COALESCE((SELECT value FROM repository_annotations WHERE did = u.did AND repository = t.repository AND key = 'io.atcr.icon'), ''),
149149- COALESCE(rs.pull_count, 0),
150150- COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = u.did AND repository = t.repository), 0),
151151- COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = u.did AND repository = t.repository), 0),
152152- t.created_at,
153153- m.hold_endpoint,
154154- COALESCE(rp.avatar_cid, ''),
155155- COALESCE(m.artifact_type, 'container-image')
156156- FROM tags t
157157- JOIN users u ON t.did = u.did
158158- JOIN manifests m ON t.did = m.did AND t.repository = m.repository AND t.digest = m.digest
159159- LEFT JOIN repository_stats rs ON t.did = rs.did AND t.repository = rs.repository
160160- LEFT JOIN repo_pages rp ON t.did = rp.did AND t.repository = rp.repository
161161- WHERE u.handle LIKE ? ESCAPE '\'
162162- OR u.did = ?
163163- OR t.repository LIKE ? ESCAPE '\'
164164- OR EXISTS (
165165- SELECT 1 FROM repository_annotations ra
166166- WHERE ra.did = u.did AND ra.repository = t.repository
167167- AND ra.value LIKE ? ESCAPE '\'
168168- )
169169- ORDER BY t.created_at DESC
170170- LIMIT ? OFFSET ?
171171- `
141141+ WITH latest_manifests AS (
142142+ SELECT did, repository, MAX(id) as latest_id
143143+ FROM manifests
144144+ GROUP BY did, repository
145145+ ),
146146+ matching_repos AS (
147147+ SELECT DISTINCT lm.did, lm.repository, lm.latest_id
148148+ FROM latest_manifests lm
149149+ JOIN users u ON lm.did = u.did
150150+ WHERE u.handle LIKE ? ESCAPE '\'
151151+ OR u.did = ?
152152+ OR lm.repository LIKE ? ESCAPE '\'
153153+ OR EXISTS (
154154+ SELECT 1 FROM repository_annotations ra
155155+ WHERE ra.did = lm.did AND ra.repository = lm.repository
156156+ AND ra.value LIKE ? ESCAPE '\'
157157+ )
158158+ ),
159159+ repo_stats AS (
160160+ SELECT
161161+ mr.did,
162162+ mr.repository,
163163+ COALESCE(rs.pull_count, 0) as pull_count,
164164+ COALESCE((SELECT COUNT(*) FROM stars WHERE owner_did = mr.did AND repository = mr.repository), 0) as star_count
165165+ FROM matching_repos mr
166166+ LEFT JOIN repository_stats rs ON mr.did = rs.did AND mr.repository = rs.repository
167167+ )
168168+ SELECT
169169+ m.did,
170170+ u.handle,
171171+ COALESCE(u.avatar, ''),
172172+ m.repository,
173173+ COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.title'), ''),
174174+ COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'org.opencontainers.image.description'), ''),
175175+ COALESCE((SELECT value FROM repository_annotations WHERE did = m.did AND repository = m.repository AND key = 'io.atcr.icon'), ''),
176176+ repo_stats.star_count,
177177+ repo_stats.pull_count,
178178+ COALESCE((SELECT COUNT(*) FROM stars WHERE starrer_did = ? AND owner_did = m.did AND repository = m.repository), 0),
179179+ COALESCE(m.artifact_type, 'container-image'),
180180+ COALESCE((SELECT tag FROM tags WHERE did = m.did AND repository = m.repository ORDER BY created_at DESC LIMIT 1), ''),
181181+ COALESCE(m.digest, ''),
182182+ COALESCE(rs.last_push, m.created_at),
183183+ COALESCE(rp.avatar_cid, '')
184184+ FROM matching_repos mr
185185+ JOIN manifests m ON mr.latest_id = m.id
186186+ JOIN users u ON m.did = u.did
187187+ JOIN repo_stats ON m.did = repo_stats.did AND m.repository = repo_stats.repository
188188+ LEFT JOIN repository_stats rs ON m.did = rs.did AND m.repository = rs.repository
189189+ LEFT JOIN repo_pages rp ON m.did = rp.did AND m.repository = rp.repository
190190+ ORDER BY COALESCE(rs.last_push, m.created_at) DESC
191191+ LIMIT ? OFFSET ?
192192+ `
172193173173- rows, err := db.Query(sqlQuery, currentUserDID, searchPattern, query, searchPattern, searchPattern, limit, offset)
194194+ rows, err := db.Query(sqlQuery, searchPattern, query, searchPattern, searchPattern, currentUserDID, limit, offset)
174195 if err != nil {
175196 return nil, 0, err
176197 }
177198 defer rows.Close()
178199179179- var pushes []Push
200200+ var cards []RepoCardData
180201 for rows.Next() {
181181- var p Push
202202+ var c RepoCardData
203203+ var ownerDID string
182204 var isStarredInt int
183205 var avatarCID string
184184- if err := rows.Scan(&p.DID, &p.Handle, &p.Repository, &p.Tag, &p.Digest, &p.Title, &p.Description, &p.IconURL, &p.PullCount, &p.StarCount, &isStarredInt, &p.CreatedAt, &p.HoldEndpoint, &avatarCID, &p.ArtifactType); err != nil {
206206+ var lastUpdatedStr sql.NullString
207207+208208+ if err := rows.Scan(&ownerDID, &c.OwnerHandle, &c.OwnerAvatarURL, &c.Repository, &c.Title, &c.Description, &c.IconURL,
209209+ &c.StarCount, &c.PullCount, &isStarredInt, &c.ArtifactType, &c.Tag, &c.Digest, &lastUpdatedStr, &avatarCID); err != nil {
185210 return nil, 0, err
186211 }
187187- p.IsStarred = isStarredInt > 0
212212+ c.IsStarred = isStarredInt > 0
213213+ if lastUpdatedStr.Valid {
214214+ if t, err := parseTimestamp(lastUpdatedStr.String); err == nil {
215215+ c.LastUpdated = t
216216+ }
217217+ }
188218 // Prefer repo page avatar over annotation icon
189219 if avatarCID != "" {
190190- p.IconURL = BlobCDNURL(p.DID, avatarCID)
220220+ c.IconURL = BlobCDNURL(ownerDID, avatarCID)
191221 }
192192- pushes = append(pushes, p)
222222+223223+ cards = append(cards, c)
224224+ }
225225+226226+ if err := rows.Err(); err != nil {
227227+ return nil, 0, err
193228 }
194229195195- // Get total count
230230+ // Get total count of matching repositories
196231 countQuery := `
197197- SELECT COUNT(DISTINCT t.id)
198198- FROM tags t
199199- JOIN users u ON t.did = u.did
200200- JOIN manifests m ON t.did = m.did AND t.repository = m.repository AND t.digest = m.digest
201201- WHERE u.handle LIKE ? ESCAPE '\'
202202- OR u.did = ?
203203- OR t.repository LIKE ? ESCAPE '\'
204204- OR EXISTS (
205205- SELECT 1 FROM repository_annotations ra
206206- WHERE ra.did = u.did AND ra.repository = t.repository
207207- AND ra.value LIKE ? ESCAPE '\'
208208- )
209209- `
232232+ WITH latest_manifests AS (
233233+ SELECT did, repository, MAX(id) as latest_id
234234+ FROM manifests
235235+ GROUP BY did, repository
236236+ )
237237+ SELECT COUNT(DISTINCT lm.did || '/' || lm.repository)
238238+ FROM latest_manifests lm
239239+ JOIN users u ON lm.did = u.did
240240+ WHERE u.handle LIKE ? ESCAPE '\'
241241+ OR u.did = ?
242242+ OR lm.repository LIKE ? ESCAPE '\'
243243+ OR EXISTS (
244244+ SELECT 1 FROM repository_annotations ra
245245+ WHERE ra.did = lm.did AND ra.repository = lm.repository
246246+ AND ra.value LIKE ? ESCAPE '\'
247247+ )
248248+ `
210249211250 var total int
212251 if err := db.QueryRow(countQuery, searchPattern, query, searchPattern, searchPattern).Scan(&total); err != nil {
213252 return nil, 0, err
214253 }
215254216216- return pushes, total, nil
255255+ return cards, total, nil
217256}
218257219258// GetUserRepositories fetches all repositories for a user
+27-42
pkg/appview/handlers/home.go
···7676 }
7777}
78787979-// RecentPushesHandler handles the HTMX request for recent pushes
7979+// RecentPushesHandler handles the HTMX request for recent repositories
8080+// Note: This endpoint returns repo cards (one per repository) instead of individual pushes
8081type RecentPushesHandler struct {
8182 DB *sql.DB
8283 Templates *template.Template
···9293 offset, _ = strconv.Atoi(o)
9394 }
94959595- userFilter := r.URL.Query().Get("user")
9696- if userFilter == "" {
9797- userFilter = r.URL.Query().Get("q")
9898- }
9999-10096 // Get current user DID (empty string if not logged in)
10197 var currentUserDID string
10298 if user := middleware.GetUser(r); user != nil {
10399 currentUserDID = user.DID
104100 }
105101106106- pushes, total, err := db.GetRecentPushes(h.DB, limit, offset, userFilter, currentUserDID)
102102+ // Get recent repositories using repo cards (sorted by last update)
103103+ repos, err := db.GetRepoCards(h.DB, limit+offset, currentUserDID, db.SortByLastUpdate)
107104 if err != nil {
108105 http.Error(w, err.Error(), http.StatusInternalServerError)
109106 return
110107 }
111108112112- // Check health status and filter out unreachable manifests for home page
113113- // Use GetCachedStatus only (no blocking) - background worker keeps cache fresh
114114- if h.HealthChecker != nil {
115115- reachablePushes := []db.Push{}
116116- for i := range pushes {
117117- if pushes[i].HoldEndpoint != "" {
118118- // Use cached status only - don't block on health checks
119119- cached := h.HealthChecker.GetCachedStatus(pushes[i].HoldEndpoint)
120120- if cached != nil {
121121- pushes[i].Reachable = cached.Reachable
122122- // Only show reachable pushes on home page
123123- if cached.Reachable {
124124- reachablePushes = append(reachablePushes, pushes[i])
125125- }
126126- } else {
127127- // No cached status - optimistically show it (background worker will check)
128128- pushes[i].Reachable = true
129129- reachablePushes = append(reachablePushes, pushes[i])
130130- }
131131- }
132132- }
133133- pushes = reachablePushes
134134- } else {
135135- // If no health checker, assume all are reachable (backward compatibility)
136136- for i := range pushes {
137137- pushes[i].Reachable = true
138138- }
109109+ // Apply offset manually (GetRepoCards doesn't support offset directly)
110110+ if offset > 0 && offset < len(repos) {
111111+ repos = repos[offset:]
112112+ } else if offset >= len(repos) {
113113+ repos = []db.RepoCardData{}
114114+ }
115115+116116+ // Limit results
117117+ if len(repos) > limit {
118118+ repos = repos[:limit]
139119 }
140120121121+ // Set registry URL on all cards
122122+ db.SetRegistryURL(repos, h.RegistryURL)
123123+141124 data := struct {
142125 PageData
143143- Pushes []db.Push
144144- HasMore bool
145145- NextOffset int
126126+ Repositories []db.RepoCardData
127127+ SearchQuery string
128128+ HasMore bool
129129+ NextOffset int
146130 }{
147147- PageData: NewPageData(r, h.RegistryURL),
148148- Pushes: pushes,
149149- HasMore: offset+limit < total,
150150- NextOffset: offset + limit,
131131+ PageData: NewPageData(r, h.RegistryURL),
132132+ Repositories: repos,
133133+ SearchQuery: "",
134134+ HasMore: len(repos) == limit,
135135+ NextOffset: offset + limit,
151136 }
152137153153- if err := h.Templates.ExecuteTemplate(w, "push-list.html", data); err != nil {
138138+ if err := h.Templates.ExecuteTemplate(w, "search-results.html", data); err != nil {
154139 http.Error(w, err.Error(), http.StatusInternalServerError)
155140 return
156141 }
+19
pkg/appview/handlers/images.go
···55 "encoding/json"
66 "errors"
77 "fmt"
88+ "html/template"
89 "io"
910 "net/http"
1011 "strings"
···163164type UploadAvatarHandler struct {
164165 DB *sql.DB
165166 Refresher *oauth.Refresher
167167+ Templates *template.Template
166168}
167169168170// validImageTypes are the allowed MIME types for avatars (matches lexicon)
···266268267269 // Return new avatar URL
268270 avatarURL := atproto.BlobCDNURL(user.DID, blobRef.Ref.Link)
271271+272272+ // Check if this is an HTMX request
273273+ if r.Header.Get("HX-Request") == "true" {
274274+ // Return HTML component for HTMX swap
275275+ data := map[string]any{
276276+ "IconURL": avatarURL,
277277+ "RepositoryName": repo,
278278+ "IsOwner": true, // Must be owner to upload
279279+ }
280280+ w.Header().Set("Content-Type", "text/html")
281281+ if err := h.Templates.ExecuteTemplate(w, "repo-avatar", data); err != nil {
282282+ http.Error(w, err.Error(), http.StatusInternalServerError)
283283+ }
284284+ return
285285+ }
286286+287287+ // JSON response for non-HTMX clients (backward compat)
269288 render.JSON(w, r, map[string]string{"avatarURL": avatarURL})
270289}
···99 {{ template "nav" . }}
10101111 <main class="container mx-auto px-4 py-8 max-w-4xl">
1212- <h1 class="text-3xl font-bold mb-2">Privacy Policy - AT Container Registry (atcr.io)</h1>
1212+ <h1 class="text-3xl font-bold mb-2">Privacy Policy - {{ .CompanyName }} ({{ .RegistryURL }})</h1>
1313 <p class="text-base-content/60 mb-8"><em>Last updated: January 2025</em></p>
14141515 <div class="prose prose-sm max-w-none space-y-8">
···1717 <h2 class="text-xl font-semibold text-primary">Data We Collect and Store</h2>
18181919 <h3 class="text-lg font-medium mt-4">Data Stored on Your PDS (Controlled by You)</h3>
2020- <p>When you use AT Container Registry, records are written to your Personal Data Server (PDS) under the <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">io.atcr.*</code> namespace. This data is stored on infrastructure you or your PDS hosting provider controls. We do not control this data, and its retention and deletion is governed by your PDS provider's policies.</p>
2020+ <p>When you use {{ .CompanyName }}, records are written to your Personal Data Server (PDS) under the <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">io.atcr.*</code> namespace. This data is stored on infrastructure you or your PDS hosting provider controls. We do not control this data, and its retention and deletion is governed by your PDS provider's policies.</p>
21212222 <h3 class="text-lg font-medium mt-6">Data Stored on Our Infrastructure</h3>
23232424- <p><strong>Layer Records:</strong> Our hold services (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.atcr.io</code>) maintain records in their embedded PDS that reference container image layers you publish. These records are public and link your AT Protocol identity (DID) to content-addressed SHA identifiers.</p>
2424+ <p><strong>Layer Records:</strong> Our hold services (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .RegistryURL }}</code>) maintain records in their embedded PDS that reference container image layers you publish. These records are public and link your AT Protocol identity (DID) to content-addressed SHA identifiers.</p>
25252626 <p><strong>OCI Blobs:</strong> Container image layers are stored in our object storage (S3). These blobs are content-addressed and deduplicated—meaning identical layers uploaded by different users are stored only once.</p>
2727···4646 <section>
4747 <h2 class="text-xl font-semibold text-primary">Our Services and Their Data</h2>
48484949- <p>AT Container Registry consists of multiple services, each with distinct data responsibilities:</p>
4949+ <p>{{ .CompanyName }} consists of multiple services, each with distinct data responsibilities:</p>
50505151- <h3 class="text-lg font-medium mt-4">AppView (atcr.io)</h3>
5151+ <h3 class="text-lg font-medium mt-4">AppView ({{ .RegistryURL }})</h3>
5252 <p>The registry frontend you interact with directly. Stores:</p>
5353 <ul class="list-disc list-inside space-y-1 ml-4">
5454 <li>OAuth sessions and tokens for authentication</li>
···5858 </ul>
59596060 <h3 class="text-lg font-medium mt-6">ATCR-Hosted Hold Services</h3>
6161- <p>Storage backends we operate (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.atcr.io</code>). Each hold has an embedded PDS and stores:</p>
6161+ <p>Storage backends we operate (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .RegistryURL }}</code>). Each hold has an embedded PDS and stores:</p>
6262 <ul class="list-disc list-inside space-y-1 ml-4">
6363 <li>OCI blobs (container image layers) in object storage</li>
6464 <li>Layer records in the hold's embedded PDS linking your DID to blob references</li>
6565 <li>Crew membership records for access control</li>
6666 </ul>
6767- <p class="mt-2">Hold services on <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">*.atcr.io</code> domains are operated by us and covered by this policy.</p>
6767+ <p class="mt-2">Hold services on <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">*.{{ .RegistryURL }}</code> domains are operated by us and covered by this policy.</p>
68686969 <h3 class="text-lg font-medium mt-6">User-Deployed Hold Services (BYOS)</h3>
7070 <p>You may use "Bring Your Own Storage" by deploying your own hold service. Data on user-deployed holds is governed by that operator's privacy policy, not ours. We can request deletion on your behalf but cannot guarantee it for services we do not control.</p>
···263263 <section>
264264 <h2 class="text-xl font-semibold text-primary">Important Notes on AT Protocol Architecture</h2>
265265266266- <p>AT Container Registry is built on the AT Protocol, which has a unique data architecture:</p>
266266+ <p>{{ .CompanyName }} is built on the AT Protocol, which has a unique data architecture:</p>
267267268268 <ol class="list-decimal list-inside space-y-2 ml-4 mt-4">
269269- <li><strong>You control your data.</strong> Most data associated with your use of AT Container Registry is stored on your Personal Data Server (PDS), which you or your chosen provider controls.</li>
269269+ <li><strong>You control your data.</strong> Most data associated with your use of {{ .CompanyName }} is stored on your Personal Data Server (PDS), which you or your chosen provider controls.</li>
270270 <li><strong>Public by design.</strong> AT Protocol data is designed to be public and distributed. Records you create, including container image references, are publicly visible and may be replicated across the network.</li>
271271 <li><strong>Content-addressed storage.</strong> OCI blobs are identified by their cryptographic hash. This means blob data is inherently pseudonymous—it cannot be attributed to you without the corresponding records that reference it.</li>
272272 <li><strong>Deletion limitations.</strong> Because AT Protocol is distributed, we cannot guarantee that copies of public records have not been made by other participants in the network. We can only delete data on infrastructure we control.</li>
···276276 <section>
277277 <h2 class="text-xl font-semibold text-primary">Bring Your Own Storage (BYOS)</h2>
278278279279- <p>AT Container Registry supports "Bring Your Own Storage" where users can deploy their own hold services to store container image blobs. This section explains how BYOS affects your privacy rights.</p>
279279+ <p>{{ .CompanyName }} supports "Bring Your Own Storage" where users can deploy their own hold services to store container image blobs. This section explains how BYOS affects your privacy rights.</p>
280280281281 <h3 class="text-lg font-medium mt-4">ATCR-Hosted Holds</h3>
282282- <p>Hold services on <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">*.atcr.io</code> domains (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.atcr.io</code>) are operated by us and fully covered by this privacy policy. We can fulfill all data access, export, and deletion requests for these services.</p>
282282+ <p>Hold services on <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">*.{{ .RegistryURL }}</code> domains (e.g., <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">hold01.{{ .RegistryURL }}</code>) are operated by us and fully covered by this privacy policy. We can fulfill all data access, export, and deletion requests for these services.</p>
283283284284 <h3 class="text-lg font-medium mt-6">User-Deployed Holds</h3>
285285 <p>If you use a hold service not operated by us:</p>
···303303 <h2 class="text-xl font-semibold text-primary">How to Exercise Your Rights</h2>
304304305305 <h3 class="text-lg font-medium mt-4">Self-Service (via Settings)</h3>
306306- <p>Most data management can be done directly through your account settings at atcr.io:</p>
306306+ <p>Most data management can be done directly through your account settings at {{ .RegistryURL }}:</p>
307307 <ul class="list-disc list-inside space-y-1 ml-4">
308308 <li><strong>Delete your data:</strong> Use the "Delete Account" button in settings. This will remove your layer records, cached data, and authentication tokens. You may also choose to have us delete <code class="bg-base-200 px-1.5 py-0.5 rounded text-sm font-mono">io.atcr.*</code> records from your PDS (requires active OAuth session).</li>
309309 <li><strong>Revoke device tokens:</strong> Manage and revoke credential helper devices in settings.</li>
···318318 <li>Questions about this policy</li>
319319 </ul>
320320321321- <p class="mt-4"><strong>Email:</strong> <a href="mailto:privacy@atcr.io" class="link link-primary">privacy@atcr.io</a></p>
321321+ <p class="mt-4"><strong>Email:</strong> <a href="mailto:privacy@{{ .RegistryURL }}" class="link link-primary">privacy@{{ .RegistryURL }}</a></p>
322322323323 <p class="mt-2">Please include your AT Protocol DID or handle so we can verify your identity.</p>
324324···330330331331 <p>For questions about this privacy policy or to exercise your data rights, contact:</p>
332332333333- <p class="mt-4"><strong>Email:</strong> <a href="mailto:privacy@atcr.io" class="link link-primary">privacy@atcr.io</a></p>
334334- <p><strong>Website:</strong> <a href="https://atcr.io" class="link link-primary">https://atcr.io</a></p>
333333+ <p class="mt-4"><strong>Email:</strong> <a href="mailto:privacy@{{ .RegistryURL }}" class="link link-primary">privacy@{{ .RegistryURL }}</a></p>
334334+ <p><strong>Website:</strong> <a href="https://{{ .RegistryURL }}" class="link link-primary">https://{{ .RegistryURL }}</a></p>
335335 </section>
336336 </div>
337337 </main>
···99 {{ template "nav" . }}
10101111 <main class="container mx-auto px-4 py-8 max-w-4xl">
1212- <h1 class="text-3xl font-bold mb-2">Terms of Service - AT Container Registry (atcr.io)</h1>
1212+ <h1 class="text-3xl font-bold mb-2">Terms of Service - {{ .CompanyName }} ({{ .RegistryURL }})</h1>
1313 <p class="text-base-content/60 mb-8"><em>Last updated: January 2025</em></p>
14141515- <p class="mb-8">These Terms of Service ("Terms") govern your use of AT Container Registry ("atcr.io", "the Service", "we", "us", "our"). By using the Service, you agree to these Terms. If you do not agree, do not use the Service.</p>
1515+ <p class="mb-8">These Terms of Service ("Terms") govern your use of {{ .CompanyName }} ("{{ .RegistryURL }}", "the Service", "we", "us", "our"). By using the Service, you agree to these Terms. If you do not agree, do not use the Service.</p>
16161717 <div class="prose prose-sm max-w-none space-y-8">
1818 <section>
1919 <h2 class="text-xl font-semibold text-primary">1. Description of Service</h2>
2020- <p>AT Container Registry is an OCI-compatible container registry built on the AT Protocol. The Service allows you to store, distribute, and manage container images using your AT Protocol identity.</p>
2020+ <p>{{ .CompanyName }} is an OCI-compatible container registry built on the AT Protocol. The Service allows you to store, distribute, and manage container images using your AT Protocol identity.</p>
2121 </section>
22222323 <section>
···148148 <section>
149149 <h2 class="text-xl font-semibold text-primary">11. Indemnification</h2>
150150151151- <p>You agree to indemnify and hold harmless AT Container Registry, its operators, and affiliates from any claims, damages, losses, or expenses (including reasonable legal fees) arising out of:</p>
151151+ <p>You agree to indemnify and hold harmless {{ .CompanyName }}, its operators, and affiliates from any claims, damages, losses, or expenses (including reasonable legal fees) arising out of:</p>
152152 <ul class="list-disc list-inside space-y-1 ml-4">
153153 <li>Your use of the Service</li>
154154 <li>Your violation of these Terms</li>
···166166 <section>
167167 <h2 class="text-xl font-semibold text-primary">13. Governing Law</h2>
168168169169- <p>These Terms shall be governed by and construed in accordance with the laws of the State of Texas, United States, without regard to conflict of law principles.</p>
169169+ <p>These Terms shall be governed by and construed in accordance with the laws of the {{ .Jurisdiction }}, without regard to conflict of law principles.</p>
170170 </section>
171171172172 <section>
173173 <h2 class="text-xl font-semibold text-primary">14. Dispute Resolution</h2>
174174175175- <p>Any disputes arising out of or relating to these Terms or the Service shall first be attempted to be resolved through good-faith negotiation. If negotiation fails, disputes shall be resolved through binding arbitration or in the courts of Texas, at our discretion.</p>
175175+ <p>Any disputes arising out of or relating to these Terms or the Service shall first be attempted to be resolved through good-faith negotiation. If negotiation fails, disputes shall be resolved through binding arbitration or in the courts of our jurisdiction, at our discretion.</p>
176176 </section>
177177178178 <section>
···184184 <section>
185185 <h2 class="text-xl font-semibold text-primary">16. Entire Agreement</h2>
186186187187- <p>These Terms, together with our <a href="/privacy" class="link link-primary">Privacy Policy</a>, constitute the entire agreement between you and AT Container Registry regarding your use of the Service.</p>
187187+ <p>These Terms, together with our <a href="/privacy" class="link link-primary">Privacy Policy</a>, constitute the entire agreement between you and {{ .CompanyName }} regarding your use of the Service.</p>
188188 </section>
189189190190 <section>
···192192193193 <p>For questions about these Terms, contact us at:</p>
194194195195- <p class="mt-4"><strong>Email:</strong> <a href="mailto:legal@atcr.io" class="link link-primary">legal@atcr.io</a></p>
196196- <p><strong>Website:</strong> <a href="https://atcr.io" class="link link-primary">https://atcr.io</a></p>
195195+ <p class="mt-4"><strong>Email:</strong> <a href="mailto:legal@{{ .RegistryURL }}" class="link link-primary">legal@{{ .RegistryURL }}</a></p>
196196+ <p><strong>Website:</strong> <a href="https://{{ .RegistryURL }}" class="link link-primary">https://{{ .RegistryURL }}</a></p>
197197 </section>
198198 </div>
199199 </main>