diff --git a/config-appview.example.yaml b/config-appview.example.yaml
index 7176efe..73932b1 100644
--- a/config-appview.example.yaml
+++ b/config-appview.example.yaml
@@ -50,6 +50,8 @@ ui:
libsql_sync_interval: 1m0s
# Source code URL displayed in the footer "Source" link. Defaults to the upstream ATCR project.
source_url: https://tangled.org/evan.jarrett.net/at-container-registry
+ # Bluesky handle or DID linked from the footer. Prefer a DID: handles can change. Defaults to the ATCR project account. Set to "" in YAML to hide the link (an empty env var will not do it: Viper treats it as unset).
+ bluesky_profile: did:plc:wfj5kyialpmcv2fzk6uqwsln
# Health check and cache settings.
health:
# How long to cache hold health check results.
diff --git a/pkg/appview/config.go b/pkg/appview/config.go
index 39a52d4..c2bfc2e 100644
--- a/pkg/appview/config.go
+++ b/pkg/appview/config.go
@@ -96,6 +96,10 @@ type UIConfig struct {
// Source code URL displayed in the footer "Source" link.
SourceURL string `yaml:"source_url" comment:"Source code URL displayed in the footer \"Source\" link. Defaults to the upstream ATCR project."`
+
+ // Bluesky profile linked from the footer. Handle or DID; prefer a DID,
+ // since handles change and a stale handle link breaks silently.
+ BlueskyProfile string `yaml:"bluesky_profile" comment:"Bluesky handle or DID linked from the footer. Prefer a DID: handles can change. Defaults to the ATCR project account. Set to \"\" in YAML to hide the link (an empty env var will not do it: Viper treats it as unset)."`
}
// HealthConfig defines health check and cache settings
@@ -236,6 +240,7 @@ func setDefaults(v *viper.Viper) {
v.SetDefault("ui.libsql_auth_token", "")
v.SetDefault("ui.libsql_sync_interval", "60s")
v.SetDefault("ui.source_url", "https://tangled.org/evan.jarrett.net/at-container-registry")
+ v.SetDefault("ui.bluesky_profile", "did:plc:wfj5kyialpmcv2fzk6uqwsln")
// Health defaults
v.SetDefault("health.cache_ttl", "15m")
diff --git a/pkg/appview/handlers/base.go b/pkg/appview/handlers/base.go
index 9bd9a0f..83fe4d2 100644
--- a/pkg/appview/handlers/base.go
+++ b/pkg/appview/handlers/base.go
@@ -53,6 +53,7 @@ type BaseUIHandler struct {
AIAdvisorEnabled bool // True when billing is fully configured AND Claude API key is set
BillingEnabled bool // True when the billing build is compiled in and Stripe is configured
SourceURL string // Source code URL for the footer "Source" link
+ BlueskyProfile string // Bluesky handle or DID for the footer link ("" hides it)
}
// IsManagedHold reports whether a hold DID is one of the appview's managed
diff --git a/pkg/appview/handlers/common.go b/pkg/appview/handlers/common.go
index d1bc30d..19e1573 100644
--- a/pkg/appview/handlers/common.go
+++ b/pkg/appview/handlers/common.go
@@ -20,6 +20,7 @@ type PageData struct {
OciClient string // Preferred OCI client for pull commands (e.g., "docker", "podman")
AIAdvisorEnabled bool // True when AI Image Advisor is available
SourceURL string // Source code URL for the footer "Source" link
+ BlueskyProfile string // Bluesky handle or DID for the footer link ("" hides it)
CurrentPath string // Request path (used for OAuth return_to)
}
@@ -51,6 +52,7 @@ func NewPageData(r *http.Request, h *BaseUIHandler) PageData {
OciClient: ociClient,
AIAdvisorEnabled: h.AIAdvisorEnabled,
SourceURL: h.SourceURL,
+ BlueskyProfile: h.BlueskyProfile,
CurrentPath: r.URL.RequestURI(),
}
}
diff --git a/pkg/appview/routes/routes.go b/pkg/appview/routes/routes.go
index f05bd75..85dc097 100644
--- a/pkg/appview/routes/routes.go
+++ b/pkg/appview/routes/routes.go
@@ -48,6 +48,7 @@ type UIDependencies struct {
WebhookDispatcher *webhooks.Dispatcher // Webhook dispatcher (nil if not configured)
ClaudeAPIKey string // Anthropic API key for AI advisor (empty = disabled)
SourceURL string // Source code URL for the footer "Source" link
+ BlueskyProfile string // Bluesky handle or DID for the footer link ("" hides it)
}
// RegisterUIRoutes registers all web UI and API routes on the provided router
@@ -88,6 +89,7 @@ func RegisterUIRoutes(router chi.Router, deps UIDependencies) {
BillingEnabled: deps.BillingManager != nil && deps.BillingManager.Enabled(),
AIAdvisorEnabled: deps.BillingManager != nil && deps.BillingManager.Enabled() && deps.ClaudeAPIKey != "",
SourceURL: deps.SourceURL,
+ BlueskyProfile: deps.BlueskyProfile,
}
// OAuth login routes (public)
diff --git a/pkg/appview/server.go b/pkg/appview/server.go
index afa07ee..b5bb4f7 100644
--- a/pkg/appview/server.go
+++ b/pkg/appview/server.go
@@ -412,6 +412,7 @@ func NewAppViewServer(cfg *Config, branding *BrandingOverrides) (*AppViewServer,
WebhookDispatcher: s.WebhookDispatcher,
ClaudeAPIKey: cfg.AI.APIKey,
SourceURL: cfg.UI.SourceURL,
+ BlueskyProfile: cfg.UI.BlueskyProfile,
LegalConfig: routes.LegalConfig{
CompanyName: cfg.Legal.CompanyName,
Jurisdiction: cfg.Legal.Jurisdiction,
diff --git a/pkg/appview/templates/components/footer.html b/pkg/appview/templates/components/footer.html
index e8190f0..6a66a40 100644
--- a/pkg/appview/templates/components/footer.html
+++ b/pkg/appview/templates/components/footer.html
@@ -5,11 +5,13 @@
Privacy·Terms
+ {{ with .BlueskyProfile }}
·
-
+
Bluesky
+ {{ end }}
{{ with .SourceURL }}
·