Help us shape the future of Go! Take the 2026 Go Developer Survey

settings

package
v2.17.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 25, 2026 License: BSD-3-Clause Imports: 52 Imported by: 0

Documentation

Overview

Package settings owns persisted application settings and their HTTP surface.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func EnsureInstanceID added in v2.16.0

func EnsureInstanceID(ctx context.Context, db *database.DB) (string, error)

EnsureInstanceID returns this installation's persisted instance ID, creating it on first start.

func RegisterSettings

func RegisterSettings(api huma.API, settingsService *SettingsService, settingsSearchService *SettingsSearchService, proxyRemoteJSON handlerutil.RemoteJSONProxy, cfg *config.Config)

RegisterSettings registers settings management routes using Huma.

Types

type GetCategoriesOutput

type GetCategoriesOutput struct {
	Body []category.Category
}

type GetPublicSettingsInput

type GetPublicSettingsInput struct {
	EnvironmentID string `path:"id" doc:"Environment ID"`
}

type GetPublicSettingsOutput

type GetPublicSettingsOutput struct {
	Body []settings.PublicSetting
}

type GetSettingsInput

type GetSettingsInput struct {
	EnvironmentID string `path:"id" doc:"Environment ID"`
}

type GetSettingsOutput

type GetSettingsOutput struct {
	Body []settings.PublicSetting
}

type Module

type Module struct {
	// contains filtered or unexported fields
}

Module joins the settings service with its route dependencies.

func New

func New(service *SettingsService, search *SettingsSearchService, proxyRemoteJSON handlerutil.RemoteJSONProxy, cfg *config.Config) *Module

New builds the settings domain around its initialized service.

func (*Module) RegisterRoutes

func (m *Module) RegisterRoutes(api huma.API)

RegisterRoutes mounts settings endpoints for runtime and schema discovery.

func (*Module) Service

func (m *Module) Service() *SettingsService

Service exposes settings operations to collaborating domains.

type OidcConfig added in v2.8.1

type OidcConfig struct {
	ClientID     string `json:"clientId"`
	ClientSecret string `json:"clientSecret"`
	IssuerURL    string `json:"issuerUrl"`
	Scopes       string `json:"scopes"`

	AuthorizationEndpoint       string `json:"authorizationEndpoint,omitempty"`
	TokenEndpoint               string `json:"tokenEndpoint,omitempty"`
	UserinfoEndpoint            string `json:"userinfoEndpoint,omitempty"`
	JwksURI                     string `json:"jwksUri,omitempty"`
	DeviceAuthorizationEndpoint string `json:"deviceAuthorizationEndpoint,omitempty"`

	// GroupsClaim is the claim path Arcane reads group memberships from on
	// every OIDC login. Matched against oidc_role_mappings to produce role
	// assignments. Default: "groups".
	GroupsClaim string `json:"groupsClaim,omitempty"`

	SkipTlsVerify bool `json:"skipTlsVerify"`
}

type SearchSettingsInput

type SearchSettingsInput struct {
	Body searchtypes.Request `doc:"Search query"`
}

type SearchSettingsOutput

type SearchSettingsOutput struct {
	Body searchtypes.Response
}

type SettingKeyNotFoundError added in v2.8.1

type SettingKeyNotFoundError struct {
	// contains filtered or unexported fields
}

func (SettingKeyNotFoundError) Error added in v2.8.1

func (e SettingKeyNotFoundError) Error() string

func (SettingKeyNotFoundError) Is added in v2.8.1

func (e SettingKeyNotFoundError) Is(target error) bool

type SettingSensitiveForbiddenError added in v2.8.1

type SettingSensitiveForbiddenError struct {
	// contains filtered or unexported fields
}

func (SettingSensitiveForbiddenError) Error added in v2.8.1

func (SettingSensitiveForbiddenError) Is added in v2.8.1

type SettingVariable added in v2.8.1

type SettingVariable struct {
	Key   string `gorm:"primaryKey"`
	Value string
}

func (SettingVariable) AsDurationSeconds added in v2.8.1

func (s SettingVariable) AsDurationSeconds() time.Duration

AsDurationSeconds returns the value as a time.Duration in seconds

func (SettingVariable) AsInt added in v2.8.1

func (s SettingVariable) AsInt() int

