Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions .github/workflows/parsar-daemon-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@
# project_daemon_platform_scope decision and the absence of a fork
# implementation for that OS.
#
# The same artifacts are consumed by the sandbox image build (the e2b
# Dockerfile at infra/e2b-templates/parsar-daemon-claudecode/e2b.Dockerfile
# pins PARSAR_DAEMON_VERSION and curls the matching linux/amd64 binary from
# the deployment's download mirror). When cutting a sandbox template
# rebuild, bump the tag here first, wait for this workflow to publish,
# then trigger the template rebuild against the new version.
# These artifacts back the device-pairing "one-line connect command"
# (scripts/install-parsar-daemon.sh / server/internal/api/
# install_parsar_daemon.sh pull from here). The sandbox image build
# (infra/sandbox/Dockerfile) does NOT consume
# this release — it compiles parsar-daemon from source in its own
# builder stage, so sandbox templates always match the checked-out
# commit and don't need a release cut first.

name: parsar-daemon release

Expand Down Expand Up @@ -68,8 +69,9 @@ jobs:
id: ver
run: |
# Strip the `parsar-daemon-` prefix from the tag so the binary
# filename matches the e2b Dockerfile's PARSAR_DAEMON_VERSION arg
# (which is bare semver like `v0.1.0` or `0.1.0`).
# filename matches what scripts/install-parsar-daemon.sh /
# server/internal/api/install_parsar_daemon.sh expect (bare
# semver like `v0.1.0` or `0.1.0`).
if [[ "${GITHUB_REF}" == refs/tags/parsar-daemon-* ]]; then
VERSION="${GITHUB_REF#refs/tags/parsar-daemon-}"
else
Expand Down Expand Up @@ -147,10 +149,10 @@ jobs:
body: |
parsar-daemon binaries for macOS + Linux (amd64 / arm64).

## Install (sandbox template)
Bump `PARSAR_DAEMON_VERSION` in
`infra/e2b-templates/parsar-daemon-claudecode/e2b.Dockerfile`
to this release, then rebuild the e2b template.
The sandbox image (infra/sandbox/Dockerfile) compiles
parsar-daemon from source and doesn't consume this release —
these binaries are for the device-pairing "one-line connect
command" only.

