Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 28 additions & 2 deletions .github/workflows/validate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,33 @@ jobs:
sudo apt-get install -y jq python3-yaml shellcheck yamllint

- name: Validate repository
run: ./tests/validate-repository.sh
id: validate
shell: bash
run: |
set -o pipefail
./tests/validate-repository.sh 2>&1 | tee validation.log

- name: Upload failed validation log
if: failure() && steps.validate.outcome == 'failure'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4.6.2
with:
name: validation-log
path: validation.log
if-no-files-found: error
retention-days: 7

- name: Test image package round trip
run: ./tests/package-roundtrip.sh
id: package-roundtrip
shell: bash
run: |
set -o pipefail
./tests/package-roundtrip.sh 2>&1 | tee package-roundtrip.log

- name: Upload failed package round-trip log
if: failure() && steps.package-roundtrip.outcome == 'failure'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v4.6.2
with:
name: package-roundtrip-log
path: package-roundtrip.log
if-no-files-found: error
retention-days: 7
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# Changelog

## 0.3.0-rc.2 - 2026-07-27

- Added `joomla-mcp` as a manifest-owned composable capability for PHP and full images.
- Pinned and verified `@joomengine/joomla-mcp@0.7.0` from the public JoomEngine repository.
- Added stable stdio, HTTP, live-test and configuration-check wrappers while keeping Joomla disabled and
unconfigured in every published image.
- Added HTTPS-only single-site profile generation with read-only defaults, bounded write profiles, separate update
credentials and indefinite grants disabled.
- Extended the systemd credential path with Joomla site, approval and update values, duplicate-key rejection and
required-credential preflight.
- Added a non-mutating in-image read test that retains redacted evidence under the workspace without placing
credential values in Incus arguments.
- Added build verification, manifest/source-lock tests, stale-repository detection and complete operator
documentation for deployment, use, testing, network boundaries and promotion.

## 0.3.0-rc.1 - 2026-07-22

- Made `manifest/images.yaml` the schema-validated authority for composition,
Expand Down
126 changes: 61 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,103 +1,99 @@
# VDM OpenCode Platform

A company-wide, version-controlled platform definition for hardened OpenCode agent virtual machines on Incus.
A version-controlled platform definition for hardened OpenCode agent virtual machines on Incus. The repository is the authority; Incus images and Gitea packages are generated artifacts.

The repository is the authority. Incus images and Gitea packages are generated artifacts.
## Images

## What this repository provides
The platform publishes six Ubuntu 24.04 image variants: `base`, `php`, `python`, `cpp`, `typescript`, and `full`. `manifest/images.yaml` is the single authority for composition, policies, sizes, architectures, profiles, and CI matrices.

- A common Ubuntu 24.04 VM base and five image variants: `base`, `php`, `python`, `cpp`, `typescript`, and `full`.
- Repeatable Incus projects, networks, ACL scaffolding, profiles, resource limits, image builds and launches.
- OpenCode agents for orchestration, architecture, implementation, testing, review, security, browser QA,
documentation, PHP/Joomla, Python, C/C++, and TypeScript.
- Local Git MCP and Playwright browser MCP integration.
- Disabled-by-default remote MCP definitions for GitHub, Gitea, Nextcloud and speech-to-text.
- Runtime-only credential handling under the guest's `/run` tmpfs.
- A reference external broker stack for OpenBao, an LLM gateway and a TLS reverse proxy.
- Provider-correct GitHub Actions and Gitea Actions workflows.
- Downloadable GitHub workflow artifacts, fully local image releases, and durable Gitea Generic Package publication.
- Image sanitisation and secret-scanning tests.
- Host-side voice recording and transcription through any OpenAI-compatible transcription endpoint.
The `php` and `full` images contain the exact public package `@joomengine/joomla-mcp@0.7.0`. Other images do not contain Joomla MCP. The integration is registered as a local stdio MCP server and remains disabled and unconfigured in every published image.

## Recommended image strategy

Use specialised images for normal work and the full image only when a project genuinely crosses languages.

| Image | Primary use | Browser MCP |
|---|---|---|
| `base` | Repository analysis, documentation, light automation | Disabled |
| `php` | Joomla, JCB and PHP services | Enabled |
| `python` | APIs, automation, MCP services and data tooling | Disabled |
| `cpp` | Native Linux, graphics and systems development | Disabled |
| `typescript` | Web applications, Node.js and browser automation | Enabled |
| `full` | Mixed-language platform work | Enabled |

