Skip to content

Commit 37bca1f

Browse files
committed
upd
1 parent 1c5a3fb commit 37bca1f

20 files changed

Lines changed: 1683 additions & 169 deletions

.dockerignore

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
.git
22
.github
3+
.agents
4+
.codex
5+
.idea
6+
.mimocode
7+
.novnc-*
38
.deepsource.toml
49
.editorconfig
10+
.env
511
.gitignore
612
.pre-commit-config.yaml
13+
.pytest_cache
14+
.ruff_cache
15+
.uv-cache*
16+
.docker-config*
17+
.venv
18+
__pycache__
19+
node_modules
20+
*.py[cod]
21+
*.log
22+
build
23+
dist
24+
docker-compose.yml
725
requirements.txt
8-
renovate.json
26+
renovate.json
27+
AGENTS.md
28+
test.py
29+
test2.py
30+
tests
31+
test_*.html
32+
test_*.png
33+
test_commands.txt

.github/workflows/headed-smoke.yml

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
name: Headed and Desktop Docker Smoke Test
2+
3+
permissions:
4+
contents: read
5+
6+
on:
7+
pull_request:
8+
paths:
9+
- "Dockerfile"
10+
- "docker-compose.yml"
11+
- ".dockerignore"
12+
- "docker/**"
13+
- "config.json"
14+
- "pyproject.toml"
15+
- "uv.lock"
16+
- "akari_bot_webrender/**"
17+
- ".github/workflows/headed-smoke.yml"
18+
push:
19+
branches:
20+
- master
21+
- main
22+
paths:
23+
- "Dockerfile"
24+
- "docker-compose.yml"
25+
- ".dockerignore"
26+
- "docker/**"
27+
- "config.json"
28+
- "pyproject.toml"
29+
- "uv.lock"
30+
- "akari_bot_webrender/**"
31+
- ".github/workflows/headed-smoke.yml"
32+
workflow_dispatch:
33+
34+
jobs:
35+
headed-smoke:
36+
runs-on: ubuntu-latest
37+
timeout-minutes: 20
38+
39+
steps:
40+
- name: Checkout repository
41+
uses: actions/checkout@v7
42+
43+
- name: Validate Docker Compose example
44+
run: |
45+
docker compose --profile headless config --quiet
46+
docker compose --profile headed config --quiet
47+
docker compose --profile desktop config --quiet
48+
49+
- name: Build headed image
50+
run: docker build --target headed --tag akari-bot-webrender:headed-smoke .
51+
52+
- name: Start headed container and render a page
53+
shell: bash
54+
run: |
55+
set -Eeuo pipefail
56+
57+
container_name="webrender-headed-smoke"
58+
response_file="${RUNNER_TEMP}/webrender-page-response.json"
59+
screenshot_file="${RUNNER_TEMP}/webrender-page.png"
60+
61+
cleanup() {
62+
exit_status=$?
63+
trap - EXIT
64+
if (( exit_status != 0 )); then
65+
docker logs "${container_name}" || true
66+
fi
67+
docker rm --force "${container_name}" >/dev/null 2>&1 || true
68+
exit "${exit_status}"
69+
}
70+
trap cleanup EXIT
71+
72+
docker run --detach \
73+
--name "${container_name}" \
74+
--shm-size=1g \
75+
--env WEBRENDER_HOST=0.0.0.0 \
76+
--publish 127.0.0.1:15551:15551 \
77+
akari-bot-webrender:headed-smoke
78+
79+
ready=0
80+
for attempt in $(seq 1 60); do
81+
if status_json=$(curl --fail --silent --show-error http://127.0.0.1:15551/status/ 2>/dev/null) \
82+
&& jq --exit-status \
83+
'.browser_initialized == true and .headless == false and .browser_mode == "headed"' \
84+
<<<"${status_json}" >/dev/null; then
85+
ready=1
86+
break
87+
fi
88+
sleep 2
89+
done
90+
91+
if (( ready != 1 )); then
92+
echo "WebRender did not become ready within 120 seconds" >&2
93+
exit 1
94+
fi
95+
96+
curl --fail-with-body --silent --show-error \
97+
--header 'Content-Type: application/json' \
98+
--data-binary '{"content":"<!doctype html><html><body><h1>Headed smoke test</h1><p>Chromium rendered this local content.</p></body></html>","output_type":"png","width":480,"height":320,"counttime":false}' \
99+
http://127.0.0.1:15551/page/ \
100+
> "${response_file}"
101+
102+
jq --exit-status \
103+
'type == "array" and length > 0 and all(.[]; type == "string" and length > 0)' \
104+
"${response_file}" >/dev/null
105+
106+
jq --raw-output '.[0]' "${response_file}" | base64 --decode > "${screenshot_file}"
107+
test -s "${screenshot_file}"
108+
109+
png_signature=$(od -An -tx1 -N8 "${screenshot_file}" | tr -d '[:space:]')
110+
test "${png_signature}" = "89504e470d0a1a0a"
111+
112+
- name: Build and smoke test desktop image
113+
shell: bash
114+
run: |
115+
set -Eeuo pipefail
116+
117+
image_name="akari-bot-webrender:desktop-smoke"
118+
container_name="webrender-desktop-smoke"
119+
120+
cleanup() {
121+
exit_status=$?
122+
trap - EXIT
123+
if (( exit_status != 0 )); then
124+
docker logs "${container_name}" || true
125+
fi
126+
docker rm --force "${container_name}" >/dev/null 2>&1 || true
127+
exit "${exit_status}"
128+
}
129+
trap cleanup EXIT
130+
131+
docker build --target desktop --tag "${image_name}" .
132+
docker run --detach \
133+
--name "${container_name}" \
134+
--shm-size=1g \
135+
--env ENABLE_NOVNC=1 \
136+
--env NOVNC_LISTEN=0.0.0.0 \
137+
--env NOVNC_PASSWORD=ci-only-password \
138+
--publish 127.0.0.1:15551:15551 \
139+
--publish 127.0.0.1:6080:6080 \
140+
"${image_name}"
141+
142+
ready=0
143+
for attempt in $(seq 1 60); do
144+
if status_json=$(curl --fail --silent --show-error http://127.0.0.1:15551/status/ 2>/dev/null) \
145+
&& jq --exit-status \
146+
'.browser_initialized == true and .headless == false and .browser_mode == "headed"' \
147+
<<<"${status_json}" >/dev/null \
148+
&& curl --fail --silent --show-error http://127.0.0.1:6080/vnc.html >/dev/null; then
149+
ready=1
150+
break
151+
fi
152+
sleep 2
153+
done
154+
155+
if (( ready != 1 )); then
156+
echo "Desktop WebRender did not become ready within 120 seconds" >&2
157+
exit 1
158+
fi
159+
160+
docker exec "${container_name}" pgrep --exact xfce4-session >/dev/null
161+
docker exec "${container_name}" pgrep --exact x11vnc >/dev/null
162+
docker exec "${container_name}" pgrep --full websockify >/dev/null

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
.venv
2+
.env
23
.idea
34

45
__pycache__
56

67
test*
8+
!tests/
9+
!tests/test_*.py
710
./dist
811
*.log
9-
node_modules/*
12+
node_modules/*
13+
14+
AGENTS.md

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ repos:
33
rev: 0.10.2
44
hooks:
55
- id: uv-export
6-
args: ["--no-hashes", "--output-file=requirements.txt"]
6+
args: ["--no-dev", "--no-hashes", "--output-file=requirements.txt"]
77

88
- repo: https://github.com/astral-sh/ruff-pre-commit
99
rev: v0.15.0

Dockerfile

Lines changed: 130 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.12
1+
FROM python:3.12-slim-bookworm AS app-base
22

33
LABEL org.opencontainers.image.url=https://github.com/Teahouse-Studios/akari-bot-webrender
44
LABEL org.opencontainers.image.documentation=https://bot.teahouse.team/
@@ -8,38 +8,139 @@ LABEL org.opencontainers.image.licenses=MIT
88
LABEL org.opencontainers.image.title="AkariBot WebRender"
99
LABEL maintainer="Teahouse Studios <admin@teahou.se>"
1010

11-
RUN pip install uv
11+
ARG WEBRENDER_UID=10001
12+
ARG WEBRENDER_GID=10001
13+
ARG UV_VERSION=0.10.3
14+
15+
ENV DEBIAN_FRONTEND=noninteractive \
16+
HOME=/home/webrender \
17+
PATH=/akari-bot-webrender/.venv/bin:${PATH} \
18+
PLAYWRIGHT_BROWSERS_PATH=/ms-playwright \
19+
PYTHONDONTWRITEBYTECODE=1 \
20+
PYTHONUNBUFFERED=1 \
21+
UV_COMPILE_BYTECODE=1 \
22+
UV_LINK_MODE=copy \
23+
UV_NO_CACHE=1 \
24+
WEBRENDER_HOST=0.0.0.0
25+
26+
RUN apt-get update \
27+
&& apt-get install -y --no-install-recommends \
28+
ca-certificates \
29+
fonts-dejavu \
30+
fonts-noto-cjk \
31+
fonts-noto-color-emoji \
32+
tini \
33+
&& rm -rf /var/lib/apt/lists/* \
34+
&& groupadd --gid "${WEBRENDER_GID}" webrender \
35+
&& useradd --uid "${WEBRENDER_UID}" --gid "${WEBRENDER_GID}" --create-home --shell /bin/sh webrender \
36+
&& install -d -o webrender -g webrender /akari-bot-webrender \
37+
&& install -d -o root -g root -m 0755 /ms-playwright
38+
39+
RUN pip install --no-cache-dir "uv==${UV_VERSION}"
1240

1341
WORKDIR /akari-bot-webrender
1442

15-
ENV PYTHONDONTWRITEBYTECODE=1
16-
ENV PYTHONUNBUFFERED=1
17-
18-
RUN apt-get update && apt-get install -y \
19-
fonts-noto-cjk \
20-
fonts-noto-color-emoji \
21-
fonts-dejavu \
22-
curl \
23-
wget \
24-
gnupg \
25-
ca-certificates \
26-
libnss3 \
27-
libatk1.0-0 \
28-
libatk-bridge2.0-0 \
29-
libcups2 \
30-
libxkbcommon0 \
31-
libxcomposite1 \
32-
libxdamage1 \
33-
libxrandr2 \
34-
libgbm1 \
35-
libpango-1.0-0 \
36-
libpangocairo-1.0-0 \
37-
libasound2 \
43+
COPY --chown=webrender:webrender pyproject.toml uv.lock README.md ./
44+
RUN uv sync --frozen --no-dev --no-install-project \
45+
&& playwright install-deps chromium \
3846
&& rm -rf /var/lib/apt/lists/*
3947

40-
COPY pyproject.toml uv.lock README.md ./
41-
RUN uv sync --frozen
42-
RUN uv run playwright install --with-deps chromium
48+
EXPOSE 15551
49+
50+
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
51+
CMD ["python", "-c", "import json, os, urllib.request; response = urllib.request.urlopen('http://127.0.0.1:' + os.getenv('WEBRENDER_PORT', '15551') + '/status/', timeout=3); assert json.load(response).get('browser_initialized') is True"]
52+
53+
# The normal image remains headless. Build it explicitly with --target headless,
54+
# or omit --target (the final default stage below is an alias of this stage).
55+
FROM app-base AS headless
56+
57+
RUN playwright install --only-shell chromium \
58+
&& chmod -R a+rX /ms-playwright \
59+
&& rm -rf /home/webrender/.cache/ms-playwright
60+
61+
COPY --chown=webrender:webrender . .
62+
RUN uv sync --frozen --no-dev \
63+
&& chmod 0755 docker/entrypoint-headless.sh docker/entrypoint-gui.sh \
64+
&& chown -R webrender:webrender /home/webrender
65+
66+
USER webrender
67+
ENTRYPOINT ["/usr/bin/tini", "--", "./docker/entrypoint-headless.sh"]
68+
CMD ["python", "./run_server.py"]
69+
70+
71+
# Shared headed Chromium and virtual-X layer. Desktop targets add their own
72+
# session implementation so the XFCE image does not also carry the lightweight WM.
73+
FROM app-base AS headed-base
74+
75+
USER root
76+
RUN playwright install --no-shell chromium \
77+
&& chmod -R a+rX /ms-playwright \
78+
&& rm -rf /home/webrender/.cache/ms-playwright \
79+
&& apt-get update \
80+
&& apt-get install -y --no-install-recommends \
81+
dbus-x11 \
82+
x11-utils \
83+
x11-xserver-utils \
84+
xvfb \
85+
&& rm -rf /var/lib/apt/lists/* \
86+
&& install -d -m 1777 /tmp/.ICE-unix /tmp/.X11-unix
87+
88+
COPY --chown=webrender:webrender . .
89+
RUN uv sync --frozen --no-dev \
90+
&& chmod 0755 docker/entrypoint-headless.sh docker/entrypoint-gui.sh \
91+
&& chown -R webrender:webrender /home/webrender
92+
93+
ENV DISPLAY=:99 \
94+
WEBRENDER_DEBUG=0 \
95+
WEBRENDER_HEADLESS=0 \
96+
XVFB_SCREEN=2560x1600x24
4397

44-
ADD . .
98+
USER webrender
99+
ENTRYPOINT ["/usr/bin/tini", "--", "./docker/entrypoint-gui.sh"]
45100
CMD ["python", "./run_server.py"]
101+
102+
103+
# Headed Chromium with a lightweight window manager.
104+
FROM headed-base AS headed
105+
106+
USER root
107+
RUN apt-get update \
108+
&& apt-get install -y --no-install-recommends \
109+
twm \
110+
xfonts-base \
111+
&& rm -rf /var/lib/apt/lists/*
112+
113+
ENV WEBRENDER_DESKTOP=twm
114+
115+
USER webrender
116+
117+
118+
# Complete XFCE desktop with optional, password-protected noVNC access.
119+
# noVNC is disabled unless ENABLE_NOVNC=1 is supplied at runtime.
120+
FROM headed-base AS desktop
121+
122+
USER root
123+
ARG NOVNC_VERSION=1.7.0
124+
ARG NOVNC_SHA256=b1003a11b6e6e8d8f7f5e5586daae7f8ca651d8aee0aa155ff9ac841c48f52c6
125+
RUN apt-get update \
126+
&& apt-get install -y --no-install-recommends \
127+
xfce4 \
128+
x11vnc \
129+
&& rm -rf /var/lib/apt/lists/* \
130+
&& uv sync --frozen --no-dev --extra desktop \
131+
&& python docker/install-novnc.py "${NOVNC_VERSION}" "${NOVNC_SHA256}" /usr/share/novnc
132+
133+
ENV ENABLE_NOVNC=0 \
134+
NO_AT_BRIDGE=1 \
135+
NOVNC_LISTEN=127.0.0.1 \
136+
NOVNC_PORT=6080 \
137+
VNC_PORT=5900 \
138+
WEBRENDER_DESKTOP=xfce
139+
140+
EXPOSE 6080
141+
142+
USER webrender
143+
144+
145+
# Keep `docker build .` backward-compatible: the default artifact is headless.
146+
FROM headless AS default

0 commit comments

Comments
 (0)