AsInt returns the value as an integer

func (SettingVariable) IsTrue added in v2.8.1

func (s SettingVariable) IsTrue() bool

IsTrue returns true if the value is a truthy string

func (SettingVariable) TableName added in v2.8.1

func (SettingVariable) TableName() string

type SettingVisibility added in v2.8.1

type SettingVisibility int
const (
	SettingVisibilityPublic   SettingVisibility = 0
	SettingVisibilityNonAdmin SettingVisibility = 1
	SettingVisibilityAll      SettingVisibility = 2
)

type Settings added in v2.8.1

type Settings struct {
	// General category
	ProjectsDirectory           SettingVariable `` /* 234-byte string literal not displayed */
	TemplatesDirectory          SettingVariable `` /* 252-byte string literal not displayed */
	FollowProjectSymlinks       SettingVariable `` /* 287-byte string literal not displayed */
	SwarmStackSourcesDirectory  SettingVariable `` /* 273-byte string literal not displayed */
	DiskUsagePath               SettingVariable `` /* 171-byte string literal not displayed */
	BaseServerURL               SettingVariable `` /* 183-byte string literal not displayed */
	EnableGravatar              SettingVariable `` /* 215-byte string literal not displayed */
	AvatarMaxUploadSizeMb       SettingVariable `` /* 258-byte string literal not displayed */
	DefaultShell                SettingVariable `` /* 162-byte string literal not displayed */
	ExperimentalFeaturesEnabled SettingVariable `` /* 251-byte string literal not displayed */
	DevelopmentBrandingEnabled  SettingVariable `` /* 274-byte string literal not displayed */
	AllEnvironmentsLimit        SettingVariable `` /* 256-byte string literal not displayed */
	EnvironmentHealthInterval   SettingVariable `` /* 395-byte string literal not displayed */

	// Docker category
	AutoUpdate                     SettingVariable `` /* 208-byte string literal not displayed */
	AutoUpdateInterval             SettingVariable `` /* 223-byte string literal not displayed */
	AutoUpdateDelay                SettingVariable `` /* 253-byte string literal not displayed */
	AutoUpdateExcludedContainers   SettingVariable `` /* 210-byte string literal not displayed */
	PollingEnabled                 SettingVariable `` /* 197-byte string literal not displayed */
	PollingInterval                SettingVariable `` /* 209-byte string literal not displayed */
	ImageEventWatcherEnabled       SettingVariable `` /* 269-byte string literal not displayed */
	DockerClientRefreshInterval    SettingVariable `` /* 274-byte string literal not displayed */
	EventCleanupInterval           SettingVariable `` /* 213-byte string literal not displayed */
	ExpiredSessionsCleanupInterval SettingVariable `` /* 266-byte string literal not displayed */
	ActivityHistoryRetentionDays   SettingVariable `` /* 418-byte string literal not displayed */
	UpgradeLogRetentionDays        SettingVariable `` /* 274-byte string literal not displayed */
	ActivityHistoryMaxEntries      SettingVariable `` /* 285-byte string literal not displayed */
	MaxConcurrentActivities        SettingVariable `` /* 275-byte string literal not displayed */
	AutoInjectEnv                  SettingVariable `` /* 243-byte string literal not displayed */
	DefaultDeployPullPolicy        SettingVariable `` /* 215-byte string literal not displayed */
	ToolsImageRegistry             SettingVariable `` /* 234-byte string literal not displayed */
	UpdateCheckRegistry            SettingVariable `` /* 258-byte string literal not displayed */
	ScheduledPruneEnabled          SettingVariable `` /* 213-byte string literal not displayed */
	ScheduledPruneInterval         SettingVariable `` /* 207-byte string literal not displayed */
	PruneContainerMode             SettingVariable `` /* 234-byte string literal not displayed */
	PruneContainerUntil            SettingVariable `` /* 238-byte string literal not displayed */
	PruneImageMode                 SettingVariable `` /* 223-byte string literal not displayed */
	PruneImageUntil                SettingVariable `` /* 222-byte string literal not displayed */
	PruneVolumeMode                SettingVariable `` /* 224-byte string literal not displayed */
	PruneNetworkMode               SettingVariable `` /* 225-byte string literal not displayed */
	PruneNetworkUntil              SettingVariable `` /* 230-byte string literal not displayed */
	PruneBuildCacheMode            SettingVariable `` /* 241-byte string literal not displayed */
	PruneBuildCacheUntil           SettingVariable `` /* 244-byte string literal not displayed */
	AutoHealEnabled                SettingVariable `` /* 217-byte string literal not displayed */
	AutoHealInterval               SettingVariable `` /* 214-byte string literal not displayed */
	AutoHealExcludedContainers     SettingVariable `` /* 233-byte string literal not displayed */
	AutoHealMaxRestarts            SettingVariable `` /* 237-byte string literal not displayed */
	AutoHealRestartWindow          SettingVariable `` /* 238-byte string literal not displayed */
	VolumeHelperIdleTimeout        SettingVariable `` /* 278-byte string literal not displayed */
	MaxImageUploadSize             SettingVariable `` /* 232-byte string literal not displayed */
	GitSyncMaxFiles                SettingVariable `` /* 275-byte string literal not displayed */
	GitSyncMaxTotalSizeMb          SettingVariable `` /* 295-byte string literal not displayed */
	GitSyncMaxBinarySizeMb         SettingVariable `` /* 310-byte string literal not displayed */
	DockerHost                     SettingVariable `` /* 175-byte string literal not displayed */
	BuildProvider                  SettingVariable `` /* 308-byte string literal not displayed */
	BuildHistoryRetentionDays      SettingVariable `` /* 282-byte string literal not displayed */
	BuildsDirectory                SettingVariable `` /* 192-byte string literal not displayed */
	BuildTimeout                   SettingVariable `` /* 204-byte string literal not displayed */
	DepotProjectId                 SettingVariable `` /* 165-byte string literal not displayed */
	DepotToken                     SettingVariable `` /* 167-byte string literal not displayed */

	// Authentication and security categories
	AuthLocalEnabled                      SettingVariable `` /* 400-byte string literal not displayed */
	AuthSessionTimeout                    SettingVariable `` /* 203-byte string literal not displayed */
	AuthPasswordPolicy                    SettingVariable `` /* 209-byte string literal not displayed */
	FeatureVulnerabilityManagementEnabled SettingVariable `` /* 348-byte string literal not displayed */
	FeatureSwarmEnabled                   SettingVariable `` /* 262-byte string literal not displayed */
	VulnerabilityScanEnabled              SettingVariable `` /* 389-byte string literal not displayed */
	VulnerabilityScanInterval             SettingVariable `` /* 243-byte string literal not displayed */
	VulnerabilityThreatIntelEnabled       SettingVariable `` /* 327-byte string literal not displayed */
	TrivyDbRegistry                       SettingVariable `` /* 270-byte string literal not displayed */
	TrivyNetwork                          SettingVariable `` /* 293-byte string literal not displayed */
	TrivySecurityOpts                     SettingVariable `` /* 319-byte string literal not displayed */
	TrivyPrivileged                       SettingVariable `` /* 244-byte string literal not displayed */
	TrivyResourceLimitsEnabled            SettingVariable `` /* 238-byte string literal not displayed */
	TrivyCpuLimit                         SettingVariable `` /* 261-byte string literal not displayed */
	TrivyMemoryLimitMb                    SettingVariable `` /* 246-byte string literal not displayed */
	TrivyConcurrentScanContainers         SettingVariable `` /* 302-byte string literal not displayed */
	TrivyConfig                           SettingVariable `` /* 200-byte string literal not displayed */
	TrivyIgnore                           SettingVariable `` /* 216-byte string literal not displayed */
	TrivyServerEnabled                    SettingVariable `` /* 372-byte string literal not displayed */
	TrivyServerUrl                        SettingVariable `` /* 276-byte string literal not displayed */
	TrivyServerToken                      SettingVariable `` /* 285-byte string literal not displayed */
	TrivyIgnoreUnfixed                    SettingVariable `` /* 316-byte string literal not displayed */
	ImagePatchSuffix                      SettingVariable `` /* 246-byte string literal not displayed */
	ImagePatchTimeoutSec                  SettingVariable `` /* 262-byte string literal not displayed */
	ImagePatchAllPlatforms                SettingVariable `` /* 278-byte string literal not displayed */
	ImageAutoPatchEnabled                 SettingVariable `` /* 290-byte string literal not displayed */
	ImageAutoPatchInterval                SettingVariable `` /* 240-byte string literal not displayed */
	LifecycleEnabled                      SettingVariable `` /* 327-byte string literal not displayed */
	LifecycleDefaultRunnerImage           SettingVariable `` /* 303-byte string literal not displayed */
	LifecycleMaxTimeoutSec                SettingVariable `` /* 276-byte string literal not displayed */
	OidcEnabled                           SettingVariable `` /* 230-byte string literal not displayed */
	OidcClientId                          SettingVariable `` /* 178-byte string literal not displayed */
	OidcClientSecret                      SettingVariable `` /* 195-byte string literal not displayed */
	OidcIssuerUrl                         SettingVariable `` /* 191-byte string literal not displayed */
	OidcAuthorizationEndpoint             SettingVariable `` /* 217-byte string literal not displayed */
	OidcTokenEndpoint                     SettingVariable `` /* 185-byte string literal not displayed */
	OidcUserinfoEndpoint                  SettingVariable `` /* 197-byte string literal not displayed */
	OidcJwksEndpoint                      SettingVariable `` /* 186-byte string literal not displayed */
	OidcDeviceAuthorizationEndpoint       SettingVariable `` /* 271-byte string literal not displayed */
	OidcScopes                            SettingVariable `` /* 181-byte string literal not displayed */
	OidcGroupsClaim                       SettingVariable `` /* 245-byte string literal not displayed */
	OidcSkipTlsVerify                     SettingVariable `` /* 210-byte string literal not displayed */
	OidcAutoRedirectToProvider            SettingVariable `` /* 243-byte string literal not displayed */
	OidcMergeAccounts                     SettingVariable `` /* 259-byte string literal not displayed */
	OidcProviderName                      SettingVariable `` /* 227-byte string literal not displayed */
	OidcProviderLogoUrl                   SettingVariable `` /* 211-byte string literal not displayed */
	OidcMobileRedirectUris                SettingVariable `` /* 286-byte string literal not displayed */

	// Notifications category (placeholder for category metadata only - actual settings managed via notification service)
	NotificationsCategoryPlaceholder SettingVariable `` /* 396-byte string literal not displayed */

	ApnsEnabled    SettingVariable `` /* 236-byte string literal not displayed */
	ApnsChannelID  SettingVariable `key:"apnsChannelId,internal"`
	ApnsSigningKey SettingVariable `key:"apnsSigningKey,internal,sensitive"`

	AgentToken                 SettingVariable `key:"agentToken,internal,sensitive"`
	BrowserSessionSigningKey   SettingVariable `key:"browserSessionSigningKey,internal,sensitive"`
	JwtSigningKeySeed          SettingVariable `key:"jwtSigningKeySeed,internal,sensitive"`
	InstanceID                 SettingVariable `key:"instanceId,internal"`
	SensitiveSettingsEncrypted SettingVariable `key:"sensitiveSettingsEncrypted,internal"`

	// Users category (admin management page - no actual settings)
	UsersCategoryPlaceholder SettingVariable `` /* 304-byte string literal not displayed */

	// API Keys category (admin management page - no actual settings)
	ApiKeysCategoryPlaceholder SettingVariable `` /* 351-byte string literal not displayed */

	S3DestinationsCategoryPlaceholder SettingVariable `` /* 393-byte string literal not displayed */

	SystemBackupsCategoryPlaceholder SettingVariable `` /* 356-byte string literal not displayed */
	SystemVolumeBackupConfig         SettingVariable `key:"systemVolumeBackupConfig,internal"`

	FederatedCredentialsCategoryPlaceholder SettingVariable `` /* 262-byte string literal not displayed */

	// Webhooks category (management page - no actual settings)
	WebhooksCategoryPlaceholder SettingVariable `` /* 392-byte string literal not displayed */

	// Timeout category
	DockerAPITimeout       SettingVariable `` /* 362-byte string literal not displayed */
	DockerImagePullTimeout SettingVariable `` /* 243-byte string literal not displayed */
	TrivyScanTimeout       SettingVariable `` /* 231-byte string literal not displayed */
	GitOperationTimeout    SettingVariable `` /* 237-byte string literal not displayed */
	HTTPClientTimeout      SettingVariable `` /* 218-byte string literal not displayed */
	RegistryTimeout        SettingVariable `` /* 218-byte string literal not displayed */
	RegistryTagTimeout     SettingVariable `` /* 278-byte string literal not displayed */
	ProxyRequestTimeout    SettingVariable `` /* 215-byte string literal not displayed */
	DeployWaitTimeout      SettingVariable `` /* 291-byte string literal not displayed */
}

