Viking Forge hosts free Viking CMS plugins and themes. Visitors can browse and download without signing in. Members manage their own work; Forge administrators can hide items or suspend accounts. The interface uses Viking CMS's colours and favicon. Return to the operations index for other services.
Browse and publish
Open /plugins or /themes to browse the separate catalogues. An item's public
page shows its Markdown documentation, version, license, time since publication,
download count and ZIP download link. /users lists makers with visible published
work. Each maker's page shows their Markdown biography, published items and
download totals. Accounts with no visible published items have no public page.
Sign in and open Your workshop to edit your public name and Markdown biography.
Create a plugin or theme draft, enter its name/version/license and Markdown
description, then upload a Viking ZIP. The archive must contain plugin.json or
theme.json at its root, with schemaVersion: 1, a package ID, name and semantic
version. Plugin and theme archives are stored and listed separately. An uploaded
package ID stays attached to its item; another member cannot register that ID for
the same package type.
Save the draft and choose Publish saved draft. Publication requires an archive whose manifest version matches the draft. Saving documentation or replacing an archive changes the private draft; publishing creates the public snapshot. Publishing again replaces the snapshot and starts its displayed publication age again. Unpublish removes the item from listings, details and download access while retaining the draft and historical download count. Direct download URLs also check current visibility. Transfers already started may finish.
Documentation and biography fields accept Markdown source. Raw HTML and unsafe
link schemes are omitted from rendered Markdown. To install a download, use
Viking CMS's local ZIP installation workflow in Plugins and packages or Themes.
The CMS's Forge browser queries these public catalogues and installs free ZIPs
without a Forge account. VIKING_FORGE_URL or --forge-url on the CMS overrides
its saved Forge site setting; the default address is https://forge.vikingcms.org.
Identity and access
Register a separate OAuth2/OIDC client with your provider and configure its
callback to https://forge.example.com/oauth2/callback. Allow your Forge origin
as a post-logout destination when the provider offers end-session logout. Map
these exact roles into the configured roles claim:
| Role | Access |
|---|---|
forge_member |
Own profile, private items, ZIP uploads, documentation/metadata changes and publication. |
forge_admin |
Member capabilities plus management of all account suspensions and item visibility. |
Roles are assigned by the identity provider. Forge management does not grant identity-provider roles. Administrators cannot rewrite another member's drafts. Suspension blocks member/admin operations and hides that account's published items. Hiding an item blocks public access and prevents its owner from publishing around moderation. Restoring an account/item restores its existing publication. An administrator cannot suspend their own account.
Mount auth configuration as individual Secret files at /etc/viking/auth. Use
the CMS authentication guide
for common key formats, with a separate Forge client and random session key:
| File | Purpose |
|---|---|
clientID, clientSecret |
Forge OAuth client credentials; omit the secret only with publicClient=true. |
jwtSecret |
At least 32 bytes of random key material, raw or base64; use a distinct key for Forge. |
discoveryURL |
Provider's HTTPS OIDC issuer/discovery base URL. |
forgeHostname |
External Forge hostname, required unless supplied by flag/environment. |
rolesClaim |
Roles claim name, default roles. |
scopes |
Space/comma separated scopes, default openid email profile; add provider scopes needed for roles. |
sessionTTL |
Browser session lifetime, default 1h. |
cookieName |
Default viking_forge_session; keep separate from CMS. |
emailClaim, nameClaim |
Profile claims, default email, name. |
audience |
Expected Bearer-token audience, defaults to the client ID. |
introspectionURL |
Optional HTTPS RFC 7662 endpoint for opaque tokens. |
authURL, tokenURL, jwksURL, issuer |
Explicit endpoint alternative to discovery; configure all four and omit discoveryURL. |
userinfoURL, cookieDomain |
Optional profile endpoint and session-cookie domain. Prefer host-only cookies. |
Forge always requires one of its two roles regardless of an allowedRoles file.
Browser writes require same-origin requests and a session-bound CSRF token.
Validated Bearer requests use the same RBAC/ownership rules and do not need CSRF.
Browser sessions and in-progress logins live in memory; restarting requires users
to sign in again. Protect the Forge origin with TLS and keep Secure cookies on.
Publish repository examples from CI
The repository's optional forge-examples-publish job uploads the example-package
artifacts and publishes them using FORGE_USERNAME and FORGE_PASSWORD. It
reuses the documentation job's Ipseity login flow with the Forge client and a
member account. An existing owned package is updated in place, including uploads
with the same version, preserving its item ID and download count. Forge remains
limited to one current public version per item. The job is allowed to fail if
Forge is unavailable or credentials are missing. See the
CI configuration guide
for provider/client settings, pipeline scope and failure behavior.
Runtime and deployment
Build forge/Dockerfile with its own forge context. VERSION stamps the binary's
version; local unstamped builds report dev. Every handler response on both
listeners includes Server: Viking Forge <version>, including OAuth, errors and
metrics. Ingress must preserve that upstream header.
| Flag | Environment | Default |
|---|---|---|
--listen |
VIKING_FORGE_LISTEN |
:8080 |
--admin-listen |
VIKING_FORGE_ADMIN_LISTEN |
127.0.0.1:8081 |
--data |
VIKING_FORGE_DATA |
/var/lib/viking-forge |
--auth-secret-dir |
VIKING_FORGE_AUTH_SECRET_DIR |
/etc/viking/auth |
--forge-hostname |
VIKING_FORGE_HOSTNAME |
Secret's forgeHostname |
--cookie-secure |
VIKING_FORGE_COOKIE_SECURE |
true |
--allow-insecure-auth-http |
VIKING_FORGE_ALLOW_INSECURE_AUTH_HTTP |
false |
The insecure-auth switch permits only loopback HTTP provider endpoints for local development. It does not permit general HTTP identity providers. Provider discovery failures retry in the background. Missing/invalid auth configuration requires correction and restart; private operations fail closed. The public handler remains usable, but the internal readiness probe fails while auth is unavailable; a Kubernetes Service using that probe removes the pod from routing.
Use the example deployment as a starting
point. Its separate namespace, workload, service and ingress manifests live in
deploy/examples/forge/. Replace the image/hostname, create the
viking-forge-auth Secret from your auth files, and choose storage/ingress
settings. Optional Ipseity client/role/membership templates are included.
This standalone deployment is independent of Website resources and
the controller's automatic CMS identity setup. It is not installed by the core
operator manifests. The runtime uses UID/GID 65532; its data volume must be writable
by that account. The example uses the dedicated viking-forge namespace,
one replica, Recreate
updates and a RWO PVC. The separate live Orbit source is orbit-deployments/forge,
registered by its parent default/04-forge.yaml; it uses the cluster
Ipseity issuer, a generated auth Secret, three-replica RWX storage and
forge.vikingcms.org TLS ingress.
Keep the admin listener internal. In Kubernetes set --admin-listen=:8081 for
pod probes/scrapes, and restrict network access to probes and monitoring clients.
It exposes unauthenticated /healthz, /readyz and /metrics; these paths do not
exist on the public listener. The server uses 5-second header, 2-minute read/write
and 60-second idle timeouts; shutdown allows a 15-second drain for each listener.
Configure matching ingress upload limits/timeouts for 64 MiB ZIP uploads.
Storage, metrics and backup
Run one Forge process per data directory. A filesystem advisory lock rejects
a second writer; use storage with working local/POSIX locks, atomic rename and
fsync. catalogue.json contains profiles, drafts, publication snapshots and
download counts. blobs/plugins/ and blobs/themes/ separately contain immutable
SHA-256-addressed archives. Never
serve this directory as a public document root: drafts must stay private.
Back up the entire data directory and auth Secret. Stop Forge or use a consistent volume snapshot, then restore into a separate stopped instance with the same file ownership. The complete catalogue and blobs must belong to the same backup. After restore check readiness, a public download, and a member draft before cutting over. No automatic backup scheduler or archive garbage collection is included yet; preserve unreferenced blobs until maintenance tooling is available.
Prometheus exposes server request counts, duration/response-size histograms and in-flight requests, plus account request/download/item metrics and item view/download/publication metrics. Forge labels use stored opaque IDs, not names, emails, tokens or arbitrary request paths. Per-account/item series grow with the catalogue and are available only on the internal listener.
Download totals count successfully written GET responses, including successful range requests. HEAD, conditional 304, failed ranges and interrupted writes do not increment them. They measure transfers, not unique people or installations. Public maker totals sum their currently visible published items. Internal account totals include all their items, including unpublished/hidden ones. Download counts persist; HTTP request/view counters reset at restart.
Paid items, authenticated download entitlements, Stripe, revenue sharing and per-member retention percentages are deferred in the project backlog.
