LIGHTHOUSE CLI
lighthouse Configuration
This document describes the YAML configuration schema consumed by the
lighthouse CLI, including both the static-site pipeline and the
Harbor platform runtime.
Table of Contents
- Config Loading
- Top-Level Keys
- GLOBAL
- LIGHTHOUSE
- HARBOR
- CLOUDFLARE
- REMOTE
- RSS
- RATINGS
- SOURCES
- Validation Rules
- Command Notes
Config Loading
The CLI loads configuration from either:
- a single YAML file
- a config directory with command-specific files
Default config directory:
~/.lighthouse/config/
Use --config to point to a different file or directory.
The preferred layout is a config directory with separate files:
lighthouse.ymlorlighthouse.yamlharbor.ymlorharbor.yaml
Those files should use the namespaced roots:
LIGHTHOUSE:HARBOR:
Directory-loading behavior is command-specific:
- static Lighthouse commands only scan
lighthouse.yml|yaml - Harbor commands only scan
harbor.yml|yaml
GLOBAL is reserved for future shared settings, but the current
directory loaders do not scan global.yml.
For backward compatibility, explicit flat Lighthouse-only files still
load when you point --config at that file directly.
Top-Level Keys
Supported root namespaces:
GLOBALReserved for shared operator settings.LIGHTHOUSEStatic-site build, deploy, rating, RSS, and source materialization config.HARBORHarbor build, runtime, auth, and app-hosting config.
GLOBAL
GLOBAL is intentionally sparse in v1. It exists so the config
surface can stay unified while Lighthouse and Harbor keep separate
schemas.
LIGHTHOUSE
LIGHTHOUSE uses the existing static-site schema. Required unless
marked optional:
LIGHTHOUSE_CLONE_URLLIGHTHOUSE_DIRECTORYLIGHTHOUSE_BRANCHBUILD_COMMANDBUILD_OUTPUT_DIRDEPLOY_ROOTRETAIN_DEPLOYMENTSEMAILCLOUDFLAREREMOTERSSRATINGSSOURCES
The sections below still refer to the keys inside LIGHTHOUSE:.
HARBOR
Supported keys:
SOURCE_DIRCLONE_URLBRANCHDEPLOY_ROOTRETAIN_DEPLOYMENTSRUNTIME_DIRDATABASE_URLBIND_HOSTBIND_PORTPUBLIC_BASE_URLOIDCPERMISSIONSMAINTENANCEWORKSPACEAPPS
Key Harbor behaviors:
- Harbor is server-local in v1
- Harbor itself is sourced from exactly one mode:
SOURCE_DIR- or
CLONE_URL+BRANCH
- Harbor stores mutable platform state in Postgres via
DATABASE_URL - docked apps are sourced from exactly one mode:
SOURCE_DIR- or
CLONE_URL+BRANCH
- docked apps are built and staged into Harbor releases
- docked apps must load runtime settings from a config file
- Harbor injects resolved identity, roles, and permissions when it proxies requests
- Harbor owns the built-in
/myself/route; app slugmyselfis reserved and may not be used by docked apps
DATABASE_URL should point Harbor at a Postgres schema it owns for:
- users
- direct permissions
- user preferences
- maintenance state
- Harbor API keys
When Harbor itself uses CLONE_URL + BRANCH, the CLI clones or
updates that checkout inside the Harbor repo cache before building the
Harbor runtime.
OIDC supported keys:
ENABLEDMODEISSUER_PATHCLIENT_IDCLIENT_SECRETSESSION_COOKIE_NAMESESSION_SECRETEMBEDDED_USERS
Only MODE: embedded is supported in v1.
Each EMBEDDED_USERS entry supports:
USERNAMEDISPLAY_USERNAMEPASSWORDSUBJECTNAMEEMAILROLES
Harbor normalizes canonical usernames to lowercase and only allows:
a-z0-9_-
The normalized canonical username is used for identity and route-safe
slugs. DISPLAY_USERNAME is the human-facing label shown in Harbor and
apps. If DISPLAY_USERNAME is omitted, the raw configured USERNAME
value is used for display before normalization.
EMBEDDED_USERS are bootstrap-only seed users, not the long-term
mutable user store. Run lighthouse harbor migrate-db to create the
Harbor schema and import either legacy state or embedded users into
Postgres. After the Harbor database is initialized, Harbor ignores
EMBEDDED_USERS for steady-state auth and reads mutable users,
permissions, preferences, and maintenance state from Postgres. Once
that database state exists, EMBEDDED_USERS may be removed from
config.
Harbor DB-backed admin commands:
lighthouse harbor add-userlighthouse harbor set-passwordlighthouse harbor list-permissionslighthouse harbor list-all-permissionslighthouse harbor add-permissionslighthouse harbor delete-permissionslighthouse harbor delete-userlighthouse harbor migrate-dblighthouse harbor maintenance onlighthouse harbor maintenance off
Permission notes:
- effective permissions are the union of role-derived permissions and direct user permissions
- the direct
adminpermission acts as a wildcard and grants all app permissions - Harbor resolves principals in this order:
- API key bearer token
- Harbor browser session cookie
- guest
- docked apps receive the derived Harbor identity via forwarded
X-Harbor-*headers, including auth method, theme, timezone, and API key ID when applicable
MAINTENANCE remains in config as default bootstrap values only.
Harbor writes the live maintenance state into Postgres during database
initialization and then mutates the database-backed value through the
Harbor CLI.
WORKSPACE supported keys:
ROOT_DIRMAX_LEASESDEFAULT_SIZE_MB
APPS is a list. Each app supports:
IDNAMEDESCRIPTIONSLUGSOURCE_DIRCLONE_URLBRANCHBUILD_COMMANDRUN_COMMANDCONFIG_PATHPUBLIC_BIND_ADDRMANAGEMENT_SOCKET_PATHGUEST_CAN_VIEWREAD_PERMISSIONSWRITE_PERMISSIONS
Each app must specify exactly one source mode:
SOURCE_DIR- or
CLONE_URLtogether withBRANCH
DESCRIPTION is optional. Harbor uses it on the landing page cards.
Whiteboard example:
SLUG: whiteboardGUEST_CAN_VIEW: trueREAD_PERMISSIONS: []WRITE_PERMISSIONS: []- Whiteboard relies on Harbor principal resolution rather than app-specific Harbor permissions; private server documents and MCP still require an authenticated Harbor principal inside Whiteboard
BUILD_COMMAND and RUN_COMMAND support these placeholders:
{release_dir}{app_dir}{config_path}{runtime_dir}
Supported keys:
ENABLEDFROMUSERSMTP_ADDRSMTP_PORTPASSWDDESTINATIONSSTARTTLSIMPLICIT_TLS
If EMAIL.ENABLED is true, the SMTP fields above must be fully
specified and DESTINATIONS must be non-empty.
Run-log delivery is controlled per invocation with --email-log.
SMTP configuration alone does not automatically send mail on every run.
CLOUDFLARE
Supported keys:
ENABLEDZONE_IDCLOUDFLARE_API_KEY
When CLOUDFLARE.ENABLED is true, these fields become required:
ZONE_IDCLOUDFLARE_API_KEY
Behavior:
- runs only during
apply - executes after the new release is activated
- sends a Cloudflare
purge_everythingrequest for the configured zone - fails the command loudly if the Cloudflare API request fails or the API reports an unsuccessful purge
Example:
CLOUDFLARE:
ENABLED: true
ZONE_ID: "replace-me"
CLOUDFLARE_API_KEY: "replace-me"
REMOTE
Supported keys:
ENABLEDROLESSH_HOSTSSH_PORTREMOTE_RUN_DIRLOCAL_CACHE_DIRREMOTE_CACHE_DIRLIGHTHOUSE_CLI_CLONE_URLLIGHTHOUSE_CLI_REPO_PATH
Roles:
localremote
Validation rules:
ROLEis required whenREMOTE.ENABLEDis trueREMOTE_RUN_DIRis required whenREMOTE.ENABLEDis trueSSH_PORTmust be an integer>= 1SSH_HOSTis required whenROLEislocalLIGHTHOUSE_CLI_CLONE_URLis required whenROLEislocalLIGHTHOUSE_CLI_REPO_PATHis required whenROLEislocal
Defaults:
SSH_PORT: 22LOCAL_CACHE_DIR: <run-dir>/remote-cacheREMOTE_CACHE_DIR: <REMOTE_RUN_DIR>/remote-cache
Behavior:
remote buildis valid only forROLE=localremote sendis valid only forROLE=localremote applyis valid for both roles- local
remote applychecks the remote cache and then SSHes into the target to invoke remoteremote apply - before triggering remote deployment, local
remote applyensures the configured remotelighthouse-clicheckout exists, pulls or clones it, installs or upgrades the package, and syncs a derived remote-roleconfig.yml - local
remote applyalso syncs portable content metadata (content-state.json) into the remote run dir - if local and remote content metadata differ, the CLI pauses and asks
you to type
localorremote - remote
remote applydeploys a tarball already staged inREMOTE_CACHE_DIR/incoming/ - remote artifact deploys never rebuild from source
Example local-side config:
REMOTE:
ENABLED: true
ROLE: "local"
SSH_HOST: "lighthouse@ubuntu-main"
SSH_PORT: 22
REMOTE_RUN_DIR: "/var/lib/lighthouse"
LIGHTHOUSE_CLI_CLONE_URL: "https://git.peisongxiao.com/peisongxiao/lighthouse-cli.git"
LIGHTHOUSE_CLI_REPO_PATH: "/srv/lighthouse-cli"
Example remote-side config:
REMOTE:
ENABLED: true
ROLE: "remote"
REMOTE_RUN_DIR: "/var/lib/lighthouse"
RSS
Supported keys:
ENABLEDRSS_FEED_RATING_THRESHOLDUPDATED_DIFF_RATING_THRESHOLD
Defaults:
ENABLED: trueRSS_FEED_RATING_THRESHOLD: 7.0UPDATED_DIFF_RATING_THRESHOLD: -1.0
Behavior:
RSS_FEED_RATING_THRESHOLDgates RSS inclusion by combined post ratingUPDATED_DIFF_RATING_THRESHOLD <= 0disables the future diff-rating LLM path and treats updated posts mechanically- the CLI materializes RSS policy into machine-owned site data so the Jekyll site can render the final XML feeds
Example:
RSS:
ENABLED: true
RSS_FEED_RATING_THRESHOLD: 7.0
UPDATED_DIFF_RATING_THRESHOLD: -1.0
RATINGS
Supported keys:
ENABLEDPROVIDEROPENROUTER_API_KEYRATINGS_MODELDEFAULT_SCOREMAX_RETRIESMAX_THREADSHTTP_TIMEOUT_SECONDSREASONING_EFFORTPROMPT
Current provider support is intentionally narrow:
PROVIDERmust beopenrouter
When RATINGS.ENABLED is true, these fields become required:
OPENROUTER_API_KEYRATINGS_MODELPROMPT
DEFAULT_SCORE must stay within [0.0, 5.0].
MAX_RETRIES must be an integer >= 1.
MAX_THREADS must be an integer >= 1.
HTTP_TIMEOUT_SECONDS must be an integer >= 1.
REASONING_EFFORT must be one of:
xhighhighmediumlowminimalnone
Rating generation behavior:
- runs during
build,apply, andlocal - never runs during
validate - retries provider failures and invalid structured outputs up to
MAX_RETRIES - runs fresh rating jobs through a bounded central worker pool sized by
MAX_THREADS - uses
HTTP_TIMEOUT_SECONDSfor the OpenRouter HTTP read timeout - sends
REASONING_EFFORTthrough OpenRouter’sreasoning.effortfield - explicitly disables streaming for rating requests
- clamps out-of-bounds scores into
[0.0, 5.0]instead of retrying - falls back to
DEFAULT_SCOREafter the retry budget is exhausted
The prompt lives in config, not in the repo. The CLI appends the raw source document below that prompt at runtime.
Example:
RATINGS:
ENABLED: true
PROVIDER: "openrouter"
OPENROUTER_API_KEY: "replace-me"
RATINGS_MODEL: "openai/gpt-5-mini"
DEFAULT_SCORE: 2.5
MAX_RETRIES: 3
MAX_THREADS: 1
HTTP_TIMEOUT_SECONDS: 120
REASONING_EFFORT: "low"
PROMPT: |
--- BEGIN TASK DESCRIPTION ---
Rate the provided document for standalone long-term
showcase value on a personal website.
--- END TASK DESCRIPTION ---
--- BEGIN SCORE EXPLANATION ---
Use a 0.0 to 5.0 scale where 2.5 is neutral.
--- END SCORE EXPLANATION ---
--- BEGIN OUTPUT FORMAT ---
Return JSON with score, reason, and signals.
--- END OUTPUT FORMAT ---
SOURCES
Each source entry supports:
NAMECLONE_URLBRANCHURL_PATHPOST_TAGRECENT_POSTS
URL_PATH is optional. If omitted, the default is:
/projects/<normalized-name>/
Special cases can override this explicitly, for example:
/blogs//maestro/
URL_PATH is the source root navigator URL. Source roots cannot be
nested inside one another: /prefix-1/ and /prefix-1/subprefix/
conflict, while /prefix/subprefix-1/ and /prefix/subprefix-2/ are
valid siblings if /prefix/ is not itself configured as a source root.
POST_TAG is optional. It controls the badge shown on cards for
materialized posts from that source. If omitted, the CLI derives the
badge from NAME by replacing - and _ with spaces and uppercasing
the result.
Examples:
POST_TAG: "BLOGS"
POST_TAG: "LANGUAGE DESIGN"
RECENT_POSTS is an optional list of glob patterns evaluated relative
to the source repo root. Matching discovered source documents are
marked for the homepage recent-posts section. Any **/ segment also
matches the current directory at that level, so:
**/*.mdincludes bothREADME.mdand nested Markdown files**/*.texincludes bothREADME.texand nested LaTeX filesthoughts/**/*.mdincludes boththoughts/post.mdand deeper files
Examples:
RECENT_POSTS:
- "**/*.md"
- "**/*.tex"
RECENT_POSTS:
- "thoughts/**/*.md"
- "announcements/*.md"
If RECENT_POSTS is omitted or empty, that source repo does not
contribute surfaced posts to the homepage recent-posts strip or to the
surfaced-post ordering in navigator views.
Validation Rules
Validation is intentionally strict. The CLI will fail if it sees:
- unknown config keys
- wrong value types
- empty required strings
RETAIN_DEPLOYMENTS < 1RATINGS.DEFAULT_SCOREoutside[0.0, 5.0]RATINGS.MAX_RETRIES < 1RATINGS.MAX_THREADS < 1REMOTE.SSH_PORT < 1- enabled
REMOTElocal-role blocks withoutSSH_HOST - enabled
REMOTEblocks withoutROLEandREMOTE_RUN_DIR - enabled
CLOUDFLAREblocks withoutZONE_IDandCLOUDFLARE_API_KEY - invalid
URL_PATHformatting - nested or duplicate source root
URL_PATHvalues - URL or generated-path conflicts
- missing local document references
- references to unmanaged
.mdor.textargets - missing referenced local assets
- existing runtime lock files for non-config-only operations
Error messages are designed to say:
- what key or path failed
- what type or shape was expected
- what value was actually received
Command Notes
validate --config-onlyvalidates merged config onlyvalidatevalidates config and the pre-build deployment inputsvalidate --freshvalidates using the same full input pipeline but ignores prior incremental rating metadata for the current runbuildvalidates, syncs, materializes, rates incrementally, builds without deployment, and writes a state snapshot for future incremental runsapplyruns the full rating, build, state, and deployment path, then purges Cloudflare ifCLOUDFLARE.ENABLEDis truecleansupports--target lighthouseand--target harborclean --target lighthouseacquires the runtime lock, removes the static-sitecache/reposdirectory withrm -rf, and recreates the empty cache directoryclean --target harborremoves the Harbor repo cache directory under the configured Harbor runtime tree and recreates it emptyapply --target harbortreats both Harbor itself and clone-backed docked apps as deploy inputs for change detection, so app-only repo updates trigger Harbor redeployscheck-depsvalidates that the current environment has the executables required for the configured feature setlocal PORTrates incrementally, builds, writes state, and serves locally without deploying to/var/www/lighthouseIt ignoresDEPLOY_ROOT.remote buildruns the full local materialize-and-build path, then packages the built site underLOCAL_CACHE_DIRremote senduploads a packaged tarball intoREMOTE_CACHE_DIR/incoming/remote applyonROLE=local, verifies the staged remote tarball and triggers deployment over SSHremote applyonROLE=localalso resolves and syncs portable content metadata before deploymentremote applyonROLE=remote, unpacks the staged tarball, activates a release, writes remote deploy state, rotates old releases, and optionally purges Cloudflare