func DefaultSettingsConfig

func DefaultSettingsConfig() *Settings

DefaultSettingsConfig returns the canonical default settings model used by Arcane.

func (*Settings) Clone added in v2.8.1

func (s *Settings) Clone() *Settings

func (*Settings) FieldByKey added in v2.8.1

func (s *Settings) FieldByKey(key string) (defaultValue string, isPublic, isSensitive bool, err error)

func (*Settings) ToSettingVariableSlice added in v2.8.1

func (s *Settings) ToSettingVariableSlice(visibility SettingVisibility, redactSensitiveValues bool) []SettingVariable

func (*Settings) UpdateField added in v2.8.1

func (s *Settings) UpdateField(key, value string, noSensitive bool) error

type SettingsHandler

type SettingsHandler struct {
	// contains filtered or unexported fields
}

SettingsHandler provides Huma-based settings management endpoints.

func (*SettingsHandler) GetCategories

func (h *SettingsHandler) GetCategories(ctx context.Context, input *struct{}) (*GetCategoriesOutput, error)

GetCategories returns the settings categories reachable at any scope.

func (*SettingsHandler) GetPublicSettings

GetPublicSettings returns public settings for an environment.

func (*SettingsHandler) GetSettings

func (h *SettingsHandler) GetSettings(ctx context.Context, input *GetSettingsInput) (*GetSettingsOutput, error)

