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
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
- The npm package name is `@mickaelroger78/opencode-manager`.
- The npm package expects prebuilt binaries in `dist/opencode-manager-{linux,darwin}-{x64,arm64}`.
- `.github/workflows/pipeline.yml` is the full CI/CD pipeline: it lints (gofmt, vet), runs unit tests, builds the four binaries, and runs a docker + podman integration matrix (`selftest` subcommand: base-image build, workspace creation, and upgrade-survives-reinstall). On push to `main` it publishes the dev channel (npm dist-tag `dev` + `docker.io/mroger78/ocm-base:dev`); on a `vX.Y.Z` tag it publishes the stable channel (npm `latest` + promotes the tested base image to `:X.Y.Z` and `:latest`). npm uses trusted publishing; the base image push needs the `DOCKERHUB_USERNAME`/`DOCKERHUB_TOKEN` secrets.
- The published base image `docker.io/mroger78/ocm-base` is the default `baseImage.name`. Its recipe lives as real files in `internal/runtime/buildcontext/` (`Dockerfile`, `Dockerfile.overlay`, `Dockerfile.workspace`, and the `opencode-manager-attach`/`opencode-manager-entrypoint` scripts). The pipeline publishes `buildcontext/Dockerfile` directly; the binary embeds the directory (`//go:embed`), materializes it to a temp dir, and builds it as a fallback, passing `BASE_IMAGE`, `EXTRA_PACKAGES`, and `EXTRA_COMMANDS` (and `UID`/`GID` for the workspace image) as `--build-arg` values (`baseBuildArgs`/`workspaceBuildArgs` in `internal/runtime/runtime.go`). So the published image and the local build never drift, and there is no Go-generated Dockerfile. `cmd/opencode-manager selftest` is an internal, undocumented integration entrypoint, not part of the public `list`/`attach` CLI.
- The published base image `docker.io/mroger78/ocm-base` is the default `baseImage.name`. Its recipe lives as real files in `internal/runtime/buildcontext/` (`Dockerfile`, `Dockerfile.overlay`, `Dockerfile.workspace`, and the `opencode-manager-attach`/`opencode-manager-entrypoint` scripts). The pipeline publishes `buildcontext/Dockerfile` directly; the binary embeds the directory (`//go:embed`), materializes it to a temp dir, and builds it as a fallback, passing `BASE_IMAGE`, `EXTRA_PACKAGES`, and `EXTRA_COMMANDS` (and `UID`/`GID` for the workspace image) as `--build-arg` values (`baseBuildArgs`/`workspaceBuildArgs` in `internal/runtime/runtime.go`). The workspace recipe also re-copies the manager scripts, so an upgraded manager replaces scripts inherited from a cached base image. `cmd/opencode-manager selftest` is an internal, undocumented integration entrypoint, not part of the public `list`/`attach` CLI.
- Built-in module sources live in the repo's top-level `modules/` directory (pure data, no Go files), grouped under a **category** directory: `cloud/aws`, `cloud/outscale`, `infra/kubernetes`, `source-code/git`, `tools/ssh`. The category is the parent directory name (`module.Module.Category`, derived in `module.Load`); `module.Catalog` scans two levels (`<root>/<category>/<module>/module.yml`) and sorts by category then name. A module is still identified by its globally unique `name`. The TUI shows a category's directory slug sentence-cased (`source-code` → "Source code") via `categoryLabel`.
- The category is mirrored in the container mount path (`/opt/opencode-manager/modules/<category>/<name>`) and recorded in each `ModuleInstance.Category` in the manifest (with a catalog fallback for pre-category manifests), so install/uninstall/reconcile can find a module's scripts. The TUI editor (`internal/tui/edit.go`) renders modules as a category browser and supports `/` filtering over name/description/category/label.
- The built-in `git` module imports host global `user.name`/`user.email` and OpenPGP signing config through its host-side `resolve` hook. If `user.signingkey` exists and the secret key is exportable, the armored secret key is base64-passed only to the container install environment, imported into `~/.gnupg`, and never persisted to the manifest.
Expand Down
4 changes: 4 additions & 0 deletions internal/runtime/buildcontext/Dockerfile.workspace
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ ARG GID

RUN set -eux; if getent group ${GID}; then group_name=$(getent group ${GID} | cut -d: -f1); else groupadd -g ${GID} debian && group_name=debian; fi; if getent passwd ${UID}; then user_name=$(getent passwd ${UID} | cut -d: -f1); usermod -d /home/debian -s /bin/bash ${user_name}; else useradd -m -u ${UID} -g ${GID} -s /bin/bash debian; user_name=debian; fi; mkdir -p /home/debian/workspace && chown -R ${UID}:${GID} /home/debian
RUN echo 'ALL ALL=(ALL:ALL) NOPASSWD:ALL' > /etc/sudoers.d/opencode-manager && chmod 0440 /etc/sudoers.d/opencode-manager
# Refresh manager-owned scripts even when the resolved base image is cached.
COPY opencode-manager-attach /usr/local/bin/opencode-manager-attach
COPY opencode-manager-entrypoint /usr/local/bin/opencode-manager-entrypoint
RUN chmod 0755 /usr/local/bin/opencode-manager-attach /usr/local/bin/opencode-manager-entrypoint
WORKDIR /home/debian/workspace
13 changes: 13 additions & 0 deletions internal/runtime/runtime_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,19 @@ func TestBaseDockerfileModuleSupport(t *testing.T) {
}
}

func TestWorkspaceDockerfileRefreshesManagerScripts(t *testing.T) {
content := readBuildFile(t, workspaceDockerfile)
for _, want := range []string{
"COPY opencode-manager-attach /usr/local/bin/opencode-manager-attach",
"COPY opencode-manager-entrypoint /usr/local/bin/opencode-manager-entrypoint",
"chmod 0755 /usr/local/bin/opencode-manager-attach /usr/local/bin/opencode-manager-entrypoint",
} {
if !strings.Contains(content, want) {
t.Fatalf("workspace Dockerfile missing %q:\n%s", want, content)
}
}
}

func TestOverlayDockerfileOnlyAddsExtras(t *testing.T) {
content := readBuildFile(t, overlayDockerfile)
for _, want := range []string{
Expand Down
Loading