From 59314a701e4becc901f6880c8ba1e90b20023686 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BA=9A?= <12574507+large-mushroom77@user.noreply.gitee.com> Date: Tue, 14 Jul 2026 14:10:26 +0800 Subject: [PATCH 01/11] =?UTF-8?q?fix:=20=E5=AE=8C=E6=88=90=20Hermes=20?= =?UTF-8?q?=E9=95=9C=E5=83=8F=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/build-docker.yml | 17 ++++++++++++++++- src/labnow-open-etc/Caddyfile | 14 +++++++++++++- src/labnow-open-web/src/App.jsx | 11 +++++++++++ src/labnow-open.Dockerfile | 5 ++++- 4 files changed, 44 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 469a287..eed6940 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -86,10 +86,25 @@ jobs: --build-arg "PROFILE_LOCALIZE=default" --build-arg "BASE_IMG=labnow/openclaw:latest" push_image labnow-open-openclaw + job-hermes: + name: "labnow-open-hermes" + runs-on: ubuntu-latest + permissions: {"contents": "read", "pull-requests": "write"} + env: + REGISTRY_SRC: quay.io + REGISTRY_DST: quay.io + steps: + - uses: actions/checkout@v7 + - run: | + source ./tool/tool.sh + build_image labnow-open-hermes latest ./src/labnow-open.Dockerfile \ + --build-arg "PROFILE_LOCALIZE=default" --build-arg "BASE_IMG=labnow/hermes:latest" + push_image labnow-open-hermes + ## Sync all images in this build (listed by "names") to mirror registry. sync_images: - needs: ["job-developer", "job-data-science", "job-openclaw"] + needs: ["job-developer", "job-data-science", "job-openclaw", "job-hermes"] runs-on: ubuntu-latest permissions: {"contents": "read", "pull-requests": "write"} steps: diff --git a/src/labnow-open-etc/Caddyfile b/src/labnow-open-etc/Caddyfile index 417a925..5d39a33 100644 --- a/src/labnow-open-etc/Caddyfile +++ b/src/labnow-open-etc/Caddyfile @@ -61,7 +61,19 @@ redir @path_user_rshiny_no_slash {path}/ 308 # Redirect paths ending in "rshiny" to "rshiny/" - handle /* { # Proxy all other requests to a default backend server on port 8888 for jupyter + @path_user_hermes_no_slash path_regexp ^{$URL_PREFIX:}hermes$ + redir @path_user_hermes_no_slash {path}/ 308 + + @path_user_hermes path_regexp ^{$URL_PREFIX:}hermes/.*$ + handle @path_user_hermes { + uri strip_prefix {$URL_PREFIX:}hermes + reverse_proxy 127.0.0.1:9119 { + header_up X-Forwarded-Prefix {$URL_PREFIX:}hermes + } + } + + + handle { # Proxy all other requests to a default backend server on port 8888 for jupyter reverse_proxy localhost:8888 } } diff --git a/src/labnow-open-web/src/App.jsx b/src/labnow-open-web/src/App.jsx index dea475c..d6b25b2 100644 --- a/src/labnow-open-web/src/App.jsx +++ b/src/labnow-open-web/src/App.jsx @@ -62,6 +62,17 @@ const PROGRAM_DEFINITIONS = { description: "Run Shiny applications for interactive dashboards. Useful for sharing data apps with your team.", }, + "hermes-gateway": { + displayName: "Hermes Gateway", + hidden: true, + }, + "hermes-dashboard": { + displayName: "Hermes", + link: "/hermes/", + logo: "logo-hermes.svg", + description: + "An agent workspace for managing sessions, skills, configurations, and plans.", + }, }; function NotificationBar({ notice, onClose }) { diff --git a/src/labnow-open.Dockerfile b/src/labnow-open.Dockerfile index 010eea6..a183d2f 100644 --- a/src/labnow-open.Dockerfile +++ b/src/labnow-open.Dockerfile @@ -34,13 +34,16 @@ RUN set -eux && source /opt/utils/script-localize.sh ${PROFILE_LOCALIZE} \ && (type caddy || (source /opt/utils/script-setup-net.sh && setup_caddy && echo "Caddy installed")) \ && mkdir -pv /etc/supervisord && ln -sf /opt/labnow-open/etc/supervisord.conf /etc/supervisord/ \ && mkdir -pv /etc/caddy && ln -sf /opt/labnow-open/etc/Caddyfile /etc/caddy/ \ + && ([ ! -f /usr/local/bin/start-supervisord.sh ] && printf '#!/bin/bash\nLOG_FORMAT=json exec supervisord -c /etc/supervisord/supervisord.conf\n' > /usr/local/bin/start-supervisord.sh || true ) \ + && ([ ! -f /usr/local/bin/start-caddy.sh ] && printf '#!/bin/bash\ncaddy run --config /etc/caddy/Caddyfile\n' > /usr/local/bin/start-caddy.sh || true ) \ + && chmod +x /usr/local/bin/start-caddy.sh /usr/local/bin/start-supervisord.sh \ && (type jupyter && echo '{"ServerApp":{"ip":"0.0.0.0","port":8888,"root_dir":"/root","default_url":"/home","token":"","password":"","allow_root":true,"allow_origin":"*","open_browser":false}}' > /opt/conda/etc/jupyter/jupyter_server_config.json || true) \ && (type jupyter && printf "[program:jupyter]\ncommand=/usr/local/bin/start-jupyterlab.sh\n" >> /etc/supervisord/supervisord.conf || true) \ && (type code-server && printf "[program:vscode]\ncommand=/usr/local/bin/start-code-server.sh\n" >> /etc/supervisord/supervisord.conf || true) \ && (type rserver && printf "[program:rserver]\ncommand=/usr/local/bin/start-rserver.sh\n" >> /etc/supervisord/supervisord.conf || true) \ && (type shiny-server && printf "[program:rshiny]\ncommand=/usr/local/bin/start-shiny-server.sh\n" >> /etc/supervisord/supervisord.conf || true) \ && (type openclaw && printf "[program:openclaw]\ncommand=/usr/local/bin/start-openclaw.sh\n" >> /etc/supervisord/supervisord.conf || true) \ - && (type hermes && printf "[program:hermes]\ncommand=/usr/local/bin/start-hermes.sh\n" >> /etc/supervisord/supervisord.conf || true) \ + && (type hermes && printf "[program:hermes-gateway]\ncommand=/usr/local/bin/start-hermes.sh gateway\nautostart=true\n\n[program:hermes-dashboard]\ncommand=/usr/local/bin/start-hermes.sh dashboard --host 127.0.0.1 --port 9119 --no-open\nautostart=true\n" >> /etc/supervisord/supervisord.conf || true) \ # cleanup of any temporary or cache files to keep the image size down && rm -rf /opt/conda/share/jupyter/lab/staging \ && source /opt/utils/script-utils.sh && install__clean From 14d8c83ba097088da312053f2bb2524032b84b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BA=9A?= <12574507+large-mushroom77@user.noreply.gitee.com> Date: Wed, 15 Jul 2026 16:33:57 +0800 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=E8=A1=A5=E9=BD=90=20Hermes=20Lau?= =?UTF-8?q?ncher=20readiness=20=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/labnow-open-etc/Caddyfile | 1 + src/labnow-open-etc/routes/hermes-readiness.caddy | 6 ++++++ src/labnow-open.Dockerfile | 3 ++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 src/labnow-open-etc/routes/hermes-readiness.caddy diff --git a/src/labnow-open-etc/Caddyfile b/src/labnow-open-etc/Caddyfile index 5d39a33..7c87327 100644 --- a/src/labnow-open-etc/Caddyfile +++ b/src/labnow-open-etc/Caddyfile @@ -72,6 +72,7 @@ } } + import /etc/caddy/enabled-routes/*.caddy handle { # Proxy all other requests to a default backend server on port 8888 for jupyter reverse_proxy localhost:8888 diff --git a/src/labnow-open-etc/routes/hermes-readiness.caddy b/src/labnow-open-etc/routes/hermes-readiness.caddy new file mode 100644 index 0000000..7f8cb2b --- /dev/null +++ b/src/labnow-open-etc/routes/hermes-readiness.caddy @@ -0,0 +1,6 @@ +@path_user_hermes_readiness path {$URL_PREFIX:/}api + +handle @path_user_hermes_readiness { + rewrite * /api/status + reverse_proxy 127.0.0.1:9119 +} diff --git a/src/labnow-open.Dockerfile b/src/labnow-open.Dockerfile index a183d2f..40207f3 100644 --- a/src/labnow-open.Dockerfile +++ b/src/labnow-open.Dockerfile @@ -33,7 +33,7 @@ RUN set -eux && source /opt/utils/script-localize.sh ${PROFILE_LOCALIZE} \ && (type supervisord || (source /opt/utils/script-setup-sys.sh && setup_supervisord && echo "Supervisord installed")) \ && (type caddy || (source /opt/utils/script-setup-net.sh && setup_caddy && echo "Caddy installed")) \ && mkdir -pv /etc/supervisord && ln -sf /opt/labnow-open/etc/supervisord.conf /etc/supervisord/ \ - && mkdir -pv /etc/caddy && ln -sf /opt/labnow-open/etc/Caddyfile /etc/caddy/ \ + && mkdir -pv /etc/caddy /etc/caddy/enabled-routes && ln -sf /opt/labnow-open/etc/Caddyfile /etc/caddy/ \ && ([ ! -f /usr/local/bin/start-supervisord.sh ] && printf '#!/bin/bash\nLOG_FORMAT=json exec supervisord -c /etc/supervisord/supervisord.conf\n' > /usr/local/bin/start-supervisord.sh || true ) \ && ([ ! -f /usr/local/bin/start-caddy.sh ] && printf '#!/bin/bash\ncaddy run --config /etc/caddy/Caddyfile\n' > /usr/local/bin/start-caddy.sh || true ) \ && chmod +x /usr/local/bin/start-caddy.sh /usr/local/bin/start-supervisord.sh \ @@ -44,6 +44,7 @@ RUN set -eux && source /opt/utils/script-localize.sh ${PROFILE_LOCALIZE} \ && (type shiny-server && printf "[program:rshiny]\ncommand=/usr/local/bin/start-shiny-server.sh\n" >> /etc/supervisord/supervisord.conf || true) \ && (type openclaw && printf "[program:openclaw]\ncommand=/usr/local/bin/start-openclaw.sh\n" >> /etc/supervisord/supervisord.conf || true) \ && (type hermes && printf "[program:hermes-gateway]\ncommand=/usr/local/bin/start-hermes.sh gateway\nautostart=true\n\n[program:hermes-dashboard]\ncommand=/usr/local/bin/start-hermes.sh dashboard --host 127.0.0.1 --port 9119 --no-open\nautostart=true\n" >> /etc/supervisord/supervisord.conf || true) \ + && (type hermes && ln -sf /opt/labnow-open/etc/routes/hermes-readiness.caddy /etc/caddy/enabled-routes/hermes-readiness.caddy || true) \ # cleanup of any temporary or cache files to keep the image size down && rm -rf /opt/conda/share/jupyter/lab/staging \ && source /opt/utils/script-utils.sh && install__clean From d03d39cc66644824c9cc0b8a00d6ed66732153cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BA=9A?= <12574507+large-mushroom77@user.noreply.gitee.com> Date: Wed, 15 Jul 2026 22:48:59 +0800 Subject: [PATCH 03/11] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=20URL=20?= =?UTF-8?q?=E5=89=8D=E7=BC=80=E6=A0=B9=E8=B7=AF=E5=BE=84=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/labnow-open-etc/Caddyfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/labnow-open-etc/Caddyfile b/src/labnow-open-etc/Caddyfile index 7c87327..e84892c 100644 --- a/src/labnow-open-etc/Caddyfile +++ b/src/labnow-open-etc/Caddyfile @@ -15,7 +15,7 @@ } # Redirect the root path to the user home page - @root path_regexp root ^{$URL_PREFIX:}/?$ + @root path_regexp root ^{$URL_PREFIX:/}?$ redir @root {$URL_PREFIX:}home 307 # Handle user home paths From 7f43656b8db451111f0d6c73e571c45e18db2501 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E5=BA=9A?= <12574507+large-mushroom77@user.noreply.gitee.com> Date: Wed, 15 Jul 2026 22:57:07 +0800 Subject: [PATCH 04/11] =?UTF-8?q?chore:=20=E5=BF=BD=E7=95=A5=E6=9C=AC?= =?UTF-8?q?=E5=9C=B0=E9=A1=B9=E7=9B=AE=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index 531f1b1..3aea2d1 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,8 @@ dmypy.json # Yarn cache .yarn/ + +# Local project documentation +development-docs/ +architecture-overview/ +docs/architecture-analysis.md From 75b2ae044bb1f9cd9037d87e8b44e8e72dfd380d Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 21:32:14 +0800 Subject: [PATCH 05/11] update agent files --- .agents/skills | 1 + .claude/skills | 1 + .github/copilot-instructions.md | 1 + .github/skills | 1 + .gitignore | 4 +--- AGENTS.md | 0 CLAUDE.md | 1 + 7 files changed, 6 insertions(+), 3 deletions(-) create mode 120000 .agents/skills create mode 120000 .claude/skills create mode 120000 .github/copilot-instructions.md create mode 120000 .github/skills create mode 100644 AGENTS.md create mode 120000 CLAUDE.md diff --git a/.agents/skills b/.agents/skills new file mode 120000 index 0000000..cd2ebc5 --- /dev/null +++ b/.agents/skills @@ -0,0 +1 @@ +../doc/skills \ No newline at end of file diff --git a/.claude/skills b/.claude/skills new file mode 120000 index 0000000..cd2ebc5 --- /dev/null +++ b/.claude/skills @@ -0,0 +1 @@ +../doc/skills \ No newline at end of file diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 120000 index 0000000..be77ac8 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1 @@ +../AGENTS.md \ No newline at end of file diff --git a/.github/skills b/.github/skills new file mode 120000 index 0000000..cd2ebc5 --- /dev/null +++ b/.github/skills @@ -0,0 +1 @@ +../doc/skills \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3aea2d1..4a49d3e 100644 --- a/.gitignore +++ b/.gitignore @@ -132,6 +132,4 @@ dmypy.json .yarn/ # Local project documentation -development-docs/ -architecture-overview/ -docs/architecture-analysis.md +doc/dev/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..e69de29 diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 0000000..47dc3e3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file From 87de74e2419a679c390629c14904e7403351aa22 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 21:51:05 +0800 Subject: [PATCH 06/11] update Caddy --- src/labnow-open-etc/CaddyRoutes/hermes.caddy | 15 ++++ .../CaddyRoutes/openclaw.caddy | 13 ++++ src/labnow-open-etc/CaddyRoutes/rserver.caddy | 10 +++ src/labnow-open-etc/CaddyRoutes/selkies.caddy | 9 +++ src/labnow-open-etc/CaddyRoutes/shiny.caddy | 10 +++ src/labnow-open-etc/CaddyRoutes/vscode.caddy | 8 ++ src/labnow-open-etc/Caddyfile | 74 +------------------ .../routes/hermes-readiness.caddy | 6 -- src/labnow-open.Dockerfile | 15 ++-- 9 files changed, 76 insertions(+), 84 deletions(-) create mode 100644 src/labnow-open-etc/CaddyRoutes/hermes.caddy create mode 100644 src/labnow-open-etc/CaddyRoutes/openclaw.caddy create mode 100644 src/labnow-open-etc/CaddyRoutes/rserver.caddy create mode 100644 src/labnow-open-etc/CaddyRoutes/selkies.caddy create mode 100644 src/labnow-open-etc/CaddyRoutes/shiny.caddy create mode 100644 src/labnow-open-etc/CaddyRoutes/vscode.caddy delete mode 100644 src/labnow-open-etc/routes/hermes-readiness.caddy diff --git a/src/labnow-open-etc/CaddyRoutes/hermes.caddy b/src/labnow-open-etc/CaddyRoutes/hermes.caddy new file mode 100644 index 0000000..fcedc89 --- /dev/null +++ b/src/labnow-open-etc/CaddyRoutes/hermes.caddy @@ -0,0 +1,15 @@ +# Hermes +@path_user_hermes_no_slash path {$URL_PREFIX:}hermes +redir @path_user_hermes_no_slash {path}/ 308 + +@path_user_hermes path {$URL_PREFIX:}hermes/* +handle @path_user_hermes { + uri strip_prefix {$URL_PREFIX:}hermes + reverse_proxy localhost:9119 +} + +@path_user_hermes_readiness path {$URL_PREFIX:/}api +handle @path_user_hermes_readiness { + rewrite * /api/status + reverse_proxy 127.0.0.1:9119 +} diff --git a/src/labnow-open-etc/CaddyRoutes/openclaw.caddy b/src/labnow-open-etc/CaddyRoutes/openclaw.caddy new file mode 100644 index 0000000..50565c2 --- /dev/null +++ b/src/labnow-open-etc/CaddyRoutes/openclaw.caddy @@ -0,0 +1,13 @@ +# OpenClaw +# Redirect non-WebSocket requests to the canonical trailing-slash URL +@path_user_openclaw_no_slash { + path {$URL_PREFIX:}openclaw + not header Connection *Upgrade* +} +redir @path_user_openclaw_no_slash {path}/ 308 + +@path_user_openclaw path {$URL_PREFIX:}openclaw/* +handle @path_user_openclaw { + uri strip_prefix {$URL_PREFIX:}openclaw + reverse_proxy localhost:18789 +} diff --git a/src/labnow-open-etc/CaddyRoutes/rserver.caddy b/src/labnow-open-etc/CaddyRoutes/rserver.caddy new file mode 100644 index 0000000..0d78e43 --- /dev/null +++ b/src/labnow-open-etc/CaddyRoutes/rserver.caddy @@ -0,0 +1,10 @@ +# RStudio Server +@path_user_rserver path {$URL_PREFIX:}rserver/* +handle @path_user_rserver { + uri strip_prefix {$URL_PREFIX:}rserver + reverse_proxy localhost:8787 { + header_up -X-Forwarded-Port + } +} +@path_user_rserver_no_slash path {$URL_PREFIX:}rserver +redir @path_user_rserver_no_slash {path}/ 308 diff --git a/src/labnow-open-etc/CaddyRoutes/selkies.caddy b/src/labnow-open-etc/CaddyRoutes/selkies.caddy new file mode 100644 index 0000000..5377774 --- /dev/null +++ b/src/labnow-open-etc/CaddyRoutes/selkies.caddy @@ -0,0 +1,9 @@ +# Selkies +@path_user_selkies_no_slash path {$URL_PREFIX:}selkies +redir @path_user_selkies_no_slash {path}/ 308 + +@path_user_selkies path {$URL_PREFIX:}selkies/* +handle @path_user_selkies { + uri strip_prefix {$URL_PREFIX:}selkies + reverse_proxy localhost:8080 +} diff --git a/src/labnow-open-etc/CaddyRoutes/shiny.caddy b/src/labnow-open-etc/CaddyRoutes/shiny.caddy new file mode 100644 index 0000000..44b7859 --- /dev/null +++ b/src/labnow-open-etc/CaddyRoutes/shiny.caddy @@ -0,0 +1,10 @@ +# Shiny Server +@path_user_rshiny path {$URL_PREFIX:}rshiny/* +handle @path_user_rshiny { + uri strip_prefix {$URL_PREFIX:}rshiny + reverse_proxy localhost:3838 { + header_up -X-Forwarded-Port + } +} +@path_user_rshiny_no_slash path {$URL_PREFIX:}rshiny +redir @path_user_rshiny_no_slash {path}/ 308 diff --git a/src/labnow-open-etc/CaddyRoutes/vscode.caddy b/src/labnow-open-etc/CaddyRoutes/vscode.caddy new file mode 100644 index 0000000..5f4dea7 --- /dev/null +++ b/src/labnow-open-etc/CaddyRoutes/vscode.caddy @@ -0,0 +1,8 @@ +# VS Code +@path_user_vscode path {$URL_PREFIX:}vscode/* +handle @path_user_vscode { + uri strip_prefix {$URL_PREFIX:}vscode + reverse_proxy localhost:9999 +} +@path_user_vscode_no_slash path {$URL_PREFIX:}vscode +redir @path_user_vscode_no_slash {path}/ 308 diff --git a/src/labnow-open-etc/Caddyfile b/src/labnow-open-etc/Caddyfile index 8e233b4..90df8a6 100644 --- a/src/labnow-open-etc/Caddyfile +++ b/src/labnow-open-etc/Caddyfile @@ -24,79 +24,13 @@ @path_user_home_no_slash path {$URL_PREFIX:}home redir @path_user_home_no_slash {path}/ 308 - # VS Code - @path_user_vscode path {$URL_PREFIX:}vscode/* - handle @path_user_vscode { - uri strip_prefix {$URL_PREFIX:}vscode - reverse_proxy localhost:9999 - } - @path_user_vscode_no_slash path {$URL_PREFIX:}vscode - redir @path_user_vscode_no_slash {path}/ 308 - - # RStudio Server - @path_user_rserver path {$URL_PREFIX:}rserver/* - handle @path_user_rserver { - uri strip_prefix {$URL_PREFIX:}rserver - reverse_proxy localhost:8787 { - header_up -X-Forwarded-Port - } - } - @path_user_rserver_no_slash path {$URL_PREFIX:}rserver - redir @path_user_rserver_no_slash {path}/ 308 - - # Shiny Server - @path_user_rshiny path {$URL_PREFIX:}rshiny/* - handle @path_user_rshiny { - uri strip_prefix {$URL_PREFIX:}rshiny - reverse_proxy localhost:3838 { - header_up -X-Forwarded-Port - } - } - @path_user_rshiny_no_slash path {$URL_PREFIX:}rshiny - redir @path_user_rshiny_no_slash {path}/ 308 - - # OpenClaw - # Redirect non-WebSocket requests to the canonical trailing-slash URL - @path_user_openclaw_no_slash { - path {$URL_PREFIX:}openclaw - not header Connection *Upgrade* - } - redir @path_user_openclaw_no_slash {path}/ 308 - - @path_user_openclaw path {$URL_PREFIX:}openclaw/* - handle @path_user_openclaw { - uri strip_prefix {$URL_PREFIX:}openclaw - reverse_proxy localhost:18789 - } - - # Hermes - @path_user_hermes_no_slash path {$URL_PREFIX:}hermes - redir @path_user_hermes_no_slash {path}/ 308 - - @path_user_hermes path {$URL_PREFIX:}hermes/* - handle @path_user_hermes { - uri strip_prefix {$URL_PREFIX:}hermes - reverse_proxy localhost:9119 - } - - # Selkies - @path_user_selkies_no_slash path {$URL_PREFIX:}selkies - redir @path_user_selkies_no_slash {path}/ 308 - - @path_user_selkies path {$URL_PREFIX:}selkies/* - handle @path_user_selkies { - uri strip_prefix {$URL_PREFIX:}selkies - reverse_proxy localhost:8080 - } - - # Health check - handle_path {$URL_PREFIX:}api { + handle_path {$URL_PREFIX:}api { # default healthcheck endpoint respond "OK" 200 } - import /etc/caddy/enabled-routes/*.caddy - - handle { # Proxy all other requests to a default backend server on port 8888 for jupyter + handle { # Proxy all other requests to a default backend server on port 8888 reverse_proxy localhost:8888 } + + import /etc/caddy/enabled-routes/*.caddy } diff --git a/src/labnow-open-etc/routes/hermes-readiness.caddy b/src/labnow-open-etc/routes/hermes-readiness.caddy deleted file mode 100644 index 7f8cb2b..0000000 --- a/src/labnow-open-etc/routes/hermes-readiness.caddy +++ /dev/null @@ -1,6 +0,0 @@ -@path_user_hermes_readiness path {$URL_PREFIX:/}api - -handle @path_user_hermes_readiness { - rewrite * /api/status - reverse_proxy 127.0.0.1:9119 -} diff --git a/src/labnow-open.Dockerfile b/src/labnow-open.Dockerfile index ad495bc..0cc3a31 100644 --- a/src/labnow-open.Dockerfile +++ b/src/labnow-open.Dockerfile @@ -32,19 +32,18 @@ RUN set -eux && source /opt/utils/script-localize.sh ${PROFILE_LOCALIZE} \ ## handle control scripts and extensions && (type supervisord || (source /opt/utils/script-setup-sys.sh && setup_supervisord && echo "Supervisord installed")) \ && (type caddy || (source /opt/utils/script-setup-net.sh && setup_caddy && echo "Caddy installed")) \ - && mkdir -pv /etc/supervisord && ln -sf /opt/labnow-open/etc/supervisord.conf /etc/supervisord/ \ - && mkdir -pv /etc/caddy /etc/caddy/enabled-routes && ln -sf /opt/labnow-open/etc/Caddyfile /etc/caddy/ \ + && mkdir -pv /etc/supervisord && ln -sf /opt/labnow-open/etc/supervisord.conf /etc/supervisord/ \ + && mkdir -pv /etc/caddy && ln -sf /opt/labnow-open/etc/Caddy* /etc/caddy/ \ && ([ ! -f /usr/local/bin/start-supervisord.sh ] && printf '#!/bin/bash\nLOG_FORMAT=json exec supervisord -c /etc/supervisord/supervisord.conf\n' > /usr/local/bin/start-supervisord.sh || true ) \ && ([ ! -f /usr/local/bin/start-caddy.sh ] && printf '#!/bin/bash\ncaddy run --config /etc/caddy/Caddyfile\n' > /usr/local/bin/start-caddy.sh || true ) \ && chmod +x /usr/local/bin/start-caddy.sh /usr/local/bin/start-supervisord.sh \ && (type jupyter && echo '{"ServerApp":{"ip":"0.0.0.0","port":8888,"root_dir":"/root","default_url":"/home","token":"","password":"","allow_root":true,"allow_origin":"*","open_browser":false}}' > /opt/conda/etc/jupyter/jupyter_server_config.json || true) \ - && (type jupyter && printf "[program:jupyter]\ncommand=/usr/local/bin/start-jupyterlab.sh\n" >> /etc/supervisord/supervisord.conf || true) \ - && (type code-server && printf "[program:vscode]\ncommand=/usr/local/bin/start-code-server.sh\n" >> /etc/supervisord/supervisord.conf || true) \ - && (type rserver && printf "[program:rserver]\ncommand=/usr/local/bin/start-rserver.sh\n" >> /etc/supervisord/supervisord.conf || true) \ - && (type shiny-server && printf "[program:rshiny]\ncommand=/usr/local/bin/start-shiny-server.sh\n" >> /etc/supervisord/supervisord.conf || true) \ - && (type openclaw && printf "[program:openclaw]\ncommand=/usr/local/bin/start-openclaw.sh\n" >> /etc/supervisord/supervisord.conf || true) \ + && (type jupyter && printf "[program:jupyter]\ncommand=/usr/local/bin/start-jupyterlab.sh\n" >> /etc/supervisord/supervisord.conf || rm -f /opt/labnow-open/etc/CaddyRoutes/jupyter.caddy ) \ + && (type code-server && printf "[program:vscode]\ncommand=/usr/local/bin/start-code-server.sh\n" >> /etc/supervisord/supervisord.conf || rm -f /opt/labnow-open/etc/CaddyRoutes/vscode.caddy ) \ + && (type rserver && printf "[program:rserver]\ncommand=/usr/local/bin/start-rserver.sh\n" >> /etc/supervisord/supervisord.conf || rm -f /opt/labnow-open/etc/CaddyRoutes/rserver.caddy ) \ + && (type shiny-server && printf "[program:rshiny]\ncommand=/usr/local/bin/start-shiny-server.sh\n" >> /etc/supervisord/supervisord.conf || rm -f /opt/labnow-open/etc/CaddyRoutes/shiny.caddy ) \ + && (type openclaw && printf "[program:openclaw]\ncommand=/usr/local/bin/start-openclaw.sh\n" >> /etc/supervisord/supervisord.conf || rm -f /opt/labnow-open/etc/CaddyRoutes/openclaw.caddy ) \ && (type hermes && printf "[program:hermes-gateway]\ncommand=/usr/local/bin/start-hermes.sh gateway\nautostart=true\n\n[program:hermes-dashboard]\ncommand=/usr/local/bin/start-hermes.sh dashboard --host 127.0.0.1 --port 9119 --no-open\nautostart=true\n" >> /etc/supervisord/supervisord.conf || true) \ - && (type hermes && ln -sf /opt/labnow-open/etc/routes/hermes-readiness.caddy /etc/caddy/enabled-routes/hermes-readiness.caddy || true) \ # cleanup of any temporary or cache files to keep the image size down && source /opt/utils/script-utils.sh && install__clean From aa54cc84a533e529f8abce5126af87fbc05ecb7a Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 21:53:07 +0800 Subject: [PATCH 07/11] debug build --- .github/workflows/build-docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 26ddc09..3299f3e 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -98,7 +98,7 @@ jobs: - run: | source ./tool/tool.sh build_image labnow-open-hermes latest ./src/labnow-open.Dockerfile \ - --build-arg "PROFILE_LOCALIZE=default" --build-arg "BASE_IMG=labnow/hermes:latest" + --build-arg "PROFILE_LOCALIZE=default" --build-arg "BASE_IMG=hermes:latest" push_image labnow-open-hermes From 70d248e86e37da918211a114822a7cdb7c65f9b3 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 22:05:01 +0800 Subject: [PATCH 08/11] update build --- .github/workflows/build-docker.yml | 6 +----- src/labnow-open.Dockerfile | 2 +- tool/cicd/docker-compose.labnow-open.DEV.yml | 13 ++++++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 3299f3e..491a9ea 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -89,10 +89,6 @@ jobs: job-hermes: name: "labnow-open-hermes" runs-on: ubuntu-latest - permissions: {"contents": "read", "pull-requests": "write"} - env: - REGISTRY_SRC: quay.io - REGISTRY_DST: quay.io steps: - uses: actions/checkout@v7 - run: | @@ -103,7 +99,7 @@ jobs: ## Sync all images in this build (listed by "names") to mirror registry. - sync_images: + job-sync-images: needs: ["job-developer", "job-data-science", "job-openclaw", "job-hermes"] runs-on: ubuntu-latest steps: diff --git a/src/labnow-open.Dockerfile b/src/labnow-open.Dockerfile index 0cc3a31..ba66431 100644 --- a/src/labnow-open.Dockerfile +++ b/src/labnow-open.Dockerfile @@ -16,7 +16,7 @@ RUN set -eux \ && source /opt/utils/script-localize.sh ${PROFILE_LOCALIZE} \ && source /opt/utils/script-setup-core.sh && setup_node_pnpm 11 \ && cd /tmp/labnow-open-web \ - && export CI=true && pnpm install --no-strict-peer-dependencies && pnpm run build \ + && export CI=true && export CARBON_TELEMETRY_DISABLED=1 && pnpm install --no-strict-peer-dependencies --ignore-scripts && pnpm run build \ && mkdir -pv /opt/labnow-open && mv dist /opt/labnow-open/web \ && ls -alh /opt/labnow-open/web /opt/labnow-open/etc diff --git a/tool/cicd/docker-compose.labnow-open.DEV.yml b/tool/cicd/docker-compose.labnow-open.DEV.yml index 8463dcf..e50539d 100644 --- a/tool/cicd/docker-compose.labnow-open.DEV.yml +++ b/tool/cicd/docker-compose.labnow-open.DEV.yml @@ -14,11 +14,14 @@ services: env_file: ["../credentials/${ENV_PROFILE:-DEV}-labnow-open.env"] user: "0:0" ports: ["${PORT_APP:-20000}:80", "${PORT_WEB:-30000}:3000"] + working_dir: /workspace volumes: - - ../../doc:/root/doc:rw - - ../../src:/root/src:rw - - ../../tool:/root/tool:rw + - ../../:/workspace:rw + - ${PNPM_STORE_PATH:-$HOME/.pnpm-store}:/workspace/.pnpm-store:rw + - ${PNPM_STORE_PATH:-$HOME/.pnpm-store}:/root/.pnpm-store:rw + # AI4Coding tools: - "$HOME/.claude:/root/.claude:rw" - - /data/.pnpm-store:/root/.local/share/pnpm:rw - working_dir: /root/src + - "$HOME/.codex:/root/.codex:rw" + - "$HOME/.copilot:/root/.copilot:rw" + - "$HOME/.gemini:/root/.gemini:rw" command: "tail -f /dev/null" From f3579b08a5967f509949afbadd978e3d9a2c96bc Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 22:08:12 +0800 Subject: [PATCH 09/11] ignore warn --- .github/workflows/build-docker.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml index 491a9ea..22716f6 100644 --- a/.github/workflows/build-docker.yml +++ b/.github/workflows/build-docker.yml @@ -51,7 +51,8 @@ jobs: --config p/javascript \ --config p/react \ --config p/owasp-top-ten \ - --exclude-rule=dockerfile.security.missing-user.missing-user + --exclude-rule=dockerfile.security.missing-user.missing-user \ + --exclude-rule=yaml.github-actions.security.github-actions-mutable-action-tag.github-actions-mutable-action-tag job-developer: name: "labnow-open-dev" From 397d8b56fb62a7e3f9068bf7e94d2fcf3812ecb0 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 22:10:28 +0800 Subject: [PATCH 10/11] debug build --- src/labnow-open.Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/labnow-open.Dockerfile b/src/labnow-open.Dockerfile index ba66431..db7d59f 100644 --- a/src/labnow-open.Dockerfile +++ b/src/labnow-open.Dockerfile @@ -1,11 +1,12 @@ # Use the existing image as the base ARG BASE_NAMESPACE="quay.io" +ARG BASE_IMG_BUILD="node:latest" ARG BASE_IMG="developer:latest" # this ENV will be used in /opt/utils/script-localize.sh ARG PROFILE_LOCALIZE="aliyun-pub" -FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG} AS builder +FROM ${BASE_NAMESPACE:+$BASE_NAMESPACE/}${BASE_IMG_BUILD} AS builder ARG PROFILE_LOCALIZE="aliyun-pub" ENV PROFILE_LOCALIZE=${PROFILE_LOCALIZE} From a18fc8d1335a9f89f2d6196c6d1b87a353d908d7 Mon Sep 17 00:00:00 2001 From: Bibo Hao Date: Thu, 30 Jul 2026 22:23:19 +0800 Subject: [PATCH 11/11] update docs --- README.md | 52 +++++++++++++++---- doc/README-cn.md | 76 +++++++++++++++++++++++++++ doc/README-dev.md | 130 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 231 insertions(+), 27 deletions(-) create mode 100644 doc/README-cn.md diff --git a/README.md b/README.md index 9695058..58dcf63 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![Visit Images on Quay.io](https://img.shields.io/badge/Quay.io-Images-green)](https://quay.io/organization/labnow) [![Visit Images on DockerHub](https://img.shields.io/badge/DockerHub-Images-green)](https://hub.docker.com/u/LabNow) -Please generously STAR★ our project or donate to us! +Please generously STAR⭐️ our project or donate to us! [![GitHub Stars](https://img.shields.io/github/stars/LabNow-ai/labnow-open.svg?label=Stars)](https://github.com/LabNow-ai/labnow-open/stargazers) Discussion and contributions are welcome: @@ -14,20 +14,39 @@ Discussion and contributions are welcome: [![Open an Issue on GitHub](https://img.shields.io/github/issues/LabNow-ai/labnow-open)](https://github.com/LabNow-ai/labnow-open/issues) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/LabNow-ai/labnow-open) +> 中文文档:[doc/README-cn.md](doc/README-cn.md) + --- -`LabNow Open` 是一个开源的容器化 AI/Data 工作空间项目,目标是让你用尽可能少的环境配置,快速得到可用的研发环境与工具门户。 +LabNow Open is an open-source, containerized AI/data science workspace. It bundles a lightweight **Web Console** that lets you manage and launch common data science services — all from a single browser tab, with minimal setup. + +## What's Included + +- **JupyterLab** — interactive notebooks and data exploration +- **VS Code** (code-server) — full-featured code editor in the browser +- **RStudio Server** — R development environment +- **Shiny Server** — interactive R web applications +- **Hermes** — gateway and dashboard for AI tool orchestration +- **OpenClaw** — AI agent platform + +All services run behind a unified **Caddy** reverse proxy on port 80, managed by **supervisord** for process lifecycle control. + +## Image Variants + +All variants are built from a single [Dockerfile](src/labnow-open.Dockerfile) and differ only in which base image they layer on top of: -项目内置了一个轻量 Web Console,用于管理和跳转常用数据科学服务,包含: +| Image Tag | Base Image | What It Contains | +|---|---|---| +| `labnow-open-dev` | `developer:latest` | Minimal dev tools + Web Console | +| `labnow-open-data-science` | `data-science-dev:latest` | Full data science stack (Python, R, Jupyter) | +| `labnow-open-hermes` | `hermes:latest` | Hermes gateway + dashboard | +| `labnow-open-openclaw` | `openclaw:latest` | OpenClaw AI agent platform | -- JupyterLab -- VS Code (code-server) -- RStudio Server -- Shiny Server +Images are published on both [Quay.io](https://quay.io/organization/labnow) and [Docker Hub](https://hub.docker.com/u/LabNow). -## 快速开始(直接使用现成镜像) +## Quick Start -如果你只想快速体验,建议直接运行镜像: +Run the full data science variant with a single command: ```bash docker run --rm -it \ @@ -36,4 +55,17 @@ docker run --rm -it \ quay.io/labnow/labnow-open-data-science:latest ``` -启动后访问:`http://localhost:8888/` +Then open **http://localhost:8888/** in your browser. The Web Console will show all available services — click any card to launch it. + +### Other Variants + +```bash +# Minimal developer image +docker run --rm -it -p 8888:80 quay.io/labnow/labnow-open-dev:latest + +# With Hermes gateway +docker run --rm -it -p 8888:80 quay.io/labnow/labnow-open-hermes:latest + +# With OpenClaw agent platform +docker run --rm -it -p 8888:80 quay.io/labnow/labnow-open-openclaw:latest +``` diff --git a/doc/README-cn.md b/doc/README-cn.md new file mode 100644 index 0000000..550b0e2 --- /dev/null +++ b/doc/README-cn.md @@ -0,0 +1,76 @@ +# LabNow Open + +[![License](https://img.shields.io/badge/License-BSD%203--Clause-green.svg)](https://opensource.org/licenses/BSD-3-Clause) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/LabNow-ai/labnow-open/build-docker.yml?branch=main)](https://github.com/LabNow-ai/labnow-open/actions) +[![GitHub Stars](https://img.shields.io/github/stars/LabNow-ai/labnow-open.svg?label=Stars)](https://github.com/LabNow-ai/labnow-open/stargazers) + +> English docs: [README.md](../README.md) + +--- + +`LabNow Open` 是一个开源的容器化 AI/数据科学工作空间项目。目标是让你用尽可能少的环境配置,快速得到可用的研发环境与统一工具门户。 + +项目内置一个轻量 **Web Console**(React SPA),通过卡片式界面管理和跳转常用数据科学服务。所有服务由 **Caddy** 统一反向代理,由 **supervisord** 负责进程生命周期管理。 + +## 集成的服务 + +| 服务 | 说明 | Web 路径 | +|---|---|---| +| **JupyterLab** | 交互式 Notebook 与数据探索 | `/lab/` | +| **VS Code** (code-server) | 浏览器中的完整代码编辑器 | `/vscode/` | +| **RStudio Server** | R 语言开发环境 | `/rserver/` | +| **Shiny Server** | R Shiny 交互式 Web 应用 | `/rshiny/` | +| **Hermes** | AI 工具网关与仪表盘 | `/hermes/` | +| **OpenClaw** | AI Agent 平台 | `/openclaw/` | + + +## 镜像变体 + +所有变体由同一个 [Dockerfile](../src/labnow-open.Dockerfile) 构建,区别仅在于所基于的基础镜像不同: + +| 镜像标签 | 基础镜像 | 包含内容 | +|---|---|---| +| `labnow-open-dev` | `developer:latest` | 精简开发工具 + Web Console | +| `labnow-open-data-science` | `data-science-dev:latest` | 完整数据科学栈(Python, R, Jupyter) | +| `labnow-open-hermes` | `hermes:latest` | Hermes 网关 + 仪表盘 | +| `labnow-open-openclaw` | `openclaw:latest` | OpenClaw AI Agent 平台 | + +镜像发布在 [Quay.io](https://quay.io/organization/labnow) 和 [Docker Hub](https://hub.docker.com/u/LabNow)。 + +## 快速开始 + +一条命令启动完整数据科学环境: + +```bash +docker run --rm -it \ + --name labnow-open \ + -p 8888:80 \ + quay.io/labnow/labnow-open-data-science:latest +``` + +浏览器访问 **http://localhost:8888/**,Web Console 会列出所有可用服务,点击卡片即可启动。 + +### 其他变体 + +```bash +# 精简开发者镜像 +docker run --rm -it -p 8888:80 quay.io/labnow/labnow-open-dev:latest + +# 带 Hermes 网关 +docker run --rm -it -p 8888:80 quay.io/labnow/labnow-open-hermes:latest + +# 带 OpenClaw Agent 平台 +docker run --rm -it -p 8888:80 quay.io/labnow/labnow-open-openclaw:latest +``` + + +## 开发与 CI/CD + +详细的本地开发指南与 CI/CD 流程说明见:[doc/README-dev.md](README-dev.md) + +## 参与贡献 + +欢迎提交 Issue 和 Pull Request,也可以通过以下方式参与讨论: + +- [![Join Discord Chat](https://img.shields.io/badge/Discuss_on-Discord-green)](https://discord.gg/kHUzgQxgbJ) +- [![Open an Issue](https://img.shields.io/github/issues/LabNow-ai/labnow-open)](https://github.com/LabNow-ai/labnow-open/issues) diff --git a/doc/README-dev.md b/doc/README-dev.md index 8942e4a..4459cf3 100644 --- a/doc/README-dev.md +++ b/doc/README-dev.md @@ -1,18 +1,76 @@ -# Develop with the `labnow/developer` image +# Development Guide -## Step 1: Start the developemnt environment +## Architecture -Start the development container and enter into the container. +``` +Browser (:80) + │ + v +┌──────────────────────────────────────────────┐ +│ Caddy (reverse proxy) │ +│ Serves static Console UI + routes traffic │ +│ /home/* → supervisord RPC (:9001) │ +│ /lab/* → JupyterLab (:8888) │ +│ /vscode/* → code-server (:9999) │ +│ /rserver/* → RStudio Server (:8787) │ +│ /rshiny/* → Shiny Server (:3838) │ +│ /hermes/* → Hermes Dashboard (:9119) │ +│ /openclaw/* → OpenClaw (:18789) │ +└──────────────────────────────────────────────┘ + │ + v +┌──────────────────────────────────────────────┐ +│ supervisord (process manager) │ +│ Auto-detects available tools and manages │ +│ their start/stop/restart lifecycle │ +└──────────────────────────────────────────────┘ + │ + v +┌──────────────────────────────────────────────┐ +│ React SPA (Web Console) │ +│ Carbon Design System, Vite 8, React 19 │ +│ Card-based UI for service management │ +└──────────────────────────────────────────────┘ +``` + +## Project Structure + +``` +labnow-open/ +├── src/ +│ ├── labnow-open.Dockerfile # Multi-stage build (builder + runtime) +│ ├── labnow-open-web/ # React 19 + Vite + Carbon Design System +│ │ ├── src/App.jsx # Console UI with service card grid +│ │ ├── src/api/ # supervisord REST API client +│ │ ├── src/hooks/ # React hooks for state management +│ │ └── public/ # Static assets and logos +│ └── labnow-open-etc/ # Runtime configuration +│ ├── supervisord.conf # Process manager configuration +│ ├── Caddyfile # Reverse proxy routes +│ └── CaddyRoutes/ # Per-service route snippets +├── tool/ +│ ├── tool.sh # CI helper: build/push Docker images +│ └── cicd/ # Dev & staging Docker Compose files +└── .github/workflows/ + └── build-docker.yml # CI/CD: semgrep SAST, multi-variant builds +``` + +## Getting Started + +### Method 1: Using run-dev.sh (Recommended) + +Start the development container and enter into it: ```bash -# method 1: ./tool/cicd/run-dev.sh up ./tool/cicd/run-dev.sh enter +``` -# method 2: +### Method 2: Manual Docker Run + +```bash docker run -d \ --name dev-labnow-open \ - --container-name=dev-labnow-open \ -p 8888:80 -p 3000:3000 \ -v $(pwd):/root/ \ quay.io/labnow/data-science-dev \ @@ -21,28 +79,66 @@ docker run -d \ docker exec -it dev-labnow-open bash ``` -## Step 2: Update configs and start the supervisord +## Running the App in Dev Mode -```bash -mkdir -pv /etc/supervisord && ln -sf /root/src/labnow-open-etc/supervisord.conf /etc/supervisord/ -mkdir -pv /etc/caddy && ln -sf /root/src/labnow-open-etc/Caddyfile /etc/caddy/ +Inside the container, link configs and start services: +```bash +mkdir -pv /etc/supervisord && ln -sf /root/src/labnow-open-etc/supervisord.conf /etc/supervisord/ +mkdir -pv /etc/caddy && ln -sf /root/src/labnow-open-etc/Caddyfile /etc/caddy/ export STATIC_DIR=/root/src/labnow-open-web/dist && start-supervisord.sh ``` -## Step 3: Access the service from the browser +Then access: **http://localhost:8888/** -http://localhost:8888/ +## Standalone Frontend Development -## Step 4: Build the image and run the built image +Develop the React frontend independently (without Docker): ```bash -docker build -t quay.io/labnow-dev/labnow-open-dev:latest \ - -f src/labnow-open.Dockerfile \ - --build-arg PROFILE_LOCALIZE=aliyun-pub . +cd src/labnow-open-web +pnpm install +pnpm run dev # Dev server on http://localhost:3000 +pnpm run build # Production build to dist/ +``` +In dev mode, Vite proxies `/api` requests to `localhost:80`, so you can run the frontend standalone against a running backend. + +## Building Docker Images Locally + +```bash +docker build -t labnow-open-dev:latest \ + -f src/labnow-open.Dockerfile \ + --build-arg PROFILE_LOCALIZE=aliyun-pub . +``` + +Run the built image: + +```bash docker run --rm -it \ --name dev-labnow-open \ -p 8080:80 \ - quay.io/labnow-dev/labnow-open-dev:latest + labnow-open-dev:latest ``` + +## CI/CD + +On push to `main`, pull requests, tag releases (`v*`), and weekly schedules, GitHub Actions: + +1. Runs **semgrep SAST** (static analysis security testing) +2. Builds all four image variants in parallel +3. Pushes to `quay.io/labnow/` and `docker.io/labnow/` +4. Optionally syncs to a mirror registry + +See [.github/workflows/build-docker.yml](../.github/workflows/build-docker.yml) for details. + +### Image Variants + +All variants are built from a single [Dockerfile](../src/labnow-open.Dockerfile) and differ only in which base image they layer on top of: + +| Image Tag | Base Image | What It Contains | +|---|---|---| +| `labnow-open-dev` | `developer:latest` | Minimal dev tools + Web Console | +| `labnow-open-data-science` | `data-science-dev:latest` | Full data science stack (Python, R, Jupyter) | +| `labnow-open-hermes` | `hermes:latest` | Hermes gateway + dashboard | +| `labnow-open-openclaw` | `openclaw:latest` | OpenClaw AI agent platform |