GetSettings returns all settings for an environment.

func (*SettingsHandler) Search

Search searches settings by query.

func (*SettingsHandler) UpdateSettings

UpdateSettings updates settings for an environment.

type SettingsSearchService added in v2.8.1

type SettingsSearchService struct {
	// contains filtered or unexported fields
}

func NewSettingsSearchService added in v2.8.1

func NewSettingsSearchService() *SettingsSearchService

func (*SettingsSearchService) GetSettingsCategories added in v2.8.1

func (s *SettingsSearchService) GetSettingsCategories() []category.Category

GetSettingsCategories returns the category index initialized by the service.

type SettingsService

type SettingsService struct {
	// contains filtered or unexported fields
}

func NewSettingsService

func NewSettingsService(ctx context.Context, db *database.DB, appConfig *config.Config) (*SettingsService, error)

func (*SettingsService) EncryptStoredSensitiveSettings added in v2.17.0

func (s *SettingsService) EncryptStoredSensitiveSettings(ctx context.Context) error

func (*SettingsService) EnsureBrowserSessionSigningKey added in v2.10.1

func (s *SettingsService) EnsureBrowserSessionSigningKey(ctx context.Context) ([]byte, error)

func (*SettingsService) EnsureDefaultSettings

func (s *SettingsService) EnsureDefaultSettings(ctx context.Context) error