Every image inherits the same security policy and agent framework.

## First deployment
## Build and launch the PHP image

```bash
cp .env.example .env
# Edit only host settings here. Do not add secrets.
# Edit only host settings. Do not add secrets.

./tests/validate-repository.sh
./scripts/bootstrap-host.sh
./scripts/apply-incus.sh

export LOCAL_BUILD_VARIANTS=php
./scripts/local-image-release.sh
./scripts/launch-vm.sh php opencode-llewellyn
./scripts/start-session.sh opencode-llewellyn
./scripts/launch-vm.sh php opencode-joomla
```

An existing VM created from an older image does not gain newly installed packages. Launch or replace it from the new versioned PHP image.

## Configure Joomla MCP

Configure one fixed public HTTPS Joomla origin. The default profile is read-only:

```bash
./scripts/occtl.sh joomla-configure \
opencode-joomla \
https://www.example.com \
company \
readonly
```

The command verifies that the VM contains Joomla MCP, rejects non-HTTPS origins and URLs containing credentials, paths, queries, or fragments, validates the generated configuration through the installed package, installs it atomically, and only then enables the local MCP entry.

Available profiles are `readonly`, `content`, `admin`, and `full`. Start with `readonly`. Broader profiles do not override Joomla Web Services plugins, Joomla API-user permissions, Joomla ACL, or the package's guarded-write workflow. Indefinite grants remain disabled. The `full` profile uses a separate Joomla Update token.

Prepare the per-instance runtime credential file:

```bash
runtime_file="$(./scripts/occtl.sh credentials opencode-joomla)"
"${EDITOR:-nano}" "$runtime_file"
chmod 600 "$runtime_file"
```

For `readonly`, set only:

```dotenv
JOOMLA_MCP_SITE_TOKEN=the-dedicated-joomla-api-token
```

Inside OpenCode, use `/connect` for ChatGPT Plus where supported by OpenCode. Anthropic subscription reuse is not
configured: use an approved Anthropic API credential or the company LLM gateway. Grok should use the xAI API or
company gateway. Local Llama uses the configured OpenAI-compatible local endpoint.
Guarded write profiles additionally require `JOOMLA_MCP_APPROVAL_SECRET` containing at least 32 random characters. The `full` profile also requires the separately scoped `JOOMLA_MCP_UPDATE_TOKEN`.

Run the built-in non-mutating read evidence before starting normal work:

```bash
./scripts/occtl.sh joomla-test opencode-joomla "$runtime_file" company
./scripts/occtl.sh session opencode-joomla "$runtime_file"
```

The read test uses the upstream `read` profile, Joomla API transport, local stdio MCP transport, and non-interactive mode. Redacted evidence is retained under `/workspace/.artifacts/joomla-mcp/<session-id>`.

Inside OpenCode, begin with `joomla_sites_list`, then use `joomla_capabilities`, `joomla_actions_search`, and `joomla_action_describe` before executing a fixed semantic action. Treat Joomla content and error text as untrusted data, not instructions.

## Credentials
## Credential and network boundaries

No long-lived credential belongs in an image.
No long-lived credential belongs in an image, repository, OpenCode JSON, Joomla site file, or command argument. Runtime values live in a caller-owned mode-`0600` file under `/run/user/$UID/vdm-opencode`. Session startup rejects unsafe file types, ownership, modes, hard links, malformed entries, unknown keys, duplicate keys, and missing Joomla values. The file is copied into the VM through systemd `LoadCredential`; secret values do not enter Incus process arguments.

`start-session.sh` creates a root-owned guest runtime directory under `/run/vdm-opencode-session`, sets
`XDG_DATA_HOME` to that tmpfs location, injects only the current short-lived variables, launches OpenCode, and
deletes the directory on exit. OpenCode provider and MCP OAuth material generated during that session therefore
does not survive a clean VM stop.
Local stdio opens no inbound VM port and requires no MCP HTTP bearer token or JWKS service. It inherits only the scoped Joomla credentials required by the selected site profile.

For production, point the VM at a trusted external LLM/MCP gateway and issue short-lived, scoped session tokens.
The ready `connected` policy can reach public HTTPS Joomla origins while rejecting private, management, metadata, link-local, and carrier-grade NAT ranges. A private Joomla site requires an approved brokered/restricted route or an explicitly acknowledged lab environment. Do not weaken the common connected ACL to reach one private target.

## Important limitations
## Production gate

