Configuration
Sourcegull is configured in three places:
- Environment variables (or the matching command-line flags) set what a process needs to start: its roles, the database, listeners, data directories, keys and repo nodes.
- Site configuration: one JSONC document, stored in the database and edited under
Site admin → Site configuration, for instance-wide behaviour (sign-in, permissions,
search, executors, email, …). Changes apply without a restart, except
gitMaxConcurrentClones(read when a repo node starts) andinsights.query.worker.concurrency(read when a worker starts). The page is split into sections;/site-admin/configuration?section=<id>opens one (general,auth,permissions,repositories,search,codeIntel,batchChanges,insights,notifications,observability). Each section links the admin pages it controls, and those pages link back under Related in their header. Find a setting above the sections searches key names and descriptions. - Settings: JSONC documents per user, per organization and global, merged into each user's effective settings.
All of the instance's own configuration can instead come from files kept in git (configuration as code): the site configuration, the global settings, and the objects otherwise created in the UI (code host connections, search contexts, roles, site admins, outbound webhooks, executor secrets, code intelligence policies and feature flags).
Environment variables
Every flag of sourcegull can be set through its SOURCEGULL_* variable (sourcegull <command> --help
lists them). Booleans accept 1/0, true/false, yes/no and on/off. The file
variables (SOURCEGULL_*_FILE) and SOURCEGULL_KEYS count as unset when empty.
Server (sourcegull run, sourcegull supervise)
| Variable | Default | Meaning |
|---|---|---|
SOURCEGULL_ROLES |
all |
Comma-separated roles: web, repo, worker, blob, or all (architecture). |
SOURCEGULL_DATABASE_URL |
PostgreSQL URL (required); may list several hosts of a replicated cluster (below). | |
SOURCEGULL_HTTP_ADDR |
0.0.0.0:7080 |
Public listener of the web role: UI, /.api/*, /healthz, /readyz. |
SOURCEGULL_INTERNAL_ADDR |
0.0.0.0:6060 |
Internal listener: /metrics, /healthz, /readyz; with the blob role also the blob API that SOURCEGULL_BLOB_STORE=http://… points at. |
SOURCEGULL_DATA_DIR |
/var/opt/sourcegull |
Persistent data. |
SOURCEGULL_SHUTDOWN_DELAY |
5s |
On SIGTERM or SIGINT, how long /readyz returns 503 while the process keeps serving, before the listeners close (500ms, 5s, 1m, or seconds; 0 stops at once). The Helm chart sets 0: its pods pause in a preStop hook instead. |
SOURCEGULL_KEYS_FILE |
$SOURCEGULL_DATA_DIR/keys |
Encryption key file (below). |
SOURCEGULL_KEYS |
The encryption keys themselves, instead of a key file (below). | |
SOURCEGULL_EXTERNAL_URL |
Default for externalURL when the site configuration does not set it. |
|
SOURCEGULL_SITE_CONFIG_FILE |
Read the site configuration from this file instead of the database; it is then read-only in the UI and API (below). | |
SOURCEGULL_RESOURCES_FILE |
Resources file: connections, search contexts, roles, site admins, webhooks, executor secrets, code intelligence policies and feature flags (below). | |
SOURCEGULL_GLOBAL_SETTINGS_FILE |
Global settings document; they are then read-only in the UI and API (below). | |
SOURCEGULL_BLOB_STORE |
<data dir>/blobs |
file:///path, http://host:port of a blob role, or s3://bucket[/prefix] (S3). |
SOURCEGULL_DB_MAX_CONNECTIONS |
20 |
Main database pool per process (below). |
SOURCEGULL_DB_WORKER_MAX_CONNECTIONS |
10 |
The worker role's own pool when it shares a process with other roles. |
SOURCEGULL_LOG_FORMAT |
json |
json or pretty. Log filters come from RUST_LOG (default info). |
SOURCEGULL_RATE_LIMIT_BACKEND |
auto |
Code host request budgets: postgres (shared by every process), memory (per process), or auto (memory when one process runs web, repo and worker). |
SOURCEGULL_LEADER_BACKEND |
postgres |
Leases of singleton tasks: Postgres advisory locks or Kubernetes Lease objects (kubernetes). |
SOURCEGULL_LEADER_NAMESPACE, SOURCEGULL_LEADER_LEASE_PREFIX, SOURCEGULL_LEADER_IDENTITY, SOURCEGULL_LEADER_LEASE_DURATION |
pod namespace, sourcegull, host name, 15 |
Kubernetes lease settings. |
SOURCEGULL_SEARCH_THREADS |
available CPUs | Search worker threads on a repo node. |
Repo nodes
| Variable | Default | Meaning |
|---|---|---|
SOURCEGULL_REPO_NODES |
this node | Comma-separated repo node addresses, e.g. http://sourcegull-repo-0.sourcegull-repo:3178,http://sourcegull-repo-1.sourcegull-repo:3178. Every process must list them with the same spelling. |
SOURCEGULL_REPO_NODE_ADDR |
the only node, or the one whose host is $HOSTNAME |
This node's own address as listed in SOURCEGULL_REPO_NODES. |
SOURCEGULL_REPO_GRPC_ADDR |
0.0.0.0:3178 |
gRPC listener of the repo role. |
SOURCEGULL_REPO_DATA_DIR |
$SOURCEGULL_DATA_DIR |
Bare repositories, index shards and caches. |
SOURCEGULL_REPO_MAX_CONCURRENT_FETCHES |
10 |
Concurrent fetches per node (clones: site configuration gitMaxConcurrentClones). |
SOURCEGULL_REPO_SSH_KEY, SOURCEGULL_REPO_SSH_KNOWN_HOSTS |
Private key and known_hosts for connections that clone over SSH. |
|
SOURCEGULL_REPO_DISABLE_INDEX |
false |
Keep no search index on this node; content search then reads from git. |
SOURCEGULL_REPO_INDEX_DEBOUNCE_MS |
5000 |
Pushes within this window are indexed in one build. |
SOURCEGULL_REPO_PAUSE_CLONES_FREE_PERCENT, SOURCEGULL_REPO_PAUSE_CLONES_FREE_GIB |
10, 20 |
Clones pause when free disk space is below both thresholds. 0 turns the pause off. |
SOURCEGULL_REPO_EVICT_FREE_PERCENT, SOURCEGULL_REPO_EVICT_FREE_GIB |
7, 10 |
Caches and shards of inactive repositories are evicted when free space is below both. 0 turns eviction off. |
SOURCEGULL_REPO_REBALANCE_CONCURRENCY |
2 |
Repositories moved to this node at once during rebalancing; 0 turns moves to it off. |
Blob storage (S3)
SOURCEGULL_S3_ENDPOINT, SOURCEGULL_S3_REGION, SOURCEGULL_S3_PATH_STYLE, SOURCEGULL_S3_ACCESS_KEY_ID,
SOURCEGULL_S3_SECRET_ACCESS_KEY, SOURCEGULL_S3_SESSION_TOKEN, SOURCEGULL_S3_CA_CERT,
SOURCEGULL_S3_INSECURE_SKIP_VERIFY, SOURCEGULL_S3_PART_SIZE, SOURCEGULL_S3_CREATE_BUCKET: see
Install → S3 blob storage.
Executors (sourcegull executor)
| Variable | Default | Meaning |
|---|---|---|
SOURCEGULL_EXECUTOR_FRONTEND_URL |
URL of the web role, e.g. http://sourcegull-web:7080. |
|
SOURCEGULL_EXECUTOR_TOKEN |
The shared token, equal to executors.accessToken in the site configuration. |
|
SOURCEGULL_EXECUTOR_NAME |
host name | Unique executor name. |
SOURCEGULL_EXECUTOR_QUEUES |
codeintel |
codeintel (auto-indexing) and/or batches (bulk change spec executions). |
SOURCEGULL_EXECUTOR_MAX_JOBS |
1 |
Jobs run at the same time. |
SOURCEGULL_EXECUTOR_RUNTIME |
docker |
docker (a container per step) or kubernetes (a Job per auto-indexing job, a Job per step of a bulk change). |
SOURCEGULL_EXECUTOR_DOCKER_NETWORK |
bridge |
Network of step containers: bridge, none or a named network. |
SOURCEGULL_EXECUTOR_DOCKER_MEMORY, SOURCEGULL_EXECUTOR_DOCKER_CPUS |
none | Limits per step container (4g, 2). |
SOURCEGULL_EXECUTOR_DOCKER_ALWAYS_PULL |
false |
Pull step images even when present. |
SOURCEGULL_EXECUTOR_POLL_INTERVAL, SOURCEGULL_EXECUTOR_SCRATCH_DIR, SOURCEGULL_EXECUTOR_GIT |
1, /tmp/sourcegull-executor, git |
Poll interval (seconds), checkout directory, git binary. |
SOURCEGULL_EXECUTOR_KUBERNETES_* |
Kubernetes runtime: NAMESPACE, HELPER_IMAGE, POD_FRONTEND_URL, RUNTIME_CLASS, NODE_SELECTOR, TOLERATIONS, SERVICE_ACCOUNT, IMAGE_PULL_SECRETS, CPU_REQUEST, MEMORY_REQUEST, CPU_LIMIT, MEMORY_LIMIT, WORKSPACE_SIZE, DEADLINE (default 4 h), START_TIMEOUT (default 600 s), KEEP_JOBS. |
Command-line client (gull)
SOURCEGULL_ENDPOINT (the instance URL) and SOURCEGULL_ACCESS_TOKEN (an
access token).
Site configuration
The site configuration is JSONC (comments and trailing commas allowed). The editor under
Site admin → Site configuration completes keys, shows their documentation, validates as you
type, and can reorganize keys by section. The JSON Schema is served at
/.api/site-config-schema. A new instance starts from a commented template that shows the
common keys.
Credentials (email.smtp.password, auth.providers[].clientSecret, executors.accessToken,
scim.authToken) are shown as "REDACTED" in the editor and the API. Saving a document that
still holds "REDACTED" keeps the stored value (an auth provider keeps the secret of the stored
provider with the same type and clientID); a new value replaces it. ${env:…} and
${file:…} references are shown as they are.
Frequently used keys:
| Key | Purpose |
|---|---|
externalURL |
The URL users reach Sourcegull at; used for OAuth callbacks, webhooks and links in emails. |
updateCheck |
Whether Site admin shows that a newer release is out (default true). When a site admin opens the overview, the server asks the GitHub releases API for the latest release (at most every 12 hours) and compares it locally; the request carries nothing about the instance. Turn it off without internet access. |
auth.providers, auth.public, auth.sessionExpiry, auth.passwordPolicy, auth.lockout, auth.rateLimits, auth.accessTokens, auth.accessRequest, auth.allowedIpAddress, auth.enableUsernameChanges |
Sign-in and accounts (permissions). |
mcp.oauth.enabled, mcp.oauth.dynamicClientRegistration, mcp.oauth.clientIdMetadataDocuments, mcp.oauth.accessTokenTTL, mcp.oauth.refreshTokenTTL, mcp.oauth.allowedRedirectHosts |
OAuth sign-in of MCP clients (MCP). |
permissions.userMapping, authz.enforceForSiteAdmins, permissions.sync* |
Repository permissions. |
scim.authToken |
Enables SCIM provisioning. |
repoListUpdateInterval, gitUpdateInterval, disableAutoGitUpdates, gitMaxConcurrentClones |
Discovery and fetching. |
repoNodes.pinnedRepos |
Pin repositories to repo nodes (repository name → repo node address). Replaces experimentalFeatures.gitServerPinnedRepos, which is still read, with a deprecation warning, when repoNodes.pinnedRepos is not set. |
codeHosts.tokenExpiryAlerts |
Emails before code host connection tokens expire: enabled (default true), daysBefore (default [14, 7, 1]), recipients (default: every site admin) (token expiry alerts). |
search.limits |
Search limits, read at each search: defaultTimeoutSeconds (20), maxTimeoutSeconds (60; timeout: is clamped to it), streamingMaxResults (500, the result count without count:), maxRepos (unset: no limit; repositories beyond it are skipped with a warning), commitDiffMaxRepos (50) and commitDiffWithTimeFilterMaxRepos (10000) for type:commit/type:diff without and with before:/after:, and for search jobs exhaustiveMaxResults (1000000) and exhaustiveTimeoutSeconds (3600, per repository revision). |
search.index.branches |
Branches to index besides the default branch, per repository (up to 63). |
search.index.recentBranches |
Also index each repository's recently updated branches: maxAge (default 30d, 0 for no limit) and max (default 20, up to 63). Off unless set (searching all branches). |
search.index.alwaysIndex |
Path patterns whose files are searched by content above 1 MiB (up to 16 MiB), per repository; "*" applies to all (search). |
executors.accessToken |
Shared secret of the executors (at least 20 characters). |
codeIntelAutoIndexing.*, codeIntel.gitlabJobTokenUploads, codeIntel.maxUploadBytes, codeIntelRanking.documentReferenceCountsEnabled |
Code navigation. |
batchChanges.* |
Bulk changes. |
insights.*, searchJobs.retention, own.syncTeamsFromGitLabGroups |
Other features. |
email.address, email.senderName, email.smtp |
Outbound email. |
webhooks.outbound.allowedHosts, webhooks.outbound.logRetention |
Outbound webhooks. |
log.securityEventLog, eventLogs.retention, eventLogs.rollupRetention |
Audit and usage logs. |
observability.logSlowSearches, observability.logSlowGraphQLRequests, outboundRequestLogLimit |
Diagnostic logs. A slow GraphQL request is stored with its variables only when it is a query, and with the values of secret-looking variables (tokens, passwords, credentials) replaced. |
With SOURCEGULL_SITE_CONFIG_FILE the document comes from a file (for example a Kubernetes
Secret) and cannot be edited in the UI; see configuration as code.
Settings and the settings cascade
Settings are per-viewer preferences. They exist at three levels:
- Global settings (Site admin → Global settings),
- each organization's settings, for its members,
- each user's own settings (User settings → Preferences).
A user's effective settings merge them in that order: objects merge key by key, and any other value at a later level replaces the earlier one, so a list set at one level replaces the whole list from the levels before it. The settings:
search.defaultPatternType(defaultkeyword): the pattern type the search boxes start with. A link to a search keeps its own, so it gives everyone the same results.search.includeArchived: see Archived repositories.quicklinks: links on the search home page. Targets arehttp,https,mailtoor relative; others are not shown.notices: banners, at the top of every page or on the search home page. Messages are Markdown, without HTML. A dismissible notice stays dismissed in that browser until its message changes.orgs.allMembersBatchChangesAdmin, in an organization's settings: every member may close and delete the organization's bulk changes.
Other keys are kept and passed through to the UI. search.includeForks is accepted but has no
effect yet: use fork:yes in the query.
Site admin → Global settings, an organization's Settings page and User settings → Preferences edit the document as forms or, under Edit as JSON, as text. A form changes only the value it touches, so comments and other keys stay. A value set at a higher level shows as the inherited default. Keys this version does not know are listed under Other.
The settings reference page at /help/settings lists every setting with its type,
allowed values and description; the editor's starting document links to it. The schema is
served at /.api/settings-schema.
Changing settings with an agent
The MCP tools are read-only: an agent connected to Sourcegull cannot change the instance through them. That keeps the tools that read code (which may hold instructions written by anyone) away from the instance's configuration. When a site admin's agent connects, the server instructions tell it where changes are made instead:
- Files. What comes from
SOURCEGULL_SITE_CONFIG_FILE,SOURCEGULL_GLOBAL_SETTINGS_FILEorSOURCEGULL_RESOURCES_FILEis changed in those files (the instructions name the ones in use), checked withsourcegull config checkand rolled out as usual (configuration as code). An agent working in the deployment's repository can propose the change as a merge request. - The GraphQL API, for everything else, with an access token of the admin. Tokens from the MCP browser sign-in do not work there.
For the API route, create a token under User settings → Access tokens with a short expiry and put it in the environment of the shell the agent runs commands in, not in the conversation:
export SOURCEGULL_ADMIN_TOKEN=<token>
The agent then reads and saves documents with the version it read, so a concurrent edit makes the save fail instead of being overwritten:
api() {
curl -sS https://sourcegull.example.com/.api/graphql \
-H "Authorization: token $SOURCEGULL_ADMIN_TOKEN" -H 'Content-Type: application/json' -d "$1"
}
# Site configuration: the version and the document (secrets show as "REDACTED")
api '{"query":"{ site { configuration { id effectiveContents validationMessages } } }"}'
# Save the edited document; "REDACTED" left in place keeps the stored secret
api "$(jq -n --arg input "$(cat site-config.jsonc)" --argjson id 42 \
'{query: "mutation($id: Int!, $input: String!) { updateSiteConfiguration(lastID: $id, input: $input) }",
variables: {id: $id, input: $input}}')"
# Global settings: the subject ID and version, then a save
api '{"query":"{ site { id latestSettings { id contents } } }"}'
api "$(jq -n --arg subject "<site id>" --arg contents "$(cat settings.jsonc)" --argjson id 7 \
'{query: "mutation($subject: ID!, $id: Int, $contents: String!) { settingsMutation(input: {subject: $subject, lastID: $id}) { overwriteSettings(contents: $contents) { empty { alwaysNil } } } }",
variables: {subject: $subject, id: $id, contents: $contents}}')"
The schema can be introspected, and the site configuration's JSON Schema is at
/.api/site-config-schema. Revoke the token when the work is done.
Encryption keys
Secrets in the database (code host tokens, OAuth credentials, executor secrets, webhook secrets
and URLs, watch webhook URLs) are encrypted with data keys, which are in turn wrapped
by key-encryption keys from the key file. The key file has one id:base64key line per key
(32 random bytes); the last line is the current key:
printf 'k1:%s\n' "$(openssl rand -base64 32)" > keys
sourcegull supervisecreates the file if it is missing.sourcegull runwithout a key file starts, but features that store secrets (code host connections) are unavailable.- Back the file up with the database. Losing it makes the stored secrets unreadable. Every Sourcegull process needs the same keys.
Rotation. Append a new key as the last line, then restart a process or run
sourcegull keys rotate. Changing the file alone does nothing: a rotation happens only when a
process starts or that command runs. The first process with the new key re-wraps every data
key, retired ones included, with it, creates new data keys and retires the old ones
(decrypt-only). It then notifies the other processes, which re-read their key file and reload the
data keys without a restart. Workers then re-encrypt every secret in the background as an
out-of-band migration; progress shows on Site admin → Migrations and in
sourcegull migrate status. When it reaches 100%, remove the old key line; nothing removes it for
you. sourcegull keys rotate with an unchanged file creates new data keys under the same key.
- Because the data keys are re-wrapped, the file never needs more than two lines, the old key and the new one, and only during a rotation.
- Every process must have the new key before or when the rotation runs. A process with only the old key cannot unwrap the re-wrapped data keys: its reload fails with a warning ("reloading the data encryption keys failed") and it keeps the keys it had until it is restarted with the new key.
- Retired data keys stay in the
encryption_keystable (one row per record class per rotation); once re-encryption reaches 100% no secret uses them. sourcegull keys statuslists the data keys and how many secrets still use retired ones.
The Compose walkthrough is in upgrade.md.
Keys in the environment
SOURCEGULL_KEYS holds the same entries as the key file, separated by commas (or newlines), the
last one current:
# One key:
export SOURCEGULL_KEYS="k1:$(openssl rand -base64 32)"
# After a rotation (keep k1 until re-encryption reaches 100%):
export SOURCEGULL_KEYS="k1:<old base64>,k2:$(openssl rand -base64 32)"
- Set either
SOURCEGULL_KEYSorSOURCEGULL_KEYS_FILE, not both (that is a startup error).sourcegull supervisedoes not create a key file whenSOURCEGULL_KEYSis set. - Each process reads the variable once at startup and removes it from its environment before it
starts any thread, so the git processes and other children it runs never inherit it. The
process's initial environment (
/proc/<pid>/environ) still holds it, readable by the same user and root, as for any variable. Secret injectors that resolve the value before the process starts (a mutating webhook that wraps the container's command, an init process that execs Sourcegull with the resolved environment) work unchanged. A value still starting withvault:is reported as an unresolved reference rather than as a malformed key. - The variable is fixed for the life of a process, so a rotation takes two restarts of every
process: one with
old,new(the first to start rotates), and one with onlynewafter re-encryption reaches 100%. A key file is re-read on rotation without a restart.sourcegull keys statusandsourcegull keys rotateaccept either source. - Anyone who can read a process's or container's environment (
docker inspect, a pod spec with a literal value) can read the keys. In Kubernetes, reference a Secret (valueFrom) or use an injector, never a literal value; the Helm chart'sencryptionKeys.source=envdoes the former.
Database connections
Each process has a main pool (SOURCEGULL_DB_MAX_CONNECTIONS, default 20) for the web, repo
and blob roles. When the worker role runs in the same process (--roles=all,
supervise), it gets its own pool (SOURCEGULL_DB_WORKER_MAX_CONNECTIONS, default 10) so
background jobs cannot starve requests; a process that runs only worker uses the main pool.
Outside these pools, each process holds one LISTEN connection and one connection per leader
lease it holds (with the Postgres leader backend).
Keep the sum over every process below PostgreSQL's max_connections. Connections show up in
pg_stat_activity with application_name sourcegull:<roles>.
Replicated databases
For a cluster with a primary and replicas (a managed database's host list, Patroni, repmgr), list every host in the URL, as libpq does:
postgres://sourcegull:[email protected]:5432,db2.example.com:5432,db3.example.com/sourcegull?sslmode=verify-full
The user, password, database and parameters are shared by all hosts; a host without a port
uses 5432. With several hosts, Sourcegull connects only to the writable primary (as
target_session_attrs=read-write does in libpq, and that parameter may be given; primary means the
same, other values are rejected). Every process:
- at startup probes all hosts at once and uses the first writable one in URL order;
- checks the primary every second over one more connection (
application_namesourcegull:primary-check); - when that check fails, probes all hosts again and moves to the new primary as soon as one
accepts writes. Requests that need a connection meanwhile wait for it (up to 10 seconds)
instead of failing; connections to the old primary are closed,
and the
LISTENand leader-lease connections reconnect to the new one; - checks that every new connection is writable, so a demoted primary that is still up is never used.
A switchover usually takes one to two seconds. Queries running on the old primary when it fails still fail. Replicas are not used for reads.
A single host name that follows the primary (a cloud provider's read-write endpoint, a
CloudNativePG -rw Service, a VIP) also works, but is only as fast as its DNS or routing
update. Sourcegull resolves the name for each new connection and keeps no DNS cache of its
own. Add ?target_session_attrs=read-write to such a URL, so connections that reach a replica
before the update are rejected and retried.
Database migrations
sourcegull migrate manages the schema: up, down --to <n>, status, validate, versions,
drift [--version <v>] (compares the live schema with a version's and prints SQL that fixes
each difference) and upgrade --from <version> [--to <version>] [--dry-run] (upgrades across
several versions after a drift check; stop Sourcegull first). Out-of-band data migrations run in the
background afterwards; Site admin → Updates lists the ones that must finish before the next
upgrade.
Configuration as code
Three files can replace what is otherwise edited in the UI, so a deployment is described by files kept in git and rolled out with Compose or Helm:
| File | Variable | Holds | Applied by |
|---|---|---|---|
| Site configuration | SOURCEGULL_SITE_CONFIG_FILE |
The site configuration document | every process, re-read every 30 s |
| Global settings | SOURCEGULL_GLOBAL_SETTINGS_FILE |
The global settings document | the leader worker, every 30 s |
| Resources | SOURCEGULL_RESOURCES_FILE |
Code host connections, search contexts, roles, site admins, outbound webhooks, executor secrets, code intelligence policies, feature flags | the leader worker, every 30 s |
What a file sets is read-only in the UI and API (mutations fail with READ_ONLY, and pages
show who manages the object). What the files do not cover stays in the database: users and
their settings, organizations, access tokens and OAuth grants, saved searches, notebooks, code
monitors, insights, bulk changes and code intelligence data, so the database still needs
backups.
sourcegull config check validates the files offline, for CI pipelines: the format, every entry
with the checks the UI applies, and secret references (resolved from the environment the
command runs in; missing ones are named, values are never printed). It exits with 1 on a
problem.
sourcegull config check --site-config site-config.jsonc --resources resources.jsonc \
--global-settings global-settings.jsonc
Secret references
Any string in the site configuration and resources files can reference secrets, so the files can be committed:
${env:NAME}: the environment variableNAME(unset is an error, never an empty string);${file:/path}: the file's contents without trailing newlines, e.g. a mounted Kubernetes or Docker secret;$${is a literal${; any other${…}is left as it is.
References are resolved every time the file is read, so a changed secret file (or, after a restart, a changed variable) is applied like a changed config file. A reference that cannot be resolved keeps the previous state and names the field and the variable or file, never a value. The global settings file does not resolve references: every user can read the global settings.
Resources file
A JSONC object with optional sections; its schema is served at /.api/resources-schema (point
"$schema" at it for editor completion). A section that is present owns the objects it lists;
an absent section manages nothing.
{
// Verified email addresses of site admins.
"siteAdmins": ["[email protected]"],
"connections": [{
"key": "gitlab-main", // stable id: renaming it recreates the connection
"kind": "gitlab", // gitlab | github | gitea
"displayName": "GitLab",
"config": { "url": "https://gitlab.example.com", "token": "${env:GITLAB_TOKEN}",
"projectQuery": ["groups/acme/projects"] }
}],
"searchContexts": [
{ "name": "backend", "description": "Backend services", "query": "repo:^gitlab\\.example\\.com/acme/" },
{ "name": "api", "repositories": [{ "repository": "gitlab.example.com/acme/api", "revisions": ["main"] }] }
],
"roles": [{ "name": "bulk-editors", "permissions": ["BATCH_CHANGES#WRITE"], "users": ["[email protected]"] }],
"outboundWebhooks": [{ "key": "audit", "url": "https://hooks.example.com/sourcegull",
"secret": "${env:HOOK_SECRET}", "eventTypes": ["batch_change:apply"] }],
"executorSecrets": [{ "scope": "batches", "name": "GITLAB_TOKEN", "value": "${env:GITLAB_TOKEN}" }],
"codeIntelPolicies": [{ "key": "default-branch", "name": "Default branches", "type": "GIT_TREE",
"pattern": "HEAD", "indexingEnabled": true }],
"featureFlags": [{ "name": "my-flag", "value": true }, { "name": "beta", "rolloutBasisPoints": 2500 }]
}
| Section | Entries | Identity (takeover) |
|---|---|---|
connections |
key, kind, displayName, config (the connection configuration, as in the UI's editor) |
same kind and code host URL |
searchContexts |
name, description, public (default true), query or repositories (repository, revisions; the default branch when empty) |
same name, instance-level |
roles |
name, permissions (NAMESPACE#ACTION), users (verified emails) |
same name |
siteAdmins |
verified email addresses | — |
outboundWebhooks |
key, url, secret, eventTypes (names, or { "eventType", "scope" }) |
same URL |
executorSecrets |
scope (batches or codeintel), name (the variable jobs see), value |
same scope and name, global |
codeIntelPolicies |
key, name, type (GIT_TREE, GIT_TAG, GIT_COMMIT), pattern, repositoryPatterns, retentionEnabled, retentionDurationHours, retainIntermediateCommits, indexingEnabled, indexCommitMaxAgeHours, indexIntermediateCommits, syntacticIndexingEnabled |
same name, not tied to one repository |
featureFlags |
name, value or rolloutBasisPoints |
same name |
Every entry has a key: key where given, else its name (<scope>/<name> for executor
secrets). On each pass, per section:
- an unchanged entry (same resolved contents) does nothing; a changed one, including a rotated secret it references, updates its object in place;
- a new key first looks for an object with its identity that no entry owns (see the table) and takes it over, so moving a running instance to a file recreates nothing; otherwise the object is created;
- a key removed from its section deletes its object (a connection's repositories go with it unless another connection lists them);
- a removed section, or an unset
SOURCEGULL_RESOURCES_FILE, releases its objects to the UI unchanged; - changes the system makes to a declared object, such as a refreshed OAuth token in a connection's configuration, are kept until the entry itself changes.
With "exclusive": true, each present section also owns the objects of its kind that the file
does not list: they become read-only and nothing of that kind can be created in the UI.
Site admins and role members are matched against verified email addresses. Every source
that marks an address verified counts: an identity provider that vouches for it at sign-in,
SCIM provisioning, the email header of http-header sign-in, and a user confirming it by email.
Only declare addresses whose verification you trust there.
A declared admin needs a way to get a verified email without an existing admin: a GitLab,
GitHub, Gitea or OpenID Connect provider, http-header with emailHeader, SCIM
(scim.authToken), or open password sign-up (allowSignup on the built-in provider) with
email.smtp, so the admin signs up and confirms the address by email. With none of these, no
admin could ever sign in, so the file is refused with an error on siteAdmins; sourcegull config check reports it when given the site config too.
A listed user becomes site admin, or gets the role, at sign-in or within 30 s, and gets it back
if it is taken away in the UI; removing the address takes back only what the file granted.
While siteAdmins is set, the first user to sign up is not made site admin, and a declared
admin's first sign-in with a provider that verifies their email creates their account even when
that provider's sign-up is closed, so a fresh SSO-only instance needs no manual step. Declared admins cannot be demoted in the UI, and site admins cannot verify or
unverify a declared address for its user; with exclusive, no one can be promoted or demoted
there, and admins the file does not list are demoted once one declared admin is site admin.
Errors. A file that cannot be read, parsed or resolved, or fails a check, changes nothing:
the last applied state stays. An entry the database rejects (for example a name another object
uses) fails alone while the others apply. Either way the error shows in the site admin status
indicator and on Site admin → Site configuration, and sourcegull_declared_config_errors{source}
is 1. Every change is a DeclaredResourceChanged or DeclaredGrantChanged security event, and
sourcegull_declared_config_changes_total{kind,action} counts them.
Global settings file
SOURCEGULL_GLOBAL_SETTINGS_FILE holds the global settings document (JSONC, as in Site admin →
Global settings). When its contents differ from the current global settings, the leader
worker saves them as a new version; the page is read-only meanwhile. Unsetting the variable
makes the global settings editable again, starting from the last version.
Deploying the files
- Helm:
config.siteConfig,config.globalSettingsandconfig.resourcesin values are rendered into a Secret mounted on every Sourcegull pod (or useconfig.existingSecret); the variables the files reference go inextraEnvVars. See the chart README. - Docker Compose: put the files in
config/of the bundle (deploy/docker/config/in a checkout; mounted at/etc/sourcegull/config), name them in.env, and put referenced variables inconfig/secrets.env.
Backups
With configuration as code, the database holds only what users create; restoring an instance from git and an empty database loses users' tokens, settings and content, not the instance's configuration. Back up the database and the encryption keys together anyway (a database backup needs the keys that were current when it was taken). The blob store holds SCIP uploads, which CI or auto-indexing can upload again.