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
12 changes: 11 additions & 1 deletion images/chromium-headful/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ RUN --mount=type=cache,target=/root/.cache/go-build,id=$CACHEIDPREFIX-go-build \
GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
go build -ldflags="-s -w" -o /out/chromium-launcher ./cmd/chromium-launcher

# Build PAC transparent proxy
RUN --mount=type=cache,target=/root/.cache/go-build,id=$CACHEIDPREFIX-go-build \
--mount=type=cache,target=/go/pkg/mod,id=$CACHEIDPREFIX-go-pkg-mod \
GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
go build -ldflags="-s -w" -o /out/pac-proxy ./cmd/pacproxy

# webrtc client
FROM node:22-bullseye-slim AS client
WORKDIR /src
Expand Down Expand Up @@ -194,6 +200,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
# Network tools
net-tools \
netcat \
iptables \
# PPA req
software-properties-common && \
# Userland apps
Expand Down Expand Up @@ -225,6 +232,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked,id=$CACHEIDPREFIX-ap
apt-get --no-install-recommends -y install \
wget ca-certificates python2 supervisor xclip xdotool unclutter \
pulseaudio dbus-x11 xserver-xorg-video-dummy \
libpacparser1 \
libcairo2 libxcb1 libxrandr2 libxv1 libopus0 libvpx7 \
x11-xserver-utils \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
Expand Down Expand Up @@ -312,6 +320,7 @@ RUN chmod +x /usr/local/bin/init-envoy.sh
# copy the kernel-images API binary built in the builder stage
COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api
COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher
COPY --from=server-builder /out/pac-proxy /usr/local/bin/pac-proxy

# Copy and compile the Playwright daemon
COPY server/runtime/playwright-daemon.ts /tmp/playwright-daemon.ts
Expand All @@ -326,6 +335,7 @@ RUN esbuild /tmp/playwright-daemon.ts \
--external:esbuild \
&& rm /tmp/playwright-daemon.ts

RUN useradd -m -s /bin/bash kernel
RUN useradd -m -s /usr/sbin/nologin pacproxy && \
useradd -m -s /bin/bash kernel

ENTRYPOINT [ "/wrapper.sh" ]
8 changes: 8 additions & 0 deletions images/chromium-headful/supervisor/services/pac-proxy.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[program:pac-proxy]
command=/usr/local/bin/pac-proxy --listen 127.0.0.1:15080 --pac /chromium/proxy.pac --pactester /usr/bin/pactester
user=pacproxy
autostart=false
autorestart=true
startsecs=1
stdout_logfile=/var/log/supervisord/pac-proxy
redirect_stderr=true
35 changes: 35 additions & 0 deletions images/chromium-headful/wrapper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,27 @@ start_dynamic_log_aggregator

export DISPLAY=:1