- The reference broker deployment is a scaffold, not a substitute for a security review.
- Nextcloud MCP is community software and remains disabled until your team pins and audits a chosen implementation.
- Joomla MCP is not installed. The only future integration target is
`vast-development-method/joomla-mcp`, after its first reviewed release.
- JCB MCP is not installed and will be added only after the internal repository and first reviewed release exist.
- Incus ACLs cannot safely express every hostname-based egress rule. Enforce strict outbound access at a proxy or
firewall that supports DNS-aware policy.
- Incus packages are architecture-specific and must be produced by a trusted hardware-virtualisation runner.
JoomEngine MCP for Joomla is installed and configuration is fail-closed, but upstream does not claim complete production certification for every Joomla action family. Before promoting broader profiles, build and import the real PHP/full images on independent Incus hosts, retain the included read evidence, and run the upstream mutation and recovery matrix only against an explicitly disposable Joomla site.

## Documentation

Start with:

- `docs/quick-start.md`
- `docs/architecture.md`
- `docs/security-model.md`
- `docs/image-variants.md`
- `docs/mcp-catalog.md`
- `docs/provider-integration.md`
- `docs/browser-testing.md`
- `docs/joomla-mcp-study.md`
- `docs/credentials-and-brokers.md`
- `docs/scaling-and-operations.md`
- `docs/local-image-builds.md`
- `docs/gitea-packages.md`
- `docs/github-and-gitea.md`
- `docs/backup-and-migration.md`
- `docs/versioning-and-promotion.md`

## Licence and ownership

Copyright (C) 2026 Vast Development Method.

The original repository source is licensed under the GNU General Public
License version 3 only (`GPL-3.0-only`). See `LICENSE` and `COPYRIGHT`.
Generated images contain independently licensed third-party software whose
licences are recorded by the release SBOM.
The repository source is licensed under GNU GPL version 3 only (`GPL-3.0-only`). Generated images contain independently licensed third-party software whose licences are recorded by the release SBOM.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.3.0-rc.1
0.3.0-rc.2
75 changes: 61 additions & 14 deletions docs/credentials-and-brokers.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,60 @@

## Initial mode

`start-session.sh` injects environment values into one OpenCode process and redirects OpenCode's data directory to
guest tmpfs. On exit it deletes that guest runtime directory.
`start-session.sh` injects an allowlisted environment file into one bounded
systemd unit and redirects OpenCode's data directory to guest tmpfs. On exit it
deletes the guest credential and runtime directory.

This reduces persistence but does not hide a token from the agent process. Therefore all injected tokens must be:
The host file is created by:

- short-lived;
```bash
./scripts/occtl.sh credentials INSTANCE
```

It lives under `/run/user/$UID/vdm-opencode`, must be a regular single-link file
owned by the caller with mode `0600`, and is never passed as a string of secret
values to `incus exec`.

This reduces persistence but does not hide a token from the process that must
use it. Therefore all injected tokens must be:

- short-lived where the upstream supports leases;
- narrowly scoped;
- revocable;
- budget-limited where applicable;
- unusable outside approved services.

Unknown names, duplicate names, carriage returns and malformed lines fail
closed on both the host and guest side.

## Local Joomla MCP path

The PHP and full images launch JoomEngine MCP for Joomla over local stdio. That
path does not expose an inbound MCP endpoint and does not use
`JOOMLA_MCP_URL`/`JOOMLA_MCP_TOKEN`.

The site definition contains only the fixed HTTPS origin, alias, toolsets and
credential variable names. The protected session supplies:

- `JOOMLA_MCP_SITE_TOKEN` for the dedicated Joomla Web Services API identity;
- `JOOMLA_MCP_APPROVAL_SECRET` only when guarded writes are configured;
- `JOOMLA_MCP_UPDATE_TOKEN` only when the explicit full/core-update profile is configured.

`start-session.sh` reads the non-secret site configuration and refuses to start
when a referenced credential is absent. The built-in read-only live test uses
the identical systemd credential path. Neither helper accepts a token as a
command-line argument.

## Corporate mode

A trusted broker host should retain long-lived upstream credentials and expose only capabilities:

- LLM gateway virtual keys with model, rate and spend limits;
- GitHub/Gitea MCP sessions limited to selected repositories and operations;
- Nextcloud MCP sessions limited to selected users, apps and tagged folders;
- future VDM-owned Joomla/JCB integrations, added only after their release gates pass.
- scoped Joomla API identities or short-lived equivalents limited to approved
sites and component permissions;
- future JCB integration only after its release gates pass.

