@lamalibre/create-lamaste 4.0.0 → 4.1.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/package.json +2 -2
- package/scripts/lamaste-priv +21 -2
- package/src/lib/oidc-issuer-key.js +124 -0
- package/src/lib/relay-services.js +4 -0
- package/src/lib/service-config.js +16 -0
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/authentication.md +45 -1
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +3 -3
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
- package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +6 -5
- package/vendor/docs/03-architecture/management-flow.md +1 -1
- package/vendor/docs/03-architecture/overview.md +5 -5
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +2 -2
- package/vendor/docs/03-architecture/state-management.md +2 -2
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/overview.md +111 -105
- package/vendor/docs/04-api-reference/registry-and-oidc.md +182 -0
- package/vendor/docs/05-operations/backup-and-restore.md +1 -1
- package/vendor/docs/05-operations/uninstalling.md +1 -1
- package/vendor/docs/05-operations/upgrades.md +6 -5
- package/vendor/docs/06-reference/config-files.md +103 -45
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +1 -1
- package/vendor/docs/06-reference/troubleshooting.md +8 -8
- package/vendor/docs/_index.json +5 -0
- package/vendor/docs/gatekeeper-design.md +8 -8
- package/vendor/docs/identity-directory-design.md +443 -0
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/package.json +1 -1
- package/vendor/server/bin/lamaste-server.js +10 -1
- package/vendor/server/package.json +4 -3
- package/vendor/server/src/commands/plugins.js +87 -0
- package/vendor/server/src/commands/registry-credential.js +142 -0
- package/vendor/server/src/panel-api.js +83 -38
- package/vendor/serverd/package.json +5 -2
- package/vendor/serverd/src/index.js +11 -0
- package/vendor/serverd/src/lib/authelia.js +11 -0
- package/vendor/serverd/src/lib/plugin-oidc.js +216 -0
- package/vendor/serverd/src/lib/plugins.js +26 -2
- package/vendor/serverd/src/lib/registry-credential.js +145 -0
- package/vendor/serverd/src/routes/management/agents.js +81 -0
- package/vendor/serverd/src/routes/management/plugins.js +65 -2
- package/vendor/serverd/src/routes/management/registry-credential.js +170 -0
- package/vendor/serverd/src/routes/management/users.js +31 -0
- package/vendor/serverd/src/routes/management.js +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lamalibre/create-lamaste",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"description": "One-command setup for secure reverse tunnels with a management dashboard",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"mtls"
|
|
44
44
|
],
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@lamalibre/lamaste": "^4.
|
|
46
|
+
"@lamalibre/lamaste": "^4.1.0",
|
|
47
47
|
"chalk": "^5.3.0",
|
|
48
48
|
"execa": "^9.0.0",
|
|
49
49
|
"listr2": "^8.0.0"
|
package/scripts/lamaste-priv
CHANGED
|
@@ -46,6 +46,17 @@ const MAX_VHOST_BYTES = 256 * 1024;
|
|
|
46
46
|
const AUTHELIA_BIN = '/usr/local/bin/authelia';
|
|
47
47
|
const AUTHELIA_CONFIG = '/etc/authelia/configuration.yml';
|
|
48
48
|
const AUTHELIA_USER = 'lamaste-authelia';
|
|
49
|
+
/**
|
|
50
|
+
* Environment of every `authelia` CLI run. The configuration is a Go
|
|
51
|
+
* template (it reads the OIDC issuer key with `{{ secret "…" }}`), so the
|
|
52
|
+
* CLI needs the same filter the service unit sets, or it fails to parse
|
|
53
|
+
* the file once an OIDC provider is configured.
|
|
54
|
+
*/
|
|
55
|
+
const AUTHELIA_CLI_ENV = Object.freeze({
|
|
56
|
+
PATH: '/usr/bin:/bin',
|
|
57
|
+
HOME: '/nonexistent',
|
|
58
|
+
X_AUTHELIA_CONFIG_FILTERS: 'template',
|
|
59
|
+
});
|
|
49
60
|
|
|
50
61
|
const SYSTEMD_RUN = '/usr/bin/systemd-run';
|
|
51
62
|
const NPX = '/usr/bin/npx';
|
|
@@ -604,7 +615,7 @@ function autheliaTotp(username) {
|
|
|
604
615
|
uid,
|
|
605
616
|
gid,
|
|
606
617
|
cwd: '/',
|
|
607
|
-
env: {
|
|
618
|
+
env: { ...AUTHELIA_CLI_ENV },
|
|
608
619
|
encoding: 'utf8',
|
|
609
620
|
timeout: 60_000,
|
|
610
621
|
},
|
|
@@ -689,5 +700,13 @@ if (require.main === module) {
|
|
|
689
700
|
process.exit(err instanceof UsageError ? 2 : 1);
|
|
690
701
|
}
|
|
691
702
|
} else {
|
|
692
|
-
module.exports = {
|
|
703
|
+
module.exports = {
|
|
704
|
+
validateVhost,
|
|
705
|
+
tokenize,
|
|
706
|
+
parse,
|
|
707
|
+
assertSiteName,
|
|
708
|
+
PANEL_SITE_RE,
|
|
709
|
+
UsageError,
|
|
710
|
+
AUTHELIA_CLI_ENV,
|
|
711
|
+
};
|
|
693
712
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { execa } from 'execa';
|
|
2
|
+
import crypto from 'node:crypto';
|
|
3
|
+
import { lstat } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { AUTHELIA_CONFIG_DIR, AUTHELIA_OIDC_KEY_PATH } from './service-config.js';
|
|
6
|
+
import { setFileOwnership } from './ownership.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The RSA key Authelia signs OpenID Connect tokens with.
|
|
10
|
+
*
|
|
11
|
+
* Generated here — by create-lamaste, on a fresh install and on every
|
|
12
|
+
* redeploy that finds it missing — rather than by the panel, so the panel
|
|
13
|
+
* never holds the key that mints identities: the file is 0600 and owned by
|
|
14
|
+
* the Authelia account, which is the only process that reads it. Authelia's
|
|
15
|
+
* configuration references it with a `{{ secret "…" }}` template expression
|
|
16
|
+
* (its unit sets `X_AUTHELIA_CONFIG_FILTERS=template`).
|
|
17
|
+
*
|
|
18
|
+
* `/etc/authelia` belongs to the lamaste user (the panel writes Authelia's
|
|
19
|
+
* configuration there), so root never writes through that path: the key is
|
|
20
|
+
* generated in this process and handed to a child running as the Authelia
|
|
21
|
+
* account, which creates the file (`wx`, 0600) and renames it into place.
|
|
22
|
+
* A symlink the panel could plant at the key's name is replaced by the
|
|
23
|
+
* rename, never followed.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const RSA_MODULUS_BITS = 4096;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Child-side writer: content on stdin, destination as the last argument.
|
|
30
|
+
* Runs as the Authelia user with its own umask; the file is created with
|
|
31
|
+
* `wx` so an existing file is never truncated or followed.
|
|
32
|
+
*/
|
|
33
|
+
export const PRIVATE_FILE_WRITER = `
|
|
34
|
+
const fs = require('node:fs');
|
|
35
|
+
const dest = process.argv[process.argv.length - 1];
|
|
36
|
+
const content = fs.readFileSync(0, 'utf8');
|
|
37
|
+
if (!content.includes('PRIVATE KEY')) { process.stderr.write('not a private key\\n'); process.exit(3); }
|
|
38
|
+
const tmp = dest + '.' + process.pid + '.tmp';
|
|
39
|
+
const fd = fs.openSync(tmp, 'wx', 0o600);
|
|
40
|
+
try {
|
|
41
|
+
fs.writeFileSync(fd, content);
|
|
42
|
+
fs.fsyncSync(fd);
|
|
43
|
+
} finally {
|
|
44
|
+
fs.closeSync(fd);
|
|
45
|
+
}
|
|
46
|
+
fs.renameSync(tmp, dest);
|
|
47
|
+
`;
|
|
48
|
+
|
|
49
|
+
/** A fresh RSA-4096 private key as PKCS#8 PEM. */
|
|
50
|
+
export function generateIssuerKeyPem() {
|
|
51
|
+
const { privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: RSA_MODULUS_BITS });
|
|
52
|
+
return privateKey.export({ type: 'pkcs8', format: 'pem' }).toString();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Write `pem` to `dest` as `uid:gid` 0600 through a child process running
|
|
57
|
+
* as that account. Root touches nothing under the destination's directory.
|
|
58
|
+
*
|
|
59
|
+
* @param {string} dest
|
|
60
|
+
* @param {string} pem
|
|
61
|
+
* @param {{ uid: number, gid: number }} ids
|
|
62
|
+
*/
|
|
63
|
+
export async function writePrivateKeyAs(dest, pem, { uid, gid }) {
|
|
64
|
+
await execa(process.execPath, ['-e', PRIVATE_FILE_WRITER, dest], {
|
|
65
|
+
uid,
|
|
66
|
+
gid,
|
|
67
|
+
input: pem,
|
|
68
|
+
cwd: '/',
|
|
69
|
+
env: { PATH: '/usr/bin:/bin' },
|
|
70
|
+
extendEnv: false,
|
|
71
|
+
timeout: 30_000,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Make sure the issuer key exists as `autheliaUid:autheliaGid` 0600.
|
|
77
|
+
*
|
|
78
|
+
* - Missing: generate and write it.
|
|
79
|
+
* - A regular file owned by the Authelia account: fix the mode through a
|
|
80
|
+
* descriptor if it drifted; keep the key (tokens stay valid).
|
|
81
|
+
* - Anything else (another owner, a symlink): replace it — a key the panel
|
|
82
|
+
* wrote is a key the panel knows.
|
|
83
|
+
*
|
|
84
|
+
* @param {{ autheliaUid: number, autheliaGid: number, dir?: string, report?: (line: string) => void }} options
|
|
85
|
+
* @returns {Promise<{ generated: boolean, path: string }>}
|
|
86
|
+
*/
|
|
87
|
+
export async function ensureOidcIssuerKey({
|
|
88
|
+
autheliaUid,
|
|
89
|
+
autheliaGid,
|
|
90
|
+
dir = AUTHELIA_CONFIG_DIR,
|
|
91
|
+
report = () => {},
|
|
92
|
+
}) {
|
|
93
|
+
const keyPath =
|
|
94
|
+
dir === AUTHELIA_CONFIG_DIR ? AUTHELIA_OIDC_KEY_PATH : join(dir, 'oidc-issuer.pem');
|
|
95
|
+
|
|
96
|
+
let st = null;
|
|
97
|
+
try {
|
|
98
|
+
st = await lstat(keyPath);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err.code !== 'ENOENT') throw err;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (st && st.isFile() && st.uid === autheliaUid) {
|
|
104
|
+
if (st.gid !== autheliaGid || (st.mode & 0o7777) !== 0o600) {
|
|
105
|
+
report('Fixing the ownership of the OIDC issuer key...');
|
|
106
|
+
await setFileOwnership(keyPath, { uid: autheliaUid, gid: autheliaGid, mode: 0o600 });
|
|
107
|
+
}
|
|
108
|
+
return { generated: false, path: keyPath };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
report(
|
|
112
|
+
st
|
|
113
|
+
? 'Replacing the OIDC issuer key (not owned by the Authelia account)...'
|
|
114
|
+
: 'Generating the OIDC issuer key (RSA 4096)...',
|
|
115
|
+
);
|
|
116
|
+
const pem = generateIssuerKeyPem();
|
|
117
|
+
await writePrivateKeyAs(keyPath, pem, { uid: autheliaUid, gid: autheliaGid });
|
|
118
|
+
|
|
119
|
+
const after = await lstat(keyPath);
|
|
120
|
+
if (!after.isFile() || after.uid !== autheliaUid || (after.mode & 0o7777) !== 0o600) {
|
|
121
|
+
throw new Error(`${keyPath} was not written as the Authelia account with mode 0600`);
|
|
122
|
+
}
|
|
123
|
+
return { generated: true, path: keyPath };
|
|
124
|
+
}
|
|
@@ -2,6 +2,7 @@ import { readFile, writeFile } from 'node:fs/promises';
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { ensureAutheliaUser, ensureChiselGroup, lookupId } from './accounts.js';
|
|
4
4
|
import { installPinnedAuthelia, installPinnedChisel } from './binaries.js';
|
|
5
|
+
import { ensureOidcIssuerKey } from './oidc-issuer-key.js';
|
|
5
6
|
import {
|
|
6
7
|
chownTree,
|
|
7
8
|
ensureDirectory,
|
|
@@ -79,6 +80,9 @@ export async function provisionRelayServices(ctx, report) {
|
|
|
79
80
|
});
|
|
80
81
|
await ensureDirectory(AUTHELIA_LOG_DIR, { uid: autheliaUid, gid: autheliaGid, mode: 0o750 });
|
|
81
82
|
await chownTree(AUTHELIA_LOG_DIR, `${autheliaUid}:${autheliaGid}`);
|
|
83
|
+
// The OIDC issuer key: Authelia's alone, generated here so the panel —
|
|
84
|
+
// which writes the configuration that references it — never holds it.
|
|
85
|
+
await ensureOidcIssuerKey({ autheliaUid, autheliaGid, report });
|
|
82
86
|
// chisel runs as nobody:lamaste-chisel; the panel creates the key (as
|
|
83
87
|
// lamaste, group lamaste-chisel). Earlier versions made it nobody:nogroup 0400.
|
|
84
88
|
await setFileOwnership(join(ctx.configDir, 'chisel-server.key'), {
|
|
@@ -24,6 +24,12 @@ WorkingDirectory=${ctx.installDir}/serverd
|
|
|
24
24
|
ExecStart=/usr/bin/node src/index.js
|
|
25
25
|
Environment=NODE_ENV=production
|
|
26
26
|
Environment=CONFIG_FILE=${ctx.configDir}/panel.json
|
|
27
|
+
# The service account has no home directory (useradd --no-create-home) and
|
|
28
|
+
# ProtectHome hides /home anyway. npm, which the panel runs to install
|
|
29
|
+
# plugins, needs a writable cache and HOME: both live in the state directory,
|
|
30
|
+
# the same convention as lamaste-server plugins (runAsLamaste).
|
|
31
|
+
Environment=HOME=${ctx.configDir}
|
|
32
|
+
Environment=npm_config_cache=${ctx.configDir}/.npm
|
|
27
33
|
Restart=always
|
|
28
34
|
RestartSec=5
|
|
29
35
|
StandardOutput=journal
|
|
@@ -187,6 +193,15 @@ export const AUTHELIA_BIN = '/usr/local/bin/authelia';
|
|
|
187
193
|
export const CHISEL_UNIT_PATH = '/etc/systemd/system/chisel.service';
|
|
188
194
|
export const AUTHELIA_UNIT_PATH = '/etc/systemd/system/authelia.service';
|
|
189
195
|
export const AUTHELIA_CONFIG_DIR = '/etc/authelia';
|
|
196
|
+
/** RSA key Authelia signs OpenID Connect tokens with (0600, the Authelia account; see lib/oidc-issuer-key.js). */
|
|
197
|
+
export const AUTHELIA_OIDC_KEY_PATH = `${AUTHELIA_CONFIG_DIR}/oidc-issuer.pem`;
|
|
198
|
+
/**
|
|
199
|
+
* Authelia's configuration is rendered as a Go template: that is how it
|
|
200
|
+
* reads the OIDC issuer key (`{{ secret "…" }}`) without the panel, which
|
|
201
|
+
* writes the configuration, ever holding the key. Set on the unit and on
|
|
202
|
+
* every `authelia` CLI invocation (lamaste-priv authelia-totp).
|
|
203
|
+
*/
|
|
204
|
+
export const AUTHELIA_CONFIG_FILTERS_ENV = 'X_AUTHELIA_CONFIG_FILTERS=template';
|
|
190
205
|
export const AUTHELIA_LOG_DIR = '/var/log/authelia';
|
|
191
206
|
export const WEB_ROOT = '/var/www/lamaste';
|
|
192
207
|
|
|
@@ -257,6 +272,7 @@ User=${AUTHELIA_USER}
|
|
|
257
272
|
Group=${AUTHELIA_USER}
|
|
258
273
|
UMask=027
|
|
259
274
|
Environment=AUTHELIA_SERVER_DISABLE_HEALTHCHECK=true
|
|
275
|
+
Environment=${AUTHELIA_CONFIG_FILTERS_ENV}
|
|
260
276
|
ExecStart=${AUTHELIA_BIN} --config ${AUTHELIA_CONFIG_DIR}/configuration.yml
|
|
261
277
|
Restart=always
|
|
262
278
|
RestartSec=5
|
|
@@ -55,14 +55,14 @@ SSH into a fresh Ubuntu droplet and run one command:
|
|
|
55
55
|
|
|
56
56
|
```bash
|
|
57
57
|
apt install -y npm
|
|
58
|
-
npx @lamalibre/create-lamaste@4.
|
|
58
|
+
npx @lamalibre/create-lamaste@4.1.0
|
|
59
59
|
```
|
|
60
60
|
|
|
61
61
|
This installs everything the relay needs: nginx, Node.js, firewall rules, certificates, and the management panel. At the end, it prints a client certificate file and password. You download the certificate, import it into your browser, and disconnect SSH. You never SSH in again.
|
|
62
62
|
|
|
63
63
|
**Option B: Desktop app (cloud provisioning)**
|
|
64
64
|
|
|
65
|
-
Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.
|
|
65
|
+
Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.1.0`) and use the Servers tab to create a DigitalOcean droplet. The app handles everything — droplet creation, Lamaste installation, certificate download, and connection — without you ever touching SSH. Cloud API tokens are stored in your OS credential store (macOS Keychain or Linux libsecret), never in plaintext. See the [Cloud Provisioning guide](../02-guides/cloud-provisioning.md) for a step-by-step walkthrough.
|
|
66
66
|
|
|
67
67
|
**Phase 2: Onboarding (10 minutes, browser)**
|
|
68
68
|
|
|
@@ -59,7 +59,7 @@ Once connected:
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
apt install -y npm
|
|
62
|
-
npx @lamalibre/create-lamaste@4.
|
|
62
|
+
npx @lamalibre/create-lamaste@4.1.0
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
The installer displays a confirmation banner showing what it will do:
|
|
@@ -253,7 +253,7 @@ Every tunnel is carried by one **agent** — the machine that runs the app. Enro
|
|
|
253
253
|
Install the Lamaste Desktop app, which provides a GUI with automatic service discovery and one-click tunnel creation:
|
|
254
254
|
|
|
255
255
|
```bash
|
|
256
|
-
npx @lamalibre/create-lamaste-desktop@4.
|
|
256
|
+
npx @lamalibre/create-lamaste-desktop@4.1.0
|
|
257
257
|
```
|
|
258
258
|
|
|
259
259
|
The app auto-detects local services (Ollama, ComfyUI, PostgreSQL, Docker containers, etc.) and lets you expose them with one click. See the [Desktop App Setup](../02-guides/desktop-app-setup.md) guide for details.
|
|
@@ -263,7 +263,7 @@ The app auto-detects local services (Ollama, ComfyUI, PostgreSQL, Docker contain
|
|
|
263
263
|
In the panel, go to **Certificates → Enrollment Token** and create a token for a label such as `laptop`. Then, on the machine that runs your app, install the agent globally and run setup:
|
|
264
264
|
|
|
265
265
|
```bash
|
|
266
|
-
npm install -g @lamalibre/lamaste-agent@4.
|
|
266
|
+
npm install -g @lamalibre/lamaste-agent@4.1.0
|
|
267
267
|
LAMALIBRE_LAMASTE_ENROLLMENT_TOKEN=<token> \
|
|
268
268
|
lamaste-agent setup --label laptop --panel-url https://203.0.113.42:9292
|
|
269
269
|
```
|
|
@@ -345,7 +345,7 @@ Your Mac DigitalOcean Droplet Internet
|
|
|
345
345
|
The installer accepts optional flags:
|
|
346
346
|
|
|
347
347
|
```bash
|
|
348
|
-
npx @lamalibre/create-lamaste@4.
|
|
348
|
+
npx @lamalibre/create-lamaste@4.1.0 [flags]
|
|
349
349
|
```
|
|
350
350
|
|
|
351
351
|
| Flag | Effect |
|
|
@@ -435,7 +435,7 @@ In development mode (`NODE_ENV=development` or `NODE_ENV` unset), the panel serv
|
|
|
435
435
|
# 2. SSH in and install
|
|
436
436
|
ssh root@203.0.113.42
|
|
437
437
|
apt install -y npm
|
|
438
|
-
npx @lamalibre/create-lamaste@4.
|
|
438
|
+
npx @lamalibre/create-lamaste@4.1.0
|
|
439
439
|
|
|
440
440
|
# 3. Download cert (from your local machine)
|
|
441
441
|
scp root@203.0.113.42:/etc/lamalibre/lamaste/pki/client.p12 .
|
|
@@ -36,7 +36,7 @@ Lamaste solves the problem of exposing local services to the internet securely.
|
|
|
36
36
|
**Option A: Manual setup (SSH)**
|
|
37
37
|
|
|
38
38
|
1. Create a fresh Ubuntu 24.04 droplet on DigitalOcean
|
|
39
|
-
2. SSH in once and run `npx @lamalibre/create-lamaste@4.
|
|
39
|
+
2. SSH in once and run `npx @lamalibre/create-lamaste@4.1.0`
|
|
40
40
|
3. Download the client certificate it prints
|
|
41
41
|
4. Import the certificate into your browser
|
|
42
42
|
5. Open `https://<droplet-ip>:9292` — you are in the admin panel
|
|
@@ -46,7 +46,7 @@ Lamaste solves the problem of exposing local services to the internet securely.
|
|
|
46
46
|
|
|
47
47
|
**Option B: Desktop app cloud provisioning**
|
|
48
48
|
|
|
49
|
-
1. Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.
|
|
49
|
+
1. Install the desktop app (`npx @lamalibre/create-lamaste-desktop@4.1.0`)
|
|
50
50
|
2. Go to the Servers tab and click "Create a new server"
|
|
51
51
|
3. Enter your DigitalOcean API token — the app validates scopes and stores it securely in your OS credential store
|
|
52
52
|
4. Pick a region (the app measures latency to each) and a domain name
|
|
@@ -93,14 +93,14 @@ All backend services bind `127.0.0.1` only — nginx is the sole public-facing s
|
|
|
93
93
|
|
|
94
94
|
State is stored in JSON files (`/etc/lamalibre/lamaste/panel.json`, `tunnels.json`) with atomic writes (write to temp → fsync → rename). No database is needed at this scale.
|
|
95
95
|
|
|
96
|
-
The installer (`npx @lamalibre/create-lamaste@4.
|
|
96
|
+
The installer (`npx @lamalibre/create-lamaste@4.1.0`) is completely non-interactive — it detects the environment, provisions everything, and prints a summary. All configuration happens through the browser-based onboarding wizard after installation.
|
|
97
97
|
|
|
98
98
|
## Quick Reference
|
|
99
99
|
|
|
100
100
|
| Item | Value |
|
|
101
101
|
| -------------------- | ------------------------------- |
|
|
102
102
|
| **Minimum VPS** | 512MB RAM, Ubuntu 24.04 |
|
|
103
|
-
| **Install command** | `npx @lamalibre/create-lamaste@4.
|
|
103
|
+
| **Install command** | `npx @lamalibre/create-lamaste@4.1.0` |
|
|
104
104
|
| **Admin panel** | `https://<ip>:9292` (mTLS) |
|
|
105
105
|
| **Auth for admin** | Client certificate (zero-login) |
|
|
106
106
|
| **Auth for apps** | TOTP 2FA via Authelia |
|
|
@@ -108,5 +108,5 @@ The installer (`npx @lamalibre/create-lamaste@4.0.0`) is completely non-interact
|
|
|
108
108
|
| **TLS certificates** | Let's Encrypt (auto-renewing) |
|
|
109
109
|
| **State storage** | JSON files (no database) |
|
|
110
110
|
| **RAM usage** | ~245MB total (all services) |
|
|
111
|
-
| **npm package** | `@lamalibre/create-lamaste@4.
|
|
111
|
+
| **npm package** | `@lamalibre/create-lamaste@4.1.0` |
|
|
112
112
|
| **License** | Polyform Noncommercial 1.0.0 |
|
|
@@ -371,7 +371,51 @@ Authelia requires three secrets, all generated with `crypto.randomBytes`:
|
|
|
371
371
|
| `sessionSecret` | Encrypts session cookies | `/etc/authelia/configuration.yml` |
|
|
372
372
|
| `storageEncryptionKey` | Encrypts SQLite database | `/etc/authelia/configuration.yml` |
|
|
373
373
|
|
|
374
|
-
A copy is also stored in `/etc/authelia/.secrets.json` (mode `600`) for reference. The configuration file itself is mode `600` to prevent unauthorized reading of these secrets.
|
|
374
|
+
A copy is also stored in `/etc/authelia/.secrets.json` (mode `600`) for reference. The configuration file itself is mode `600` to prevent unauthorized reading of these secrets. When an OpenID Connect provider is configured (below), `.secrets.json` also holds `oidcHmacSecret`.
|
|
375
|
+
|
|
376
|
+
### OpenID Connect provider
|
|
377
|
+
|
|
378
|
+
Authelia is also the relay's OpenID Connect provider, at `https://auth.<domain>` (`/.well-known/openid-configuration`, `/jwks.json`, `/api/oidc/token`, `/api/oidc/device-authorization`, `/api/oidc/userinfo` — all served by the auth vhost's plain proxy location, without forward auth). The clients come from two places: every enabled plugin's manifest (`oidcClients` — e.g. Feria's `feria-cli`, a public device-code client so `feria login` authenticates through the relay) and the panel itself (`lamaste-server`, a confidential client-credentials client, when the registry credential uses `auth.kind: oidc`). The panel rewrites `identity_providers.oidc` after every plugin lifecycle change and every registry-credential change, restarting Authelia only when the section actually changed, and removes the section when no client is left (Authelia refuses an `oidc` section without clients).
|
|
379
|
+
|
|
380
|
+
```yaml
|
|
381
|
+
identity_providers:
|
|
382
|
+
oidc:
|
|
383
|
+
hmac_secret: <random-64-byte-hex>
|
|
384
|
+
jwks:
|
|
385
|
+
- key_id: lamaste-oidc
|
|
386
|
+
algorithm: RS256
|
|
387
|
+
use: sig
|
|
388
|
+
key: { { secret "/etc/authelia/oidc-issuer.pem" | mindent 10 "|" | msquote } }
|
|
389
|
+
lifespans:
|
|
390
|
+
device_code: 10 minutes
|
|
391
|
+
claims_policies:
|
|
392
|
+
feria-cli:
|
|
393
|
+
access_token:
|
|
394
|
+
- groups
|
|
395
|
+
- preferred_username
|
|
396
|
+
clients:
|
|
397
|
+
- client_id: feria-cli
|
|
398
|
+
client_name: Feria CLI
|
|
399
|
+
public: true
|
|
400
|
+
authorization_policy: two_factor
|
|
401
|
+
consent_mode: explicit
|
|
402
|
+
scopes: [openid, profile, groups]
|
|
403
|
+
grant_types: ['urn:ietf:params:oauth:grant-type:device_code']
|
|
404
|
+
token_endpoint_auth_method: none
|
|
405
|
+
access_token_signed_response_alg: RS256
|
|
406
|
+
requested_audience_mode: explicit
|
|
407
|
+
claims_policy: feria-cli
|
|
408
|
+
audience: ['https://feria.example.com']
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Key choices:
|
|
412
|
+
|
|
413
|
+
- **The issuer key is Authelia's alone.** `/etc/authelia/oidc-issuer.pem` (RSA 4096) is generated by `create-lamaste` as the `lamaste-authelia` account, mode `0600`. The panel, which writes the configuration, cannot read it: the configuration carries a template expression instead of the key, and Authelia's unit (and `lamaste-priv authelia-totp`, which runs Authelia's CLI) sets `X_AUTHELIA_CONFIG_FILTERS=template` so Authelia expands it at load time. Because the whole file is then a Go template, the panel refuses to write any value containing `{{` or `}}`. The panel reads the file back by swapping that one line for a placeholder before parsing it as YAML.
|
|
414
|
+
- **JWT access tokens.** `access_token_signed_response_alg: RS256` makes access tokens RFC 9068 JWTs a resource server (Feria) verifies against `/jwks.json`. `groups` and `preferred_username` are UserInfo claims by default; the per-client **claims policy** copies them into the access token when the client declares `accessTokenClaims` — only for claims whose scope was granted.
|
|
415
|
+
- **Confidential clients** carry a pbkdf2-sha512 digest of their secret (`$pbkdf2-sha512$310000$<salt>$<key>`, salt and key in go-crypt's adapted base64: `+` written as `.`, no padding). The panel generates the secret, keeps the plaintext in `/etc/lamalibre/lamaste/oidc-clients.json` (0600), and shows it once.
|
|
416
|
+
- **Client credentials.** For the panel's own `lamaste-server` client Authelia sets both `sub` and `client_id` to the client id (there is no end user), which is also why such a client must have `authorization_policy: one_factor` and no `openid`/`offline_access` scope — Authelia refuses the configuration otherwise. The panel calls the token endpoint on `127.0.0.1:9091` with `X-Forwarded-Proto: https` and `X-Forwarded-Host: auth.<domain>`, which is where Authelia takes the issuer from, so the JWT says `iss: https://auth.<domain>` exactly as through nginx.
|
|
417
|
+
|
|
418
|
+
See [Registry Credential and OIDC Clients](../04-api-reference/registry-and-oidc.md) for the manifest fields and the API.
|
|
375
419
|
|
|
376
420
|
### Safety rule: never delete the last user
|
|
377
421
|
|
|
@@ -29,7 +29,7 @@ For the IP-based admin panel (`https://<ip>:9292`), there is no domain to verify
|
|
|
29
29
|
|
|
30
30
|
### When certificates are issued
|
|
31
31
|
|
|
32
|
-
- **Self-signed + mTLS certs** — created during `npx @lamalibre/create-lamaste@4.
|
|
32
|
+
- **Self-signed + mTLS certs** — created during `npx @lamalibre/create-lamaste@4.1.0` installation
|
|
33
33
|
- **Core Let's Encrypt certs** — issued during onboarding for `panel`, `auth`, and `tunnel` subdomains
|
|
34
34
|
- **App Let's Encrypt certs** — issued when you create a new tunnel or a managed static site
|
|
35
35
|
- **Custom-domain site certs** — issued when a custom-domain site's DNS is verified: one certificate lineage, named after the site's domain, covering the domain and every redirect alias (re-issued when aliases change)
|
|
@@ -82,7 +82,7 @@ The most critical scenario is the mTLS CA expiring, but it has a 10-year validit
|
|
|
82
82
|
|
|
83
83
|
### Let's Encrypt implementation
|
|
84
84
|
|
|
85
|
-
Lamaste uses certbot with the nginx plugin for certificate issuance. The implementation lives in `@lamalibre/lamaste@4.
|
|
85
|
+
Lamaste uses certbot with the nginx plugin for certificate issuance. The implementation lives in `@lamalibre/lamaste@4.1.0/server` (`packages/core/lib/src/server/certbot.ts`), wrapped by `packages/server/daemon/src/lib/certbot.js`.
|
|
86
86
|
|
|
87
87
|
The panel runs as the unprivileged `lamaste` user and never runs `certbot` or `openssl` on Let's Encrypt files directly. It calls two root-owned wrapper scripts through sudo:
|
|
88
88
|
|
|
@@ -193,7 +193,7 @@ This three-step check prevents unnecessary certificate issuance and respects rat
|
|
|
193
193
|
|
|
194
194
|
#### Custom-domain site certificate issuance
|
|
195
195
|
|
|
196
|
-
A custom-domain static site may have redirect aliases (e.g. `www.myblog.net`). `issueSiteCert(fqdn, aliases, email)` in `@lamalibre/lamaste@4.
|
|
196
|
+
A custom-domain static site may have redirect aliases (e.g. `www.myblog.net`). `issueSiteCert(fqdn, aliases, email)` in `@lamalibre/lamaste@4.1.0/server` keeps them on **one** lineage named after the primary domain:
|
|
197
197
|
|
|
198
198
|
```bash
|
|
199
199
|
sudo /usr/local/sbin/lamaste-certbot issue admin@example.com myblog.net myblog.net www.myblog.net
|
|
@@ -25,7 +25,7 @@ You encounter mTLS during two moments:
|
|
|
25
25
|
|
|
26
26
|
### Importing the certificate
|
|
27
27
|
|
|
28
|
-
After running `npx @lamalibre/create-lamaste@4.
|
|
28
|
+
After running `npx @lamalibre/create-lamaste@4.1.0`, the installer prints something like:
|
|
29
29
|
|
|
30
30
|
```
|
|
31
31
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -101,7 +101,7 @@ When connecting a machine to Lamaste using `lamaste-agent`, you should **not** u
|
|
|
101
101
|
2. Click "Generate" and enter a label (e.g., `macbook-pro`)
|
|
102
102
|
3. Save the displayed password — it cannot be retrieved later
|
|
103
103
|
4. Download the `.p12` file
|
|
104
|
-
5. Share the `.p12` file and password with the agent machine through a secure channel, then install the agent there (`npm install -g @lamalibre/lamaste-agent@4.
|
|
104
|
+
5. Share the `.p12` file and password with the agent machine through a secure channel, then install the agent there (`npm install -g @lamalibre/lamaste-agent@4.1.0`) and run `lamaste-agent setup` (an enrollment token avoids moving the key at all — see [Agent Setup](../02-guides/agent-setup.md))
|
|
105
105
|
|
|
106
106
|
Agent certificates have capability-based access. By default, a new agent can only read tunnel configuration. Admins can grant additional capabilities per-agent from the panel UI:
|
|
107
107
|
|
|
@@ -264,9 +264,9 @@ The integration flow:
|
|
|
264
264
|
}
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
### Client SDK (`@lamalibre/lamaste-tickets@4.
|
|
267
|
+
### Client SDK (`@lamalibre/lamaste-tickets@4.1.0`)
|
|
268
268
|
|
|
269
|
-
The `@lamalibre/lamaste-tickets@4.
|
|
269
|
+
The `@lamalibre/lamaste-tickets@4.1.0` package is a TypeScript SDK that provides the client-side ticket lifecycle for plugins and agents. It uses `undici` for HTTP with mTLS dispatcher support and has no other runtime dependencies.
|
|
270
270
|
|
|
271
271
|
The SDK exports three main classes:
|
|
272
272
|
|
|
@@ -144,7 +144,7 @@ On the API side the same rule holds: `GET /api/tunnels/agent-config` returns onl
|
|
|
144
144
|
|
|
145
145
|
### Client configuration
|
|
146
146
|
|
|
147
|
-
The Chisel client runs as a user-level service managed by `lamaste-agent` (and `lamaste-agentd`): a LaunchAgent plist on macOS, a systemd **user** unit on Linux. Both are generated by one implementation in `@lamalibre/lamaste@4.
|
|
147
|
+
The Chisel client runs as a user-level service managed by `lamaste-agent` (and `lamaste-agentd`): a LaunchAgent plist on macOS, a systemd **user** unit on Linux. Both are generated by one implementation in `@lamalibre/lamaste@4.1.0/agent`. The client command looks like:
|
|
148
148
|
|
|
149
149
|
```bash
|
|
150
150
|
chisel client \
|
|
@@ -208,7 +208,7 @@ location / {
|
|
|
208
208
|
|
|
209
209
|
### Concurrency and locking
|
|
210
210
|
|
|
211
|
-
Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH`, releasing a revoked agent's tunnels, and startup reconciliation — runs under one tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.
|
|
211
|
+
Every tunnel workflow — create, delete, toggle, assign, reconfigure, the combined `PATCH`, releasing a revoked agent's tunnels, and startup reconciliation — runs under one tunnel lock (`withTunnelLock` in `@lamalibre/lamaste@4.1.0/server`'s `tunnels.ts`), with its validation inside the lock. Two concurrent requests therefore cannot both pass a uniqueness check for the same subdomain or port. Authfile renders are additionally serialized with a keyed promise-chain mutex in `chisel-users.ts`, so enrollments and credential rotations never interleave authfile writes with tunnel changes.
|
|
212
212
|
|
|
213
213
|
### Startup reconciliation
|
|
214
214
|
|
|
@@ -223,7 +223,7 @@ It **fails closed**: if the authfile cannot be established, Chisel is stopped **
|
|
|
223
223
|
|
|
224
224
|
### Installation
|
|
225
225
|
|
|
226
|
-
On the server, Chisel is installed by `npx @lamalibre/create-lamaste@4.
|
|
226
|
+
On the server, Chisel is installed by `npx @lamalibre/create-lamaste@4.1.0` — as root, together with its hardened systemd unit (`User=nobody`, `Group=lamaste-chisel`, `NoNewPrivileges`, `ProtectSystem=strict`, empty `CapabilityBoundingSet`) — and kept at the pinned release by every redeploy. The panel never installs a binary or writes the unit; onboarding only checks the binary exists, creates the server key and authfile, and starts the service. Both the server installer and the agents download the pinned release (`CHISEL_RELEASE` in `@lamalibre/lamaste@4.1.0`'s `constants.ts`) from its fixed URL, with no GitHub API lookup and no "latest":
|
|
227
227
|
|
|
228
228
|
```
|
|
229
229
|
https://github.com/jpillora/chisel/releases/download/v1.12.0/chisel_1.12.0_<os>_<arch>.gz
|
|
@@ -320,7 +320,7 @@ sudo systemctl restart chisel
|
|
|
320
320
|
|
|
321
321
|
```bash
|
|
322
322
|
# Install globally, then set up the agent (interactive)
|
|
323
|
-
npm install -g @lamalibre/lamaste-agent@4.
|
|
323
|
+
npm install -g @lamalibre/lamaste-agent@4.1.0
|
|
324
324
|
lamaste-agent setup --label <label>
|
|
325
325
|
|
|
326
326
|
# Apply tunnel changes now (the sync timer does it within 30 s anyway)
|
|
@@ -59,7 +59,7 @@ The token is single-use and expires after 10 minutes. `tunnels:read` is enough f
|
|
|
59
59
|
Install the agent **globally** first:
|
|
60
60
|
|
|
61
61
|
```bash
|
|
62
|
-
npm install -g @lamalibre/lamaste-agent@4.
|
|
62
|
+
npm install -g @lamalibre/lamaste-agent@4.1.0
|
|
63
63
|
```
|
|
64
64
|
|
|
65
65
|
A global install is required, not a convenience. Setup installs a sync timer (see [How tunnel changes reach the agent](#how-tunnel-changes-reach-the-agent)) that runs the installed `lamaste-agent` every 30 seconds for as long as the agent exists. An `npx` copy lives in npm's cache, which npm may clear at any time — the timer would silently stop. `setup` and `update` therefore refuse to run from `npx`, and they check this **before** an enrollment token is spent. For an installation npm does not manage, set `LAMALIBRE_LAMASTE_AGENT_CLI_PATH` to the absolute path of the `lamaste-agent` script the timer should run.
|
|
@@ -148,7 +148,7 @@ Each agent authenticates to the relay with its own chisel credential, stored in
|
|
|
148
148
|
Upgrade agents **before** the server. Agents from before this version reject the new shape of the relay's Chisel arguments; a current agent works against an older server, keeping its credential until the server offers rotation.
|
|
149
149
|
|
|
150
150
|
```bash
|
|
151
|
-
npm install -g @lamalibre/lamaste-agent@4.
|
|
151
|
+
npm install -g @lamalibre/lamaste-agent@4.1.0
|
|
152
152
|
lamaste-agent update
|
|
153
153
|
```
|
|
154
154
|
|
|
@@ -171,7 +171,7 @@ lamaste-agent uninstall --label <l> # stop and remove one agent, including its
|
|
|
171
171
|
|
|
172
172
|
| Symptom | Cause and fix |
|
|
173
173
|
| ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
-
| `setup` says the agent must be installed globally | It was run through `npx`. Run `npm install -g @lamalibre/lamaste-agent@4.
|
|
174
|
+
| `setup` says the agent must be installed globally | It was run through `npx`. Run `npm install -g @lamalibre/lamaste-agent@4.1.0`, then `lamaste-agent setup ...` again. The enrollment token was not spent. |
|
|
175
175
|
| Log shows a TLS / x509 error | `tunnel.<domain>` does not present a valid certificate for its name. Check the relay's certificate (`Certificates` page) and that the agent's clock is correct. |
|
|
176
176
|
| Log shows authentication failed | The credential was rotated on the panel. The next sync picks it up; `lamaste-agent chisel refresh-credential` applies it now. |
|
|
177
177
|
| Log shows `access denied` for a remote | The relay no longer grants this agent that port: the tunnel was disabled, deleted, or moved to another agent. The next sync drops it. If it persists, check `lamaste-agent status` — the sync timer may not be running. |
|
|
@@ -196,7 +196,7 @@ The admin certificate (`CN=admin`) has unrestricted access to all panel endpoint
|
|
|
196
196
|
|
|
197
197
|
### Distribute to the Agent Machine
|
|
198
198
|
|
|
199
|
-
Send the `.p12` file and password to the agent machine (macOS or Linux) securely, and install the agent there with `npm install -g @lamalibre/lamaste-agent@4.
|
|
199
|
+
Send the `.p12` file and password to the agent machine (macOS or Linux) securely, and install the agent there with `npm install -g @lamalibre/lamaste-agent@4.1.0` and run `lamaste-agent setup --label <label>` — or import it into the Desktop App. An enrollment token is preferable where possible, because the private key is then generated on the agent and never travels. See [Agent Setup](agent-setup.md) for details.
|
|
200
200
|
|
|
201
201
|
### Update Capabilities
|
|
202
202
|
|
|
@@ -293,7 +293,7 @@ Revocation is immediate — the certificate serial is added to a revocation list
|
|
|
293
293
|
|
|
294
294
|
### mTLS Rotation Internals
|
|
295
295
|
|
|
296
|
-
Admin certificate rotation lives only in `lamaste-server reset-admin` (`packages/server/cli/src/commands/reset-admin.js`), run as root on the server. The panel's `rotateClientCert()` in `lib/mtls.js` is a stub that returns `503` with that instruction; `@lamalibre/lamaste@4.
|
|
296
|
+
Admin certificate rotation lives only in `lamaste-server reset-admin` (`packages/server/cli/src/commands/reset-admin.js`), run as root on the server. The panel's `rotateClientCert()` in `lib/mtls.js` is a stub that returns `503` with that instruction; `@lamalibre/lamaste@4.1.0/server` no longer exports a rotation function. The PKI directory belongs to `lamaste`, so `reset-admin` runs every file operation inside it (`openssl genrsa`/`req`/`x509 -req`/`pkcs12`, `cp`, `chmod`) as `lamaste`, and writes `panel.json` and `revoked.json` as fresh files renamed into place — root never writes through a path the panel could have replaced with a symlink.
|
|
297
297
|
|
|
298
298
|
The PKCS12 bundle uses legacy encryption settings (`-keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -macalg sha1`) because newer OpenSSL defaults produce files that macOS Keychain Access cannot import.
|
|
299
299
|
|
|
@@ -14,7 +14,7 @@ Before you start, make sure you have:
|
|
|
14
14
|
|
|
15
15
|
| Requirement | Details |
|
|
16
16
|
| ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
-
| **Desktop app installed** | `npx @lamalibre/create-lamaste-desktop@4.
|
|
17
|
+
| **Desktop app installed** | `npx @lamalibre/create-lamaste-desktop@4.1.0` ([setup guide](desktop-app-setup.md)) |
|
|
18
18
|
| **DigitalOcean account** | A free account at [cloud.digitalocean.com](https://cloud.digitalocean.com). **If you have other infrastructure on DO, create a dedicated team first** (see below) |
|
|
19
19
|
| **Payment method** | A credit card or PayPal on file in DigitalOcean (required to create droplets) |
|
|
20
20
|
| **Domain name** | Optional for initial setup — you can add a domain later through the panel |
|
|
@@ -221,7 +221,7 @@ The wizard shows up to 12 steps with live progress indicators. Each step shows a
|
|
|
221
221
|
| **Waiting for boot** | Polls DigitalOcean until the droplet has a public IP and is ready (up to 5 minutes) |
|
|
222
222
|
| **Setting up DNS records** | Creates A and wildcard A records in DigitalOcean DNS (only if a domain was selected in the Domain step) |
|
|
223
223
|
| **Connecting via SSH** | Establishes an SSH connection using the temporary key |
|
|
224
|
-
| **Installing Lamaste** | Runs `npx @lamalibre/create-lamaste@4.
|
|
224
|
+
| **Installing Lamaste** | Runs `npx @lamalibre/create-lamaste@4.1.0` on the droplet (up to 10 minutes) |
|
|
225
225
|
| **Retrieving credentials** | Downloads the admin certificate (`.p12` file) from the droplet |
|
|
226
226
|
| **Enrolling admin certificate** | Imports the certificate into your OS credential store |
|
|
227
227
|
| **Saving configuration** | Writes the server entry to `~/.lamalibre/lamaste/servers.json` |
|
|
@@ -315,7 +315,7 @@ If you already have a Lamaste server set up (via the manual SSH method), you can
|
|
|
315
315
|
|
|
316
316
|
The app checks that the panel is reachable before adding it. The server is added with `active: false` — click **Set Active** on its card to start using it.
|
|
317
317
|
|
|
318
|
-
> **Note:** Adding an existing server registers it in the app but does not configure authentication. You will still need to set up your agent certificate separately using `lamaste-agent setup` (after `npm install -g @lamalibre/lamaste-agent@4.
|
|
318
|
+
> **Note:** Adding an existing server registers it in the app but does not configure authentication. You will still need to set up your agent certificate separately using `lamaste-agent setup` (after `npm install -g @lamalibre/lamaste-agent@4.1.0`) or by importing a certificate manually.
|
|
319
319
|
|
|
320
320
|
---
|
|
321
321
|
|