Files
Joey-nexu f1a0b60c6c chore: unify product name display to OpenDesign repo-wide (#6998)
* chore: unify product name display to OpenDesign repo-wide

Rename the user-facing product name from "Open Design" to "OpenDesign"
across documentation, web/desktop UI strings, i18n locales, prompts,
plugin/skill/design-system content, changelogs, and specs, matching the
landing-page rename already in flight.

Deliberately excluded so installed-app identity stays untouched:
- packages/release PRODUCT_NAME and everything derived from it
  (app bundle names, installer/registry identity, updater feeds)
- tools/pack, tools/release, apps/packaged, landing page (PR #6983)
- mocks/ recorded traces and LICENSE
- test fixtures and comments that encode real on-disk install paths
  (Open Design.app, Program Files\Open Design, AppData paths)

Validated with pnpm guard, pnpm typecheck, and targeted web/desktop/host
test runs over the renamed strings.

* fix(web): keep two-letter author monogram for CamelCase brand names

authorInitials split words on whitespace only, so the renamed single-token
author name "OpenDesign" produced a one-letter monogram. Treat lower-to-upper
CamelCase boundaries as word breaks so the avatar fallback stays "OD".

* fix(release): restore spaced Windows source artifact names in win.ps1

tools/pack still derives builder outputs from the unchanged PRODUCT_NAME
("Open Design"), so the asset script must look for the spaced filenames.
Matches the mac/mac-intel/linux asset scripts, which were already kept on
the original names.

---------

Co-authored-by: Joey <236967869+joeylee12629-star@users.noreply.github.com>
2026-08-18 07:09:45 +00:00

539 lines
18 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# OpenDesign — One-Click Installer
# Docker Compose wrapper for Linux and macOS
#
# Usage:
# Clone this repo, then run:
# ./install.sh [--non-interactive] [--port 7456] [--image <ref>] [--skip-docker-install] [--no-systemd]
set -euo pipefail
# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------
DEFAULT_PORT=7456
DEFAULT_IMAGE="ghcr.io/nexu-io/od:latest"
DEFAULT_MEM_LIMIT="384m"
HEALTH_TIMEOUT=60
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
DEPLOY_DIR="$(dirname "$SCRIPT_DIR")"
COMPOSE_FILE="${DEPLOY_DIR}/docker-compose.yml"
OVERRIDE_FILE="${DEPLOY_DIR}/docker-compose.override.yml"
LINUX_OVERRIDE_FILE="${DEPLOY_DIR}/docker-compose.linux.yml"
# Build the -f argument list: always include the base file,
# add the Linux host-network override, and add docker-compose.override.yml if present
# (used by tests for isolation).
COMPOSE_FILES=(-f "$COMPOSE_FILE")
if [ "$(uname -s)" = "Linux" ] && [ -f "$LINUX_OVERRIDE_FILE" ]; then
COMPOSE_FILES+=(-f "$LINUX_OVERRIDE_FILE")
fi
if [ -f "$OVERRIDE_FILE" ]; then
COMPOSE_FILES+=(-f "$OVERRIDE_FILE")
fi
if [ ! -f "$COMPOSE_FILE" ]; then
echo "WARN: docker-compose.yml not found at ${COMPOSE_FILE}" >&2
echo " Clone the repository and run this script from deploy/scripts/." >&2
exit 1
fi
ENV_FILE="${DEPLOY_DIR}/.env"
# ---------------------------------------------------------------------------
# Colors & formatting
# ---------------------------------------------------------------------------
BOLD="" DIM="" RED="" GREEN="" YELLOW="" CYAN="" RESET=""
if [ -t 1 ]; then
BOLD="\033[1m" DIM="\033[2m" RED="\033[31m" GREEN="\033[32m"
YELLOW="\033[33m" CYAN="\033[36m" RESET="\033[0m"
fi
step() { printf " ${DIM}▸${RESET} %s\n" "$1"; }
ok() { printf " ${GREEN}✓${RESET} %s\n" "$1"; }
warn() { printf " ${YELLOW}!${RESET} %s\n" "$1" >&2; }
error() { printf " ${RED}✗${RESET} %s\n" "$1" >&2; }
info() { printf " ${CYAN}›${RESET} %s\n" "$1"; }
prompt_text() {
_prompt="$1" _default="$2"
printf "%s [%s]: " "$_prompt" "$_default" >&2
read -r _val
PROMPT_RESULT="${_val:-$_default}"
}
prompt_confirm() {
_question="$1" _default="$2"
if [ "$NON_INTERACTIVE" = "1" ]; then
return 0
fi
_yn_default="y"
if [ "$_default" = "0" ]; then _yn_default="n"; fi
printf "%s [%s]: " "$_question" "$_yn_default" >&2
read -r _yn
case "$_yn" in
[Yy]*) return 0 ;;
[Nn]*) return 1 ;;
*) [ "$_default" = "1" ] && return 0; return 1 ;;
esac
}
# ---------------------------------------------------------------------------
# Argument parsing
# ---------------------------------------------------------------------------
NON_INTERACTIVE=0
OPT_PORT=""
OPT_IMAGE=""
OPT_SKIP_DOCKER=0
OPT_NO_SYSTEMD=0
while [ $# -gt 0 ]; do
case "$1" in
--non-interactive) NON_INTERACTIVE=1 ;;
--port) shift; OPT_PORT="$1" ;;
--port=*) OPT_PORT="${1#--port=}" ;;
--image) shift; OPT_IMAGE="$1" ;;
--image=*) OPT_IMAGE="${1#--image=}" ;;
--skip-docker-install) OPT_SKIP_DOCKER=1 ;;
--no-systemd) OPT_NO_SYSTEMD=1 ;;
--help|-h)
echo "Usage: install.sh [options]"
echo ""
echo "Options:"
echo " --non-interactive Use defaults for all prompts"
echo " --port <n> Host port (default: ${DEFAULT_PORT})"
echo " --image <ref> Docker image reference"
echo " --skip-docker-install Do not attempt to install Docker"
echo " --no-systemd Skip systemd unit creation"
echo " --help Show this help"
exit 0
;;
*)
warn "Unknown argument: $1 (ignored)"
;;
esac
shift
done
# ---------------------------------------------------------------------------
# Banner
# ---------------------------------------------------------------------------
printf "\n"
printf "${BOLD} ┌──────────────────────────────────────┐${RESET}\n"
printf "${BOLD} │${RESET} ${BOLD}│${RESET}\n"
printf "${BOLD} │${RESET} ${CYAN}◈${RESET} ${BOLD}OpenDesign${RESET} ${BOLD}│${RESET}\n"
printf "${BOLD} │${RESET} ${DIM}One-Click Installer${RESET} ${BOLD}│${RESET}\n"
printf "${BOLD} │${RESET} ${BOLD}│${RESET}\n"
printf "${BOLD} └──────────────────────────────────────┘${RESET}\n"
printf "\n"
# ---------------------------------------------------------------------------
# 1. OS detection
# ---------------------------------------------------------------------------
OS="$(uname -s)"
ARCH="$(uname -m)"
case "$OS" in
Linux)
if [ -f /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release
DISTRO="$ID"
DISTRO_VERSION="$VERSION_ID"
else
DISTRO="unknown"
DISTRO_VERSION=""
fi
step "OS: Linux ${DISTRO} ${DISTRO_VERSION} (${ARCH})"
;;
Darwin)
step "OS: macOS $(sw_vers -productVersion) (${ARCH})"
DISTRO="macos"
;;
*)
error "Unsupported OS: ${OS}. This script supports Linux and macOS."
exit 1
;;
esac
# ---------------------------------------------------------------------------
# 2. Container runtime detection (Docker or Podman)
# ---------------------------------------------------------------------------
CONTAINER_CMD=""
CONTAINER_RUNTIME=""
detect_container_runtime() {
if command -v docker >/dev/null 2>&1; then
CONTAINER_CMD="docker"
CONTAINER_RUNTIME="docker"
return 0
fi
if command -v podman >/dev/null 2>&1; then
CONTAINER_CMD="podman"
CONTAINER_RUNTIME="podman"
return 0
fi
return 1
}
detect_compose() {
if $CONTAINER_CMD compose version >/dev/null 2>&1; then
COMPOSE_CMD="${CONTAINER_CMD} compose"
return 0
fi
if [ "$CONTAINER_RUNTIME" = "podman" ] && command -v podman-compose >/dev/null 2>&1; then
COMPOSE_CMD="podman-compose"
return 0
fi
if [ "$CONTAINER_RUNTIME" = "docker" ] && command -v docker-compose >/dev/null 2>&1; then
COMPOSE_CMD="docker-compose"
return 0
fi
return 1
}
check_runtime_running() {
$CONTAINER_CMD info >/dev/null 2>&1
}
if ! detect_container_runtime; then
if [ "$OPT_SKIP_DOCKER" = "1" ]; then
error "No container runtime found (Docker or Podman) and --skip-docker-install was set."
exit 1
fi
warn "No container runtime found (Docker or Podman)."
case "$DISTRO" in
ubuntu|debian)
install_cmd="sudo apt-get update && sudo apt-get install -y docker.io docker-compose-plugin"
step "Install Docker with: ${install_cmd}"
step "Or Podman: sudo apt-get install -y podman podman-compose"
;;
fedora)
install_cmd="sudo dnf install -y docker docker-compose-plugin"
step "Install Docker with: ${install_cmd}"
step "Or Podman: sudo dnf install -y podman podman-compose"
;;
centos|rhel|rocky|alma)
install_cmd="sudo yum install -y docker docker-compose-plugin"
step "Install Docker with: ${install_cmd}"
step "Or Podman: sudo yum install -y podman podman-compose"
;;
macos)
install_cmd="brew install --cask docker"
step "Install Docker with: ${install_cmd}"
step "Or Podman: brew install podman && podman machine init && podman machine start"
;;
*)
error "Cannot auto-detect install method for: ${DISTRO}"
step "Install Docker: https://docs.docker.com/get-docker/"
step "Or Podman: https://podman.io/getting-started/installation"
exit 1
;;
esac
if [ "$NON_INTERACTIVE" = "0" ]; then
if prompt_confirm "Install Docker now?" 0; then
step "Running: ${install_cmd}"
eval "$install_cmd"
if ! detect_container_runtime; then
error "Installation did not succeed. Install manually and re-run."
exit 1
fi
else
error "A container runtime is required. Install Docker or Podman and re-run."
exit 1
fi
else
error "A container runtime is required in non-interactive mode. Install Docker or Podman first."
exit 1
fi
fi
if ! detect_compose; then
error "Container Compose is not available."
if [ "$CONTAINER_RUNTIME" = "podman" ]; then
case "$OS" in
Darwin) step "Install with: brew install podman-compose" ;;
*) step "Install with: sudo apt-get install podman-compose (or equivalent for your distro)" ;;
esac
else
step "Install the compose plugin: https://docs.docker.com/compose/install/"
fi
exit 1
fi
# The Linux host-network override uses the !reset YAML tag (Docker Compose
# v2.17+). Reject legacy docker-compose v1 and podman-compose early so users
# get a clear message instead of a YAML parse error mid-install.
if [ "$OS" = "Linux" ] && [ -f "$LINUX_OVERRIDE_FILE" ]; then
if [ "$COMPOSE_CMD" != "docker compose" ]; then
error "The Linux host-network override requires 'docker compose' v2."
error "Found: ${COMPOSE_CMD}"
step "Install the Docker Compose plugin: https://docs.docker.com/compose/install/"
exit 1
fi
_compose_ver="$($COMPOSE_CMD version --short 2>/dev/null || echo "0.0.0")"
_compose_major="$(echo "$_compose_ver" | cut -d. -f1)"
_compose_minor="$(echo "$_compose_ver" | cut -d. -f2)"
if [ "$_compose_major" -lt 2 ] || { [ "$_compose_major" -eq 2 ] && [ "$_compose_minor" -lt 17 ]; }; then
error "Docker Compose v2.17 or later required for the Linux override (found v${_compose_ver})."
step "Upgrade: https://docs.docker.com/compose/install/"
exit 1
fi
fi
if ! check_runtime_running; then
warn "${CONTAINER_RUNTIME} is not running."
case "$DISTRO" in
ubuntu|debian|fedora|centos|rhel|rocky|alma)
if [ "$CONTAINER_RUNTIME" = "podman" ]; then
step "Start with: podman machine init && podman machine start"
else
step "Start with: sudo systemctl start docker"
fi
;;
macos)
if [ "$CONTAINER_RUNTIME" = "podman" ]; then
step "Start with: podman machine start"
else
step "Open Docker Desktop from your Applications folder."
fi
;;
esac
if [ "$NON_INTERACTIVE" = "0" ]; then
if prompt_confirm "Retry after starting ${CONTAINER_RUNTIME}?" 1; then
if ! check_runtime_running; then
error "${CONTAINER_RUNTIME} is still not running. Start it and re-run."
exit 1
fi
else
exit 1
fi
else
exit 1
fi
fi
RUNTIME_VERSION="$(${CONTAINER_CMD} --version 2>/dev/null || echo 'unknown')"
COMPOSE_VERSION="$(${COMPOSE_CMD} version 2>/dev/null || echo 'unknown')"
step "Runtime: ${CONTAINER_RUNTIME} ${RUNTIME_VERSION}"
step "Compose: ${COMPOSE_VERSION}"
# ---------------------------------------------------------------------------
# 2b. Check if OpenDesign is already running
# ---------------------------------------------------------------------------
if $CONTAINER_CMD ps --filter "name=open-design" --format '{{.Names}}' 2>/dev/null | grep -q 'open-design'; then
STATUS="$($CONTAINER_CMD inspect --format '{{.State.Status}}' open-design 2>/dev/null || echo 'unknown')"
IMAGE="$($CONTAINER_CMD inspect --format '{{.Config.Image}}' open-design 2>/dev/null || echo 'unknown')"
PORTS="$($CONTAINER_CMD port open-design 2>/dev/null || echo 'unknown')"
error "OpenDesign is already running."
printf "\n"
printf " Container: open-design\n"
printf " Status: %s\n" "$STATUS"
printf " Image: %s\n" "$IMAGE"
printf " Ports: %s\n" "$PORTS"
printf "\n"
step "To update: ${SCRIPT_DIR}/update.sh"
step "To remove: ${SCRIPT_DIR}/uninstall.sh"
printf "\n"
exit 1
fi
# ---------------------------------------------------------------------------
# 3. Port conflict detection
# ---------------------------------------------------------------------------
PORT="${OPT_PORT:-$DEFAULT_PORT}"
check_port_in_use() {
if command -v ss >/dev/null 2>&1; then
ss -tlnp 2>/dev/null | grep -q ":${1} " && return 0
elif command -v lsof >/dev/null 2>&1; then
lsof -i :"$1" 2>/dev/null | grep -q LISTEN && return 0
fi
return 1
}
if check_port_in_use "$PORT"; then
warn "Port ${PORT} is already in use."
if [ "$NON_INTERACTIVE" = "0" ]; then
_suggest=$((PORT + 1))
prompt_text "Enter a different port" "$_suggest"
PORT="$PROMPT_RESULT"
if check_port_in_use "$PORT"; then
error "Port ${PORT} is also in use. Please pick a free port."
exit 1
fi
else
error "Port ${PORT} is occupied. Use --port to specify a different one."
exit 1
fi
fi
# ---------------------------------------------------------------------------
# 4. Interactive prompts
# ---------------------------------------------------------------------------
IMAGE="${OPT_IMAGE:-$DEFAULT_IMAGE}"
ALLOWED_ORIGINS=""
MEM_LIMIT="$DEFAULT_MEM_LIMIT"
if [ "$NON_INTERACTIVE" = "0" ]; then
printf "\n"
prompt_text "Docker image" "$DEFAULT_IMAGE"
IMAGE="$PROMPT_RESULT"
prompt_text "Port" "$PORT"
PORT="$PROMPT_RESULT"
prompt_text "Allowed origins (CORS, comma-separated, or empty)" ""
ALLOWED_ORIGINS="$PROMPT_RESULT"
prompt_text "Memory limit" "$DEFAULT_MEM_LIMIT"
MEM_LIMIT="$PROMPT_RESULT"
if check_port_in_use "$PORT"; then
error "Port ${PORT} is already in use. Aborting."
exit 1
fi
fi
# ---------------------------------------------------------------------------
# 5. Generate .env
# ---------------------------------------------------------------------------
if [ -f "$ENV_FILE" ]; then
BACKUP="${ENV_FILE}.$(date +%Y%m%d%H%M%S).bak"
step "Existing .env found. Backing up to ${BACKUP}"
cp "$ENV_FILE" "$BACKUP"
fi
GENERATED_TOKEN=$(openssl rand -hex 32 2>/dev/null || od -vAn -N32 -tx1 /dev/urandom | tr -d ' \n' 2>/dev/null)
cat > "$ENV_FILE" << ENVFILE
# Generated by install.sh on $(date -u +%Y-%m-%dT%H:%M:%SZ)
OPEN_DESIGN_IMAGE=${IMAGE}
OPEN_DESIGN_PORT=${PORT}
OPEN_DESIGN_ALLOWED_ORIGINS=${ALLOWED_ORIGINS}
OPEN_DESIGN_MEM_LIMIT=${MEM_LIMIT}
NODE_OPTIONS=--max-old-space-size=192
OD_API_TOKEN=${GENERATED_TOKEN}
ENVFILE
ok "Written ${ENV_FILE}"
# ---------------------------------------------------------------------------
# 6. Pull and start
# ---------------------------------------------------------------------------
step "Pulling image: ${IMAGE}"
$COMPOSE_CMD "${COMPOSE_FILES[@]}" pull
step "Starting OpenDesign..."
$COMPOSE_CMD "${COMPOSE_FILES[@]}" up -d --no-build
# ---------------------------------------------------------------------------
# 7. Health check
# ---------------------------------------------------------------------------
step "Waiting for health check (up to ${HEALTH_TIMEOUT}s)..."
HEALTH_URL="http://127.0.0.1:${PORT}/api/health"
HEALTH_OK=0
ELAPSED=0
while [ "$ELAPSED" -lt "$HEALTH_TIMEOUT" ]; do
if command -v curl >/dev/null 2>&1; then
HTTP_CODE="$(curl -s -o /dev/null -w '%{http_code}' "$HEALTH_URL" 2>/dev/null || echo '000')"
elif command -v wget >/dev/null 2>&1; then
HTTP_CODE="$(wget -q -O /dev/null --server-response "$HEALTH_URL" 2>&1 | grep 'HTTP/' | tail -1 | awk '{print $2}')"
else
HTTP_CODE="000"
fi
if [ "$HTTP_CODE" = "200" ]; then
HEALTH_OK=1
break
fi
sleep 2
ELAPSED=$((ELAPSED + 2))
done
if [ "$HEALTH_OK" = "1" ]; then
ok "Daemon is healthy (${HTTP_CODE} OK)"
else
warn "Health check did not pass within ${HEALTH_TIMEOUT}s."
step "Check status: ${COMPOSE_CMD} \"${COMPOSE_FILES[@]}\" logs"
fi
# ---------------------------------------------------------------------------
# 8. systemd unit (Linux only)
# ---------------------------------------------------------------------------
if [ "$OS" = "Linux" ] && [ "$OPT_NO_SYSTEMD" = "0" ]; then
if command -v systemctl >/dev/null 2>&1; then
SYSTEMD_DIR="${HOME}/.config/systemd/user"
SYSTEMD_UNIT="${SYSTEMD_DIR}/open-design.service"
mkdir -p "$SYSTEMD_DIR"
CONTAINER_BIN="$(command -v "$CONTAINER_CMD")"
{
echo "[Unit]"
echo "Description=OpenDesign daemon (${CONTAINER_RUNTIME} Compose)"
if [ "$CONTAINER_RUNTIME" = "docker" ]; then
echo "After=${CONTAINER_RUNTIME}.service"
echo "Requires=${CONTAINER_RUNTIME}.service"
fi
echo ""
echo "[Service]"
echo "Type=oneshot"
echo "RemainAfterExit=yes"
echo "WorkingDirectory=${DEPLOY_DIR}"
# Build the compose -f list to match what the installer used at install
# time, so systemd restarts use the same file set (including the Linux
# host-network override when present).
_COMPOSE_ARGS="-f ${COMPOSE_FILE}"
if [ -f "$LINUX_OVERRIDE_FILE" ]; then
_COMPOSE_ARGS="${_COMPOSE_ARGS} -f ${LINUX_OVERRIDE_FILE}"
fi
if [ -f "$OVERRIDE_FILE" ]; then
_COMPOSE_ARGS="${_COMPOSE_ARGS} -f ${OVERRIDE_FILE}"
fi
echo "ExecStart=${CONTAINER_BIN} compose ${_COMPOSE_ARGS} up -d --no-build"
echo "ExecStop=${CONTAINER_BIN} compose ${_COMPOSE_ARGS} down"
echo "TimeoutStartSec=120"
echo ""
echo "[Install]"
echo "WantedBy=default.target"
} > "$SYSTEMD_UNIT"
systemctl --user daemon-reload
systemctl --user enable open-design 2>/dev/null || true
ok "systemd unit installed: open-design.service"
step "Manage with: systemctl --user start|stop|status open-design"
else
warn "systemd not found. Skipping service installation."
fi
fi
# ---------------------------------------------------------------------------
# 9. Summary
# ---------------------------------------------------------------------------
printf "\n"
printf "${BOLD}${GREEN} ── Installation Complete ──────────────────────────${RESET}\n"
printf "\n"
printf " URL: http://127.0.0.1:%s\n" "$PORT"
printf " Image: %s\n" "$IMAGE"
printf " Data volume: open_design_data\n"
printf " Config: %s\n" "$ENV_FILE"
if [ "$OS" = "Linux" ] && [ -f "${HOME}/.config/systemd/user/open-design.service" ]; then
printf " Service: systemd (open-design.service)\n"
fi
printf "\n"
printf " Next steps:\n"
printf " Update: %s/update.sh\n" "$SCRIPT_DIR"
printf " Uninstall: %s/uninstall.sh\n" "$SCRIPT_DIR"
printf " Logs: %s logs -f\n" "$COMPOSE_CMD"
printf "\n"
printf " Open http://127.0.0.1:%s in your browser\n\n" "$PORT"