Files
Alexandru MacocianandCopilot 80451af915
Charlie/project-charlie: Build image / config (push) Successful in 2s
Charlie/project-charlie: Deploy runners / config (push) Successful in 0s
Charlie/project-charlie: Deploy runners / deploy-morgott (push) Skipped
Charlie/project-charlie: Deploy runners / deploy-melina (push) Skipped
Charlie/project-charlie: Deploy stack / config (push) Successful in 1s
Charlie/project-charlie: Deploy stack / deploy (push) Skipped
Charlie/project-charlie: Build image / build (push) Successful in 5m57s
Run the agent as an unprivileged user against read-only repos
The entrypoint now mirrors and locks the repo checkout read-only
(root-owned) as root, renders the sherlock config into the copilot
user's home, then drops the service to the jarvis user. The jarvis
binary carries CAP_SETUID/SETGID so the non-root service can spawn the
agent as the separate copilot user.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 5749c447-6ecd-46bd-946d-21b4d101d084
2026-07-30 18:51:25 +02:00

91 lines
4.1 KiB
Docker

# ---------- Builder ----------
FROM golang:1.26-bookworm AS builder
WORKDIR /src
# Cache modules first.
COPY go.mod go.sum ./
RUN go mod download
# Build the static (cgo-free, pure-Go sqlite) binary.
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -ldflags "-s -w" -o /out/jarvis ./cmd/jarvis
# Install the sherlock credential broker + its MCP servers so the headless
# Copilot agent gets live read-only access to Grafana, Gitea, and hosts.
# Pinned to a released tag; the module is public so no auth is needed.
# GOPROXY=direct + GOSUMDB=off because it is served from Charlie's Gitea,
# not the public proxy/checksum database.
ARG SHERLOCK_VERSION=v0.1.11
RUN GOBIN=/out GOFLAGS=-trimpath GOPROXY=direct GOSUMDB=off CGO_ENABLED=0 \
go install \
gitea.alexandru.macocian.me/amacocian/sherlock/cmd/sherlock@${SHERLOCK_VERSION} \
gitea.alexandru.macocian.me/amacocian/sherlock/cmd/grafana-mcp@${SHERLOCK_VERSION} \
gitea.alexandru.macocian.me/amacocian/sherlock/cmd/gitea-mcp@${SHERLOCK_VERSION} \
gitea.alexandru.macocian.me/amacocian/sherlock/cmd/gssh-mcp@${SHERLOCK_VERSION}
# ---------- Runtime ----------
# node base image so the GitHub Copilot CLI (a Node package) runs headless.
FROM node:22-bookworm-slim AS runtime
# Runtime deps: git (clone Charlie repos for context), ca-certs, jq (parse
# the Gitea API in the entrypoint), tini (PID 1 signal handling), gosu (drop
# privileges to the jarvis user after fixing the bind-mount ownership),
# gettext-base (envsubst, to render the sherlock config from env), libcap2-bin
# (setcap: grant the jarvis binary CAP_SETUID/SETGID so it can drop the agent
# to a separate unprivileged user without the whole service running as root).
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates jq tini curl gosu gettext-base libcap2-bin \
&& rm -rf /var/lib/apt/lists/*
# Install the GitHub Copilot CLI globally (provides the `copilot` binary).
RUN npm install -g @github/copilot && npm cache clean --force
# Two unprivileged users:
# jarvis (10001) runs the service and owns /data (the SQLite DB).
# copilot (10002) runs the --yolo agent + MCP subprocess tree, confined to
# its own writable home; the mirrored repos are mounted read-only.
# /context holds the cloned repos (locked read-only at startup by the
# entrypoint); /data holds the SQLite DB.
RUN useradd --create-home --uid 10001 jarvis \
&& useradd --create-home --uid 10002 copilot \
&& mkdir -p /context /data \
&& chown -R jarvis:jarvis /context /data
COPY --from=builder /out/jarvis /usr/local/bin/jarvis
# Let the (non-root) jarvis service drop the agent subprocess to the copilot
# user via setuid/setgid, while the service itself keeps running as jarvis.
RUN setcap cap_setuid,cap_setgid+ep /usr/local/bin/jarvis
# The mirrored repos are checked out root-owned and read-only; allow any user
# (the copilot agent) to run read-only git commands inside them.
RUN git config --system --add safe.directory '*'
# sherlock CLI + MCP servers on PATH so `sherlock copilot` can spawn them.
COPY --from=builder /out/sherlock /out/grafana-mcp /out/gitea-mcp /out/gssh-mcp /usr/local/bin/
COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
# The container starts as root so the entrypoint can chown the /data bind
# mount (which Docker creates root-owned on the host), then drops to the
# unprivileged jarvis user via gosu before running the service.
WORKDIR /context
ENV HOME="/home/jarvis" \
JARVIS_LISTEN=":8080" \
JARVIS_DB_PATH="/data/jarvis.db" \
JARVIS_WORKDIR="/context" \
JARVIS_AGENT_WORKDIR="/context/Charlie/project-charlie" \
JARVIS_ADD_DIRS="/context" \
JARVIS_COPILOT_USER="copilot" \
GITEA_URL="https://gitea.alexandru.macocian.me" \
GITEA_ORGS="Charlie" \
GRAFANA_URL="https://grafana.alexandru.macocian.me" \
GSSH_URL="https://terminal.alexandru.macocian.me" \
SHERLOCK_ISSUER="https://id.alexandru.macocian.me/application/o/sherlock-cli/" \
SHERLOCK_KEYRING="memory" \
SHERLOCK_CONFIG="/home/copilot/.config/sherlock/config.toml"
EXPOSE 8080
VOLUME ["/data"]
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]