landfall 0.2.0 → 0.3.0

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/README.md CHANGED
@@ -4,14 +4,15 @@ Set up Linux servers and deploy sites to them behind [Traefik](https://traefik.i
4
4
 
5
5
  ```sh
6
6
  npm i -g landfall
7
- lf setup het --host 203.0.113.7 # fresh Ubuntu/Debian box → Docker, firewall, Traefik, ssh key
8
- cd my-site && lf deploy -s het # live at https://my-site.203-0-113-7.sslip.io
7
+ lf setup vps --host 203.0.113.7 # fresh Ubuntu/Debian box → Docker, firewall, Traefik, ssh key
8
+ cd my-site && lf deploy -s vps # live at https://my-site.203-0-113-7.sslip.io
9
9
  ```
10
10
 
11
- - **Servers are ssh targets.** Any `~/.ssh/config` alias works. If `ssh het` works, `lf deploy -s het` works.
12
- - **No agent on the server.** Everything is plain Docker Compose under `/opt/landfall`, so you can read and fix it by hand.
11
+ - **Servers are ssh targets.** Any `~/.ssh/config` alias works. If `ssh vps` works, `lf deploy -s vps` works.
12
+ - **No agent on the server.** Every site is a plain Docker Compose project under `/opt/landfall`, so you can read, run and fix it by hand.
13
+ - **Your compose.yaml is the site.** App, database, worker: `docker compose up` runs it locally, `lf deploy` runs the same file behind HTTPS.
13
14
  - **Every change is journaled** on the server and can be undone with `lf undo`.
14
- - **Three ways in:** plain commands for scripts, CI and agents; a web dashboard (`lf`); a terminal dashboard (`lf tui`).
15
+ - **Three ways in:** plain commands for scripts, CI and agents; a web dashboard (`lf ui`); a terminal dashboard (`lf tui`).
15
16
 
16
17
  `lf` is short for `landfall`. Both commands are installed.
17
18
 
@@ -19,8 +20,6 @@ cd my-site && lf deploy -s het # live at https://my-site.203-0-113-7.sslip.
19
20
 
20
21
  - [Install](#install)
21
22
  - [Concepts](#concepts)
22
- - [Using landfall from scripts and agents](#using-landfall-from-scripts-and-agents)
23
- - [Command reference](#command-reference)
24
23
  - [Prepare a server](#prepare-a-server)
25
24
  - [Deploy a site](#deploy-a-site)
26
25
  - [Day-to-day](#day-to-day)
@@ -30,6 +29,8 @@ cd my-site && lf deploy -s het # live at https://my-site.203-0-113-7.sslip.
30
29
  - [History and undo](#history-and-undo)
31
30
  - [Managing servers](#managing-servers)
32
31
  - [Dashboards](#dashboards)
32
+ - [Using landfall from scripts and agents](#using-landfall-from-scripts-and-agents)
33
+ - [Command reference](#command-reference)
33
34
  - [Files and config](#files-and-config)
34
35
  - [Troubleshooting](#troubleshooting)
35
36
  - [Development](#development)
@@ -50,9 +51,9 @@ lf <command> --help # every option of one command
50
51
  | term | meaning |
51
52
  |---|---|
52
53
  | **server** | An ssh target: a `~/.ssh/config` alias, or one landfall registered with `lf server add`/`lf setup`. Pass it as `-s <name>`. The first server you add becomes the default. |
53
- | **site** | One deployed thing on a server: a static folder, a Dockerfile app or a prebuilt image. It runs as container `landfall-<name>` and Traefik routes its domains to it. |
54
+ | **site** | One Compose project on a server: your `compose.yaml`, or one landfall generates for a static folder, a Dockerfile or a prebuilt image. Services with `x-landfall: { port }` are routed through Traefik. |
54
55
  | **project file** | `./.landfall.json`, written by the first `lf deploy`: `{ "name", "server", "domains" }`. Commands that take `[name]` default to this site, and `-s` defaults to its server. |
55
- | **main domain** | Optional per-server domain (`lf server domain het example.com`). Sites then default to `<site>.example.com`, and a bare name like `shop` means `shop.example.com`. Without one, sites get `<site>.<ip-with-dashes>.sslip.io` (e.g. `blog.203-0-113-7.sslip.io`), which resolves without any DNS setup. |
56
+ | **main domain** | Optional per-server domain (`lf server domain vps example.com`). Sites then default to `<site>.example.com`, and a bare name like `shop` means `shop.example.com`. Without one, sites get `<site>.<ip-with-dashes>.sslip.io` (e.g. `blog.203-0-113-7.sslip.io`), which resolves without any DNS setup. |
56
57
  | **release** | Every deploy creates one, with an id like `20260122143005`. The last 5 are kept. `lf rollback` switches between them. |
57
58
  | **run** | One landfall invocation that changed something on a server. Runs are listed by `lf history` and reverted by `lf undo`. |
58
59
 
@@ -61,113 +62,28 @@ How defaults resolve, for every command:
61
62
  - **site** `[name]`: the argument, else `name` from `./.landfall.json`.
62
63
  - **server** `-s`: the flag, else `server` from `./.landfall.json`, else the default server (`lf server use`).
63
64
 
64
- ## Using landfall from scripts and agents
65
-
66
- Every dashboard action is a plain command, and both dashboards print the command they run (`$ lf logs blog -s het -f`).
67
-
68
- - **No prompts:** `-y` accepts confirmations and takes defaults. Without a terminal, prompts are never shown. A
69
- command that would ask then **fails** and tells you to pass `-y`. It never silently goes ahead.
70
- - **Passwords:** pass them with `--password-stdin` or `$LANDFALL_PASSWORD` (`setup`, `server add`). For `lf auth`, use `-p`.
71
- - **JSON output:** `--json` works on `ls`, `info`, `server config`, `server domain`, `domains`, `firewall`,
72
- `updates` and `history`.
73
- - **Dry runs:** `lf setup <name> --check` changes nothing. `lf updates` only reports.
74
- - **Exit codes:** `0` means success, `1` means error (the message starts with `error:` on stderr), and `2` means unknown command.
75
- - **Safety rails:** `-y` never overrides a safety refusal, for example a firewall change that would lock out ssh, or
76
- setup steps that would break running services. Only `--force` does.
77
-
78
- A typical unattended flow:
79
-
80
- ```sh
81
- printf '%s' "$ROOT_PW" | lf setup box --host 203.0.113.7 --password-stdin -y
82
- lf server domain box example.com -y
83
- lf deploy ./dist -n blog -s box -y # → https://blog.example.com
84
- lf ls -s box --json
85
- lf history -s box --json
86
- lf undo -s box -y # revert the last run
87
- ```
88
-
89
- ## Command reference
90
-
91
- `[name]` is a site or server and is optional (see [Concepts](#concepts)). Most site and server commands take `-s <server>`. Every command takes `-y` and `--help`.
92
-
93
- **Servers**
94
-
95
- | command | what it does |
96
- |---|---|
97
- | `lf setup <name> [--host ip]` | Turns a box into a deploy target: ssh key, Docker, ufw, fail2ban, deploy user, ssh hardening and Traefik. Idempotent. |
98
- | `lf setup <name> --check` | Reports what is set up and what setup would change. Changes nothing. |
99
- | `lf setup <name> --key-only` | Only installs landfall's ssh key. |
100
- | `lf server add <name> [--host ip] [-u user] [-p port] [-i key] [--password-stdin] [--domain d] [--default]` | Registers a server without installing anything on it. Writes `Host <name>` to `~/.ssh/config`. |
101
- | `lf server list [--all]` | Lists registered servers. `--all` adds every ssh alias. Alias: `servers`. |
102
- | `lf server info [name] [--json]` | Shows OS and release support, kernel, uptime, CPU, RAM, disk, addresses and containers. Alias: `info`. |
103
- | `lf server config [name] [--json]` | Shows everything landfall and ssh use for a server. Does not connect. |
104
- | `lf server domain [name] [domain...] [--add d] [--primary d] [--rm d] [--clear] [--json]` | Manages main domains (the first is primary) and checks their DNS. |
105
- | `lf server use <name>` | Sets the default server. |
106
- | `lf server rm <name>` | Forgets a server locally. The server is untouched. |
107
- | `lf ssh [name] [-- cmd]` | Opens a shell or runs a one-off command. |
108
- | `lf keygen <name>` | Creates `~/.ssh/landfall_<name>` and prints the public key. |
109
-
110
- **Sites**
111
-
112
- | command | what it does |
113
- |---|---|
114
- | `lf deploy [dir] [-d domain...] [-n name] [-b cmd] [--spa] [--image img] [--port n] [-e K=V] [--env-file f] [--static] [--keep n]` | Deploys a static folder, a Dockerfile app or an image. |
115
- | `lf ls [--json]` | Lists sites on the server with their status. Alias: `sites list`. |
116
- | `lf sites info [name]` | Shows domains, container and release history. |
117
- | `lf domains [name] [--add d] [--rm d] [--set d] [--json]` | Lists domains with their DNS and HTTPS state, or changes them. |
118
- | `lf auth [name] -u user [-p pw]` / `--rm-user u` / `--off` | Puts basic-auth login in front of a site. |
119
- | `lf open [name]` | Opens the site in the browser. |
120
- | `lf logs [name] [-f] [-n lines]` | Shows container logs. |
121
- | `lf env [name] [K=V...] [-u K]` | Shows or changes env vars. Recreates the container. |
122
- | `lf rollback [name] [--to id]` | Switches to the previous release, or to the given one. |
123
- | `lf restart` / `stop` / `start [name]` | Restarts, stops or starts the site. |
124
- | `lf sites rm <name>` | Deletes the site and its files. |
125
-
126
- **Traefik, firewall, updates, history**
127
-
128
- | command | what it does |
129
- |---|---|
130
- | `lf traefik setup [--email e] [--dashboard host\|traefik\|off] [--staging] [--image img] [--host-network]` | Installs or reconfigures landfall's Traefik. |
131
- | `lf traefik use [container] [--network n --entrypoint e --resolver r]` | Routes sites through a Traefik you already run. |
132
- | `lf traefik status` / `logs [-f]` / `restart` | Inspects or restarts Traefik. `lf traefik` alone means `status`. |
133
- | `lf firewall [--json]` | Lists ufw rules and, for each listening port, whether it is reachable. |
134
- | `lf firewall allow` / `deny <port[/proto]> [--from ip] [--comment c]` | Adds a rule. A deny goes first, so it wins. |
135
- | `lf firewall update <n> [--port p] [--from ip] [--action allow\|deny\|limit]` | Changes a rule. |
136
- | `lf firewall rm <n>` / `enable` / `disable` | Removes a rule, or turns ufw on or off. |
137
- | `lf updates [--no-refresh] [--all] [--json]` | Shows pending OS packages, newer images, and Docker/compose versions. Changes nothing. |
138
- | `lf update [site...] [--packages] [--security] [--reboot] [--images] [--compose]` | Applies updates. Image and compose updates can be undone. |
139
- | `lf history [-n 20] [--json]` | Lists what landfall changed on the server, run by run. Alias: `log`. |
140
- | `lf undo [n \| n.m] [--all] [--force]` | Reverts the latest run, run `n`, step `n.m`, or everything. Alias: `revert`. |
141
-
142
- **Dashboards**
143
-
144
- | command | what it does |
145
- |---|---|
146
- | `lf` / `lf ui [--port 4777] [--no-open]` | Starts the web dashboard on 127.0.0.1 and prints a login link. |
147
- | `lf tui` | Starts the terminal dashboard. |
148
-
149
65
  ## Prepare a server
150
66
 
151
67
  ### A fresh server (only a root password)
152
68
 
153
69
  ```sh
154
- lf setup het --host 203.0.113.7
70
+ lf setup vps --host 203.0.113.7
155
71
  ```
156
72
 
157
- 1. Creates `~/.ssh/landfall_het` and installs it with `ssh-copy-id`. You type the root password once.
73
+ 1. Creates `~/.ssh/landfall_vps` and installs it with `ssh-copy-id`. You type the root password once.
158
74
  2. Installs Docker, ufw (allowing ssh, 80 and 443), fail2ban and unattended-upgrades.
159
75
  3. Creates the user `deploy` in the docker group and disables ssh password login. Root keeps key-only access.
160
- 4. Writes `Host het` to `~/.ssh/config`, creating the file if needed. `ssh het` now logs in as `deploy`.
76
+ 4. Writes `Host vps` to `~/.ssh/config`, creating the file if needed. `ssh vps` now logs in as `deploy`.
161
77
  5. Starts Traefik, which gets Let's Encrypt certificates for every site.
162
78
 
163
79
  Setup shows what it is about to change and asks once (`-y` skips the question). Key login is checked **before**
164
80
  password login is turned off, so a failed key setup can't lock you out.
165
81
 
166
- ### A server you already reach with `ssh het`
82
+ ### A server you already reach with `ssh vps`
167
83
 
168
84
  ```sh
169
- lf setup het --check # what is there, what would change
170
- lf setup het
85
+ lf setup vps --check # what is there, what would change
86
+ lf setup vps
171
87
  ```
172
88
 
173
89
  landfall reads host, user, port and key from your ssh config (`ssh -G`) and keeps a key that works. It inspects
@@ -209,13 +125,14 @@ No domain yet? Leave out `-d`. landfall offers `<name>.<ip-with-dashes>.sslip.io
209
125
 
210
126
  | kind | when | how |
211
127
  |---|---|---|
212
- | **static** | default | Uploads the folder and serves it with `nginx:alpine` (gzip, long cache headers for assets). The folder is `.` if it has an `index.html`, else the first of `dist/ build/ out/ public/ _site/ site/`. `--spa` sends unknown paths to `index.html`. |
128
+ | **compose** | the folder has a `compose.yaml` | Uploads the folder and runs it as one Compose project. See [Compose sites](#compose-sites). |
129
+ | **static** | no compose file, no Dockerfile | Uploads the folder and serves it with `nginx:alpine` (gzip, long cache headers for assets). The folder is `.` if it has an `index.html`, else the first of `dist/ build/ out/ public/ _site/ site/`. `--spa` sends unknown paths to `index.html`. |
213
130
  | **dockerfile** | the folder has a `Dockerfile` | Uploads the folder and builds it on the server. The port comes from `EXPOSE`, else `--port` (default 3000). `--static` forces static. |
214
- | **image** | `--image ghcr.io/me/app:1.2` | Pulls the image and runs it. Needs `--port`. For private registries: `lf ssh het -- docker login ghcr.io`. |
131
+ | **image** | `--image ghcr.io/me/app:1.2` | Pulls the image and runs it. Needs `--port`. For private registries: `lf ssh vps -- docker login ghcr.io`. |
215
132
 
216
133
  ```sh
217
134
  cd my-site
218
- lf deploy -d example.com -d www.example.com -s het # static, name = folder name (-n to override)
135
+ lf deploy -d example.com -d www.example.com -s vps # static, name = folder name (-n to override)
219
136
  lf deploy -b "npm run build" # build locally first
220
137
  lf deploy -d app.example.com -e DATABASE_URL=postgres://… --env-file .env.production # Dockerfile app
221
138
  lf deploy -n api --image ghcr.io/me/api:1.4 --port 8080 -d api.example.com # image
@@ -225,24 +142,92 @@ lf deploy # later: redeploy using
225
142
  The first deploy writes `.landfall.json`. Commit it:
226
143
 
227
144
  ```json
228
- { "name": "my-site", "server": "het", "domains": ["example.com", "www.example.com"] }
145
+ { "name": "my-site", "server": "vps", "domains": ["example.com", "www.example.com"] }
146
+ ```
147
+
148
+ Local `.env*` files are never used on the server: each site has its own `.env` there. Pass variables with
149
+ `-e`/`--env-file`, or later with `lf env`. `--keep 10` keeps more releases.
150
+
151
+ ### Compose sites
152
+
153
+ A site can be a whole Compose project: the app, its database, a worker. The same `compose.yaml` runs locally
154
+ with `docker compose up` and on the server with `lf deploy`. `lf init` writes one for an existing project:
155
+
156
+ ```sh
157
+ lf init --postgres # compose.yaml (app + db + volume) and compose.override.yaml (local ports)
158
+ echo POSTGRES_PASSWORD=dev > .env # local only
159
+ docker compose up --build # http://localhost:3000
160
+ lf deploy -e POSTGRES_PASSWORD=$(openssl rand -hex 16) # the first time; later just lf deploy
229
161
  ```
230
162
 
231
- Local `.env*` files are never uploaded. Pass variables with `-e`/`--env-file`, or later with `lf env`.
232
- Switching a static release only reloads nginx, so there is no downtime. `--keep 10` keeps more releases.
163
+ ```yaml
164
+ services:
165
+ app:
166
+ build: .
167
+ env_file: [{ path: .env, required: false }]
168
+ environment:
169
+ DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app
170
+ depends_on: { db: { condition: service_healthy } }
171
+ x-landfall:
172
+ port: 3000 # Traefik serves the site's domains from here
173
+ admin:
174
+ image: adminer
175
+ x-landfall: { port: 8080, domains: [db.example.com] } # its own domains
176
+ db:
177
+ image: postgres:17-alpine
178
+ environment: { POSTGRES_USER: app, POSTGRES_DB: app, POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:?}" }
179
+ volumes: [db:/var/lib/postgresql/data]
180
+ healthcheck: { test: pg_isready -U app, interval: 5s }
181
+ volumes:
182
+ db:
183
+ ```
184
+
185
+ - **`x-landfall`** on a service routes it: `port` is where Traefik sends requests, `domains` gives it its own
186
+ (a bare name or `@` works as in `-d`). One routed service without `domains` serves the site's domains, which
187
+ `-d` and `lf domains` manage. Services without `x-landfall` (the database) stay off Traefik's network.
188
+ - **`ports:` is refused**, since a published port bypasses Traefik and the firewall. Put local ports in
189
+ `compose.override.yaml`: `docker compose` merges it locally, `lf deploy` leaves it out. To publish a port on
190
+ the server anyway, add `x-landfall: { publish: true }` to that service.
191
+ - **Named volumes keep your data** across deploys and rollbacks. `lf sites rm` keeps them, so deploying the
192
+ site again finds its data; `lf sites rm --volumes` deletes them. Each deploy gets a new release folder, so
193
+ a writable bind mount like `./data` is refused: write data to a named volume (read-only `./dist:…:ro` is fine).
194
+ - **Images built on the server** are tagged `<site>-<service>:<release>`, so a rollback runs the exact image again.
195
+ - Variables in `compose.yaml` come from the site's `.env` on the server (`-e`, `--env-file`, `lf env`).
196
+ `${VAR:?}` makes a missing one fail the deploy before anything changes.
197
+ - Your own `traefik.*` labels win: a service with its own router keeps them and landfall adds only the network.
198
+ - `-f other.yaml` deploys another file; it is saved in `.landfall.json`. Sites need Docker Compose 2.24 or newer
199
+ on the server (`lf update --compose`).
200
+
201
+ The static, Dockerfile and image kinds are Compose sites too: landfall writes their `compose.yaml` for you.
202
+
203
+ ### Running without landfall
204
+
205
+ Nothing on the server depends on the CLI. Each site is a folder you can drive with plain `docker compose`:
206
+
207
+ ```sh
208
+ cd -P /opt/landfall/sites/blog/current # -P: into the release folder itself
209
+ docker compose ps # or logs -f, restart, up -d, down
210
+ cd /opt/landfall/sites/blog # roll back by hand: point current at an older release …
211
+ ln -sfn releases/20260122143005 current
212
+ cd -P current && docker compose up -d # … and bring it up
213
+ ```
214
+
215
+ `/opt/landfall/README` on the server explains the same. A project you started yourself behind landfall's
216
+ Traefik shows up in `lf sites` as not deployed by landfall; `lf adopt <project>` makes it a site that `lf` can
217
+ list, show logs for, restart and remove, while it keeps running from its own folder.
233
218
 
234
219
  ### Main domains
235
220
 
236
221
  ```sh
237
- lf server domain het example.com # checks DNS for example.com and *.example.com
222
+ lf server domain vps example.com # checks DNS for example.com and *.example.com
238
223
  lf deploy # site "blog" → blog.example.com
239
224
  lf deploy -d shop # no dot → shop.example.com
240
225
  lf domains blog --add www # www.example.com
241
226
  lf deploy -d @ # @ → example.com itself
242
227
  lf domains blog --add @ # blog also serves example.com
243
- lf server domain het --add other.org # more main domains; the first stays primary
244
- lf server domain het --primary other.org # switch the primary
245
- lf server domain het --rm other.org # drop one; --clear drops all (back to sslip.io)
228
+ lf server domain vps --add other.org # more main domains; the first stays primary
229
+ lf server domain vps --primary other.org # switch the primary
230
+ lf server domain vps --rm other.org # drop one; --clear drops all (back to sslip.io)
246
231
  ```
247
232
 
248
233
  `server domain` prints the A records to add: one for the domain and a wildcard `*.example.com`. With those in
@@ -255,9 +240,10 @@ and so is a redeploy whose domain comes only from `.landfall.json` (pass `-d` to
255
240
  ## Day-to-day
256
241
 
257
242
  ```sh
258
- lf ls # sites with status
259
- lf sites info # domains, container, releases
243
+ lf ls # sites with status, and your own containers behind Traefik
244
+ lf sites info # services, domains, releases
260
245
  lf logs -f # follow logs (-n 500 for more lines)
246
+ lf logs blog db # one service
261
247
  lf rollback # previous release (--to <id> for a specific one)
262
248
  lf env API_KEY=abc # set env (container is recreated); -u KEY removes one
263
249
  lf restart | stop | start
@@ -267,7 +253,7 @@ lf auth -u anna # login in front of the site (asks for the pa
267
253
  lf auth --rm-user anna | --off
268
254
  lf open
269
255
  lf sites rm blog # delete the site (asks you to type the name)
270
- lf logs api -s het # another site on another server
256
+ lf logs api -s vps # another site on another server
271
257
  ```
272
258
 
273
259
  Commands that remove something ask first: `sites rm`, `domains --rm/--set`, `server domain --rm/--clear`,
@@ -287,7 +273,7 @@ lf update --compose # newest compose, where landfall installed it
287
273
  ```
288
274
 
289
275
  Package upgrades (apt-get, dnf/yum, apk) cannot be undone. Image and compose updates record the previous version,
290
- so `lf undo` restores it. Landfall never touches images pinned by digest, Dockerfile sites or foreign containers.
276
+ so `lf undo` restores it. landfall never touches images pinned by digest, Dockerfile sites or foreign containers.
291
277
  Version-pinned tags show the newest tag of the same shape (`traefik:v3.6` → `traefik:v3.7`).
292
278
 
293
279
  ## Traefik
@@ -312,7 +298,8 @@ Traefik only answers hosts it has a certificate for (`sniStrict`). A domain poin
312
298
  by no site fails the TLS handshake, as if nothing listened there, instead of showing a 404 or Traefik's own
313
299
  certificate; plain HTTP to it redirects to HTTPS and fails the same way. A new site's domain fails the same way
314
300
  for the seconds until Let's Encrypt issues its certificate. Your own certificates and Traefik config go into
315
- `/opt/landfall/traefik/dynamic/`: landfall rewrites only `landfall.yml` there.
301
+ `/opt/landfall/traefik/dynamic/`: landfall rewrites only `landfall.yml` there. If you edit
302
+ `docker-compose.yml` by hand, `lf traefik setup` shows the diff and stops; `--force` overwrites your edits.
316
303
 
317
304
  The dashboard password is generated and printed once (`--dashboard-password` sets your own). Image, network mode,
318
305
  e-mail, staging and dashboard are remembered per server.
@@ -320,10 +307,15 @@ e-mail, staging and dashboard are remembered per server.
320
307
  **Using a Traefik you already run.** If 80/443 belong to your own Traefik container, landfall does not install a second one:
321
308
 
322
309
  ```sh
323
- lf traefik use -s het # finds the container on :80/:443
324
- lf traefik use traefik-reverseproxy-1 -s het # or name it
310
+ lf traefik use -s vps # finds the container on :80/:443
311
+ lf traefik use proxy-traefik-1 -s vps # or name it
325
312
  ```
326
313
 
314
+ Containers you run yourself behind that Traefik (compose projects with `traefik.http.routers.*.rule` labels)
315
+ show up in `lf ls` and the dashboard as "not deployed by landfall", with their domains and folder. landfall
316
+ never touches them; deploying one of their domains warns, since Traefik would send visitors to only one.
317
+ `lf adopt <project>` turns one into a site (see [Running without landfall](#running-without-landfall)).
318
+
327
319
  landfall reads the network, HTTPS entrypoint and cert resolver from the container's flags. If your Traefik is
328
320
  configured by a file, pass `--network proxy --entrypoint websecure --resolver letsencrypt`. The choice is stored
329
321
  in `/opt/landfall/proxy.env`. landfall never changes that Traefik.
@@ -331,7 +323,7 @@ in `/opt/landfall/proxy.env`. landfall never changes that Traefik.
331
323
  ## Firewall
332
324
 
333
325
  ```sh
334
- lf firewall -s het # rules + each listening port: open, limited, blocked, localhost only
326
+ lf firewall -s vps # rules + each listening port: open, limited, blocked, localhost only
335
327
  lf firewall allow 51820/udp
336
328
  lf firewall allow 5432 --from 10.0.0.0/8 --comment "db, lan only"
337
329
  lf firewall deny 8080 # goes before the allow rules, so it wins
@@ -351,8 +343,8 @@ Every change landfall makes is appended to `/opt/landfall/journal.log` on the se
351
343
  command, what changed and the shell that reverts it.
352
344
 
353
345
  ```sh
354
- lf history -s het # newest first; ↺ undoable, · not undoable, ✓ already undone
355
- lf undo -s het # revert the latest run with something left to undo
346
+ lf history -s vps # newest first; ↺ undoable, · not undoable, ✓ already undone
347
+ lf undo -s vps # revert the latest run with something left to undo
356
348
  lf undo 4 # run #4
357
349
  lf undo 4.2 # only its second step
358
350
  lf undo --all # everything, newest first
@@ -369,14 +361,14 @@ lf undo --all # everything, newest first
369
361
  ## Managing servers
370
362
 
371
363
  ```sh
372
- lf server add het # register an existing ssh alias
373
- lf server add box --host 1.2.3.4 -u ubuntu -i ~/.ssh/box --default
374
- lf server add web --host 1.2.3.4 -u admin # asks the password once, installs ~/.ssh/landfall_web
375
- lf server config het # landfall entry, Traefik settings, ssh login, keys, Host block
364
+ lf server add vps # register an existing ssh alias
365
+ lf server add box --host 198.51.100.4 -u ubuntu -i ~/.ssh/box --default
366
+ lf server add web --host 198.51.100.5 -u admin # asks the password once, installs ~/.ssh/landfall_web
367
+ lf server config vps # landfall entry, Traefik settings, ssh login, keys, Host block
376
368
  lf server list --all
377
- lf server use het
378
- lf server rm het # forget locally
379
- lf ssh het -- docker ps
369
+ lf server use vps
370
+ lf server rm vps # forget locally
371
+ lf ssh vps -- docker ps
380
372
  ```
381
373
 
382
374
  `server add` installs nothing on the server. It writes a `Host <name>` block between `# >>> landfall:<name>` markers
@@ -386,19 +378,19 @@ gone, and keeps a backup as `~/.ssh/config.landfall-bak`.
386
378
 
387
379
  ## Dashboards
388
380
 
389
- **Web (`lf` or `lf ui`).** The dashboard listens on `127.0.0.1` and opens your browser. Servers are on the left
381
+ **Web (`lf ui`).** The dashboard listens on `127.0.0.1` and opens your browser. Servers are on the left
390
382
  (★ marks the default). A server tab has Sites, Updates, Firewall, History and Settings. **Deploy folder…** browses
391
383
  local folders or takes a dropped folder or `.zip`. Every action streams the output of its plain command into a side
392
384
  panel. Destructive actions ask first. Login works through the printed `/login?t=…` link. The token is kept in
393
385
  `~/.config/landfall/ui-token`, and you can delete that file to rotate it. Requests with a foreign `Host` or `Origin`
394
- are refused. Running `lf` again while the dashboard is running just opens the page.
386
+ are refused. Running `lf ui` again while the dashboard is running just opens the page.
395
387
 
396
388
  **Terminal (`lf tui`).**
397
389
 
398
390
  ```
399
- landfall 0.1.0 this folder: blog → het · blog.example.com
400
- ╭ Servers ──────────╮╭ Sites on het traefik Up 3 days ─────────────────────╮
401
- │ › ● het default │ │ › ● blog static blog.example.com 2h ago │
391
+ landfall this folder: blog → vps · blog.example.com
392
+ ╭ Servers ──────────╮╭ Sites on vps traefik Up 3 days ─────────────────────╮
393
+ │ › ● vps default │ │ › ● blog static blog.example.com 2h ago │
402
394
  │ ● box │ │ ● api dockerfile api.example.com 5d ago │
403
395
  │ ○ nas ssh config│ │ │
404
396
  │ + add a server │ │ │
@@ -409,6 +401,93 @@ landfall 0.1.0 this folder: blog → het · blog.ex
409
401
  Keys: `↵` connect/open, `d` deploy this folder, `u` check and set up, `a` add a server, `f` firewall, `h` history,
410
402
  `z` undo, `x` shell, `esc` stop/back, `?` all keys. Dot colors: green ready, yellow not set up, red unreachable, ○ not connected.
411
403
 
404
+ ## Using landfall from scripts and agents
405
+
406
+ Every dashboard action is a plain command, and both dashboards print the command they run (`$ lf logs blog -s vps -f`).
407
+
408
+ - **No prompts:** `-y` accepts confirmations and takes defaults. Without a terminal, prompts are never shown. A
409
+ command that would ask then **fails** and tells you to pass `-y`. It never silently goes ahead.
410
+ - **Passwords:** pass them with `--password-stdin` or `$LANDFALL_PASSWORD` (`setup`, `server add`). For `lf auth`, use `-p`.
411
+ - **JSON output:** `--json` works on `ls`, `info`, `server config`, `server domain`, `domains`, `firewall`,
412
+ `updates` and `history`.
413
+ - **Dry runs:** `lf setup <name> --check` changes nothing. `lf updates` only reports.
414
+ - **Exit codes:** `0` means success, `1` means error (the message starts with `error:` on stderr), and `2` means unknown command.
415
+ - **Safety rails:** `-y` never overrides a safety refusal, for example a firewall change that would lock out ssh, or
416
+ setup steps that would break running services. Only `--force` does.
417
+
418
+ A typical unattended flow:
419
+
420
+ ```sh
421
+ printf '%s' "$ROOT_PW" | lf setup box --host 203.0.113.7 --password-stdin -y
422
+ lf server domain box example.com -y
423
+ lf deploy ./dist -n blog -s box -y # → https://blog.example.com
424
+ lf ls -s box --json
425
+ lf history -s box --json
426
+ lf undo -s box -y # revert the last run
427
+ ```
428
+
429
+ ## Command reference
430
+
431
+ `[name]` is a site or server and is optional (see [Concepts](#concepts)). Most site and server commands take `-s <server>`. Every command takes `-y` and `--help`.
432
+
433
+ **Servers**
434
+
435
+ | command | what it does |
436
+ |---|---|
437
+ | `lf setup <name> [--host ip]` | Turns a box into a deploy target: ssh key, Docker, ufw, fail2ban, deploy user, ssh hardening and Traefik. Idempotent. |
438
+ | `lf setup <name> --check` | Reports what is set up and what setup would change. Changes nothing. |
439
+ | `lf setup <name> --key-only` | Only installs landfall's ssh key. |
440
+ | `lf server add <name> [--host ip] [-u user] [-p port] [-i key] [--password-stdin] [--domain d] [--default]` | Registers a server without installing anything on it. Writes `Host <name>` to `~/.ssh/config`. |
441
+ | `lf server list [--all]` | Lists registered servers. `--all` adds every ssh alias. Alias: `servers`. |
442
+ | `lf server info [name] [--json]` | Shows OS and release support, kernel, uptime, CPU, RAM, disk, addresses and containers. Alias: `info`. |
443
+ | `lf server config [name] [--json]` | Shows everything landfall and ssh use for a server. Does not connect. |
444
+ | `lf server domain [name] [domain...] [--add d] [--primary d] [--rm d] [--clear] [--json]` | Manages main domains (the first is primary) and checks their DNS. |
445
+ | `lf server use <name>` | Sets the default server. |
446
+ | `lf server rm <name>` | Forgets a server locally. The server is untouched. |
447
+ | `lf ssh [name] [-- cmd]` | Opens a shell or runs a one-off command. |
448
+ | `lf keygen <name>` | Creates `~/.ssh/landfall_<name>` and prints the public key. |
449
+
450
+ **Sites**
451
+
452
+ | command | what it does |
453
+ |---|---|
454
+ | `lf init [dir] [--postgres] [--port n]` | Writes `compose.yaml` and `compose.override.yaml` for the project, to run it locally and deploy it. |
455
+ | `lf deploy [dir] [-d domain...] [-n name] [-f file] [-b cmd] [--spa] [--image img] [--port n] [-e K=V] [--env-file f] [--static] [--keep n]` | Deploys a Compose project, static folder, Dockerfile app or image. |
456
+ | `lf ls [--json] [--others]` | Lists sites on the server with their status, plus containers Traefik routes that landfall did not deploy (read only; `--json --others` gives `{sites, others}`). Alias: `sites list`. |
457
+ | `lf sites info [name]` | Shows services, domains and release history. |
458
+ | `lf domains [name] [--add d] [--rm d] [--set d] [--json]` | Lists domains with their DNS and HTTPS state, or changes them. |
459
+ | `lf auth [name] -u user [-p pw]` / `--rm-user u` / `--off` | Puts basic-auth login in front of a site. |
460
+ | `lf open [name]` | Opens the site in the browser. |
461
+ | `lf logs [name] [service] [-f] [-n lines]` | Shows logs of all services, or one. |
462
+ | `lf env [name] [K=V...] [-u K]` | Shows or changes the site's `.env`. Recreates the services. |
463
+ | `lf rollback [name] [--to id]` | Switches to the previous release, or to the given one. |
464
+ | `lf restart` / `stop` / `start [name] [service]` | Restarts, stops or starts the site, or one service. |
465
+ | `lf sites rm <name> [--volumes]` | Deletes the site and its files. Named volumes stay unless `--volumes`. |
466
+ | `lf adopt <project> [--name n]` | Makes a Compose project you started yourself a site, so `ls`, logs, restart and rm work for it. |
467
+
468
+ **Traefik, firewall, updates, history**
469
+
470
+ | command | what it does |
471
+ |---|---|
472
+ | `lf traefik setup [--email e] [--dashboard host\|traefik\|off] [--staging] [--image img] [--host-network] [--force]` | Installs or reconfigures landfall's Traefik. `--force` overwrites hand edits. |
473
+ | `lf traefik use [container] [--network n --entrypoint e --resolver r]` | Routes sites through a Traefik you already run. |
474
+ | `lf traefik status` / `logs [-f]` / `restart` | Inspects or restarts Traefik. `lf traefik` alone means `status`. |
475
+ | `lf firewall [--json]` | Lists ufw rules and, for each listening port, whether it is reachable. |
476
+ | `lf firewall allow` / `deny <port[/proto]> [--from ip] [--comment c]` | Adds a rule. A deny goes first, so it wins. |
477
+ | `lf firewall update <n> [--port p] [--from ip] [--action allow\|deny\|limit]` | Changes a rule. |
478
+ | `lf firewall rm <n>` / `enable` / `disable` | Removes a rule, or turns ufw on or off. |
479
+ | `lf updates [--no-refresh] [--all] [--json]` | Shows pending OS packages, newer images, and Docker/compose versions. Changes nothing. |
480
+ | `lf update [site...] [--packages] [--security] [--reboot] [--images] [--compose]` | Applies updates. Image and compose updates can be undone. |
481
+ | `lf history [-n 20] [--json]` | Lists what landfall changed on the server, run by run. Alias: `log`. |
482
+ | `lf undo [n \| n.m] [--all] [--force]` | Reverts the latest run, run `n`, step `n.m`, or everything. Alias: `revert`. |
483
+
484
+ **Dashboards**
485
+
486
+ | command | what it does |
487
+ |---|---|
488
+ | `lf ui [--port 4777] [--no-open]` | Starts the web dashboard on 127.0.0.1 and prints a login link. |
489
+ | `lf tui` | Starts the terminal dashboard. |
490
+
412
491
  ## Files and config
413
492
 
414
493
  | local file | content |
@@ -427,12 +506,6 @@ Keys: `↵` connect/open, `d` deploy this folder, `u` check and set up, `a` add
427
506
  | `LANDFALL_SSH_CONFIG` | use another ssh config file (`ssh -F`) |
428
507
  | `LANDFALL_PASSWORD` | login password for `setup`/`server add` |
429
508
 
430
- **Coming from `srv`** (landfall's old name): `~/.config/srv` moves to `~/.config/landfall` on the first run, and
431
- `srv.json`, `~/.ssh/srv_<name>`, `# >>> srv:` blocks and `$SRV_*` variables keep working. A server set up by `srv`
432
- still has `/opt/srv`: other commands refuse it until `lf setup <name>` moves it to `/opt/landfall` and renames
433
- the site containers. Sites keep serving, Traefik restarts once, and older changes can no longer be undone.
434
- All of this lives in `src/legacy.ts`.
435
-
436
509
  On the server:
437
510
 
438
511
  ```
@@ -440,17 +513,19 @@ On the server:
440
513
  traefik/docker-compose.yml Traefik, ports 80/443, Let's Encrypt (HTTP-01)
441
514
  traefik/letsencrypt/acme.json
442
515
  proxy.env only with `lf traefik use`
516
+ README how to run sites by hand
443
517
  sites/<name>/
444
518
  site.json metadata + release history
445
- docker-compose.yml container landfall-<name> on network "web" with Traefik labels
446
- nginx.conf static sites
447
- .env app sites (0600)
519
+ .env the site's variables (0600)
448
520
  releases/<id>/ last 5 kept
521
+ compose.yaml yours, or generated for static/Dockerfile/image
522
+ compose.override.yaml written by landfall: project name, Traefik labels, networks
523
+ .env -> ../../.env
449
524
  current -> releases/<id>
450
525
  journal.log every change, with the shell that reverts it
451
526
  ```
452
527
 
453
- `cd /opt/landfall/sites/blog && docker compose ps` works as usual.
528
+ `cd -P /opt/landfall/sites/blog/current && docker compose ps` works as usual.
454
529
 
455
530
  ## Troubleshooting
456
531
 
@@ -460,7 +535,11 @@ On the server:
460
535
  | certificate warning in the browser | you used `--staging`: `lf setup <name> --no-staging` |
461
536
  | no certificate at all | DNS must point at the server and 80/443 must be open; see `lf traefik logs` |
462
537
  | TLS handshake fails | no site serves the domain (`lf domains --add …`), or its certificate isn't issued yet: `lf traefik logs` |
463
- | site container keeps restarting | `lf logs`; for apps check `--port` and env |
538
+ | site container keeps restarting | `lf logs`; for apps check `--port` / `x-landfall.port` and env |
539
+ | `… publishes 5432 on the server, past Traefik and the firewall` | move `ports:` to `compose.override.yaml`, or allow it with `x-landfall: { publish: true }` |
540
+ | `sites need docker compose 2.24 or newer` | `lf update --compose` |
541
+ | `required variable … is missing` | set it on the server: `lf env KEY=…` or `lf deploy -e KEY=…` |
542
+ | `… was edited by hand` (traefik setup) | move your change to `dynamic/` or flags, or `lf traefik setup --force` |
464
543
  | `skipping firewall/ssh hardening: …` | setup protected something running there. Fix it, or re-run with `--force` |
465
544
  | `skipping traefik: port 80 is in use by …` | free the port, then `lf traefik setup`. If it's a Traefik, `lf traefik use` |
466
545
  | `… blocks ssh port 22 … (--force to do it anyway)` | `lf firewall allow 22/tcp` first, or force if you have another way in |