Content Management that can withstand the plundering hoards

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.