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/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml
index 7fe251c..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"
@@ -86,10 +87,21 @@ jobs:
--build-arg "PROFILE_LOCALIZE=default" --build-arg "BASE_IMG=openclaw:latest"
push_image labnow-open-openclaw
+ job-hermes:
+ name: "labnow-open-hermes"
+ runs-on: ubuntu-latest
+ 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=hermes:latest"
+ push_image labnow-open-hermes
+
## Sync all images in this build (listed by "names") to mirror registry.
job-sync-images:
- needs: ["job-developer", "job-data-science", "job-openclaw"]
+ needs: ["job-developer", "job-data-science", "job-openclaw", "job-hermes"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
diff --git a/.gitignore b/.gitignore
index 531f1b1..4a49d3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,3 +130,6 @@ dmypy.json
# Yarn cache
.yarn/
+
+# Local project documentation
+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
diff --git a/README.md b/README.md
index 9695058..58dcf63 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
[](https://quay.io/organization/labnow)
[](https://hub.docker.com/u/LabNow)
-Please generously STAR★ our project or donate to us!
+Please generously STAR⭐️ our project or donate to us!
[](https://github.com/LabNow-ai/labnow-open/stargazers)
Discussion and contributions are welcome:
@@ -14,20 +14,39 @@ Discussion and contributions are welcome:
[](https://github.com/LabNow-ai/labnow-open/issues)
[](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
+
+[](https://opensource.org/licenses/BSD-3-Clause)
+[](https://github.com/LabNow-ai/labnow-open/actions)
+[](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,也可以通过以下方式参与讨论:
+
+- [](https://discord.gg/kHUzgQxgbJ)
+- [](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 |
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 0a2a26a..90df8a6 100644
--- a/src/labnow-open-etc/Caddyfile
+++ b/src/labnow-open-etc/Caddyfile
@@ -24,78 +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
}
- # Default backend
- handle {
+ 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-web/src/App.jsx b/src/labnow-open-web/src/App.jsx
index 2c3fb82..26ae2a9 100644
--- a/src/labnow-open-web/src/App.jsx
+++ b/src/labnow-open-web/src/App.jsx
@@ -33,41 +33,46 @@ import { useSupervisorController } from "./hooks/useSupervisorController";
import { buildHomePath, buildWorkspacePath } from "./utils/runtimeBase";
const PROGRAM_DEFINITIONS = {
- caddy: { displayName: "Caddy Server", hidden: true },
- jupyter: {
+ "caddy": { displayName: "Caddy Server", hidden: true },
+ "jupyter": {
displayName: "JupyterLab",
- link: "/lab",
+ link: "/lab/",
logo: "logo-jupyter.svg",
- description:
- "Interactive notebooks for Python and data workflows. Great for exploration and quick experiments.",
+ description: "Interactive notebooks for Python and data workflows. Great for exploration and quick experiments."
},
- vscode: {
+ "vscode": {
displayName: "VS Code",
- link: "/vscode",
+ link: "/vscode/",
logo: "logo-vscode.svg",
- description:
- "A full-featured code editor in your browser. Edit files, run terminals, and manage projects.",
+ description: "A full-featured code editor in your browser. Edit files, run terminals, and manage projects."
},
- rserver: {
+ "rserver": {
displayName: "R-Studio",
- link: "/rserver",
+ link: "/rserver/",
logo: "logo-rserver.svg",
- description:
- "RStudio Server for R development and analysis. Build scripts, run models, and visualize results.",
+ description: "RStudio Server for R development and analysis. Build scripts, run models, and visualize results."
},
- rshiny: {
+ "rshiny": {
displayName: "R Shiny",
- link: "/rshiny",
+ link: "/rshiny/",
logo: "logo-rshiny.svg",
- description:
- "Run Shiny applications for interactive dashboards. Useful for sharing data apps with your team.",
+ description: "Run Shiny applications for interactive dashboards. Useful for sharing data apps with your team."
},
- openclaw: {
+ "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."
+ },
+ "openclaw": {
displayName: "OpenClaw",
link: "/openclaw/",
logo: "logo-openclaw.svg",
- description:
- "OpenClaw AI Agent platform. Build and deploy intelligent agents with natural language.",
+ description: "OpenClaw AI Agent platform. Build and deploy intelligent agents with natural language."
},
};
@@ -466,7 +471,7 @@ export default function App() {
title="labnow.ai"
>
- LabNow.ai
+ LabNow®
/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 \
- && chmod +x /usr/local/bin/start-supervisord.sh \
- ## components that are optional, only add them to supervisord if they exist
+ && 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) \
- ## optional agent components: openclaw and hermes
- && (type openclaw && printf "[program:openclaw]\ncommand=/usr/local/bin/start-openclaw.sh\nautostart=true\n" >> /etc/supervisord/supervisord.conf || true) \
- && (type hermes && printf "[program:hermes]\ncommand=/usr/local/bin/start-hermes.sh\nautostart=true\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) \
# cleanup of any temporary or cache files to keep the image size down
&& source /opt/utils/script-utils.sh && install__clean
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"