From 3002b7385a61f27248d9dec8e4cdb7cd27e81361 Mon Sep 17 00:00:00 2001 From: jmservera <8036360+jmservera@users.noreply.github.com> Date: Mon, 27 Jul 2026 22:58:16 +0200 Subject: [PATCH 1/4] feat: add version About dialog, installer, and release image pipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add Help > About dialog showing GET /control/version (FR-G3) - add POSIX install.sh local mode and .env.template (FR-D2/D3/E5/G4) - add release workflow pushing 8 images to ghcr on v* tags (FR-B1/B2/B3) 🚀 Part of #29 - Generated by Copilot --- .env.template | 73 +++++++++++ .github/workflows/release.yml | 89 +++++++++++++ install.sh | 142 +++++++++++++++++++++ services/frontend/src/App.svelte | 56 +++++++- services/frontend/src/lib/control.js | 12 ++ tests/acceptance/s14-theme-g-about.spec.js | 38 ++++++ 6 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 .env.template create mode 100644 .github/workflows/release.yml create mode 100644 install.sh create mode 100644 tests/acceptance/s14-theme-g-about.spec.js diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..7d01e13 --- /dev/null +++ b/.env.template @@ -0,0 +1,73 @@ +# UbeROS environment configuration (installer template) +# The installer (install.sh) copies this file to `.env` on first run and never +# overwrites an existing `.env`. Edit `.env` (not this template) to adjust your +# environment; `.env` is gitignored so machine- or company-specific overrides +# are never pushed to the repo. All values below are neutral, safe defaults. + +# --- ROS / Gazebo selection --- +# Switching the ROS 2 distribution (e.g. kilted -> jazzy) is a one-line change. +# See ADR-001. +ROS_DISTRO=kilted +GZ_RELEASE=ionic + +# --- ROS workspace location --- +# Host directory for the ROS workspace; its `src` subfolder is bind-mounted +# into the ROS and editor services at /ros_ws/src. May point outside the +# repository (e.g. UBEROS_WORKSPACE=/home/me/uberos-ws) to keep the repo clean +# and avoid workspace pollution. Defaults to the in-repo ./workspace directory. +UBEROS_WORKSPACE=./workspace + +# --- Ingress --- +# Host-published port for the reverse proxy (the only exposed port). +UBEROS_PORT=8080 + +# --- ROS middleware --- +ROS_DOMAIN_ID=42 +RMW_IMPLEMENTATION=rmw_fastrtps_cpp + +# --- Software rendering (default; GPU is an opt-in overlay) --- +LIBGL_ALWAYS_SOFTWARE=1 +MESA_GL_VERSION_OVERRIDE=3.3 +MESA_GLSL_VERSION_OVERRIDE=330 + +# --- Authentication --- +# Set to "basic" to enable Nginx basic auth at the proxy. Required before any +# non-localhost exposure (NFR N-05). Default is off for localhost Init. +# When "basic", provide credentials first: +# htpasswd -c config/nginx/.htpasswd admin +# The frontend menu shows a Logout action only while auth is enabled (BR-008). +UBEROS_AUTH=off + +# --- Operational control plane --- +# Comma-separated services the system menu is allowed to restart (BR-007). +# The control service, proxy, and discovery-server are intentionally excluded. +UBEROS_SERVICES=ros,gazebo,turtlesim,editor,frontend + +# Installed simulators exposed by the registry (FR-A1) and which of them +# auto-start at stack up (FR-B8). UBEROS_SIMULATORS is the install set (FR-D): +# list the simulator ids to install and show in the menu; omit one to exclude +# it (its menu entry disappears and launch/stop is rejected). The services are +# always-on (no compose profiles); this gates the registry/menu, not the build. +# Defaults to the whole catalog. UBEROS_SIMULATORS_AUTOSTART, when unset, honors +# each entry's registry `autostart` flag (default both on). The control plane +# starts/stops these on demand and reconciles autostart intent at boot. +UBEROS_SIMULATORS=gazebo,turtlesim +UBEROS_SIMULATORS_AUTOSTART= + +# Running UbeROS version surfaced by the control /version endpoint and the +# frontend About dialog (Theme G). The release pipeline overrides it at +# build/deploy time; leave it as `dev` for local development. +UBEROS_VERSION=dev + +# --- Intel GPU overlay (compose.override.intel.yaml) --- +# Host-specific group IDs for the DRI render nodes. Find them with +# getent group render video +# and set the numeric GIDs here if the default group names do not resolve. +# UBEROS_RENDER_GID=render +# UBEROS_VIDEO_GID=video + +# --- npm registry (frontend build) --- +# Registry used by the frontend image's `npm install`. The default is the public +# npm registry. Point this at a corporate/internal proxy in your local `.env` if +# your network requires it (that value stays out of the repo). +NPM_REGISTRY=https://registry.npmjs.org/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4709c61 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,89 @@ +name: Release — Build & Push Images +# UbeROS release pipeline (Theme B, FR-B1/FR-B2/FR-B3). On a `v*` version tag, +# build every service image for linux/amd64 and push it to GitHub Container +# Registry (ghcr.io) tagged with the release version. Pre-release tags (a `-` +# in the version, e.g. v0.4.0-beta) additionally publish a moving `:beta` tag so +# testers can track the latest pre-release. The version flows into each image as +# the UBEROS_VERSION build-arg, which stamps the OCI version label (FR-A5, PR-3) +# and the control /version endpoint default. +# +# Bundle assembly, checksums, release notes, and the GitHub Release itself land +# in a later pipeline stage (PR-12); this stage only publishes images. + +on: + push: + tags: + - 'v*' + +permissions: + contents: read + packages: write + +env: + REGISTRY: ghcr.io + IMAGE_NAMESPACE: ghcr.io/jmservera/uberos + +jobs: + publish-images: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - service: ros + context: ./services/ros + - service: gazebo + context: ./services/gazebo + - service: turtlesim + context: ./services/turtlesim + - service: editor + context: ./services/editor + - service: frontend + context: ./services/frontend + - service: gzweb-client + context: ./services/gazebo/client + - service: control + context: ./services/control + - service: proxy + context: ./services/proxy + steps: + - uses: actions/checkout@v4 + + - name: Derive version and image tags + id: meta + run: | + set -euo pipefail + version="${GITHUB_REF_NAME#v}" + image="${IMAGE_NAMESPACE}/${{ matrix.service }}" + tags="${image}:${version}" + # A hyphen marks a semver pre-release (e.g. 0.4.0-beta); publish the + # moving :beta tag alongside the pinned version (docs/VERSIONING.md). + case "${version}" in + *-*) tags="${tags},${image}:beta" ;; + esac + { + echo "version=${version}" + echo "tags=${tags}" + } >> "${GITHUB_OUTPUT}" + echo "Publishing ${{ matrix.service }} as: ${tags}" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push ${{ matrix.service }} + uses: docker/build-push-action@v6 + with: + context: ${{ matrix.context }} + platforms: linux/amd64 + push: true + tags: ${{ steps.meta.outputs.tags }} + build-args: | + UBEROS_VERSION=${{ steps.meta.outputs.version }} + NPM_REGISTRY=https://registry.npmjs.org/ diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..a469df6 --- /dev/null +++ b/install.sh @@ -0,0 +1,142 @@ +#!/bin/sh +# UbeROS installer (Theme D/E/G, FR-D2/FR-D3/FR-E5/FR-G4). +# +# Bootstraps a UbeROS deployment from either a source checkout (local mode: +# build the images from source) or a released bundle (release mode: pull pinned +# images). This first stage implements local mode and the shared plumbing: +# argument parsing, mode auto-detection, `.env` generation from `.env.template`, +# and `--version`/`--help`. Guided wizard, workspace provisioning, and release +# mode land in later installer stages (PR-7, PR-8, PR-13). +# +# POSIX sh only (no bashisms) so the installer runs on the widest range of hosts. +set -eu + +# Installer version (FR-G4). The release pipeline stamps the real version into +# the bundled copy; a source checkout reports `dev`. +INSTALLER_VERSION="dev" + +# Resolve the repository/bundle root from this script's location so the +# installer works regardless of the caller's working directory. +SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd -P)" +ROOT_DIR="${SCRIPT_DIR}" + +ENV_TEMPLATE="${ROOT_DIR}/.env.template" +ENV_FILE="${ROOT_DIR}/.env" + +MODE="" # empty => auto-detect + +log() { printf '%s\n' "$*"; } +err() { printf 'error: %s\n' "$*" >&2; } +die() { err "$*"; exit 1; } + +usage() { + cat <<'EOF' +UbeROS installer + +Usage: ./install.sh [options] + +Options: + --mode Installation mode. Defaults to auto-detect: + local = build images from a source checkout + release = use pinned images from a release bundle + --version Print the installer version and exit. + -h, --help Show this help and exit. + +Local mode builds every service image from source and starts the stack. Once it +is up, open the UbeROS UI through the proxy (default http://localhost:8080). +EOF +} + +# Detect whether this tree is a source checkout (build from source) or a +# released bundle (pinned images). A bundle ships compose.release.yaml; a source +# checkout ships the services/ build contexts. +detect_mode() { + if [ -f "${ROOT_DIR}/compose.release.yaml" ]; then + printf 'release' + elif [ -d "${ROOT_DIR}/services" ] && [ -f "${ROOT_DIR}/compose.yaml" ]; then + printf 'local' + else + printf 'unknown' + fi +} + +# Ensure a docker compose CLI is available (v2 plugin or legacy binary). +compose() { + if docker compose version >/dev/null 2>&1; then + docker compose "$@" + elif command -v docker-compose >/dev/null 2>&1; then + docker-compose "$@" + else + die "docker compose is required but was not found on PATH" + fi +} + +# Create .env from the template on first run; never clobber an existing .env so +# user edits survive re-runs (FR-E5). +ensure_env() { + if [ -f "${ENV_FILE}" ]; then + log "Keeping existing .env (not overwritten)." + return 0 + fi + [ -f "${ENV_TEMPLATE}" ] || die "missing ${ENV_TEMPLATE}; cannot generate .env" + cp "${ENV_TEMPLATE}" "${ENV_FILE}" + log "Generated .env from .env.template." +} + +install_local() { + [ -f "${ROOT_DIR}/compose.yaml" ] || die "local mode requires compose.yaml at ${ROOT_DIR}" + ensure_env + log "Building UbeROS images from source..." + ( cd "${ROOT_DIR}" && compose build ) + log "Starting the UbeROS stack..." + ( cd "${ROOT_DIR}" && compose up -d ) + port="$(grep -E '^UBEROS_PORT=' "${ENV_FILE}" 2>/dev/null | head -n1 | cut -d= -f2)" + port="${port:-8080}" + log "UbeROS is starting. Open the UI at http://localhost:${port}" +} + +install_release() { + # Release mode (pinned images, checksum verification, upgrade) is implemented + # in a later installer stage (PR-13). Fail clearly until then. + die "release mode is not available in this installer build yet" +} + +# --- Argument parsing (POSIX) ------------------------------------------------ +while [ "$#" -gt 0 ]; do + case "$1" in + --mode) + [ "$#" -ge 2 ] || die "--mode requires an argument (local|release)" + MODE="$2" + shift 2 + ;; + --mode=*) + MODE="${1#--mode=}" + shift + ;; + --version) + printf '%s\n' "${INSTALLER_VERSION}" + exit 0 + ;; + -h|--help) + usage + exit 0 + ;; + *) + err "unknown option: $1" + usage >&2 + exit 2 + ;; + esac +done + +if [ -z "${MODE}" ]; then + MODE="$(detect_mode)" + [ "${MODE}" = "unknown" ] && die "could not auto-detect install mode; pass --mode local|release" + log "Auto-detected install mode: ${MODE}" +fi + +case "${MODE}" in + local) install_local ;; + release) install_release ;; + *) die "invalid mode: ${MODE} (expected local or release)" ;; +esac diff --git a/services/frontend/src/App.svelte b/services/frontend/src/App.svelte index 521a4a1..aa969ee 100644 --- a/services/frontend/src/App.svelte +++ b/services/frontend/src/App.svelte @@ -13,7 +13,7 @@ LAYOUT_PRESETS, LAYOUTS, } from './lib/panels.js'; - import { getConfig, getServices, getSimulators, restartService, launchSimulator, stopSimulator, getSettings, saveSettings } from './lib/control.js'; + import { getConfig, getServices, getSimulators, restartService, launchSimulator, stopSimulator, getSettings, saveSettings, getVersion } from './lib/control.js'; const LAYOUT_KEY = 'uberos.layout.v1'; const LAYOUT_KEYS = Object.keys(LAYOUTS); @@ -77,6 +77,13 @@ let settingsUser = 'default'; // reserved key for future per-user scoping (FR-C4) let configError = ''; + // --- Help ▸ About dialog (Theme G, FR-G3) ------------------------------ + // Surfaces the running UbeROS version from the control plane's /version + // endpoint (UBEROS_VERSION, defaults to `dev`) so operators can confirm which + // release they are running. + let showAbout = false; // dialog visibility (FR-G3) + let version = 'dev'; // running version fetched from GET /control/version + const factories = { terminal: buildTerminalPanel, editor: buildEditorPanel, @@ -373,6 +380,18 @@ showConfig = false; } + // Open the About dialog and refresh the version from the control plane so the + // displayed value always reflects the running deployment (FR-G3). + function openAbout() { + showAbout = true; + closeMenu(); + getVersion().then((v) => { version = v; }); + } + + function closeAbout() { + showAbout = false; + } + // Validate the draft before saving (FR-C3). Server-side validation is the // source of truth; this gives immediate, friendly feedback. function validateDraft() { @@ -703,6 +722,9 @@ authEnabled = cfg.auth && cfg.auth !== 'off' && cfg.auth !== 'none'; }); + // Prime the About dialog's version so it is correct on first open (FR-G3). + getVersion().then((v) => { version = v; }); + // Load persisted system settings and apply their effects (Theme C). // Apply even in pop-out sub-windows so the theme stays consistent. loadSettings(); @@ -878,6 +900,13 @@ on:click|stopPropagation={openConfig} >Configuration + + + {#if statusMsg}{statusMsg}{/if} {#if authEnabled} @@ -947,8 +976,33 @@ {/if} + + + {#if showAbout} + + {/if} +