diff --git a/.github/workflows/build-master.yaml b/.github/workflows/build-master.yaml index 133350e..dd0f61e 100644 --- a/.github/workflows/build-master.yaml +++ b/.github/workflows/build-master.yaml @@ -6,7 +6,9 @@ on: jobs: meta: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 outputs: short-sha: ${{ steps.sha.outputs.short }} steps: diff --git a/.github/workflows/build-pr.yaml b/.github/workflows/build-pr.yaml index 6dcc7bf..5153525 100644 --- a/.github/workflows/build-pr.yaml +++ b/.github/workflows/build-pr.yaml @@ -7,7 +7,9 @@ on: jobs: meta: if: contains(github.event.pull_request.labels.*.name, 'build') && github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 outputs: short-sha: ${{ steps.sha.outputs.short }} steps: @@ -31,7 +33,9 @@ jobs: comment: needs: [meta, build] if: contains(github.event.pull_request.labels.*.name, 'build') && github.event.pull_request.head.repo.full_name == github.repository - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 steps: - name: Comment PR with image tags uses: actions/github-script@v7 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5648e37..768c06c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,9 @@ concurrency: jobs: test: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 steps: - uses: actions/checkout@v4 @@ -45,7 +47,9 @@ jobs: run: make test lint: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 548fa03..5fde2ca 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -29,7 +29,9 @@ concurrency: jobs: goreleaser-check: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-l-x64 steps: - uses: actions/checkout@v4 with: @@ -51,8 +53,55 @@ jobs: env: LATEST_TAG: latest + - name: Build server and sandbox images + run: | + mkdir -p linux/amd64 + cp dist/panda-server_linux_amd64_v1/panda-server linux/amd64/panda-server + docker build -f goreleaser.server.Dockerfile -t panda-server-smoke . + docker build -f sandbox/Dockerfile -t panda-sandbox-smoke . + + - name: Smoke test server sandbox initialization + run: | + # Write a minimal config that the server can parse. + # The server will initialize the sandbox (Docker access, image pull, + # network creation) before attempting to connect to the proxy. + cat > /tmp/smoke-config.yaml <<'CONFIG' + server: + host: "0.0.0.0" + port: 2480 + transport: sse + sandbox: + image: "panda-sandbox-smoke:latest" + network: "panda-smoke-test" + proxy: + url: "http://localhost:19999" + CONFIG + + # Run the server and capture logs. It will fail at proxy connection, + # but we check that sandbox initialization completed successfully. + LOGS=$(docker run --rm \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -v /tmp/smoke-config.yaml:/app/config.yaml:ro \ + --group-add "$(stat -c '%g' /var/run/docker.sock)" \ + panda-server-smoke \ + serve --config /app/config.yaml 2>&1 || true) + + echo "$LOGS" + + if echo "$LOGS" | grep -q "Sandbox service started"; then + echo "Smoke test passed: sandbox initialized successfully" + else + echo "Smoke test failed: sandbox did not initialize" + exit 1 + fi + + # Clean up the network created by the server. + docker network rm panda-smoke-test 2>/dev/null || true + build-sandbox: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/eval.yaml b/.github/workflows/eval.yaml index 9e58de3..d5918c6 100644 --- a/.github/workflows/eval.yaml +++ b/.github/workflows/eval.yaml @@ -40,7 +40,9 @@ on: jobs: evaluate: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 timeout-minutes: 60 steps: @@ -224,7 +226,9 @@ jobs: comment_mode: always compare-models: - runs-on: ubuntu-latest + runs-on: + - self-hosted-ghr + - size-m-x64 needs: evaluate if: github.event_name == 'workflow_dispatch' && inputs.model == 'claude-sonnet-4-5' diff --git a/goreleaser.server.Dockerfile b/goreleaser.server.Dockerfile index 8846773..df69184 100644 --- a/goreleaser.server.Dockerfile +++ b/goreleaser.server.Dockerfile @@ -22,7 +22,7 @@ RUN mkdir -p /model/all-MiniLM-L6-v2 && \ # ============================================================================= FROM alpine:3.21 -RUN apk add --no-cache ca-certificates tzdata +RUN apk add --no-cache ca-certificates tzdata docker-cli RUN addgroup -g 1000 panda && \ adduser -u 1000 -G panda -D panda diff --git a/pkg/cli/init.go b/pkg/cli/init.go index 2d89f84..4317a19 100644 --- a/pkg/cli/init.go +++ b/pkg/cli/init.go @@ -7,6 +7,8 @@ import ( "os" "os/exec" "path/filepath" + "strconv" + "syscall" "time" dockerimage "github.com/docker/docker/api/types/image" @@ -197,6 +199,8 @@ proxy: } func buildComposeTemplate(serverImage, configDir string) string { + dockerGID := detectDockerSocketGID() + return fmt.Sprintf(`# panda server - Docker Compose configuration # Generated by 'panda init'. Managed by 'panda server' commands. @@ -205,6 +209,8 @@ services: image: %s container_name: panda-server restart: unless-stopped + group_add: + - "%s" ports: - "127.0.0.1:2480:2480" volumes: @@ -223,7 +229,7 @@ networks: volumes: panda-storage: -`, serverImage, configDir) +`, serverImage, dockerGID, configDir) } func checkDockerAndPullImages() error { @@ -298,3 +304,23 @@ func pullImage(cli *dockerclient.Client, image string) error { return nil } + +// detectDockerSocketGID returns the group ID that owns /var/run/docker.sock. +// This is used to add the correct group to the server container so the +// non-root panda user can access the Docker socket. Falls back to "0" (root) +// if the socket cannot be stat'd. +func detectDockerSocketGID() string { + const dockerSocket = "/var/run/docker.sock" + + info, err := os.Stat(dockerSocket) + if err != nil { + return "0" + } + + stat, ok := info.Sys().(*syscall.Stat_t) + if !ok { + return "0" + } + + return strconv.FormatUint(uint64(stat.Gid), 10) +}