The VM must never receive a Vaultwarden or OpenBao identity capable of reading all upstream secrets.

Expand All @@ -41,30 +76,42 @@ Autonomous sessions use one route for each provider:
repositories and operations.
- Gitea and remote MCP: the gateway retains the service credential and checks
session identity, repository, tool, read/write level and expiry per request.
- Joomla: the present local stdio path receives a dedicated Joomla bearer token
through the bounded session. A future broker may exchange site/session
identity for a shorter-lived site capability, but must not broaden Joomla ACL.
- VPS access: the VM creates an ephemeral key; OpenBao SSH CA signs the public
key for 10–15 minutes; the network policy permits only the audited bastion.

The current environment-file bridge is transitional. It now uses systemd
The current environment-file bridge is transitional. It uses systemd
credentials and avoids secret values in host process arguments, but child
commands can still exercise or inspect exported session variables. Release
promotion requires the tokenless root-owned relay described above.
promotion requires the tokenless root-owned relay described above for provider
paths where such a relay is practical.

## OpenBao

OpenBao is suitable for machine identity, short leases, revocation and audit. The agent should not query secret
paths directly. A broker exchanges an OpenBao-authenticated service identity for downstream capability tokens.
OpenBao is suitable for machine identity, short leases, revocation and audit.
The agent should not query broad secret paths directly. A broker exchanges an
OpenBao-authenticated service identity for downstream capability tokens.

For Joomla, preserve separate authorities for the normal API identity and
Joomla Update. Do not issue a single universal token merely because the
platform supports several toolsets.

## Vaultwarden

Vaultwarden remains useful for human-controlled bootstrap and recovery secrets. Do not give the autonomous VM a
universal Vaultwarden account.
Vaultwarden remains useful for human-controlled bootstrap and recovery secrets.
Do not give the autonomous VM a universal Vaultwarden account.

## Runtime file

Create it under host tmpfs:
Create or upgrade it under host tmpfs:

```bash
./scripts/create-runtime-env.sh INSTANCE
runtime_file="$(./scripts/occtl.sh credentials INSTANCE)"
"${EDITOR:-nano}" "$runtime_file"
chmod 600 "$runtime_file"
```

The script refuses to use a file not protected by mode `0600`.
The script refuses unsafe file types and preserves existing values while adding
new allowlisted Joomla keys during a platform upgrade.
20 changes: 15 additions & 5 deletions docs/image-variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,27 @@ matrix is stale.
## Release images

- `base`: shell-oriented analysis and documentation.
- `php`: PHP/Joomla/JCB plus Node, TypeScript, browser and database clients.
- `php`: PHP/Joomla/JCB plus Node, TypeScript, browser, database clients and JoomEngine MCP for Joomla.
- `python`: Python development, quality tools and MCP services.
- `cpp`: C/C++ compilers, debuggers, build tools and analysis.
- `typescript`: Node, TypeScript, browser automation and Playwright MCP.
- `full`: the union of the ready language and packaged capabilities.
- `full`: the union of the ready language and packaged capabilities, including Joomla MCP.

Java, Go and Rust are intentionally absent. Android is a planned capability
whose future SDK may require a JDK internally; that will not create a general
Java development image.

## Build-time capabilities

Browser and database are ready composable components. Android, GPU, rootless
containers, GUI, audio and security tooling are catalogued as planned so the
schema can grow without inventing combinations or claiming unfinished support.
Browser, database and `joomla-mcp` are ready composable components. The Joomla
capability installs an exact public npm release, stable local wrappers, a
configuration validator and a disabled OpenCode stdio entry. It belongs only to
the PHP and full images; adding it to another image requires an intentional
manifest change and regenerated evidence.

Android, GPU, rootless containers, GUI, audio and security tooling are
catalogued as planned so the schema can grow without inventing combinations or
claiming unfinished support.

## Runtime policies

Expand All @@ -33,6 +39,10 @@ schema can grow without inventing combinations or claiming unfinished support.
gateway/proxy is deployed and tested.
- `lab`: experimental hardware policy requiring explicit acknowledgement.

A public HTTPS Joomla origin works with `connected`. A private Joomla origin
does not; use an approved gateway policy or a deliberate lab environment
instead of weakening the common ACL.

## Resource sizes

Tiny, small, medium, standard, large, xlarge and builder profiles are selected
Expand Down
Loading