Feather-Page CMS is a web interface for managing small static websites. It is a Phoenix application (LiveView admin, SQLite) that renders sites to static HTML and deploys them by FTPS to the site owner's own hosting.
- A simple interface that can be used by non-technical users.
- Website should look like a real web-developer hand-coded them.
- Deployed websites should be static.
- Deployed websites should NOT load any external resources.
- Deployed websites should be as small as possible.
- Deployed websites should be SEO friendly.
- Deployed websites are on domains that belong to the user.
- Erlang/OTP 28 and Elixir 1.19
rclonefor deployments (FTPS)d2(at least v0.9.0; theDockerfilepins the exact version) to render diagram blocks; without it diagrams show their D2 sourcechroma(v2; theDockerfilepins the exact version) to highlight code blocks; without it code blocks stay plainbrotli(optional) to precompress exported sites as.br; without it only.gzis written- No system libvips needed:
vixdownloads a precompiled libvips on first compile - Noto Color Emoji (optional,
fonts-noto-color-emojion Debian) for colour emoji in the favicons and share cards the export draws; without it emoji come out in whatever font has them inotify-tools(optional, Linux) for live reload in development- Node.js with npm: the asset build installs the editor's packages from
assets/package-lock.json(npm ci);mix setupdownloads the standalonetailwindandesbuildbinaries - Git:
mix deps.get(part ofmix setup) fetches the component librarysetzkastenand daisyUI from GitHub
mix setup # deps, database (SQLite file feather_dev.db), seeds, assets
mix phx.server # http://localhost:4000The seeds create the super admin admin@example.com and a demo site. There are no passwords:
enter the email on the login page and open the magic link from the dev mailbox at
http://localhost:4000/dev/mailbox.
Uploaded images are stored in storage/ (git-ignored).
| Task | Purpose |
|---|---|
mix feather.create_user EMAIL [--super-admin] |
Create (or update) a user who can log in by magic link |
mix test |
Run the test suite |
mix precommit |
Compile with warnings as errors, unlock unused deps, format, run Credo, run the tests |
mix ecto.reset |
Drop, migrate and seed the development database |
In a release the same is available through bin/feather eval, see Feather.Release
(migrate/0, create_user/2). bin/server migrates and starts the app.
Feather.Release.backfill_suggestions/0 fills in the model's alt texts, descriptions and short
post titles for content from before OLLAMA_URL was set; deploy the sites by hand afterwards.
| Variable | Description | Default |
|---|---|---|
PHX_HOST |
Public host name of the CMS (prod); the inboxes of the sites' fediverse accounts live on it | example.com |
PORT |
HTTP port | 4000 |
SECRET_KEY_BASE |
Signs cookies and tokens (prod, required) | |
DATABASE_PATH |
SQLite database file (prod, required) | |
POOL_SIZE |
Database connections (prod) | 5 |
STORAGE_PATH |
Images, rendered diagrams, the pictures of the authors of approved fediverse replies and build output (prod, required) | dev: storage/ |
CONFIG_ENCRYPTION_KEY |
32 random bytes, base64; encrypts deployment credentials, archive.org keys and the private keys, mentions, Timeline posts, members' replies and outgoing deliveries of the sites' fediverse accounts (prod, required) | fixed dev/test keys |
UNSPLASH_ACCESS_KEY |
Unsplash API key (optional) | |
HARDCOVER_TOKEN |
Hardcover API key for the book search (optional; without it books are searched on OpenLibrary) | |
OLLAMA_URL |
Base URL of an Ollama instance (e.g. http://ollama:11434), which suggests alt texts, descriptions and titles of short posts (optional; without it no suggestions are made) |
|
OLLAMA_MODEL |
The Ollama model that makes the suggestions; it must read images | gemma4:26b-a4b-it-qat |
FEDIVERSE_PROXY |
URL of an HTTP proxy (e.g. http://proxy:3128) that all requests to fediverse servers tunnel through, so they do not learn where the CMS runs (optional); the CMS still checks every address, but the proxy itself must reach only public addresses. Even with the proxy the CMS resolves the servers' names itself, so its DNS should go through a resolver that does not reveal where it runs |
|
IMAGE_CLEANUP |
false disables the daily deletion of orphaned images |
enabled |
SMTP_ADDRESS, SMTP_PORT, SMTP_USERNAME, SMTP_PASSWORD |
Outgoing mail (prod) | localhost, 587 |
Generate a CONFIG_ENCRYPTION_KEY with
elixir -e 'IO.puts(Base.encode64(:crypto.strong_rand_bytes(32)))'.
Fediverse servers post to the inboxes of the sites' accounts below /fediverse/inbox/ on PHX_HOST. A proxy in front
of the CMS that asks for a login or blocks countries must open that prefix to the internet, matching it after decoding
the path and resolving dot segments, and must reject encoded slashes. The CMS answers every other path below the prefix
with 404 and limits the requests per client; when the proxy connects from a private network, the client is the last
public address in its X-Forwarded-For.
Add https://<PHX_HOST>/mcp as a custom connector in Claude (or as the MCP server of another
client) and allow it on the consent page. Tokens are bound to this URL, built from PHX_HOST.
lib/feather/: contexts with the domain logic (Accounts,Sites,Content,Books,Media,Publishing,Redirects,OAuth,Fediverse)lib/feather/static_site/: static export and preview (sinks, feed, sitemap, Markdown alternates,llms.txt, the fediverse documents, the theme registryThemes); each theme's EEx templates live inthemes/<theme>/lib/feather_web/: the LiveView admin, built with the shared component librarysetzkasten(Tailwind CSS and daisyUI), and the MCP server at/mcp(FeatherWeb.MCP, its tools inmcp/tools/)priv/static_site/: the stylesheets and fonts of the themes (themes/<theme>/) and the translations of the generated static sitestest/: ExUnit tests, the executable specification (DECISIONS.md, 0008)ops/: production setup (compose.yml,stack.env.example); CI pushes the image toghcr.io/feather-page/cmson every push tomainCONTEXT.md,DECISIONS.md: domain language and architecture decisions
TODO: Add license information.