The web editor lets people who do not use Git change catalogues, topology, steps and procedures from the web console. Each change is a draft: a branch and a working copy of its own, with the language server running on it and dry runs of its procedures. A draft reaches the configuration only through a review on the forge (a GitLab merge request or a GitHub pull request): the editor never writes to the target branch.
sequenceDiagram
actor U as Operator
participant W as Web console
participant A as API (/v1/editor relay)
participant E as stellar-editor
participant G as Git remote / forge
U->>W: Start a draft
W->>A: POST /v1/editor/drafts
A->>E: relayed
E->>G: fetch the target branch
E->>E: branch editor/<user>/<id>, working copy
U->>W: edit files (LSP over WebSocket)
U->>W: Dry run…
W->>E: POST …/dry-run (stellar run --dry)
U->>W: Propose for review
E->>G: commit (author: the user), push, open or update the review
G-->>G: review, approvals, mergeArchitecture#
The editor is a service of its own, stellar-editor, separate from the API so that the API holds
neither Git nor forge credentials. The API relays /v1/editor and everything under it, HTTP and
WebSocket, to the address of api.editor_url, with the same identity headers; the web
application reaches it on the same origin.
| Relay error | Status | Why |
|---|---|---|
editor::disabled | 404 | No api.editor_url: no editor behind this API |
editor::unreachable | 502 | The editor service does not answer |
editor::too-large | 413 | A request body over 16 MiB |
The all-in-one binary stellar-mcs runs the editor when editor.repository is set.
Configuration#
api:
editor_url: http://127.0.0.1:8090
editor:
listen: 127.0.0.1:8090
workdir: /var/lib/stellar/editor
repository: https://gitlab.example.org/ops/mcs-config.git
branch: main
path: .
forge:
kind: gitlab # gitlab, github, none or direct
api: https://gitlab.example.org/api/v4
project: ops/mcs-config
token_env: STELLAR_FORGE_TOKEN
cli: stellar
declared_identity: true| Key | Default | Meaning |
|---|---|---|
editor.listen | 127.0.0.1:8090 | Listen address of the service |
editor.workdir | /var/lib/stellar/editor | Its clone of the repository and the working copies of the drafts |
editor.repository | unset | Git remote of the configuration repository (a URL or a path) |
editor.branch | main | Branch the reviews target, and drafts start from |
editor.path | . | The configuration repository within the Git repository (examples/config) |
editor.forge.kind | none | gitlab: merge requests; github: pull requests; none: branches pushed, no review opened; direct: no review, a proposed draft merged and published at once (a sandbox) |
editor.forge.api | https://gitlab.com/api/v4 or https://api.github.com | API of a self-hosted forge |
editor.forge.project | '' | group/repository (GitLab) or owner/repository (GitHub) |
editor.forge.token_env | STELLAR_FORGE_TOKEN | Environment variable holding the service token of the MCS |
editor.cli | stellar | The stellar command, for dry runs |
editor.declared_identity | true | Whether declared identities are accepted; false: OIDC tokens only |
The service token of the MCS pushes the branches and opens the reviews; give it the rights to
push branches and open merge or pull requests, and protect the target branch so that only a
reviewed merge enters it. In a cell, it is the secret forge_token
(stellar-admin cell secret <cell> forge_token).
Drafts#
- Start. A draft has a title (the title of its review). It is a branch
editor/<user>/<id>, started from the target branch as it is on the remote at that moment, in a working copy of its own. - Ownership. Only the user who started a draft may change it, propose it or abandon it; everyone else sees it read-only.
- Files. Reading, writing and deleting files stay within the configuration repository of the
working copy: never in
.git, never outside. - Changes. The draft lists the files it adds, modifies and deletes against the target branch, and shows their diff.
- Abandon. Removes the working copy and the local branch; a review already opened stays on the forge.
Language server#
Each open draft runs stellar-lsp on its working copy, over a WebSocket
(/v1/editor/drafts/{id}/lsp). The web console uses CodeMirror 6 and its official LSP client:
live diagnostics, completion by role, hover and definition, exactly as in
VS Code. The editor accepts only file URIs within the draft: a message naming any
other file is refused.
Dry runs#
Dry run… runs the procedure under the cursor after asking for its inputs, or a run request of
the draft: the service saves the request, runs stellar run --dry on the working copy (at the
speed chosen, 0.1 to 100), and streams its output as it comes, ending with exit <code>. At most
two dry runs go at once (429 editor::busy otherwise); each starts its own local NATS server and
components, so the host needs nats-server. See Simulated Targets.
Review#
Propose for review commits the changes of the draft, pushes its branch and opens the review, or
finds the one already open, which the push updates. The commits are authored by the user
(OIDC or declared identity) and committed by the MCS (Stellar Control editor); the description
of the review says who proposed it. With forge.kind: none, the branch is pushed and nothing
else.
Without review: direct#
A sandbox of one user, such as the cell of an online trial, has no forge and nobody to review:
with forge.kind: direct, Publish makes the draft the current configuration at once.
- The configuration of the draft is compiled (
stellar compile): when it does not compile, nothing else happens and the errors come back (422 editor::invalid). - Its changes are committed as the user and merged into the target branch as it is now (the drafts published meanwhile included); a conflict with them stops here.
- The target branch is pushed, and the merged configuration compiled again and published to
the NATS server of the editor (
nats.url, its credentials and TLS): the reconciler applies it, and its journal names the user as its publisher.
The draft then shows its publication (the commit of the target branch and the snapshot) and
starts again from there. The remote takes pushes to its checked-out branch in a cell
(receive.denyCurrentBranch=updateInstead), so its files, which the next start of the cell
compiles, follow.
Identity#
The editor identifies its users like the API: a checked OIDC token first
(Authorization: Bearer, against auth.jwks_file), else a declared identity (X-Stellar-User)
unless editor.declared_identity: false.
| Code | Status | Why |
|---|---|---|
auth::invalid-token | 401 | Token refused |
auth::no-provider | 401 | A token, but no auth.jwks_file |
auth::token-required | 401 | A declared identity, refused by editor.declared_identity: false |
auth::anonymous | 401 | No identity |
A WebSocket opened by a browser cannot carry headers: the identity then comes in its query
(?token=… or ?user=…).
HTTP routes#
The routes of the editor, under /v1/editor behind the API. Errors have the body of the API
({"errors": [{code, message}]}).
| Route | Effect |
|---|---|
GET /v1/editor | Target branch, forge kind and project |
GET /v1/editor/drafts | Every draft, newest first |
POST /v1/editor/drafts | Starts a draft: {"title"} |
GET /v1/editor/drafts/{id} | A draft, its files and its changes |
DELETE /v1/editor/drafts/{id} | Abandons a draft |
GET, PUT, DELETE /v1/editor/drafts/{id}/files/{path} | Reads, writes (plain text body) or deletes a file |
GET /v1/editor/drafts/{id}/diff | Its diff against the target branch |
POST /v1/editor/drafts/{id}/review | Proposes it for review: {"message"} optional |
POST /v1/editor/drafts/{id}/dry-run?speed=X | Dry run of the run request of the body (YAML), output streamed |
GET /v1/editor/drafts/{id}/lsp | The language server of the draft, over WebSocket |