Repository navigation
Expand file tree
/
Copy pathforge.toml.example
More file actions
326 lines (303 loc) · 19.4 KB
/
Copy pathforge.toml.example
File metadata and controls
326 lines (303 loc) · 19.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# Forge configuration (TOML form).
#
# Copy this to `forge.toml` (loaded automatically if present in the working
# directory) or point $FORGE_CONFIG at it (the file must then exist: a missing
# $FORGE_CONFIG file fails startup). This file is the base layer; any
# `FORGE_*` environment variable overrides the matching key (see .env.example).
# Nested keys map section-by-section: `[auth].bootstrap_admin.username` is set
# via `FORGE_AUTH__BOOTSTRAP_ADMIN__USERNAME`.
#
# Keys marked "(default: …)" may be omitted entirely.
[server]
bind = "0.0.0.0:8421"
# Public base URL for absolute NuGet V3 service-index @id values. No trailing slash.
public_base_url = "http://localhost:8421"
# The placeholder credentials in this example (minioadmin S3 keys, bootstrap
# admin@example.com/changeme) are refused at startup unless this is true. Keep it for a
# local dev copy only; in production replace the placeholders and drop it.
allow_insecure_defaults = true # (default: false)
# Upload limits are set per hosted repository in the web UI (Repositories →
# Edit → "Maximum upload size") or the admin API (max_upload_bytes); without
# one a repository accepts pushes of 512 MiB (docker: blobs of 1 GiB). Uploads
# stream to a temp file (never buffered in memory); larger pushes get HTTP 413.
# (server.max_upload_bytes is gone: startup refuses a config that sets it.)
# Pushes and first-time proxy downloads are staged here; files a killed process
# left behind are removed at startup. All staged files of this instance share
# max_staging_bytes; beyond it pushes and downloads get 503 + Retry-After.
# staging_dir = "/var/tmp/forge" # (default: <system temp dir>/forge-staging)
# max_staging_bytes = 4294967296 # (default: 4 x the largest of the default upload limits, proxy.max_package_bytes, docker.max_blob_bytes; 0 = no limit)
# It also caps the upload limit a repository may be given. Set below 1 GiB, it
# lowers the docker defaults (upload limit, docker.max_blob_bytes) to fit.
# Host names (port ignored) clients reach this server under besides the host of
# public_base_url, e.g. a name internal clients use. Only gates the
# trusted-header realms (Admin → Authentication): a request naming any other
# host is not named by the proxy's headers (DNS-rebinding guard). The guard
# needs the client's own Host: the reverse proxy must forward it (nginx:
# proxy_set_header Host $host). Never add a name the proxy rewrites Host to
# ("forge" for proxy_pass http://forge:8421): every request then names an
# allowed host, and the guard is off.
allowed_hosts = [] # (default: [])
# Connection and request time limits. A client that takes longer to send its
# request headers, or whose request body stalls longer, is cut off; a handler
# that has not answered within request_timeout_seconds gets 408 (an already
# streaming download is not cut off; a proxy's download from its upstream may
# take [proxy] download_timeout_seconds, and the handler has
# request_timeout_seconds again after it). Keep request_timeout_seconds above the
# slowest push of the largest repository upload limit you expect. header_read_timeout_seconds also
# closes an idle keep-alive connection: keep it above your reverse proxy's
# upstream keep-alive idle timeout, or the proxy may reuse a connection Forge is
# closing and answer 502. 0 disables a limit.
header_read_timeout_seconds = 150 # (default: 150)
body_idle_timeout_seconds = 60 # (default: 60)
request_timeout_seconds = 900 # (default: 900)
[database]
url = "postgres://forge:forge@localhost:5432/forge"
max_connections = 20 # (default: 20) connection-pool size
acquire_timeout_seconds = 10 # (default: 10) wait for a free connection before failing
[s3]
endpoint = "http://localhost:8422"
region = "us-east-1" # (default: "us-east-1")
bucket = "forge"
# Where the S3 credentials come from (default: "static"):
# static access_key / secret_key below.
# default_chain the AWS SDK default credentials chain: AWS_* env vars (with
# AWS_SESSION_TOKEN), the shared profile, web identity (IRSA:
# AWS_ROLE_ARN + AWS_WEB_IDENTITY_TOKEN_FILE), EKS Pod
# Identity / ECS task role, EC2 instance profile.
# web_identity exchange token_file for temporary keys with STS
# AssumeRoleWithWebIdentity ([s3.web_identity] below), against
# AWS STS or MinIO / Ceph RGW.
# Temporary credentials are refreshed before they expire. The short-lived
# modes refuse access_key / secret_key (remove them).
credentials = "static"
access_key = "minioadmin" # static only
secret_key = "minioadmin" # static only
force_path_style = true # (default: true) required for versitygw/MinIO
multipart_threshold = 8388608 # (default: 8 MiB) switch to multipart above this many bytes
# Dedup PUTs with `If-None-Match: *` instead of HEAD-then-PUT: auto | on | off.
# "auto" tries the conditional PUT and falls back to HEAD-then-PUT for the
# process lifetime if the backend rejects it (versitygw); "on" forces it (AWS
# S3/MinIO); "off" disables it. Pure optimization — behavior is identical.
conditional_writes = "auto" # (default: "auto")
# credentials = "web_identity" only.
# [s3.web_identity]
# The role to assume (AWS requires one; none for MinIO's claim-based policies).
# role_arn = "arn:aws:iam::123456789012:role/forge"
# The token (a projected service-account token, any OIDC token), read again on
# every refresh so a rotated token is picked up.
# token_file = "/var/run/secrets/forge/s3/token"
# Unset: AWS STS in s3.region. MinIO / Ceph RGW: their URL (usually endpoint).
# sts_endpoint = "https://minio.example.com:9000"
# session_name = "forge" # (default: "forge") RoleSessionName
# duration_seconds = 3600 # 900..43200 (default: the STS default)
[crypto]
# Secret encryption at rest, REQUIRED: proxy passwords, realm secrets, the
# email credentials, the registry-token key and the OIDC issuer keys are
# sealed under a DEK, wrapped by a KEK, wrapped by a root key held outside the
# database. Startup refuses a configuration without a usable root key. Keep
# the root key with your backups: without it the stored secrets cannot be
# read. See docs/deployment.md, "Secret encryption at rest".
algorithm = "aes-256-gcm" # (default) | "xchacha20-poly1305"
[crypto.root]
# Exactly one provider: "env" or "file".
provider = "env"
# env: the NAME of the variable holding the base64 32-byte root key
# (generate it once with `openssl rand -base64 32`).
root_key_env = "FORGE_ROOT_KEY" # (default)
# Root rotation on a running cluster (docs/deployment.md): the previous key,
# reachable until every KEK is re-wrapped under the current one.
# root_key_env_previous = "FORGE_ROOT_KEY_PREVIOUS"
# file: the path of a file holding the key (a mounted Kubernetes secret).
# provider = "file"
# root_key_path = "/run/secrets/forge_root_key"
# root_key_path_previous = "/run/secrets/forge_root_key_previous"
[crypto.cache]
# The unwrapped keys in memory (zeroized on eviction).
unwrapped_key_ttl_secs = 300 # (default: 300)
unwrapped_key_max = 64 # (default: 64)
[crypto.rotation]
# The rotation worker: age-based rotation, the re-encrypt sweep and the
# KEK re-wrap after a root rotation. One instance runs a pass at a time; the
# admin POST /admin/crypto/rotate/* endpoints take the same lock.
enabled = true # (default: true)
poll_interval_seconds = 300 # (default: 300)
dek_max_age_days = 30 # (default: 30; 0 disables)
kek_max_age_days = 365 # (default: 365; 0 disables)
sweep_batch_size = 500 # (default: 500) rows per re-encrypt batch
[auth]
# Anonymous access, the login screen, passwords over HTTP Basic, the session
# lifetimes and the login lockout are not configured here: they are auth
# settings, set in the web UI (Admin → Settings) or through
# /admin/settings/auth, and every instance applies them without a restart.
# Startup refuses the keys that used to set them (anonymous_mode,
# anonymous_ip_allowlist, login_mode, allow_password_basic,
# session_idle_ttl_seconds, session_absolute_ttl_seconds, login_max_failures,
# login_failure_window_seconds). See docs/deployment.md, "Access and sign-in
# settings".
# CIDRs of reverse proxies whose X-Forwarded-For is trusted (the client address
# behind them keys the login lockout), and whose headers a trusted-header realm
# reads (from any other peer they are ignored).
trusted_proxy_cidrs = [] # (default: [])
# Most argon2 password verifications running at once (~19 MiB and a core each);
# more queue for a few seconds, then fail as unavailable (not counted as a
# failed password). 0 = the number of available CPUs.
password_verify_concurrency = 0 # (default: 0)
# How long an instance trusts its cached copy of the auth_realm table and of
# the auth settings: other instances follow an admin's change within it.
realm_cache_ttl_seconds = 10 # (default: 10)
# Concurrent upstream credential forwards (verdict-cache misses) allowed per
# credential-forwarding realm, per instance; excess logins fail closed (401).
credential_forward_max_concurrent = 32 # (default: 32)
# Optional: bootstrap admin, created once on the first start with a password.
# Omit the section, or leave the password empty/unset, for no bootstrap admin
# (remove the password once real admins exist). "changeme" is refused unless
# server.allow_insecure_defaults = true. The username is a UPN
# (local-part@domain), as every account is named; default admin@example.com.
[auth.bootstrap_admin]
username = "admin@example.com"
password = "changeme"
[redis]
# Optional shared L2 cache + invalidation backplane. Omit (or leave the url
# blank) for L1-only caching.
# url = "redis://localhost:6379"
# A slower/partitioned Redis is treated as a cache miss instead of hanging reads.
# connect_timeout_ms = 2000 # (default: 2000)
# response_timeout_ms = 2000 # (default: 2000)
[proxy]
# Outbound HTTP to proxy upstreams (nuget.org, Azure Artifacts, GitHub
# Packages, Artifactory, Nexus, ProGet, …). Upstream credentials configured on
# a proxy repository are sent with every request to the remote_url host
# (service index, version listing, .nuspec and .nupkg).
connect_timeout_seconds = 15 # (default: 15)
read_timeout_seconds = 60 # (default: 60) max silence between two reads
metadata_timeout_seconds = 60 # (default: 60) total, per metadata request
download_timeout_seconds = 1800 # (default: 1800) total, per .nupkg or layer download
# The largest metadata document. One upstream operation (a version listing,
# dating the package delay) keeps at most 4 x this of the documents it reads.
max_metadata_bytes = 33554432 # (default: 32 MiB)
max_package_bytes = 1073741824 # (default: 1 GiB)
# Metadata bodies up to this size stay in memory; larger ones spill to a
# temp file that is removed when the request ends. Packages always stream
# to a temp file.
spool_memory_bytes = 2097152 # (default: 2 MiB)
# The metadata documents all upstream reads of this instance hold at once
# while their formats parse them (which takes up to as much again). A read
# that would go over waits for others, up to metadata_timeout_seconds, then
# answers 503 with Retry-After, instead of growing the process past its
# memory limit. Size the limit for twice this (docs/deployment.md, Memory).
max_metadata_memory_bytes = 268435456 # (default: 256 MiB)
[nuget]
# .nupkg archive safety limits for published, proxied and imported packages.
# Only the .nuspec manifest is ever decompressed, so it alone is capped by size
# and compression ratio; other entries (large native binaries included) are
# only bounded by the archive-shape limits below. The zip directory is read
# one entry at a time, so the entry count costs time, not memory.
max_entries = 1000000 # (default: 1000000)
max_central_directory_bytes = 268435456 # (default: 256 MiB) zip directory read to check every entry
max_total_uncompressed_bytes = 17179869184 # (default: 16 GiB) total declared size of all entries
max_nuspec_bytes = 1048576 # (default: 1 MiB) also caps an upstream .nuspec a proxy verifies
max_nuspec_compression_ratio = 200 # (default: 200)
[npm]
# What the npm registries (/npm, see docs/npm.md) read of a request. A
# tarball itself is bounded by its repository's upload limit.
max_document_bytes = 33554432 # (default: 32 MiB) a publish document without its tarball, an unpublish, an audit
max_unpacked_bytes = 1073741824 # (default: 1 GiB) the most a published or proxied tarball may unpack to
[cli_login]
# Logins of command-line clients (`npm login`, `yarn npm login`), with a
# password or approved in the browser.
token_lifetime_days = 90 # (default: 90; 0: never expires) the API token a login mints
web_login_ttl_seconds = 600 # (default: 600; 60-3600) how long a login approved in the browser waits
[docker]
# The Docker / OCI registry under /v2 (see docs/docker.md).
# max_blob_bytes = 1073741824 # (default: 1 GiB, or what an explicit server.max_staging_bytes holds) largest layer a proxy downloads; staged on disk (pushes follow the repository's upload limit)
token_ttl_seconds = 300 # (default: 300) registry token lifetime, 60-3600
# Signs registry tokens (at least 32 characters). Unset: a key generated once
# and kept in the database, sealed by the secret encryption.
# token_secret = "…"
upload_ttl_seconds = 86400 # (default: 24 h) abandoned upload sessions expire
unreferenced_blob_ttl_seconds = 86400 # (default: 24 h, at least 1 h) unused blobs no manifest references
sweep_interval_seconds = 300 # (default: 300)
[cache]
l1_max_capacity = 10000 # (default: 10000) entries; one over 16 KiB counts once per 16 KiB
default_ttl_seconds = 30 # (default: 30)
[blob_cache]
# Optional local disk cache for package blobs: hot downloads skip S3, and
# cached packages keep resolving while S3 is unreachable (pushes and uncached
# downloads then answer 503 with Retry-After, and /readyz reports the object
# store "degraded" instead of failing). S3 stays the source of truth. Unset
# path = no cache. Each instance needs a directory of its own.
# Forge creates the directory (mode 0700) and refuses one another user owns,
# such as a mounted volume's root: name a subdirectory of the volume.
# path = "/var/cache/forge/blobs"
# "auto": the size of the filesystem holding `path` minus min_free_bytes (and
# server.max_staging_bytes when staging is on the same filesystem), re-read
# every minute; or a fixed number of bytes. On a Kubernetes emptyDir, statvfs
# sees the node's disk, not the volume's sizeLimit: set a number below it.
# max_bytes = "auto" # (default: "auto")
# Free space the cache leaves on its filesystem, evicting instead of filling it.
# min_free_bytes = 1073741824 # (default: 5 % of the filesystem)
[outbox]
batch_size = 100 # (default: 100)
grace_seconds = 3600 # (default: 3600) grace before a dereferenced blob is deleted
poll_interval_seconds = 10 # (default: 10)
processed_retention_seconds = 604800 # (default: 604800 = 7 days) keep processed outbox rows this long; 0 = never prune
[retention]
# Retention is set in the web UI, for every instance: each repository's policy
# (Repositories → <name> → Retention), and whether runs happen and how long
# the audit log is kept (Admin → Retention). These keys only tune the worker.
# poll_interval_seconds = 30 # (default: 30) how often this instance looks for a due repository
# lease_seconds = 300 # (default: 300) lease a run holds, renewed while it works
[telemetry]
# OpenTelemetry metrics, traces and logs over OTLP/HTTP (docs/deployment.md →
# Telemetry), each off unless turned on. Where they go is set by the standard
# OTEL_* variables: OTEL_EXPORTER_OTLP_ENDPOINT (default
# http://localhost:4318, a Collector beside Forge), OTEL_EXPORTER_OTLP_HEADERS,
# OTEL_RESOURCE_ATTRIBUTES, OTEL_TRACES_SAMPLER, …
# metrics = true # (default: false)
# traces = true # (default: false)
# logs = true # (default: false) what RUST_LOG prints, with its trace
# The repository's name on the metrics (forge.repository). Off, the
# repositories add up: for an install with very many.
# per_repository = true # (default: true)
# How often an instance counts its repositories, their versions and stored
# bytes, and the outbox backlog for the metrics (database queries).
# inventory_interval_seconds = 300 # (default: 300, at least 10)
[traffic]
# The traffic statistics (Admin → Traffic; docs/deployment.md → Traffic
# statistics). What they record and the MaxMind account their country and
# ASN databases come from are set in the web UI. Where Forge downloads those
# databases: MaxMind, or a mirror answering its download API.
# maxmind_url = "https://download.maxmind.com" # (default)
# Optional: OpenID Connect / Microsoft Entra interactive login.
# This block is a one-shot SEED — on first boot it is imported into an
# `auth_realm` row and authentication is managed in the admin UI from then on
# (the block is ignored afterwards; the same applies to [auth.upstream_basic]).
# All flow state and sessions live in Postgres, so any instance can complete a
# callback another instance started. The client_secret is redacted from logs
# and admin API output.
# [oidc]
# enabled = true # (default: true)
# issuer = "https://login.microsoftonline.com/<tenant-id>/v2.0" # must be https
# client_id = "00000000-0000-0000-0000-000000000000"
# client_secret = "<application-client-secret>" # redacted from APIs/logs
# # or no secret: Forge signs a client assertion as an OIDC issuer of its own
# # (public_base_url, publishing /.well-known/openid-configuration + jwks.json);
# # register it as an Entra federated credential (see DEVELOPMENT.md)
# client_authentication = "federated_credential" # (default: "client_secret")
# assertion_audience = "api://AzureADTokenExchange" # (default) the assertion's aud
# redirect_url = "http://localhost:8421/auth/oidc/callback" # must match public_base_url host + this path
# scopes = ["openid", "profile", "email"] # (default) openid is always added
# clock_skew_seconds = 60 # (default: 60) exp/nbf/iat leeway
# # claim → user mapping (fallback chain: username_claim → email → sub)
# username_claim = "preferred_username" # (default)
# email_claim = "email" # (default)
# display_name_claim = "name" # (default)
# # provisioning; whether a first login creates the account is set for each
# # realm in the admin UI (Admin → Authentication), not here
# link_existing_by_username = false # (default) opt-in; never links password accounts or admins
# # admin assignment — empty admin_groups means OIDC never grants admin (safe default)
# admin_groups = ["forge-admins"] # group/role values that grant is_admin
# admin_claim = "roles" # (default) claim matched against admin_groups ("roles" or "groups")
# # IdPs that serve tokens and keys on other hosts than the issuer's (Google):
# endpoint_hosts = ["oauth2.googleapis.com", "www.googleapis.com"] # (default: [] = issuer host only)
# # The session lifetimes are auth settings (Admin → Settings), not set here.