From 4f651d026e7301cba060156ba4bda7d54bd73c8c Mon Sep 17 00:00:00 2001 From: yy <56745951+lingdie@users.noreply.github.com> Date: Fri, 15 May 2026 10:33:54 +0800 Subject: [PATCH 1/9] Add Kylin and Anolis OS runtimes (#17) --- .../operating-systems/anolis/23.4/Dockerfile | 49 ++++++++++ .../operating-systems/anolis/23.4/build.sh | 42 ++++++++ .../kylin/v10-sp3/Dockerfile | 49 ++++++++++ .../operating-systems/kylin/v10-sp3/build.sh | 42 ++++++++ .../operating-systems/anolis/23.4/Dockerfile | 25 +++++ .../operating-systems/anolis/23.4/build.sh | 33 +++++++ .../23.4/project-template/README.en_US.md | 46 +++++++++ .../23.4/project-template/README.zh_CN.md | 46 +++++++++ .../23.4/project-template/entrypoint.sh | 32 +++++++ .../kylin/v10-sp3/Dockerfile | 25 +++++ .../operating-systems/kylin/v10-sp3/build.sh | 33 +++++++ .../v10-sp3/project-template/README.en_US.md | 46 +++++++++ .../v10-sp3/project-template/README.zh_CN.md | 46 +++++++++ .../v10-sp3/project-template/entrypoint.sh | 32 +++++++ tests/runtime-conformance/README.md | 2 + tests/runtime-conformance/run.sh | 6 ++ .../operating-systems/anolis/23.4/smoke.sh | 96 +++++++++++++++++++ .../operating-systems/kylin/v10-sp3/smoke.sh | 96 +++++++++++++++++++ tooling/scripts/cleanup.sh | 17 +++- tooling/scripts/configure-login.sh | 16 ++-- tooling/scripts/configure-user.sh | 13 ++- tooling/scripts/install-base-pkg-rpm.sh | 60 ++++++++++++ .../scripts/l10n/en_US/configure-locale.sh | 19 +++- .../scripts/l10n/zh_CN/configure-locale.sh | 19 +++- tooling/scripts/svc/configure-crond.sh | 8 +- tooling/scripts/svc/configure-sshd.sh | 8 +- 26 files changed, 883 insertions(+), 23 deletions(-) create mode 100644 base-images/operating-systems/anolis/23.4/Dockerfile create mode 100644 base-images/operating-systems/anolis/23.4/build.sh create mode 100644 base-images/operating-systems/kylin/v10-sp3/Dockerfile create mode 100644 base-images/operating-systems/kylin/v10-sp3/build.sh create mode 100644 runtime-images/operating-systems/anolis/23.4/Dockerfile create mode 100644 runtime-images/operating-systems/anolis/23.4/build.sh create mode 100644 runtime-images/operating-systems/anolis/23.4/project-template/README.en_US.md create mode 100644 runtime-images/operating-systems/anolis/23.4/project-template/README.zh_CN.md create mode 100644 runtime-images/operating-systems/anolis/23.4/project-template/entrypoint.sh create mode 100644 runtime-images/operating-systems/kylin/v10-sp3/Dockerfile create mode 100644 runtime-images/operating-systems/kylin/v10-sp3/build.sh create mode 100644 runtime-images/operating-systems/kylin/v10-sp3/project-template/README.en_US.md create mode 100644 runtime-images/operating-systems/kylin/v10-sp3/project-template/README.zh_CN.md create mode 100644 runtime-images/operating-systems/kylin/v10-sp3/project-template/entrypoint.sh create mode 100644 tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh create mode 100644 tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh create mode 100644 tooling/scripts/install-base-pkg-rpm.sh diff --git a/base-images/operating-systems/anolis/23.4/Dockerfile b/base-images/operating-systems/anolis/23.4/Dockerfile new file mode 100644 index 00000000..239b9b44 --- /dev/null +++ b/base-images/operating-systems/anolis/23.4/Dockerfile @@ -0,0 +1,49 @@ +# These ARGs can be overridden at build time to customize the image +ARG REGISTRY=ghcr.io +ARG TOOLING_REPO=labring-actions/devbox-tooling +ARG L10N=en_US +ARG TARGETARCH +ARG ARCH=${TARGETARCH:-amd64} +ARG DEFAULT_DEVBOX_USER=devbox +ARG ANOLIS_IMAGE=openanolis/anolisos:23.4 +# These ARGs are not recommended to be overridden at build time. +# Instead, update the Dockerfile directly for consistent builds, +# and release new versions as needed. +ARG BASE_TOOLS_VERSION=v0.0.1-alpha.1 + +FROM ${REGISTRY}/${TOOLING_REPO}/tooling:${BASE_TOOLS_VERSION} AS tooling +FROM ${ANOLIS_IMAGE} +ARG L10N +ARG TARGETARCH +ARG ARCH=${TARGETARCH:-amd64} +ARG DEFAULT_DEVBOX_USER +LABEL org.opencontainers.image.authors="The Devbox Authors" +# Define some environment variables +## BASE_TOOLS_DIR: Directory where base tools are installed +ENV BASE_TOOLS_DIR=/opt/base-tools +## L10N: Internationalization setting +ENV L10N=${L10N} +## ARCH: System architecture (from build-arg) +ENV ARCH=${ARCH} +## DEFAULT_DEVBOX_USER: Default user for the devbox environment +ENV DEFAULT_DEVBOX_USER=${DEFAULT_DEVBOX_USER} +## PROJECT_DIR: Default devbox project directory inside the container +ENV PROJECT_DIR=/home/devbox/project +## S6_STAGE2_HOOK: Hook script executed BEFORE s6-rc compilation +## This allows us to dynamically disable services based on DEVBOX_ENV +ENV S6_STAGE2_HOOK=/etc/s6-overlay-hook/pre-rc-init.d/pre-rc-init.sh +## S6_KILL_GRACETIME: Time to wait before forcefully killing all processes during shutdown +ENV S6_KILL_GRACETIME=500 + +# Copy tooling assets from the tooling stage +COPY --from=tooling ${BASE_TOOLS_DIR} ${BASE_TOOLS_DIR} +# Add build script and execute it +COPY build.sh /build.sh +RUN chmod +x /build.sh && \ + /build.sh && \ + rm -f /build.sh && \ + rm -rf ${BASE_TOOLS_DIR} +## Locale: generated during build, but also export at runtime so non-login processes use UTF-8 +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 +ENTRYPOINT [ "/init" ] diff --git a/base-images/operating-systems/anolis/23.4/build.sh b/base-images/operating-systems/anolis/23.4/build.sh new file mode 100644 index 00000000..8e01ac7c --- /dev/null +++ b/base-images/operating-systems/anolis/23.4/build.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Current BASE_TOOLS_DIR: $BASE_TOOLS_DIR" +echo "Current L10N: $L10N" +echo "Current ARCH: $ARCH" +echo "Current DEFAULT_DEVBOX_USER: $DEFAULT_DEVBOX_USER" + +chmod +x "$BASE_TOOLS_DIR/scripts/"*.sh + +# Install base packages for Anolis/RPM family +"$BASE_TOOLS_DIR/scripts/install-base-pkg-rpm.sh" + +# Install cron, s6, and the SDK server from the shared tooling scripts +"$BASE_TOOLS_DIR/scripts/install-crond.sh" +"$BASE_TOOLS_DIR/scripts/install-s6.sh" +"$BASE_TOOLS_DIR/scripts/install-sdk-server.sh" + +# Configure svc +"$BASE_TOOLS_DIR/scripts/configure-svc.sh" + +# Configure other utilities +"$BASE_TOOLS_DIR/scripts/configure-logrotate.sh" +"$BASE_TOOLS_DIR/scripts/configure-login.sh" + +# Configure localization (L10N) +"$BASE_TOOLS_DIR/scripts/configure-l10n.sh" + +# Configure user devbox +"$BASE_TOOLS_DIR/scripts/configure-user.sh" "$DEFAULT_DEVBOX_USER" + +# Install user-facing runtime docs (single source from the shared tooling bundle) +if [ -d "$BASE_TOOLS_DIR/docs" ]; then + install -d /usr/share/devbox/docs + cp "$BASE_TOOLS_DIR"/docs/README.s6-user-guide*.md /usr/share/devbox/docs/ + chmod 644 /usr/share/devbox/docs/README.s6-user-guide*.md +else + echo "No docs directory found in $BASE_TOOLS_DIR; skipping s6 user-guide install" +fi + +# Cleanup +"$BASE_TOOLS_DIR/scripts/cleanup.sh" diff --git a/base-images/operating-systems/kylin/v10-sp3/Dockerfile b/base-images/operating-systems/kylin/v10-sp3/Dockerfile new file mode 100644 index 00000000..721c0e40 --- /dev/null +++ b/base-images/operating-systems/kylin/v10-sp3/Dockerfile @@ -0,0 +1,49 @@ +# These ARGs can be overridden at build time to customize the image +ARG REGISTRY=ghcr.io +ARG TOOLING_REPO=labring-actions/devbox-tooling +ARG L10N=en_US +ARG TARGETARCH +ARG ARCH=${TARGETARCH:-amd64} +ARG DEFAULT_DEVBOX_USER=devbox +ARG KYLIN_IMAGE=macrosan/kylin:v10-sp3 +# These ARGs are not recommended to be overridden at build time. +# Instead, update the Dockerfile directly for consistent builds, +# and release new versions as needed. +ARG BASE_TOOLS_VERSION=v0.0.1-alpha.1 + +FROM ${REGISTRY}/${TOOLING_REPO}/tooling:${BASE_TOOLS_VERSION} AS tooling +FROM ${KYLIN_IMAGE} +ARG L10N +ARG TARGETARCH +ARG ARCH=${TARGETARCH:-amd64} +ARG DEFAULT_DEVBOX_USER +LABEL org.opencontainers.image.authors="The Devbox Authors" +# Define some environment variables +## BASE_TOOLS_DIR: Directory where base tools are installed +ENV BASE_TOOLS_DIR=/opt/base-tools +## L10N: Internationalization setting +ENV L10N=${L10N} +## ARCH: System architecture (from build-arg) +ENV ARCH=${ARCH} +## DEFAULT_DEVBOX_USER: Default user for the devbox environment +ENV DEFAULT_DEVBOX_USER=${DEFAULT_DEVBOX_USER} +## PROJECT_DIR: Default devbox project directory inside the container +ENV PROJECT_DIR=/home/devbox/project +## S6_STAGE2_HOOK: Hook script executed BEFORE s6-rc compilation +## This allows us to dynamically disable services based on DEVBOX_ENV +ENV S6_STAGE2_HOOK=/etc/s6-overlay-hook/pre-rc-init.d/pre-rc-init.sh +## S6_KILL_GRACETIME: Time to wait before forcefully killing all processes during shutdown +ENV S6_KILL_GRACETIME=500 + +# Copy tooling assets from the tooling stage +COPY --from=tooling ${BASE_TOOLS_DIR} ${BASE_TOOLS_DIR} +# Add build script and execute it +COPY build.sh /build.sh +RUN chmod +x /build.sh && \ + /build.sh && \ + rm -f /build.sh && \ + rm -rf ${BASE_TOOLS_DIR} +## Locale: generated during build, but also export at runtime so non-login processes use UTF-8 +ENV LANG=en_US.UTF-8 +ENV LC_ALL=en_US.UTF-8 +ENTRYPOINT [ "/init" ] diff --git a/base-images/operating-systems/kylin/v10-sp3/build.sh b/base-images/operating-systems/kylin/v10-sp3/build.sh new file mode 100644 index 00000000..ed4ff78c --- /dev/null +++ b/base-images/operating-systems/kylin/v10-sp3/build.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Current BASE_TOOLS_DIR: $BASE_TOOLS_DIR" +echo "Current L10N: $L10N" +echo "Current ARCH: $ARCH" +echo "Current DEFAULT_DEVBOX_USER: $DEFAULT_DEVBOX_USER" + +chmod +x "$BASE_TOOLS_DIR/scripts/"*.sh + +# Install base packages for Kylin/RPM family +"$BASE_TOOLS_DIR/scripts/install-base-pkg-rpm.sh" + +# Install cron, s6, and the SDK server from the shared tooling scripts +"$BASE_TOOLS_DIR/scripts/install-crond.sh" +"$BASE_TOOLS_DIR/scripts/install-s6.sh" +"$BASE_TOOLS_DIR/scripts/install-sdk-server.sh" + +# Configure svc +"$BASE_TOOLS_DIR/scripts/configure-svc.sh" + +# Configure other utilities +"$BASE_TOOLS_DIR/scripts/configure-logrotate.sh" +"$BASE_TOOLS_DIR/scripts/configure-login.sh" + +# Configure localization (L10N) +"$BASE_TOOLS_DIR/scripts/configure-l10n.sh" + +# Configure user devbox +"$BASE_TOOLS_DIR/scripts/configure-user.sh" "$DEFAULT_DEVBOX_USER" + +# Install user-facing runtime docs (single source from the shared tooling bundle) +if [ -d "$BASE_TOOLS_DIR/docs" ]; then + install -d /usr/share/devbox/docs + cp "$BASE_TOOLS_DIR"/docs/README.s6-user-guide*.md /usr/share/devbox/docs/ + chmod 644 /usr/share/devbox/docs/README.s6-user-guide*.md +else + echo "No docs directory found in $BASE_TOOLS_DIR; skipping s6 user-guide install" +fi + +# Cleanup +"$BASE_TOOLS_DIR/scripts/cleanup.sh" diff --git a/runtime-images/operating-systems/anolis/23.4/Dockerfile b/runtime-images/operating-systems/anolis/23.4/Dockerfile new file mode 100644 index 00000000..46051e71 --- /dev/null +++ b/runtime-images/operating-systems/anolis/23.4/Dockerfile @@ -0,0 +1,25 @@ +# These ARGs can be overridden at build time to customize the image +ARG REPO=labring-actions/devbox-base-images +ARG REGISTRY=ghcr.io +ARG L10N=en_US +ARG L10N_NORMALIZED=en-us +ARG DEFAULT_DEVBOX_USER=devbox + +# These ARGs are not recommended to be overridden at build time. +# Instead, update the Dockerfile directly for consistent builds, +# and release new versions as needed. +ARG OS_IMAGE_VERSION=v0.0.1-alpha.1-${L10N_NORMALIZED} + +FROM ${REGISTRY}/${REPO}/anolis-23.4:${OS_IMAGE_VERSION} +ARG L10N +ARG DEFAULT_DEVBOX_USER +ENV L10N=${L10N} +ENV PROJECT_TEMPLATE_DIR=/project-template +COPY ./project-template ${PROJECT_TEMPLATE_DIR} +COPY ./build.sh /build.sh +RUN chmod +x /build.sh && \ + /build.sh && \ + rm -f /build.sh && \ + rm -rf ${PROJECT_TEMPLATE_DIR} +# Set the working directory to the default devbox user's project directory +WORKDIR /home/${DEFAULT_DEVBOX_USER}/project diff --git a/runtime-images/operating-systems/anolis/23.4/build.sh b/runtime-images/operating-systems/anolis/23.4/build.sh new file mode 100644 index 00000000..590b98e8 --- /dev/null +++ b/runtime-images/operating-systems/anolis/23.4/build.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +L10N=${L10N:-en_US} +DEFAULT_DEVBOX_USER=${DEFAULT_DEVBOX_USER:-devbox} +PROJECT_TEMPLATE_DIR=${PROJECT_TEMPLATE_DIR:-/project-templates} +DOCS_DIR=${DOCS_DIR:-/usr/share/devbox/docs} + +if ! id -u "$DEFAULT_DEVBOX_USER" &>/dev/null; then + echo "User $DEFAULT_DEVBOX_USER does not exist" + exit 1 +fi + +TARGET_DIR="/home/$DEFAULT_DEVBOX_USER/project" +mkdir -p "$TARGET_DIR" + +if [ -f "$PROJECT_TEMPLATE_DIR/README.$L10N.md" ]; then + echo "README $PROJECT_TEMPLATE_DIR/README.$L10N.md exists. Copying to $TARGET_DIR/README.md" + cp "$PROJECT_TEMPLATE_DIR/README.$L10N.md" "$TARGET_DIR/README.md" +else + echo "README $PROJECT_TEMPLATE_DIR/README.$L10N.md does not exist. Skipping copy." +fi + +if [ -f "$DOCS_DIR/README.s6-user-guide.$L10N.md" ]; then + cp "$DOCS_DIR/README.s6-user-guide.$L10N.md" "$TARGET_DIR/README.s6-user-guide.md" +elif [ -f "$DOCS_DIR/README.s6-user-guide.en_US.md" ]; then + cp "$DOCS_DIR/README.s6-user-guide.en_US.md" "$TARGET_DIR/README.s6-user-guide.md" +fi + +cp "$PROJECT_TEMPLATE_DIR/"*.sh "$TARGET_DIR/" + +# Set ownership to default devbox user +chown -R "$DEFAULT_DEVBOX_USER:$DEFAULT_DEVBOX_USER" "$TARGET_DIR" diff --git a/runtime-images/operating-systems/anolis/23.4/project-template/README.en_US.md b/runtime-images/operating-systems/anolis/23.4/project-template/README.en_US.md new file mode 100644 index 00000000..75bad07f --- /dev/null +++ b/runtime-images/operating-systems/anolis/23.4/project-template/README.en_US.md @@ -0,0 +1,46 @@ +# AnolisOS 23.4 Runtime Template + +This template provides a minimal **operating-system runtime** based on Anolis OS 23.4. +Use it when you need a localized Linux base and full control of your language, framework, or application stack. + +## Runtime Summary + +- OS version: `Anolis OS 23.4` +- Base runtime image: `anolis-23.4` +- Entrypoint script: `entrypoint.sh` +- Default service port: `8080` + +## Template Files + +- `entrypoint.sh`: creates a static `index.html` and starts a lightweight HTTP server + +## Run in DevBox + +Run commands from `/home/devbox/project`. + +```bash +bash entrypoint.sh +``` + +Behavior: +- Uses `PORT` environment variable when provided, defaults to `8080`. +- Serves files from `/home/devbox/project/www`. +- Prefers `busybox httpd` and falls back to `python3 -m http.server` when that applet is unavailable. + +## Verify Service + +```bash +curl http://127.0.0.1:8080 +``` + +Expected output: + +```text +Hello, World! +``` + +## Customization + +- Replace `entrypoint.sh` with your own process startup script. +- Use `dnf` or `yum` to install application dependencies in this AnolisOS base. +- Align container exposed ports with your service port. diff --git a/runtime-images/operating-systems/anolis/23.4/project-template/README.zh_CN.md b/runtime-images/operating-systems/anolis/23.4/project-template/README.zh_CN.md new file mode 100644 index 00000000..e6075011 --- /dev/null +++ b/runtime-images/operating-systems/anolis/23.4/project-template/README.zh_CN.md @@ -0,0 +1,46 @@ +# AnolisOS 23.4 运行时模板 + +该模板提供一个基于 Anolis OS 23.4 的最小化**操作系统运行时**。 +适用于需要国产化 Linux 基础环境,并在其上自行安装语言、框架或业务依赖的场景。 + +## 运行时概览 + +- 系统版本:`Anolis OS 23.4` +- 基础运行时镜像:`anolis-23.4` +- 启动脚本:`entrypoint.sh` +- 默认服务端口:`8080` + +## 模板文件 + +- `entrypoint.sh`:生成静态 `index.html` 并启动轻量 HTTP 服务 + +## 在 DevBox 中运行 + +以下命令在 `/home/devbox/project` 目录执行。 + +```bash +bash entrypoint.sh +``` + +行为说明: +- 支持通过 `PORT` 环境变量覆盖端口,默认值为 `8080`。 +- 默认从 `/home/devbox/project/www` 目录提供静态内容。 +- 优先使用 `busybox httpd`,不可用时回退到 `python3 -m http.server`。 + +## 验证服务 + +```bash +curl http://127.0.0.1:8080 +``` + +预期输出: + +```text +Hello, World! +``` + +## 自定义建议 + +- 可将 `entrypoint.sh` 替换为你的进程启动脚本。 +- 使用 `dnf` 或 `yum` 在该 AnolisOS 基础镜像中安装业务依赖。 +- 保持容器暴露端口与服务监听端口一致。 diff --git a/runtime-images/operating-systems/anolis/23.4/project-template/entrypoint.sh b/runtime-images/operating-systems/anolis/23.4/project-template/entrypoint.sh new file mode 100644 index 00000000..b178e7fa --- /dev/null +++ b/runtime-images/operating-systems/anolis/23.4/project-template/entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail + +if [ "$(id -u)" -eq 0 ] && [ "${DEVBOX_ENTRYPOINT_AS_DEVBOX:-1}" = "1" ] && id devbox >/dev/null 2>&1; then + export DEVBOX_ENTRYPOINT_AS_DEVBOX=0 + SCRIPT_PATH=$(readlink -f "$0") + exec runuser -u devbox -- bash "$SCRIPT_PATH" "$@" +fi + +# Serve a simple "Hello, World!" page +PORT=${PORT:-8080} +PROJECT_DIR=${PROJECT_DIR:-/home/devbox/project} +ROOT_DIR="$PROJECT_DIR/www" +mkdir -p "$ROOT_DIR" + +cat >"$ROOT_DIR/index.html" <<'HTML' +Hello, World! +HTML + +echo "Starting HTTP server on port $PORT (serving $ROOT_DIR)" + +if command -v busybox >/dev/null 2>&1 && busybox --list 2>/dev/null | grep -qx httpd; then + exec busybox httpd -f -p "$PORT" -h "$ROOT_DIR" +fi + +if command -v python3 >/dev/null 2>&1; then + cd "$ROOT_DIR" + exec python3 -m http.server "$PORT" --bind 0.0.0.0 +fi + +echo "No supported HTTP server found (busybox httpd or python3 http.server)." >&2 +exit 1 diff --git a/runtime-images/operating-systems/kylin/v10-sp3/Dockerfile b/runtime-images/operating-systems/kylin/v10-sp3/Dockerfile new file mode 100644 index 00000000..16eacc83 --- /dev/null +++ b/runtime-images/operating-systems/kylin/v10-sp3/Dockerfile @@ -0,0 +1,25 @@ +# These ARGs can be overridden at build time to customize the image +ARG REPO=labring-actions/devbox-base-images +ARG REGISTRY=ghcr.io +ARG L10N=en_US +ARG L10N_NORMALIZED=en-us +ARG DEFAULT_DEVBOX_USER=devbox + +# These ARGs are not recommended to be overridden at build time. +# Instead, update the Dockerfile directly for consistent builds, +# and release new versions as needed. +ARG OS_IMAGE_VERSION=v0.0.1-alpha.1-${L10N_NORMALIZED} + +FROM ${REGISTRY}/${REPO}/kylin-v10-sp3:${OS_IMAGE_VERSION} +ARG L10N +ARG DEFAULT_DEVBOX_USER +ENV L10N=${L10N} +ENV PROJECT_TEMPLATE_DIR=/project-template +COPY ./project-template ${PROJECT_TEMPLATE_DIR} +COPY ./build.sh /build.sh +RUN chmod +x /build.sh && \ + /build.sh && \ + rm -f /build.sh && \ + rm -rf ${PROJECT_TEMPLATE_DIR} +# Set the working directory to the default devbox user's project directory +WORKDIR /home/${DEFAULT_DEVBOX_USER}/project diff --git a/runtime-images/operating-systems/kylin/v10-sp3/build.sh b/runtime-images/operating-systems/kylin/v10-sp3/build.sh new file mode 100644 index 00000000..590b98e8 --- /dev/null +++ b/runtime-images/operating-systems/kylin/v10-sp3/build.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +set -euo pipefail + +L10N=${L10N:-en_US} +DEFAULT_DEVBOX_USER=${DEFAULT_DEVBOX_USER:-devbox} +PROJECT_TEMPLATE_DIR=${PROJECT_TEMPLATE_DIR:-/project-templates} +DOCS_DIR=${DOCS_DIR:-/usr/share/devbox/docs} + +if ! id -u "$DEFAULT_DEVBOX_USER" &>/dev/null; then + echo "User $DEFAULT_DEVBOX_USER does not exist" + exit 1 +fi + +TARGET_DIR="/home/$DEFAULT_DEVBOX_USER/project" +mkdir -p "$TARGET_DIR" + +if [ -f "$PROJECT_TEMPLATE_DIR/README.$L10N.md" ]; then + echo "README $PROJECT_TEMPLATE_DIR/README.$L10N.md exists. Copying to $TARGET_DIR/README.md" + cp "$PROJECT_TEMPLATE_DIR/README.$L10N.md" "$TARGET_DIR/README.md" +else + echo "README $PROJECT_TEMPLATE_DIR/README.$L10N.md does not exist. Skipping copy." +fi + +if [ -f "$DOCS_DIR/README.s6-user-guide.$L10N.md" ]; then + cp "$DOCS_DIR/README.s6-user-guide.$L10N.md" "$TARGET_DIR/README.s6-user-guide.md" +elif [ -f "$DOCS_DIR/README.s6-user-guide.en_US.md" ]; then + cp "$DOCS_DIR/README.s6-user-guide.en_US.md" "$TARGET_DIR/README.s6-user-guide.md" +fi + +cp "$PROJECT_TEMPLATE_DIR/"*.sh "$TARGET_DIR/" + +# Set ownership to default devbox user +chown -R "$DEFAULT_DEVBOX_USER:$DEFAULT_DEVBOX_USER" "$TARGET_DIR" diff --git a/runtime-images/operating-systems/kylin/v10-sp3/project-template/README.en_US.md b/runtime-images/operating-systems/kylin/v10-sp3/project-template/README.en_US.md new file mode 100644 index 00000000..810baf14 --- /dev/null +++ b/runtime-images/operating-systems/kylin/v10-sp3/project-template/README.en_US.md @@ -0,0 +1,46 @@ +# Kylin V10 SP3 Runtime Template + +This template provides a minimal **operating-system runtime** based on Kylin Linux Advanced Server V10 SP3. +Use it when you need a localized Linux base and full control of your language, framework, or application stack. + +## Runtime Summary + +- OS version: `Kylin Linux Advanced Server V10 SP3` +- Base runtime image: `kylin-v10-sp3` +- Entrypoint script: `entrypoint.sh` +- Default service port: `8080` + +## Template Files + +- `entrypoint.sh`: creates a static `index.html` and starts a lightweight HTTP server + +## Run in DevBox + +Run commands from `/home/devbox/project`. + +```bash +bash entrypoint.sh +``` + +Behavior: +- Uses `PORT` environment variable when provided, defaults to `8080`. +- Serves files from `/home/devbox/project/www`. +- Prefers `busybox httpd` and falls back to `python3 -m http.server` when that applet is unavailable. + +## Verify Service + +```bash +curl http://127.0.0.1:8080 +``` + +Expected output: + +```text +Hello, World! +``` + +## Customization + +- Replace `entrypoint.sh` with your own process startup script. +- Use `dnf` or `yum` to install application dependencies in this Kylin base. +- Align container exposed ports with your service port. diff --git a/runtime-images/operating-systems/kylin/v10-sp3/project-template/README.zh_CN.md b/runtime-images/operating-systems/kylin/v10-sp3/project-template/README.zh_CN.md new file mode 100644 index 00000000..3483e9a9 --- /dev/null +++ b/runtime-images/operating-systems/kylin/v10-sp3/project-template/README.zh_CN.md @@ -0,0 +1,46 @@ +# Kylin V10 SP3 运行时模板 + +该模板提供一个基于 Kylin Linux Advanced Server V10 SP3 的最小化**操作系统运行时**。 +适用于需要国产化 Linux 基础环境,并在其上自行安装语言、框架或业务依赖的场景。 + +## 运行时概览 + +- 系统版本:`Kylin Linux Advanced Server V10 SP3` +- 基础运行时镜像:`kylin-v10-sp3` +- 启动脚本:`entrypoint.sh` +- 默认服务端口:`8080` + +## 模板文件 + +- `entrypoint.sh`:生成静态 `index.html` 并启动轻量 HTTP 服务 + +## 在 DevBox 中运行 + +以下命令在 `/home/devbox/project` 目录执行。 + +```bash +bash entrypoint.sh +``` + +行为说明: +- 支持通过 `PORT` 环境变量覆盖端口,默认值为 `8080`。 +- 默认从 `/home/devbox/project/www` 目录提供静态内容。 +- 优先使用 `busybox httpd`,不可用时回退到 `python3 -m http.server`。 + +## 验证服务 + +```bash +curl http://127.0.0.1:8080 +``` + +预期输出: + +```text +Hello, World! +``` + +## 自定义建议 + +- 可将 `entrypoint.sh` 替换为你的进程启动脚本。 +- 使用 `dnf` 或 `yum` 在该 Kylin 基础镜像中安装业务依赖。 +- 保持容器暴露端口与服务监听端口一致。 diff --git a/runtime-images/operating-systems/kylin/v10-sp3/project-template/entrypoint.sh b/runtime-images/operating-systems/kylin/v10-sp3/project-template/entrypoint.sh new file mode 100644 index 00000000..b178e7fa --- /dev/null +++ b/runtime-images/operating-systems/kylin/v10-sp3/project-template/entrypoint.sh @@ -0,0 +1,32 @@ +#!/bin/bash +set -euo pipefail + +if [ "$(id -u)" -eq 0 ] && [ "${DEVBOX_ENTRYPOINT_AS_DEVBOX:-1}" = "1" ] && id devbox >/dev/null 2>&1; then + export DEVBOX_ENTRYPOINT_AS_DEVBOX=0 + SCRIPT_PATH=$(readlink -f "$0") + exec runuser -u devbox -- bash "$SCRIPT_PATH" "$@" +fi + +# Serve a simple "Hello, World!" page +PORT=${PORT:-8080} +PROJECT_DIR=${PROJECT_DIR:-/home/devbox/project} +ROOT_DIR="$PROJECT_DIR/www" +mkdir -p "$ROOT_DIR" + +cat >"$ROOT_DIR/index.html" <<'HTML' +Hello, World! +HTML + +echo "Starting HTTP server on port $PORT (serving $ROOT_DIR)" + +if command -v busybox >/dev/null 2>&1 && busybox --list 2>/dev/null | grep -qx httpd; then + exec busybox httpd -f -p "$PORT" -h "$ROOT_DIR" +fi + +if command -v python3 >/dev/null 2>&1; then + cd "$ROOT_DIR" + exec python3 -m http.server "$PORT" --bind 0.0.0.0 +fi + +echo "No supported HTTP server found (busybox httpd or python3 http.server)." >&2 +exit 1 diff --git a/tests/runtime-conformance/README.md b/tests/runtime-conformance/README.md index 7098014d..dda9c426 100644 --- a/tests/runtime-conformance/README.md +++ b/tests/runtime-conformance/README.md @@ -31,7 +31,9 @@ Runtime-specific checks: | Runtime | Checks | | --- | --- | +| `operating-systems/anolis/23.4` | Anolis identity, busybox, localized README, root/devbox entrypoint order | | `operating-systems/debian/12.6` | Debian identity, busybox, localized README, root/devbox entrypoint order | +| `operating-systems/kylin/v10-sp3` | Kylin identity, busybox, localized README, root/devbox entrypoint order | | `operating-systems/ubuntu/22.04` | Ubuntu identity, busybox, localized README, root/devbox entrypoint order | | `operating-systems/ubuntu-cuda/12.4.1` | Ubuntu identity, CUDA compiler/runtime presence, busybox, localized README, root/devbox entrypoint order | | `languages/c/gcc-12.2.0` | GCC version, C template, compile/server smoke, root/devbox entrypoint order | diff --git a/tests/runtime-conformance/run.sh b/tests/runtime-conformance/run.sh index 3072308b..276d52e2 100755 --- a/tests/runtime-conformance/run.sh +++ b/tests/runtime-conformance/run.sh @@ -572,12 +572,18 @@ check_sandbox_runtime() { check_runtime_specifics() { log "check runtime-specific contract" case "$RUNTIME_PATH" in + operating-systems/anolis/23.4) + check_os_runtime anolis + ;; operating-systems/debian/12.6) check_os_runtime debian ;; operating-systems/ubuntu/22.04) check_os_runtime ubuntu ;; + operating-systems/kylin/v10-sp3) + check_os_runtime kylin + ;; operating-systems/ubuntu-cuda/12.4.1) check_os_runtime ubuntu check_cuda_runtime diff --git a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh new file mode 100644 index 00000000..71568939 --- /dev/null +++ b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh @@ -0,0 +1,96 @@ +#!/bin/bash +set -eu + +project_dir=/home/devbox/project + +if [ ! -d "$project_dir" ]; then + echo "Missing project dir: $project_dir" >&2 + exit 1 +fi + +# load profile env (best effort) +set +u +[ -f /etc/profile ] && . /etc/profile || true +if [ -d /etc/profile.d ]; then + for f in /etc/profile.d/*.sh; do + [ -r "$f" ] && . "$f" || true + done +fi +[ -f /home/devbox/.bashrc ] && . /home/devbox/.bashrc || true +set -u + +if [ "${SMOKE_DEBUG:-}" = "1" ]; then + echo "SMOKE_DEBUG=1" + echo "user=$(id -un) uid=$(id -u) gid=$(id -g)" + echo "HOME=$HOME" + echo "SHELL=${SHELL:-}" + echo "PATH=$PATH" + for cmd in dnf yum rpm busybox bash sudo curl wget git python3; do + if command -v "$cmd" >/dev/null 2>&1; then + echo "cmd:$cmd=$(command -v "$cmd")" + else + echo "cmd:$cmd=missing" + fi + done +fi + +if ! grep -qi anolis /etc/os-release; then + echo "Expected Anolis in /etc/os-release" >&2 + exit 1 +fi + +if ! id devbox >/dev/null 2>&1; then + echo "User devbox not found" >&2 + exit 1 +fi + +if [ ! -f "$project_dir/README.md" ]; then + echo "Missing README.md in $project_dir" >&2 + exit 1 +fi + +if [ ! -f "$project_dir/entrypoint.sh" ]; then + echo "Missing entrypoint.sh in $project_dir" >&2 + exit 1 +fi + +if ! command -v busybox >/dev/null 2>&1; then + echo "busybox not found" >&2 + exit 1 +fi + +if ! command -v dnf >/dev/null 2>&1 && ! command -v yum >/dev/null 2>&1; then + echo "dnf/yum not found" >&2 + exit 1 +fi + +if [ ! -x /usr/sbin/sshd ]; then + echo "sshd not found" >&2 + exit 1 +fi + +# entrypoint smoke +entrypoint="$project_dir/entrypoint.sh" +if [ ! -f "$entrypoint" ]; then + echo "Missing entrypoint.sh in $project_dir" >&2 + exit 1 +fi + +if ! command -v bash >/dev/null 2>&1; then + echo "bash not found" >&2 + exit 1 +fi + +( cd "$project_dir" && bash "$entrypoint" ) >/tmp/entrypoint.log 2>&1 & +pid=$! +sleep 3 +if ! kill -0 "$pid" >/dev/null 2>&1; then + echo "entrypoint exited early" >&2 + echo "---- entrypoint log ----" >&2 + cat /tmp/entrypoint.log >&2 || true + exit 1 +fi +kill "$pid" >/dev/null 2>&1 || true +wait "$pid" >/dev/null 2>&1 || true + +echo "ok" diff --git a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh new file mode 100644 index 00000000..fb25e996 --- /dev/null +++ b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh @@ -0,0 +1,96 @@ +#!/bin/bash +set -eu + +project_dir=/home/devbox/project + +if [ ! -d "$project_dir" ]; then + echo "Missing project dir: $project_dir" >&2 + exit 1 +fi + +# load profile env (best effort) +set +u +[ -f /etc/profile ] && . /etc/profile || true +if [ -d /etc/profile.d ]; then + for f in /etc/profile.d/*.sh; do + [ -r "$f" ] && . "$f" || true + done +fi +[ -f /home/devbox/.bashrc ] && . /home/devbox/.bashrc || true +set -u + +if [ "${SMOKE_DEBUG:-}" = "1" ]; then + echo "SMOKE_DEBUG=1" + echo "user=$(id -un) uid=$(id -u) gid=$(id -g)" + echo "HOME=$HOME" + echo "SHELL=${SHELL:-}" + echo "PATH=$PATH" + for cmd in dnf yum rpm busybox bash sudo curl wget git; do + if command -v "$cmd" >/dev/null 2>&1; then + echo "cmd:$cmd=$(command -v "$cmd")" + else + echo "cmd:$cmd=missing" + fi + done +fi + +if ! grep -qi kylin /etc/os-release; then + echo "Expected Kylin in /etc/os-release" >&2 + exit 1 +fi + +if ! id devbox >/dev/null 2>&1; then + echo "User devbox not found" >&2 + exit 1 +fi + +if [ ! -f "$project_dir/README.md" ]; then + echo "Missing README.md in $project_dir" >&2 + exit 1 +fi + +if [ ! -f "$project_dir/entrypoint.sh" ]; then + echo "Missing entrypoint.sh in $project_dir" >&2 + exit 1 +fi + +if ! command -v busybox >/dev/null 2>&1; then + echo "busybox not found" >&2 + exit 1 +fi + +if ! command -v dnf >/dev/null 2>&1 && ! command -v yum >/dev/null 2>&1; then + echo "dnf/yum not found" >&2 + exit 1 +fi + +if [ ! -x /usr/sbin/sshd ]; then + echo "sshd not found" >&2 + exit 1 +fi + +# entrypoint smoke +entrypoint="$project_dir/entrypoint.sh" +if [ ! -f "$entrypoint" ]; then + echo "Missing entrypoint.sh in $project_dir" >&2 + exit 1 +fi + +if ! command -v bash >/dev/null 2>&1; then + echo "bash not found" >&2 + exit 1 +fi + +( cd "$project_dir" && bash "$entrypoint" ) >/tmp/entrypoint.log 2>&1 & +pid=$! +sleep 3 +if ! kill -0 "$pid" >/dev/null 2>&1; then + echo "entrypoint exited early" >&2 + echo "---- entrypoint log ----" >&2 + cat /tmp/entrypoint.log >&2 || true + exit 1 +fi +kill "$pid" >/dev/null 2>&1 || true +wait "$pid" >/dev/null 2>&1 || true + +echo "ok" diff --git a/tooling/scripts/cleanup.sh b/tooling/scripts/cleanup.sh index 37db5ea8..10ad6282 100644 --- a/tooling/scripts/cleanup.sh +++ b/tooling/scripts/cleanup.sh @@ -1,7 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -# Remove apt cache to reduce image size -apt-get clean && rm -rf /var/lib/apt/lists/* + +# Remove package manager caches to reduce image size +if command -v apt-get >/dev/null 2>&1; then + apt-get clean + rm -rf /var/lib/apt/lists/* +fi +if command -v dnf >/dev/null 2>&1; then + dnf clean all + rm -rf /var/cache/dnf +elif command -v yum >/dev/null 2>&1; then + yum clean all + rm -rf /var/cache/yum +fi # Clear bash history rm -rf /root/.bash_history @@ -9,4 +20,4 @@ rm -rf /root/.bash_history # Remove log files and temporary files find /var/log -type f -delete find /tmp -type f -delete -find /var/tmp -type f -delete \ No newline at end of file +find /var/tmp -type f -delete diff --git a/tooling/scripts/configure-login.sh b/tooling/scripts/configure-login.sh index 36bffc8b..ec3cfcbe 100644 --- a/tooling/scripts/configure-login.sh +++ b/tooling/scripts/configure-login.sh @@ -1,21 +1,25 @@ #!/usr/bin/env bash set -euo pipefail +UTMP_GROUP=utmp +if ! getent group "$UTMP_GROUP" >/dev/null 2>&1; then + UTMP_GROUP=root +fi # Override wtmp rotation -cat > /etc/logrotate.d/wtmp <<'EOF' +cat > /etc/logrotate.d/wtmp < /etc/logrotate.d/btmp <<'EOF' +cat > /etc/logrotate.d/btmp < /run/utmp chmod 664 /run/utmp - chown root:utmp /run/utmp -fi \ No newline at end of file + chown root:"$UTMP_GROUP" /run/utmp +fi diff --git a/tooling/scripts/configure-user.sh b/tooling/scripts/configure-user.sh index f3a4679e..3adc7fdc 100644 --- a/tooling/scripts/configure-user.sh +++ b/tooling/scripts/configure-user.sh @@ -2,6 +2,7 @@ set -euo pipefail DEFAULT_USER=${1:-devbox} +ADMIN_GROUP=sudo # Add user devbox if id -u "$DEFAULT_USER" &>/dev/null; then echo "User $DEFAULT_USER already exists" @@ -20,8 +21,16 @@ else SHELL_PATH=/bin/sh fi useradd -m -s "$SHELL_PATH" "$DEFAULT_USER" -# Add user devbox to sudoers with NOPASSWD -usermod -aG sudo "$DEFAULT_USER" && echo "$DEFAULT_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers +# Add user devbox to the distro's admin group and sudoers with NOPASSWD +if ! getent group "$ADMIN_GROUP" >/dev/null 2>&1; then + if getent group wheel >/dev/null 2>&1; then + ADMIN_GROUP=wheel + else + groupadd "$ADMIN_GROUP" + fi +fi +usermod -aG "$ADMIN_GROUP" "$DEFAULT_USER" +echo "$DEFAULT_USER ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers # Change the password of user devbox # The password is randomly generated and not logged or stored for security reasons. # SSH key-based authentication is required, as password authentication is disabled in sshd_config. diff --git a/tooling/scripts/install-base-pkg-rpm.sh b/tooling/scripts/install-base-pkg-rpm.sh new file mode 100644 index 00000000..b443f3dd --- /dev/null +++ b/tooling/scripts/install-base-pkg-rpm.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +if command -v dnf >/dev/null 2>&1; then + PM=dnf +elif command -v yum >/dev/null 2>&1; then + PM=yum +else + echo "Neither dnf nor yum is available" >&2 + exit 1 +fi + +package_available() { + "$PM" list --showduplicates "$1" >/dev/null 2>&1 +} + +LOCALE_PACKAGES=( + glibc-locale-source + langpacks-en +) + +if package_available glibc-langpack-en; then + LOCALE_PACKAGES+=(glibc-langpack-en) +else + LOCALE_PACKAGES+=(glibc-all-langpacks) +fi + +if [ "${L10N:-en_US}" = "zh_CN" ]; then + LOCALE_PACKAGES+=(langpacks-zh_CN) +fi + +"$PM" install -y \ + bash \ + busybox \ + ca-certificates \ + cronie \ + curl \ + diffutils \ + findutils \ + git \ + gzip \ + iproute \ + logrotate \ + openssh-clients \ + openssh-server \ + openssl \ + passwd \ + procps-ng \ + shadow \ + sudo \ + tar \ + util-linux \ + vim-enhanced \ + wget \ + which \ + xz \ + "${LOCALE_PACKAGES[@]}" + +"$PM" clean all +rm -rf /var/cache/dnf /var/cache/yum diff --git a/tooling/scripts/l10n/en_US/configure-locale.sh b/tooling/scripts/l10n/en_US/configure-locale.sh index eb9a924f..24a63b52 100644 --- a/tooling/scripts/l10n/en_US/configure-locale.sh +++ b/tooling/scripts/l10n/en_US/configure-locale.sh @@ -1,7 +1,18 @@ #!/usr/bin/env bash set -euo pipefail -echo "LC_ALL=en_US.UTF-8" >> /etc/environment -echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen -echo "LANG=en_US.UTF-8" > /etc/locale.conf -locale-gen en_US.UTF-8 \ No newline at end of file +LOCALE=en_US.UTF-8 + +echo "LC_ALL=$LOCALE" >> /etc/environment +echo "$LOCALE UTF-8" >> /etc/locale.gen +echo "LANG=$LOCALE" > /etc/locale.conf + +if locale -a 2>/dev/null | grep -Eqi '^(en_US\.utf8|en_US\.UTF-8)$'; then + echo "$LOCALE already available" +elif command -v locale-gen >/dev/null 2>&1; then + locale-gen "$LOCALE" +elif command -v localedef >/dev/null 2>&1; then + localedef -i en_US -f UTF-8 "$LOCALE" || echo "Unable to generate $LOCALE with localedef; continuing" +else + echo "No locale generator found; continuing" +fi diff --git a/tooling/scripts/l10n/zh_CN/configure-locale.sh b/tooling/scripts/l10n/zh_CN/configure-locale.sh index f4d2074f..732a7653 100644 --- a/tooling/scripts/l10n/zh_CN/configure-locale.sh +++ b/tooling/scripts/l10n/zh_CN/configure-locale.sh @@ -2,7 +2,18 @@ set -euo pipefail # Currently, we set locale to en_US.UTF-8 even in zh_CN images -echo "LC_ALL=en_US.UTF-8" >> /etc/environment -echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen -echo "LANG=en_US.UTF-8" > /etc/locale.conf -locale-gen en_US.UTF-8 \ No newline at end of file +LOCALE=en_US.UTF-8 + +echo "LC_ALL=$LOCALE" >> /etc/environment +echo "$LOCALE UTF-8" >> /etc/locale.gen +echo "LANG=$LOCALE" > /etc/locale.conf + +if locale -a 2>/dev/null | grep -Eqi '^(en_US\.utf8|en_US\.UTF-8)$'; then + echo "$LOCALE already available" +elif command -v locale-gen >/dev/null 2>&1; then + locale-gen "$LOCALE" +elif command -v localedef >/dev/null 2>&1; then + localedef -i en_US -f UTF-8 "$LOCALE" || echo "Unable to generate $LOCALE with localedef; continuing" +else + echo "No locale generator found; continuing" +fi diff --git a/tooling/scripts/svc/configure-crond.sh b/tooling/scripts/svc/configure-crond.sh index 94f52181..6a614e5d 100644 --- a/tooling/scripts/svc/configure-crond.sh +++ b/tooling/scripts/svc/configure-crond.sh @@ -4,6 +4,10 @@ set -euo pipefail BASE_TOOLS_DIR=${BASE_TOOLS_DIR:-/opt/base-tools} ROOT_DIR=$BASE_TOOLS_DIR/scripts/svc source $ROOT_DIR/common.sh +LOG_GROUP=nogroup +if ! getent group "$LOG_GROUP" >/dev/null 2>&1; then + LOG_GROUP=nobody +fi # crond service make_longrun crond /usr/sbin/supercronic /etc/crontab touch "$S6_DIR/crond/dependencies.d/startup" @@ -18,7 +22,7 @@ echo 'crond-pipeline' > "$S6_DIR/crond-log/pipeline-name" # crond log preparation service make_oneshot_up crond-log-prepare \ 'if { mkdir -p /var/log/crond }' \ - 'if { chown nobody:nogroup /var/log/crond }' \ + "if { chown nobody:${LOG_GROUP} /var/log/crond }" \ 'chmod 02755 /var/log/crond' touch "$S6_DIR/crond-log-prepare/dependencies.d/base" @@ -33,4 +37,4 @@ for f in \ [ -f "$f" ] && chmod 700 "$f" || true done -echo "crond services ensured." >&2 \ No newline at end of file +echo "crond services ensured." >&2 diff --git a/tooling/scripts/svc/configure-sshd.sh b/tooling/scripts/svc/configure-sshd.sh index 68463c2c..5358926b 100644 --- a/tooling/scripts/svc/configure-sshd.sh +++ b/tooling/scripts/svc/configure-sshd.sh @@ -4,6 +4,10 @@ set -euo pipefail BASE_TOOLS_DIR=${BASE_TOOLS_DIR:-/opt/base-tools} ROOT_DIR=$BASE_TOOLS_DIR/scripts/svc source $ROOT_DIR/common.sh +LOG_GROUP=nogroup +if ! getent group "$LOG_GROUP" >/dev/null 2>&1; then + LOG_GROUP=nobody +fi # Configure sshd SSHD_CONFIG=/etc/ssh/sshd_config @@ -43,7 +47,7 @@ echo 'sshd-pipeline' > "$S6_DIR/sshd-log/pipeline-name" # Prepare sshd log directory service make_oneshot_up sshd-log-prepare \ 'if { mkdir -p /var/log/sshd }' \ - 'if { chown nobody:nogroup /var/log/sshd }' \ + "if { chown nobody:${LOG_GROUP} /var/log/sshd }" \ 'chmod 02755 /var/log/sshd' touch "$S6_DIR/sshd-log-prepare/dependencies.d/base" @@ -58,4 +62,4 @@ for f in \ [ -f "$f" ] && chmod 700 "$f" || true done -echo "sshd services ensured." >&2 \ No newline at end of file +echo "sshd services ensured." >&2 From b33a88f51c692199a2d850c39addcc28d99f0bb1 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 11:15:12 +0800 Subject: [PATCH 2/9] Fix sshd host key generation --- tooling/scripts/svc/configure-sshd.sh | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tooling/scripts/svc/configure-sshd.sh b/tooling/scripts/svc/configure-sshd.sh index 5358926b..e5f1fbfe 100644 --- a/tooling/scripts/svc/configure-sshd.sh +++ b/tooling/scripts/svc/configure-sshd.sh @@ -35,6 +35,24 @@ mkdir -p /run/sshd && chmod 755 /run/sshd # sshd service make_longrun sshd /usr/sbin/sshd -D -e +cat > "$S6_DIR/sshd/run" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +exec 2>&1 + +mkdir -p /run/sshd +chmod 755 /run/sshd + +if ! ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1; then + if ! command -v ssh-keygen >/dev/null 2>&1; then + echo "ssh-keygen is required to generate sshd host keys" >&2 + exit 1 + fi + ssh-keygen -A +fi + +exec /usr/sbin/sshd -D -e +EOF touch "$S6_DIR/sshd/dependencies.d/startup" echo 'sshd-log' > "$S6_DIR/sshd/producer-for" From af239346adbf57c0bea3e506bfc848bc559978d0 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 11:45:38 +0800 Subject: [PATCH 3/9] Fix sshd AuthorizedKeysFile override --- tooling/scripts/svc/configure-sshd.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tooling/scripts/svc/configure-sshd.sh b/tooling/scripts/svc/configure-sshd.sh index e5f1fbfe..ea180283 100644 --- a/tooling/scripts/svc/configure-sshd.sh +++ b/tooling/scripts/svc/configure-sshd.sh @@ -15,8 +15,8 @@ set_sshd_config() { local key value key="$(echo "$1" | awk '{print $1}')" value="$(echo "$1" | cut -d' ' -f2-)" - if grep -q "^$key " "$SSHD_CONFIG"; then - sed -i "s|^$key .*|$key $value|" "$SSHD_CONFIG" + if grep -Eq "^[[:space:]]*$key[[:space:]]+" "$SSHD_CONFIG"; then + sed -i -E "s|^[[:space:]]*$key[[:space:]]+.*|$key $value|" "$SSHD_CONFIG" else echo "$key $value" >> "$SSHD_CONFIG" fi From 77657afc8ecfe18a0a241e1b06db752d766ba957 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 12:14:15 +0800 Subject: [PATCH 4/9] Fix Kylin VS Code Server prerequisites --- .../operating-systems/kylin/v10-sp3/build.sh | 38 +++++++++++++++++++ .../operating-systems/kylin/v10-sp3/smoke.sh | 11 ++++++ tooling/scripts/install-base-pkg-rpm.sh | 1 + 3 files changed, 50 insertions(+) diff --git a/base-images/operating-systems/kylin/v10-sp3/build.sh b/base-images/operating-systems/kylin/v10-sp3/build.sh index ed4ff78c..3e02f35a 100644 --- a/base-images/operating-systems/kylin/v10-sp3/build.sh +++ b/base-images/operating-systems/kylin/v10-sp3/build.sh @@ -11,6 +11,44 @@ chmod +x "$BASE_TOOLS_DIR/scripts/"*.sh # Install base packages for Kylin/RPM family "$BASE_TOOLS_DIR/scripts/install-base-pkg-rpm.sh" +# Kylin V10 SP3 ships glibc 2.28, which meets the VS Code Server Linux +# prerequisite, but its GCC 7 libstdc++ only provides GLIBCXX_3.4.24. +# Install an Anolis 8 libstdc++ build with GLIBCXX_3.4.25 while keeping the +# system glibc unchanged. +tmp_dir="$(mktemp -d)" +trap 'rm -rf "$tmp_dir"' EXIT +case "${ARCH:-amd64}" in + amd64) + rpm_arch=x86_64 + libstdcxx_rpm_sha256="1cdcd031a575525b43c2787f750a855228433e982a7ddffc9d12d01f73f5ca68" + ;; + arm64) + rpm_arch=aarch64 + libstdcxx_rpm_sha256="412632fe27bf8ab264b8b4544a466225adc59bcb7cc30dce2f08f9630073b18c" + ;; + *) + echo "Unsupported ARCH for Kylin libstdc++ compatibility package: ${ARCH:-}" >&2 + exit 1 + ;; +esac +libstdcxx_rpm_url="https://mirrors.openanolis.cn/anolis/8/BaseOS/${rpm_arch}/os/Packages/libstdc++-8.5.0-24.0.1.an8.${rpm_arch}.rpm" +libstdcxx_rpm="$tmp_dir/libstdc++.rpm" +curl -fsSL "$libstdcxx_rpm_url" -o "$libstdcxx_rpm" +echo "$libstdcxx_rpm_sha256 $libstdcxx_rpm" | sha256sum -c - +( + cd "$tmp_dir" + rpm2cpio "$libstdcxx_rpm" | cpio -idm --quiet ./usr/lib64/libstdc++.so.6 ./usr/lib64/libstdc++.so.6.0.25 +) +install -m 0755 "$tmp_dir/usr/lib64/libstdc++.so.6.0.25" /usr/lib64/libstdc++.so.6.0.25 +ln -sf libstdc++.so.6.0.25 /usr/lib64/libstdc++.so.6 +ldconfig +if ! grep -ao 'GLIBCXX_3\.4\.25' /usr/lib64/libstdc++.so.6 >/dev/null 2>&1; then + echo "libstdc++ does not provide GLIBCXX_3.4.25" >&2 + exit 1 +fi +rm -rf "$tmp_dir" +trap - EXIT + # Install cron, s6, and the SDK server from the shared tooling scripts "$BASE_TOOLS_DIR/scripts/install-crond.sh" "$BASE_TOOLS_DIR/scripts/install-s6.sh" diff --git a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh index fb25e996..09b765bc 100644 --- a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh +++ b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh @@ -69,6 +69,17 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +glibc_version="$(ldd --version | head -n1 | grep -oE '[0-9]+[.][0-9]+' | tail -n1)" +if ! awk -v version="$glibc_version" 'BEGIN { split(version, v, "."); exit !((v[1] > 2) || (v[1] == 2 && v[2] >= 28)) }'; then + echo "glibc $glibc_version is older than the VS Code Server minimum 2.28" >&2 + exit 1 +fi + +if ! grep -ao 'GLIBCXX_3\.4\.25' /usr/lib64/libstdc++.so.6 >/dev/null 2>&1; then + echo "libstdc++ does not provide GLIBCXX_3.4.25 required by VS Code Server" >&2 + exit 1 +fi + # entrypoint smoke entrypoint="$project_dir/entrypoint.sh" if [ ! -f "$entrypoint" ]; then diff --git a/tooling/scripts/install-base-pkg-rpm.sh b/tooling/scripts/install-base-pkg-rpm.sh index b443f3dd..890fa49b 100644 --- a/tooling/scripts/install-base-pkg-rpm.sh +++ b/tooling/scripts/install-base-pkg-rpm.sh @@ -33,6 +33,7 @@ fi bash \ busybox \ ca-certificates \ + cpio \ cronie \ curl \ diffutils \ From b027a3aa30d2bfb498c1cdaa917558cdcf8cf764 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 12:39:46 +0800 Subject: [PATCH 5/9] Check Anolis VS Code Server prerequisites --- .../operating-systems/anolis/23.4/smoke.sh | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh index 71568939..bdf9e1aa 100644 --- a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh +++ b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh @@ -69,6 +69,17 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +glibc_version="$(ldd --version | head -n1 | grep -oE '[0-9]+[.][0-9]+' | tail -n1)" +if ! awk -v version="$glibc_version" 'BEGIN { split(version, v, "."); exit !((v[1] > 2) || (v[1] == 2 && v[2] >= 28)) }'; then + echo "glibc $glibc_version is older than the VS Code Server minimum 2.28" >&2 + exit 1 +fi + +if ! grep -ao 'GLIBCXX_3\.4\.25' /usr/lib64/libstdc++.so.6 >/dev/null 2>&1; then + echo "libstdc++ does not provide GLIBCXX_3.4.25 required by VS Code Server" >&2 + exit 1 +fi + # entrypoint smoke entrypoint="$project_dir/entrypoint.sh" if [ ! -f "$entrypoint" ]; then From 092f1d806ff9e7e05559dfdceb7e6117a16063b9 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 13:05:00 +0800 Subject: [PATCH 6/9] Enable ssh TCP forwarding --- tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh | 5 +++++ tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh | 5 +++++ tooling/scripts/svc/configure-sshd.sh | 1 + 3 files changed, 11 insertions(+) diff --git a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh index bdf9e1aa..8bea20a5 100644 --- a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh +++ b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh @@ -69,6 +69,11 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +if ! /usr/sbin/sshd -T | grep -qx 'allowtcpforwarding yes'; then + echo "sshd AllowTcpForwarding is not enabled" >&2 + exit 1 +fi + glibc_version="$(ldd --version | head -n1 | grep -oE '[0-9]+[.][0-9]+' | tail -n1)" if ! awk -v version="$glibc_version" 'BEGIN { split(version, v, "."); exit !((v[1] > 2) || (v[1] == 2 && v[2] >= 28)) }'; then echo "glibc $glibc_version is older than the VS Code Server minimum 2.28" >&2 diff --git a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh index 09b765bc..3ba0dc1e 100644 --- a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh +++ b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh @@ -69,6 +69,11 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +if ! /usr/sbin/sshd -T | grep -qx 'allowtcpforwarding yes'; then + echo "sshd AllowTcpForwarding is not enabled" >&2 + exit 1 +fi + glibc_version="$(ldd --version | head -n1 | grep -oE '[0-9]+[.][0-9]+' | tail -n1)" if ! awk -v version="$glibc_version" 'BEGIN { split(version, v, "."); exit !((v[1] > 2) || (v[1] == 2 && v[2] >= 28)) }'; then echo "glibc $glibc_version is older than the VS Code Server minimum 2.28" >&2 diff --git a/tooling/scripts/svc/configure-sshd.sh b/tooling/scripts/svc/configure-sshd.sh index ea180283..1acbc546 100644 --- a/tooling/scripts/svc/configure-sshd.sh +++ b/tooling/scripts/svc/configure-sshd.sh @@ -27,6 +27,7 @@ set_sshd_config 'IgnoreRhosts yes' set_sshd_config 'ListenAddress 0.0.0.0' set_sshd_config 'Port 22' set_sshd_config 'AuthorizedKeysFile /usr/start/.ssh/authorized_keys' +set_sshd_config 'AllowTcpForwarding yes' set_sshd_config 'PasswordAuthentication no' set_sshd_config 'PubKeyAuthentication yes' set_sshd_config 'PermitRootLogin prohibit-password' From dfabaa9346535d8e983b70d2407ee8774c4c540a Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 14:37:44 +0800 Subject: [PATCH 7/9] Allow non-root SSH logins in OS runtimes --- tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh | 7 +++++++ .../runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh | 7 +++++++ tooling/scripts/svc/configure-sshd.sh | 1 + 3 files changed, 15 insertions(+) diff --git a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh index 8bea20a5..7b84ae0f 100644 --- a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh +++ b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh @@ -69,6 +69,13 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +for nologin_file in /run/nologin /etc/nologin; do + if [ -e "$nologin_file" ]; then + echo "$nologin_file blocks non-root SSH logins" >&2 + exit 1 + fi +done + if ! /usr/sbin/sshd -T | grep -qx 'allowtcpforwarding yes'; then echo "sshd AllowTcpForwarding is not enabled" >&2 exit 1 diff --git a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh index 3ba0dc1e..4c280db2 100644 --- a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh +++ b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh @@ -69,6 +69,13 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +for nologin_file in /run/nologin /etc/nologin; do + if [ -e "$nologin_file" ]; then + echo "$nologin_file blocks non-root SSH logins" >&2 + exit 1 + fi +done + if ! /usr/sbin/sshd -T | grep -qx 'allowtcpforwarding yes'; then echo "sshd AllowTcpForwarding is not enabled" >&2 exit 1 diff --git a/tooling/scripts/svc/configure-sshd.sh b/tooling/scripts/svc/configure-sshd.sh index 1acbc546..c7fbb087 100644 --- a/tooling/scripts/svc/configure-sshd.sh +++ b/tooling/scripts/svc/configure-sshd.sh @@ -43,6 +43,7 @@ exec 2>&1 mkdir -p /run/sshd chmod 755 /run/sshd +rm -f /run/nologin /etc/nologin if ! ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1; then if ! command -v ssh-keygen >/dev/null 2>&1; then From b1d8de8e1cbcc6077d4d34660ad5dc5e244b4819 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 15 May 2026 14:37:44 +0800 Subject: [PATCH 8/9] Allow non-root SSH logins in OS runtimes --- tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh | 7 +++++++ .../runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh | 7 +++++++ tooling/scripts/svc/configure-sshd.sh | 1 + 3 files changed, 15 insertions(+) diff --git a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh index 8bea20a5..7b84ae0f 100644 --- a/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh +++ b/tests/runtime-smoke/operating-systems/anolis/23.4/smoke.sh @@ -69,6 +69,13 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +for nologin_file in /run/nologin /etc/nologin; do + if [ -e "$nologin_file" ]; then + echo "$nologin_file blocks non-root SSH logins" >&2 + exit 1 + fi +done + if ! /usr/sbin/sshd -T | grep -qx 'allowtcpforwarding yes'; then echo "sshd AllowTcpForwarding is not enabled" >&2 exit 1 diff --git a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh index 3ba0dc1e..4c280db2 100644 --- a/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh +++ b/tests/runtime-smoke/operating-systems/kylin/v10-sp3/smoke.sh @@ -69,6 +69,13 @@ if [ ! -x /usr/sbin/sshd ]; then exit 1 fi +for nologin_file in /run/nologin /etc/nologin; do + if [ -e "$nologin_file" ]; then + echo "$nologin_file blocks non-root SSH logins" >&2 + exit 1 + fi +done + if ! /usr/sbin/sshd -T | grep -qx 'allowtcpforwarding yes'; then echo "sshd AllowTcpForwarding is not enabled" >&2 exit 1 diff --git a/tooling/scripts/svc/configure-sshd.sh b/tooling/scripts/svc/configure-sshd.sh index 1acbc546..c7fbb087 100644 --- a/tooling/scripts/svc/configure-sshd.sh +++ b/tooling/scripts/svc/configure-sshd.sh @@ -43,6 +43,7 @@ exec 2>&1 mkdir -p /run/sshd chmod 755 /run/sshd +rm -f /run/nologin /etc/nologin if ! ls /etc/ssh/ssh_host_*_key >/dev/null 2>&1; then if ! command -v ssh-keygen >/dev/null 2>&1; then From c5ed09f8c44ebb4b64425dc991bc5d77a3225ab0 Mon Sep 17 00:00:00 2001 From: yy Date: Tue, 2 Jun 2026 16:52:03 +0800 Subject: [PATCH 9/9] Add Next.js runtime template --- base-images/frameworks/next.js/v16/Dockerfile | 20 + base-images/frameworks/next.js/v16/build.sh | 7 + .../frameworks/next.js/v16/Dockerfile | 24 + .../frameworks/next.js/v16/build.sh | 49 + .../next.js/v16/project-template/.gitignore | 25 + .../v16/project-template/README.en_US.md | 58 + .../v16/project-template/README.zh_CN.md | 58 + .../v16/project-template/app/globals.css | 135 +++ .../v16/project-template/app/layout.tsx | 19 + .../next.js/v16/project-template/app/page.tsx | 39 + .../v16/project-template/entrypoint.sh | 37 + .../v16/project-template/next-env.d.ts | 6 + .../v16/project-template/next.config.ts | 5 + .../v16/project-template/package-lock.json | 1041 +++++++++++++++++ .../next.js/v16/project-template/package.json | 30 + .../v16/project-template/tsconfig.json | 30 + tests/runtime-conformance/README.md | 1 + tests/runtime-conformance/run.sh | 19 +- .../frameworks/next.js/v16/smoke.sh | 94 ++ 19 files changed, 1695 insertions(+), 2 deletions(-) create mode 100644 base-images/frameworks/next.js/v16/Dockerfile create mode 100644 base-images/frameworks/next.js/v16/build.sh create mode 100644 runtime-images/frameworks/next.js/v16/Dockerfile create mode 100644 runtime-images/frameworks/next.js/v16/build.sh create mode 100644 runtime-images/frameworks/next.js/v16/project-template/.gitignore create mode 100644 runtime-images/frameworks/next.js/v16/project-template/README.en_US.md create mode 100644 runtime-images/frameworks/next.js/v16/project-template/README.zh_CN.md create mode 100644 runtime-images/frameworks/next.js/v16/project-template/app/globals.css create mode 100644 runtime-images/frameworks/next.js/v16/project-template/app/layout.tsx create mode 100644 runtime-images/frameworks/next.js/v16/project-template/app/page.tsx create mode 100644 runtime-images/frameworks/next.js/v16/project-template/entrypoint.sh create mode 100644 runtime-images/frameworks/next.js/v16/project-template/next-env.d.ts create mode 100644 runtime-images/frameworks/next.js/v16/project-template/next.config.ts create mode 100644 runtime-images/frameworks/next.js/v16/project-template/package-lock.json create mode 100644 runtime-images/frameworks/next.js/v16/project-template/package.json create mode 100644 runtime-images/frameworks/next.js/v16/project-template/tsconfig.json create mode 100644 tests/runtime-smoke/frameworks/next.js/v16/smoke.sh diff --git a/base-images/frameworks/next.js/v16/Dockerfile b/base-images/frameworks/next.js/v16/Dockerfile new file mode 100644 index 00000000..f800858b --- /dev/null +++ b/base-images/frameworks/next.js/v16/Dockerfile @@ -0,0 +1,20 @@ +# These ARGs can be overridden at build time to customize the image +ARG REPO=labring-actions/devbox-base-images +ARG REGISTRY=ghcr.io +ARG L10N=en_US +ARG L10N_NORMALIZED=en-us + +# These ARGs are not recommended to be overridden at build time. +# Instead, update the Dockerfile directly for consistent builds, +# and release new versions as needed. +ARG NODE_IMAGE_VERSION=v0.0.1-alpha.1-${L10N_NORMALIZED} + +FROM ${REGISTRY}/${REPO}/node.js-20:${NODE_IMAGE_VERSION} +ARG L10N +LABEL org.opencontainers.image.authors="The Devbox Authors" +ENV L10N=${L10N} +# Add build script and execute it +COPY build.sh /build.sh +RUN chmod +x /build.sh && \ + /build.sh && \ + rm -f /build.sh diff --git a/base-images/frameworks/next.js/v16/build.sh b/base-images/frameworks/next.js/v16/build.sh new file mode 100644 index 00000000..e97c7658 --- /dev/null +++ b/base-images/frameworks/next.js/v16/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +NEXT_VERSION=${NEXT_VERSION:-16.2.6} + +# Install the Next.js project scaffolding CLI globally for convenience. +npm install -g "create-next-app@${NEXT_VERSION}" diff --git a/runtime-images/frameworks/next.js/v16/Dockerfile b/runtime-images/frameworks/next.js/v16/Dockerfile new file mode 100644 index 00000000..a5b99d65 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/Dockerfile @@ -0,0 +1,24 @@ +# These ARGs can be overridden at build time to customize the image +ARG REPO=labring-actions/devbox-base-images +ARG REGISTRY=ghcr.io +ARG L10N=en_US +ARG L10N_NORMALIZED=en-us + +# These ARGs are not recommended to be overridden at build time. +# Instead, update the Dockerfile directly for consistent builds, +# and release new versions as needed. +ARG RUNTIME_IMAGE_VERSION=v0.0.1-alpha.1-${L10N_NORMALIZED} + +FROM ${REGISTRY}/${REPO}/next.js-v16:${RUNTIME_IMAGE_VERSION} +ARG L10N +LABEL org.opencontainers.image.authors="The Devbox Authors" +ENV L10N=${L10N} +ENV PROJECT_TEMPLATE_DIR=/project-template +COPY ./project-template ${PROJECT_TEMPLATE_DIR} +COPY ./build.sh /build.sh +RUN chmod +x /build.sh && \ + /build.sh && \ + rm -f /build.sh && \ + rm -rf ${PROJECT_TEMPLATE_DIR} +# Set the working directory to the default devbox user's project directory +WORKDIR /home/${DEFAULT_DEVBOX_USER}/project diff --git a/runtime-images/frameworks/next.js/v16/build.sh b/runtime-images/frameworks/next.js/v16/build.sh new file mode 100644 index 00000000..c729217f --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/build.sh @@ -0,0 +1,49 @@ +#!/usr/bin/env bash +set -euo pipefail + +L10N=${L10N:-en_US} +DEFAULT_DEVBOX_USER=${DEFAULT_DEVBOX_USER:-devbox} +PROJECT_TEMPLATE_DIR=${PROJECT_TEMPLATE_DIR:-/project-template} + +if ! id -u "$DEFAULT_DEVBOX_USER" &>/dev/null; then + echo "User $DEFAULT_DEVBOX_USER does not exist" + exit 1 +fi + +TARGET_DIR="/home/$DEFAULT_DEVBOX_USER/project" +mkdir -p "$TARGET_DIR" + +if [ -f "$PROJECT_TEMPLATE_DIR/README.$L10N.md" ]; then + echo "README $PROJECT_TEMPLATE_DIR/README.$L10N.md exists. Copying to $TARGET_DIR/README.md" + cp "$PROJECT_TEMPLATE_DIR/README.$L10N.md" "$TARGET_DIR/README.md" +else + echo "README $PROJECT_TEMPLATE_DIR/README.$L10N.md does not exist. Skipping copy." +fi + +DOCS_DIR=${DOCS_DIR:-/usr/share/devbox/docs} +if [ -f "$DOCS_DIR/README.s6-user-guide.$L10N.md" ]; then + cp "$DOCS_DIR/README.s6-user-guide.$L10N.md" "$TARGET_DIR/README.s6-user-guide.md" +elif [ -f "$DOCS_DIR/README.s6-user-guide.en_US.md" ]; then + cp "$DOCS_DIR/README.s6-user-guide.en_US.md" "$TARGET_DIR/README.s6-user-guide.md" +fi + +# Copy project template contents (except localized readmes handled above). +# Using `/.` keeps hidden files/dirs if present. +cp -R "${PROJECT_TEMPLATE_DIR}/." "$TARGET_DIR/" + +# If we wrote a localized README.md, remove the localized variants to keep the +# project dir clean (optional; safe if they don't exist). +rm -f "$TARGET_DIR/README.en_US.md" "$TARGET_DIR/README.zh_CN.md" || true + +# Ensure entrypoint is executable if present. +if [ -f "$TARGET_DIR/entrypoint.sh" ]; then + chmod +x "$TARGET_DIR/entrypoint.sh" +fi + +# Install dependencies and build the Next.js project. +cd "$TARGET_DIR" +npm install +npm run build + +# Set ownership to default devbox user +chown -R "$DEFAULT_DEVBOX_USER:$DEFAULT_DEVBOX_USER" "$TARGET_DIR" diff --git a/runtime-images/frameworks/next.js/v16/project-template/.gitignore b/runtime-images/frameworks/next.js/v16/project-template/.gitignore new file mode 100644 index 00000000..6be16910 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/.gitignore @@ -0,0 +1,25 @@ +# dependencies +/node_modules + +# Next.js +/.next +/out + +# production +/build + +# logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# environment +.env*.local + +# editor +.idea +.vscode + +# system +.DS_Store diff --git a/runtime-images/frameworks/next.js/v16/project-template/README.en_US.md b/runtime-images/frameworks/next.js/v16/project-template/README.en_US.md new file mode 100644 index 00000000..ee288c5a --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/README.en_US.md @@ -0,0 +1,58 @@ +# Next.js v16 Runtime Template + +This template provides a React web application runtime powered by **Next.js 16** using the App Router and TypeScript. + +## Runtime Summary + +- Framework/runtime version: `Next.js 16` +- Base runtime image: `next.js-v16` +- Entrypoint script: `entrypoint.sh` +- Default service port: `3000` + +## Template Files + +- `app/layout.tsx`: root layout and page metadata +- `app/page.tsx`: default homepage rendered by the App Router +- `app/globals.css`: shared application styles +- `next.config.ts`: Next.js configuration +- `tsconfig.json`: TypeScript compiler configuration +- `entrypoint.sh`: starts Next.js in dev or production mode + +## Run in DevBox + +Run commands from `/home/devbox/project`. + +### Development mode + +```bash +bash entrypoint.sh +``` + +Behavior: +- Runs `npm install` to ensure dependencies are up to date. +- Starts `next dev` on `0.0.0.0:3000` for hot reloading. + +### Production mode + +```bash +bash entrypoint.sh production +``` + +Behavior: +- Installs dependencies needed for the production build. +- Builds the project with `npm run build`. +- Prunes development dependencies after the build. +- Starts `next start` on `0.0.0.0:3000`. + +## Verify Service + +```bash +curl http://127.0.0.1:3000 +``` + +## Customization + +- Edit `app/page.tsx` to change the default page. +- Add routes by creating folders under `app/`. +- Update `app/globals.css` for shared styling. +- Modify `next.config.ts` for framework options such as image domains or build output. diff --git a/runtime-images/frameworks/next.js/v16/project-template/README.zh_CN.md b/runtime-images/frameworks/next.js/v16/project-template/README.zh_CN.md new file mode 100644 index 00000000..609f9097 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/README.zh_CN.md @@ -0,0 +1,58 @@ +# Next.js v16 运行时模板 + +该模板提供一个基于 **Next.js 16** 的 React Web 应用运行时,使用 App Router 和 TypeScript。 + +## 运行时概览 + +- 框架/运行时版本:`Next.js 16` +- 基础运行时镜像:`next.js-v16` +- 启动脚本:`entrypoint.sh` +- 默认服务端口:`3000` + +## 模板文件 + +- `app/layout.tsx`:根布局和页面元数据 +- `app/page.tsx`:由 App Router 渲染的默认首页 +- `app/globals.css`:应用共享样式 +- `next.config.ts`:Next.js 配置 +- `tsconfig.json`:TypeScript 编译配置 +- `entrypoint.sh`:以开发或生产模式启动 Next.js + +## 在 DevBox 中运行 + +以下命令在 `/home/devbox/project` 目录执行。 + +### 开发模式 + +```bash +bash entrypoint.sh +``` + +行为说明: +- 执行 `npm install` 确保依赖已安装。 +- 以 `next dev` 启动,监听 `0.0.0.0:3000` 并支持热重载。 + +### 生产模式 + +```bash +bash entrypoint.sh production +``` + +行为说明: +- 安装生产构建所需依赖。 +- 执行 `npm run build` 构建项目。 +- 构建完成后裁剪开发依赖。 +- 以 `next start` 启动,监听 `0.0.0.0:3000`。 + +## 验证服务 + +```bash +curl http://127.0.0.1:3000 +``` + +## 自定义建议 + +- 修改 `app/page.tsx` 调整默认页面。 +- 在 `app/` 下创建目录以添加路由。 +- 修改 `app/globals.css` 调整全局样式。 +- 修改 `next.config.ts` 配置图片域名、构建输出等框架选项。 diff --git a/runtime-images/frameworks/next.js/v16/project-template/app/globals.css b/runtime-images/frameworks/next.js/v16/project-template/app/globals.css new file mode 100644 index 00000000..9e7d14d0 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/app/globals.css @@ -0,0 +1,135 @@ +:root { + color-scheme: light; + --background: #f8fafc; + --foreground: #172033; + --muted: #5f6c7b; + --panel: #ffffff; + --border: #d9e2ec; + --accent: #0f766e; + --accent-strong: #115e59; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + min-height: 100%; +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} + +a { + color: inherit; +} + +.page { + min-height: 100vh; + display: grid; + place-items: center; + padding: 48px 20px; +} + +.shell { + width: min(880px, 100%); + display: grid; + gap: 28px; +} + +.intro { + display: grid; + gap: 18px; +} + +.eyebrow { + margin: 0; + color: var(--accent-strong); + font-size: 0.85rem; + font-weight: 700; + letter-spacing: 0; + text-transform: uppercase; +} + +h1 { + margin: 0; + max-width: 760px; + font-size: clamp(2.25rem, 7vw, 4.75rem); + line-height: 1; + letter-spacing: 0; +} + +.summary { + margin: 0; + max-width: 660px; + color: var(--muted); + font-size: 1.08rem; + line-height: 1.7; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 12px; +} + +.button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 44px; + padding: 0 18px; + border: 1px solid var(--accent); + border-radius: 6px; + background: var(--accent); + color: #ffffff; + font-size: 0.95rem; + font-weight: 700; + text-decoration: none; +} + +.button.secondary { + background: transparent; + color: var(--accent-strong); +} + +.details { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 12px; +} + +.detail { + min-height: 112px; + padding: 18px; + border: 1px solid var(--border); + border-radius: 8px; + background: var(--panel); +} + +.detail h2 { + margin: 0 0 10px; + font-size: 0.95rem; +} + +.detail p { + margin: 0; + color: var(--muted); + font-size: 0.92rem; + line-height: 1.55; +} + +@media (max-width: 720px) { + .page { + place-items: start; + } + + .details { + grid-template-columns: 1fr; + } +} diff --git a/runtime-images/frameworks/next.js/v16/project-template/app/layout.tsx b/runtime-images/frameworks/next.js/v16/project-template/app/layout.tsx new file mode 100644 index 00000000..0ebbea24 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/app/layout.tsx @@ -0,0 +1,19 @@ +import type { Metadata } from 'next'; +import './globals.css'; + +export const metadata: Metadata = { + title: 'Next.js DevBox Template', + description: 'A Next.js runtime template for Sealos DevBox.', +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/runtime-images/frameworks/next.js/v16/project-template/app/page.tsx b/runtime-images/frameworks/next.js/v16/project-template/app/page.tsx new file mode 100644 index 00000000..cac0e118 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/app/page.tsx @@ -0,0 +1,39 @@ +export default function Home() { + return ( +
+
+
+

DevBox runtime

+

Next.js is ready.

+

+ Build routes in the App Router, iterate with hot reload, and switch + to the production server when your app is ready to ship. +

+ +
+ +
+
+

App Router

+

Create pages, layouts, and loading states under the app folder.

+
+
+

TypeScript

+

Template files are typed by default for confident iteration.

+
+
+

Port 3000

+

The entrypoint binds to all interfaces for DevBox access.

+
+
+
+
+ ); +} diff --git a/runtime-images/frameworks/next.js/v16/project-template/entrypoint.sh b/runtime-images/frameworks/next.js/v16/project-template/entrypoint.sh new file mode 100644 index 00000000..d395dc05 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/entrypoint.sh @@ -0,0 +1,37 @@ +#!/bin/bash +set -euo pipefail + +if [ "$(id -u)" -eq 0 ] && [ "${DEVBOX_ENTRYPOINT_AS_DEVBOX:-1}" = "1" ] && id devbox >/dev/null 2>&1; then + export DEVBOX_ENTRYPOINT_AS_DEVBOX=0 + SCRIPT_PATH=$(readlink -f "$0") + exec runuser -u devbox -- bash "$SCRIPT_PATH" "$@" +fi + +app_env=${1:-development} +export HOME=${HOME:-/home/devbox} +export HOST=${HOST:-0.0.0.0} +export PORT=${PORT:-3000} +export npm_config_cache=${npm_config_cache:-$HOME/.npm} +mkdir -p "$npm_config_cache" + +dev_commands() { + echo "Running Next.js development environment..." + npm install + npm run dev -- --hostname "$HOST" --port "$PORT" +} + +prod_commands() { + echo "Running Next.js production environment..." + npm install + npm run build + npm prune --omit=dev + npm run start -- --hostname "$HOST" --port "$PORT" +} + +if [ "$app_env" = "production" ] || [ "$app_env" = "prod" ] ; then + echo "Production environment detected" + prod_commands +else + echo "Development environment detected" + dev_commands +fi diff --git a/runtime-images/frameworks/next.js/v16/project-template/next-env.d.ts b/runtime-images/frameworks/next.js/v16/project-template/next-env.d.ts new file mode 100644 index 00000000..9edff1c7 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/runtime-images/frameworks/next.js/v16/project-template/next.config.ts b/runtime-images/frameworks/next.js/v16/project-template/next.config.ts new file mode 100644 index 00000000..b22af960 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/next.config.ts @@ -0,0 +1,5 @@ +import type { NextConfig } from 'next'; + +const nextConfig: NextConfig = {}; + +export default nextConfig; diff --git a/runtime-images/frameworks/next.js/v16/project-template/package-lock.json b/runtime-images/frameworks/next.js/v16/project-template/package-lock.json new file mode 100644 index 00000000..03ad97a9 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/package-lock.json @@ -0,0 +1,1041 @@ +{ + "name": "next-devbox-template", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "next-devbox-template", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "next": "16.2.6", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@types/node": "24.12.4", + "@types/react": "19.2.16", + "@types/react-dom": "^19.2.3", + "typescript": "5.9.3" + }, + "engines": { + "node": ">=20.9.0", + "npm": ">=10.0.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@next/env": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz", + "integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz", + "integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz", + "integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz", + "integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz", + "integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz", + "integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz", + "integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz", + "integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz", + "integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.16", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.16.tgz", + "integrity": "sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.33", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.33.tgz", + "integrity": "sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001793", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz", + "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz", + "integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.2.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.2.6", + "@next/swc-darwin-x64": "16.2.6", + "@next/swc-linux-arm64-gnu": "16.2.6", + "@next/swc-linux-arm64-musl": "16.2.6", + "@next/swc-linux-x64-gnu": "16.2.6", + "@next/swc-linux-x64-musl": "16.2.6", + "@next/swc-win32-arm64-msvc": "16.2.6", + "@next/swc-win32-x64-msvc": "16.2.6", + "sharp": "^0.34.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/runtime-images/frameworks/next.js/v16/project-template/package.json b/runtime-images/frameworks/next.js/v16/project-template/package.json new file mode 100644 index 00000000..a52d8fe3 --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/package.json @@ -0,0 +1,30 @@ +{ + "name": "next-devbox-template", + "private": true, + "version": "1.0.0", + "description": "Next.js DevBox runtime template", + "license": "MIT", + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start" + }, + "engines": { + "node": ">=20.9.0", + "npm": ">=10.0.0" + }, + "dependencies": { + "next": "16.2.6", + "react": "19.2.7", + "react-dom": "19.2.7" + }, + "devDependencies": { + "@types/node": "24.12.4", + "@types/react": "19.2.16", + "@types/react-dom": "^19.2.3", + "typescript": "5.9.3" + }, + "overrides": { + "postcss": "8.5.15" + } +} diff --git a/runtime-images/frameworks/next.js/v16/project-template/tsconfig.json b/runtime-images/frameworks/next.js/v16/project-template/tsconfig.json new file mode 100644 index 00000000..d73edcae --- /dev/null +++ b/runtime-images/frameworks/next.js/v16/project-template/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": ["node_modules"] +} diff --git a/tests/runtime-conformance/README.md b/tests/runtime-conformance/README.md index dda9c426..d79fb6c1 100644 --- a/tests/runtime-conformance/README.md +++ b/tests/runtime-conformance/README.md @@ -53,6 +53,7 @@ Runtime-specific checks: | `languages/python/3.12` | Python 3.12, pip, pip mirror for `zh_CN`, root/devbox entrypoint order | | `languages/rust/1.81.0` | Rust/Cargo 1.81.0, Cargo mirror for `zh_CN`, Cargo template, prebuilt release binary, root/devbox entrypoint order | | `frameworks/nest.js/v11` | Node 20, Nest CLI, npm mirror for `zh_CN`, build output, root/devbox entrypoint order | +| `frameworks/next.js/v16` | Node 20, create-next-app, npm mirror for `zh_CN`, Next.js build output, root/devbox entrypoint order | | `frameworks/nginx/1.22.1` | Nginx 1.22.1, config test, `/tmp/nginx-devbox` runtime paths, no distro default include pollution, root/devbox entrypoint order | | `frameworks/openclaw/latest` | Node 22, OpenClaw, Clawhub, Bun, npm mirror for `zh_CN`, safe `.env.example`, root/devbox entrypoint order | | `frameworks/sandbox/v1` | workspace ownership, codex-gateway, Codex CLI, Node/npm, Python/pip, kubectl, helm, bun, ripgrep, bubblewrap, npm/pip mirrors for `zh_CN` | diff --git a/tests/runtime-conformance/run.sh b/tests/runtime-conformance/run.sh index 276d52e2..8f00bc5c 100755 --- a/tests/runtime-conformance/run.sh +++ b/tests/runtime-conformance/run.sh @@ -196,7 +196,7 @@ check_no_root_owned_project_files() { http_port_for_runtime() { case "$RUNTIME_PATH" in - frameworks/nest.js/v11) + frameworks/nest.js/v11 | frameworks/next.js/v16) printf '3000' ;; frameworks/openclaw/latest | frameworks/sandbox/*) @@ -210,7 +210,7 @@ http_port_for_runtime() { http_timeout_for_runtime() { case "$RUNTIME_PATH" in - frameworks/nest.js/v11 | languages/net/*) + frameworks/nest.js/v11 | frameworks/next.js/v16 | languages/net/*) printf '120' ;; languages/rust/*) @@ -525,6 +525,18 @@ check_nest_runtime() { assert_file "$PROJECT_DIR/dist/main.js" } +check_next_runtime() { + check_node_runtime 20 + assert_command create-next-app + assert_file "$PROJECT_DIR/package.json" + assert_file "$PROJECT_DIR/package-lock.json" + assert_file "$PROJECT_DIR/next.config.ts" + assert_file "$PROJECT_DIR/tsconfig.json" + assert_file "$PROJECT_DIR/app/page.tsx" + assert_file "$PROJECT_DIR/app/layout.tsx" + assert_dir "$PROJECT_DIR/.next" +} + check_openclaw_runtime() { check_node_runtime 22 assert_command openclaw @@ -642,6 +654,9 @@ check_runtime_specifics() { frameworks/nest.js/v11) check_nest_runtime ;; + frameworks/next.js/v16) + check_next_runtime + ;; frameworks/nginx/1.22.1) check_nginx_runtime ;; diff --git a/tests/runtime-smoke/frameworks/next.js/v16/smoke.sh b/tests/runtime-smoke/frameworks/next.js/v16/smoke.sh new file mode 100644 index 00000000..e61479f5 --- /dev/null +++ b/tests/runtime-smoke/frameworks/next.js/v16/smoke.sh @@ -0,0 +1,94 @@ +#!/bin/bash +set -eu + +project_dir=/home/devbox/project + +if [ ! -d "$project_dir" ]; then + echo "Missing project dir: $project_dir" >&2 + exit 1 +fi + +# load profile env (best effort) +set +u +[ -f /etc/profile ] && . /etc/profile || true +if [ -d /etc/profile.d ]; then + for f in /etc/profile.d/*.sh; do + [ -r "$f" ] && . "$f" || true + done +fi +[ -f /home/devbox/.bashrc ] && . /home/devbox/.bashrc || true +set -u + +if [ "${SMOKE_DEBUG:-}" = "1" ]; then + echo "SMOKE_DEBUG=1" + echo "user=$(id -un) uid=$(id -u) gid=$(id -g)" + echo "HOME=$HOME" + echo "SHELL=${SHELL:-}" + echo "PATH=$PATH" + for cmd in node npm yarn pnpm npx create-next-app; do + if command -v "$cmd" >/dev/null 2>&1; then + echo "cmd:$cmd=$(command -v "$cmd")" + else + echo "cmd:$cmd=missing" + fi + done +fi + +cd "$project_dir" + +node -e 'process.exit(process.versions.node.split(".")[0]==="20"?0:1)' + +for file in package.json package-lock.json next.config.ts tsconfig.json app/page.tsx app/layout.tsx README.md; do + if [ ! -f "$project_dir/$file" ]; then + echo "Missing $file in $project_dir" >&2 + exit 1 + fi +done + +if [ ! -d "$project_dir/.next" ]; then + echo "Missing prebuilt .next output in $project_dir" >&2 + exit 1 +fi + +entrypoint="$project_dir/entrypoint.sh" +if [ ! -x "$entrypoint" ]; then + echo "Missing executable entrypoint.sh in $project_dir" >&2 + exit 1 +fi + +stop_entrypoint() { + pid="$1" + kill -TERM "-$pid" >/dev/null 2>&1 || kill -TERM "$pid" >/dev/null 2>&1 || true + sleep 1 + kill -KILL "-$pid" >/dev/null 2>&1 || kill -KILL "$pid" >/dev/null 2>&1 || true + wait "$pid" >/dev/null 2>&1 || true +} + +if ! command -v setsid >/dev/null 2>&1; then + echo "setsid not found" >&2 + exit 1 +fi + +setsid bash -lc "cd '$project_dir' && bash '$entrypoint' production" >/tmp/entrypoint.log 2>&1 & +pid=$! +for _ in $(seq 1 60); do + if curl -fsS --max-time 2 http://127.0.0.1:3000/ >/tmp/next-smoke.html 2>/tmp/next-smoke.err; then + stop_entrypoint "$pid" + grep -q "Next.js is ready" /tmp/next-smoke.html + echo "ok" + exit 0 + fi + if ! kill -0 "$pid" >/dev/null 2>&1; then + echo "entrypoint exited early" >&2 + echo "---- entrypoint log ----" >&2 + cat /tmp/entrypoint.log >&2 || true + exit 1 + fi + sleep 2 +done + +echo "entrypoint did not serve HTTP on port 3000" >&2 +echo "---- entrypoint log ----" >&2 +cat /tmp/entrypoint.log >&2 || true +stop_entrypoint "$pid" +exit 1