func (*SettingsService) EnsureEncryptionKey

func (s *SettingsService) EnsureEncryptionKey(ctx context.Context) (string, error)

func (*SettingsService) EnsureJwtSigningKey added in v2.10.0

func (s *SettingsService) EnsureJwtSigningKey(ctx context.Context) (*mldsa.PrivateKey, error)

func (*SettingsService) GetBoolSetting

func (s *SettingsService) GetBoolSetting(ctx context.Context, key string, defaultValue bool) bool

func (*SettingsService) GetIntSetting

func (s *SettingsService) GetIntSetting(ctx context.Context, key string, defaultValue int) int

func (*SettingsService) GetSettingType

func (s *SettingsService) GetSettingType(key string) string

GetSettingType returns the type from the setting metadata

func (*SettingsService) GetSettings

func (s *SettingsService) GetSettings(_ context.Context) (*Settings, error)

GetSettings returns the shared, read-only effective (env-override-applied) settings snapshot. Mutation flows go through UpdateSettings, which works on an explicit clone.

func (*SettingsService) GetSettingsConfig

func (s *SettingsService) GetSettingsConfig() *Settings

func (*SettingsService) GetSettingsOrDefaults

func (s *SettingsService) GetSettingsOrDefaults(ctx context.Context) *Settings

