mirror of
https://tangled.org/evan.jarrett.net/at-container-registry
synced 2026-09-24 03:04:16 +00:00
add new upcloud cli deploy
This commit is contained in:
@@ -57,8 +57,8 @@ type ServerConfig struct {
|
||||
// Short name used in page titles and browser tabs.
|
||||
ClientShortName string `yaml:"client_short_name" comment:"Short name used in page titles and browser tabs."`
|
||||
|
||||
// Separate domain for OCI registry API.
|
||||
RegistryDomain string `yaml:"registry_domain" comment:"Separate domain for OCI registry API (e.g. \"buoy.cr\"). Browser visits redirect to BaseURL."`
|
||||
// Separate domains for OCI registry API. First entry is the primary (used for JWT service name and UI display).
|
||||
RegistryDomains []string `yaml:"registry_domains" comment:"Separate domains for OCI registry API (e.g. [\"buoy.cr\"]). First is primary. Browser visits redirect to BaseURL."`
|
||||
}
|
||||
|
||||
// UIConfig defines web UI settings
|
||||
@@ -145,7 +145,7 @@ func setDefaults(v *viper.Viper) {
|
||||
v.SetDefault("server.client_name", "AT Container Registry")
|
||||
v.SetDefault("server.client_short_name", "ATCR")
|
||||
v.SetDefault("server.oauth_key_path", "/var/lib/atcr/oauth/client.key")
|
||||
v.SetDefault("server.registry_domain", "")
|
||||
v.SetDefault("server.registry_domains", []string{})
|
||||
|
||||
// UI defaults
|
||||
v.SetDefault("ui.database_path", "/var/lib/atcr/ui.db")
|
||||
@@ -241,8 +241,8 @@ func LoadConfig(yamlPath string) (*Config, error) {
|
||||
|
||||
// deriveServiceName extracts the JWT service name from the config.
|
||||
func deriveServiceName(cfg *Config) string {
|
||||
if cfg.Server.RegistryDomain != "" {
|
||||
return cfg.Server.RegistryDomain
|
||||
if len(cfg.Server.RegistryDomains) > 0 {
|
||||
return cfg.Server.RegistryDomains[0]
|
||||
}
|
||||
return getServiceName(cfg.Server.BaseURL)
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ func setupHoldTestDB(t *testing.T) *sql.DB {
|
||||
}
|
||||
// Limit to single connection to avoid race conditions in tests
|
||||
db.SetMaxOpenConns(1)
|
||||
// Clean slate: shared-cache in-memory DB may retain data from prior subtests
|
||||
db.Exec("DELETE FROM hold_captain_records")
|
||||
t.Cleanup(func() { db.Close() })
|
||||
return db
|
||||
}
|
||||
|
||||
+25
-9
@@ -240,10 +240,10 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer,
|
||||
mainRouter.Use(routes.CORSMiddleware())
|
||||
|
||||
// Registry domain redirect middleware
|
||||
if cfg.Server.RegistryDomain != "" {
|
||||
mainRouter.Use(RegistryDomainRedirect(cfg.Server.RegistryDomain, cfg.Server.BaseURL))
|
||||
if len(cfg.Server.RegistryDomains) > 0 {
|
||||
mainRouter.Use(RegistryDomainRedirect(cfg.Server.RegistryDomains, cfg.Server.BaseURL))
|
||||
slog.Info("Registry domain redirect enabled",
|
||||
"registry_domain", cfg.Server.RegistryDomain,
|
||||
"registry_domains", cfg.Server.RegistryDomains,
|
||||
"ui_base_url", cfg.Server.BaseURL)
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer,
|
||||
OAuthStore: s.OAuthStore,
|
||||
Refresher: s.Refresher,
|
||||
BaseURL: baseURL,
|
||||
RegistryDomain: cfg.Server.RegistryDomain,
|
||||
RegistryDomain: primaryRegistryDomain(cfg.Server.RegistryDomains),
|
||||
DeviceStore: s.DeviceStore,
|
||||
HealthChecker: s.HealthChecker,
|
||||
ReadmeFetcher: s.ReadmeFetcher,
|
||||
@@ -499,7 +499,10 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer,
|
||||
mainRouter.Get("/health", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
|
||||
if err := json.NewEncoder(w).Encode(map[string]string{"status": "ok"}); err != nil {
|
||||
http.Error(w, "encode error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
// Register credential helper version API (public endpoint)
|
||||
@@ -577,10 +580,15 @@ func (s *AppViewServer) createTokenIssuer() (*token.Issuer, error) {
|
||||
)
|
||||
}
|
||||
|
||||
// RegistryDomainRedirect redirects all non-registry requests from the registry
|
||||
// domain to the UI domain. Only /v2 and /v2/* pass through for Docker clients.
|
||||
// RegistryDomainRedirect redirects all non-registry requests from registry
|
||||
// domains to the UI domain. Only /v2 and /v2/* pass through for Docker clients.
|
||||
// Uses 307 (Temporary Redirect) to preserve POST method/body.
|
||||
func RegistryDomainRedirect(registryDomain, uiBaseURL string) func(http.Handler) http.Handler {
|
||||
func RegistryDomainRedirect(registryDomains []string, uiBaseURL string) func(http.Handler) http.Handler {
|
||||
domains := make(map[string]bool, len(registryDomains))
|
||||
for _, d := range registryDomains {
|
||||
domains[d] = true
|
||||
}
|
||||
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
host := r.Host
|
||||
@@ -588,7 +596,7 @@ func RegistryDomainRedirect(registryDomain, uiBaseURL string) func(http.Handler)
|
||||
host = host[:idx]
|
||||
}
|
||||
|
||||
if host == registryDomain {
|
||||
if domains[host] {
|
||||
path := r.URL.Path
|
||||
if path == "/v2" || path == "/v2/" || strings.HasPrefix(path, "/v2/") {
|
||||
next.ServeHTTP(w, r)
|
||||
@@ -605,6 +613,14 @@ func RegistryDomainRedirect(registryDomain, uiBaseURL string) func(http.Handler)
|
||||
}
|
||||
}
|
||||
|
||||
// primaryRegistryDomain returns the first registry domain, or empty string if none.
|
||||
func primaryRegistryDomain(domains []string) string {
|
||||
if len(domains) > 0 {
|
||||
return domains[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// initializeJetstream initializes the Jetstream workers for real-time events and backfill.
|
||||
func (s *AppViewServer) initializeJetstream() {
|
||||
jetstreamURL := s.Config.Jetstream.URL
|
||||
|
||||
@@ -131,6 +131,22 @@ func valueToNode(v reflect.Value) (*yaml.Node, error) {
|
||||
return mapToNode(v)
|
||||
}
|
||||
|
||||
// Slice → yaml sequence
|
||||
if v.Kind() == reflect.Slice {
|
||||
seq := &yaml.Node{
|
||||
Kind: yaml.SequenceNode,
|
||||
Tag: "!!seq",
|
||||
}
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
elemNode, err := valueToNode(v.Index(i))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("slice index %d: %w", i, err)
|
||||
}
|
||||
seq.Content = append(seq.Content, elemNode)
|
||||
}
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
// Scalar types
|
||||
node := &yaml.Node{Kind: yaml.ScalarNode}
|
||||
switch v.Kind() {
|
||||
|
||||
@@ -114,7 +114,7 @@ func (h *XRPCHandler) HandleGetSubscriptionInfo(w http.ResponseWriter, r *http.R
|
||||
stats, err := h.pdsServer.GetQuotaForUserWithTier(r.Context(), userDID, h.manager.quotaMgr)
|
||||
if err == nil {
|
||||
info.CurrentUsage = stats.TotalSize
|
||||
info.CrewTier = stats.Tier // tier from local crew record (what's actually enforced)
|
||||
info.CrewTier = stats.Tier // tier from local crew record (what's actually enforced)
|
||||
info.CurrentLimit = stats.Limit
|
||||
|
||||
// If no subscription but crew has a tier, show that as current
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
// Package db contains a vendored from github.com/bluesky-social/indigo/carstore/sqlite_store.go
|
||||
// Package db contains a vendored from github.com/bluesky-social/indigo/carstore/sqlite_store.go
|
||||
// Source: github.com/bluesky-social/indigo@v0.0.0-20260203235305-a86f3ae1f8ec/carstore/
|
||||
// Reason: indigo's carstore hardcodes mattn/go-sqlite3, which conflicts with go-libsql
|
||||
// (both bundle SQLite C libraries and cannot coexist in the same binary).
|
||||
//
|
||||
// This package replaces the mattn driver with go-libsql and removes Prometheus metrics.
|
||||
// Once upstream accepts a driver-agnostic constructor, this vendored copy can be removed.
|
||||
// Modifications:
|
||||
// - Replaced mattn/go-sqlite3 driver with go-libsql
|
||||
// - Removed all Prometheus metric counters and .Inc() calls
|
||||
// - Changed package from 'carstore' to 'db'
|
||||
// - Added NewSQLiteStoreWithDB constructor for injecting an existing *sql.DB
|
||||
// - Changed sql.Open("sqlite3", path) to sql.Open("libsql", ...) with proper DSN
|
||||
|
||||
// Modifications:
|
||||
// - Replaced mattn/go-sqlite3 driver with go-libsql
|
||||
// - Removed all Prometheus metric counters and .Inc() calls
|
||||
// - Changed package from 'carstore' to 'db'
|
||||
// - Added NewSQLiteStoreWithDB constructor for injecting an existing *sql.DB
|
||||
// - Changed sql.Open("sqlite3", path) to sql.Open("libsql", ...) with proper DSN
|
||||
package db
|
||||
|
||||
import (
|
||||
|
||||
Reference in New Issue
Block a user