Optional IDE provisioning and named members¶
This optional extension supplies a JetBrains remote backend; it is not required
for the CLI workflow in CLI-first arena workflow. Provisioning is a separate factory
context, not an escape hatch from an execution arena. Plain CLion/PyCharm need
neither AI Assistant nor Junie nor a vendor-egress exception. Ordinary managed
arena.py create/add does not expose backend/seed flags.
The current lower-level tooling imports a backend, snapshots reviewed UI files and initializes runtime copies. Accepted policy keeps acquisition/download access separate from execution: the arena retains its inference-only external path, with no JetBrains/vendor Internet route. Real downloader, GUI, licensing and offline reconnect acceptance remain manual/unverified, not fixture-certified. See Known issues and assurance limits for broader runtime and containment limits.
Three-stage provisioning factory¶
---
config:
flowchart:
nodeSpacing: 18
rankSpacing: 25
---
flowchart TD
V["Vendor artifacts / offline inputs<br/>origin review required"]
B["Separate reviewed backend<br/>pinned build/platform + manifest"]
P["(1) Authorized scratch provisioning<br/>compatible dev image; no task/secrets"]
R["(2) Human review / snapshot<br/>stopped writers; exact UI files + hashes"]
S["Reusable UI-only seed<br/>pinned image/profile/build"]
M["(3) Private untrusted member runtime<br/>RW copies; no JetBrains Internet route"]
I["Existing broker / egress policy<br/>permitted inference export only"]
V -->|"trusted acquisition + verification"| B
V -.->|"conditional authorized provisioning access only"| P
B -->|"backend mounted RO"| P
P -->|"candidate settings; not automatically trusted"| R
R -->|"sanitize + hash + snapshot"| S
S -->|"copy once into new compatible runtime"| M
B -->|"backend mounted RO; staged integration only"| M
M -->|"permitted inference export only"| I
classDef external fill:#eeeeee,stroke:#666666,color:#333333
classDef guard fill:#e4effb,stroke:#35669a,color:#193d62
classDef accepted fill:#e4f0e6,stroke:#477650,color:#25472b
classDef untrusted fill:#fbe1dc,stroke:#a74432,color:#54251e
class V external
class B,P,R,I guard
class S accepted
class M untrusted
Figure 1. Provisioning → reviewed seed → private runtime: a conceptual lifecycle, not proof of automated factory or real IDE acceptance.¶
Provision/acquire: the operator selects a compatible development image, independently reviewed backend and client materials. Default scratch provisioning is offline/internal. Any temporary download access needs separate authorization;
--allow-downloadsselectspasta, not a vendor-only allowlist. The operator must assess its reachable networks before starting it.Review/promote: stop host/container writers, inspect only selected UI preferences, remove sensitive content and snapshot the reviewed hashes. Backend and seed remain separate pinned inputs; a changed scratch runtime is not a seed.
Initialize/use: trusted setup copies the seed into a new member’s private runtime and mounts the backend read-only. Reusing a seed means independent copies, never one shared writable tree. New copies require the exact recorded image ID/profile and matching backend product/build/platform. Initialized runtime is preserved rather than reseeded on supported replacement; persistence is not backup. Runtime edits never synchronize to the seed. A new seed requires a separate authorized provisioning/review/snapshot cycle.
Blue labels identify operator controls, green the accepted reusable input, gray external inputs and red the mutable untrusted runtime. Words and copy/RO/RW labels carry the same meaning without color. These are documented stages, not a new one-command factory or managed IDE-member deployment interface.
Select the reference variables explicitly: TOOLS is the absolute trusted
tools/ai directory, ROOT the private management root, ARENA an existing
arena name and EVIDENCE a new private host evidence directory outside mounts.
These are optional provisioning inputs, not variables from the CLI recipe.
Layout and prerequisites¶
ROOT defaults to $HOME/.local/share/dev-environment when not overridden;
keep it outside all task mounts. The following map describes setup.py’s
current lower-level preparation, including scratch provisioning and staged
IDE-enabled development members. It does not add IDE support to managed add.
ROOT/ private host management root
artifacts/jetbrains/ARTIFACT/
backend/ reviewed backend files
metadata.json separate origin/integrity record
seed/jetbrains/SEED_PROFILE/
config/ selected reviewed UI files
cache/ empty in accepted snapshot
share/ empty in accepted snapshot
metadata.json exact identity/hash selection
runtime/ENVIRONMENT/
jetbrains/{config,cache,share}/ private mutable copies
state/ XDG state; NOT jetbrains/state
agents/{config,cache,data,state,codex}/ separate agent state
metadata.json initialization/selection record
cache/ENVIRONMENT/ccache/ optional development compiler cache
ssh/ENVIRONMENT/ private identities/pins + RO guards
quadlet/ENVIRONMENT.* staged source units
PROVISION_SCRATCH/ new empty scratch workspace OUTSIDE ROOT
IDE_SCRATCH/ distinct scratch member workspace
---
config:
flowchart:
nodeSpacing: 20
rankSpacing: 30
subGraphTitleMargin:
bottom: 45
top: 8
---
flowchart TD
subgraph HOST["Private host ROOT"]
B["artifacts/jetbrains/ARTIFACT/backend<br/>operator-controlled reviewed input"]
S["seed/jetbrains/SEED_PROFILE<br/>config + empty cache/share; not mounted"]
R["runtime/ENVIRONMENT/jetbrains<br/>config / cache / share: private RW copies"]
X["runtime/ENVIRONMENT/state + agents<br/>separate private mutable state"]
G["ssh/ENVIRONMENT + cache/ENVIRONMENT<br/>RO guards / optional RW ccache"]
end
W["Separate selected workspace/data<br/>scratch only during provisioning"]
subgraph MEMBER[" "]
MB["/opt/jetbrains/backend<br/>RO backend"]
MR["/home/ci/.config + .cache + .local/share<br/>RW private IDE state"]
MX["/home/ci/.local/state + .local/ai-agent<br/>RW separate state"]
MG["RO SSH/agent/cache guards<br/>RW optional compiler cache"]
MW["/home/ci/workspace + selected data<br/>mutable task contents"]
end
S -->|"initial copy only; never shared RW"| R
B -->|"RO bind"| MB
R -->|"RW binds"| MR
X -->|"RW binds"| MX
G -->|"distinct RO/RW binds; see table"| MG
W -->|"workspace RW; data RO/RW as approved"| MW
classDef guard fill:#e4effb,stroke:#35669a,color:#193d62
classDef external fill:#eeeeee,stroke:#666666,color:#333333
classDef untrusted fill:#fbe1dc,stroke:#a74432,color:#54251e
style HOST fill:#f8fafc,stroke:#87939e
style MEMBER fill:#f8fafc,stroke:#87939e
class B,S,G guard
class R,X external
class MB,MR,MX,MG,MW,W untrusted
Figure 2. Host inputs and private copies mapped into one untrusted member; arrows describe copy or bind access, not containment certification.¶
Host source |
Member destination |
Access / lifecycle |
|---|---|---|
|
|
RO; separate reviewed artifact |
|
|
RW; initial seed copy |
|
|
RW; initial empty seed copy |
|
|
RW; initial empty seed copy |
|
|
RW; newly created, not seeded |
|
|
RW; separate agent state |
|
|
RW only when enabled; dev only, not provisioning |
|
|
RO when cache policy selected |
|
|
RO dedicated member SSH material |
|
|
RO host-controlled launchers/guards |
Selected workspace and optional data outside ROOT |
|
Workspace RW; selected data RO/RW; no data in provisioning |
The agent launcher sets its XDG config/cache/data/state beneath
/home/ci/.local/ai-agent/{config,cache,data,state} and CODEX_HOME beneath
.../codex; these are not IDE seed contents. No provider credential belongs
in IDE/agent runtime or reusable seed state. RO describes container bind access,
not host filesystem immutability. Backend/seed identity manifests detect changes.
Setup stages directories at mode 0700 and non-script files at 0600 (scripts 0755),
under the deployment user’s ownership; keep-id uses inspected image ci IDs.
Private SELinux mode uses Z for runtime/guards/workspace, but the reusable
backend uses z. These labels do not authorize relabeling unrelated host trees.
Host shell means Bash on the Linux rootless Podman workstation, ordinary user,
using trusted tools/ai outside execution mounts. Python 3.11+, jq, OpenSSH,
working keep-id storage and a reachable user systemd/Quadlet are prerequisites.
Run from a non-traced shell and stop at every failed precondition/command; do
not copy later blocks over a partial attempt. Evidence directories must exist
privately outside all execution mounts, with each selected output absent.
The canonical root must be private/user-owned; symlink components, existing
artifact/seed names, nonprivate ancestors and common-lock contention are refused,
not overwritten. Do not chmod/relabel unrelated trees to bypass those checks.
Acquisition and origin evidence¶
On an authorized connected acquisition host, obtain and authenticate the downloader through the official offline guide. No real downloader was supplied during implementation. Inspect its actual help first:
: "${DOWNLOADER:?approved absolute path to authenticated supplied downloader}"
/bin/bash "$DOWNLOADER" --help
The guide documented -h as an alternative; use that only for the supplied
version if needed. No download flags are prescribed here without actual help.
Confirm bounded product/build/platform filters, output semantics and budgets
before acquisition; missing filters can download everything. Select one exact
supported product/build/backend platform (no latest, wildcard, version range or
unapproved EAP) and matching client/JBR for each local client platform. Preserve
products.json, authenticated vendor KEYS and independent verification
evidence. A download’s own hash is not origin verification. PyCharm edition and
remote-development support require separate confirmation.
Create ORIGIN_JSON outside the arena from actual independent verification,
replacing every placeholder:
{
"schema_version": 1,
"source_url": "https://download.jetbrains.com/REPLACE-WITH-EXACT-ARCHIVE-PATH",
"vendor_sha256": "REPLACE-WITH-64-LOWERCASE-HEX-FROM-INDEPENDENT-VENDOR-EVIDENCE",
"verification_method": "vendor-checksum",
"verification_reference": "REPLACE-WITH-CHECKSUM-URL-OR-VERIFIED-SIGNATURE-AND-KEY-REFERENCE",
"verified_by": "REPLACE-WITH-OPERATOR",
"verified_at": "REPLACE-WITH-VERIFICATION-TIMESTAMP"
}
verification_method also accepts vendor-signature. Import records operator
attestation, checks official JetBrains HTTPS origin fields and checksum equality;
it does not fetch vendor metadata or independently verify signatures.
Import and scratch-only provisioning¶
Assign these values from the reviewed backend archive, its metadata, the chosen
image’s embedded provenance and the operator’s free-port/subnet inventory.
All paths must be absolute. PROVISION_SCRATCH and IDE_SCRATCH must be
distinct new paths outside ROOT and all existing workspace/data mounts.
Use fresh product-specific artifact/seed/provisioning names. Do not mount personal
home, real projects/data, provider secrets or credential stores into provisioning.
: "${TOOLS:?absolute trusted tools/ai path}" "${ROOT:?private canonical root}"
: "${UNITS:?absolute user Quadlet source directory}" "${EVIDENCE:?private evidence directory outside mounts}"
: "${IMAGE:?reviewed preloaded compatible image}" "${IMAGE_PROFILE:?matching embedded profile}"
: "${PRODUCT:?clion or pycharm}" "${BUILD:?exact reviewed backend build}"
: "${PLATFORM:?matching linux-x64 or linux-arm64}" "${PRODUCT_DIR:?actual product config directory, e.g. CLion2026.2}"
: "${ARTIFACT:?new product-build-platform artifact name}" "${SEED_PROFILE:?new reviewed UI seed name}"
: "${APPROVED_ARCHIVE:?authenticated backend tar archive}" "${VENDOR_SHA256:?independently verified vendor checksum}"
: "${ORIGIN_JSON:?reviewed origin attestation JSON}" "${SELINUX_MODE:?none or private; private required on SELinux hosts}"
: "${PROVISION_NAME:?new scratch-only provisioning identity}"
: "${PROVISION_PORT:?unused host-loopback port, e.g. 2209}"
: "${PROVISION_SUBNET:?unused checked internal IPv4 /24, e.g. 10.92.0.0/24}"
: "${PROVISION_SCRATCH:?new dedicated empty provisioning workspace}"
: "${IDE_SCRATCH:?different new dedicated empty IDE member workspace}"
test "$PROVISION_SCRATCH" != "$IDE_SCRATCH"
mkdir -m 700 "$PROVISION_SCRATCH" "$IDE_SCRATCH"
python3 -B "$TOOLS/ide/jetbrains/provision.py" import --root "$ROOT" \
--name "$ARTIFACT" --product "$PRODUCT" --build "$BUILD" --platform "$PLATFORM" \
--archive "$APPROVED_ARCHIVE" --sha256 "$VENDOR_SHA256" --origin "$ORIGIN_JSON"
BACKEND="$ROOT/artifacts/jetbrains/$ARTIFACT/backend"
jq '{product,build,platform,archive_sha256,origin_evidence}' "$ROOT/artifacts/jetbrains/$ARTIFACT/metadata.json"
python3 -B "$TOOLS/setup.py" prepare --root "$ROOT" --role provision \
--name "$PROVISION_NAME" --port "$PROVISION_PORT" --image-profile "$IMAGE_PROFILE" \
--image "$IMAGE" --workspace "$PROVISION_SCRATCH" --subnet "$PROVISION_SUBNET" \
--backend "$BACKEND" --selinux "$SELINUX_MODE"
Import checks product-info.json product/build/Linux launch architecture and
safe single-top-directory tar extraction. ZIP/DMG/installers, hardlinks, sparse or
special files, duplicate/traversal paths and escaping/dangling/cyclic symlinks fail
closed; internal relative JBR links are retained without writing through links.
Default limits are 8 GiB compressed/summed member bytes and 200,000 entries;
--max-bytes/--max-members are explicit operator overrides. Owners/modes
are not preserved beyond executable status. Review the resulting manifest.
Preparation stages units, but starts nothing. Default provisioning has a checked
dedicated internal network and host-loopback SSH, never the development/egress
networks. Network=none cannot provide published SSH. If temporary downloads
are explicitly authorized, a new provisioning name with --allow-downloads
uses temporary pasta and stages no network unit; this is not runtime policy.
Provisioning cannot autostart and supplies no project/data/provider-secret mounts.
For default offline provisioning only, inspect the generated sources and verify no user/system unit-name collisions. After explicit operator authorization, install only absent sources (the selected user source directory must exist):
(
set -eu
for suffix in network container; do
test ! -e "$UNITS/$PROVISION_NAME.$suffix" && test ! -L "$UNITS/$PROVISION_NAME.$suffix"
done
set -C
for suffix in network container; do
cat "$ROOT/quadlet/$PROVISION_NAME.$suffix" > "$UNITS/$PROVISION_NAME.$suffix"
done
)
systemctl --user daemon-reload
systemctl --user start "$PROVISION_NAME.service"
ssh -F "$ROOT/ssh/$PROVISION_NAME/config" "$PROVISION_NAME" true
Do not apply the two-source example to connected pasta provisioning; that
branch has only its container source. Partial installation is not acceptance:
retain evidence, stop and reconcile rather than blindly retrying over conflicts.
Client GUI: standalone Gateway/plain IDE on the same workstation selects generated
pinned SSH configuration (127.0.0.1, selected provisioning port, ci),
existing backend /opt/jetbrains/backend and scratch project
/home/ci/workspace. Existing-backend GUI labels/switches are version-dependent;
confirm them first. Verify backend/client/JBR build, indexing, compiler/Python,
reconnect and shutdown. Keep host-side IDE credentials outside reusable state.
Licensing is a manual prerequisite. The current CLion registration and offline-work guide describe commercial offline activation codes obtained on an authorized connected machine. Non-commercial CLion cannot obtain such a code and requires initial online account activation; confirm the selected PyCharm requirements independently. License server/Vault access is an extra dependency, not baseline egress permission. Report GUI acceptance blocked if no approved offline-compatible licensing exists. Never put codes, cookies, license stores or account/password data in seeds/evidence.
Snapshot reviewed UI files only¶
Disconnect clients, close projects, stop every host/container writer and prevent
restarts throughout review/copying. Review individual UTF-8 regular files in the
provisioning runtime; sanitize them there, then record each actual SHA256 in
REVIEWED_SELECTION_JSON. Never recursively select the profile. Example
selection shape (replace directory/hash with the actual reviewed file):
{
"schema_version": 1, "reviewed": true,
"files": [
{"path": "config/JetBrains/CLion2026.2/options/editor.xml",
"sha256": "REPLACE-WITH-64-LOWERCASE-HEX-OF-REVIEWED-FILE"}
]
}
Allowlisted options are editor.xml, colors.scheme.xml,
code.style.schemes.xml, keymap.xml, laf.xml, ui.lnf.xml and
ide.general.xml; single keymaps/*.xml, colors/*.icls and
codestyles/*.xml files are also supported in the selected product directory.
Credential/path-history names and common content markers are rejected, but this
is not a comprehensive secret detector. Human review remains mandatory.
Limits: 1 MiB/file, 1,000 files/32 MiB total, no symlink ancestors; hashes include
valid empty files. Build/platform are operator declarations: verify against
backend metadata/GUI, not UI bytes alone.
Quadlet normally removes the container on stop. Create an unstarted network-none inspect record with the stopped runtime’s pinned image and exact mounts; it is not GUI evidence or a writer. Host shell:
systemctl --user stop "$PROVISION_NAME.service"
test "$(systemctl --user show "$PROVISION_NAME.service" -p ActiveState --value)" = inactive
RECORD="$PROVISION_NAME-seed-record"
PROVISION_IMAGE=$(jq -er .image_id "$ROOT/runtime/$PROVISION_NAME/metadata.json")
CI_UID=$(jq -er .ci_uid "$ROOT/runtime/$PROVISION_NAME/metadata.json")
CI_GID=$(jq -er .ci_gid "$ROOT/runtime/$PROVISION_NAME/metadata.json")
podman create --name "$RECORD" --pull=never --network=none --entrypoint=/bin/true \
--userns="keep-id:uid=$CI_UID,gid=$CI_GID" --user "$CI_UID:$CI_GID" \
-v "$ROOT/runtime/$PROVISION_NAME/jetbrains/config:/home/ci/.config:ro" \
-v "$ROOT/runtime/$PROVISION_NAME/jetbrains/cache:/home/ci/.cache:ro" \
-v "$ROOT/runtime/$PROVISION_NAME/jetbrains/share:/home/ci/.local/share:ro" "$PROVISION_IMAGE"
: "${REVIEWED_SELECTION_JSON:?explicit reviewed exact file/hash selection outside arena}"
python3 -B "$TOOLS/ide/jetbrains/provision.py" snapshot --root "$ROOT" \
--profile "$SEED_PROFILE" --runtime "$PROVISION_NAME" --container "$RECORD" \
--product "$PRODUCT" --build "$BUILD" --platform "$PLATFORM" \
--product-dir "$PRODUCT_DIR" --selection "$REVIEWED_SELECTION_JSON" --writers-stopped
podman rm "$RECORD"
jq . "$ROOT/seed/jetbrains/$SEED_PROFILE/metadata.json"
Snapshot checks image ID, stopped state/mounts and other active overlapping
containers before/after copying. The common setup lock cannot freeze unrelated
host writers. Keep them stopped; --writers-stopped is an acknowledgement, not
a substitute. The new seed’s full file/hash mapping, profile and exact image ID
are checked on runtime preparation. Cache/share are empty, with no backend,
plugins, recent projects, licenses, logs or agents. Existing names/incomplete
state are refused; use a new profile for each review.
Add a separate named IDE member¶
The CLI arena.py add does not currently expose backend/seed flags. Integrating
an IDE backend/seed into a managed member remains an advanced staged operation;
do not assume ordinary add installs an IDE. The legacy add-environment
interface accepts a reviewed member JSON with backend and seed fields,
but stages files only and does not establish managed deployment ownership.
It must not be mixed into the managed create/add/remove workflow without a
separately reviewed adoption/deployment plan. Preserve existing initialized CLI
state and keys. Optional offline GUI connection, licensing and reconnect remain
manual checks; no AI plugin or vendor-egress exception is required.
Gateway client, offline inputs and tunnel boundary¶
A seed supplies neither local client/JBR nor metadata hosting. Prestage matching
client/JBR, products.json and authenticated KEYS on the Gateway client.
On Linux, user-scoped files under ~/.config/JetBrains/RemoteDev/ select URLs.
Artifact hosting/trusted TLS is not implemented here. Any approved serving
service must be provisioning-only, loopback-only on a distinct port, with
read-only artifacts and no inference credentials/config; never reuse
ai-api:8080 or an SSH port. Preserve literal <PRODUCT_CODE>.
Client shell, only after supplying authenticated URLs and confirming the selected Gateway’s settings. Existing files require deliberate backup/review, not overwrite:
: "${BACKEND_URL_ROOT:?approved backend metadata URL root on client-accessible provisioning service}"
: "${CLIENT_URL:?matching client directory URL}" "${JBR_URL:?matching client JBR directory URL}"
: "${KEYS_URL:?authenticated vendor KEYS URL}"
SETTINGS="$HOME/.config/JetBrains/RemoteDev"
(
set -eu
for setting in productsInfoUrl clientDownloadUrl jreDownloadUrl pgpPublicKeyUrl; do
test ! -e "$SETTINGS/$setting" && test ! -L "$SETTINGS/$setting"
done
mkdir -p "$SETTINGS"
set -C
printf '%s\n' "$BACKEND_URL_ROOT/<PRODUCT_CODE>/products.json" > "$SETTINGS/productsInfoUrl"
printf '%s\n' "$CLIENT_URL" > "$SETTINGS/clientDownloadUrl"
printf '%s\n' "$JBR_URL" > "$SETTINGS/jreDownloadUrl"
printf '%s\n' "$KEYS_URL" > "$SETTINGS/pgpPublicKeyUrl"
)
cat "$SETTINGS/productsInfoUrl"
Restart Gateway and verify matching artifacts/reconnect with external Internet unavailable under existing approved client policy. URL creation is not successful GUI acceptance. Other client OS setting locations and version-management policy are in the official guide; disabling updates does not supply missing artifacts.
The SSH examples assume Gateway and Podman run on the same Linux workstation.
SSH 127.0.0.1:2211 refers to that host’s loopback publication, not backend
localhost. A separate client needs explicitly reviewed transport/tunneling to the
deployment host; never widen SSH publication to 0.0.0.0. Localhost artifact
URLs refer to the client’s machine, not deployment host/backend/broker.
Permitted SSH local TCP forwarding reaches backend loopback only; root/password,
agent/X11, remote forwarding and tunnel devices are disabled. The tunnel remains
an additional boundary exposing selected backend services to client software.
On the deployment host/client respectively, inspect effective settings/listeners:
ssh -G -F "$ROOT/ssh/$ENVIRONMENT/config" "$ENVIRONMENT"
podman port "$ENVIRONMENT"
ss -ltnp
Confirm pinned host key, no agent forwarding, host publication/client listeners loopback-only on IPv4/IPv6, and actual backend destinations of local forwards. Assess client filesystem/credentials/integrations. Optional MCP needs separate version support, authentication, loopback binding, capability/permission and onward-access review; do not publish its listener or grant blanket filesystem/ terminal access. No AI plugin is required for these checks.
Synthetic tests/jetbrains.py safety and disposable provisioning evidence do
not download/execute real IDEs or prove licensing/client/tunnel behavior. See
Standalone verification commands and evidence boundary. Optional GNOME/Wayland RDP is a separate final manual
experiment: record actual host/server/client versions, transport/authentication,
reconnect and logout/session lifetime. A previously stable session on another
node is not acceptance here. No RDP installation or host-policy change is
automated or authorized by this reference.
Mount layout: separate follow-up design¶
The current layout already groups IDE state into broad dedicated private
.config, .cache and .local/share binds. Agent state, XDG state, compiler
cache and read-only guards have independent paths and lifecycles. Never substitute
the operator’s personal home/config/cache/local directories for these sources.
Proposed, not implemented: consider consolidating only dedicated per-member
state if a later source-driven design demonstrates a benefit. A broad .local
bind would also cover executable paths and overlap agent/XDG state; merging
config/cache can change guard precedence, seed scope and ownership. Fewer mounts
do not strengthen containment. Keep RO backend/guards and separate ccache policy
unless an explicitly reviewed design preserves their authority and persistence.
Such a change needs separate migration/compatibility tests for initialized members, UID/GID and SELinux handling, overlapping bind precedence, independent seed copies and sanitization, changed-image refresh/rollback, no state loss/reseed, scripting cache exclusion, retained recreation and multi-member separation. A managed pinned backend/seed interface and repeatable factory CLI also require their own design and acceptance, including real client/offline/licensing evidence. None is supplied by this documentation edit. Use Arena administration reference for current ownership and recovery, Bounded security verification for bounded evidence and Arena troubleshooting and known issues for safe diagnosis.