From aafd1df98a4773d8b5a844489d065ddf5bf2b18c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 15 Sep 2026 04:53:05 +0000 Subject: [PATCH 1/5] Rebrand the Windows desktop installer as FreeOS and bundle openXYOS. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Wails/NSIS shell now ships as FreeOS (name, icons, installer strings, and FreeOS-desktop-windows-*-.exe artifacts). Desktop launch defaults to ~/.freeos / FREEOS_HOME, starts a bundled Node + openXYOS sidecar, and enables the organization module on first run so install → open is the product path. Co-authored-by: XYAI Labs --- .github/workflows/octop-desktop.yml | 35 +- README.md | 22 +- README_CN.md | 8 +- desktop/README.md | 100 ++++-- desktop/package-release.sh | 7 +- .../portable/AGENT_ELECTRON_INTEGRATION.md | 15 +- desktop/portable/README.md | 17 +- desktop/portable/_common.sh | 20 +- desktop/portable/bundle-org-sidecar.sh | 327 ++++++++++++++++++ desktop/portable/package.sh | 17 +- desktop/portable/templates/README.txt | 27 +- desktop/portable/templates/start.bat | 33 +- desktop/portable/templates/start.sh | 35 +- desktop/src/Taskfile.yml | 4 +- desktop/src/assets/freeos-logo.png | Bin 0 -> 21700 bytes desktop/src/assets/index.html | 30 +- desktop/src/assets/tray-icon-template.png | Bin 1641 -> 2967 bytes desktop/src/assets/tray-icon.png | Bin 8025 -> 18682 bytes desktop/src/build/appicon-macos.png | Bin 433408 -> 29008 bytes desktop/src/build/appicon.png | Bin 235931 -> 23213 bytes desktop/src/build/config.yml | 14 +- desktop/src/build/darwin/Info.dev.plist | 12 +- desktop/src/build/darwin/Info.plist | 12 +- desktop/src/build/darwin/Taskfile.yml | 4 +- desktop/src/build/linux/Taskfile.yml | 4 +- desktop/src/build/stamp_version.py | 6 +- desktop/src/build/windows/Taskfile.yml | 6 +- desktop/src/build/windows/info.json | 14 +- desktop/src/build/windows/nsis/project.nsi | 6 +- .../src/build/windows/nsis/wails_tools.nsh | 12 +- desktop/src/build/windows/wails.exe.manifest | 2 +- desktop/src/desktop_copy.go | 39 ++- desktop/src/desktop_copy_test.go | 6 +- desktop/src/download.go | 31 +- desktop/src/download_test.go | 8 +- desktop/src/embed_portable.go | 6 +- desktop/src/main.go | 28 +- desktop/src/preventsleep_linux.go | 4 +- desktop/src/process.go | 29 +- desktop/src/process_sidecar.go | 142 ++++++++ desktop/src/process_sidecar_test.go | 48 +++ desktop/src/settings.go | 32 +- desktop/src/settings_home_test.go | 46 +++ docs/assets/freeos-mark.png | Bin 0 -> 20611 bytes scripts/release_download_links.py | 8 +- src/octop/infra/server.py | 9 + src/octop/modules/org_os/service.py | 15 + tests/unit/desktop/test_stamp_version.py | 2 +- tests/unit/test_org_module.py | 17 + tests/unit/test_release_download_links.py | 26 +- 50 files changed, 1035 insertions(+), 250 deletions(-) create mode 100755 desktop/portable/bundle-org-sidecar.sh create mode 100644 desktop/src/assets/freeos-logo.png create mode 100644 desktop/src/process_sidecar.go create mode 100644 desktop/src/process_sidecar_test.go create mode 100644 desktop/src/settings_home_test.go create mode 100644 docs/assets/freeos-mark.png diff --git a/.github/workflows/octop-desktop.yml b/.github/workflows/octop-desktop.yml index 9a8239a5..8c75b142 100644 --- a/.github/workflows/octop-desktop.yml +++ b/.github/workflows/octop-desktop.yml @@ -1,5 +1,9 @@ -name: Octop Desktop Package +name: FreeOS Desktop Package +# Filename kept as octop-desktop.yml so auto-tag / existing dispatch keep working. +# Publishes FreeOS-desktop-* / FreeOS-portable-* (Windows NSIS .exe is the +# product installer: Octop shell + bundled openXYOS). +# # 正式发版:Auto Tag 在打 v* 后与 Release / Docker Publish 一并 dispatch。 # 产物 upsert 到同一 GitHub Release。手工补包:workflow_dispatch + release_tag # (不必跑在 v* ref 上)。attach_from_run 只挂已有 artifact、不重打。 @@ -11,6 +15,7 @@ on: pull_request: paths: - "desktop/**" + - "modules/openxyos/**" - ".github/workflows/octop-desktop.yml" workflow_dispatch: inputs: @@ -30,7 +35,7 @@ on: default: "" type: string attach_from_run: - description: "Existing workflow run ID — attach its Octop-* artifacts and skip the 6-platform rebuild" + description: "Existing workflow run ID — attach its FreeOS-* artifacts and skip the 6-platform rebuild" required: false default: "" type: string @@ -160,6 +165,12 @@ jobs: enable-cache: true python-version: "3.12" + - uses: actions/setup-node@v5 + with: + node-version: "20" + cache: npm + cache-dependency-path: modules/openxyos/package-lock.json + # GitHub Cache outages must not fail the build — PBS download is cheap enough. - name: Cache python-build-standalone downloads continue-on-error: true @@ -184,7 +195,7 @@ jobs: - name: Smoke import (native host only) run: | set -euo pipefail - staging="desktop/portable/release/Octop-${{ matrix.plat }}" + staging="desktop/portable/release/FreeOS-${{ matrix.plat }}" if [[ -x "${staging}/runtime/bin/python3" ]]; then py="${staging}/runtime/bin/python3" elif [[ -f "${staging}/runtime/python.exe" ]]; then @@ -209,6 +220,16 @@ jobs: PYTHONNOUSERSITE=1 \ "$py" desktop/portable/verify_imports.py \ "${verify_args[@]}" + if [[ ! -f "${staging}/org-sidecar/openxyos/backend/server.ts" ]]; then + echo "org-sidecar missing under ${staging}" >&2 + exit 1 + fi + if [[ -f "${staging}/org-sidecar/node/node.exe" || -x "${staging}/org-sidecar/node/bin/node" ]]; then + echo "org-sidecar Node runtime present" + else + echo "bundled Node missing under ${staging}/org-sidecar/node" >&2 + exit 1 + fi # macOS: fail if any native extension linked Homebrew/MacPorts paths. if [[ "${{ matrix.plat }}" == darwin-* ]]; then REPO_ROOT="$PWD" # shellcheck source=desktop/portable/_common.sh @@ -247,14 +268,14 @@ jobs: wails3 task package ARCH=${{ matrix.arch }} VERSION=${{ env.OCTOP_VERSION }} - PORTABLE_ZIP=../portable/release/Octop-portable-${{ matrix.plat }}-${{ env.OCTOP_VERSION }}.zip + PORTABLE_ZIP=../portable/release/FreeOS-portable-${{ matrix.plat }}-${{ env.OCTOP_VERSION }}.zip # archive: false — upload the prebuilt zip as-is. Default archive=true would # wrap it again, so Actions UI / "Download artifact" becomes zip-in-zip. # With archive:false, artifact name is the filename (name: is ignored). - uses: actions/upload-artifact@v7 with: - path: desktop/portable/release/Octop-portable-${{ matrix.plat }}-${{ env.OCTOP_VERSION }}.zip + path: desktop/portable/release/FreeOS-portable-${{ matrix.plat }}-${{ env.OCTOP_VERSION }}.zip archive: false if-no-files-found: error retention-days: 14 @@ -262,7 +283,7 @@ jobs: - name: Upload bundled desktop package uses: actions/upload-artifact@v7 with: - path: desktop/src/bin/Octop-desktop-${{ matrix.plat }}-${{ env.OCTOP_VERSION }}.* + path: desktop/src/bin/FreeOS-desktop-${{ matrix.plat }}-${{ env.OCTOP_VERSION }}.* archive: false if-no-files-found: error retention-days: 14 @@ -310,7 +331,7 @@ jobs: # zips into directories, so files: release-assets/* would skip them. - uses: actions/download-artifact@v8 with: - pattern: Octop-* + pattern: FreeOS-* path: release-assets merge-multiple: true skip-decompress: true diff --git a/README.md b/README.md index b54814e6..7b1c14bf 100644 --- a/README.md +++ b/README.md @@ -52,10 +52,25 @@ Operator detail: [docs/asset-loop.md](docs/asset-loop.md). ## Run FreeOS +### Windows 用户:下载安装包 → 安装 → 打开即用 FreeOS(Octop壳+组织能力) + +给非开发者的路径: + +1. 打开 [GitHub Releases](https://github.com/XYAIStudio/FreeOS/releases/latest),下载 + `FreeOS-desktop-windows-amd64-.exe`(普通 64 位电脑)或 + `FreeOS-desktop-windows-arm64-.exe`(ARM 电脑)。 +2. 双击安装包。安装程序会放到「程序文件」并创建开始菜单和桌面快捷方式。 +3. 打开 **FreeOS**。第一次启动会解压内置运行环境(可能要一两分钟),然后出现设置向导。 +4. 设好管理员密码后即可聊天。组织控制台(openXYOS)已随安装包内置,无需再装 Node;侧栏 **Organization** 默认打开。 + +数据目录默认是 `%USERPROFILE%\.freeos`(可用环境变量 `FREEOS_HOME` 改)。旧版 Octop 的 `~/.octop` 仍会被识别。 + +安装包由 CI 工作流 **FreeOS Desktop Package**(文件名仍是 `.github/workflows/octop-desktop.yml`,给现有发版脚本用)在 Windows runner 上打出来。本仓库的云环境打不出 `.exe`;合并后由该 job 产出。 + ### Prerequisites - Python 3.12+ (the project uses [uv](https://docs.astral.sh/uv/)) -- Node.js 20.19+ only if you start the organization sidecar or rebuild the dashboard +- Node.js 20.19+ only if you start the organization sidecar from source, or rebuild the dashboard. The Windows installer already bundles Node + openXYOS. ### From this repository @@ -133,7 +148,7 @@ Inbound import compiles the blueprint **and** registers a FreeOS agent (`org-pol ## Host platform features (from Octop) -Unchanged in this fork: multi-user JWT, multi-agent chat, expert library, connectors, ACP, knowledge bases, bundled plugins, cron, terminal/browser/desktop surfaces. Upstream-oriented installers and desktop artifacts still refer to **Octop** and `~/.octop/` — use `freeos` / `FREEOS_HOME` here. +Unchanged in this fork: multi-user JWT, multi-agent chat, expert library, connectors, ACP, knowledge bases, bundled plugins, cron, terminal/browser/desktop surfaces. The **Windows desktop installer** is branded **FreeOS** and defaults to `~/.freeos` / `FREEOS_HOME`. The Python package, `octop` CLI alias, `OCTOP_*` env vars, and `octop.db` stay for compatibility. Longer host docs: [docs/user-guide.md](docs/user-guide.md), [docs/configuration.md](docs/configuration.md), [docs/architecture.md](docs/architecture.md), [README_CN.md](README_CN.md). @@ -151,7 +166,8 @@ Intentional for package compatibility (not a rebrand miss): - Python package name `octop` and `import octop` - CLI `octop` alongside `freeos` -- `OCTOP_*` environment variables +- `OCTOP_*` environment variables (desktop also sets `FREEOS_HOME`) - SQLite file `octop.db` inside the home directory +- CI workflow **filename** `.github/workflows/octop-desktop.yml` (display name is FreeOS Desktop Package) See [docs/architecture-integration.md](docs/architecture-integration.md) for the control/data-plane split and the running self-growth loop. diff --git a/README_CN.md b/README_CN.md index 5607704a..8b475e66 100644 --- a/README_CN.md +++ b/README_CN.md @@ -204,13 +204,13 @@ curl -fsSL https://finnie-1258344699.cos.ap-guangzhou.myqcloud.com/octop/install 完整安装选项见 [scripts/README.md](scripts/README.md)(`--version`、`--from-source`、`--mirror` 及 Windows 参数)。 -**桌面客户端**(图形界面,无需终端)— 从 [GitHub Releases](https://github.com/TencentCloud/Octop/releases/latest) 下载对应平台的安装包: +**Windows 用户:下载安装包 → 安装 → 打开即用 FreeOS(Octop壳+组织能力)** — 从 [GitHub Releases](https://github.com/XYAIStudio/FreeOS/releases/latest) 下载: | 平台 | 制品 | |------|------| -| Windows | `Octop-desktop-windows-amd64-.exe`(64 位)/ `Octop-desktop-windows-arm64-.exe`(ARM64)— NSIS 安装程序 | -| macOS | `Octop-desktop-darwin-arm64-.dmg`(Apple 芯片)/ `Octop-desktop-darwin-amd64-.dmg`(Intel) | -| Linux | `Octop-desktop-linux-amd64-.tar.gz` / `Octop-desktop-linux-arm64-.tar.gz` | +| Windows | `FreeOS-desktop-windows-amd64-.exe`(64 位)/ `FreeOS-desktop-windows-arm64-.exe`(ARM64)— NSIS 安装程序,内置 openXYOS,打开即可聊天+组织 | +| macOS | `FreeOS-desktop-darwin-arm64-.dmg`(Apple 芯片)/ `FreeOS-desktop-darwin-amd64-.dmg`(Intel) | +| Linux | `FreeOS-desktop-linux-amd64-.tar.gz` / `FreeOS-desktop-linux-arm64-.tar.gz` | | 飞牛 NAS(FnOS) | `Octop-fnos-docker-.fpk`(依赖 Docker)/ `Octop-fnos-native-.fpk`(无需 Docker)— 通过应用中心安装 | 桌面客户端说明见 [desktop/README.md](desktop/README.md),飞牛打包指南见 [fnos/README.md](fnos/README.md)。 diff --git a/desktop/README.md b/desktop/README.md index 51895384..64098ec6 100644 --- a/desktop/README.md +++ b/desktop/README.md @@ -1,44 +1,68 @@ -# Octop desktop (Wails v3 + green portable) +# FreeOS desktop (Wails v3 + green portable) -All desktop-client code lives here. This is **not** `src/octop/infra/desktop` -(remote desktop streaming). +Windows end-user product: **download the NSIS `.exe` → install → open FreeOS** +(Octop host shell + bundled openXYOS organization console). This is **not** +`src/octop/infra/desktop` (remote desktop streaming). | Path | Role | |------|------| -| [`portable/`](portable/) | Green zip packaging (was `scripts/green/`) | -| [`src/`](src/) | Wails v3 shell: load bundled zip, spawn Octop, tray/settings | +| [`portable/`](portable/) | Green zip packaging (CPython + host wheel + Node/openXYOS sidecar) | +| [`src/`](src/) | Wails v3 shell: extract zip, start host + sidecar, tray/settings | | [`package-release.sh`](package-release.sh) | Native end-to-end portable + Wails release build | ## Data directory -Same as the Octop CLI/server default: +Same precedence as the FreeOS CLI/server: -- `OCTOP_HOME` → `~/.octop` (or the existing `OCTOP_HOME` env) -- Green runtime extract → `~/.octop/portable/` -- Shell prefs → `~/.octop/desktop-settings.json` +1. `FREEOS_HOME` +2. `OCTOP_HOME` (legacy) +3. existing `~/.freeos` +4. existing `~/.octop` +5. new installs → `~/.freeos` + +- Green runtime extract → `{home}/portable/` +- Organization sidecar data → `{home}/org-os/` +- Shell prefs → `{home}/desktop-settings.json` + +## What the installer starts + +On first open the shell: + +1. Extracts the bundled portable runtime (Python host + Node + openXYOS). +2. Starts the openXYOS sidecar on `http://127.0.0.1:3780` when `org-sidecar/` is present. +3. Starts the FreeOS host with `FREEOS_HOME` and `FREEOS_ORG_ENABLE=1`. +4. Opens the desktop window on the host UI. After the setup wizard, chat and + **Organization** are both available — no separate Node install. ## Build green zip -From repo root: +From repo root (needs Node 20+ so openXYOS can be built into the zip): ```bash make -f desktop/portable/Makefile green ``` -CI: `.github/workflows/octop-desktop.yml` builds native platform/arch variants. -`v*` tags and `workflow_dispatch` (platforms=`all`) run all six; pull requests -that touch `desktop/` build `darwin-*` and `windows-*` (amd64 + arm64). Each job -first creates the green zip and then packages the matching Wails application. +Skip the sidecar only when debugging the Python runtime: `SKIP_ORG_SIDECAR=1`. + +CI: `.github/workflows/octop-desktop.yml` (**name:** FreeOS Desktop Package) +builds native platform/arch variants. `v*` tags and `workflow_dispatch` +(platforms=`all`) run all six; pull requests that touch `desktop/` or +`modules/openxyos/` build `darwin-*` and `windows-*` (amd64 + arm64). Each job +creates the green zip (including the sidecar) and packages the Wails app. + +This cloud/Linux VM cannot emit a Windows `.exe`. The job that does is +**FreeOS Desktop Package** → matrix `windows-amd64` / `windows-arm64` +(`wails3 task package` + NSIS). ## Build a complete desktop release Run the end-to-end script on the matching native host. It builds the Dashboard, -creates and verifies the portable runtime, embeds it into Wails, and produces -the final native package: +creates and verifies the portable runtime (with openXYOS), embeds it into Wails, +and produces the final native package: ```bash desktop/package-release.sh -# Reuse an existing desktop/portable/release/Octop-portable--.zip: +# Reuse an existing desktop/portable/release/FreeOS-portable--.zip: desktop/package-release.sh darwin-arm64 --reuse-portable ``` @@ -59,51 +83,59 @@ cd desktop/src go mod tidy wails3 build # development binary under desktop/src/bin/ wails3 task package ARCH=arm64 VERSION= \ - PORTABLE_ZIP=../portable/release/Octop-portable-darwin-arm64-.zip + PORTABLE_ZIP=../portable/release/FreeOS-portable-darwin-arm64-.zip ``` -Dev against an already-running Octop (skips the bundled green zip): +Dev against an already-running FreeOS host (skips the bundled green zip): ```bash cd desktop/src OCTOP_DESKTOP_URL=http://127.0.0.1:8088 wails3 dev ``` -Without `OCTOP_DESKTOP_URL`, first launch uses `~/.octop/portable/` if valid, +Without `OCTOP_DESKTOP_URL`, first launch uses `~/.freeos/portable/` if valid, otherwise extracts the matching zip shipped with the desktop package (embedded in the Windows and Linux binaries, under `Contents/Resources` on macOS). The -Wails shell never downloads Octop. For local runtime debugging, set -`OCTOP_DESKTOP_PORTABLE_ZIP=/absolute/path/Octop-portable--.zip`. +Wails shell never downloads the host over the network. For local runtime +debugging, set +`OCTOP_DESKTOP_PORTABLE_ZIP=/absolute/path/FreeOS-portable--.zip`. On later launches, a newer bundled portable version replaces the extracted -runtime after creating a consistent SQLite backup under `~/.octop/backups/`. -The upgraded Octop process then applies the normal database migrations during -startup. Newer extracted runtimes are never downgraded; PostgreSQL remains -externally managed and is not copied by the desktop shell. +runtime after creating a consistent SQLite backup under `{home}/backups/`. +The upgraded host then applies the normal database migrations during startup. +Newer extracted runtimes are never downgraded; PostgreSQL remains externally +managed and is not copied by the desktop shell. -GitHub Release names follow `Octop----.`: +GitHub Release names follow `FreeOS----.`: -- Desktop GUI: `Octop-desktop--.dmg` (macOS; open and drag - `Octop.app` into Applications), `.exe` (Windows NSIS installer — copies - into `Program Files\Octop` and creates Start Menu + desktop shortcuts), +- Desktop GUI: `FreeOS-desktop--.dmg` (macOS; open and drag + `FreeOS.app` into Applications), `.exe` (Windows NSIS installer — copies + into `Program Files\FreeOS` and creates Start Menu + desktop shortcuts), `.tar.gz` (Linux) -- Green runtime zip: `Octop-portable--.zip` +- Green runtime zip: `FreeOS-portable--.zip` - PyPI wheels stay `octop--py3-none-any.whl` (PEP 427) +Older `Octop-portable-*` / `Octop-.zip` files beside the binary are still +accepted when extracting a local/dev build. They are **not** published anymore. + The Linux tar.gz contains only the GUI binary; it has no separate portable zip or -server terminal process. Runtime upgrades remain owned by Octop: -the shell sets `OCTOP_GREEN_PACKAGES`, so `octop update` upgrades the extracted -`packages/` directory through Octop's existing `--target` logic. +server terminal process. Runtime upgrades remain owned by the host: +the shell sets `OCTOP_GREEN_PACKAGES`, so `octop update` / `freeos update` +upgrades the extracted `packages/` directory through the existing `--target` +logic. Linux also needs GTK4 + WebKitGTK 6 to link. macOS 12+. ## Icons +FreeOS circular mark (gray ring, yellow / green / red teardrops, blue center). + | File | Used for | Rule | |------|----------|------| | `src/build/appicon.png` | Windows `.ico`, Linux | Full-bleed 512x512 artwork | | `src/build/appicon-macos.png` | macOS `.icns` | 1024x1024 canvas, artwork 824x824 centred | | `src/assets/tray-icon.png` | Tray + app icon on Windows/Linux | Full-bleed | | `src/assets/tray-icon-template.png` | macOS menu bar | 88px canvas, 64px black-on-transparent glyph | +| `src/assets/freeos-logo.png` | Splash / loading | Transparent circular mark | macOS sizes both surfaces to a fixed box, so the padding has to live in the artwork: the Dock follows Apple's 824/1024 icon grid, and Wails scales the menu diff --git a/desktop/package-release.sh b/desktop/package-release.sh index 3d8e16d9..9a856b5b 100755 --- a/desktop/package-release.sh +++ b/desktop/package-release.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash -# Build one native Octop desktop release: Dashboard → portable runtime → Wails package. +# Build one native FreeOS desktop release: Dashboard → portable runtime +# (including bundled Node + openXYOS) → Wails package. # Run once per native platform; the GitHub Actions matrix runs all six variants. set -euo pipefail @@ -22,7 +23,7 @@ Platforms: windows-arm64 windows-amd64 The platform defaults to the current native host. --reuse-portable skips -rebuilding desktop/portable/release/Octop-portable--.zip when it already exists. +rebuilding desktop/portable/release/FreeOS-portable--.zip when it already exists. EOF } @@ -80,7 +81,7 @@ else bash "${REPO_ROOT}/desktop/portable/package.sh" "$plat" fi -staging="${REPO_ROOT}/desktop/portable/release/Octop-${plat}" +staging="${REPO_ROOT}/desktop/portable/release/$(portable_staging_name "$plat")" requirements="${REPO_ROOT}/desktop/portable/requirements-${plat}.txt" if [[ "$plat" == windows-* ]]; then portable_python="${staging}/runtime/python.exe" diff --git a/desktop/portable/AGENT_ELECTRON_INTEGRATION.md b/desktop/portable/AGENT_ELECTRON_INTEGRATION.md index 2fdee543..f94fb0d8 100644 --- a/desktop/portable/AGENT_ELECTRON_INTEGRATION.md +++ b/desktop/portable/AGENT_ELECTRON_INTEGRATION.md @@ -9,7 +9,7 @@ path (no system Python, no `PYTHONPATH=packages`). CI / `make -f desktop/portable/Makefile green` produces: ``` -desktop/portable/release/Octop-portable--.zip +desktop/portable/release/FreeOS-portable--.zip ``` Platforms: `darwin-arm64` `darwin-amd64` `linux-amd64` `linux-arm64` @@ -18,9 +18,10 @@ Platforms: `darwin-arm64` `darwin-amd64` `linux-amd64` `linux-arm64` Layout after extract (outside asar): ``` -Octop-/ +FreeOS-/ runtime/ portable CPython packages/ site-packages + org-sidecar/ bundled Node + openXYOS launch.py start.sh / start.bat ``` @@ -32,11 +33,11 @@ Octop-/ beside the executable (Linux); the desktop shell does not download it at runtime. 2. Extract to a writable user directory **outside** `app.asar`. 3. On macOS, after checksum: `xattr -dr com.apple.quarantine `. -4. Set `OCTOP_HOME` to Octop's default data dir (`~/.octop`, or `$OCTOP_HOME` - if already set). Do **not** use the zip's `./data` folder when launching - from the Wails desktop shell (`desktop/src`). - Extract the zip under `~/.octop/portable/` so runtime files stay next to - user data without overwriting `octop.db`. +4. Set `FREEOS_HOME` (and `OCTOP_HOME` to the same path). Default is `~/.freeos` + unless `OCTOP_HOME` is already set or `~/.octop` already exists. + Do **not** use the zip's `./data` folder when launching from the Wails + desktop shell (`desktop/src`). Extract the zip under `{home}/portable/` + so runtime files stay next to user data without overwriting `octop.db`. 5. Set `PYTHONNOUSERSITE=1` and `OCTOP_GREEN_PACKAGES=/packages`. **Do not set `PYTHONPATH`.** diff --git a/desktop/portable/README.md b/desktop/portable/README.md index eb76aa6c..1bd2142c 100644 --- a/desktop/portable/README.md +++ b/desktop/portable/README.md @@ -1,8 +1,8 @@ -# Octop 绿色便携包(多平台) +# FreeOS 绿色便携包(多平台) -解压即用:内置便携 CPython + Octop 及依赖,通过 `start.sh` / `start.bat` 启动。 -**不依赖**系统 Python,也**不包含** Wails / 桌面壳——用浏览器打开 Dashboard。 -首启走上游正常 setup wizard(本目录**不含** OOB / UI 裁剪)。 +解压即用:内置便携 CPython + FreeOS 宿主 + Node/openXYOS 组织边车,通过 `start.sh` / `start.bat` 启动。 +**不依赖**系统 Python / Node。单独解压绿包时用浏览器打开 Dashboard;完整「安装即用」产品是 Wails NSIS 安装包。 +首启走正常 setup wizard。 ## 与上游解耦 @@ -29,12 +29,13 @@ make -f desktop/portable/Makefile green ## 产物布局 -公开文件名:`Octop-portable--.zip`。zip 内目录仍是: +公开文件名:`FreeOS-portable--.zip`。zip 内目录是: ``` -Octop-/ +FreeOS-/ runtime/ # python-build-standalone - packages/ # Octop + 依赖(site-packages,可搬迁) + packages/ # FreeOS 宿主 + 依赖(site-packages,可搬迁) + org-sidecar/ # 内置 Node + 已构建的 openXYOS launch.py # 启动引导(site.addsitedir / Windows pywin32) start.sh # macOS / Linux start.bat # Windows @@ -114,5 +115,5 @@ Actions 使用 GitHub 上游 PBS(`PBS_BASE_URL`),本机构建默认 npmmir ## Electron 壳 -壳只消费 `Octop-portable--.zip`(zip 内仍是 `Octop-/`),不要把绿包编进 asar。步骤见 +壳只消费 `FreeOS-portable--.zip`(zip 内是 `FreeOS-/`),不要把绿包编进 asar。步骤见 [`AGENT_ELECTRON_INTEGRATION.md`](AGENT_ELECTRON_INTEGRATION.md)。 diff --git a/desktop/portable/_common.sh b/desktop/portable/_common.sh index 8963b9fe..1f1cecbd 100644 --- a/desktop/portable/_common.sh +++ b/desktop/portable/_common.sh @@ -23,16 +23,22 @@ ALL_PLATS=( windows-arm64 ) -# Public GitHub Release names: Octop----. -# Zip payload directory stays Octop-/ (the desktop unzip strips the first -# path component). PyPI wheels keep the PEP 427 name and are not renamed here. +# Public GitHub Release names: FreeOS----. +# Zip payload directory is FreeOS-/ (the desktop unzip strips the first +# path component). Octop-* aliases are no longer published; the unzip step still +# accepts an Octop-/ prefix from older local zips. PyPI wheels keep the +# PEP 427 name `octop` and are not renamed here. octop_version() { sed -nE 's/^version[[:space:]]*=[[:space:]]*"([^"]+)".*/\1/p' \ "${REPO_ROOT}/pyproject.toml" | head -1 } +portable_staging_name() { + echo "FreeOS-${1}" +} + portable_zip_basename() { - echo "Octop-portable-${1}-$(octop_version).zip" + echo "FreeOS-portable-${1}-$(octop_version).zip" } desktop_pkg_basename() { @@ -40,9 +46,9 @@ desktop_pkg_basename() { local ver ver="$(octop_version)" case "$plat" in - darwin-*) echo "Octop-desktop-${plat}-${ver}.dmg" ;; - windows-*) echo "Octop-desktop-${plat}-${ver}.exe" ;; - linux-*) echo "Octop-desktop-${plat}-${ver}.tar.gz" ;; + darwin-*) echo "FreeOS-desktop-${plat}-${ver}.dmg" ;; + windows-*) echo "FreeOS-desktop-${plat}-${ver}.exe" ;; + linux-*) echo "FreeOS-desktop-${plat}-${ver}.tar.gz" ;; *) echo "unknown platform: ${plat}" >&2 return 1 diff --git a/desktop/portable/bundle-org-sidecar.sh b/desktop/portable/bundle-org-sidecar.sh new file mode 100755 index 00000000..d1055a7a --- /dev/null +++ b/desktop/portable/bundle-org-sidecar.sh @@ -0,0 +1,327 @@ +#!/usr/bin/env bash +# Bundle a Node runtime + built openXYOS into a green portable staging tree +# so the desktop installer can start the organization sidecar on a clean machine +# (no system Node required). +# +# Usage: +# bash desktop/portable/bundle-org-sidecar.sh +# +# Layout written under /org-sidecar/: +# node/ official Node.js runtime for +# openxyos/ built frontend (dist/) + backend TS + pruned node_modules +# start-sidecar.sh / start-sidecar.bat +# VERSION.txt +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +# shellcheck source=desktop/portable/_common.sh +source "${REPO_ROOT}/desktop/portable/_common.sh" + +NODE_VERSION="${NODE_VERSION:-20.19.5}" +NODE_BASE_URL="${NODE_BASE_URL:-https://nodejs.org/dist/v${NODE_VERSION}}" + +node_archive_name() { + case "$1" in + windows-amd64) echo "node-v${NODE_VERSION}-win-x64.zip" ;; + windows-arm64) echo "node-v${NODE_VERSION}-win-arm64.zip" ;; + linux-amd64) echo "node-v${NODE_VERSION}-linux-x64.tar.xz" ;; + linux-arm64) echo "node-v${NODE_VERSION}-linux-arm64.tar.xz" ;; + darwin-amd64) echo "node-v${NODE_VERSION}-darwin-x64.tar.gz" ;; + darwin-arm64) echo "node-v${NODE_VERSION}-darwin-arm64.tar.gz" ;; + *) + echo "unknown platform: $1" >&2 + return 1 + ;; + esac +} + +node_extract_dir() { + case "$1" in + windows-amd64) echo "node-v${NODE_VERSION}-win-x64" ;; + windows-arm64) echo "node-v${NODE_VERSION}-win-arm64" ;; + linux-amd64) echo "node-v${NODE_VERSION}-linux-x64" ;; + linux-arm64) echo "node-v${NODE_VERSION}-linux-arm64" ;; + darwin-amd64) echo "node-v${NODE_VERSION}-darwin-x64" ;; + darwin-arm64) echo "node-v${NODE_VERSION}-darwin-arm64" ;; + *) return 1 ;; + esac +} + +download_node() { + local plat="$1" + local archive dest url + archive="$(node_archive_name "$plat")" + dest="${GREEN_CACHE}/node/${archive}" + mkdir -p "$(dirname "$dest")" + if [[ -f "$dest" && -s "$dest" ]]; then + echo "[org-sidecar] reusing ${dest}" >&2 + echo "$dest" + return 0 + fi + url="${NODE_BASE_URL}/${archive}" + echo "[org-sidecar] downloading ${url}" >&2 + if command -v curl >/dev/null 2>&1; then + curl -fsSL --retry 4 --retry-delay 2 -o "${dest}.partial" "$url" + elif command -v wget >/dev/null 2>&1; then + wget -q -O "${dest}.partial" "$url" + else + echo "[org-sidecar] need curl or wget to download Node" >&2 + exit 1 + fi + mv "${dest}.partial" "$dest" + echo "$dest" +} + +extract_node() { + local archive="$1" + local dest="$2" + rm -rf "$dest" + mkdir -p "$dest" + case "$archive" in + *.zip) + if command -v unzip >/dev/null 2>&1; then + unzip -q "$archive" -d "$dest" + else + py="" + if command -v python3 >/dev/null 2>&1; then + py=python3 + elif command -v python >/dev/null 2>&1; then + py=python + fi + if [[ -z "$py" ]]; then + echo "[org-sidecar] need unzip or python to extract Node zip" >&2 + exit 1 + fi + "$py" -c "import shutil,sys; shutil.unpack_archive(sys.argv[1], sys.argv[2])" \ + "$archive" "$dest" + fi + ;; + *.tar.xz|*.tar.gz) + tar -xf "$archive" -C "$dest" + ;; + *) + echo "[org-sidecar] unsupported Node archive: ${archive}" >&2 + exit 1 + ;; + esac +} + +build_openxyos() { + local work="$1" + local src="${REPO_ROOT}/modules/openxyos" + if [[ ! -f "${src}/package.json" ]]; then + echo "[org-sidecar] openXYOS missing at ${src}" >&2 + exit 1 + fi + if ! command -v npm >/dev/null 2>&1; then + echo "[org-sidecar] npm is required to build openXYOS (Node ${NODE_VERSION}+)" >&2 + exit 1 + fi + echo "[org-sidecar] assembling openXYOS build tree → ${work}" >&2 + rm -rf "$work" + mkdir -p "$work" + # Copy sources only; install + build happen in the work tree. + if command -v rsync >/dev/null 2>&1; then + rsync -a --delete \ + --exclude node_modules --exclude dist --exclude .git --exclude .github \ + --exclude backups --exclude dist-backup-20260626 --exclude V0.5 \ + "${src}/" "${work}/" + else + python3 - "$src" "$work" <<'PY' +import shutil, sys +from pathlib import Path +src, dest = Path(sys.argv[1]), Path(sys.argv[2]) +skip = {"node_modules", "dist", ".git", ".github", "backups", "dist-backup-20260626", "V0.5"} +for item in src.iterdir(): + if item.name in skip: + continue + target = dest / item.name + if item.is_dir(): + shutil.copytree(item, target, ignore=shutil.ignore_patterns(*skip)) + else: + shutil.copy2(item, target) +PY + fi + ( + cd "$work" + echo "[org-sidecar] npm ci" >&2 + npm ci --no-audit --no-fund + echo "[org-sidecar] npm run build" >&2 + npm run build + echo "[org-sidecar] prune to production + tsx (sidecar starts via node --import tsx)" >&2 + npm prune --omit=dev --no-audit --no-fund + npm install tsx@4.19.2 --omit=dev --no-audit --no-fund --no-package-lock + ) + if [[ ! -f "${work}/dist/index.html" ]]; then + echo "[org-sidecar] vite build did not produce dist/index.html" >&2 + exit 1 + fi +} + +write_sidecar_launchers() { + local dest="$1" + cat > "${dest}/start-sidecar.sh" <<'EOF' +#!/usr/bin/env bash +# Start the bundled openXYOS sidecar (no system Node required). +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +if [[ -x "${ROOT}/node/bin/node" ]]; then + NODE="${ROOT}/node/bin/node" +elif [[ -f "${ROOT}/node/node.exe" ]]; then + NODE="${ROOT}/node/node.exe" +else + echo "start-sidecar: bundled Node missing under ${ROOT}/node" >&2 + exit 1 +fi +APP="${ROOT}/openxyos" +HOME_DIR="${FREEOS_HOME:-${OCTOP_HOME:-${HOME}/.freeos}}" +DATA_DIR="${FREEOS_ORG_DATA:-${HOME_DIR}/org-os}" +PORT="${FREEOS_ORG_SIDECAR_PORT:-3780}" +mkdir -p "${DATA_DIR}" +if [[ -f "${DATA_DIR}/sidecar.env" ]]; then + set -a + # shellcheck disable=SC1091 + source "${DATA_DIR}/sidecar.env" + set +a +fi +if [[ -z "${JWT_SECRET:-}" || -z "${COOKIE_SECRET:-}" ]]; then + JWT_SECRET="${JWT_SECRET:-$(openssl rand -hex 32 2>/dev/null || python3 -c 'import secrets; print(secrets.token_hex(32))')}" + COOKIE_SECRET="${COOKIE_SECRET:-$(openssl rand -hex 32 2>/dev/null || python3 -c 'import secrets; print(secrets.token_hex(32))')}" + umask 077 + cat > "${DATA_DIR}/sidecar.env" < "${dest}/start-sidecar.bat" <<'EOF' +@echo off +setlocal EnableExtensions EnableDelayedExpansion +set "ROOT=%~dp0" +if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%" +set "NODE=%ROOT%\node\node.exe" +if not exist "%NODE%" ( + echo start-sidecar.bat: bundled Node missing at %NODE% + exit /b 1 +) +set "APP=%ROOT%\openxyos" +if not defined FREEOS_HOME if defined OCTOP_HOME set "FREEOS_HOME=%OCTOP_HOME%" +if not defined FREEOS_HOME set "FREEOS_HOME=%USERPROFILE%\.freeos" +if not defined FREEOS_ORG_DATA set "FREEOS_ORG_DATA=%FREEOS_HOME%\org-os" +if not defined FREEOS_ORG_SIDECAR_PORT set "FREEOS_ORG_SIDECAR_PORT=3780" +if not exist "%FREEOS_ORG_DATA%" mkdir "%FREEOS_ORG_DATA%" +if exist "%FREEOS_ORG_DATA%\sidecar.env" for /f "usebackq tokens=1,* delims==" %%A in ("%FREEOS_ORG_DATA%\sidecar.env") do set "%%A=%%B" +if not defined JWT_SECRET ( + for /f %%I in ('powershell -NoProfile -Command "[guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N')"') do set "JWT_SECRET=%%I" +) +if not defined COOKIE_SECRET ( + for /f %%I in ('powershell -NoProfile -Command "[guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N')"') do set "COOKIE_SECRET=%%I" +) +> "%FREEOS_ORG_DATA%\sidecar.env" echo JWT_SECRET=%JWT_SECRET% +>> "%FREEOS_ORG_DATA%\sidecar.env" echo COOKIE_SECRET=%COOKIE_SECRET% +set "NODE_ENV=production" +set "PORT=%FREEOS_ORG_SIDECAR_PORT%" +set "DB_DIALECT=sqlite" +set "DATABASE_PATH=%FREEOS_ORG_DATA%\xiongyuan.db" +set "AIR_GAP_MODE=true" +if not defined ALLOW_PUBLIC_REGISTRATION set "ALLOW_PUBLIC_REGISTRATION=false" +if not defined SEED_DEMO_DATA set "SEED_DEMO_DATA=false" +if not defined CORS_ORIGIN set "CORS_ORIGIN=http://127.0.0.1:8088,http://localhost:8088,http://127.0.0.1:18900,http://localhost:18900" +cd /d "%APP%" +"%NODE%" --import tsx backend/server.ts +exit /b %ERRORLEVEL% +EOF +} + +copy_openxyos_runtime() { + local work="$1" + local dest="$2" + rm -rf "$dest" + mkdir -p "$dest" + for part in backend dist node_modules package.json; do + if [[ ! -e "${work}/${part}" ]]; then + echo "[org-sidecar] missing ${work}/${part}" >&2 + exit 1 + fi + if [[ -d "${work}/${part}" ]]; then + cp -a "${work}/${part}" "${dest}/${part}" + else + cp -a "${work}/${part}" "${dest}/${part}" + fi + done + # tsx resolve + package scripts; lockfile optional. + if [[ -f "${work}/package-lock.json" ]]; then + cp -a "${work}/package-lock.json" "${dest}/package-lock.json" + fi +} + +main() { + local plat="${1:-}" + local staging="${2:-}" + if [[ -z "$plat" || -z "$staging" ]]; then + echo "usage: bundle-org-sidecar.sh " >&2 + exit 2 + fi + if ! is_known_plat "$plat"; then + echo "unknown platform: ${plat}" >&2 + exit 2 + fi + if [[ "${SKIP_ORG_SIDECAR:-0}" == "1" ]]; then + echo "[org-sidecar] SKIP_ORG_SIDECAR=1 — not bundling openXYOS" >&2 + return 0 + fi + + local dest="${staging}/org-sidecar" + mkdir -p "$dest" + + local archive extracted inner + archive="$(download_node "$plat")" + extracted="${GREEN_CACHE}/node/extract-${plat}" + extract_node "$archive" "$extracted" + inner="${extracted}/$(node_extract_dir "$plat")" + if [[ ! -d "$inner" ]]; then + echo "[org-sidecar] Node extract missing ${inner}" >&2 + exit 1 + fi + rm -rf "${dest}/node" + cp -a "$inner" "${dest}/node" + if [[ "$plat" == windows-* ]]; then + if [[ ! -f "${dest}/node/node.exe" ]]; then + echo "[org-sidecar] node.exe missing after extract" >&2 + exit 1 + fi + elif [[ ! -x "${dest}/node/bin/node" ]]; then + echo "[org-sidecar] bin/node missing after extract" >&2 + exit 1 + fi + + local work="${GREEN_CACHE}/openxyos-${plat}" + build_openxyos "$work" + copy_openxyos_runtime "$work" "${dest}/openxyos" + write_sidecar_launchers "$dest" + + { + echo "platform=${plat}" + echo "node=${NODE_VERSION}" + echo "openxyos_bundled=1" + sed -n 's/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/freeos_version=\1/p' \ + "${REPO_ROOT}/pyproject.toml" | head -1 + } > "${dest}/VERSION.txt" + + echo "[org-sidecar] bundled → ${dest}" >&2 +} + +main "${1:-}" "${2:-}" diff --git a/desktop/portable/package.sh b/desktop/portable/package.sh index f78c9546..f7e141a9 100755 --- a/desktop/portable/package.sh +++ b/desktop/portable/package.sh @@ -11,13 +11,14 @@ # OCTOP_GREEN_OFFLINE=1 bash desktop/portable/package.sh # require local wheels # # Layout of each zip: -# Octop-/ +# FreeOS-/ # runtime/ portable CPython # packages/ site-packages (uv --target, relocatable) +# org-sidecar/ bundled Node + openXYOS (organization console) # start.sh / start.bat # README.txt # -# Public filename is Octop-portable--.zip (see portable_zip_basename). +# Public filename is FreeOS-portable--.zip (see portable_zip_basename). set -euo pipefail REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" @@ -84,7 +85,7 @@ assemble_one() { local plat="$1" local runtime="${GREEN_RUNTIMES}/${plat}" local wheel_dir="${GREEN_WHEELS}/${plat}" - local staging="${GREEN_RELEASE}/Octop-${plat}" + local staging="${GREEN_RELEASE}/$(portable_staging_name "$plat")" local zip_path="${GREEN_RELEASE}/$(portable_zip_basename "$plat")" local pyplat @@ -235,6 +236,9 @@ assemble_one() { cp "${TEMPLATES}/README.txt" "${staging}/README.txt" chmod +x "${staging}/start.sh" + echo "[package] ${plat}: bundling openXYOS sidecar" >&2 + bash "${REPO_ROOT}/desktop/portable/bundle-org-sidecar.sh" "$plat" "$staging" + # Windows: pywin32 DLLs must be findable next to python.exe (or on PATH). # --target installs leave them under packages/pywin32_system32 only. if [[ "$plat" == windows-* ]]; then @@ -252,6 +256,9 @@ assemble_one() { echo "pbs_tag=${PBS_TAG}" sed -n 's/^version[[:space:]]*=[[:space:]]*"\([^"]*\)".*/octop_version=\1/p' \ "${REPO_ROOT}/pyproject.toml" | head -1 + if [[ -d "${staging}/org-sidecar/openxyos" ]]; then + echo "org_sidecar=1" + fi } > "${staging}/VERSION.txt" rm -f "$zip_path" @@ -262,7 +269,7 @@ assemble_one() { ( cd "$GREEN_RELEASE" if command -v zip >/dev/null 2>&1; then - zip -qry "$zip_name" "Octop-${plat}" + zip -qry "$zip_name" "$(portable_staging_name "$plat")" else # Windows runners often have `python` but not `python3` / `zip`. py="" @@ -275,7 +282,7 @@ assemble_one() { echo "[package] need zip or python to create archive" >&2 exit 1 fi - "$py" -c "import shutil; shutil.make_archive('${zip_stem}', 'zip', '.', 'Octop-${plat}')" + "$py" -c "import shutil; shutil.make_archive('${zip_stem}', 'zip', '.', '$(portable_staging_name "$plat")')" fi ) echo "[package] wrote ${zip_path}" diff --git a/desktop/portable/templates/README.txt b/desktop/portable/templates/README.txt index d3e86635..536eb4f7 100644 --- a/desktop/portable/templates/README.txt +++ b/desktop/portable/templates/README.txt @@ -1,25 +1,29 @@ -Octop green portable package -============================ +FreeOS green portable package +============================= -Extract this zip anywhere. It includes a portable CPython runtime and Octop -dependencies. No system Python install is required. +Extract this zip anywhere. It includes a portable CPython runtime, FreeOS +(Octop-compatible host), and a bundled Node + openXYOS organization sidecar. +No system Python or Node install is required. Start ----- macOS / Linux: ./start.sh Windows: start.bat -Defaults: http://127.0.0.1:8088 data dir = ./data (OCTOP_HOME) +Defaults: http://127.0.0.1:8088 data dir = ./data (FREEOS_HOME) ./start.sh --home /path/to/data --host 127.0.0.1 --port 8088 -First launch follows the normal Octop setup wizard (create admin password). +First launch follows the normal FreeOS setup wizard (create admin password). +The organization module is enabled automatically; the sidecar listens on +http://127.0.0.1:3780 and the dashboard /organization page embeds it. Layout ------ - runtime/ portable CPython - packages/ Octop + locked dependencies (site-packages) - launch.py entry bootstrap (loads packages/ + Windows pywin32 DLLs) + runtime/ portable CPython + packages/ FreeOS host + locked dependencies (site-packages) + org-sidecar/ bundled Node + built openXYOS + launch.py entry bootstrap (loads packages/ + Windows pywin32 DLLs) start.sh / start.bat README.txt VERSION.txt @@ -35,3 +39,8 @@ Notes Windows: if import pywintypes fails, rebuild from a current green package (launch.py + pywin32 DLL copy). Do not set PYTHONPATH manually. + +Compatibility +------------- + OCTOP_HOME is still honored as a legacy alias for FREEOS_HOME. + The Python package and CLI remain `octop` alongside `freeos`. diff --git a/desktop/portable/templates/start.bat b/desktop/portable/templates/start.bat index bdf2d580..69866d2d 100644 --- a/desktop/portable/templates/start.bat +++ b/desktop/portable/templates/start.bat @@ -1,16 +1,18 @@ @echo off setlocal EnableExtensions EnableDelayedExpansion -rem Octop green portable launcher (Windows) +rem FreeOS green portable launcher (Windows) rem Usage: rem start.bat -rem start.bat --home D:\octop-data +rem start.bat --home D:\freeos-data rem start.bat --home .\data --host 0.0.0.0 --port 8088 set "ROOT=%~dp0" if "%ROOT:~-1%"=="\" set "ROOT=%ROOT:~0,-1%" -if not defined OCTOP_HOME set "OCTOP_HOME=%ROOT%\data" +if not defined FREEOS_HOME if defined OCTOP_HOME set "FREEOS_HOME=%OCTOP_HOME%" +if not defined FREEOS_HOME set "FREEOS_HOME=%ROOT%\data" +set "OCTOP_HOME=%FREEOS_HOME%" set "HOST=127.0.0.1" set "PORT=8088" set "EXTRA=" @@ -22,6 +24,7 @@ if /I "%~1"=="--home" ( echo start.bat: --home requires a path exit /b 1 ) + set "FREEOS_HOME=%~2" set "OCTOP_HOME=%~2" shift shift @@ -54,18 +57,21 @@ shift goto parse :help -echo Octop green portable launcher +echo FreeOS green portable launcher echo. -echo Usage: start.bat [--home DIR] [--host HOST] [--port PORT] [octop run args...] +echo Usage: start.bat [--home DIR] [--host HOST] [--port PORT] [freeos run args...] echo. echo Defaults: -echo OCTOP_HOME / --home %%ROOT%%\data -echo --host 127.0.0.1 -echo --port 8088 +echo FREEOS_HOME / --home %%ROOT%%\data +echo --host 127.0.0.1 +echo --port 8088 exit /b 0 :run -if not exist "%OCTOP_HOME%" mkdir "%OCTOP_HOME%" +if not defined FREEOS_ORG_ENABLE set "FREEOS_ORG_ENABLE=1" +if not defined FREEOS_ORG_SIDECAR_URL set "FREEOS_ORG_SIDECAR_URL=http://127.0.0.1:3780" +if not defined OPENXYOS_BASE_URL set "OPENXYOS_BASE_URL=%FREEOS_ORG_SIDECAR_URL%" +if not exist "%FREEOS_HOME%" mkdir "%FREEOS_HOME%" set "PY=%ROOT%\runtime\python.exe" if not exist "%PY%" ( @@ -82,7 +88,12 @@ rem Prefer launch.py (site.addsitedir + pywin32 DLL path). Do not set PYTHONPATH set "PYTHONNOUSERSITE=1" set "PYTHONPATH=" -echo [octop] home=%OCTOP_HOME% -echo [octop] http://%HOST%:%PORT% +if exist "%ROOT%\org-sidecar\start-sidecar.bat" ( + echo [freeos] organization sidecar → %FREEOS_ORG_SIDECAR_URL% + start "" /B "%ROOT%\org-sidecar\start-sidecar.bat" +) + +echo [freeos] home=%FREEOS_HOME% +echo [freeos] http://%HOST%:%PORT% "%PY%" "%ROOT%\launch.py" run --host %HOST% --port %PORT% %EXTRA% exit /b %ERRORLEVEL% diff --git a/desktop/portable/templates/start.sh b/desktop/portable/templates/start.sh index 8f4800d0..9065516b 100755 --- a/desktop/portable/templates/start.sh +++ b/desktop/portable/templates/start.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Octop green portable launcher (macOS / Linux). +# FreeOS green portable launcher (macOS / Linux). # Usage: # ./start.sh # ./start.sh --home /path/to/data @@ -7,7 +7,8 @@ set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -export OCTOP_HOME="${OCTOP_HOME:-${ROOT}/data}" +export FREEOS_HOME="${FREEOS_HOME:-${OCTOP_HOME:-${ROOT}/data}}" +export OCTOP_HOME="${FREEOS_HOME}" HOST="127.0.0.1" PORT="8088" @@ -17,6 +18,7 @@ while [[ $# -gt 0 ]]; do case "$1" in --home) [[ $# -ge 2 ]] || { echo "start.sh: --home requires a path" >&2; exit 1; } + FREEOS_HOME="$2" OCTOP_HOME="$2" shift 2 ;; @@ -32,17 +34,18 @@ while [[ $# -gt 0 ]]; do ;; -h|--help) cat <28KnVhHK(7(%+c-uwCf{)u-M zwHCAH-upbyIcJ}J_Ks9jk;OzOK?eW;Q(jJ59RT3KKj8pWWbl`PTZt6_Xhh3POKSLJ z9<6x$;A_qSr`(p-{)1LiawmDT!I$yylakUnwoypinjhn-)d_GuNF(_pNFsey(Hd!`eUX{z;P0SOOlcQ1bZ1_nfib+v+4mP}qIA6+jK8`v3iFa1o()$TJRIPYjgqE>Oe+r$08m$xS{?8w4k8>r z`Ufp9uj{DH%uI_!+E)9yDnmYE3=dmdCaEcp=uWXh*F3-_=M@GTGx zsTL#qHr2?WjrbqFx{)#Taek*p{gb@wi{8Ttekdmf0tV#y>{X~*eax;exPUtXRqt^S z)2UENm8a%>va3A=$mS}v?DKw`wB*knxHB^|m$|vSf0S;C`|oOKSXjT7mzM)Pgbexl z`J3TzBMWJU>(FCi(}Cd? zaBy%q4X6`5Om%1DXyt=KsTxGa!Y<-qEiakv;JQiTbYFi+5AcI<>hsLW+U4E^os92$+qZ_ofwcQ~MgvATLhs#Zl zii&bO121^Exrxh=tN@AX+jOFpzjq_kb~h7x4Q|SZ5II`-P$~fU8%kv`dvKKsm)mW@ zRkVNQS75+S1QRgQ($V=j=1Qi5gNOxmyOOEmAg)yz3R7n9uCNkDOI2!>|4^_L2sd3N ziTQ;miSrRra=?L)6x^0kKh#h#cm!hSM8u)4zFv^0iV-VeV@*xZ!otF>KYh_e<$CLm z(#HT&uvRY=8i9d^hUV_=9i&sKg`G5LA!VE1pT*FAL1aXWJM;Js!+q7No;+!I`wso< zpF$1hH&OYwxw%y0kb8hLa&knyl2P+4Utq|c0SPtA9~2C8DZE+Tvaa7BU`DPQz?DWVzF3*x!RV8 zq@b&-i(X9ZvyZfN@A^)KyPe(sb58{J$&LKm<41X^8p zUFZ6R*f%l#JwjRR+&nxQA`bI46Q-u72zo8|{54BQ_c5gWo&Lwmt;`)plocAxup)I^ zo*qu{iY*iv({{WytPlQfZs$;4U0r$b``2PKkKBbMTyB~vG4k>8u?G0sWjAbVAGvr` z>oCG*9fp@rSp>K8H9gVWi_O-GMjHM(cE%rTb-Oc$oRXhjN)!o~z zt5dJCvNrxpjhZ;Ux7_}BhsWO6*QrRSA{X)b9#bUToVZ6oCrybyVOWZekJPzN=aMjM zbgf7#BHOT|&fXC?UV}qG?*61I+W4pfZFkw&bnfdzdSg9GOSa>S;(K?Vr=+U7$|9h% zXjE?5zSK{X}Wj8~pI8BJLIn$)Qvc1PGEqk}iwQIkW zG3QaS#mKWWy^eagOq-vv;7a!MkGS_dS@oZ1scskAR*^J)y1#PH6!lN3TX1+sD}UY3 zsRVP7#*dWF)nNW@!s#HVsQAFi%X_7#tNUhO!GWF;;6Z3Li2eHYYpeIsqGz5;amGh* z_b#H1MQ?wp<9xA*ls>}B7+4H!*K1pb=!%&fN_bJB%)9evE+WiTNogah<;3E<=tb0=)NO*jMmcK)Uj zq@|@<(kDea{~!vUMnejwm&QSCX=_V-dbpt^Cnj!btf;^ch((CX4~7YV=~OAvLf+N& zEXr~?MU?Q(8^Loe=-}VKB5r$=eO--u+H2dx#=3$Q-~6NlM7Wt~3%T@|XTo$0;DC{! zs__OghbFK1qyl9zpZFKA8>gk~Ipdep_O2rW3Rb|A7F4I@w6Z1e;kjMNuVJp_t(4LyDJgd$7JN%u z$r=Fw2uHiEytZc#4L$<^GTC#ja?+gaDzTt-pw)DY!tRE9>@V5bVZCagXZe##ratw;b(bCjL?5UKZf%lj7k&%%LBlr1>IQT$%umJu0@F4&9txJ4 z=Wblx%RN#Di3wus1;QIwSwl-P}1x z!6LhrtDe)pIb0~vWW=xjIkJ;(IwUey&y`k@TUKT_F`6aJPl|y&{eFHx4Y09zety<) za5xNTHF(B_x*Cm|HmjH7PuqEBLm&?fSwf!YBqBbinfgav{x@rsMTG&0#NGM!-wZ?9W!}Lk0#0w$)2L(3Pjax(B8i=4AA`x;lE*pPavg9&gW1mhINW zUzQ=%G_amO&ri`5*x1#FED8Tg`|Wxm-o*1qLNyBw@ySJkf!pYx*B5z@+cg%E$gqaK z!|=-|d~9sfU48r?Phyhn z^d%j;pad(_^j}?^=sqw|Qnt3X?quUAMx4TlR_JqqaXDm6l4(&+P8ZB@(|4n9Yt3qj zd(F-wzkU1W2!qDu{F<>ta9(x3Iqq5$_i~s>%adxghImyHzfC4Yh;5F2)1*}{OdRuk zT_8)hmL+1lriinF<$R)Y9Qc4mp{b#fC=_rz-lu%E48pdoj2}PnX;A+#md~II#D?jx zB9lQ?t*x!cL|xXqO@CypuY%AjE+8;45?sNJyu3Vdy-KZ^o(&+8RWzVtwOZj=%qUsIQxo4dW%{eX`Scn>Zu=@XKZKc#?)T6T)(R0JDbG7V@(&6=u>C8Sh?q2Z`4Pk#Lm<35PpN`Ep&|uC)(;L?PNs^-++t+7 zqfLpsIWY>=nFPpU4Z-v)&TBhaXxTW7O~UiHGUx?sLQ;|eC6@T3fMMxhyVA0`bpbRD z%~tJfv7^R^Q`2f=cEfyjuzJun7ae#CcRk-9)Ja8PdH&{f_yKNh@PRMt^2&-Txbmm9 zNerrlLQq|*t1x&8guDCuA=u{TDu~^te6yj?j^)f9DJXqksM3Oh@3}o}`{map{&MRcl9OZn<9(5f zxXW#~DxD=A9qVviHbr)}sPIhyG2;4&m3v=s2!fL!&c2nUrKRmFB6bm0)IZ#al4_%c zw33pNgW#zlbGbfTFynEnc~%2T7|5W%hldpj@$l*uDz&;c+qZTFMh{&CW&_U$84B#0 z&$})shsh~}0xo}i`O>#*J;LSS<|=PLxQ_q;W6`-4O4<$9s_V$!H~fPeJrSJ~^JA`O zfBM#9vxJ(s%c)q#*5b}6tDjsYNE&)gMIapoizySs(OQC?hMAMdjUZ4V_M z(a3zyz8w{YV}Ztsic3-3?k#=7AP%?S<_?<_f1WqLZ*_kx?RS_E_d1;Cr%x(P+#q(^ zNdJ1!E7jZcPrxsqw|jAlm0!`}3#LvY(kovWF$EI?QdE(|-zZ7*sqsmSJ+w!}XW@>V z)yB;}0##CsaB6<-W-qCTr)EfPLj#ZH{pFrGOY+bhYUp?kYqU8rGIE&d$V7oGQEqA9 zf2T&&$KM~XKqf{{I&mulIV=RkL){ncn$>e%&3;$=ufbZrc=4AEdx$LPtmQeH~PkC9OW;MDm zk~|&{7V&y80v13ueMVt2T1|y5g4zuu8-?hv=;pBIluujjuLz3ndJsSL{?o_Go;Cc&LR&pJ7g2re z;W}e1G6BxOKVQ(w?Ndd4P6y^#A7cHL{?=}ArG1#>;(ZSCf)HOH?$wQs97!3lrhN0e z{3q7>E>7Hi=5Mj8cz~ato{3((q~rJ*Vx0SYhvm(0Br>^d#8OGUW4{`^>;hEJ2@?Y+-F3x727qmmpI;jev+0y!0(L_WEG4@qj&| zXJSxLQxoS)hpO0}g;&?-C!;b$uangvG5SI5_D$GWEK#K1eKuvc8Cu9lMnQ_lp#W4| zNa#L=8|&nQ!-tE?&PhFu}!N=*x9^9J1k_$L*F~_E2+tI7rG^Z$_~h74v~E?VRaaNHm_uVvT*>AJ)5g_V z35_YZEC%*6RKW%hZx*)~MeOX%T5oWW5{{H#hW&Wn)hQ|S5dbdMGP_FZ%q^XxOMK~f z)0nuoJO>UExLK@WccZ%~PtQvy{5%-_y}e4lw;K_v@Sg0fru-0jE0(8 zV?=%M0S6sumiUN&d9>t>91DYG8Y;Q9wu`W`9zN8XLpyaLhm=7M`a^ZY%MVw=yjrw& zcf+3{h6v)%#O=9K5p#hB1^)blgV@07Xsx%(WT%HUoP8&<3Kon?DoOH}vi!p-<}hiU zvbbZL>0$Tn87$R%Z*rSIO({hxkyZnnkmNNvdB=254?^IaGh7E#GpEgX?z2#q69Lqf z7V4U-#_03)eXSW7*p##E-q(k%Y@w$iss;v|j0q*%5m!QFRj07HXJ=%j5x1D==+CvK z*}JX9Ij<`B4SDC;o9)y01)YkOw2+bT&Ox zOjfYII$3)WACvDeb4g-jRy85K^TooHLxgZw| zDPdqjeWvm^r@}m>xd%D)ry-w(+>^}AN@YTiY;2?N(t9$C;SMLrJl380x$#~1W5_+& zKV{g7H~E}x^0E$h7HgGFM+z2-3gdk_cB_!lmKVhRGK>8$2 zJrRk9N6!e7NhS5mXE27@lZA`L)sF=#Dhz|?gp>iJuDAl8%r4Cu_bD59yE)Hor8yiO zA|1F{I{}^*Pg&qOwK(oxHrg zy96oyGQ!~{c)z|6xjI_9c^%z{iw%JMREb8}If(cw!%(oR8YHWNv0=go=grtW2G-C8#Zo9DMuy^dMfOUOIl$ zN#aMdelPF>5;TT7I`foR5n)7#a3J#dk&@EM>$KdGuK04a^r9#7Ip8q9FPcDjdjCgC zg&Qvh1~Spb&02Zt$j>vYF#1ai3#;shwFdswE)m#Dvh`T@(nNe9CVCi2lF5GfOyizK z3Ae;`?BPH2YLV-JKM7ybSwcW4)s>Kx#Qljn;Z$S{Bzf9%a@Ou^YB-&(Hz%5~I$i;} zQ+v{){?`HZ`T6kDU_0kZHegZr9Sk79>CVeJn`tqQ_S_6oVxOT7x`Tr)7rFT`6yAeZ z=zhEcByJwb9w1ImP8y1{&5aTu!wipr@I;F{7#&Ilj9pNr4*%}! z+fD+@AV;a;@pv9D3T0q}2Xbt4KmjEn;nno8T~QHf{rPsIoCS0B4NWh>a<_KbJVl)M zH`HjmfvWbb=CE<}su%knz%6`NDbReEG-!HN#C5Ybd3AO5fB+roROEz=PM{OaU`J@7 zHl>SAB#vSLSZSjlu8$x?R`nnMN7+i^2)B72>}W%l(|Lz;(s@cxX&Qy9H6PvP9h_?H zQ+F~pdKgq}{&k{UpBP^aJIhw@Xq3=x6Kc=2#Y7~HW{cLhc0RxmvBq%~U$00e;jP(?z(i54L3_#S$w}9Iec;1&&xygk43lkI>dY#h z)z1nuPGTlCAEiw1^Xa>;llEOtjIm&V4MIIp=|YUs`nk2!8h+Gc3F7WNg7GIfIJgAt zjX2IPFA=TbUnj`%P%|iVo*T!2hD>2`p54ny5`>XbhC$#s4lkR~GaM5raz!r(Wm>XSD z?yC7uI^0MysGc=T`qu`DnQvvfU70W2`R@8^YHE*XFldsZf`Szu;%_`eIIH@!@3FB) zq$DKF&V%<&4O(|^`BK-{*HzHJnpIq^j`#KTiC7NdXQifUkBFX~!HPfsTew*M=XG@6 zp;uS+68D8gJ2b8jT7B4TJB7W6x#GXlcEwTgP&ANkl%EuW3NQv|a`E~y4b$wOZ?jzO zW5(|eeR36ca&Vc+kgsCb_OCk`bY1dxsMa+y1o;E2k#qu89O-oKm<%e^4g`33p=Jw1 zeZ34!0Blw@EG)JT=#$V~$iRbsWY>5f@$=_T(*CiNrDh9f505>8IwaYS8LN8V?k30k z8?jrHt0nLHuHRehZ|+*?v}n*ko*lV+Qh<1Ug1_mp=>d9AM)M7g%Ur8sn?Ys06?__j@(Ot+uJ;+;w-5hv%ZZI$Fe}Jec+SJ2QF*24UDHNp-{EryK z|4^3elngXHI`Clt!UqoV}hq%KGRZMyi0=|1_$+wFu0 zN0ZaA`JgP!H{%;@M`F-3RAymGyJok!8N?}oKnF+n(byvJBjFN5lhwHT?r0X&$H#}P z*LC?jOkm!D@AT#-NTGPDaZv0x-y1;4b07b3W8*B;{S8O*FQJxzc|4IJyuT2Zp~) zgNCTSdd4dNggoHb$4n>XiU1K@CJXsQ5k9NGKS?`ji8!i8Q89SD`p;5EVL@3`>+wGs zAKr_-r!6$xbqJn>jkNaEVBZU7g~<@xce2-q(q=d;7>H*s7{y&-gQK2zsAv+~KGM z1ayvfcKE)V5|>ciCQWq)KGZE*g3T&qHv(%$yg;Kaov?I^O@K^qzICehtbVNXt}5># zTfo)qDIJq8e7N;<2vuks-tP^d5Ba6LH=6x(gmb!~cfDY(`zGx|B6npgIoPDp*O#J4 z;EO#hmRGNEWza>4arg(Y7l&MtG5-zcCzCm&Bkj;Ge$ zY<<&4Jv{kD#8{XFd+H51s5w!abEVL5WCs}Wqr|)*hJP+ZzV(^8xVpA#J)Ct0n$P6r zLCsf|=eyftCve$~`)?xq(vh3n`g$+bUR>kd*xsSYEWblyMFwak+_K9L$HKVA_NIzw z7i6 zE{@G7I4Thl1Nziq|0$jrZHCx(ViFR)soz0hm#=JTxg7wF{qPRa)S34L6Dq%cX?qs? zJB-r*SKfoyzsCW9MbNL-h~up!e6w*u;XQM=S&>Whzl6DAjO=F2P`n;`WE|^n;vWnS zMr${y$bDRW&ZO3yp0DMMj8^4$ujl9Iy+p=x7*OGD()*dfI**Czs98^zp;vyF$d5uv znS)cRu?UimyOWbIAhGM3a&cB5i`N`SvOPQ2Q!q&m)gQ~}@`A7!v~*{$)?1%6V=g$) zjdrEH__rQ(8-zu~Iu8gcz;9``*?v+mgFbaQ-;}jQ#gL18Sq^l)?ef(VAyu!mf|{}Z zXifEP5j(ygxMhH1yE;+w+D(&cmGeqcCji5B!KOMtWD0CQT_7xK0OK}1fpFb(X$NB~ zE263e6SqQ?C(EshUC&SCFgje>@-{4N=IQ_gTQ7@fj zih_@YfpcJIZ|@sZHce>CxD|YGbo49s)M0a7`I0O0Rh4JHmzB2WM@EbSS``P zkbHSzVKRv?LuxHPaP_nG6?fnA)bJHwGWzijdKNq*FC%T=<<@p|YJi5d-x(Y>_D{i9 zoAa-^nYrx{pA@SebCGPypKIU*cC0hqL#)(_?1P#bP7P~@3=X0TSp>*GWP`w{()-IN z8hdUyDo#!{2HeQd%<}$!SNDNu;A<4hVc7P#J7?V0YE;hqkF&z_%F(a|@HaFNX}DtR zvc9Rh`7F9@si9KjOhvOk2YuwH4eSC(o8kJ3 z9`~6iHw7}oL~JQkuL>weheq1Amc_tC#{^O!&2qGEMo71}cXxLU7KRz0%J1{}>9SOa zfM1E+3rf|S~fTS7rWrIhj{`eP&Fz-c?i`8n<0yLV^i2cMp^yi>3O zcBj6Vu?Ak6^C|W(%M36tqhKKuEB{I-PZ1fAo^DY@8ZeY+Q1pe3)7DK;aGKD zt=VH0`;)T_@RJNbTm9YV->IbRM6sm&{Wn^D;J8kf&8}Gv-<8oUI=0hXQ$HMNfBOfg z>--=&Xqw_p$<$s2{)aOV-UBubdXDXK({28IB#IfRfKV2eHgmElV%|sg^jQB4+RMf=viJo6eNgRFqiOFOZ#`jNSDuqC!>6!rFh|?6iVB$4-+V zM+c6^$S*#ZqrvxUYuODO`5%R8!(gyqYh`~yR7`Ze&ut>{V02O;`UAfZa-{R(9|9Ll zOtq0;%~~@&G{Vuv2^l2`bv4LOWpZ9(33Ge`OA85k%jBV(0y zfz%&M37NEv9xj%259|M07-8{E*R?#2I~RNM=5O^tmnpAyq;!6DHVIZJ)qq^01Kt5@ zQ!+3PSB3z9GlQhT=cF|j0RigoFb_US8_{>cFuCh<%QMM@Z*7)c9u5u-_cL>Im245a z+Io)DSA;LWWZ?eA&>B>daJ$d`S!;@+5Um@Z*duAB84!w} zTd2f_XJGX_UaA<7<%kCWV5Xb0R>`@$J=|PCX?t2elB(=j?Rk^_{i8##H{FRsjo3=KtHw_XMe1Lh?dOg|C~+>kQGl=S-%`tSq4sd10}`~j8=z!GoUL7YU0Nz1SXX zJY6j~F2i7}8M}hmC-=T{sgzuz)4j15(K>A>i7h#s9-e|(9qH{B8ZfJX`P6Ri_7V>nP70nCul7^6Pe8~ zfb%s%^y}ziqBfK4&J?VFZofO5pI_Zy!xpFs=%_xyTnNk{g;TS*9?w#6JzJ?UJ-WDB z*xqBvdG_$VJZImpp5gG-Z_j88Z>BYYXYBY9f-yY)A1R>+y=brX8v!zD<&8nT6GWlQ zOWb!IAy|q26%OKW%=LS&b?Ss~uAZJ9mOYx1lD}uBiV1?;$RyYH&vyQB+KIi*gXMrd z{L4SBI1c?LGo%6Y{{4~7@R4(SC3xT)++V`i0^fc{0*QXcbFxsGu9k|MI`+lt#;JxB zLOVRvkoix8#iIf*3!rkuG?dB$CV(|V1Miz+>B#n}b>{*+Okuy(23#}K{!?8=HRP}$4&C$`( zT@euxJ%S|hwDP(-IyxTp+7)S{r*Pd+C^UmszO2n3WoT$+z~>sE~-+u=2aX#AX(O2(S*wK!_Da#6qTibxfDOg{c1I zF&&W&p=_0*l{r+a8Cm-9O}Cco4HIPJ0i4m;?A{=P+y_WXV5c{#v#p0<0|^NU&Iz=8 zS_hLVdfE#h&eXoYiq5*eSv%@%Ua*+mDc|;FW%*hl+YxlM>?<#4Xqm#AMmhF+RT9~{ zD@gHL2Nj@#=hTzuTryZHox!z~!hSs`?zHGIby#>hykQ+_MHcopY1m@bXqA5%eN#Y1 zi1b-7HVhO(Se2_Pg2zGvj2EI5OS&0(2>^N~{Osg)D8RwUGQ3Sa#=l0CxcebKnZW00 zC^;`Ej!e+Q)NnhWy1U|E;_wjZNxRuEwp`c&|0&Hf%YVZgyqKI=48+56V^Mm& zj95+aD4OR|nWz~Zm%nVMUMm^}1@<|nK6(k*kV0#(Q^YUI(E#|ku-O?FD%yr8!xe5y zd4c>KZrjoB?h}O60cAIHcf=4@)>nz>iRgCDH1c(99#3okEi`{n?t4+$J0CyxcyQM1 z7wjA!$qMQ-0v;-Tc8*u*AjcYfG@@)VmT$;7^E?Cmhykd%gj!km^75n5F6;8(Rxy3h zozo%2FpL8whoF8__{1d+H5SO;cK-SJ)pQ>7gb~_U)@rJp&l~VHeR_l;AdsfrfSu5B zW&bJW_xePkBOGBg|KpYR35S_k=dthOZ5Gj!fa|7B-G~$HtCj~+u(`;3K-#JUD_iRY zmgkgR(}w|aDyyt2w8rL9bl=`g>}Ely?9c{svPH0Qf!oT0=_h*u~#NwUKNNO=Ci zp_r_9<2ZitUt9GT)bR1S9dBfrt2&=QI%BgePWSlzT6`3nT9l^4&e>d7tK{j)=2bHS zxN%?uC{Hav$&LyS%Kt>h<{pZ<+1Z^)GK+T|DpYgC z#^`9tCg!ZgRjA1DZJ8Kcj%O7Eco{H29R2!jutC2dS6$WC+QHlZ6cJ_#OVqX6P1;c< zXb`hI_0Dg#ocso^u4LL5E1*E*@LH?u--M|DIzl%%Hd(dOZTi0JYc1_O{;vvz;ni~T zX^*=L^?8x3`qtQH9fD+b0)cHyk=SDYE(cCGa!%9GRgvkQh`vV zqcpp7#s`cbBO@cY;kt%;hVGG(%HQ_Sp!mrBC{HRbFfo3;In4C9>SO$`Mv#xsTaV@t z`9&ejcYi^^@_AvQ**T}w+ntq2{$tg92Pa-~INU!NvqOr-K~J9k1(lemX5-OLRdWWa zyt;gEky<810(*o@w91()bvUXF z6c2^_7>WixI}Dz|_LW7%A4;ur3$;I^pzH&6KH9V&|zi#YgJd+Ztz3fK&glbzol?@3(??c z;i5>a-WvP)SFQ0UvrXTg3yx9WEgvtfyP0-5@&@TqWek8g7*Mv_8)vLCn|UZMKW#!hay$=pj2<7J>u6~dlhFY50jBiQ@~BixM)`c&@!-6s}y4py{Dk|TNj zQWP-&L?V+s%A`f=OdP22?1a(g+}zwjUSd_(=aWW5W8znBat%^KL7kmqP=gwiYp>vi z(>dd%wWT)C4X6TLv4=otrIXWy9(Ek+O>xfW_E7>)QpNb{Ou^LiT?m)uDM`81Z2MH` zJHQ1P_<Y+xO&<7NyML`hXVq3{d|F zX>G-1WnzMh%J)gJya44_>06$S^kAOh%oxEKu1gE08V4k}FnT~0SNg$qb?3dE`L~|e ztlY#BO>UN_T}DPZR|j(^DNHTS*Z&F%2u1GymC%UW;Xz-@$HM6@7`^{BPaz ztsF*Q1Dq)loHtwfpUE9<71ut$n0ykQ`IlYbPcGu+Lh~#jR$l9bApIS+tgeh}XC$ln z53k;TKs zID*Dv7Fu$Bmhij3B?r%^`{wQ06`Rhs-^0y#EW-7sYL1sX6IU2Qda)u7LbnY-(O8TVe`1WdGrF+B{rmeoT%M1!?JOou#Z3o)`@Pct9ab zjaH5YF+&r&o>q+-fjx{g1ry(E;3yOAfz3DJ*>cY8(LP ze{&MlMR#CGd9gdT_5hM2MkLGi9-o@MGWh;&Y0g1~_-0EZH7TRv)S_d?SxgRc!u*mQ zDPDR@PG6o*g2eeI>zB2htekKIL!M=ami?>A=uNsAE?&Ga_H>du{(%bhC336?@gB}# zQ)19E@Qx(rMx^5IGsqN3eEgX35$6k6C>5B<)tHkJKr~jD6=$q0FPOdMlIhmc77BDn zvl9MvIol*fyV#)N`!22ns`wc3I zn|<)_D`#YEzze{Iz3mkxeP86P{N|~~a(7vfJIJ8p9wwBEWbeSyb$@Q<8~zy8pV!)q zfY!}IjHENBhn*y#d$9t_Bt)pdh#O3lTpH-iDkA@xs8q_bYWQ(VGb1Mlgmzqi1<}XNY7N*K8BLtLOHe+5WCA$D z`|zR9G5W;dzO&yhi_>?oaj-ra&DOy3JRhZVMnEv|SN1o1R=kjyqZ^+>5o-*pP>u`i5U+z$mQUIo$btZY`jr3LMPyYP*4gc?le8ohw?Z z&a$$74X_JG5C;ZIN0z~p05a1Bp;TK5v}m9qC@MNS@Vkt;veo=e3P+G5k#dkJ!{AFr zKd5nFWc$j0ei2ktVN|(N<8^c?-sIeM659R5%ucToHt|ym-aEA{qan#^9fzybtHk@|5VK8$ls$A}wmD%g5?DWBci-W;aJ>ps=V& z*O6~Y9z+1(Y?YNz7OWpL7-&c1c|CI892}c&3>M% z#-Ivrv?@zDm2WuOrbl5&k=eFtLy`BJ?z@w;eP5^R=z7Yl%f}a{Zcs*#ged8;Er7)s zwP~B)`7=BFv$J8$O&T8oSUCS$UA&8wHiL3vfQl^86SG`{F*orAQOd5`2+fjKcb5co zIT1rcnCZUe`0h_bkDa^7o1M-!jrku>dkl+i?JCV+n>-V>wfmhjL2QPUi6yYQL##s0 z$;1Kogoj^`&O?|TZ+v}yKZ6?z#&;;yp`f6kaOJF(@$0G&jcGCx#oxbwe?>t-0Rh$A zcp$#lU_R;sr}3cBX+#gkTJZf_aA@P`;}d)J|W)4 z-|yfM+;!rB_e!uT7}hrY8D_4{h%geyrs6 zBoFLnA8?ut#0zQjIAN&%^`m}%%-F@YwmIkm5*ADNyl%^S9NZZ0^pAdv|98f5$)H4O zBUTD|YG6^GxHN=#1*%Mq)R7pJPwZ5ID)JDpQRxK*86p@x0lF!4NRr=Hn;e(wprA@P zvff}iQ!r$q-pWu-m-S3qROYLx2=P%;a|H~R#7A)+bzP+i#4a;v5J` z(r$g58LKuM@jv0!s~d+}i&3OrYE336Ia+lLHP<(XMevCgkQkbm$C6$H4(dkK)qM%hroLgF%Aan=(3WMkD+vxT46mSm~O6*4rT9M zFG-YhSf=*S8?%ImMgIJ0U-Uj+4l87wxLl|=rUQZr5OaZ=-d^dB3*taC^O;PlVALLy z$xo^=4wSf)Ed1Quyf6DR%R}~amu^XejaegvWL8|B2eha(1b!Xw^^A<>wgZz~TUxps z;e90`p#aa@AH8&u{1v}BK{cW- zC>RnPyNKG~-fTJqm2-QWo10DEPuPy2l?srSl@*@`_3!onr>W7_o*QDw_Av{(IbMMZ z4`+(_x)nV45k-cSmL`4cH)4sU{rxr>zH%z$W995_?cpA7aD!U{qj&`ElxT^L!c3NwHm6#8$YzwYgi5t%4VWSlgm z82vSf#pOjO1?$j!8bikuytKLsx}G8fP)2qST zw8J68byKNO!^yCtgjm8c$gdxIf#b4}Vq2=A;gA|VxS?T507!)CjYJv|X*#ism^X7g zm~RiMiVuxsaa#WlH74=<4ui&4vu!B4(0J{-3W*Vj-FHGCoJe_{HI)1FK?0!yY`FI` z`g_F?v)$A-A!OjUffCqZ5^yBgpq$CbMX-YL#j@Tybswkfu)K54b%I85|ts zEx#2I5oLK+D_t#49Wik8dEx|JN@&!+bWp7)|0%7 z%IZW{&bRc9&26sfET-=A>t5JGDlqUUb_`fhtTYP{xZ1J<9@(L9wuP7lB;o zwNB&hot@cb8+QE$8*yPf4O=g1>1>z41;#CQH9pesOpHJC9=DRPguRaRKuM}&!-$i2 zbA(=Go9zAuJXYRa9-Zr+D+49Pqsxd%1z08$K|P#zbW~I(Q&;zf%i++#fXye^vvbi1 zN^oc>JQP$E8l?_e@C?_1wr>|Q{`13ydj3L4_SLL@M$q|ry7}pqV^YkAm=6w(f8+{W z{ho)CcPKwU)~;&9M-t)2Nugf07{1BUmHlL2YD;u&B%to^udc3Mroo)GuY_DMVe=1! z5Of3t1j@-$cb%Zj66`3ftgNa=tRDJ(2|)TSH$Q)~(Q&DXtjNt=_YUjJPKmKP~`+f$r->7VY#=~zYKX)1biF`THT50#aAbs=Z zjjEQWCaCHk%?#2{OiHSD!#rFJ#K{9A7QFu7(oe-H2OdHu#NnV==FrRk^rY*>2uu@3 z?rAoB8H$~3Y)@1Nc@YZE-XdFAh;Un)nz%2Ahkn)uohEH2)i~PI3pV7MBuJLV$sMrc zonu4)2hc{Y8`1L`=|Ayv_0-<_-!$%M1x3Y)WU^fFnjir!1wQE=Xf2RYq{Suc;jCG3 zSU)~~z6Sk$U2Z9)d>;QcZ$3ZY9c_nl)-khI(3JShc^4f=*P`Hd zptQ0P?$W*1hf=NI;N)bXu|>Rl{h#QsZO|g!vUv$?3jUjYf(^r`Dnp1?H~+ar$pZm8 zF!R@{KK|#=mQVCaAqtv!Xp!ktHvJ|Y7G`F< zU%7q#z3n^}Bk6EK*ZWs*%BS*fPT5`1k>+Ri{gkEW11)bX*)vJ0mK&Z0`%TAfwTm0e z&9p@E*EpV&7vrAp5327(#-PGPx5;v}vjwtou@2=l&-2JPv;Lf^YHV0%x8OF@9TO! zpQ{~HfeTp{{MW@LIpZNK0Esi9%a_xUfE@_fbZ&=5QMQV$Ot%~-y|=w9a3&TOdq~Z{ z8+En*1$ldX>3ys}K075nU3!R#wAzq0;AB})L&_J9>}2&{M0OyJ4FhJwU2XiVP`{4X zkMlCwauRe_t#u>}c&@m%b5-|*lP3Kcz#!qk$w+%Hfq3CeY^y_MXl7E<&&Rnff6*bx zc*c+nw@T3VwV1Z@7db-T0(s#M5Em0$O^3&8GHLGNY>F|KZ#t;>*G+tO%M~o?kZoHhg|Xr7+fC*gI_}vIl36ctAMn3 zTdnv_B-*e|1zbFycAu<~RJ?C+Yp?lzw|!EaUH(vIMc)M0%F<yFC63G|Z?r~GF^{h9x)V;=MDTY#hJRql%+z$uJO>}GPVr5y z52I$2i9*R538UCyX9HUoJ$}wcKsKHLLNzJb>(lv!Lb@(+D~`!`c~>NtP-Bi-XSf|7 z-OPU`E5Lno4(D-at#yDZxGsBwC^txZ@-~I4Zb%qcsMB^Lrtx=I-`KpI$CPsL&|4!W zNNy)D1Qs-JO8_h$GSeQ*T5Gg6_60Chx?s|p%8T@Lh5N!N&J&S{m^KO&3KiA-d-onO zRqUR7XT;U*nHa}7cCjLPjz6kD@}VqWN1PBHr)G>YjQ?)f?u~yof&E%nxgdwD!8&Y( zY<-yfJ99O=KeWTr&TTP&J*2+auMUu^hZ|7)&&i?P;-qtNriC&9h~r0rj(>5Fm%A_m zfHwY1mwv-2P#0O32^-BKM<&$<-{srRAcS}TR|MwM6-~RA-3eu7zH(>JveQIxDa;{{ zIJDh*i%JaZ?$>Rl;W0xUi@M`9N(cP|F-O+omez8%txue*d%n8Xbi*TUi^XCY=p$Px z5T4wQy$)OX3wX@*;7c4)gM4^4c6KDWHg2h?*z|S`MqE!m17jfz9B`aR=%MQezkX5o zR*rsmTOL|4Xn1a5aI4{#RDIpXA}{a4^u7A+>2XwWN+9dp`^_U2|D8isNBzF95SY-Y zK3@x>33#oC<(L*a*l`HV$TLB24HAW;5ICCd>+5ruUOt#)fSXd|G<9cBk2klXkJF1$ z*CWafuW#~9g7lij1eb;8-Lc2vmZe`ZV#OvK{4s}fU(1>Idb13g&T=2VR8?-gR6Olc zc9LeL-)U-bZ>6#}i*Pky_uIQNJG=Ayq3gA_36g@+;);D{rMC|h&r3iTN;zqlU6h-4N{=gl@-yO~4F| zww|6|$m*tU0{7k~dU0q(?1i4+(@dp>$%(){%anF3$jj{B2&r{Oo(Q&I`yYKH^5D!EZn!A~<4WdpSA zW_%v``7sb0=kJbHN1?*PShGjpZG22hN;;|)HoJM#b9duPVq(UA_we=mP^s+Stc?-) z5*E^vDrUfZUUlM+wlWi@j-A3o0Fq;3Iu=1?n~xkQ zX97G~hv@lF-n%A4+MqoAw%LTBG z12703V&+k-QdLuHS(Pi2p=3jFcD>Sws;8r&38h2~zACED7@RjVnt_ zoFQ9bN~G0biNz)omPuG5T)3#fsjS@=u|lDDOS_ohsb4(V6x^94aT^*dSfzEZTqt?^ zjP&*6W&*=rPPzjbocZv;I6prhuL4!WOwGGgn}^pZUa|Nte<{5W_y)4jT=CgkXHRhz zklXyJq7pj$V<1-r6S_NkTy+Y8SQ%~bH(JV67ydpTK=FZo1fEpviZ9M6kY0` zKkE;#r}Q-{Km6ntb1>x!U-~>HIT@r#;df0;D4;V0v2hbNPwv`F3JQToKsZ&R3zpoN zP9owzPfuf@k%^W->D$Z(PBDB7ib8==p!{x#DrC0u!M}ltprY$*Bpi+VuZ2h~G^}?& z7#upk)zy3I3o`TSny1V`g|&Ya>+{!ErQ&0g*h$*G!MyKYxjF=;9TE^}BUa5J3W9=y zjxa6sVt-Ea7hn&3FA65yYs39#+hga!_~WLS7*0MJC-m}6IpkcwUW8OI#!{5sgS(?} z$@hmkI12`x4rRo}v67;qqU6Lvt?M7t;XnF2Vlg4e91oL$A3rN9*03KxVojq>W|R}J z?taK7>0h8PGkYm3EhH4Gi?hZYrDp6er?4ph_p*gXg{OFTAKu<96 zgIlD;P(VaTMC2ZMmBcX8QY{f5+mkIM1qDL)0m*IVr+)yO1bL0q0Xxc+khF-1jEqbt z4?W=$<2WU4TQl$R5SQ1RgLl(>mzkLI7iXweb{(U3_Qf|Vq>}9|xDg1}r|ew4mu)#D zY&kfZnnrovzsD?o$H&-Ri=No@ome^C+fqXt3T!!ILj9&jMo8CEy7-BKynwAl+XW=^ z2i94Itw4>9=NAxgxVmBBTnTCqO==Btl>6NI^J*kQ*;lid_I!2CZ1z{K0&|RAv*U@Acj^(fVH2`!2J)KsC0{vow zCQNviVbTBHSXDJ_f}eZed7rES>YPW4^7FSh-K%wH06PjJoO13CFxB)KOzLZ#7B|Qn zRI>@&n~#7>iradmoQYSlO5k9uTErph{o(ljj&6ODeGD&~?5m4yjEuIUye|jn`JU5g zu`_&&A1>{Dt_597^`3cK(r&ajW92-e!)=>aZA!by)Q$%i(yKQ*>HsIjERW#S zK6_0NxjTGqIdnkD02~LLKFd_mHgekkVvFz_tf|{oLC)LjdDO2)(>6AJHswJ zGL@q%hElVPv^uGejJK4-;UYw5OFim#JLr1|O=g>rqrH9Uj>PpkzZuOqL-7&o(K_ii zs*Wbj6FE%`3BfU4_MuHBU&JX9xDQ^af)d66^rV$2=@C8d^Ah-<}=_o!#g ziWOBzFsK+`6|Aycod+R{J_gud<|XgLukYl0y`lf8rKhew(j6C<`~gg7B@Ab!Xb>99 zv$Nkjdx*tIBqQImNS_3>=kwm^X;J$xo>>9m#`ui!g}`dj=8xO*wNOc(nT}uIBYH0p&bCi67e(`-UnA!R!%p= z^{dOt;gfleOMi{%kL2UyV`E`qnQUxo!U$eRRuEih$kRD$=)S?L{z;BdnPq9LK+0u+ zIxi7l$M7Q4CG9@Af`o)HxPt>6+DNSmr6~Tc47JD$ zgMaG~RSV67jEV^vAR!=t_Nom&bdxjAoVUm}G8k$L0`PW=osI1@)I)2W@ecp!@h>eY zFJ`4}P-`HxP1KX$)Kyh&55WVL11ziXgtrF!2J2RNI7n2JHUXl0%D`h^L(uW;>Z;T4)bcS5#Eg?>A5%!}jw@ zVxnZml;yfV0s##fCA@j4qKgi_{2-V&d<%O!2>e89(%;;WPrWmTs9iORB zAjX4`s)NO@6D;Gq@CpovT!<+wA|{n#BIGj!t%mv8nVA8LQm{waS3sJ)ic8~kbDuhF zk>w@OkLjr4+5flj4g`d0G#Z`$-rmw&@j6kag)ix7X}cvED*>J6&`V~cfWSfz=&FjY zFs6V?WR#IM%A(M`2pltia76DF5SDIGYiO}h|5-V{D%-NwcRUM;*5@7|N^7lf&7O2u zdt7aahZ~E#;;v|6zA;8zJ8iF&8ETD)pFcgH#l+~+L7NsL(^bze(1*z<9I#Tq2KX&A zRzFMdp9}P|W+}xUT#y5RFf7Es5mq*b)WtXM3e!{GWTSzSmf%uaH|TkkGop(1e#x6a zlnv*Iws6kqW262v%kCcrxNjN&CrvgpGaJ|W5Ou%-$4!6r>OyyKZ*K>n9B4FpybS~b ztPtG>;h}s&F@<`h9mg{}j?46s(ot7zIX*E&K2b7u2RF*-thEX|Mdrng%tu**L9+iv zdARC#@E_$N9GUq`=65=7IAUFEt9m!wWZHSAdyi!@62K0Xbyt+>qnDM)nOIF4jU=MH z_(w^@9xra|<)_VgOCmJZr=RMBQwx3zM|9nHPkG_u<9h`o%Qz0k=uFTVLUzPTMv$Z$ zP1#Bnog?Jw?|&9z^1=Y_LY3_dIYmzi2^>vO6R%KQ(cZj&f|k#&oV@Nli1~zsg@Yty zWqq&Ww|1c;Rt%Mx53E&D-@je_Ul(t}Py5dwl61rz#vG&?Dn4jP?r~k$zM+NHw2k~1 D+xUKF literal 0 HcmV?d00001 diff --git a/desktop/src/assets/index.html b/desktop/src/assets/index.html index 8934bc43..e8737469 100644 --- a/desktop/src/assets/index.html +++ b/desktop/src/assets/index.html @@ -3,7 +3,7 @@ - Octop + FreeOS