2026-01-29 02:17:34 +00:00
|
|
|
package devops
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
docs(audit): add dependency security audit report (#248)
* feat(devops): migrate filesystem operations to io.Local abstraction
Migrate config.go:
- os.ReadFile → io.Local.Read
Migrate devops.go:
- os.Stat → io.Local.IsFile
Migrate images.go:
- os.MkdirAll → io.Local.EnsureDir
- os.Stat → io.Local.IsFile
- os.ReadFile → io.Local.Read
- os.WriteFile → io.Local.Write
Migrate test.go:
- os.ReadFile → io.Local.Read
- os.Stat → io.Local.IsFile
Migrate claude.go:
- os.Stat → io.Local.IsDir
Updated tests to reflect improved behavior:
- Manifest.Save() now creates parent directories
- hasFile() correctly returns false for directories
Part of #101 (io.Medium migration tracking issue).
Closes #107
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(io): migrate remaining packages to io.Local abstraction
Migrate filesystem operations to use the io.Local abstraction for
improved security, testability, and consistency:
- pkg/cache: Replace os.ReadFile, WriteFile, Remove, RemoveAll with
io.Local equivalents. io.Local.Write creates parent dirs automatically.
- pkg/agentic: Migrate config.go and context.go to use io.Local for
reading config files and gathering file context.
- pkg/repos: Use io.Local.Read, Exists, IsDir, List for registry
operations and git repo detection.
- pkg/release: Use io.Local for config loading, existence checks,
and artifact discovery.
- pkg/devops/sources: Use io.Local.EnsureDir for CDN download.
All paths are converted to absolute using filepath.Abs() before
calling io.Local methods to handle relative paths correctly.
Closes #104, closes #106, closes #108, closes #111
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(io): migrate pkg/cli and pkg/container to io.Local abstraction
Continue io.Medium migration for the remaining packages:
- pkg/cli/daemon.go: PIDFile Acquire/Release now use io.Local.Read,
Delete, and Write for managing daemon PID files.
- pkg/container/state.go: LoadState and SaveState use io.Local for
JSON state persistence. EnsureLogsDir uses io.Local.EnsureDir.
- pkg/container/templates.go: Template loading and directory scanning
now use io.Local.IsFile, IsDir, Read, and List.
- pkg/container/linuxkit.go: Image validation uses io.Local.IsFile,
log file check uses io.Local.IsFile. Streaming log file creation
(os.Create) remains unchanged as io.Local doesn't support streaming.
Closes #105, closes #107
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs(audit): add dependency security audit report
Complete security audit of all project dependencies:
- Run govulncheck: No vulnerabilities found
- Run go mod verify: All modules verified
- Document 15 direct dependencies and 161 indirect
- Assess supply chain risks: Low risk overall
- Verify lock files are committed with integrity hashes
- Provide CI integration recommendations
Closes #185
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ci): build core CLI from source instead of downloading release
The workflows were trying to download from a non-existent release URL.
Now builds the CLI directly using `go build` with version injection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: trigger CI with updated workflow
* chore(ci): add workflow_dispatch trigger for manual runs
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 08:04:26 +00:00
|
|
|
|
|
|
|
|
"github.com/host-uk/core/pkg/io"
|
2026-01-29 02:17:34 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ClaudeOptions configures the Claude sandbox session.
|
|
|
|
|
type ClaudeOptions struct {
|
|
|
|
|
NoAuth bool // Don't forward any auth
|
|
|
|
|
Auth []string // Selective auth: "gh", "anthropic", "ssh", "git"
|
|
|
|
|
Model string // Model to use: opus, sonnet
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Claude starts a sandboxed Claude session in the dev environment.
|
|
|
|
|
func (d *DevOps) Claude(ctx context.Context, projectDir string, opts ClaudeOptions) error {
|
|
|
|
|
// Auto-boot if not running
|
|
|
|
|
running, err := d.IsRunning(ctx)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if !running {
|
|
|
|
|
fmt.Println("Dev environment not running, booting...")
|
|
|
|
|
if err := d.Boot(ctx, DefaultBootOptions()); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to boot: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Mount project
|
|
|
|
|
if err := d.mountProject(ctx, projectDir); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to mount project: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare environment variables to forward
|
|
|
|
|
envVars := []string{}
|
|
|
|
|
|
|
|
|
|
if !opts.NoAuth {
|
|
|
|
|
authTypes := opts.Auth
|
|
|
|
|
if len(authTypes) == 0 {
|
|
|
|
|
authTypes = []string{"gh", "anthropic", "ssh", "git"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, auth := range authTypes {
|
|
|
|
|
switch auth {
|
|
|
|
|
case "anthropic":
|
|
|
|
|
if key := os.Getenv("ANTHROPIC_API_KEY"); key != "" {
|
|
|
|
|
envVars = append(envVars, "ANTHROPIC_API_KEY="+key)
|
|
|
|
|
}
|
|
|
|
|
case "git":
|
|
|
|
|
// Forward git config
|
|
|
|
|
name, _ := exec.Command("git", "config", "user.name").Output()
|
|
|
|
|
email, _ := exec.Command("git", "config", "user.email").Output()
|
|
|
|
|
if len(name) > 0 {
|
|
|
|
|
envVars = append(envVars, "GIT_AUTHOR_NAME="+strings.TrimSpace(string(name)))
|
|
|
|
|
envVars = append(envVars, "GIT_COMMITTER_NAME="+strings.TrimSpace(string(name)))
|
|
|
|
|
}
|
|
|
|
|
if len(email) > 0 {
|
|
|
|
|
envVars = append(envVars, "GIT_AUTHOR_EMAIL="+strings.TrimSpace(string(email)))
|
|
|
|
|
envVars = append(envVars, "GIT_COMMITTER_EMAIL="+strings.TrimSpace(string(email)))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build SSH command with agent forwarding
|
|
|
|
|
args := []string{
|
2026-02-05 11:00:49 +00:00
|
|
|
"-o", "StrictHostKeyChecking=accept-new",
|
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs
Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.
Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.
Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add collect, config, crypt, plugin packages and fix all lint issues
Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)
Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.
Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00
|
|
|
"-o", "UserKnownHostsFile=~/.core/known_hosts",
|
2026-01-29 02:17:34 +00:00
|
|
|
"-o", "LogLevel=ERROR",
|
|
|
|
|
"-A", // SSH agent forwarding
|
2026-02-05 11:00:49 +00:00
|
|
|
"-p", "2222",
|
2026-01-29 02:17:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
args = append(args, "root@localhost")
|
|
|
|
|
|
|
|
|
|
// Build command to run inside
|
|
|
|
|
claudeCmd := "cd /app && claude"
|
|
|
|
|
if opts.Model != "" {
|
|
|
|
|
claudeCmd += " --model " + opts.Model
|
|
|
|
|
}
|
|
|
|
|
args = append(args, claudeCmd)
|
|
|
|
|
|
|
|
|
|
// Set environment for SSH
|
|
|
|
|
cmd := exec.CommandContext(ctx, "ssh", args...)
|
|
|
|
|
cmd.Stdin = os.Stdin
|
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
|
|
|
|
|
|
// Pass environment variables through SSH
|
|
|
|
|
for _, env := range envVars {
|
|
|
|
|
parts := strings.SplitN(env, "=", 2)
|
|
|
|
|
if len(parts) == 2 {
|
|
|
|
|
cmd.Env = append(os.Environ(), env)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Println("Starting Claude in sandboxed environment...")
|
|
|
|
|
fmt.Println("Project mounted at /app")
|
|
|
|
|
fmt.Println("Auth forwarded: SSH agent" + formatAuthList(opts))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
return cmd.Run()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func formatAuthList(opts ClaudeOptions) string {
|
|
|
|
|
if opts.NoAuth {
|
|
|
|
|
return " (none)"
|
|
|
|
|
}
|
|
|
|
|
if len(opts.Auth) == 0 {
|
|
|
|
|
return ", gh, anthropic, git"
|
|
|
|
|
}
|
|
|
|
|
return ", " + strings.Join(opts.Auth, ", ")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CopyGHAuth copies GitHub CLI auth to the VM.
|
|
|
|
|
func (d *DevOps) CopyGHAuth(ctx context.Context) error {
|
|
|
|
|
home, err := os.UserHomeDir()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ghConfigDir := filepath.Join(home, ".config", "gh")
|
docs(audit): add dependency security audit report (#248)
* feat(devops): migrate filesystem operations to io.Local abstraction
Migrate config.go:
- os.ReadFile → io.Local.Read
Migrate devops.go:
- os.Stat → io.Local.IsFile
Migrate images.go:
- os.MkdirAll → io.Local.EnsureDir
- os.Stat → io.Local.IsFile
- os.ReadFile → io.Local.Read
- os.WriteFile → io.Local.Write
Migrate test.go:
- os.ReadFile → io.Local.Read
- os.Stat → io.Local.IsFile
Migrate claude.go:
- os.Stat → io.Local.IsDir
Updated tests to reflect improved behavior:
- Manifest.Save() now creates parent directories
- hasFile() correctly returns false for directories
Part of #101 (io.Medium migration tracking issue).
Closes #107
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(io): migrate remaining packages to io.Local abstraction
Migrate filesystem operations to use the io.Local abstraction for
improved security, testability, and consistency:
- pkg/cache: Replace os.ReadFile, WriteFile, Remove, RemoveAll with
io.Local equivalents. io.Local.Write creates parent dirs automatically.
- pkg/agentic: Migrate config.go and context.go to use io.Local for
reading config files and gathering file context.
- pkg/repos: Use io.Local.Read, Exists, IsDir, List for registry
operations and git repo detection.
- pkg/release: Use io.Local for config loading, existence checks,
and artifact discovery.
- pkg/devops/sources: Use io.Local.EnsureDir for CDN download.
All paths are converted to absolute using filepath.Abs() before
calling io.Local methods to handle relative paths correctly.
Closes #104, closes #106, closes #108, closes #111
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore(io): migrate pkg/cli and pkg/container to io.Local abstraction
Continue io.Medium migration for the remaining packages:
- pkg/cli/daemon.go: PIDFile Acquire/Release now use io.Local.Read,
Delete, and Write for managing daemon PID files.
- pkg/container/state.go: LoadState and SaveState use io.Local for
JSON state persistence. EnsureLogsDir uses io.Local.EnsureDir.
- pkg/container/templates.go: Template loading and directory scanning
now use io.Local.IsFile, IsDir, Read, and List.
- pkg/container/linuxkit.go: Image validation uses io.Local.IsFile,
log file check uses io.Local.IsFile. Streaming log file creation
(os.Create) remains unchanged as io.Local doesn't support streaming.
Closes #105, closes #107
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* docs(audit): add dependency security audit report
Complete security audit of all project dependencies:
- Run govulncheck: No vulnerabilities found
- Run go mod verify: All modules verified
- Document 15 direct dependencies and 161 indirect
- Assess supply chain risks: Low risk overall
- Verify lock files are committed with integrity hashes
- Provide CI integration recommendations
Closes #185
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(ci): build core CLI from source instead of downloading release
The workflows were trying to download from a non-existent release URL.
Now builds the CLI directly using `go build` with version injection.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* chore: trigger CI with updated workflow
* chore(ci): add workflow_dispatch trigger for manual runs
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 08:04:26 +00:00
|
|
|
if !io.Local.IsDir(ghConfigDir) {
|
2026-01-29 02:17:34 +00:00
|
|
|
return nil // No gh config to copy
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use scp to copy gh config
|
|
|
|
|
cmd := exec.CommandContext(ctx, "scp",
|
2026-02-05 11:00:49 +00:00
|
|
|
"-o", "StrictHostKeyChecking=accept-new",
|
feat: infrastructure packages and lint cleanup (#281)
* ci: consolidate duplicate workflows and merge CodeQL configs
Remove 17 duplicate workflow files that were split copies of the
combined originals. Each family (CI, CodeQL, Coverage, PR Build,
Alpha Release) had the same job duplicated across separate
push/pull_request/schedule/manual trigger files.
Merge codeql.yml and codescan.yml into a single codeql.yml with
a language matrix covering go, javascript-typescript, python,
and actions — matching the previous default setup coverage.
Remaining workflows (one per family):
- ci.yml (push + PR + manual)
- codeql.yml (push + PR + schedule, all languages)
- coverage.yml (push + PR + manual)
- alpha-release.yml (push + manual)
- pr-build.yml (PR + manual)
- release.yml (tag push)
- agent-verify.yml, auto-label.yml, auto-project.yml
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* feat: add collect, config, crypt, plugin packages and fix all lint issues
Add four new infrastructure packages with CLI commands:
- pkg/config: layered configuration (defaults → file → env → flags)
- pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums)
- pkg/plugin: plugin system with GitHub-based install/update/remove
- pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate)
Fix all golangci-lint issues across the entire codebase (~100 errcheck,
staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that
`core go qa` passes with 0 issues.
Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 11:34:43 +00:00
|
|
|
"-o", "UserKnownHostsFile=~/.core/known_hosts",
|
2026-01-29 02:17:34 +00:00
|
|
|
"-o", "LogLevel=ERROR",
|
2026-02-05 11:00:49 +00:00
|
|
|
"-P", "2222",
|
2026-01-29 02:17:34 +00:00
|
|
|
"-r", ghConfigDir,
|
|
|
|
|
"root@localhost:/root/.config/",
|
|
|
|
|
)
|
|
|
|
|
return cmd.Run()
|
|
|
|
|
}
|