## Install (local laptop)
```sh
Expand Down
136 changes: 87 additions & 49 deletions .github/workflows/sandbox-image-release.yml
Original file line number Diff line number Diff line change
@@ -1,49 +1,61 @@
# Build + push the agent_daemon sandbox image to GitHub Container Registry.
# Build + push the Parsar sandbox images to GHCR.
#
# GATED: auto-triggers (push / tag) are intentionally disabled. The
# Dockerfile copies envd out of `e2bdev/base:latest`
# (`COPY --from=e2bdev/base:latest /usr/bin/envd ...`), but upstream
# removed envd from that image, so the build currently fails with
# `"/usr/bin/envd": not found`. Until the Dockerfile sources envd from a
# working location, this workflow only runs on manual workflow_dispatch
# so it cannot red-X every push that touches infra/sandbox/**.
# One Dockerfile (infra/sandbox/Dockerfile), two published images selected
# by --build-arg BASE_IMAGE:
# - parsar-sandbox local docker-backed sandbox provider
# (AGENT_DAEMON_SANDBOX_BACKEND=docker),
# multi-arch (amd64 + arm64)
# - parsar-daemon-claudecode e2b.app SaaS template, amd64 only
#
# To re-enable automatic builds: fix the envd COPY in
# infra/sandbox/parsar-daemon-claudecode/Dockerfile, then restore the
# `push:` trigger block commented out under `on:` below.
# Both compile parsar-daemon + the parsar CLI from source in the shared
# Dockerfile's builder stage — neither depends on a published
# parsar-daemon release existing first (see parsar-daemon-release.yml,
# which is a separate artifact for the device-pairing connect command).
#
# Triggers (when re-enabled):
# - Push to main when files under infra/sandbox/** change
# - Tag push matching `sandbox-v*` (release tags)
# - Manual workflow_dispatch
# Triggers:
# - Pull request touching sandbox inputs -> build only, no push
# - Push to main touching sandbox inputs -> :latest + :<sha>
# - Tag push matching `sandbox-v*` -> :<release-tag>
# - Manual workflow_dispatch -> build + push
#
# Output: ghcr.io/<owner-lowercase>/parsar-sandbox:<tag>
# - `latest` on main pushes
# - `<sha>` short-SHA tag on every build
# - `<release-tag>` on tag pushes (e.g. `sandbox-v0.0.1`)
# Output:
# ghcr.io/<owner-lowercase>/parsar-sandbox:<tag>
# ghcr.io/<owner-lowercase>/parsar-daemon-claudecode:<tag>
# - latest on default-branch pushes
# - <short-sha> on every build
# - <release-tag> on tag pushes (e.g. sandbox-v0.1.0)
#
# Operators using their own registry can copy this workflow into a fork
# and swap the `images:` field below. The sandboxset.yaml manifests
# point at `ghcr.io/<your-org>/parsar-sandbox:latest` by default.
# Operators using their own registry: copy this into a fork and swap the
# `images:` field, or just build infra/sandbox/Dockerfile locally and
# never run this workflow.

name: sandbox image release
name: sandbox images

on:
# Auto-triggers disabled until the envd COPY in the Dockerfile is
# fixed (see header). Uncomment to restore automatic builds:
# push:
# branches: [main]
# paths:
# - "infra/sandbox/parsar-daemon-claudecode/**"
# - ".github/workflows/sandbox-image-release.yml"
# tags:
# - "sandbox-v*"
push:
branches: [main]
paths:
- "infra/sandbox/**"
- "internal/**"
- "apps/parsar/**"
- "apps/parsar-daemon/**"
- "go.mod"
- "go.sum"
- "go.work"
- ".github/workflows/sandbox-image-release.yml"
tags:
- "sandbox-v*"
pull_request:
paths:
- "infra/sandbox/**"
- "internal/**"
- "apps/parsar/**"
- "apps/parsar-daemon/**"
- "go.mod"
- "go.sum"
- "go.work"
- ".github/workflows/sandbox-image-release.yml"
workflow_dispatch:
inputs:
parsar_daemon_version:
description: "parsar-daemon release tag to bake into the image (blank = latest)"
required: false
default: ""

permissions:
contents: read
Expand All @@ -52,22 +64,49 @@ permissions:
jobs:
build-and-push:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- variant: local
image_name: parsar-sandbox
base_image: ubuntu:22.04
platforms_pr: linux/amd64
platforms_publish: linux/amd64,linux/arm64
- variant: e2b
image_name: parsar-daemon-claudecode
base_image: e2bdev/base:latest
# e2b.app sandboxes are amd64 only — no point publishing arm64.
platforms_pr: linux/amd64
platforms_publish: linux/amd64
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Set up QEMU
if: github.event_name != 'pull_request' && matrix.variant == 'local'
uses: docker/setup-qemu-action@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Choose build platforms
id: platforms
shell: bash
run: |
if [[ "$GITHUB_EVENT_NAME" == "pull_request" ]]; then
echo "value=${{ matrix.platforms_pr }}" >> "$GITHUB_OUTPUT"
else
echo "value=${{ matrix.platforms_publish }}" >> "$GITHUB_OUTPUT"
fi

- name: Compute lowercase GHCR image name
id: image
shell: bash
run: echo "name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/parsar-sandbox" >> "$GITHUB_OUTPUT"
run: echo "name=ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/${{ matrix.image_name }}" >> "$GITHUB_OUTPUT"

- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
with:
registry: ghcr.io
Expand All @@ -79,24 +118,23 @@ jobs:
uses: docker/metadata-action@v6
with:
images: ${{ steps.image.outputs.name }}
# latest on main, sha for every push, release tag on tag push
tags: |
type=ref,event=branch
type=ref,event=pr
type=ref,event=tag
type=sha,format=short
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push
- name: Build image
uses: docker/build-push-action@v7
with:
context: infra/sandbox/parsar-daemon-claudecode
file: infra/sandbox/parsar-daemon-claudecode/Dockerfile
platforms: linux/amd64
push: true
context: .
file: infra/sandbox/Dockerfile
platforms: ${{ steps.platforms.outputs.value }}
build-args: |
BASE_IMAGE=${{ matrix.base_image }}
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
PARSAR_DAEMON_REPO=${{ github.repository }}
PARSAR_DAEMON_VERSION=${{ github.event.inputs.parsar_daemon_version }}
cache-from: type=gha
cache-to: type=gha,mode=max
cache-from: type=gha,scope=sandbox-${{ matrix.variant }}
cache-to: type=gha,mode=max,scope=sandbox-${{ matrix.variant }}
6 changes: 6 additions & 0 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ user is the administrator.
- Docker Engine with Docker Compose v2.
- Linux host with access to `/var/run/docker.sock`. The local compose stack
enables Docker-managed agent sandboxes and mounts the Docker socket.
- To actually run Agents in sandbox mode, build the sandbox image first —
there is no published default:
```bash
docker build -f infra/sandbox/Dockerfile -t parsar-sandbox:local .
```
Everything else in the stack works without it.

## What The Installer Does

Expand Down
14 changes: 8 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,16 +154,18 @@ services:
PARSAR_DISCORD_APP_ID: "${PARSAR_DISCORD_APP_ID:-}"
PARSAR_DISCORD_BOT_TOKEN: "${PARSAR_DISCORD_BOT_TOKEN:-}"
# Docker sandbox — Agent runs inside a sandboxed container with
# Claude Code + OpenCode + Codex + Pi + parsar-daemon. The server
# calls docker to start / exec / stop sandbox containers via the
# mounted /var/run/docker.sock below. Build the sandbox image
# first (README's 5.2 section):
# docker build -f infra/sandbox/Dockerfile.local -t parsar-sandbox:local .
# Claude Code + Codex + Pi + parsar-daemon. The server calls docker
# to start / exec / stop sandbox containers via the mounted
# /var/run/docker.sock below. There is no published image for this —
# build one locally first (README's 5.2 section):
# docker build -f infra/sandbox/Dockerfile -t parsar-sandbox:local .
# Until you do, sandbox-mode Agent runs fail with "no such image";
# everything else in this stack works fine without it.
# The network below is pinned to `parsar_default` — the top-level
# `name: parsar` + `networks:` block at the head of this file
# guarantees that alias regardless of the clone directory.
AGENT_DAEMON_SANDBOX_BACKEND: "docker"
AGENT_DAEMON_SANDBOX_DOCKER_IMAGE: "${PARSAR_SANDBOX_IMAGE:-ghcr.io/minimax-ai-dev/parsar-sandbox:latest}"
AGENT_DAEMON_SANDBOX_DOCKER_IMAGE: "${PARSAR_SANDBOX_IMAGE:-parsar-sandbox:local}"
AGENT_DAEMON_SANDBOX_DOCKER_NETWORK: "${PARSAR_PROJECT_NAME:-parsar}_default"
AGENT_DAEMON_SANDBOX_SERVER_URL: "http://parsar-server:8080"
group_add:
Expand Down
24 changes: 15 additions & 9 deletions docs/deploy/lan-deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,25 +207,31 @@ sudo docker run --rm --entrypoint ls parsar:local /usr/local/share/parsar/daemon
### 5.2 Build the sandbox image

The sandbox image is the container Agents start when running in
Docker-sandbox mode; it contains Claude Code + Codex + parsar-daemon.
Docker-sandbox mode; it contains Claude Code + Codex + Pi CLI +
parsar-daemon. It builds parsar-daemon and the parsar CLI from source in
its own Go builder stage — independent of 5.1, order doesn't matter.

```bash
# With proxy (read from .env):
source .env
sudo docker build \
-f infra/sandbox/Dockerfile.local \
-f infra/sandbox/Dockerfile \
-t parsar-sandbox:local \
--build-arg http_proxy="$HTTP_PROXY" \
--build-arg https_proxy="$HTTPS_PROXY" \
.

