{"id":48595727,"url":"https://github.com/sphireinc/Foundry","last_synced_at":"2026-04-11T10:01:09.029Z","repository":{"id":345203798,"uuid":"1174135986","full_name":"sphireinc/Foundry","owner":"sphireinc","description":"A Markdown-first CMS written in Go with themes, plugins, and an integrated admin UI","archived":false,"fork":false,"pushed_at":"2026-04-04T23:14:12.000Z","size":16044,"stargazers_count":164,"open_issues_count":0,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-04-05T01:25:31.569Z","etag":null,"topics":["cms","content-management-system","markdown","wordpress"],"latest_commit_sha":null,"homepage":"https://sphireinc.github.io/Foundry/","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sphireinc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":"NOTICE","maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-06T05:26:39.000Z","updated_at":"2026-04-04T23:14:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sphireinc/Foundry","commit_stats":null,"previous_names":["sphireinc/foundry"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/sphireinc/Foundry","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphireinc%2FFoundry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphireinc%2FFoundry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphireinc%2FFoundry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphireinc%2FFoundry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sphireinc","download_url":"https://codeload.github.com/sphireinc/Foundry/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sphireinc%2FFoundry/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31676210,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-11T08:18:19.405Z","status":"ssl_error","status_checked_at":"2026-04-11T08:17:08.892Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cms","content-management-system","markdown","wordpress"],"created_at":"2026-04-08T21:00:37.817Z","updated_at":"2026-04-11T10:01:09.016Z","avatar_url":"https://github.com/sphireinc.png","language":"Go","funding_links":[],"categories":["Go"],"sub_categories":[],"readme":"# Foundry\n\n[![Build Status](https://img.shields.io/github/actions/workflow/status/sphireinc/foundry/build.yml?branch=main)](https://github.com/sphireinc/foundry/actions)\n[![CodeQL](https://github.com/sphireinc/Foundry/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/sphireinc/Foundry/actions/workflows/github-code-scanning/codeql)\n[![Pages](https://github.com/sphireinc/Foundry/actions/workflows/pages/pages-build-deployment/badge.svg)](https://github.com/sphireinc/Foundry/actions/workflows/pages/pages-build-deployment)\n[![Go Report Card](https://goreportcard.com/badge/github.com/sphireinc/foundry)](https://goreportcard.com/report/github.com/sphireinc/foundry)\n![Last Commit](https://img.shields.io/github/last-commit/sphireinc/foundry)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg height=\"300\" src=\"readme-assets/logo.png\" alt=\"logo\"\u003e\n\u003c/p\u003e\n\nFoundry is a Markdown-first CMS written in Go. It keeps content in files, renders through themes, extends through plugins, and supports both static output and local preview serving.\n\nThe project is aimed at teams that want a file-based workflow without giving up CMS-style features such as taxonomies, theme-owned custom fields, feeds, plugin hooks, and an admin surface.\n\nSee more of Foundry here: [Foundry Screenshots](README_SCREENSHOTS.md)\n\n## What Foundry does\n\n- Stores pages and posts as Markdown with frontmatter\n- Supports language-aware routing and content grouping\n- Builds a normalized site graph in memory\n- Uses themes for layouts, partials, and theme assets\n- Uses plugins for hooks, asset injection, and runtime extensions\n- Generates RSS and sitemap output\n- Publishes static output to `public/`\n- Serves the site locally with live reload during development\n- Tracks document, template, data, and taxonomy dependencies for incremental rebuilds\n\n## Quick Start\n\nThe fastest way to get the project running locally is via Docker:\n\n```bash\nsh scripts/docker-init.sh\ndocker compose up -d --build\n```\n\nThe default [docker-compose.yml](/Users/JuanSanchez/WebstormProjects/cms/docker-compose.yml) is local-development oriented:\n\n- it bind-mounts the project source into the container\n- it keeps `data/` and `public/` on named Docker volumes\n- it reads `.env` when present\n- `scripts/docker-init.sh` creates a `.env` file with random local secrets for local use\n\nFor a harder production shape, use [docker-compose.prod.yml](/Users/JuanSanchez/WebstormProjects/cms/docker-compose.prod.yml):\n\n```bash\nexport FOUNDRY_ADMIN_SESSION_SECRET=\"$(openssl rand -hex 32)\"\nexport FOUNDRY_ADMIN_TOTP_SECRET_KEY=\"$(openssl rand -base64 32)\"\ndocker compose -f docker-compose.prod.yml up -d --build\n```\n\nThe production compose setup:\n\n- uses explicit required secrets\n- runs with a read-only root filesystem\n- drops Linux capabilities\n- enables `no-new-privileges`\n- uses a tmpfs for `/tmp`\n- uses named volumes for `content/`, `themes/`, `plugins/`, `data/`, and `public/`\n\nBefore using the production overlay in anything real, update\n`content/config/site.docker.prod.yaml` so `base_url` matches your deployed\nHTTPS origin.\n\nOtherwise, see the [Getting Started](#getting-started) section for how to install the `foundry` command, run Foundry locally, or run it in portable standalone mode without Docker.\n\nFoundry will run on `http://localhost:8080/` by default, and the admin panel\nis reachable at `http://localhost:8080/__admin`. The default login on a \nnew install is `admin:admin`.\n\nProduction note: set `admin.session_secret` in `content/config/site.yaml` or\n`FOUNDRY_ADMIN_SESSION_SECRET` in the environment. Foundry stores only hashed\nsession tokens at rest, and an explicit session secret strengthens that hashing.\n\n## Project layout\n\n```text\ncmd/\n  foundry/         main CLI entrypoint\n  plugin-sync/     generated plugin import synchronizer\n\ninternal/\n  admin/           admin auth, HTTP handlers, service layer, UI templates\n  assets/          asset sync and CSS bundling\n  commands/        CLI command implementations\n  config/          config loading, editing, validation\n  content/         document parsing, loading, site graph assembly\n  data/            data file loading\n  deps/            dependency graph and incremental rebuild planning\n  feed/            RSS and sitemap generation\n  markup/          Markdown rendering\n  plugins/         plugin metadata, loading, lifecycle, sync\n  renderer/        template rendering and output writing\n  router/          URL assignment\n  server/          preview server, watcher, incremental rebuild orchestration\n  site/            higher-level graph loading helpers\n  taxonomy/        taxonomy indexing and archive helpers\n  theme/           theme management and validation\n\nplugins/           built-in plugins\nthemes/            installed themes\ncontent/           project content\ndata/              project data files\ndocs/              GitHub Pages site and published coverage\nscripts/           release and maintenance utilities\n```\n\n## Architecture\n\nFoundry keeps a clean separation between content loading, route assignment, rendering, and runtime orchestration.\n\nThe main pipeline is:\n\n```text\nconfig -\u003e content/data load -\u003e site graph -\u003e route assignment\n      -\u003e dependency graph -\u003e renderer -\u003e build output or preview server\n```\n\nTwo graph types matter:\n\n- `SiteGraph`: the in-memory representation of documents, routes, taxonomies, config, and loaded data\n- `DependencyGraph`: the rebuild graph used to decide which outputs must be regenerated after a change\n\nThe dependency graph includes taxonomy archive outputs, so incremental rebuilds can target both document pages and taxonomy pages.\n\n## Formatting\n\nFoundry uses:\n\n- `go fmt` for Go code\n- `prettier` for JS, CSS, HTML, and Markdown assets/docs\n\nInstall the formatter tooling once:\n\n```bash\nnpm install\n```\n\nThen run:\n\n```bash\nmake fmt\nmake fmt-web\nmake fmt-all\n```\n\n## Local UI testing\n\nFoundry now includes local-only browser coverage using Playwright for:\n\n- the shipped default frontend theme\n- the shipped default admin theme\n\nInstall the tooling once:\n\n```bash\nnpm install\nnpx playwright install\n```\n\nThen run:\n\n```bash\nmake test-e2e\n```\n\nUseful variants:\n\n```bash\nnpm run test:e2e\nnpm run test:e2e:full\nnpm run test:e2e:headed\nnpm run test:e2e:ui\n```\n\nNotes:\n\n- these tests start a real local Foundry server against a disposable temp workspace, so `content/`, `data/`, `public/`, and backup output are isolated from the repo during the run\n- they are intended for local development only right now; there is no GitHub Action for them yet\n- `make test-e2e` and `npm run test:e2e:full` run pre/post cleanup so `e2e-*` documents, media, users, backups, and lifecycle artifacts do not accumulate\n- admin tests default to `admin/admin`, but you can override credentials with:\n  - `FOUNDRY_E2E_ADMIN_USER`\n  - `FOUNDRY_E2E_ADMIN_PASS`\n\n## Official JavaScript SDKs\n\nFoundry now ships two official framework-agnostic JavaScript SDKs under `sdk/`:\n\n- `sdk/admin`\n- `sdk/frontend`\n\nThey exist to give admin frontends, plugin UIs, and JS-powered themes a supported client contract instead of forcing each consumer to hand-roll fetch logic against unstable internal endpoints.\n\nThe Admin SDK targets the authenticated admin API under `admin.path + /api`.\n\nThe Frontend SDK targets the public Foundry platform surface under `/__foundry`, with a live JSON API in preview/server mode and generated static artifacts under `public/__foundry/` for built sites.\n\nThe shared SDK core handles:\n\n- request construction\n- normalized JSON/error handling\n- capability discovery helpers\n- common client configuration\n\nThe current official browser entrypoints are:\n\n```text\n/__foundry/sdk/admin/index.js\n/__foundry/sdk/frontend/index.js\n```\n\nExample:\n\n```js\nimport { createAdminClient } from '/__foundry/sdk/admin/index.js';\nimport { createFrontendClient } from '/__foundry/sdk/frontend/index.js';\n\nconst admin = createAdminClient({ baseURL: '/__admin' });\nconst frontend = createFrontendClient({ mode: 'auto' });\n```\n\nCurrent capability discovery endpoints:\n\n- admin: `\u003cadmin.path\u003e/api/capabilities`\n- frontend: `/__foundry/api/capabilities`\n\nBuilt sites also emit frontend SDK data artifacts under:\n\n```text\npublic/__foundry/\n  capabilities.json\n  site.json\n  navigation.json\n  routes.json\n  collections.json\n  search.json\n  preview.json\n  content/\u003cid\u003e.json\n  sdk/...\n```\n\nThe shipped themes use these SDKs too:\n\n- the default admin theme imports the Admin SDK from `/__foundry/sdk/admin/index.js`\n- the default frontend theme boots a small SDK-based runtime from `/theme/js/foundry-theme.js`\n\nPlugin-defned admin pages and widgets can also target a stable shell contract now. A plugin can declare admin page and widget bundles in `plugin.yaml`, Foundry exposes those bundles under `\u003cadmin.path\u003e/extensions/\u003cplugin\u003e/...`, and the default admin shell will automatically import them when their page or widget slot is active. Admin pages can also declare `nav_group` (`dashboard`, `content`, `manage`, or `admin`) so they land in the right sidebar group. The shell dispatches `foundry:admin-extension-page` and `foundry:admin-extension-widget` and exposes `window.FoundryAdmin` so plugin code can mount against a supported runtime surface instead of private admin internals. The built-in Extensions admin page itself uses a separate route, `\u003cadmin.path\u003e/a-extensions`, so it does not collide with the plugin asset namespace.\n\n## Theme and plugin security\n\nFoundry now treats themes and plugins as different security surfaces.\n\n- Themes are restricted through `theme.yaml -\u003e security`\n- Plugins declare intended power through `plugin.yaml -\u003e permissions`\n\nTheme security currently covers:\n\n- a curated public-safe template context instead of raw config\n- CSP generation for public pages\n- validation of undeclared remote assets in theme HTML, CSS, and JS\n- explicit declaration of outbound frontend request origins\n\nPlugin permissions currently cover declaration and visibility, not true sandboxing.\nIn-process plugins are still trusted Go code today, but Foundry now also has a\nworking out-of-process RPC host for the first migrated hook family\n(`OnContext`). Foundry can now:\n\n- load and validate structured permission declarations\n- surface plugin risk and approval requirements in the admin UI\n- report declared permissions with `foundry plugin security \u003cname\u003e`\n- run `foundry plugin validate --security` for explicit security validation\n- statically analyze plugin source for risky imports, hooks, and calls\n- compare detected capabilities against declared permissions\n- require explicit approval for risky or mismatched plugins during CLI and admin install, enable, and update flows\n- record security-oriented plugin approval actions in the audit log\n- execute `runtime.mode: rpc` plugins out of process when they use the supported RPC protocol and hook family\n\nSee the full authoring guides for details:\n\n- `docs/themes/` for `theme.yaml -\u003e security`\n- `docs/plugins/` for `plugin.yaml -\u003e permissions`\n- `docs/plugins/` also includes the full structured permission reference for plugin authors\n\nPlugin runtime metadata now supports a real `runtime` block. The current RPC\nhost supports the `context` hook family over a JSON RPC transport, with a\nsanitized environment and no host-granted filesystem, network, or process\ncapability channels. Broader hook migration and stronger OS-level sandboxing\nstill build on that foundation.\n\n## Getting Started\n\n### Prerequisites\n\n- Go `1.22` or newer\n- A working `PATH` that includes `$(go env GOPATH)/bin` if you install with `go install`\n\n### Install\n\nInstall the CLI:\n\n```bash\ngo install github.com/sphireinc/foundry/cmd/foundry@latest\n```\n\nVerify the install:\n\n```bash\nfoundry version\n```\n\nThat prints runtime-aware version metadata, including the current version, commit, build date,\ninstall mode, target platform, and executable path.\n\nIf you are working from a local checkout instead of a global install, you can run:\n\n```bash\ngo run ./cmd/foundry version\n```\n\n### Start a site from the repo layout\n\nFoundry expects a file-based project layout. The quickest way to get running is to start with this shape:\n\n```text\ncontent/\n  config/\n    site.yaml\n  pages/\n    index.md\n  posts/\n  images/\n  uploads/\ndata/\nthemes/\nplugins/\n```\n\nMinimal `content/config/site.yaml` (though you can just cope the `example.site.yaml`):\n\n```yaml\ntitle: My Site\nbase_url: http://localhost:8080\ntheme: default\n\ncontent_dir: content\npublic_dir: public\nthemes_dir: themes\ndata_dir: data\nplugins_dir: plugins\n\nserver:\n  addr: :8080\n  live_reload: true\n  live_reload_mode: stream\n```\n\nMinimal `content/pages/index.md`:\n\n```md\n---\ntitle: Home\n---\n\n# Hello from Foundry\n```\n\n### Run it\n\n#### Docker\n\nLocal development with Docker:\n\n```bash\nsh scripts/docker-init.sh\ndocker compose up -d --build\n```\n\nThat uses:\n\n- [docker-compose.yml](/Users/JuanSanchez/WebstormProjects/cms/docker-compose.yml)\n- [content/config/site.docker.yaml](/Users/JuanSanchez/WebstormProjects/cms/content/config/site.docker.yaml)\n\nThe default Docker setup is intentionally development-oriented:\n\n- the project root is bind-mounted into the container\n- `data/` and `public/` stay on named Docker volumes\n- local Docker secrets can live in `.env`\n\nProduction-shaped Docker deployment:\n\n```bash\nexport FOUNDRY_ADMIN_SESSION_SECRET=\"$(openssl rand -hex 32)\"\nexport FOUNDRY_ADMIN_TOTP_SECRET_KEY=\"$(openssl rand -base64 32)\"\ndocker compose -f docker-compose.prod.yml up -d --build\n```\n\nThat uses:\n\n- [docker-compose.prod.yml](/Users/JuanSanchez/WebstormProjects/cms/docker-compose.prod.yml)\n- [content/config/site.docker.prod.yaml](/Users/JuanSanchez/WebstormProjects/cms/content/config/site.docker.prod.yaml)\n\nThe production compose shape is stricter:\n\n- explicit required secrets\n- read-only root filesystem\n- `tmpfs` for `/tmp`\n- `cap_drop: ALL`\n- `no-new-privileges`\n- named volumes for `content/`, `themes/`, `plugins/`, `data/`, and `public/`\n\nBefore using the production Docker overlay, set the real HTTPS origin in\n`content/config/site.docker.prod.yaml`.\n\n#### Source / local binary\n\nStart the local preview server from the project root:\n\n```bash\nfoundry serve\n```\n\nThen open `http://localhost:8080/`.\n\nIf you want Foundry to stay running after you close the terminal, use standalone mode:\n\n```bash\nfoundry serve-standalone\nfoundry status\nfoundry logs -f\nfoundry stop\nfoundry update check\nfoundry service install\nfoundry service status\nfoundry service restart\n```\n\nStandalone mode stores its runtime files under `.foundry/run/` in the project:\n\n- `standalone.json`\n- `standalone.log`\n\nTo produce static output:\n\n```bash\nfoundry build\n```\n\nGenerated files will be written to `public/`.\n\nFor preview-oriented output that includes non-published workflow states:\n\n```bash\nfoundry build --preview\n```\n\nThat also writes a preview manifest at `public/preview-links.json`.\n\n### Common commands\n\n```bash\nfoundry version\nfoundry build\nfoundry build --preview\nfoundry build --env preview --target production\nfoundry serve\nfoundry serve --debug\nfoundry serve-standalone\nfoundry serve-preview\nfoundry status\nfoundry restart\nfoundry stop\nfoundry logs -f\nfoundry service install\nfoundry service status\nfoundry service restart\nfoundry service uninstall\nfoundry release cut v1.3.3\nfoundry update check\nfoundry update apply\nfoundry plugin list --enabled\nfoundry theme list\nfoundry theme migrate field-contracts\nfoundry routes check\nfoundry admin hash-password your-password\n```\n\n### Typical local workflow\n\n1. Update `content/config/site.yaml`.\n2. Add pages and posts under `content/pages` and `content/posts`.\n3. Put media under the dedicated collection roots: `content/images`, `content/videos`, `content/audio`, and `content/documents`.\n4. Reference media from Markdown with the `media:` scheme.\n5. Run `foundry serve` during development.\n6. Run `foundry build` before publishing or checking generated output.\n\n### Standalone mode\n\nFor simple self-hosting or local development where you want Foundry to keep running in the background without Docker, use:\n\n```bash\nfoundry serve-standalone\n```\n\nThen manage it with:\n\n```bash\nfoundry status\nfoundry logs\nfoundry logs -f\nfoundry restart\nfoundry stop\nfoundry update check\nfoundry update apply\n```\n\nNotes:\n\n- standalone mode is designed for macOS and Linux\n- it writes state and logs under `.foundry/run/`\n- if you launch standalone from source with `go run`, Foundry first builds a managed binary under `.foundry/run/` and keeps that binary running instead of keeping `go run` alive after logout\n- it is a portable convenience runtime, not a replacement for Docker, `systemd`, or `launchd` in more serious production setups\n\n### Managed service mode\n\nFor a more durable self-hosted runtime that behaves more like `nginx`, install\nFoundry as a user-level OS service:\n\n```bash\nfoundry service install\nfoundry service status\nfoundry service restart\nfoundry service uninstall\n```\n\nBehavior by platform:\n\n- Linux: installs a user service under `~/.config/systemd/user/`\n- macOS: installs a LaunchAgent under `~/Library/LaunchAgents/`\n\nFoundry stores service metadata and logs under `.foundry/run/` in the project.\nIf Foundry was launched from source via `go run`, service installation first\nbuilds a managed binary under `.foundry/run/` and points the service at that\nbinary.\n\nOn Linux, user services may need lingering enabled to survive logout and reboot:\n\n```bash\nloginctl enable-linger \"$USER\"\n```\nEmbedded media uses normal Markdown image syntax:\n\n```md\n![Hero image](media:images/hero/banner.jpg)\n![Walkthrough video](media:videos/demo.mp4)\n```\n\nFile links use normal Markdown link syntax:\n\n```md\n[Download the spec](media:documents/spec-sheet.pdf)\n```\n\nAdmin uploads return stable references in the same format, for example:\n\n```text\nmedia:images/posts/launch/diagram.png\nmedia:videos/posts/launch/demo.mp4\nmedia:documents/posts/launch/spec-sheet.pdf\n```\n\nIf a page appears to hang during local preview, run `foundry serve --debug` to emit per-request timing plus runtime snapshots, including:\n\n- heap allocation and in-use heap\n- stack and total runtime memory\n- goroutine count\n- active request count\n- GC count\n- process user/system CPU time and request CPU percentage estimates\n\nIf live reload causes browser connection stalls in development, switch `server.live_reload_mode` from `stream` to `poll`. `stream` uses Server-Sent Events and refreshes immediately. `poll` trades a small delay for simpler connection behavior.\n\n## Deploy and operations\n\nFoundry supports environment-specific config overlays and named deploy targets.\n\nIf `content/config/site.preview.yaml` exists, it can be layered on top of the base config with:\n\n```bash\nfoundry build --env preview\n```\n\nNamed targets are configured under `deploy.targets` and applied with:\n\n```bash\nfoundry build --target production\nfoundry build --env staging --target edge\n```\n\nIf `deploy.default_target` is set, Foundry applies that target automatically when no explicit `--target` flag is provided.\n\n`foundry doctor` now reports timing breakdowns for:\n\n- plugin config hooks\n- content/data loading\n- route assignment\n- route hooks\n- asset sync\n- renderer\n- feed generation\n\n`foundry validate` now checks:\n\n- broken internal links\n- broken `media:` references\n- missing layout templates\n- orphaned media\n- duplicate URLs\n- duplicate type/lang slug combinations\n- taxonomy inconsistencies\n\nThe content command set also includes portability and migration helpers:\n\n```bash\nfoundry content export bundle.zip\nfoundry backup create\nfoundry backup list\nfoundry content import markdown ./legacy-markdown\nfoundry content import wordpress ./wordpress.xml\nfoundry content migrate layout page landing --dry-run\nfoundry content migrate field-rename marketing old_field new_field --dry-run\n```\n\n## Backups\n\nFoundry now has a dedicated backup flow for the content tree:\n\n```bash\nfoundry backup create\nfoundry backup create ./archives/manual-snapshot.zip\nfoundry backup list\nfoundry backup git-snapshot \"before launch\"\nfoundry backup git-log 10\n```\n\nBy default, managed backups are written under:\n\n```text\n.foundry/backups\n```\n\nBackups are zip archives of `content/` plus a small `backup-manifest.json`\nentry. Before writing the archive, Foundry checks free disk space on the target\nfilesystem and refuses to start the backup if there is not enough headroom.\n\nFoundry also supports local Git-backed snapshots as a second backup format:\n\n- the snapshot repo lives under `.foundry/backups/git`\n- each snapshot copies the current `content/` tree into that repo\n- Foundry commits only when there are actual content changes\n- this is a local history mechanism today, not a remote Git push integration yet\n\nYou can also enable debounced on-change backups in `content/config/site.yaml`:\n\n```yaml\nbackup:\n  enabled: true\n  dir: \".foundry/backups\"\n  on_change: true\n  debounce_seconds: 45\n  retention_count: 20\n  min_free_mb: 256\n  headroom_percent: 125\n```\n\nWhen `backup.on_change` is enabled, `foundry serve` and `foundry serve-preview`\nwill create a backup after the content tree goes quiet for the configured\ndebounce window. Foundry also prunes older backups beyond `retention_count`.\n\nThe admin Themes screen now also exposes manual zip backup creation, download,\nand restore actions for the same archive set.\n\n## Release updates\n\nFoundry includes a release-aware updater for managed standalone installs:\n\n```bash\nfoundry update check\nfoundry update apply\n```\n\nBehavior depends on install mode:\n\n- `standalone`: self-update supported\n- `docker`: update availability only, roll out a new image manually\n- `source`: update availability only, pull and rebuild manually\n- `binary`: update availability only unless you run it under `serve-standalone`\n\nSelf-update uses GitHub Releases metadata, compares the running version against\nthe latest release tag, downloads the matching release asset, verifies a\npublished `.sha256` asset when available, replaces the binary, and restarts the\nstandalone runtime.\n\nThis repository also includes a GitHub Actions release workflow at\n`.github/workflows/release.yml`. Pushing a `v*.*.*` tag now generates and uploads:\n\n- `foundry-linux-amd64.tar.gz`\n- `foundry-linux-arm64.tar.gz`\n- `foundry-darwin-amd64.tar.gz`\n- `foundry-darwin-arm64.tar.gz`\n- `foundry-windows-amd64.tar.gz`\n- matching `.sha256` files for each archive\n\nThe admin Themes screen also shows current version, latest release version,\ninstall mode, and whether self-update is supported.\n\n## Cutting a release\n\nTo cut a release tag locally:\n\n```bash\nfoundry release cut v1.3.3\n```\n\nTo cut and push the tag in one step:\n\n```bash\nfoundry release cut v1.3.3 --push\n```\n\nYou can also use the repo-local wrapper:\n\n```bash\nscripts/release.sh v1.3.3 --push\n```\n\nRelease cutting rules:\n\n- run it from the repository root\n- the worktree must be clean\n- versions must be in `vX.Y.Z` format\n\nWhen the tag is pushed, `.github/workflows/release.yml` builds and uploads the\n`foundry-*.tar.gz` archives and matching `.sha256` files to GitHub Releases.\n\n## Content model\n\nFoundry currently supports two primary document types:\n\n- `page`\n- `post`\n\nContent is loaded from:\n\n- `content/pages`\n- `content/posts`\n- `content/images`\n- `content/videos`\n- `content/audio`\n- `content/documents`\n\nLanguage variants are represented by a leading language directory. For example:\n\n```text\ncontent/pages/about.md\ncontent/pages/fr/about.md\ncontent/posts/launch.md\ncontent/posts/fr/launch.md\n```\n\nMarkdown files use frontmatter for metadata such as:\n\n- `title`\n- `slug`\n- `layout`\n- `draft`\n- `summary`\n- `date`\n- `updated_at`\n- `tags`\n- `categories`\n- custom `taxonomies`\n- `fields`\n- arbitrary `params`\n\n### Multimedia\n\nFoundry supports images, video, audo, and downloadable files through the `media:` reference scheme.\n\n- `media:images/...` resolves to `/images/...`\n- `media:uploads/...` resolves to `/uploads/...`\n- `media:assets/...` resolves to `/assets/...`\n\nThe renderer infers the output element from the target file extension:\n\n- image files render as `\u003cimg\u003e`\n- video files render as `\u003cvideo controls\u003e`\n- audio files render as `\u003caudio controls\u003e`\n- other files remain standard links\n\n## Configuration\n\nThe main config file is typically:\n\n```text\ncontent/config/site.yaml\n```\n\nThe admin `Settings` area edits this same file through structured forms and an\n`Advanced YAML` tab. Foundry keeps YAML as the storage format on disk, but the\nadmin works with a structured config object so common settings can be edited as\nforms and then written back safely to `site.yaml`.\n\nImportant config groups:\n\n- `admin`: admin service settings\n- `server`: preview server settings\n- `content`: content directory conventions and default layouts\n- `taxonomies`: taxonomy definitions and archive layouts\n- `plugins`: enabled plugins\n- `security`: security-sensitive rendering settings\n- `feed`: RSS and sitemap output paths\n\n### Advanced custom fields\n\nFoundry handles advanced custom fields through theme-owned contracts, not through `content/config/site.yaml`.\n\nCanonical guide:\n\n- GitHub Pages: [Advanced Custom Fields](https://sphireinc.github.io/foundry/custom-fields/)\n\n- Themes declare supported fields in `theme.yaml` under `field_contracts`\n- Page-specific field values stay in page frontmatter under `fields:`\n- Shared/global field values live in `content/custom-fields.yaml`\n- The admin editor resolves the current page's available fields from the active theme\n- The admin `Custom Fields` section edits shared/global field groups declared by the active theme\n- `fields:` is for persisted schema-backed content only; plugins must not write derived render\n  metadata into document fields\n- Plugin-derived values such as TOC data should be exposed at render time through runtime context\n  data instead\n\nExample `theme.yaml`:\n\n```yaml\nfield_contracts:\n  - key: marketing-page\n    title: Marketing Page\n    description: Fields for standard marketing pages.\n    target:\n      scope: document\n      types: [page]\n      layouts: [page]\n      slugs: [pricing, about, contact]\n    fields:\n      - name: hero_title\n        type: text\n        label: Hero Title\n        required: true\n      - name: hero_body\n        type: textarea\n        label: Hero Body\n  - key: site_marketing\n    title: Site Marketing\n    target:\n      scope: shared\n      key: site_marketing\n    fields:\n      - name: primary_cta_label\n        type: text\n        label: Primary CTA Label\n```\n\nExample page frontmatter:\n\n```yaml\n---\ntitle: Pricing\nslug: pricing\nlayout: page\nfields:\n  hero_title: Clear pricing for modern teams\n  hero_body: Foundry keeps publishing infrastructure calm and predictable.\n---\n```\n\nExample shared values:\n\n```yaml\nvalues:\n  site_marketing:\n    primary_cta_label: Start with Foundry\n```\n\nIf you still have legacy config-owned field schemas, migrate them with:\n\n```bash\nfoundry theme migrate field-contracts\n```\n\nPlugin-author note:\n\n- Do not use `doc.Fields` for derived values like `toc`, `has_toc`, `reading_time`, or similar\n  render-time metadata\n- `doc.Fields` now belongs to the active theme contract and is validated as persisted content\n- Runtime-only plugin data should be recomputed or attached through render-time context data\n  (`ctx.Data`)\n\n### Admin\n\n`admin.path` controls where the themeable admin shell is mounted. By default it is `/__admin`. The shell itself is public so the browser can load HTML, CSS, and JavaScript. Authenticated API access is session-based by default.\n\nAdmin users live in a filesystem-backed YAM file, which defaults to:\n\n```text\ncontent/config/admin-users.yaml\n```\n\nExample:\n\n```yaml\nusers:\n  - username: admin\n    name: Admin User\n    email: admin@example.com\n    role: admin\n    password_hash: argon2id$...\n```\n\nGenerate a password hash with:\n\n```bash\nfoundry admin hash-password \"your-password\"\n```\n\nOr generate a starter YAML snippet with:\n\n```bash\nfoundry admin sample-user admin \"Admin User\" admin@example.com \"your-password\"\n```\n\nBrowser sessions are stored in a secure cookie scoped to `admin.path`, expire\nafter 30 minutes of inactivity by default, and are renewed while the user\nremains active. Foundry persists only a token hash in\n`data/admin/sessions.yaml`, not the raw reusable bearer token.\n\n#### Admin security\n\n- set `admin.session_secret` or `FOUNDRY_ADMIN_SESSION_SECRET` in production\n- without it, session tokens are still hashed at rest, but with plain SHA-256 instead of keyed HMAC-SHA-256\n- set `admin.totp_secret_key` or `FOUNDRY_ADMIN_TOTP_SECRET_KEY` in production to encrypt stored TOTP secrets at rest\n- TOTP setup now requires that key; legacy plaintext TOTP secrets still verify and are migrated to encrypted form when used\n- admin password hashes now default to `argon2id$...`\n- legacy PBKDF2 password hashes still verify and are upgraded automatically on successful login\n- `admin.session_idle_timeout_minutes` controls inactivity expiry\n- `admin.session_max_age_minutes` caps total session lifetime even if the session remains active\n- `admin.single_session_per_user` optionally revokes older sessions when the same user signs in again\n- for non-local admin access, set `base_url` to an `https://...` origin and make sure your reverse proxy forwards HTTPS correctly so secure cookies behave as expected\n- rotating `admin.session_secret` invalidates all persisted browser sessions\n- rotating `admin.totp_secret_key` requires users with stored TOTP secrets to re-enroll if their secret has not already been migrated with the new key\n- if either secret is suspected compromised, rotate it, revoke sessions, and review the audit log before restoring access\n- compatibility note: legacy plaintext sessions, legacy plaintext TOTP secrets, and legacy PBKDF2 password hashes remain supported only as a migration bridge and are intended to be removed after the `1.4.x` line\n\n`admin.access_token` is now optional. If set, it still works for API automation with:\n\n- `Authorization: Bearer \u003ctoken\u003e`\n- `X-Foundry-Admin-Token: \u003ctoken\u003e`\n\nAdmin themes live under:\n\n```text\nthemes/admin-themes/\u003cname\u003e/\n  admin-theme.yaml\n  index.html\n  assets/\n    admin.css\n    admin.js\n```\n\n`admin-theme.yaml` is the admin-theme manifest. It now supports:\n\n- `admin_api`\n- `sdk_version`\n- `compatibility_version`\n- `components`\n- `widget_slots`\n- `screenshots`\n\nBoth shipped themes now declare and validate against the current SDK contract:\n\n- frontend theme `sdk_version: v1`\n- admin theme `sdk_version: v1`\n\nThe current stable admin-theme component contract is:\n\n- `shell`\n- `login`\n- `navigation`\n- `documents`\n- `media`\n- `users`\n- `config`\n- `plugins`\n- `themes`\n- `audit`\n\nThe current stable admin-theme widget-slot contract is:\n\n- `overview.after`\n- `documents.sidebar`\n- `media.sidebar`\n- `plugins.sidebar`\n\nSet the active admin theme with:\n\n```yaml\nadmin:\n  enabled: true\n  path: /__admin\n  session_secret: replace-this-in-production\n  totp_secret_key: replace-this-with-base64-32-byte-key\n  theme: default\n  users_file: content/config/admin-users.yaml\n  session_ttl_minutes: 30\n  session_idle_timeout_minutes: 30\n  session_max_age_minutes: 480\n  single_session_per_user: false\n```\n\n`admin.local_only` is an optional convenience restriction for local development. It is not the default posture and should not be treated as the only security boundary in front of a reverse proxy.\n\nThe dfault admin theme now includes:\n\n- a structured frontmatter editor that stays in sync with raw Markdown\n- media-picker insertion for stable `media:` references\n- document and media history/trash views with restore and purge actions\n- restore-preview flows that load a diff before a document restore is committed\n- media replacement while preserving the canonical reference path\n- an audit log view\n- dedicated user-security flows for password reset tokens, TOTP setup/disable, and session revocation\n- a Debug page with runtime, content, storage, integrity, activity, and persisted build-report visibility when `admin.debug.pprof` is enabled\n- keyboard shortcuts:\n  - `Cmd/Ctrl+S` save the current form\n  - `Cmd/Ctrl+Enter` preview the current document\n  - `Cmd/Ctrl+K` open the command palette\n  - `Shift+/` toggle shortcut help\n  - `g d`, `g m`, `g u`, `g a` jump to Documents, Media, Users, and Audit\n\nThe admin UI also includes breadcrumbs, toast notifications, unsaved-change warnings, clearer error panels, a command palette for fast navigation and creation shortcuts, review/scheduled overview queues, and client-side pagination/sorting for the major management tables.\n\n### Security\n\n`security.allow_unsafe_html` controls whether raw HTML in Markdown is preserved in rendered output.\n\n### Server\n\n`server.live_reload` turns live reload on during local preview.\n\n`server.live_reload_mode` controls the transport:\n\n- `stream`: uses a long-lived SSE connection to `/__reload`\n- `poll`: polls `/__reload/poll` every 1.5 seconds and reloads when the rebuild version changes\n\nUse `poll` if your browser or proxy environment is sensitive to long-lived local connections.\n\nThe preview/admin server uses explicit read, write, and idle timeouts by default.\n\nStatic builds now also emit a frontend search index at:\n\n```text\npublic/search.json\n```\n\nThe generated and live search surfaces now include snippets, and the search APIs apply simple weighted ranking so title and summary matches are promoted ahead of body-only matches.\n\n`foundry validate` now checks for:\n\n- broken `media:` references\n- broken internal links to routes and static files\n\n## Themes\n\nThemes live under `themes/\u003cname\u003e/`.\n\nA theme typically contains:\n\n```text\nthemes/default/\n  assets/\n    css/\n  layouts/\n    base.html\n    index.html\n    page.html\n    post.html\n    list.html\n    partials/\n      head.html\n      header.html\n      footer.html\n  theme.yaml\n```\n\nThemes are responsible for:\n\n- page and post presentation\n- shared base layout\n- taxonomy archive templates\n- theme-specific assets\n\nTheme manifests now support richer metadata:\n\n- `supported_layouts`\n- `config_schema`\n- `field_contracts`\n- `screenshots`\n- `compatibility_version`\n\nLaunch themes are also expected to support the current minimum slot contract:\n\n- `head.end`\n- `body.start`\n- `body.end`\n- `page.before_main`\n- `page.after_main`\n- `page.before_content`\n- `page.after_content`\n- `post.before_header`\n- `post.after_header`\n- `post.before_content`\n- `post.after_content`\n- `post.sidebar.top`\n- `post.sidebar.overview`\n- `post.sidebar.bottom`\n\nTheme validation now checks both of these conditions:\n\n- the theme manifest declares the required slots\n- the corresponding layouts actually render those slots\n\nIt also checks:\n\n- required layouts and partials\n- template references to missing partials/layouts\n- template parse failures with diagnostics suitable for admin reporting\n\n## Plugins\n\nPlugins live under `plugins/\u003cname\u003e/` and are registered through generated imports.\n\nCurrent plugin capabilities include:\n\n- lifecycle hooks during load/build/serve\n- route and rendering hooks\n- HTML slot injection\n- asset injection\n- plugin validation and dependency checks\n\nPlugin manifests now also support:\n\n- `dependencies`\n- `compatibility_version`\n- `config_schema`\n- `screenshots`\n\nPlugin installation is intentionally conservative now: install sources are restricted to GitHub over `https` or `git@github.com`. Installing a plugin still means trusting third-party code, so treat it as a supply-chain boundary.\n\nPlugin management is safer now:\n\n- updates keep rollback snapshots under `plugins/.rollback/\u003cname\u003e/...`\n- plugins can be rolled back to the latest preserved snapshot\n- admin plugin records now include health/diagnostic reporting, dependency/config metadata, and rollback availability\n\n## Incremental rebuilds\n\nFoundry maintains a dependency graph that relates:\n\n- source files to documents\n- templates to outputs\n- data keys to outputs\n- documents to taxonomy archives\n- taxonomy archives to rendered URLs\n\nWhen possible, the preview server rebuilds only the affected outputs instead of running a full site rebuild.\n\n## Asset pipeline\n\nThe asset pipeline can:\n\n- copy content assets\n- copy images\n- copy uploads\n- copy theme assets\n- copy enabled plugin assets\n- build a bundled CSS file in `public/assets/css/foundry.bundle.css`\n\nAsset roots and plugin/theme names are validated as safe paths, and symlinked asset files are rejected.\n\n## GitHub Pages\n\nThe repository publishes a small docs site from `docs/` that includes:\n\n- a project overview\n- the CLI contract\n- the latest HTML coverage report generated in CI\n\n## Development\n\nUseful commands while working on the repo:\n\n```bash\ngo test ./...\ngo vet ./...\ngo run ./cmd/plugin-sync\n```\n\nThe main CI workflow also verifies formatting, syncs generated plugin imports, builds the project, runs tests, and publishes the coverage report to GitHub Pages on `main`.\n\n\n# License\n\nPlease see `LICENSE`, `LICENSE.PLUGIN.EXCEPTION`, and `LICENSE.THEMES.EXCEPTION` for full license information.\n\n```text\nFoundry includes additional permissions under AGPLv3 section 7 for\nthird-party plugins and themes. Plugins, extensions, modules, themes,\ntemplates, skins, style packages, and similar customizations that work\nwith Foundry solely through its documented public extension and theming\ninterfaces are treated as separate and independent works. They are not\nsubject to the AGPL solely because they interoperate with Foundry\nthrough those public interfaces, and their authors may license them\nunder terms of their choice, including proprietary terms.\n\nThese permissions do not apply to Foundry itself, to modifications of\nFoundry, to works based on non-public or internal interfaces, or to\nworks that copy code from Foundry except as otherwise permitted.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsphireinc%2FFoundry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsphireinc%2FFoundry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsphireinc%2FFoundry/lists"}