[@layout]: ../docs.layout.md

# Publish

A site ships either as static files from `pmx export` or as a live server from `pmx serve`. Both build the same pages through the same access rules.

Choose the target using [How PathMX runs](./runs.guide.md), then follow the
matching workflow below. Run commands from the [Quickstart](./first-site.guide.md)
project; `bun run pmx` uses its installed CLI without a global launcher.

---

## Static export

1. Export with the public origin the site will live at.

   ```sh
   bun run pmx export --origin https://example.com
   ```

   You should see `Exported N routes to …/dist`. Use `--out <path>` for a
   different directory and `--base-path /guide` to mount below a prefix.

2. Look at the output.

   ```text
   dist/
   ├── index.html, index.md      each page and its authored Markdown
   ├── llms.txt, robots.txt, sitemap.xml
   ├── 404.html                 missing-page document
   ├── .pmx/                     hashed styles, scripts, and build marker
   └── .pathmx/manifest.json     canonical route contract for hosts
   ```

   Upload `dist/` to any static host. Pages keep client-side navigation and
   refresh themselves when a new deployment lands.

3. Read the diagnostics. Export includes only anonymously readable pages.
   Server-only behavior that cannot run in the exported site is reported with
   its Source. Review these diagnostics before publishing. Browser-local Actions
   need a supported [browser host](../extend/core.reference.md#browser-publications);
   static export does not make arbitrary server operations available offline.

Preview the files over HTTP before uploading. If Python 3 is installed:

```sh
python3 -m http.server 8080 --directory dist
```

Open `http://localhost:8080/` and follow the workshop lesson link. Reload the
lesson's deep URL, view its `.md` alternate, and check the stylesheet and Player.
This previews files only; it does not run server Actions. Stop it with Ctrl+C.
Your production host must serve the exported routes; see the route manifest when
configuring redirects or rewrites.

Follow the [Cloudflare guide](../hosting/cloudflare.guide.md) to publish with the optional adapter.

---

## Missing pages

PathMX shows a themed recovery page when a browser opens a missing page. The
requested URL stays in the address bar and the response carries HTTP status
`404`. To replace its content, add `paths/404.page.md`:

```md
# This trail ends here

The page may have moved. Let's find another way.

[Back to home](/)
```

The override uses normal Markdown, theme settings, and layout selection. It must
be publicly readable; a private override is ignored. Open `/404.page` to preview
it as an ordinary page. Missing-page responses use browser navigation rather
than the Source runtime, so recovery links also work without JavaScript. Reload
a missing URL to see edits to its recovery content.

Requests that prefer `text/markdown` receive the override's Markdown, or the
default recovery text when no override exists. Raw Source reads and API errors
retain their normal error responses. Recovery responses are marked `noindex`
and are not cached.

The live server and Cloudflare adapter select the recovery page automatically.
Directory exports include `404.html`; configure other hosts to serve it for
missing URLs with status `404`, preserving the requested URL. With `--base-path
/guide`, that file is `dist/guide/404.html`. A file-only preview server may show
its own error page until configured to use this file.

The automatic convention currently applies to `404.page.md`. Files such as
`500.page.md` or `503.page.md` remain ordinary pages; they do not enable exception
handling or maintenance mode.

---

## Live server

1. Set the production environment in `<project>/.env`.

   ```env
   PATHMX_ORIGIN=https://example.com
   PATHMX_HOST=127.0.0.1
   PATHMX_PORT=3003
   ```

   Command-line `--host` and `--port` win, then these variables, then
   `PORT`. The default is `0.0.0.0:3000`.

2. Run the server from the project directory under a process supervisor.

   ```sh
   bun run start
   ```

   It stays in the foreground, watches Markdown and CSS, and shuts down
   cleanly on `SIGINT` or `SIGTERM`.

3. Probe health.

   ```sh
   curl -I http://127.0.0.1:3003/api/runtime/health
   ```

   You should see `204` while the Source is valid and `503` after a
   rejected edit until it is corrected.

Keep `.env` and `.pathmx/` out of Git and in your backup policy. Source under
`paths/` stays in version control.

---

## Supervise a live process

For Deploy's macOS adapter, prepare one ordinary server checkout from the same
network Git repository as your authoring checkout. Install Bun and Python 3 for
the service user, clone the project, run `bun install --frozen-lockfile`, and create
a separate `live` branch with `git switch -c live`. The committed dependencies
must already include the compatible `@pathmx/deploy` package. Configure `.env`
and verify `bun run start` plus the health probe above before supervising it.
Stop the foreground process before loading the service.

This complete example assumes a macOS user named `site`, a checkout at
`/Users/site/sources/course`, and Bun at `/Users/site/.bun/bin/bun`. Replace those
paths and the service label with your actual values; plist strings do not expand
`~` or shell variables. Create
`~/Library/LaunchAgents/net.example.course.plist` for that service user:

```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>net.example.course</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/site/sources/course/node_modules/.bin/pmx</string>
    <string>serve</string>
  </array>
  <key>WorkingDirectory</key><string>/Users/site/sources/course</string>
  <key>EnvironmentVariables</key>
  <dict>
    <key>PATH</key><string>/Users/site/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
    <key>PATHMX_HOST</key><string>127.0.0.1</string>
    <key>PATHMX_PORT</key><string>3003</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/Users/site/Library/Logs/course.log</string>
  <key>StandardErrorPath</key><string>/Users/site/Library/Logs/course-error.log</string>
</dict>
</plist>
```

---

## Load and verify the service

Run these on the server as the service user, with its GUI login session available:

```sh
mkdir -p ~/Library/Logs
plutil -lint ~/Library/LaunchAgents/net.example.course.plist
launchctl bootstrap "gui/$(id -u)" ~/Library/LaunchAgents/net.example.course.plist
launchctl enable "gui/$(id -u)/net.example.course"
launchctl kickstart "gui/$(id -u)/net.example.course"
launchctl print "gui/$(id -u)/net.example.course"
curl -I http://127.0.0.1:3003/api/runtime/health
```

The plist check returns OK; the service report should show a running process,
and health returns `204`. Inspect the two log files if it stops or health fails.
A user LaunchAgent depends on that user's GUI domain; an SSH-only session does not
create it. Do not bootstrap a second copy over an already-loaded service. Configure
TLS/proxy delivery separately before exposing the public origin.

These are server-changing bootstrap commands. [Deploy setup](../hosting/deployment.reference.md#configure-a-target)
subsequently discovers this existing service; it does not create it.

For systemd, containers, or managed hosts, configure the equivalent executable and
working directory. The process runs in the foreground and closes its live streams
and plugin resources on shutdown. The supervisor owns restart policy; the host
owns TLS, proxies, logs, and backups.

The project manifest anchors environment, plugins, Actions, and `db/`.
Selecting another Source root does not relocate those resources. Use a loopback
binding behind a colocated proxy or tunnel. Keep secrets and durable runtime state
in the backup policy, with appropriate filesystem access.

---

## Update a writable repository

For an existing macOS launchd service, [Server deployment](../hosting/deployment.reference.md)
coordinates shared-Git updates, backups, validation, restart, and recovery reports.
Use its setup and dry-run before the first managed update.

A live site that accepts Source mutations needs one stable checkout. Checkpoint
and push domain Source changes, back up `.env` and durable `db/`, then
fetch and merge or fast-forward the new revision. Install locked dependencies,
run `bun run check`, restart the supervised process, and check health and representative
application behavior.

Rollback with a corrective or revert commit merged into the live history so later
domain changes survive. Immutable release-directory switching fits read-only
Sources; it does not preserve a writable repository's continuity by itself.

---

## Next

Back to the [docs home](../index.md), or continue with
[Authoring](../authoring/index.md).