# Without proxy:
sudo docker build -f infra/sandbox/Dockerfile.local -t parsar-sandbox:local .
sudo docker build -f infra/sandbox/Dockerfile -t parsar-sandbox:local .

# On Apple Silicon (arm64):
sudo docker build --platform linux/arm64 -f infra/sandbox/Dockerfile -t parsar-sandbox:local .
```

> `Dockerfile.local` copies the daemon binary from the server image and
> downloads the Claude Code CLI from a CDN — no GitHub Release required.
> **You must complete 5.1 before running 5.2.**
> `Dockerfile` downloads the Claude Code + Codex CLIs from their
> respective CDN/release endpoints and compiles parsar-daemon + the
> parsar CLI from this repo's source — no GitHub Release, no dependency
> on the server image (5.1) being built first.

Verify:

Expand Down Expand Up @@ -334,7 +340,7 @@ sudo docker compose -f docker-compose.yml down -v # also delete data volumes
```bash
git pull
sudo docker build -t parsar:local .
sudo docker build -f infra/sandbox/Dockerfile.local -t parsar-sandbox:local .
sudo docker build -f infra/sandbox/Dockerfile -t parsar-sandbox:local .
sudo docker compose -f docker-compose.yml up -d --force-recreate
```

Expand Down Expand Up @@ -374,7 +380,7 @@ snippets in §5) because `docker build` does not read `.env`.
| Feishu login reports `redirect_uri mismatch` | `.env`'s `REDIRECT_URI` does not match the Feishu console | Keep both sides completely identical (scheme, IP, port, path) |
| Other machines cannot reach 18080 | Firewall blocking it | `sudo ufw allow 18080/tcp` or the equivalent firewall rule |
| Device pairing downloads daemon and hits **404** | The GHCR image has no embedded daemon | Build the server image locally (§5.1) |
| Agent reports **"no runtime yet — ask an admin to rebuild it"** | The sandbox image lacks the Agent CLI | Rebuild the sandbox image via `Dockerfile.local` (§5.2), then click Rebuild in the UI |
| Agent reports **"no runtime yet — ask an admin to rebuild it"** | The sandbox image lacks the Agent CLI | Rebuild the sandbox image via `Dockerfile` (§5.2), then click Rebuild in the UI |
| Sandbox comes up but the daemon cannot reach the server | Compose project name is not `parsar`, so network names do not match | Use the repo's `docker-compose.yml` (has `name: parsar` pinned at the top); do not override with `-p <other name>` |
| Group @Bot **no response**, DM works fine | `PARSAR_FEISHU_DEFAULT_BOT_OPEN_ID` not set | Add the Bot's open_id to `.env` and restart the server |
| Bot receives messages but does not reply | Outbound worker is not up | Grep server logs for `feishu outbound`; confirm `PARSAR_FEISHU_OUTBOUND=true` (compose already sets it) |
Expand Down Expand Up @@ -425,7 +431,7 @@ vim .env # fill in Feishu credentials + master key + PARSAR_HOST_IP + Bot Open

# 2. Build images
sudo docker build -t parsar:local .
sudo docker build -f infra/sandbox/Dockerfile.local -t parsar-sandbox:local .
sudo docker build -f infra/sandbox/Dockerfile -t parsar-sandbox:local .

# 3. Bring it up
sudo docker compose -f docker-compose.yml up -d
Expand Down
Loading