Skip to content
Open
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
14 changes: 14 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
.git
.codex
.github
.knowledge
node_modules
**/node_modules
**/.cache
**/.turbo
coverage
dist
dist-test
dist-web
packages/*/coverage
packages/*/dist
packages/*/dist-test
packages/*/dist-web
**/.vite
**/*.log
third_party/skiller-desktop-skills-manager/out
53 changes: 48 additions & 5 deletions .github/actions/free-docker-disk/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,51 @@ runs:
- name: Free disk for Docker builds
shell: bash
run: |
set -euxo pipefail
set -euo pipefail

df -h
docker system df || true
if command -v df >/dev/null 2>&1; then
df -h || true
else
echo "df is not available; Docker disk cleanup will run without a free-space precheck." >&2
fi

threshold_gib="${DOCKER_GIT_FREE_DOCKER_DISK_MIN_AVAILABLE_GIB:-40}"
force_cleanup="${DOCKER_GIT_FORCE_FREE_DOCKER_DISK:-0}"
force_cleanup_normalized="${force_cleanup,,}"
force_cleanup_enabled=0

if [[ ! "$threshold_gib" =~ ^[0-9]+$ ]]; then
echo "Invalid DOCKER_GIT_FREE_DOCKER_DISK_MIN_AVAILABLE_GIB: $threshold_gib" >&2
exit 1
fi

case "$force_cleanup_normalized" in
1|true|yes|on)
force_cleanup_enabled=1
;;
*)
force_cleanup_enabled=0
;;
esac

if command -v df >/dev/null 2>&1; then
available_kib="$(df -Pk / 2>/dev/null | awk 'NR == 2 { print $4 }' || true)"
else
available_kib=""
fi
if [[ ! "$available_kib" =~ ^[0-9]+$ ]]; then
echo "Could not parse available disk space from df output; Docker disk cleanup will run." >&2
available_kib=0
fi
threshold_kib="$((threshold_gib * 1024 * 1024))"

if [[ "$force_cleanup_enabled" != "1" && "$available_kib" -ge "$threshold_kib" ]]; then
echo "Skipping Docker disk cleanup: / has at least ${threshold_gib}GiB available."
exit 0
fi

echo "Running Docker disk cleanup: available=${available_kib}KiB threshold=${threshold_kib}KiB force=${force_cleanup}."
timeout 20s docker system df || true

sudo rm -rf \
/opt/ghc \
Expand All @@ -21,5 +62,7 @@ runs:

docker system prune -af --volumes || true

df -h
docker system df || true
if command -v df >/dev/null 2>&1; then
df -h || true
fi
timeout 20s docker system df || true
18 changes: 18 additions & 0 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ jobs:
name: E2E (Browser command)
runs-on: ubuntu-latest
timeout-minutes: 40
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -170,6 +173,9 @@ jobs:
name: E2E (OpenCode)
runs-on: ubuntu-latest
timeout-minutes: 40
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -187,6 +193,9 @@ jobs:
name: E2E (Clone cache)
runs-on: ubuntu-latest
timeout-minutes: 40
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -204,6 +213,9 @@ jobs:
name: E2E (Login context)
runs-on: ubuntu-latest
timeout-minutes: 40
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -221,6 +233,9 @@ jobs:
name: E2E (Runtime volumes + SSH)
runs-on: ubuntu-latest
timeout-minutes: 60
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@v6
with:
Expand All @@ -238,6 +253,9 @@ jobs:
name: E2E (Clone auto-open SSH)
runs-on: ubuntu-latest
timeout-minutes: 40
env:
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: "0"
DOCKER_GIT_E2E_REUSE_WORKSPACE_INSTALL: "1"
steps:
- uses: actions/checkout@v6
with:
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ services:
context: .
dockerfile: packages/api/Dockerfile
args:
DOCKER_GIT_CONTROLLER_REV: ${DOCKER_GIT_CONTROLLER_REV:-unknown}
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: ${DOCKER_GIT_CONTROLLER_BUILD_SKILLER:-1}
UBUNTU_APT_MIRROR: ${UBUNTU_APT_MIRROR:-}
container_name: ${DOCKER_GIT_API_CONTAINER_NAME:-docker-git-api}
environment:
DOCKER_GIT_API_PORT: ${DOCKER_GIT_API_PORT:-3334}
DOCKER_GIT_CONTROLLER_REV: ${DOCKER_GIT_CONTROLLER_REV:-unknown}
DOCKER_GIT_DOCKER_RUNTIME: ${DOCKER_GIT_DOCKER_RUNTIME:-isolated}
DOCKER_HOST: ${DOCKER_GIT_CONTROLLER_DOCKER_HOST:-unix:///var/run/docker.sock}
DOCKER_GIT_DOCKERD_TCP_HOST: ${DOCKER_GIT_DOCKERD_TCP_HOST:-tcp://0.0.0.0:2375}
Expand Down
1 change: 1 addition & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ services:
dockerfile: packages/api/Dockerfile
args:
DOCKER_GIT_CONTROLLER_REV: ${DOCKER_GIT_CONTROLLER_REV:-unknown}
DOCKER_GIT_CONTROLLER_BUILD_SKILLER: ${DOCKER_GIT_CONTROLLER_BUILD_SKILLER:-1}
UBUNTU_APT_MIRROR: ${UBUNTU_APT_MIRROR:-}
container_name: ${DOCKER_GIT_API_CONTAINER_NAME:-docker-git-api}
environment:
Expand Down
114 changes: 84 additions & 30 deletions packages/api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
FROM ubuntu:26.04
FROM ubuntu:26.04 AS controller-base

ARG DOCKER_GIT_CONTROLLER_REV=unknown
ARG UBUNTU_APT_MIRROR=
LABEL io.prover-coder-ai.docker-git.controller-rev=$DOCKER_GIT_CONTROLLER_REV

ENV DEBIAN_FRONTEND=noninteractive
ENV DOCKER_GIT_CONTROLLER_REV=$DOCKER_GIT_CONTROLLER_REV
ENV BUN_INSTALL=/opt/bun
ENV PATH=/opt/bun/bin:$PATH
WORKDIR /workspace
Expand Down Expand Up @@ -55,16 +52,23 @@ RUN curl -fsSL https://deb.nodesource.com/setup_24.x | bash - \
&& npm i -g node-gyp \
&& rm -rf /var/lib/apt/lists/*

FROM controller-base AS workspace-deps

COPY package.json bun.lock bunfig.toml tsconfig.base.json tsconfig.json ./
COPY patches ./patches
COPY scripts ./scripts
COPY packages ./packages
COPY .gitmodules ./.gitmodules
COPY third_party ./third_party
RUN mkdir -p packages/api packages/app packages/docker-git-session-sync packages/lib
COPY packages/api/package.json ./packages/api/package.json
COPY packages/app/package.json ./packages/app/package.json
COPY packages/docker-git-session-sync/package.json ./packages/docker-git-session-sync/package.json
COPY packages/lib/package.json ./packages/lib/package.json

RUN set -eu; \
for attempt in 1 2 3 4 5; do \
if bun install --frozen-lockfile --silent; then \
if bun install \
--frozen-lockfile \
--silent \
--filter @effect-template/api \
--filter @effect-template/lib \
--filter @prover-coder-ai/docker-git-session-sync; then \
exit 0; \
fi; \
echo "bun install attempt ${attempt} failed; retrying..." >&2; \
Expand All @@ -73,28 +77,78 @@ RUN set -eu; \
done; \
echo "bun install failed after retries" >&2; \
exit 1

FROM workspace-deps AS workspace-static

COPY patches ./patches
COPY scripts ./scripts
COPY packages/docker-git-session-sync ./packages/docker-git-session-sync
COPY packages/lib ./packages/lib

RUN bun run --cwd packages/docker-git-session-sync build
RUN bun run --cwd packages/lib build
RUN bun run --cwd packages/api build
RUN bun scripts/skiller-apply-docker-git-patches.mjs
RUN test -f third_party/skiller-desktop-skills-manager/package.json \
&& cd third_party/skiller-desktop-skills-manager \
&& for attempt in 1 2 3 4 5; do \
if bun install --frozen-lockfile --silent; then \
break; \
fi; \
if [ "$attempt" = "5" ]; then \
echo "skiller bun install failed after retries" >&2; \
exit 1; \
fi; \
echo "skiller bun install attempt ${attempt} failed; retrying..." >&2; \
rm -rf /root/.bun/install/cache node_modules; \
sleep $((attempt * 2)); \
done \
&& bun run build \
&& touch out/.docker-git-browser-folder-picker.patch \
&& mkdir -p out/preload \
&& ln -sf index.mjs out/preload/index.js

FROM controller-base AS skiller-build

ARG DOCKER_GIT_CONTROLLER_BUILD_SKILLER=1

COPY patches ./patches
COPY scripts/skiller-apply-docker-git-patches.mjs ./scripts/skiller-apply-docker-git-patches.mjs
COPY .gitmodules ./.gitmodules
COPY third_party ./third_party

RUN if [ "$DOCKER_GIT_CONTROLLER_BUILD_SKILLER" = "1" ]; then \
bun scripts/skiller-apply-docker-git-patches.mjs; \
else \
echo "Skipping Skiller build for controller image."; \
fi
RUN if [ "$DOCKER_GIT_CONTROLLER_BUILD_SKILLER" = "1" ]; then \
test -f third_party/skiller-desktop-skills-manager/package.json \
&& cd third_party/skiller-desktop-skills-manager \
&& for attempt in 1 2 3 4 5; do \
if bun install --frozen-lockfile --silent; then \
break; \
fi; \
if [ "$attempt" = "5" ]; then \
echo "skiller bun install failed after retries" >&2; \
exit 1; \
fi; \
echo "skiller bun install attempt ${attempt} failed; retrying..." >&2; \
rm -rf /root/.bun/install/cache node_modules; \
sleep $((attempt * 2)); \
done \
&& bun run build \
&& touch out/.docker-git-browser-folder-picker.patch \
&& mkdir -p out/preload \
&& ln -sf index.mjs out/preload/index.js; \
else \
cd third_party/skiller-desktop-skills-manager \
&& mkdir -p node_modules/electron/dist out/main out/renderer out/preload \
&& printf '%s\n' '#!/usr/bin/env sh' 'echo "Skiller is not bundled in this controller image." >&2' 'exit 1' \
> node_modules/electron/dist/electron \
&& chmod +x node_modules/electron/dist/electron \
&& printf '%s\n' 'throw new Error("Skiller is not bundled in this controller image.")' > out/main/index.js \
&& printf '%s\n' '<!doctype html><title>Skiller unavailable</title>' > out/renderer/index.html \
&& printf '%s\n' 'export {}' > out/preload/index.mjs \
&& touch out/.docker-git-browser-folder-picker.patch \
&& ln -sf index.mjs out/preload/index.js; \
fi

FROM workspace-static AS controller-final

COPY .gitmodules ./.gitmodules
COPY --from=skiller-build /workspace/third_party/skiller-desktop-skills-manager ./third_party/skiller-desktop-skills-manager
COPY packages/api ./packages/api

RUN ./packages/api/node_modules/.bin/tsc -p packages/api/tsconfig.json

ARG DOCKER_GIT_CONTROLLER_REV=unknown
ARG DOCKER_GIT_CONTROLLER_BUILD_SKILLER=1
LABEL io.prover-coder-ai.docker-git.controller-rev=$DOCKER_GIT_CONTROLLER_REV
LABEL io.prover-coder-ai.docker-git.controller-build-skiller=$DOCKER_GIT_CONTROLLER_BUILD_SKILLER

ENV DOCKER_GIT_CONTROLLER_REV=$DOCKER_GIT_CONTROLLER_REV
ENV DOCKER_GIT_CONTROLLER_BUILD_SKILLER=$DOCKER_GIT_CONTROLLER_BUILD_SKILLER
ENV DOCKER_GIT_API_PORT=3334
ENV DOCKER_GIT_DOCKER_RUNTIME=isolated
ENV DOCKER_HOST=unix:///var/run/docker.sock
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/services/terminal-sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,11 +766,11 @@ const writePtyInput = (pty: PtyBridge | null, data: string): void => {
const shellQuote = (value: string): string => `'${value.replace(/'/gu, "'\\''")}'`

// CHANGE: Predicate for when tmux should forward right-click pane events.
// WHY: Mouse-aware apps and copy/view mode still need pane mouse events, while tmux menus must stay disabled.
// WHY: Mouse-aware apps receive pane events; copy/view mode keeps tmux handling unless mouse tracking is active.
// QUOTE(TZ): issue #340 right-click must not open the default tmux menu in browser terminals.
// REF: PR #342 tmux right-click handling.
// SOURCE: n/a
// FORMAT THEOREM: mouse-aware-or-copy-mode => predicate evaluates truthy in tmux.
// FORMAT THEOREM: mouse_any_flag or non-copy/view pane mode => predicate evaluates truthy in tmux.
// PURITY: CORE
// EFFECT: none
// INVARIANT: The predicate contains only tmux format language and no shell interpolation.
Expand Down
2 changes: 2 additions & 0 deletions packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
"lint:effect": "NODE_OPTIONS=--max-old-space-size=4096 PATH=../../scripts:$PATH eslint --config eslint.effect-ts-check.config.mjs .",
"prebuild:docker-git": "bun install --cwd ../.. && bun run --cwd ../docker-git-session-sync build && bun run --cwd ../lib build",
"build:docker-git": "vite build --config vite.docker-git.config.ts",
"prebuild:docker-git:reuse-install": "bun run --cwd ../docker-git-session-sync build && bun run --cwd ../lib build",
"build:docker-git:reuse-install": "vite build --config vite.docker-git.config.ts",
"check": "bun run typecheck",
"clone": "bun run build:docker-git && bun dist/src/docker-git/main.js clone",
"open": "bun run build:docker-git && bun dist/src/docker-git/main.js open",
Expand Down
47 changes: 46 additions & 1 deletion packages/app/src/docker-git/browser-frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
resolveBrowserFrontendStatePath,
shouldReuseBrowserFrontend
} from "./browser-frontend-state.js"
import { findReachableApiBaseUrl } from "./controller-health.js"
import { resolveConfiguredApiBaseUrl, resolveExplicitApiBaseUrl } from "./controller-reachability.js"
import { type ControllerRuntime, ensureControllerReady, resolveApiBaseUrl } from "./controller.js"
import {
runCommandCapture,
Expand Down Expand Up @@ -146,6 +148,49 @@ const readBrowserFrontendRuntimeState = (
webState: readBrowserFrontendState(statePath)
})

// CHANGE: prefer the host-facing controller URL for the browser web proxy.
// WHY: controller bootstrap may select a Docker bridge IP before the published localhost port is reachable, but the served browser runtime must keep durable state and proxy config on the externally reachable endpoint.
// QUOTE(ТЗ): "комментарии ребита надо было тоже поддержать"
// REF: PR #344 E2E (Browser command) regression.
// SOURCE: n/a
// FORMAT THEOREM: explicit_api -> explicit_api; reachable(configured_api) -> configured_api; otherwise -> selected_api
// PURITY: SHELL
// EFFECT: Effect<string, never, ControllerRuntime>
// INVARIANT: explicit DOCKER_GIT_API_URL is never overridden by auto-discovery.
// COMPLEXITY: O(1) probes/O(1) space.
/**
* Resolves the API URL used by the browser frontend proxy.
*
* @returns Effect with the explicit API URL, the reachable configured host URL, or the selected controller URL.
*
* @pure false
* @effect FetchHttpClient through controller health probing.
* @invariant Explicit `DOCKER_GIT_API_URL` has precedence over all inferred endpoints.
* @precondition `ensureControllerReady` has already completed for inferred endpoints.
* @postcondition A configured host URL is used only after a successful health probe.
* @complexity O(1) time and O(1) space for the bounded candidate set.
* @throws Never - health probe failures fall back to the selected controller URL.
*/
const resolveBrowserFrontendApiBaseUrl = (): Effect.Effect<string, never, ControllerRuntime> => {
const selectedApiBaseUrl = resolveApiBaseUrl()
const explicitApiBaseUrl = resolveExplicitApiBaseUrl()
if (explicitApiBaseUrl !== undefined) {
return Effect.succeed(explicitApiBaseUrl)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

const configuredApiBaseUrl = resolveConfiguredApiBaseUrl()
if (configuredApiBaseUrl === selectedApiBaseUrl) {
return Effect.succeed(selectedApiBaseUrl)
}

return findReachableApiBaseUrl([configuredApiBaseUrl]).pipe(
Effect.match({
onFailure: () => selectedApiBaseUrl,
onSuccess: (apiBaseUrl) => apiBaseUrl
})
)
}

const stopCurrentWebServer = (): Effect.Effect<
void,
ControllerBootstrapError | PlatformError,
Expand Down Expand Up @@ -173,7 +218,7 @@ const prepareBrowserStack = (): Effect.Effect<
yield* _(Effect.log("Ensuring docker-git API controller is current."))
yield* _(ensureControllerReady())

const apiBaseUrl = resolveApiBaseUrl()
const apiBaseUrl = yield* _(resolveBrowserFrontendApiBaseUrl())
const runtimeState = yield* _(readBrowserFrontendRuntimeState(statePath))
const reuseInput: BrowserFrontendReuseInput = {
apiBaseUrl,
Expand Down
Loading
Loading