diff --git a/specs/devenv.md b/specs/devenv.md deleted file mode 100644 index 49589e4..0000000 --- a/specs/devenv.md +++ /dev/null @@ -1,184 +0,0 @@ -# devenv -**Import:** `dappco.re/go/core/container/devenv` -**Files:** 8 - -## Types - -### `BootOptions` -`type BootOptions struct` - -Startup settings for the development VM. - -- `Memory int`: Guest memory in MB. `DefaultBootOptions` sets this to `4096`. -- `CPUs int`: Guest CPU count. `DefaultBootOptions` sets this to `2`. -- `Name string`: Container name. `DefaultBootOptions` sets this to `core-dev`. -- `Fresh bool`: Stops any existing environment before booting a new one. - -### `CDNConfig` -`type CDNConfig struct` - -CDN download configuration. - -- `URL string`: Base URL used by the CDN image source. - -### `ClaudeOptions` -`type ClaudeOptions struct` - -Controls how `DevOps.Claude` launches a sandboxed Claude session inside the VM. - -- `NoAuth bool`: Disables extra auth forwarding. -- `Auth []string`: Requested auth categories. Recognized values in the current implementation are `gh`, `anthropic`, `ssh`, and `git`. -- `Model string`: Optional model flag appended to the `claude` command. - -### `Config` -`type Config struct` - -Top-level configuration loaded from `~/.core/config.yaml`. - -- `Version int`: Config schema version. -- `Images ImagesConfig`: Image source settings. - -### `DevOps` -`type DevOps struct` - -High-level façade that composes configuration loading, image management, LinuxKit VM lifecycle, SSH access, project mounting, and helper workflows. - -### `DevStatus` -`type DevStatus struct` - -Snapshot returned by `DevOps.Status`. - -- `Installed bool`: Whether the platform image file is present locally. -- `Running bool`: Whether the tracked dev container is currently running. -- `ImageVersion string`: Installed image version from the manifest, when known. -- `ContainerID string`: Current container ID, when present. -- `Memory int`: Memory allocation recorded on the container. -- `CPUs int`: CPU allocation recorded on the container. -- `SSHPort int`: SSH port reported by status. The current implementation always returns `DefaultSSHPort`. -- `Uptime time.Duration`: Time since the container started when it is running. - -### `GitHubConfig` -`type GitHubConfig struct` - -GitHub Releases source configuration. - -- `Repo string`: Repository in `owner/repo` form. - -### `ImageInfo` -`type ImageInfo struct` - -Manifest entry for an installed image. - -- `Version string`: Installed version string. -- `SHA256 string`: Optional checksum field stored in the manifest. -- `Downloaded time.Time`: Download time recorded at install. -- `Source string`: Source identifier such as `github` or `cdn`. - -### `ImageManager` -`type ImageManager struct` - -Image installer and update checker backed by a manifest and an ordered list of `sources.ImageSource` implementations. - -### `ImagesConfig` -`type ImagesConfig struct` - -Image source selection and per-source configuration. - -- `Source string`: Source mode. The code handles `auto`, `github`, and `cdn`. -- `GitHub GitHubConfig`: GitHub Releases settings. -- `Registry RegistryConfig`: Registry settings kept in configuration but not consumed by the current source builder. -- `CDN CDNConfig`: CDN settings. - -### `Manifest` -`type Manifest struct` - -On-disk JSON manifest for installed images. - -- `Images map[string]ImageInfo`: Installed images keyed by image filename. - -### `RegistryConfig` -`type RegistryConfig struct` - -Container registry configuration. - -- `Image string`: Registry image reference. - -### `ServeOptions` -`type ServeOptions struct` - -Options for `DevOps.Serve`. - -- `Port int`: Display port. The implementation defaults this to `8000`. -- `Path string`: Optional subdirectory below `projectDir` to mount and serve. - -### `ShellOptions` -`type ShellOptions struct` - -Options for `DevOps.Shell`. - -- `Console bool`: Uses the serial console socket instead of SSH. -- `Command []string`: Remote command to run. An empty slice opens an interactive SSH shell. - -### `TestCommand` -`type TestCommand struct` - -Named command entry from `.core/test.yaml`. - -- `Name string`: Command selector. -- `Run string`: Shell command to execute. - -### `TestConfig` -`type TestConfig struct` - -Project-local test configuration loaded from `.core/test.yaml`. - -- `Version int`: Config version. -- `Command string`: Default test command. -- `Commands []TestCommand`: Named commands that can be selected by `TestOptions.Name`. -- `Env map[string]string`: Environment entries parsed from the YAML file. - -### `TestOptions` -`type TestOptions struct` - -Controls how `DevOps.Test` chooses a command. - -- `Name string`: Named command from `.core/test.yaml`. -- `Command []string`: Explicit command override supplied as arguments. - -## Functions - -### Top-Level Functions -- `ConfigPath() (string, error)`: Resolves the global config path to `~/.core/config.yaml`. -- `DefaultConfig() *Config`: Returns version `1` config with `auto` image selection, GitHub repo `host-uk/core-images`, and registry image `ghcr.io/host-uk/core-devops`. -- `LoadConfig(m io.Medium) (*Config, error)`: Loads the global config file with the shared config service and falls back to defaults when the file is absent or the home directory cannot be resolved. -- `ImageName() string`: Builds the platform image filename as `core-devops--.qcow2`. -- `ImagesDir() (string, error)`: Uses `CORE_IMAGES_DIR` when set, otherwise resolves `~/.core/images`. -- `ImagePath() (string, error)`: Joins `ImagesDir` and `ImageName`. -- `DefaultBootOptions() BootOptions`: Returns `Memory: 4096`, `CPUs: 2`, and `Name: core-dev`. -- `DetectServeCommand(m io.Medium, projectDir string) string`: Chooses a serve command by checking for Laravel, Node.js, Composer PHP, Go, Django, and then falling back to `python3 -m http.server 8000`. -- `DetectTestCommand(m io.Medium, projectDir string) string`: Chooses a test command from `.core/test.yaml`, Composer, npm, Go, pytest, or Taskfile conventions. -- `LoadTestConfig(m io.Medium, projectDir string) (*TestConfig, error)`: Reads and unmarshals `.core/test.yaml` from the project directory. -- `New(m io.Medium) (*DevOps, error)`: Loads configuration, creates an `ImageManager`, creates a `container.LinuxKitManager`, and returns a ready `DevOps` instance. - -### `DevOps` -- `(*DevOps).IsInstalled() bool`: Checks whether the platform image file exists on the configured medium. -- `(*DevOps).Install(ctx context.Context, progress func(downloaded, total int64)) error`: Downloads and records the dev image through the embedded `ImageManager`. -- `(*DevOps).CheckUpdate(ctx context.Context) (current, latest string, hasUpdate bool, err error)`: Delegates update discovery to the `ImageManager`. -- `(*DevOps).Boot(ctx context.Context, opts BootOptions) error`: Verifies that the image is installed, optionally stops an existing environment, starts a detached LinuxKit VM on `DefaultSSHPort`, and retries SSH host-key scanning for up to 60 seconds. -- `(*DevOps).Stop(ctx context.Context) error`: Finds the `core-dev` container and stops it through the container manager. -- `(*DevOps).IsRunning(ctx context.Context) (bool, error)`: Reports whether the `core-dev` container exists and is marked running. -- `(*DevOps).Status(ctx context.Context) (*DevStatus, error)`: Combines manifest information and the current container record into a status snapshot. -- `(*DevOps).Shell(ctx context.Context, opts ShellOptions) error`: Opens either an SSH shell or a serial-console connection, failing if the environment is not running. -- `(*DevOps).Serve(ctx context.Context, projectDir string, opts ServeOptions) error`: Mounts the requested project path into `/app`, auto-detects a serve command, and runs it over SSH. -- `(*DevOps).Test(ctx context.Context, projectDir string, opts TestOptions) error`: Chooses a test command from explicit arguments, a named config entry, or auto-detection, then executes it over SSH from `/app`. -- `(*DevOps).Claude(ctx context.Context, projectDir string, opts ClaudeOptions) error`: Auto-boots when needed, mounts the project, prepares forwarded auth-related environment variables, and runs `claude` inside the VM over SSH agent forwarding. -- `(*DevOps).CopyGHAuth(ctx context.Context) error`: Copies the host `~/.config/gh` directory into `/root/.config/` inside the VM with `scp` when the host config exists. - -### `ImageManager` -- `NewImageManager(m io.Medium, cfg *Config) (*ImageManager, error)`: Ensures the images directory exists, loads or creates the manifest, and builds the ordered image-source list from the current config. -- `(*ImageManager).IsInstalled() bool`: Checks whether the current platform image file exists. -- `(*ImageManager).Install(ctx context.Context, progress func(downloaded, total int64)) error`: Picks the first available configured source, downloads the current platform image, records its version and source, and saves the manifest. -- `(*ImageManager).CheckUpdate(ctx context.Context) (current, latest string, hasUpdate bool, err error)`: Reads the current version from the manifest, asks the first available source for the latest version, and compares them. - -### `Manifest` -- `(*Manifest).Save() error`: Serializes the manifest as JSON and writes it to the bound manifest path. diff --git a/specs/devenv/RFC.md b/specs/devenv/RFC.md new file mode 100644 index 0000000..ea1a078 --- /dev/null +++ b/specs/devenv/RFC.md @@ -0,0 +1,191 @@ +# devenv +**Import:** `dappco.re/go/core/container/devenv` +**Files:** 8 + +## Constants + +### `DefaultSSHPort` +`const DefaultSSHPort = 2222` + +Default SSH port used for SSH access, host-key scanning, and the `SSHPort` field returned by `DevOps.Status`. + +## Types + +### `Config` +`type Config struct` + +Global configuration loaded from `~/.core/config.yaml`. + +- `Version int`: Configuration version. +- `Images ImagesConfig`: Image-source configuration. + +### `ImagesConfig` +`type ImagesConfig struct` + +Image-source selection and per-source settings. + +- `Source string`: Source selector. `ImageManager` handles `github`, `cdn`, and otherwise falls back to trying GitHub then CDN. +- `GitHub GitHubConfig`: GitHub Releases settings. +- `Registry RegistryConfig`: Registry settings passed into `sources.SourceConfig`; the built-in sources do not use them. +- `CDN CDNConfig`: CDN settings. + +### `GitHubConfig` +`type GitHubConfig struct` + +GitHub Releases settings. + +- `Repo string`: Repository in `owner/repo` form. + +### `RegistryConfig` +`type RegistryConfig struct` + +Container registry settings. + +- `Image string`: Registry image reference. + +### `CDNConfig` +`type CDNConfig struct` + +CDN download settings. + +- `URL string`: Base URL used by the CDN source. + +### `DevOps` +`type DevOps struct` + +Facade for config loading, image management, LinuxKit lifecycle, SSH access, project mounting, and helper commands. + +### `BootOptions` +`type BootOptions struct` + +Boot-time settings for the dev VM. + +- `Memory int`: Guest memory in MB. `DefaultBootOptions` sets `4096`. +- `CPUs int`: Guest CPU count. `DefaultBootOptions` sets `2`. +- `Name string`: Container name passed to `container.Run`. `DefaultBootOptions` sets `core-dev`. +- `Fresh bool`: When true, `Boot` calls `Stop` before starting a new VM. + +### `DevStatus` +`type DevStatus struct` + +Status snapshot returned by `DevOps.Status`. + +- `Installed bool`: Whether the current platform image file exists locally. +- `Running bool`: Whether the tracked `core-dev` container is running. +- `ImageVersion string`: Version recorded in the manifest for the current image, when present. +- `ContainerID string`: Container ID for `core-dev`, when present. +- `Memory int`: Memory recorded on the container. +- `CPUs int`: CPU count recorded on the container. +- `SSHPort int`: SSH port reported by status. The implementation sets this to `DefaultSSHPort`. +- `Uptime time.Duration`: Time since container start when the container is running. + +### `ImageManager` +`type ImageManager struct` + +Image installer and update checker backed by a manifest and an ordered list of `sources.ImageSource` implementations. + +### `Manifest` +`type Manifest struct` + +On-disk JSON manifest for installed images. + +- `Images map[string]ImageInfo`: Installed images keyed by image filename. + +### `ImageInfo` +`type ImageInfo struct` + +Metadata recorded for an installed image. + +- `Version string`: Installed version string. +- `SHA256 string`: Optional checksum field. +- `Downloaded time.Time`: Download timestamp recorded during install. +- `Source string`: Source identifier such as `github` or `cdn`. + +### `ServeOptions` +`type ServeOptions struct` + +Options for `DevOps.Serve`. + +- `Port int`: Port value used for the printed localhost URL. Zero defaults to `8000`. +- `Path string`: Optional subdirectory below `projectDir` to mount and serve. + +### `ShellOptions` +`type ShellOptions struct` + +Options for `DevOps.Shell`. + +- `Console bool`: Uses the serial console socket instead of SSH. +- `Command []string`: Remote command to run. An empty slice opens an interactive SSH session. + +### `TestConfig` +`type TestConfig struct` + +Project-local test configuration loaded from `.core/test.yaml`. + +- `Version int`: Config version. +- `Command string`: Default test command for `DetectTestCommand`. +- `Commands []TestCommand`: Named commands available to `TestOptions.Name`. +- `Env map[string]string`: Environment entries parsed from YAML. `DevOps.Test` does not currently apply them. + +### `TestCommand` +`type TestCommand struct` + +Named command entry from `.core/test.yaml`. + +- `Name string`: Command selector. +- `Run string`: Shell command to execute. + +### `TestOptions` +`type TestOptions struct` + +Options for `DevOps.Test`. + +- `Name string`: Named command from `.core/test.yaml`. +- `Command []string`: Explicit command override supplied as arguments. + +### `ClaudeOptions` +`type ClaudeOptions struct` + +Options for `DevOps.Claude`. + +- `NoAuth bool`: Disables extra auth handling beyond the SSH connection itself. +- `Auth []string`: Requested auth list. When empty, `Claude` uses `gh`, `anthropic`, `ssh`, and `git`; only `anthropic` and `git` currently add extra environment variables. +- `Model string`: Optional model flag appended as `claude --model `. + +## Functions + +### Top-Level Functions +- `DefaultConfig() *Config`: Returns config version `1` with `auto` image selection, GitHub repo `host-uk/core-images`, and registry image `ghcr.io/host-uk/core-devops`. +- `ConfigPath() (string, error)`: Resolves `~/.core/config.yaml` from the current home directory. +- `LoadConfig(m io.Medium) (*Config, error)`: Loads the global config through the shared config service. If the config path cannot be resolved or the file is absent, it returns `DefaultConfig()`. +- `New(m io.Medium) (*DevOps, error)`: Loads config, creates an `ImageManager`, creates a local `container.LinuxKitManager`, and returns a ready `DevOps`. +- `ImageName() string`: Returns `core-devops--.qcow2` for the current platform. +- `ImagesDir() (string, error)`: Returns `CORE_IMAGES_DIR` when set, otherwise resolves `~/.core/images`. +- `ImagePath() (string, error)`: Joins `ImagesDir` and `ImageName`. +- `DefaultBootOptions() BootOptions`: Returns `Memory: 4096`, `CPUs: 2`, and `Name: core-dev`. +- `DetectServeCommand(m io.Medium, projectDir string) string`: Chooses a serve command by checking for Laravel, Node `dev`, Node `start`, Composer PHP, Go with `main.go`, Django, and then `python3 -m http.server 8000`. +- `DetectTestCommand(m io.Medium, projectDir string) string`: Chooses a test command from `.core/test.yaml`, Composer, npm, Go, pytest, or `Taskfile.yaml` / `Taskfile.yml`. +- `LoadTestConfig(m io.Medium, projectDir string) (*TestConfig, error)`: Reads and unmarshals `projectDir/.core/test.yaml`. + +### `DevOps` +- `(*DevOps).IsInstalled() bool`: Checks whether the current platform image file exists on the configured medium. +- `(*DevOps).Install(ctx context.Context, progress func(downloaded, total int64)) error`: Delegates image download and manifest updates to the embedded `ImageManager`. +- `(*DevOps).CheckUpdate(ctx context.Context) (current, latest string, hasUpdate bool, err error)`: Delegates update checks to the embedded `ImageManager`. +- `(*DevOps).Boot(ctx context.Context, opts BootOptions) error`: Requires an installed image, uses `IsRunning` / `Stop` for preflight, starts a detached LinuxKit VM with `opts.Name`, `opts.Memory`, `opts.CPUs`, and `DefaultSSHPort`, then waits up to 60 seconds for SSH host-key scanning to succeed. +- `(*DevOps).Stop(ctx context.Context) error`: Finds the container named `core-dev` and stops it through the container manager. +- `(*DevOps).IsRunning(ctx context.Context) (bool, error)`: Reports whether the container named `core-dev` exists and has `container.StatusRunning`. +- `(*DevOps).Status(ctx context.Context) (*DevStatus, error)`: Combines local image state, manifest data for the current image name, and container details for `core-dev`. +- `(*DevOps).Shell(ctx context.Context, opts ShellOptions) error`: Requires a running environment, then opens either the serial console or an SSH session with optional command arguments. +- `(*DevOps).Serve(ctx context.Context, projectDir string, opts ServeOptions) error`: Requires a running environment, mounts `projectDir` or `projectDir/` into `/app`, auto-detects a serve command from that path, prints the localhost URL, and runs `cd /app && ` over SSH. +- `(*DevOps).Test(ctx context.Context, projectDir string, opts TestOptions) error`: Requires a running environment, chooses a command with priority `Command` > named config entry > auto-detection, then runs `cd /app && ` over SSH. +- `(*DevOps).Claude(ctx context.Context, projectDir string, opts ClaudeOptions) error`: Auto-boots with `DefaultBootOptions` when needed, mounts the project into `/app`, always uses SSH agent forwarding, conditionally forwards `ANTHROPIC_API_KEY` and git author / committer values, and runs `claude` with an optional `--model` flag. +- `(*DevOps).CopyGHAuth(ctx context.Context) error`: If `~/.config/gh` exists on the host, copies it recursively into `/root/.config/` inside the VM with `scp`. + +### `ImageManager` +- `NewImageManager(m io.Medium, cfg *Config) (*ImageManager, error)`: Ensures the images directory exists, loads or creates `manifest.json`, and builds the ordered source list from the current config. +- `(*ImageManager).IsInstalled() bool`: Checks whether the current platform image file exists on the configured medium. +- `(*ImageManager).Install(ctx context.Context, progress func(downloaded, total int64)) error`: Chooses the first available source, asks it for the latest version, downloads the current platform image into the images directory, records version / source / download time in the manifest, and saves the manifest. +- `(*ImageManager).CheckUpdate(ctx context.Context) (current, latest string, hasUpdate bool, err error)`: Reads the current version from the manifest, asks the first available source for its latest version, and reports whether the strings differ. + +### `Manifest` +- `(*Manifest).Save() error`: JSON-encodes the manifest and writes it to its bound path with the configured medium. diff --git a/specs/sources.md b/specs/sources/RFC.md similarity index 62% rename from specs/sources.md rename to specs/sources/RFC.md index 0957cbd..6f28b73 100644 --- a/specs/sources.md +++ b/specs/sources/RFC.md @@ -7,22 +7,22 @@ ### `ImageSource` `type ImageSource interface` -Download source abstraction used by `devenv.ImageManager`. +Interface implemented by dev-image download sources. - `Name() string`: Returns the source identifier. - `Available() bool`: Reports whether the source can be used in the current environment. -- `LatestVersion(ctx)`: Returns the latest available version string. -- `Download(ctx, m, dest, progress)`: Downloads the image into `dest` with the provided medium and optional progress callback. +- `LatestVersion(ctx context.Context) (string, error)`: Returns the latest available version string. +- `Download(ctx context.Context, m io.Medium, dest string, progress func(downloaded, total int64)) error`: Downloads the image into `dest` and may report byte progress through the callback. ### `SourceConfig` `type SourceConfig struct` -Shared configuration passed to concrete image sources. +Shared configuration passed to concrete sources. - `GitHubRepo string`: GitHub repository in `owner/repo` form. -- `RegistryImage string`: Registry image reference reserved in the config shape. -- `CDNURL string`: CDN or object storage base URL. -- `ImageName string`: Expected asset filename. +- `RegistryImage string`: Registry image reference carried through the config shape but unused by the built-in sources. +- `CDNURL string`: Base URL for CDN or object-storage downloads. +- `ImageName string`: Asset filename to fetch. ### `CDNSource` `type CDNSource struct` @@ -40,12 +40,12 @@ Shared configuration passed to concrete image sources. - `NewCDNSource(cfg SourceConfig) *CDNSource`: Returns a CDN-backed source with the supplied configuration. - `(*CDNSource).Name() string`: Returns `cdn`. - `(*CDNSource).Available() bool`: Reports whether `SourceConfig.CDNURL` is non-empty. -- `(*CDNSource).LatestVersion(ctx context.Context) (string, error)`: Tries to fetch `/manifest.json` and currently returns `latest` whether the manifest fetch succeeds or falls back. -- `(*CDNSource).Download(ctx context.Context, m io.Medium, dest string, progress func(downloaded, total int64)) error`: Downloads `/` over HTTP into `dest/`, creating the destination directory first and invoking the progress callback as bytes are written. +- `(*CDNSource).LatestVersion(ctx context.Context) (string, error)`: Issues `GET /manifest.json` and currently returns `latest` without parsing the response body. +- `(*CDNSource).Download(ctx context.Context, m io.Medium, dest string, progress func(downloaded, total int64)) error`: Downloads `/` into `dest/`, creates the destination directory, rejects non-200 responses, and invokes the progress callback as bytes are written. ### `GitHubSource` - `NewGitHubSource(cfg SourceConfig) *GitHubSource`: Returns a GitHub Releases source with the supplied configuration. - `(*GitHubSource).Name() string`: Returns `github`. - `(*GitHubSource).Available() bool`: Requires a `gh` binary on `PATH` and a successful `gh auth status`. - `(*GitHubSource).LatestVersion(ctx context.Context) (string, error)`: Runs `gh release view -R --json tagName -q .tagName` and returns the trimmed tag. -- `(*GitHubSource).Download(ctx context.Context, m io.Medium, dest string, progress func(downloaded, total int64)) error`: Runs `gh release download` for the configured asset pattern into the destination directory and ignores the progress callback. +- `(*GitHubSource).Download(ctx context.Context, m io.Medium, dest string, progress func(downloaded, total int64)) error`: Runs `gh release download -R -p -D --clobber`; it does not use the provided medium or progress callback.