diff --git a/.gitignore b/.gitignore index 759d0fa4..ddfa7a84 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,9 @@ org-templates/**/.auth-token # GitHub App private key + other local-only secrets — never committed. .secrets/ *.pem + +# Cloned-via-manifest dirs — populated locally by scripts/clone-manifest.sh, +# tracked in their own standalone repos. Never commit to core. +/org-templates/ +/plugins/ +/workspace-configs-templates/ diff --git a/docker-compose.yml b/docker-compose.yml index 9f82f18c..00431a98 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -128,11 +128,22 @@ services: CONFIGS_DIR: /configs CONFIGS_HOST_DIR: "${CONFIGS_HOST_DIR:-${PWD}/workspace-configs-templates}" PLUGINS_HOST_DIR: "${PLUGINS_HOST_DIR:-${PWD}/plugins}" + # github-app-auth plugin — injects GITHUB_TOKEN / GH_TOKEN into every + # workspace env from the App installation token. Remap the host-side + # path in GITHUB_APP_PRIVATE_KEY_FILE to /secrets/github-app.pem inside + # the container (the private key is bind-mounted below read-only). + # Soft-dep: skipped entirely when GITHUB_APP_ID is unset. + GITHUB_APP_ID: "${GITHUB_APP_ID:-}" + GITHUB_APP_INSTALLATION_ID: "${GITHUB_APP_INSTALLATION_ID:-}" + GITHUB_APP_PRIVATE_KEY_FILE: "/secrets/github-app.pem" volumes: - ./workspace-configs-templates:/configs - ./org-templates:/org-templates:ro - ./plugins:/plugins:ro - /var/run/docker.sock:/var/run/docker.sock + # App private key — read-only bind-mount. The host-side path is + # gitignored per .gitignore rules (/.secrets/ + *.pem). + - ./.secrets/github-app.pem:/secrets/github-app.pem:ro ports: - "${PLATFORM_PUBLISH_PORT:-8080}:${PLATFORM_PORT:-8080}" networks: diff --git a/platform/cmd/server/main.go b/platform/cmd/server/main.go index 6d99701d..d65d493f 100644 --- a/platform/cmd/server/main.go +++ b/platform/cmd/server/main.go @@ -22,6 +22,11 @@ import ( "github.com/Molecule-AI/molecule-monorepo/platform/internal/scheduler" "github.com/Molecule-AI/molecule-monorepo/platform/internal/supervised" "github.com/Molecule-AI/molecule-monorepo/platform/internal/ws" + + // External plugin — registers an EnvMutator that injects GITHUB_TOKEN / + // GH_TOKEN from a GitHub App installation token. Soft-dep: only active + // when GITHUB_APP_ID env var is set (see main() for the gate). + pluginloader "github.com/Molecule-AI/molecule-ai-plugin-github-app-auth/pluginloader" ) func main() { @@ -138,6 +143,22 @@ func main() { wh.SetCPProvisioner(cpProv) } + // github-app-auth plugin — injects GITHUB_TOKEN + GH_TOKEN into every + // workspace env using the App's installation access token (rotates ~hourly). + // Soft-skip when GITHUB_APP_* env vars are absent so dev/self-hosters + // without an App configured keep working; fail-loud only on MISCONFIG + // (e.g. APP_ID set but key file missing), not on unset. + if os.Getenv("GITHUB_APP_ID") != "" { + if reg, err := pluginloader.BuildRegistry(); err != nil { + log.Fatalf("github-app-auth plugin: %v", err) + } else { + wh.SetEnvMutators(reg) + log.Printf("github-app-auth: registered, %d mutator(s) in chain", reg.Len()) + } + } else { + log.Println("github-app-auth: GITHUB_APP_ID unset — skipping plugin registration (agents will use any PAT from .env)") + } + // Offline handler: broadcast event + auto-restart the dead workspace onWorkspaceOffline := func(innerCtx context.Context, workspaceID string) { if err := broadcaster.RecordAndBroadcast(innerCtx, "WORKSPACE_OFFLINE", workspaceID, map[string]interface{}{}); err != nil { diff --git a/platform/go.mod b/platform/go.mod index 579256ff..40a93c6c 100644 --- a/platform/go.mod +++ b/platform/go.mod @@ -15,6 +15,7 @@ require ( require ( github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect + github.com/Molecule-AI/molecule-ai-plugin-github-app-auth v0.0.0-20260416194734-2cd28737f845 // indirect github.com/alicebob/miniredis/v2 v2.37.0 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect @@ -48,6 +49,7 @@ require ( github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect diff --git a/platform/go.sum b/platform/go.sum index d509601a..fd2eaa58 100644 --- a/platform/go.sum +++ b/platform/go.sum @@ -1,5 +1,7 @@ github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= +github.com/Molecule-AI/molecule-ai-plugin-github-app-auth v0.0.0-20260416194734-2cd28737f845 h1:Pae8GmpJOP/Bpf2KE1FhdN3zoPSbV/tl25yiAqEc4lM= +github.com/Molecule-AI/molecule-ai-plugin-github-app-auth v0.0.0-20260416194734-2cd28737f845/go.mod h1:3a6LR/zd7FjR9ZwLTbytwYlWuCBsbCOVFlEg0WnoYiM= github.com/alicebob/miniredis/v2 v2.37.0 h1:RheObYW32G1aiJIj81XVt78ZHJpHonHLHW7OLIshq68= github.com/alicebob/miniredis/v2 v2.37.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -88,6 +90,8 @@ github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=