Skip to content

Commit 716fc8e

Browse files
authored
api endpoint to upload custom extensions and restart (#71)
* add api endpoint to upload custom extensions and restart - adjusts flag logic to merge flags and treat /chromium/flags as a runtime overlay * update tests * add some debug logs * more debug * more logs * simplify * . * .. * begin cleanup * fix bug * simplify * rip start-chromium bash script, long live a real program * . * . * tweaks * . * disable stz * fix test * rm redundant code * adjust flags to be json * bot comments * suddenly apt can't find `chromium`, add some debugging * more debug * chromium is so back * fix tests * bot comments
1 parent 2d73dc8 commit 716fc8e

File tree

21 files changed

+1570
-196
lines changed

21 files changed

+1570
-196
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Test chromium-launcher
2+
3+
on:
4+
push:
5+
branches: ["main"]
6+
pull_request:
7+
branches: ["main"]
8+
workflow_dispatch:
9+
10+
jobs:
11+
test:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4
19+
20+
- name: Set up Go
21+
uses: actions/setup-go@v5
22+
with:
23+
go-version-file: "server/go.mod"
24+
cache: true
25+
26+
- name: Run chromium-launcher unit tests
27+
run: go test ./cmd/chromium-launcher -v
28+
working-directory: server

images/chromium-headful/Dockerfile

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@ COPY server/go.sum ./
1010
RUN go mod download
1111

1212
COPY server/ .
13+
14+
# Build kernel-images API
1315
RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
1416
go build -ldflags="-s -w" -o /out/kernel-images-api ./cmd/api
1517

18+
# Build chromium launcher
19+
RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
20+
go build -ldflags="-s -w" -o /out/chromium-launcher ./cmd/chromium-launcher
21+
1622
# webrtc client
1723
FROM node:22-bullseye-slim AS client
1824
WORKDIR /src
@@ -89,7 +95,6 @@ RUN apt-get update && \
8995
# Userland apps
9096
sudo add-apt-repository ppa:mozillateam/ppa && \
9197
sudo apt-get install -y --no-install-recommends \
92-
chromium-browser \
9398
libreoffice \
9499
x11-apps \
95100
xpdf \
@@ -169,14 +174,13 @@ COPY --from=xorg-deps /usr/local/lib/xorg/modules/drivers/dummy_drv.so /usr/lib/
169174
COPY --from=xorg-deps /usr/local/lib/xorg/modules/input/neko_drv.so /usr/lib/xorg/modules/input/neko_drv.so
170175

171176
COPY images/chromium-headful/image-chromium/ /
172-
COPY images/chromium-headful/start-chromium.sh /images/chromium-headful/start-chromium.sh
173-
RUN chmod +x /images/chromium-headful/start-chromium.sh
174177
COPY images/chromium-headful/wrapper.sh /wrapper.sh
175178
COPY images/chromium-headful/supervisord.conf /etc/supervisor/supervisord.conf
176179
COPY images/chromium-headful/supervisor/services/ /etc/supervisor/conf.d/services/
177180

178181
# copy the kernel-images API binary built in the builder stage
179182
COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api
183+
COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher
180184

181185
RUN useradd -m -s /bin/bash kernel
182186

images/chromium-headful/start-chromium.sh

Lines changed: 0 additions & 48 deletions
This file was deleted.

images/chromium-headful/supervisor/services/chromium.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[program:chromium]
2-
command=/bin/bash -lc '/images/chromium-headful/start-chromium.sh'
2+
command=/usr/local/bin/chromium-launcher
33
autostart=false
44
autorestart=true
55
startsecs=5

images/chromium-headless/image/Dockerfile

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,16 @@ COPY server/go.mod ./
1111
COPY server/go.sum ./
1212
RUN go mod download
1313

14-
# Copy the rest of the server source and build the binary
1514
COPY server/ .
15+
16+
# Build kernel-images API
1617
RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
1718
go build -ldflags="-s -w" -o /out/kernel-images-api ./cmd/api
1819

20+
# Build chromium launcher
21+
RUN GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
22+
go build -ldflags="-s -w" -o /out/chromium-launcher ./cmd/chromium-launcher
23+
1924
FROM docker.io/ubuntu:22.04
2025

2126
RUN set -xe; \
@@ -72,12 +77,11 @@ ENV WITHDOCKER=true
7277
# Create a non-root user with a home directory
7378
RUN useradd -m -s /bin/bash kernel
7479

75-
# Xvfb helper and supervisor-managed start scripts
76-
COPY images/chromium-headless/image/start-chromium.sh /images/chromium-headless/image/start-chromium.sh
80+
# supervisor start scripts
7781
COPY images/chromium-headless/image/start-xvfb.sh /images/chromium-headless/image/start-xvfb.sh
78-
RUN chmod +x /images/chromium-headless/image/start-chromium.sh /images/chromium-headless/image/start-xvfb.sh
82+
RUN chmod +x /images/chromium-headless/image/start-xvfb.sh
7983

80-
# Wrapper script set environment
84+
# Wrapper script to set environment
8185
COPY images/chromium-headless/image/wrapper.sh /usr/bin/wrapper.sh
8286

8387
# Supervisord configuration
@@ -86,5 +90,6 @@ COPY images/chromium-headless/image/supervisor/services/ /etc/supervisor/conf.d/
8690

8791
# Copy the kernel-images API binary built in the builder stage
8892
COPY --from=server-builder /out/kernel-images-api /usr/local/bin/kernel-images-api
93+
COPY --from=server-builder /out/chromium-launcher /usr/local/bin/chromium-launcher
8994

9095
ENTRYPOINT [ "/usr/bin/wrapper.sh" ]

images/chromium-headless/image/start-chromium.sh

Lines changed: 0 additions & 48 deletions
This file was deleted.

images/chromium-headless/image/supervisor/services/chromium.conf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[program:chromium]
2-
command=/bin/bash -lc '/images/chromium-headless/image/start-chromium.sh'
2+
command=/usr/local/bin/chromium-launcher --headless
33
autostart=false
44
autorestart=true
55
startsecs=5

server/cmd/api/api/api.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ import (
88
"sync"
99
"time"
1010

11+
"github.com/onkernel/kernel-images/server/lib/devtoolsproxy"
1112
"github.com/onkernel/kernel-images/server/lib/logger"
1213
oapi "github.com/onkernel/kernel-images/server/lib/oapi"
1314
"github.com/onkernel/kernel-images/server/lib/recorder"
15+
"github.com/onkernel/kernel-images/server/lib/scaletozero"
1416
)
1517

1618
type ApiService struct {
@@ -26,16 +28,23 @@ type ApiService struct {
2628
// Process management
2729
procMu sync.RWMutex
2830
procs map[string]*processHandle
31+
32+
// DevTools upstream manager (Chromium supervisord log tailer)
33+
upstreamMgr *devtoolsproxy.UpstreamManager
34+
35+
stz scaletozero.Controller
2936
}
3037

3138
var _ oapi.StrictServerInterface = (*ApiService)(nil)
3239

33-
func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFactory) (*ApiService, error) {
40+
func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFactory, upstreamMgr *devtoolsproxy.UpstreamManager, stz scaletozero.Controller) (*ApiService, error) {
3441
switch {
3542
case recordManager == nil:
3643
return nil, fmt.Errorf("recordManager cannot be nil")
3744
case factory == nil:
3845
return nil, fmt.Errorf("factory cannot be nil")
46+
case upstreamMgr == nil:
47+
return nil, fmt.Errorf("upstreamMgr cannot be nil")
3948
}
4049

4150
return &ApiService{
@@ -44,6 +53,8 @@ func New(recordManager recorder.RecordManager, factory recorder.FFmpegRecorderFa
4453
defaultRecorderID: "default",
4554
watches: make(map[string]*fsWatch),
4655
procs: make(map[string]*processHandle),
56+
upstreamMgr: upstreamMgr,
57+
stz: stz,
4758
}, nil
4859
}
4960

server/cmd/api/api/api_test.go

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,12 @@ import (
99
"os"
1010
"testing"
1111

12+
"log/slog"
13+
14+
"github.com/onkernel/kernel-images/server/lib/devtoolsproxy"
1215
oapi "github.com/onkernel/kernel-images/server/lib/oapi"
1316
"github.com/onkernel/kernel-images/server/lib/recorder"
17+
"github.com/onkernel/kernel-images/server/lib/scaletozero"
1418
"github.com/stretchr/testify/assert"
1519
"github.com/stretchr/testify/require"
1620
)
@@ -20,7 +24,7 @@ func TestApiService_StartRecording(t *testing.T) {
2024

2125
t.Run("success", func(t *testing.T) {
2226
mgr := recorder.NewFFmpegManager()
23-
svc, err := New(mgr, newMockFactory())
27+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
2428
require.NoError(t, err)
2529

2630
resp, err := svc.StartRecording(ctx, oapi.StartRecordingRequestObject{})
@@ -34,7 +38,7 @@ func TestApiService_StartRecording(t *testing.T) {
3438

3539
t.Run("already recording", func(t *testing.T) {
3640
mgr := recorder.NewFFmpegManager()
37-
svc, err := New(mgr, newMockFactory())
41+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
3842
require.NoError(t, err)
3943

4044
// First start should succeed
@@ -49,7 +53,7 @@ func TestApiService_StartRecording(t *testing.T) {
4953

5054
t.Run("custom ids don't collide", func(t *testing.T) {
5155
mgr := recorder.NewFFmpegManager()
52-
svc, err := New(mgr, newMockFactory())
56+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
5357
require.NoError(t, err)
5458

5559
for i := 0; i < 5; i++ {
@@ -82,7 +86,7 @@ func TestApiService_StopRecording(t *testing.T) {
8286

8387
t.Run("no active recording", func(t *testing.T) {
8488
mgr := recorder.NewFFmpegManager()
85-
svc, err := New(mgr, newMockFactory())
89+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
8690
require.NoError(t, err)
8791

8892
resp, err := svc.StopRecording(ctx, oapi.StopRecordingRequestObject{})
@@ -95,7 +99,7 @@ func TestApiService_StopRecording(t *testing.T) {
9599
rec := &mockRecorder{id: "default", isRecordingFlag: true}
96100
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
97101

98-
svc, err := New(mgr, newMockFactory())
102+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
99103
require.NoError(t, err)
100104
resp, err := svc.StopRecording(ctx, oapi.StopRecordingRequestObject{})
101105
require.NoError(t, err)
@@ -110,7 +114,7 @@ func TestApiService_StopRecording(t *testing.T) {
110114

111115
force := true
112116
req := oapi.StopRecordingRequestObject{Body: &oapi.StopRecordingJSONRequestBody{ForceStop: &force}}
113-
svc, err := New(mgr, newMockFactory())
117+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
114118
require.NoError(t, err)
115119
resp, err := svc.StopRecording(ctx, req)
116120
require.NoError(t, err)
@@ -124,7 +128,7 @@ func TestApiService_DownloadRecording(t *testing.T) {
124128

125129
t.Run("not found", func(t *testing.T) {
126130
mgr := recorder.NewFFmpegManager()
127-
svc, err := New(mgr, newMockFactory())
131+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
128132
require.NoError(t, err)
129133
resp, err := svc.DownloadRecording(ctx, oapi.DownloadRecordingRequestObject{})
130134
require.NoError(t, err)
@@ -144,7 +148,7 @@ func TestApiService_DownloadRecording(t *testing.T) {
144148
rec := &mockRecorder{id: "default", isRecordingFlag: true, recordingData: randomBytes(minRecordingSizeInBytes - 1)}
145149
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
146150

147-
svc, err := New(mgr, newMockFactory())
151+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
148152
require.NoError(t, err)
149153
// will return a 202 when the recording is too small
150154
resp, err := svc.DownloadRecording(ctx, oapi.DownloadRecordingRequestObject{})
@@ -174,7 +178,7 @@ func TestApiService_DownloadRecording(t *testing.T) {
174178
rec := &mockRecorder{id: "default", recordingData: data}
175179
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
176180

177-
svc, err := New(mgr, newMockFactory())
181+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
178182
require.NoError(t, err)
179183
resp, err := svc.DownloadRecording(ctx, oapi.DownloadRecordingRequestObject{})
180184
require.NoError(t, err)
@@ -194,7 +198,7 @@ func TestApiService_Shutdown(t *testing.T) {
194198
rec := &mockRecorder{id: "default", isRecordingFlag: true}
195199
require.NoError(t, mgr.RegisterRecorder(ctx, rec), "failed to register recorder")
196200

197-
svc, err := New(mgr, newMockFactory())
201+
svc, err := New(mgr, newMockFactory(), newTestUpstreamManager(), scaletozero.NewNoopController())
198202
require.NoError(t, err)
199203

200204
require.NoError(t, svc.Shutdown(ctx))
@@ -284,3 +288,8 @@ func newMockFactory() recorder.FFmpegRecorderFactory {
284288
return rec, nil
285289
}
286290
}
291+
292+
func newTestUpstreamManager() *devtoolsproxy.UpstreamManager {
293+
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
294+
return devtoolsproxy.NewUpstreamManager("", logger)
295+
}

0 commit comments

Comments
 (0)