- Rust 53%
- Python 34%
- JavaScript 7%
- Shell 3%
- Jinja-like 2%
- 3 more languages 1%
lichen.page #
lichen.page is a cms for static site generators built on atproto. you can see screenshots of it, read about its background, or try it out in one the following ways:
- use the hosted version — login with atproto
- run it locally — see the quickstart
- use the malleable software version
lichen.page is a piece of software fan-fiction (aka a fork) of lichen-markdown which was itself a fork of lichen-gemini, which is composed of a few forth scripts.
In the third edition, the CMS was rewritten in Rust to enable realtime collaboration using yrs and work with any static site generator.
This is an independent and experimental project and welcomes contributions, feedback, bug reports, and conversation.

artwork created by frsw
Quickstart #
git clone https://tangled.org/notplants.net/lichen.page.git
cd lichen.page
cargo run -- server run
# then visit http://127.0.0.1:9000/
This starts the lichen server on your computer, without requiring a login.
Use the dashboard to create or open a site. Sites live in ~/.lichen/.
To serve a single site from a directory instead, use cargo run -- server run --site ./site.
For all features, see README-extended.md.
Commands #
cargo run -- runs the lichen executable.
lichen server run— run locally; add--site <PATH>for a standalone site.lichen server init/build/export— prepare, build, or export a standalone site (default:./site).lichen remote …— log in to a remote server, manage sites, and synchronize local clones.lichen update— update the executable.
Use lichen --help to explore commands.
The extended command reference covers all options.
Configuration #
The default lichen stores data in ~/.lichen/ (or LM_ROOT_DIR) and uses port 9000
(overridden by --port or LM_SERVER_PORT). The server configuration is in ~/.lichen/lichen.toml,
and each site configuration is in ~/.lichen/<site>/settings.toml
For site settings, lichen reads its settings from layered sources:
- optional CLI args:
--site <path>establishes the project root (defaults to./site).--settings <path>sets the path to the settings file (defaults to<site_dir>/settings.toml). - optional environment variables prefixed with
LM_(includingLM_SITE_DIRandLM_SETTINGS_PATH, for exampleLM_AUTO_SAVE=true). - optional values in
settings.toml. - default values.
CLI args override environment variables which override file values which override the defaults. standalone server commands use this configuration scheme. Multi-site hosting
uses lichen.toml instead; see README-extended.md.
the standard usage is: ./lichen server run --site path/to/your/site (with other configurations stored in path/to/your/site/settings.toml).
All possible configurations are shown in settings.toml.example along with explanations.
auto_save automatically saves edits made in the CMS, but can be disabled within settings. Clients without JavaScript cannot use autosave.
Authentication & User Management #
For standalone or multi-site hosting, if use_auth is not explicitly set to true in configuration, authentication will not be required and anyone can access the CMS.
If use_auth is set to true, then authentication is required, via one of the configured auth_providers set in lichen.toml,
which can have a values of file, atproto or oidc or a list of these.
Users are stored as individual TOML files in the users/ directory within your site folder:
site/
└── users/
├── alice.toml
└── bob.toml
In file auth mode, user files contain an Argon2 password hash:
password_hash = "$argon2id$v=19$m=19456,t=2,p=1$..."
And you can use the built-in CLI commands to manage users (passwords are hashed automatically):
# create a new user (prompts for password)
cargo run -- server users --site ./site add yourusername
# update an existing user's password
cargo run -- server users --site ./site set-password yourusername
To start an authenticated session, visit /login/. After logging in, the browser receives a session cookie (managed by tower-sessions) which allows access to protected CMS routes.
Static Site Generators #
lichen uses a simple built-in static site generator by default, but it can also be configured to use other static site generators.
these are the possible values for the ssg setting in settings.toml. each also requires being enabled as a compilation feature.
lichen– the default, implements a markdown + Tera pipeline entirely inside this crate.hugo– enable thehugoCargo feature and install thehugoCLI somewhere on$PATH.mdbook– enable themdbookCargo feature. This routes all builds through themdbook_drivercrate so no external binary is required.
additional static site generators can be added by implementing the StaticSiteGenerator trait in ssg_trait.rs.
Multi-Site Mode #
lichen can also be run in multi-site mode, which allows hosting multiple sites from a single server instance with a management dashboard. Each site is served based on its domain name via the HTTP Host header.
cargo run -- server run --multi
For full documentation of multisite mode, see README-extended.md.
Deploy as a Nix service #
This repo's flake ships everything needed to run lichen as a systemd service on NixOS:
-
packages.lichen(alsopackages.default) — the unified client/server binary, built with client support, Git, ATProto, the optional terminal, and iroh. Other features can be selected when building.gitandsshare wrapped onto itsPATHfor git-backed sites and deploy keys.nix build .#lichen # -> ./result/bin/lichen nix run .#lichen -- server run --multi # run it directly -
nixosModules.lichen— a NixOS module exposingservices.lichen. Import it in a configuration and pass your deployment vars; it defines a hardenedsystemd.services.lichen(Restart=always,StateDirectory,EnvironmentFile,DynamicUserby default).{ inputs.lichen.url = "git+https://tangled.org/notplants.net/lichen.page"; outputs = { self, nixpkgs, lichen }: { nixosConfigurations.myhost = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; modules = [ lichen.nixosModules.lichen { services.lichen = { enable = true; port = 8080; bindAddress = "100.84.190.30"; # e.g. a Tailscale IP; "0.0.0.0" = all baseUrl = "https://staging.lichen.page"; dataDir = "/var/lib/lichen"; # sites + users/ + lichen.toml live here environmentFile = "/run/secrets/lichen.env"; # atproto/OAuth/PDS secrets — NEVER in the store openFirewall = true; }; } ]; }; }; }Options (
services.lichen.*):enable,package,bindAddress,port,dataDir,user/group/dynamicUser,environmentFile,baseUrl,extraArgs,openFirewall. The typed options are passed through asLM_*env vars (LM_SERVER_PORT,LM_BIND_ADDRESS,LM_PUBLIC_URL,LM_ROOT_DIR); any other server setting can go inenvironmentFileor alichen.tomlindataDir. Keep secrets only inenvironmentFile— it is read by systemd at start and never copied into the nix store. See nix/lichen-service.nix.
Agent Entrypoint #
For the malleable software version, paste this prompt into your AI agent of choice:
Please read https://tangled.org/notplants.net/lichen.page/blob/main/.agents/skills/lichen/SKILL.md, and explain the project. what is the malleable software framing? how can we install this and use this?