# Optionally pre-trust a custom CA in Chromium's NSS DB before Chromium starts.
# This is useful for HTTPS MITM demos without --ignore-certificate-errors.
import_chromium_nss_ca() {
local cert_path="${PAC_MITM_CA_CERT_PATH:-}"
local cert_name="${PAC_MITM_CA_CERT_NICKNAME:-pac-mitm}"
if [[ -z "$cert_path" || ! -f "$cert_path" ]]; then
return 0
fi

echo "[wrapper] Importing custom Chromium NSS CA from $cert_path"
runuser -u kernel -- sh -lc "
set -e
mkdir -p \"\$HOME/.pki/nssdb\"
if [ ! -f \"\$HOME/.pki/nssdb/cert9.db\" ]; then
certutil -N -d sql:\"\$HOME/.pki/nssdb\" --empty-password
fi
certutil -D -d sql:\"\$HOME/.pki/nssdb\" -n '$cert_name' >/dev/null 2>&1 || true
certutil -A -d sql:\"\$HOME/.pki/nssdb\" -n '$cert_name' -t 'C,,' -i '$cert_path'
"
}

# Predefine ports and export for services
export INTERNAL_PORT="${INTERNAL_PORT:-9223}"
export CHROME_PORT="${CHROME_PORT:-9222}"
Expand All @@ -144,6 +165,7 @@ cleanup () {
# Re-enable scale-to-zero if the script terminates early
enable_scale_to_zero
supervisorctl -c /etc/supervisor/supervisord.conf stop chromium || true
supervisorctl -c /etc/supervisor/supervisord.conf stop pac-proxy || true
supervisorctl -c /etc/supervisor/supervisord.conf stop kernel-images-api || true
supervisorctl -c /etc/supervisor/supervisord.conf stop dbus || true
# Stop log tailers
Expand Down Expand Up @@ -207,6 +229,19 @@ done
# autolaunch attempts that failed and spammed logs.
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/dbus/system_bus_socket"

echo "[wrapper] Starting PAC transparent proxy via supervisord"
supervisorctl -c /etc/supervisor/supervisord.conf start pac-proxy
echo "[wrapper] Waiting for PAC transparent proxy on 127.0.0.1:15080..."
for i in {1..50}; do
if nc -z 127.0.0.1 15080 2>/dev/null; then
break
fi
sleep 0.2
done

# Optional CA trust import happens before Chromium launch.
import_chromium_nss_ca

# Start Chromium with display :1 and remote debugging, loading our recorder extension.
echo "[wrapper] Starting Chromium via supervisord on internal port $INTERNAL_PORT"
supervisorctl -c /etc/supervisor/supervisord.conf start chromium
Expand Down
42 changes: 33 additions & 9 deletions server/cmd/api/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -53,6 +55,17 @@ type ApiService struct {

// policy management
policy *policy.Policy

// chromiumConfigMu serializes mutable Chromium runtime config under /chromium.
chromiumConfigMu sync.Mutex
// Runtime Chromium flags overlay path.
chromiumFlagsPath string
// PAC script path managed by /chromium/proxy/pac endpoints.
chromiumPACPath string
// PAC script URL served by this API and exposed in PAC status responses.
chromiumPACURL string
// PAC OS-level apply state path used for GET status reporting.
chromiumPACStatePath string
}

var _ oapi.StrictServerInterface = (*ApiService)(nil)
Expand All @@ -69,16 +82,27 @@ func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFa
return nil, fmt.Errorf("nekoAuthClient cannot be nil")
}

apiPort := 10001
if rawPort := strings.TrimSpace(os.Getenv("PORT")); rawPort != "" {
if parsed, err := strconv.Atoi(rawPort); err == nil && parsed > 0 {
apiPort = parsed
}
}

return &ApiService{
recordManager: recordManager,
factory: factory,
defaultRecorderID: "default",
watches: make(map[string]*fsWatch),
procs: make(map[string]*processHandle),
upstreamMgr: upstreamMgr,
stz: stz,
nekoAuthClient: nekoAuthClient,
policy: &policy.Policy{},
recordManager: recordManager,
factory: factory,
defaultRecorderID: "default",
watches: make(map[string]*fsWatch),
procs: make(map[string]*processHandle),
upstreamMgr: upstreamMgr,
stz: stz,
nekoAuthClient: nekoAuthClient,
policy: &policy.Policy{},
chromiumFlagsPath: "/chromium/flags",
chromiumPACPath: "/chromium/proxy.pac",
chromiumPACURL: fmt.Sprintf("http://127.0.0.1:%d/chromium/proxy/pac/script", apiPort),
chromiumPACStatePath: "/chromium/pac-state.json",
}, nil
}

Expand Down
10 changes: 5 additions & 5 deletions server/cmd/api/api/chromium.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,11 @@ func (s *ApiService) UploadExtensionsAndRestart(ctx context.Context, request oap
// and writes the result back to /chromium/flags. Returns the merged tokens or an error.
func (s *ApiService) mergeAndWriteChromiumFlags(ctx context.Context, newTokens []string) ([]string, error) {
log := logger.FromContext(ctx)

const flagsPath = "/chromium/flags"
s.chromiumConfigMu.Lock()
defer s.chromiumConfigMu.Unlock()

// Read existing runtime flags from /chromium/flags (if any)
existingTokens, err := chromiumflags.ReadOptionalFlagFile(flagsPath)
existingTokens, err := chromiumflags.ReadOptionalFlagFile(s.chromiumFlagsPath)
if err != nil {
log.Error("failed to read existing flags", "error", err)
return nil, fmt.Errorf("failed to read existing flags: %w", err)
Expand All @@ -310,13 +310,13 @@ func (s *ApiService) mergeAndWriteChromiumFlags(ctx context.Context, newTokens [
mergedTokens := chromiumflags.MergeFlags(existingTokens, newTokens)

// Ensure the chromium directory exists
if err := os.MkdirAll("/chromium", 0o755); err != nil {
if err := os.MkdirAll(filepath.Dir(s.chromiumFlagsPath), 0o755); err != nil {
log.Error("failed to create chromium dir", "error", err)
return nil, fmt.Errorf("failed to create chromium dir: %w", err)
}

// Write flags file with merged flags
if err := chromiumflags.WriteFlagFile(flagsPath, mergedTokens); err != nil {
if err := chromiumflags.WriteFlagFile(s.chromiumFlagsPath, mergedTokens); err != nil {
log.Error("failed to write flags", "error", err)
return nil, fmt.Errorf("failed to write flags: %w", err)
}
Expand Down
Loading
Loading