GetSettingsOrDefaults logs any settings load failure and always returns a non-nil *Settings; a zero-valued struct makes SettingVariable helpers fall back to the caller's default.

func (*SettingsService) GetStringSetting

func (s *SettingsService) GetStringSetting(ctx context.Context, key, defaultValue string) string

func (*SettingsService) IsEnvOverrideActive

func (s *SettingsService) IsEnvOverrideActive(key string) bool

IsEnvOverrideActive reports whether a non-empty environment override controls the setting.

func (*SettingsService) IsFeatureEnabled added in v2.11.1

func (s *SettingsService) IsFeatureEnabled(ctx context.Context, id features.ID) bool

IsFeatureEnabled resolves feature availability on this environment.

func (*SettingsService) ListSettings

func (s *SettingsService) ListSettings(visibility SettingVisibility) []SettingVariable

func (*SettingsService) LoadDatabaseSettings

func (s *SettingsService) LoadDatabaseSettings(ctx context.Context) error

func (*SettingsService) NormalizeBuildsDirectory

func (s *SettingsService) NormalizeBuildsDirectory(ctx context.Context) error

func (*SettingsService) NormalizeProjectsDirectory

func (s *SettingsService) NormalizeProjectsDirectory(ctx context.Context, projectsDirEnv string) error

func (*SettingsService) NotifySettingsChanges

func (s *SettingsService) NotifySettingsChanges(ctx context.Context, keys ...string) error

NotifySettingsChanges publishes current values for keys through the settings write mutex. It is used when another settings-table workflow performs persistence.

func (*SettingsService) PersistEnvSettingsIfMissing

func (s *SettingsService) PersistEnvSettingsIfMissing(ctx context.Context) error

func (*SettingsService) PruneUnknownSettings

func (s *SettingsService) PruneUnknownSettings(ctx context.Context) error

func (*SettingsService) RequireFeature added in v2.11.1

func (s *SettingsService) RequireFeature(ctx context.Context, id features.ID) error

RequireFeature rejects operations when their runtime feature is disabled.

func (*SettingsService) SetBoolSetting

func (s *SettingsService) SetBoolSetting(ctx context.Context, key string, value bool) error

func (*SettingsService) SetContainerAutoUpdateExclusionInternal

func (s *SettingsService) SetContainerAutoUpdateExclusionInternal(ctx context.Context, containerName string, excluded bool) error

SetContainerAutoUpdateExclusionInternal adds (excluded) or removes a container name in the autoUpdateExcludedContainers setting.

func (*SettingsService) SetIntSetting

func (s *SettingsService) SetIntSetting(ctx context.Context, key string, value int) error

func (*SettingsService) SetStringSetting

func (s *SettingsService) SetStringSetting(ctx context.Context, key, value string) error

func (*SettingsService) Stop added in v2.15.0

func (s *SettingsService) Stop(ctx context.Context) error

Stop drains accepted effects and joins the settings worker.

func (*SettingsService) SubscribeSettingsChanges

func (s *SettingsService) SubscribeSettingsChanges(keys []string, callback func([]libarcane.SettingUpdate)) func()

SubscribeSettingsChanges invokes callback once with the matching values when any subscribed key is present in a successful update. Callbacks run serially on the effects worker, outside the write mutex.

func (*SettingsService) UpdateSetting

func (s *SettingsService) UpdateSetting(ctx context.Context, key, value string) error

func (*SettingsService) UpdateSettingValues

func (s *SettingsService) UpdateSettingValues(ctx context.Context, updates []libarcane.SettingUpdate) error

UpdateSettingValues persists a group of internal setting values atomically and publishes the refreshed snapshot before returning.

func (*SettingsService) UpdateSettings

func (s *SettingsService) UpdateSettings(ctx context.Context, updates settings.Update) ([]SettingVariable, error)

UpdateSettings publishes the refreshed snapshot before returning. Each subscriber's effects remain ordered and may finish after this method returns.

type UpdateSettingsInput

type UpdateSettingsInput struct {
	EnvironmentID string          `path:"id" doc:"Environment ID"`
	Body          settings.Update `doc:"Settings update data"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL