mcproc 0.0.0-stage → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -2
- package/dist/cli.js +13579 -0
- package/dist/cli.js.map +1 -0
- package/docs/hosts.md +84 -0
- package/docs/incidents.md +39 -0
- package/docs/init.md +30 -0
- package/docs/logs.md +50 -0
- package/docs/manifest.md +83 -0
- package/docs/memory.md +57 -0
- package/docs/monit.md +40 -0
- package/docs/readiness.md +53 -0
- package/docs/reloads.md +67 -0
- package/docs/urls.md +56 -0
- package/docs/variables.md +34 -0
- package/docs/worktrees.md +47 -0
- package/package.json +76 -4
- package/runtime/package-lock.json +945 -0
- package/runtime/package.json +8 -0
package/docs/hosts.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Hosts
|
|
2
|
+
|
|
3
|
+
Apps run on the local machine by default. A host declared under `hosts:` is reached over ssh, `docker exec`, or both; the manifest is read where mcproc runs, and each app is driven where it is bound.
|
|
4
|
+
|
|
5
|
+
## Apps on several hosts
|
|
6
|
+
|
|
7
|
+
When part of a dev stack runs elsewhere, say watch builds on your machine and the dev server inside a
|
|
8
|
+
dev container, bind those apps to their host with `host:`:
|
|
9
|
+
|
|
10
|
+
```yaml
|
|
11
|
+
apps:
|
|
12
|
+
libs-watch:
|
|
13
|
+
description: watch build of the shared libraries
|
|
14
|
+
kind: build
|
|
15
|
+
run: npm run watch:libs
|
|
16
|
+
host: local
|
|
17
|
+
web:
|
|
18
|
+
description: dev server inside the dev container
|
|
19
|
+
run: npm run dev
|
|
20
|
+
host: devbox
|
|
21
|
+
hosts:
|
|
22
|
+
devbox:
|
|
23
|
+
ssh: dev@devbox.local
|
|
24
|
+
ssh_args: ["-p", "2222"]
|
|
25
|
+
cwd: /home/dev/project
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
A bound app always runs on its host. An app without `host:` runs wherever the command points:
|
|
29
|
+
`--host`, or local when there is none.
|
|
30
|
+
|
|
31
|
+
- Without `--host`, every command reaches each app at its own host. `ls` prints one table with a HOST
|
|
32
|
+
column (one probe per host, in parallel). `start`/`stop`/`restart`/`delete` of a profile or `all` go to
|
|
33
|
+
each app's host in dependency order, and a dependency on another host starts first, on that host.
|
|
34
|
+
`logs -f` streams them all, and `monit` shows them all on one screen.
|
|
35
|
+
- With `--host X`, a command reaches only the apps that run on X: the apps bound to X and the unbound
|
|
36
|
+
ones. Naming an app bound elsewhere is refused before anything runs.
|
|
37
|
+
- A host that does not answer never holds the others back. `ls` shows its apps as `unreachable` rows
|
|
38
|
+
with the reason. `monit` keeps their last known rows and names the host in the footer. `logs -f` prints
|
|
39
|
+
one note per app and picks the stream up where it was once the host is back, and a host that was down
|
|
40
|
+
at the start gets connected later. A change that fails on one host keeps the rows of what already
|
|
41
|
+
happened (`start` skips what comes after the failure). A readonly host refuses the whole change before
|
|
42
|
+
any host is touched, and the error names the apps that run there, dependencies included.
|
|
43
|
+
- ssh gives up connecting after 10 s (`-o ConnectTimeout=10`). A `ConnectTimeout` in `ssh_args` wins.
|
|
44
|
+
|
|
45
|
+
## What a host needs
|
|
46
|
+
|
|
47
|
+
`ps`, `ss` (or `/proc`) and `tar`. Following logs live uses only POSIX tools there (`sh`, `stat` or `wc`,
|
|
48
|
+
`tail`, `head`, `od`). pm2 comes from the host's login-shell PATH (Node 18 or newer)
|
|
49
|
+
or from `unpack`. mcproc itself is only installed where the agent runs; locally it ships its own
|
|
50
|
+
pm2. `doctor` tells you which pm2 a host would use and why, or why none is usable.
|
|
51
|
+
`curl` or `wget` for URL checks on the host (optional: without them a url is checked from this machine under `url_check: auto`, and reads `unchecked` under `url_check: host`), and `getent` to report where a url's name resolves there.
|
|
52
|
+
|
|
53
|
+
## How ports are found
|
|
54
|
+
|
|
55
|
+
`ls` runs one command on the host: `pm2 jlist`, `ps -eo pid,ppid` and `ss -ltnp` / `ss -lunp`
|
|
56
|
+
(falling back to `/proc/net/tcp` and `/proc/*/fd` when `ss` is missing or fails). pm2's pid plus its
|
|
57
|
+
descendants are matched to listening sockets, then compared with the declared ports:
|
|
58
|
+
`listening`, `declared-not-listening` (naming the process that holds the port, if any), `undeclared`.
|
|
59
|
+
Ports of processes owned by other users show without a pid, as the kernel reports them.
|
|
60
|
+
Ports published by Docker containers show the container name instead of a pid.
|
|
61
|
+
|
|
62
|
+
## Hosts without pm2: `unpack`
|
|
63
|
+
|
|
64
|
+
`unpack` makes a host self-sufficient without installing anything system-wide. mcproc does not
|
|
65
|
+
contain pm2: it ships pm2's lockfile and assembles the tree once on
|
|
66
|
+
your machine with `npm ci` (needs `npm`, `tar` and access to the npm registry the first time; cached
|
|
67
|
+
in `~/.cache/mcproc/pm2` afterwards), then copies it into `<cwd>/.mcproc/runtime` on that host (or
|
|
68
|
+
`--dir`, which must be a directory of its own: a `bin/` mcproc did not write is refused). When the
|
|
69
|
+
host has no Node 18 or newer it also brings an official Node build: downloaded once on your machine
|
|
70
|
+
from nodejs.org, checked against the release's `SHASUMS256.txt`, cached in `~/.cache/mcproc/node`,
|
|
71
|
+
then streamed to the host, which needs no internet access. It copies the manifest there too, but never
|
|
72
|
+
over a hand-written one unless `--force`. Running it again changes nothing while the versions match
|
|
73
|
+
(an unpacked Node stays at its version until you pass `--node-version` or `--force`); a newer pm2 or
|
|
74
|
+
Node lands next to the old one, and for a daemon that is already running mcproc prints the
|
|
75
|
+
`bin/pm2 update` command instead of running it.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
mcproc unpack --host stand # pm2 only, when the host has Node >= 18
|
|
79
|
+
mcproc unpack --host box --with-node always # bare images such as alpine
|
|
80
|
+
mcproc unpack --host stand --dir /opt/mcproc/runtime # then add hosts.stand.runtime: /opt/mcproc/runtime
|
|
81
|
+
```
|
|
82
|
+
Hosts without bash (alpine, busybox) need `shell: /bin/sh -c` in their `hosts:` entry. The official
|
|
83
|
+
Node builds are linked against the C++ runtime library, so a bare alpine needs `apk add libstdc++`
|
|
84
|
+
first (glibc distributions already have it); `unpack` checks this before downloading anything.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# New errors at a glance: `incidents`
|
|
2
|
+
|
|
3
|
+
`incidents` (MCP tool, and `mcproc incidents` on the CLI) returns only the errors that are new since they were last
|
|
4
|
+
taken, each with lines of context before and after, a stack trace folded in, plus crashes and restarts pm2 saw. Nothing
|
|
5
|
+
new is one short line, so an agent can poll it during long work with whatever its client offers (a loop, a cron, a
|
|
6
|
+
monitor), and read `logs` only for an incident worth a closer look. `mcproc incidents -f` keeps watching in a terminal.
|
|
7
|
+
|
|
8
|
+
A call takes what it returns: the pick-up point, kept in `.mcproc/incidents.json`, moves past those incidents for every
|
|
9
|
+
session, agent and CLI call on this machine, so nobody gets the same incident twice. `peek` (`--peek`) looks without
|
|
10
|
+
taking. An agent takes once it has dealt with what it was shown: the incidents screen in `mcproc monit` then shows those
|
|
11
|
+
incidents muted ([monit](monit.md)). A first call looks back 15 minutes (`since`). One poll reads at most 1 MB of new log
|
|
12
|
+
per file (the most recent part) and says how much it skipped.
|
|
13
|
+
|
|
14
|
+
An answer carries about 20,000 characters of incidents, newest first. The older ones past that are counted in `more`
|
|
15
|
+
and, on a take, taken with the rest; `logs` shows them. An incident's own line keeps up to 2000 characters, its context
|
|
16
|
+
lines and stack frames up to 300.
|
|
17
|
+
|
|
18
|
+
Clearing logs starts it over: `flush`, or `c` twice in `mcproc monit` (the logs of the running apps in view), empties
|
|
19
|
+
the log files and records when; nothing logged before the clear comes up again, and monit shows `cleared HH:MM` in the
|
|
20
|
+
log pane title.
|
|
21
|
+
|
|
22
|
+
What counts as an error: `level=error|fatal`, JSON `"level":"error"` or 50/60, `ERROR`/`FATAL` as a word, `SomeError:` /
|
|
23
|
+
`…Exception`, `Traceback`, `panic:`, `Unhandled…`, and Node's `E…` codes such as `EADDRINUSE`. A lowercase "error" in
|
|
24
|
+
a sentence does not count, and neither does a line merely being on stderr. Per app:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
apps:
|
|
28
|
+
api:
|
|
29
|
+
errors:
|
|
30
|
+
match: ['Request failed with status 5\d\d'] # more patterns (JavaScript regular expressions)
|
|
31
|
+
ignore: ['ECONNRESET.*healthcheck'] # never an incident
|
|
32
|
+
context: [3, 5] # lines before and after
|
|
33
|
+
stderr: true # every stderr line counts (default false)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`errors: false` turns it off for an app. Patterns are matched against the first 2000 characters of each line; a pattern
|
|
37
|
+
that repeats a repeat, like `(a+)+` or `(\w+\s*)*`, is refused when the manifest loads, because on a line that almost
|
|
38
|
+
matches it could run for minutes. Patterns that still take longer than 2 seconds on an app's log are stopped: that app
|
|
39
|
+
reports an error and keeps its place until they are fixed, the other apps are not held up.
|
package/docs/init.md
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# `init`: drafting the manifest
|
|
2
|
+
|
|
3
|
+
`mcproc init` writes `.mcproc/` with a manifest draft read from `package.json` scripts and workspaces, nx `project.json`, `turbo.json`, `ecosystem.json` and a `Procfile`. Every candidate comes with a `# source:` trailer saying where it was found. On a plain repository every candidate is commented out; a turbo repository with a persistent `dev` task gets its dev stack live.
|
|
4
|
+
|
|
5
|
+
## Turbo monorepos
|
|
6
|
+
|
|
7
|
+
On a turbo repo whose `turbo.json` has a persistent `dev` task, `init` drafts the dev stack live: each workspace package
|
|
8
|
+
with a `dev` script becomes an uncommented app named after the package, and together they form the `dev` profile, so
|
|
9
|
+
`start dev` runs them. Their placeholder description, `TODO: what this app does`, lets the manifest load as is until you
|
|
10
|
+
replace it. The root `dev` script, other persistent tasks (under a commented profile) and every other script stay commented.
|
|
11
|
+
|
|
12
|
+
- Libraries consumed from `dist/` (a `build` script plus `main`, `module`, `types` or an `exports` target under `dist/`)
|
|
13
|
+
share two apps instead of a `<pkg>-build` each: `libs`, one `turbo run build` over them (`kind: task`), and `libs-watch`,
|
|
14
|
+
`turbo watch build` once `libs` has completed (turbo 1 has no `turbo watch`, so there `libs` comes alone). A live app
|
|
15
|
+
that depends on such a library, directly or through other packages, gets
|
|
16
|
+
`depends_on: [{ app: libs, condition: completed, timeout: 600 }]`: the longest wait the manifest allows, since a cold
|
|
17
|
+
build of many libraries can outlast the default 60 s.
|
|
18
|
+
- A library live through its own `dev` watch is drafted as a `build` app and left out of `libs`, so its first build is not
|
|
19
|
+
awaited: the draft leaves a note on each app that uses it saying how to wait for it.
|
|
20
|
+
- Ports are also guessed for Next (3000), Vite (`server.port` in `vite.config.*`, or `preview.port` for `vite preview`,
|
|
21
|
+
as a literal or an env read's fallback, `process.env` or loadEnv's `env`, directly or through a constant of the config;
|
|
22
|
+
5173 or 4173 when the section sets no port, no guess when it sets one `init` cannot read) and, in `src/`, `PORT = N`,
|
|
23
|
+
`DEFAULT_PORT = N`, `PORT ?? N`, `PORT || N` or `.listen(N)`. The guess shows as `port?` in each candidate's
|
|
24
|
+
`# source:` trailer; a live app also declares it as `port:` (and `url:`), so check it with `ls` (a wrong guess shows
|
|
25
|
+
✗) and correct it by hand: `--adopt-ports` adds the real port but leaves the guess in place.
|
|
26
|
+
- `init --adopt-ports` (MCP: `adopt_ports`) drafts nothing and ignores `--force`: it needs an existing manifest and writes
|
|
27
|
+
the ports `ls` reports as undeclared for the running apps into it, comments and layout kept, then prints the diff. Ports
|
|
28
|
+
from 32768 up (the ephemeral range, almost always transient) and ports another app declares are reported, not adopted.
|
|
29
|
+
It refuses a manifest that marks the local host readonly, and a file that changed while the hosts were probed
|
|
30
|
+
(`ALREADY_EXISTS`: run it again).
|
package/docs/logs.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Logs
|
|
2
|
+
|
|
3
|
+
`logs` reads pm2's log files: one app's tail, several apps merged by time, or a live follow. pm2 stamps every line to the millisecond (`2026-10-12T21:00:00.123: …`).
|
|
4
|
+
|
|
5
|
+
## Since a point in time
|
|
6
|
+
|
|
7
|
+
After a restart, "what happened since" is one call over a profile instead of a tail per app. The MCP `logs` tool takes
|
|
8
|
+
`targets` (apps, profiles or `all`) and `since`: a span (`90s`, `2m`, `2h`, at most 24 h), a time
|
|
9
|
+
(`2026-10-14T07:30[:00[.000]]`), or `last`, everything since your previous `logs` call over those apps in this MCP
|
|
10
|
+
session (a first call gives the last 100 lines of each log file). A time without a zone is the host's local time; with
|
|
11
|
+
`Z` or `+02:00` it is converted with each host's own zone. Lines are picked by pm2's stamps, which are in each host's
|
|
12
|
+
local time, and ordered by the moment they were written: each host's UTC offset, read along with its clock, puts hosts
|
|
13
|
+
in different zones on one timeline (a host whose zone cannot be read is placed by its raw stamps, and a note says so).
|
|
14
|
+
Each host's clock may still drift: a host whose clock runs ahead or behind has its lines placed that much later or
|
|
15
|
+
earlier. Each file is read at most 1 MB back: when the window reaches further, `skipped` names the bytes not read.
|
|
16
|
+
`lines` caps the merged answer (the most recent kept) and `truncated` counts what was dropped; `targets` without
|
|
17
|
+
`since` gives the latest `lines` (default 100) across the apps.
|
|
18
|
+
On the command line, `mcproc logs base --since 2m` prints the merged lines and exits, and `-f --since 2m` then follows
|
|
19
|
+
from exactly where that read ended; an app that was not running yet is shown from the same cutoff once it starts. `last`
|
|
20
|
+
needs the session that keeps the marks, so the CLI refuses it.
|
|
21
|
+
|
|
22
|
+
## Live logs
|
|
23
|
+
|
|
24
|
+
`monit` and `logs -f` show a line moments after the app writes it. On this machine mcproc reads the
|
|
25
|
+
pm2 log files itself and the system tells it when they change, so no command runs per read. On a host
|
|
26
|
+
reached over ssh or docker, one connection stays open while you watch: a small `sh` loop there checks
|
|
27
|
+
the followed files four times a second and sends only what was added. If that connection drops, mcproc
|
|
28
|
+
reconnects and meanwhile reads on its own clock (monit every 2 s, `logs -f` every second).
|
|
29
|
+
|
|
30
|
+
The flat view of `monit` orders the lines of one host by their stamps, then by arrival; with apps on several
|
|
31
|
+
hosts, whose clocks may differ, by arrival.
|
|
32
|
+
|
|
33
|
+
## Colors
|
|
34
|
+
|
|
35
|
+
`mcproc logs` and `monit` show the colors your apps write, as on their own terminal. `logs` prints them only
|
|
36
|
+
to a terminal: `--color` (or `FORCE_COLOR`) forces them into a pipe, `--no-color` or `NO_COLOR` turns them
|
|
37
|
+
off. `--json`, the MCP `logs` tool and `monit --once` always give plain text. Other escapes an app writes
|
|
38
|
+
(cursor moves, window titles) are dropped.
|
|
39
|
+
|
|
40
|
+
Many tools stop coloring when their output is not a terminal, and under pm2 it never is. Give such an app
|
|
41
|
+
`FORCE_COLOR: 1` in its `env`. mcproc never sets it on its own, because it puts color codes into that app's
|
|
42
|
+
pm2 log files:
|
|
43
|
+
|
|
44
|
+
```yaml
|
|
45
|
+
apps:
|
|
46
|
+
web:
|
|
47
|
+
description: Next.js dev server
|
|
48
|
+
run: npm run dev
|
|
49
|
+
env: { FORCE_COLOR: 1 }
|
|
50
|
+
```
|
package/docs/manifest.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# The manifest
|
|
2
|
+
|
|
3
|
+
`.mcproc/mcproc.yaml` describes every launch of a project: what each app is, how it starts, where it runs and what it listens on. `init` drafts it ([init](init.md)); everything else reads it.
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
version: 1
|
|
7
|
+
project: demo # pm2 namespace and process-name prefix; default: directory name
|
|
8
|
+
vars: # per-checkout values, see [variables](variables.md)
|
|
9
|
+
slot: 0
|
|
10
|
+
apps:
|
|
11
|
+
libs:
|
|
12
|
+
description: one-shot build of the shared libraries into dist/
|
|
13
|
+
kind: task # runs once; ls shows done / failed (exit N)
|
|
14
|
+
run: npm run build:libs
|
|
15
|
+
api-build:
|
|
16
|
+
description: tsc watch build of the API into dist/
|
|
17
|
+
kind: build # build | server | worker | task (default server)
|
|
18
|
+
run: npx tsc -w -p apps/api
|
|
19
|
+
api:
|
|
20
|
+
description: HTTP API serving /health
|
|
21
|
+
run: node dist/apps/api/main.js
|
|
22
|
+
cwd: . # relative to the project root
|
|
23
|
+
env: { PORT: 3310 }
|
|
24
|
+
port: 3310 # declared listening port (or ports: [3310, 9230])
|
|
25
|
+
url: http://localhost:3310/ # optional: the address to open; ls/describe show it with the HTTP answer, monit shows it
|
|
26
|
+
memory: 600M # expected memory, see [memory](memory.md)
|
|
27
|
+
containers: [demo-db] # docker containers the app owns
|
|
28
|
+
watch: [dist/apps/api] # pm2 restarts the server when the build output changes
|
|
29
|
+
depends_on:
|
|
30
|
+
- { app: libs, condition: completed } # wait for the build to exit 0
|
|
31
|
+
- api-build # plain name = started (start order only)
|
|
32
|
+
profiles:
|
|
33
|
+
base: [libs, api-build, api] # "all" is built in
|
|
34
|
+
hosts: # "local" is implicit
|
|
35
|
+
stand:
|
|
36
|
+
ssh: stand # alias from ~/.ssh/config; no secrets live here
|
|
37
|
+
cwd: /srv/demo # project root on that host
|
|
38
|
+
prod:
|
|
39
|
+
ssh: prod
|
|
40
|
+
docker: { container: demo-app, user: node }
|
|
41
|
+
cwd: /home/node/demo
|
|
42
|
+
readonly: true # every mutating tool is refused here
|
|
43
|
+
```
|
|
44
|
+
`version` is the format of the file. `mcproc` names the mcproc that wrote it: `init` records its own version there
|
|
45
|
+
and nothing rewrites it later. `describe` shows it next to the running version, and a manifest from a newer mcproc
|
|
46
|
+
that fails to load says so in the error's hint.
|
|
47
|
+
|
|
48
|
+
Other app fields: `host` ([hosts](hosts.md)), `url_check` ([urls](urls.md)), `watch_delay`, `ignore_watch`, `autorestart`, `pm2:` (raw pm2 fields merged last).
|
|
49
|
+
Other host fields: `ssh_args`, `shell` (default `/bin/bash -lc`), `pm2` (binary or command), `pm2_home`,
|
|
50
|
+
`runtime`, `env` (merged into every app on that host). Names match `^[a-z0-9][a-z0-9._-]*$`.
|
|
51
|
+
|
|
52
|
+
The folder is created with a `.gitignore` containing `*`: it never enters your repository, so each
|
|
53
|
+
checkout keeps its own. `.mcproc/README.md` holds free-form notes that `describe` returns.
|
|
54
|
+
|
|
55
|
+
## Kinds
|
|
56
|
+
|
|
57
|
+
| kind | what it is | autorestart | `ls` shows after it exits (autorestart on: pm2's words; off: the words below) |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `server` | a process that listens (default) | on | `stopped`, `errored` |
|
|
60
|
+
| `worker` | a process without a port | on | `stopped`, `errored` |
|
|
61
|
+
| `build` | a long-running watch build | on | `stopped`, `errored` |
|
|
62
|
+
| `task` | runs once: a build, a migration, a seed | off | `done`, `failed (exit N)`, `stopped` |
|
|
63
|
+
|
|
64
|
+
Autorestart defaults to on for every kind except `task`, which defaults it off. `autorestart: false` on any kind
|
|
65
|
+
gives the same behaviour as a task, and `autorestart: true` on a task is refused. For an app with autorestart off,
|
|
66
|
+
the exit code is pm2's `exit_code`: 0 is `done`; no code, 130 or 143 (a stop) is `stopped`; any other code is
|
|
67
|
+
`failed (exit N)`. So a task that itself exits 130 or 143 reads `stopped`. A manual stop reads `stopped` because
|
|
68
|
+
mcproc puts `trap 'exit 130' INT; trap 'exit 143' TERM;` in front of the command of an app with autorestart off,
|
|
69
|
+
since pm2 itself records exit code 0 for a process killed by a signal. Two edges: a `run` that starts with `exec …`
|
|
70
|
+
replaces the shell and loses the trap, so a manual stop of it reads `done`; a task whose process tree takes longer
|
|
71
|
+
than pm2's kill timeout (1.6 s by default) to exit after SIGINT, whether it ignores the signal or only cleans up
|
|
72
|
+
slowly, is killed with SIGKILL and also reads `done`. The host shell must be POSIX (`bash` or `sh`): the default shell and the
|
|
73
|
+
documented `/bin/sh -c` both are. pm2 keeps no exit time, so UPTIME stays `-`. `start` on a done or failed task runs
|
|
74
|
+
it again. `init` drafts `build`, `migrate`, `seed` and `once` scripts as tasks and watch-style commands as `build`.
|
|
75
|
+
|
|
76
|
+
## Process names
|
|
77
|
+
|
|
78
|
+
In pm2 the processes are named `<project>-<app>` (`demo-api`, `demo-api-build`) and grouped in the
|
|
79
|
+
`<project>` namespace; mcproc itself always takes and shows the short app name. The prefix is not
|
|
80
|
+
cosmetic: pm2 matches processes by name only, ignoring namespaces, so with bare names a second
|
|
81
|
+
project's `api` would restart the first project's `api` with its own settings. For the same reason
|
|
82
|
+
`start` and `restart` refuse with `NAME_COLLISION` when the name already belongs to another namespace
|
|
83
|
+
or to another checkout that declares the same `project`; give that checkout its own `project:`.
|
package/docs/memory.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Memory: budgets and the lease queue
|
|
2
|
+
|
|
3
|
+
Several stands on one machine can starve it, so a `start` asks for memory first. The manifest declares what an app needs:
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
resources:
|
|
7
|
+
reserve: 2G # what the host keeps for everything else (default 2G; also a percentage, 15%)
|
|
8
|
+
margin: 10% # added to every estimate (default 10%)
|
|
9
|
+
settle: 20 # seconds a fresh start still counts as ramping up (default 20)
|
|
10
|
+
apps:
|
|
11
|
+
api:
|
|
12
|
+
memory: 1.5G # what the app is expected to take
|
|
13
|
+
containers: [demo-db] # docker containers it owns: removed with it, counted in its memory
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- **The estimate**, per app: the larger of the declared `memory:` and what a running sibling slot of the same app
|
|
17
|
+
measures (its process tree plus its declared containers; the largest among the siblings). The lease's need is
|
|
18
|
+
`ceil(estimate × (1 + margin))`.
|
|
19
|
+
- **Admission.** The start runs at once when `MemAvailable − reserve − ramping` covers the need and nothing is pending
|
|
20
|
+
ahead of it. `ramping` is the need of running leases admitted within `settle` seconds, or whose starter is still alive.
|
|
21
|
+
Otherwise the answer is `queued #N`, and the queue is strict FIFO: a small start never jumps a large one. A stand that
|
|
22
|
+
could not fit even on an idle host is refused with `NEVER_FITS`.
|
|
23
|
+
- **The watcher** is a pm2 process, `mcproc-watcher` in the `mcproc` namespace. It judges the head every 2 s and at once
|
|
24
|
+
when the queue changes, exits after a minute idle, and the next queued start launches it again. While the head waits,
|
|
25
|
+
each tick reads the memory alone; its siblings are measured again when that reading lets it fit, when the queue
|
|
26
|
+
changes, and every 30 s. Each full judgement budgets the head again (apps that came up meanwhile drop out of its need),
|
|
27
|
+
and a queued start keeps the `--host` it was given. It ends leases whose owner is gone (`checkout gone`, `requester gone`) and an admitted exec
|
|
28
|
+
that has not run within 60 s (`the exec did not take its turn`). `stop` and `delete` cancel the starts they overlap
|
|
29
|
+
before stopping, and free the running leases afterwards.
|
|
30
|
+
- `docker stats` costs about 2 s per call, so the containers' memory is asked only by `ls` (its MEM column includes it for
|
|
31
|
+
apps that declare `containers:`), `describe` and the sibling measurement; never by monit's loop or by `wait`.
|
|
32
|
+
- **Looking and cancelling.** `mcproc leases` (tool `leases`) shows the host's queue: who runs, who waits and why, the
|
|
33
|
+
memory reading and the watcher. `leases cancel <id>` cancels a pending lease or a starting exec; a starting start is
|
|
34
|
+
refused: stop its target instead. `ls` shows a queued app as `queued #N`, `wait` rides through the queue, `doctor`
|
|
35
|
+
reports the state directory and the watcher. `start --note <text>` keeps a reason on the lease.
|
|
36
|
+
- **State** lives in `~/.local/state/mcproc` (or `$XDG_STATE_HOME/mcproc`, or `MCPROC_STATE_DIR`): `leases/`, `slots/`,
|
|
37
|
+
`watcher.json` and `watcher.ecosystem.json`. A dev checkout runs the watcher from the bundle `MCPROC_CLI_ENTRY` names.
|
|
38
|
+
|
|
39
|
+
## Exec: one-off work under a lease
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
mcproc exec --memory 4G --note "e2e run" -- npx vitest run # --timeout <s> bounds the wait in the queue (default 600, max 3600)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`exec` runs a command from the project root with the manifest's `exec.env` (templates allowed, so it can point at this
|
|
46
|
+
checkout's ports) and `MCPROC_PROJECT` set. With `--memory` it is a lease like a start: it runs at once when the host has
|
|
47
|
+
room, else waits its turn, and frees the lease when the command exits. Without it, it runs at once. The MCP tool returns
|
|
48
|
+
the exit code and the last 200 lines of output, and is marked destructive: it runs any local argv. It reads that output
|
|
49
|
+
through pipes: a process the command leaves behind on them (a server sent to the background) loses them one second after
|
|
50
|
+
the command exits (its next write gets EPIPE), so such a process should detach its own stdio.
|
|
51
|
+
|
|
52
|
+
## Limits
|
|
53
|
+
|
|
54
|
+
- Leases are local: `exec` and the worktree commands run on this machine only (a `host` other than `local` is refused), and a `start` of an app bound to a remote host that
|
|
55
|
+
declares `memory:` is refused (`LEASE_UNSUPPORTED_HOST`). That makes monit's `R` key (`start all --reload`) refuse such
|
|
56
|
+
a manifest.
|
|
57
|
+
- Without `/proc/meminfo` (not Linux) there is no memory reading, so nothing is checked.
|
package/docs/monit.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# `mcproc monit`
|
|
2
|
+
|
|
3
|
+
A terminal dashboard on the same data as `ls` and `logs`: a table of apps with real memory and CPU (summed over each
|
|
4
|
+
app's process tree, which is what `bun run dev` or `next dev` hide behind their launcher), the ports with ✓/✗, the
|
|
5
|
+
runtime command, a live log tail with history, and the selected app's details. `mcproc monit --once` prints one frame
|
|
6
|
+
as text, which is what an agent or a script reads.
|
|
7
|
+
|
|
8
|
+
## Keys
|
|
9
|
+
|
|
10
|
+
| key | what it does |
|
|
11
|
+
|---|---|
|
|
12
|
+
| `j` / `k`, Tab | select an app (off in the incidents view) |
|
|
13
|
+
| `↑` / `↓`, PageUp / PageDown, the mouse wheel | scroll the log pane; End returns to the tail |
|
|
14
|
+
| `o` / `e` / `b` | show stdout, stderr or both |
|
|
15
|
+
| `r` / `s` / `x` | restart, start or stop the selected app (dashboard only) |
|
|
16
|
+
| `c` twice | clear the logs of the running apps in view; the pane title then shows `cleared HH:MM`, and `incidents` starts over there |
|
|
17
|
+
| `R` twice | reload every app in view the way `mcproc start all --reload` does: in dependency order with their `depends_on` waits, running apps restart, stopped ones start, `kind: task` apps run again. A view with an app on a readonly host is refused whole; narrow it with `h` |
|
|
18
|
+
| `f` | the flat view: every app's logs merged into one labeled stream, `app \| line` and `app ! line` for stderr, one color per app, like `mcproc logs -f`; `f` again returns |
|
|
19
|
+
| `i` | the incidents of the apps in view: errors, crashes and restarts with their context, as the `incidents` tool shows them, from the last 15 minutes or the last clear, then new ones as they happen; `i` again returns |
|
|
20
|
+
| `t` | on the incidents screen, hide or show the incidents an agent has taken |
|
|
21
|
+
| `h` | cycle hosts, from `all` to each host, narrowing the screen the way `--host` narrows a command |
|
|
22
|
+
| `q` | quit |
|
|
23
|
+
|
|
24
|
+
The incidents screen is for you: it reads from its own position and never takes anything, so the agent's next
|
|
25
|
+
`incidents` call returns exactly what it would have ([incidents](incidents.md)). What an agent has taken is shown in
|
|
26
|
+
muted colors and marked `taken`, and the title counts it. An incident counts as taken when its time stamp is at or
|
|
27
|
+
before the last line a take read in that file.
|
|
28
|
+
|
|
29
|
+
## Several hosts
|
|
30
|
+
|
|
31
|
+
With host-bound apps the screen opens on every app at its own host, with a HOST column, and reads each app's logs
|
|
32
|
+
where it runs. A host that does not answer keeps its last known rows, and the footer names it ([hosts](hosts.md)).
|
|
33
|
+
|
|
34
|
+
## What the screen shows
|
|
35
|
+
|
|
36
|
+
Memory is the sum of each process's resident set, so shared pages count once per process; pm2's own root-pid figure
|
|
37
|
+
is shown next to it in the details. CPU is per core over the refresh interval. Long log lines wrap with indented
|
|
38
|
+
continuations. The selected app's detail line counts its in-process reloads since its pm2 start
|
|
39
|
+
([reloads](reloads.md)). The screen shows an app's URL but never requests it, so your dev servers' request logs
|
|
40
|
+
stay yours ([urls](urls.md)). How lines reach the screen: [live logs](logs.md#live-logs).
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Readiness: `wait` and `depends_on` conditions
|
|
2
|
+
|
|
3
|
+
`wait` (MCP tool, `mcproc wait <app> --until <cond> [-t 60]` on the CLI) blocks until an app is usable or finished,
|
|
4
|
+
instead of sleeping and polling:
|
|
5
|
+
|
|
6
|
+
| `until` | met when |
|
|
7
|
+
|---|---|
|
|
8
|
+
| `listening` | every declared port is bound (needs `port`/`ports`) |
|
|
9
|
+
| `url-ok` | the `url` answers 2xx/3xx, checked as `ls` checks it (on the host, and from this machine when the host gets no answer; see [urls](urls.md)) |
|
|
10
|
+
| `exited` | the process left `online`; with autorestart off the answer carries `exit_code` and `done` / `failed (exit N)` / `stopped`, otherwise pm2's `stopped` or `errored` |
|
|
11
|
+
| `log:<regex>` | a new line of its out or err log matches (ANSI stripped, matched against the line without pm2's time stamp; the answer keeps the stamp; history is not searched) |
|
|
12
|
+
| `idle:<seconds>` | no log output for that long: a watcher settled |
|
|
13
|
+
| `reloaded` | the app's next in-process reload (see [reloads](reloads.md)) has been quiet for 2 s; the answer carries its time, its line count and its first restart or rebuild line, else its first line |
|
|
14
|
+
|
|
15
|
+
Without `curl` or `wget` on the host, `url-ok` under `url_check: auto` asks from this machine; under `url_check: host` it runs to the timeout and reports `unchecked`.
|
|
16
|
+
`log:`, `idle:` and `reloaded` see only output written after the call begins, and notice a dead process only every 5 s; for
|
|
17
|
+
"is it up" prefer `listening` or `url-ok`. `wait --until exited` succeeds (exit code 0) once the process has exited,
|
|
18
|
+
whatever its exit code; scripts read `exit_code` in `--json` or the `(exit N)` in the line. A wait on an app that
|
|
19
|
+
was never started is a `NOT_STARTED` error, not a result. `listening` needs a declared port, `url-ok` a declared
|
|
20
|
+
url and `reloaded` an app without `reloads: false`, else `BAD_ARGUMENT`. `exited` on an app that has already stopped answers at once.
|
|
21
|
+
|
|
22
|
+
After a `restart`, and for an app that pm2 itself restarts on a change (`watch:`), wait until `listening`. An app that
|
|
23
|
+
reloads in place (a watcher: tsx watch, nodemon, `node --watch`, Vite, `next dev`) keeps its process and its port, so
|
|
24
|
+
`listening` answers at once while the old code may still run: after an edit, follow it with `wait until: reloaded`.
|
|
25
|
+
|
|
26
|
+
The answer is one line: `web: listening after 1.3 s (:3000 ✓)`, `libs: done after 12.4 s (exit 0)`. On timeout
|
|
27
|
+
(default 60 s, max 600) it says what was last seen (`web: listening not reached after 60.0 s (status online, :3000 ✗
|
|
28
|
+
nothing listens)`), and `ok` is false. If the process dies meanwhile, the wait ends at once with its reason and
|
|
29
|
+
status and no observation, for example `web: exited (stopped) after 3.2 s while waiting for listening` (`observed`
|
|
30
|
+
is in `--json`). Either way it is a result, not an error, and the CLI exits 1. Local apps are checked twice a second, remote ones once; `log:`/`idle:`/`reloaded` read the log as it grows. A process that pm2 is
|
|
31
|
+
restarting (a watch restart, a restart delay) is not dead: the wait goes on, bounded by the timeout. `listening` and
|
|
32
|
+
`url-ok` count only while the process is online or launching; a restart in progress is waited out, and a port or url that
|
|
33
|
+
still answers after the process died is not ready. Claude
|
|
34
|
+
Code does not cut a long MCP call, but moves one into the background after two minutes. A cancelled MCP `wait` (or
|
|
35
|
+
`start`) call stops the polling at once.
|
|
36
|
+
|
|
37
|
+
`depends_on` entries can name what they wait for:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
depends_on:
|
|
41
|
+
- logger # a name: start order only
|
|
42
|
+
- { app: libs, condition: completed } # the task exited with code 0
|
|
43
|
+
- { app: api, condition: ready, timeout: 90 } # ports listening, or the url answers; default 60 s, max 600
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`completed` needs a `kind: task` dependency; `ready` one with a port or a url (ports are checked when declared, the
|
|
47
|
+
url otherwise). `start` launches dependencies first and waits at each such edge, on the dependency's
|
|
48
|
+
host, also when the dependent runs elsewhere. A done task is run again, a running one is waited on. When a dependency
|
|
49
|
+
fails, times out or dies, the dependent is not started: its row reads `skipped` with the reason, and so do the apps
|
|
50
|
+
that depend on it; unrelated apps still start. The answer adds `waited 4.2 s for libs (completed)` for each wait that was met (a failed
|
|
51
|
+
wait shows in the rows), `start`'s JSON carries the waits as `waits[]`, and the CLI exits 1 when anything was skipped. With `--host X`, dependencies that run elsewhere are neither started nor waited for.
|
|
52
|
+
`restart` does not order apps by their dependencies or wait for conditions; `start` does, and `start --reload`
|
|
53
|
+
restarts the apps that are already online the same way.
|
package/docs/reloads.md
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Reloads
|
|
2
|
+
|
|
3
|
+
Dev tools in watch mode reload after an edit inside their own process. pm2 sees none of it: RESTARTS stays 0, and a
|
|
4
|
+
port that never closed says nothing about which code answers. mcproc reads these reloads from the app's log. A
|
|
5
|
+
reload is one of these lines, matched without pm2's stamp and with ANSI stripped:
|
|
6
|
+
|
|
7
|
+
- a restart: tsx's `[tsx] … Restarting...`, nodemon's `restarting due to changes`, `node --watch`'s `Restarting '…'`;
|
|
8
|
+
- a rebuild: Vite's `[vite] page reload` or `hmr update`, Next's `✓ Compiled in …` (with a page path, `✓ Compiled /page
|
|
9
|
+
in …`, it is that page's first compile and does not count) and `Reload env:`, tsc's `File change detected. Starting
|
|
10
|
+
incremental compilation`;
|
|
11
|
+
- a repeated `listening on` or `listening at` line, for watchers that print nothing else (`bun --watch`).
|
|
12
|
+
|
|
13
|
+
Marker lines less than 2 s apart, stdout and stderr together, are one reload: a library rebuild that prints a line per
|
|
14
|
+
changed file counts once, and so does a restart followed by the server's new `listening` line. The first burst after
|
|
15
|
+
pm2 starts the process, when it holds only `listening` lines, is the boot, not a reload. A restart less than 2 s after
|
|
16
|
+
the boot's `listening` line joins the boot's burst: it counts once, and is shown by the restart line. Counting starts at
|
|
17
|
+
pm2's start of the process: lines an earlier run left in the same log file never count.
|
|
18
|
+
|
|
19
|
+
Per app:
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
apps:
|
|
23
|
+
api:
|
|
24
|
+
description: API under a home-grown file watcher
|
|
25
|
+
run: node scripts/dev.js
|
|
26
|
+
reloads:
|
|
27
|
+
match: ['^\[devwatch\] reloaded'] # more markers (JavaScript regular expressions)
|
|
28
|
+
ignore: ['listening on queue'] # never a reload
|
|
29
|
+
defaults: true # false: only your own patterns, no built-in markers
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`reloads: false` turns it off for an app. Own patterns follow the rules of `errors` patterns ([incidents](incidents.md)): matched against the first
|
|
33
|
+
2000 characters of each line, a repeat of a repeat refused when the manifest loads, and stopped when they take longer
|
|
34
|
+
than 2 seconds on an app's log, which leaves that app's reloads uncounted with an error while the other apps go on.
|
|
35
|
+
|
|
36
|
+
Four places show them:
|
|
37
|
+
|
|
38
|
+
- `wait until: reloaded` (`mcproc wait web --until reloaded`) answers once the app's next reload has been quiet for 2 s,
|
|
39
|
+
with its time, its line count and its first restart or rebuild line, else its first line:
|
|
40
|
+
`web: reloaded after 3.0 s (3 lines from 2026-10-14T12:00:01.000): [vite] (client) page reload /src/a.ts`. A server
|
|
41
|
+
whose reload restarts it may print its new `listening` line more than 2 s later, after the reload was answered: wait
|
|
42
|
+
until `listening` next. A wait that times out or is aborted while a reload is still under way says so, with `ok`
|
|
43
|
+
false: `reload in progress since 2026-10-14T12:00:59.000 (2 lines): [tsx] change in src/a.ts Restarting...`.
|
|
44
|
+
- `incidents` with `include_reloads: true` (`mcproc incidents --reloads`) adds one `reloaded` incident per app with
|
|
45
|
+
the reloads since the last call: how many, the first and the last one's time, and the last one's first restart or
|
|
46
|
+
rebuild line, else its first line.
|
|
47
|
+
- `ls` with `reloads: true` (`mcproc ls --reloads`) adds each running app's reloads since its pm2 start to RESTARTS,
|
|
48
|
+
`0 (+7)`, and to each JSON row as `reloads: { count, last, partial }`. It reads each host's logs once more, so a plain
|
|
49
|
+
`ls` does not count them.
|
|
50
|
+
- `monit` shows them in the selected app's detail line while it runs (online or launching), `restarts 0 (+7 reloads,
|
|
51
|
+
last 12:01:05)`, counted for the apps whose logs it follows: the selected one on the dashboard, every running one in
|
|
52
|
+
the flat view. `monit --once` counts from the history it reads.
|
|
53
|
+
|
|
54
|
+
Limits. A count marked `≥` (`0 (+≥7)`, `+≥7 reloads`) is a lower bound: the log was cleared since the process started,
|
|
55
|
+
or the app wrote more than the last 1 MB that is read. monit and `monit --once` count from the history they read, the
|
|
56
|
+
last `--lines` lines of each log (200 by default), so an app that has run for a while often shows `+≥7` there where
|
|
57
|
+
`ls --reloads` shows an exact `+7`. An err log whose first line came more than a minute after the start also marks
|
|
58
|
+
monit's count `≥`: monit cannot tell where in the file its history began. A reload whose lines are split between two
|
|
59
|
+
`incidents` calls is reported in both. With `include_reloads`, an app whose own `reloads` patterns run away also has
|
|
60
|
+
its error incidents held back, and its point stays where it was, until the patterns are fixed. The process start is
|
|
61
|
+
pm2's start time read in the host's current time zone, so a daylight saving change since the start, or a pm2 daemon
|
|
62
|
+
started under a different `TZ`, can shift it, and with it which lines count.
|
|
63
|
+
|
|
64
|
+
A process that runs several servers (a turbo or concurrently dev task, `docker compose up`) is one app, and only its
|
|
65
|
+
first burst of `listening` lines is the boot: a later server whose first `listening` line comes more than 2 s after the
|
|
66
|
+
one before it counts as a reload. Put that server's line in the app's `reloads.ignore`, for example
|
|
67
|
+
`ignore: ['ws listening on']`.
|
package/docs/urls.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# URLs
|
|
2
|
+
|
|
3
|
+
An app may declare `url`, the address you open in a browser; `init` drafts `http://localhost:<port>`
|
|
4
|
+
for every server it finds a port for. `ls`, `describe`, `monit --once`, `wait until: url-ok` and a `ready`
|
|
5
|
+
dependency on a url all check it the same way and report `ok` (2xx/3xx), `error` (4xx/5xx: up but refusing),
|
|
6
|
+
`down` (nothing answered) or `unchecked` (not requested, or `url_check: host` on a host with neither curl nor
|
|
7
|
+
wget). It is a liveness check, not a trust check: self-signed development certificates are accepted, and a
|
|
8
|
+
redirect is an answer (it is not followed, except by `wget` on a host without curl).
|
|
9
|
+
The interactive `monit` shows the URL but never requests it, so your dev servers' request logs stay yours.
|
|
10
|
+
|
|
11
|
+
Where it is requested from is set per app with `url_check`:
|
|
12
|
+
|
|
13
|
+
- `auto` (the default): on the app's host first (`curl`, or `wget` when there is no curl). An HTTP answer
|
|
14
|
+
there is the result. When the host gets none, mcproc requests the url from this machine, where mcproc
|
|
15
|
+
and usually your browser run, so a dev container whose name for the site points at itself, or whose
|
|
16
|
+
`localhost` is not where the port is published, still reads `✓ 200 (here)`. When neither answers it is `down`.
|
|
17
|
+
- `host`: only on the app's host.
|
|
18
|
+
- `here`: only from this machine. `ls`, `wait` and `ready` never request it on the host; `doctor` may, to show
|
|
19
|
+
where it answers.
|
|
20
|
+
|
|
21
|
+
On the `local` host this machine is the host: the url is requested once, whatever `url_check` says. A url the
|
|
22
|
+
host answers costs one request, on the host; the second request is made only for a url the host could not reach.
|
|
23
|
+
On the host each url's name lookup is bounded at 2 s, its request at 2 s (3 s with `wget`), and the host's
|
|
24
|
+
environment lookups at about 4 s (where it has `timeout`), so a host whose resolver drops packets makes `ls` and
|
|
25
|
+
each poll of `wait until: url-ok` that much slower. The probe's time budget grows with the number of urls it checks
|
|
26
|
+
to match (30 s, plus 4 s and 5 s per url), up to 120 s.
|
|
27
|
+
|
|
28
|
+
One catch of `auto`: a `localhost` (or loopback-address) url of an app on a remote host can read `✓ 200 (here)`
|
|
29
|
+
when this machine happens to serve the same port, even while that app is down. `wait` and a `ready` dependency
|
|
30
|
+
still require the app's process to be online; `url_check: host` rules the catch out.
|
|
31
|
+
|
|
32
|
+
`ls` marks an answer from this machine with `(here)`, and a `down` url with the short reason of the last place
|
|
33
|
+
tried: `refused`, `not resolved`, `timeout`, `tls`, `reset` or `no answer`. The JSON rows carry `url_from` (`host`,
|
|
34
|
+
`here`, or null when unchecked) and `url_reason` (why it is down, with both sides when neither answered, as in
|
|
35
|
+
`on box: refused; here: refused`, or why it was taken from this machine), which `describe` prints too.
|
|
36
|
+
|
|
37
|
+
When the url's name resolves on the host to the host itself (for instance through a hostname line in its
|
|
38
|
+
`/etc/hosts`), that is the reason only when the url answered from this machine:
|
|
39
|
+
`on box site.test resolves to 127.0.1.1, the host itself`, the dev container whose name for the site means another
|
|
40
|
+
machine. Otherwise the host's own failure stays the reason and the address follows as a note, so a box whose own
|
|
41
|
+
name resolves to itself and whose app is down reads `✗ down (refused)`, with the `url_reason`
|
|
42
|
+
`on box: refused (devbox resolves to 127.0.1.1, the host itself)`.
|
|
43
|
+
|
|
44
|
+
`doctor` shows each host's environment (container or not, hostname) and where each of its apps' urls answers, and
|
|
45
|
+
suggests `url_check: auto` for an app with `url_check: host` whose url answers only from here.
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
apps:
|
|
49
|
+
web:
|
|
50
|
+
description: dev server in the container
|
|
51
|
+
run: yarn dev
|
|
52
|
+
port: 3000
|
|
53
|
+
url: https://site.test/
|
|
54
|
+
url_check: here # checked only from this machine: ls, wait and ready never request it in the container
|
|
55
|
+
host: box
|
|
56
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Variables and the per-checkout override
|
|
2
|
+
|
|
3
|
+
Ports, container names and paths that differ per checkout are variables, declared once under `vars:` with a default:
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
vars:
|
|
7
|
+
slot: 0 # the main checkout; a worktree gets its own (see [worktrees](worktrees.md))
|
|
8
|
+
web_port: '{{ 5181 + slot * 100 }}' # a default may use the vars before it
|
|
9
|
+
apps:
|
|
10
|
+
web:
|
|
11
|
+
description: dev server
|
|
12
|
+
run: npm run dev -- --port {{ web_port }}
|
|
13
|
+
port: '{{ web_port }}' # port and ports are templates only as quoted strings
|
|
14
|
+
db:
|
|
15
|
+
description: postgres for the stand
|
|
16
|
+
run: docker run --rm --name {{ project }}-db -p {{ 5432 + slot }}:5432 postgres
|
|
17
|
+
containers: [ '{{ project }}-db' ]
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `{{ expr }}` takes names, integers, `+ - * / %` and parentheses; division truncates. `\{{` writes a literal `{{`,
|
|
21
|
+
which a docker `--format '{{.Names}}'` in a `run` line needs. Any other `{{` in a string under `apps`, `hosts`, `worktree` or `exec` is
|
|
22
|
+
refused when the manifest loads (`BAD_TEMPLATE`, naming the field: `apps.ps.run: unexpected "." in ".Names"`). The syntax is `{{ }}` because bash, Caddy and compose own
|
|
23
|
+
the other ones.
|
|
24
|
+
- The built-ins are `project` (the pm2 namespace of this checkout) and `root` (its project root).
|
|
25
|
+
- Templates work in every string field under `apps`, `hosts`, `worktree` and `exec.env`. They do not work in `project`, in
|
|
26
|
+
the names of apps, profiles and hosts, or in the `vars` names.
|
|
27
|
+
- `.mcproc/mcproc.local.yaml` is the checkout's own override (it lives in the gitignored folder). It holds `project` (the
|
|
28
|
+
slug this checkout runs under), `repo` (the main checkout, for a worktree) and `vars` (values that win over the
|
|
29
|
+
defaults). A var the manifest does not declare is refused. In a main checkout it may also hold `worktree` (`copy`,
|
|
30
|
+
`link`): machine-local extras for every new worktree (see [worktrees](worktrees.md)).
|
|
31
|
+
- `describe` without an app prints the resolved vars, where each value came from, and the override's path.
|
|
32
|
+
|
|
33
|
+
The slot idiom: the manifest says `slot: 0` and writes ports as `'{{ 5181 + slot * 100 }}'` and containers as
|
|
34
|
+
`{{ project }}-db`. The main checkout keeps 5181 and `demo-db`; a worktree at slot 2 gets 5381 and `demo-w2-db`.
|