diff --git a/.gitattributes b/.gitattributes index b3e40d3..8639fe3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,18 @@ # Keep shell scripts Linux-compatible +/.gitattributes text eol=lf +/.gitignore text eol=lf *.sh text eol=lf +*.sh.in text eol=lf easymesh text eol=lf install.sh text eol=lf relay-manager text eol=lf # Markdown files *.md text eol=lf +*.txt text eol=lf +*.env text eol=lf +*.yml text eol=lf +*.yaml text eol=lf # Binary files core/** binary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..bd201c2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,92 @@ +name: RC validation + +on: + push: + branches: + - main + - "release/**" + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + validate-and-build: + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install validation tools + run: sudo apt-get update && sudo apt-get install --yes shellcheck unzip + + - name: Bash syntax and regression tests + run: bash tests/static-smoke.sh + + - name: ShellCheck at error severity + run: | + shellcheck --severity=error easymesh relay-manager install.sh uninstall.sh + shellcheck --severity=error release/build-release.sh release/online-install.sh.in + shellcheck --severity=error tests/*.sh + + - name: Build verified RC packages + id: release + run: | + . ./versions.env + echo "version=$BEHIFY_EASYMESH_VERSION" >> "$GITHUB_OUTPUT" + bash release/build-release.sh + + - name: Validate package contents and bootstrap failures + run: bash tests/run-all.sh release-output + + - name: Retain exact tested RC bytes + uses: actions/upload-artifact@v4 + with: + name: rc-release-assets-${{ github.sha }} + path: | + release-output/behify-easymesh-v${{ steps.release.outputs.version }}-linux-x86_64.tar.gz + release-output/behify-easymesh-v${{ steps.release.outputs.version }}-linux-aarch64.tar.gz + release-output/online-install-v${{ steps.release.outputs.version }}.sh + release-output/install.sh + release-output/SHA256SUMS + release-output/easytier-v2.6.4-source.tar.gz + if-no-files-found: error + retention-days: 7 + + native: + name: Native ${{ matrix.architecture }} + needs: validate-and-build + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - architecture: x86_64 + runner: ubuntu-24.04 + - architecture: aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v4 + + - name: Download exact built RC bytes + uses: actions/download-artifact@v4 + with: + name: rc-release-assets-${{ github.sha }} + path: release-output + + - name: Verify release checksums + run: cd release-output && sha256sum --check SHA256SUMS + + - name: Execute matching package and strict-offline install + run: bash tests/native-package-smoke.sh release-output ${{ matrix.architecture }} + + - name: Disposable real-systemd install, upgrade, and rollback + run: | + . ./versions.env + sudo bash tests/systemd-release-smoke.sh \ + "release-output/behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-${{ matrix.architecture }}.tar.gz" \ + "${{ matrix.architecture }}" diff --git a/.gitignore b/.gitignore index 19092cd..8bb8a81 100644 --- a/.gitignore +++ b/.gitignore @@ -17,10 +17,27 @@ tmp/ # Local test outputs dist/ build/ +.release-cache/ +release-output/ +release-staging/ +*.tar.gz +SHA256SUMS.local # Secrets / local env .env +*.env.local *.key *.pem +*.secret +*.secrets +config.local.* +relays.local.json +mesh.env + +# Installer and rollback output +*.backup +*.bak +backups/ +easymesh-core-backup-*/ *.zip diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6c378ee --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,59 @@ +# Changelog + +All notable changes to Behify EasyMesh are documented here. + +## [1.0.0] - 2026-09-05 + +### Stable Promotion + +- Promoted the validated v1 release candidate to the first stable Behify EasyMesh release. +- Retained EasyTier v2.6.4 and architecture-specific packages for Linux x86_64 and aarch64. +- Retained transactional installation, strict-offline verification, service-state preservation, and rollback. +- Retained root-only mesh secret storage, secret-free service arguments, and WARN-level managed-service logging. +- Retained isolated Dokodemo-Door relay management and the tested EasyTier networking and routing behavior. +- Recorded the successful RC3 CI gates and real two-server direct-P2P test with bidirectional ping and 0% packet loss. + +## [1.0.0-rc.3] - 2026-09-05 + +### Changed + +- Reduced the Connect to Mesh introduction to one concise peer/UDP hint. +- Styled the generated 32-character network secret for visibility and made custom secret entry visible and directly editable. +- Removed the secret verification step and all second-confirmation behavior from network-secret entry. +- Clarified one-command stable and versioned candidate installation while keeping the installer non-interactive after installation. + +### Security + +- Preserved RC2's root-only secret files, config-based secret transport, secret-free service arguments, and WARN-level managed-service logging. +- Documented that visible interactive secret entry can be captured by terminal history or observers; the program does not repeat a custom value after entry. + +## [1.0.0-rc.2] - 2026-09-05 + +### Changed + +- Kept mesh secrets out of service arguments and INFO-level effective-config logs. +- Generated 32-character secrets are shown once for interactive copying; custom entry stays hidden. +- Encryption now defaults on, multi-thread defaults off, and IPv6 keeps its previous default. +- Removed the duplicate EasyTier version row from the dashboard. +- Clarified one-way peer initiation and connectivity troubleshooting. +- Added a checksummed, byte-identical `install.sh` release bootstrap alias. + +## [1.0.0-rc.1] - 2026-09-04 + +### Added + +- Verified architecture-specific release packages for Linux x86_64 and aarch64. +- Transactional installation, runtime upgrade, rollback, and bounded uninstall workflows. +- Isolated Dokodemo-Door relay management through `behify-relay.service`. +- Public security reporting guidance and third-party license notices. + +### Changed + +- EasyTier v2.6.4 is the only supported runtime. +- Runtime binaries are assembled from pinned official EasyTier release assets instead of being stored in Git. +- Mesh secrets are stored in a root-only environment file instead of the systemd unit. + +### Removed + +- Legacy EasyTier v2.0.3 binaries, selectors, and download fallbacks. +- Global EasyTier process termination from maintenance tasks. diff --git a/README.fa.md b/README.fa.md new file mode 100644 index 0000000..2b57235 --- /dev/null +++ b/README.fa.md @@ -0,0 +1,116 @@ +
+ +# Behify EasyMesh + +Behify EasyMesh ابزاری source-available برای مدیریت شبکه EasyTier و رله اختیاری و ایزوله Dokodemo-Door روی لینوکس است. نسخه `1.0.0` نسخه پایدار فعلی است، از EasyTier `v2.6.4` استفاده می‌کند و معماری‌های `x86_64` و `aarch64` را پشتیبانی می‌کند. + +## ارتباط با پروژه اصلی و مجوز + +این پروژه بر پایه [Easy-Mesh نوشته Musixal](https://github.com/Musixal/Easy-Mesh) ساخته شده است. فایل‌های `LICENSE` و `NOTICE` بدون تغییر حفظ شده‌اند. [EasyTier](https://github.com/EasyTier/EasyTier) و Xray-core رله، پروژه‌های بالادستی جداگانه‌اند؛ جزئیات در [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md) آمده است. + +مجوز سفارشی این مخزن source-available است و مجوز متن‌باز مورد تایید OSI نیست. طبق همین مجوز، استفاده از نرم‌افزار برای ساخت یا قرار دادن آن در محتوای منتشرشده در YouTube یا هر پلتفرم اشتراک ویدیو ممنوع است. پیش از استفاده یا بازنشر، [LICENSE](LICENSE) را بخوانید. + +## پیش‌نیازها + +- لینوکس دارای systemd +- معماری `x86_64` / `amd64` یا `aarch64` / `arm64` +- `bash`، OpenSSL، Python 3 و ابزارهای استاندارد GNU +- برای نصب آنلاین: `curl`، گواهی‌های CA و `tar` + +نصب‌کننده بسته‌های سیستم‌عامل را خودکار نصب نمی‌کند. `iperf3` فقط برای آزمون دستی سرعت اختیاری است. + +## نصب + +برای نسخه پایدار، نصب با یک فرمان انجام می‌شود: + +
+ +```bash +curl -fsSL https://github.com/behifyn/behify-easymesh/releases/latest/download/install.sh | sudo bash +``` + +
+ +این نشانی آخرین نسخه پایدار GitHub را هدف می‌گیرد. نصب‌کننده در پایان `Run: sudo easymesh` را نشان می‌دهد و منو را خودکار باز نمی‌کند. + +برای نصب و سپس اجرای نسخه پایدار در یک فرمان: + +
+ +```bash +curl -fsSL https://github.com/behifyn/behify-easymesh/releases/latest/download/install.sh -o /tmp/behify-install.sh && sudo bash /tmp/behify-install.sh && sudo easymesh +``` + +
+ +نصب مستقیم نسخه دقیق v1.0.0 نیز با این فرمان ممکن است: + +
+ +```bash +curl -fsSL https://github.com/behifyn/behify-easymesh/releases/download/v1.0.0/install.sh | sudo bash +``` + +
+ +فرمان‌های pipe کوتاه‌اند، اما خود bootstrap را جداگانه بررسی نمی‌کنند. روش پیشنهادی نسخه پایدار، دریافت و بررسی فایل پیش از اجراست: + +
+ +```bash +version=v1.0.0 +curl -fLO "https://github.com/behifyn/behify-easymesh/releases/download/$version/install.sh" +curl -fLO "https://github.com/behifyn/behify-easymesh/releases/download/$version/SHA256SUMS" +grep ' install.sh$' SHA256SUMS | sha256sum -c - +sudo bash install.sh +``` + +
+ +فایل نسخه‌دار `online-install-v1.0.0.sh` از نظر بایت با `install.sh` یکسان است. هر دو معماری را تشخیص می‌دهند، بسته ثابت همان معماری را می‌گیرند، SHA-256 و مسیرهای archive را بررسی می‌کنند و سپس نصب‌کننده آفلاین داخل بسته را اجرا می‌کنند. + +برای نصب کاملا آفلاین، بسته مناسب و `SHA256SUMS` را به سرور منتقل کنید: + +
+ +```bash +grep 'behify-easymesh-v1.0.0-linux-x86_64.tar.gz$' SHA256SUMS | sha256sum -c - +tar -xzf behify-easymesh-v1.0.0-linux-x86_64.tar.gz +cd behify-easymesh-v1.0.0-linux-x86_64 +sudo EASYMESH_OFFLINE=1 bash install.sh +``` + +
+ +روی ARM64 نام `aarch64` را جایگزین کنید. فایل‌های Source code خودکار GitHub نصب‌کننده آفلاین نیستند. + +## استفاده + +
+ +```bash +sudo easymesh +easymesh --version +``` + +
+ +باز کردن منو هیچ دانلود یا جایگزینی EasyTier انجام نمی‌دهد. گزینه **Connect to the Mesh Network** تنظیمات mesh را ایجاد یا جایگزین می‌کند. تنظیمات پیش‌فرض عمومی شامل رمزنگاری فعال، multi-thread غیرفعال و IPv6 فعال مطابق رفتار قبلی است. secret تصادفی ۳۲ نویسه‌ای یک بار و به‌شکل برجسته در ترمینال تعاملی نمایش داده می‌شود؛ Enter همان مقدار را می‌پذیرد، یا می‌توان secret سفارشی را به‌صورت قابل مشاهده تایپ و پیش از Enter اصلاح کرد. برنامه مقدار سفارشی را پس از ورود دوباره چاپ نمی‌کند. + +فایل‌های `/etc/behify-easymesh/mesh.env` و `/etc/behify-easymesh/easytier.toml` فقط برای root و با سطح دسترسی `0600` هستند. سرویس مدیریت‌شده secret را از config خصوصی می‌خواند، آن را وارد آرگومان process نمی‌کند و برای جلوگیری از ثبت effective config در سطح INFO، گزارش کنسول را روی WARN می‌گذارد. گزینه ۵ منو فقط با درخواست مستقیم کاربر secret ذخیره‌شده را نشان می‌دهد. + +برای جزئیات به [تنظیمات](docs/configuration.md) و [عملیات و نگهداری](docs/operations.md) مراجعه کنید. + +## نکات اتصال + +می‌توان آدرس سرور دوم را فقط روی یک node وارد کرد و در node شنونده یا reverse، فیلد peer را خالی گذاشت. اگر اتصال از A به B برقرار نشد، جهت B به A را امتحان کنید و دسترسی UDP، فایروال سیستم، فایروال ارائه‌دهنده، NAT و پروتکل پشتیبانی‌شده دیگری را بررسی کنید. ممکن است پس از شروع اتصال از یک سمت، مسیر مستقیم P2P شکل بگیرد. Behify به صورت خودکار peer معکوس اضافه نمی‌کند و routing خود EasyTier را تغییر نمی‌دهد. + +نمایش Peer و Peer-Center از قالب ترمینالی EasyTier استفاده می‌کند. Routes در صورت پشتیبانی `watch` از حالت no-wrap استفاده می‌کند و در غیر این صورت به wrapping عادی برمی‌گردد. + +## امنیت + +آسیب‌پذیری‌ها را طبق [SECURITY.md](SECURITY.md) به صورت خصوصی گزارش کنید. secret واقعی mesh یا اطلاعات ورود سرور را در گزارش عمومی قرار ندهید. + +نسخه منتشرشده RC3 همه مرحله‌های CI شامل validate-and-build، اجرای native روی x86_64 و aarch64، بررسی package، نصب strict-offline و آزمون real-systemd را با موفقیت گذراند. این نسخه همچنین روی دو سرور واقعی x86_64 نصب شد؛ هر دو سرویس active و enabled بودند، مسیر DIRECT/P2P برقرار شد و ping دوطرفه با ۰٪ packet loss موفق بود. نسخه پایدار v1.0.0 همان runtime و رفتار آزموده‌شده را بدون تغییر در شبکه یا سرویس‌ها ارتقا می‌دهد. + +
diff --git a/README.md b/README.md index d6eda68..71b6316 100644 --- a/README.md +++ b/README.md @@ -1,148 +1,90 @@ # Behify EasyMesh -Private Behify build of EasyMesh for offline server deployment and customized server-side usage. +Behify EasyMesh is a source-available Linux administration tool for an EasyTier mesh and an optional isolated Dokodemo-Door relay. Version `1.0.0` is the current stable release. It pins EasyTier `v2.6.4` and supports Linux `x86_64` and `aarch64`. -## Status +Persian documentation: [README.fa.md](README.fa.md) -This repository is currently a private modified build based on the original Easy-Mesh project. +## Attribution and License -Current focus: +Behify EasyMesh is based on [Easy-Mesh by Musixal](https://github.com/Musixal/Easy-Mesh). `LICENSE` and `NOTICE` are retained unchanged. [EasyTier](https://github.com/EasyTier/EasyTier) and the optional Xray-core relay runtime are separate upstream projects; see [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). -- Keep the v2 script as the main version -- Prepare offline installation support -- Keep local EasyTier core binaries inside the repository -- Customize visible branding for Behify -- Improve reliability step by step +This repository uses the included custom source-available license, not an OSI-approved open-source license. The Software shall not be used to create or be included in any form of video content published on YouTube or any other video-sharing platform. Read [LICENSE](LICENSE) before use or redistribution. -## Install +## Requirements -Clone or upload this repository to your server, then run: +- Linux with systemd +- `x86_64` / `amd64` or `aarch64` / `arm64` +- `bash`, OpenSSL, Python 3, and standard GNU utilities +- `curl`, CA certificates, and `tar` for online installation -```bash -sudo bash install.sh -sudo easymesh -``` +The installer does not install operating-system packages. `iperf3` is optional and used only for manual throughput tests. -Use EasyTier v2.6.4: - -```bash -sudo easymesh 2.6.4 -sudo easymesh --core v2.6.4 -``` +## Install -Strict offline: +For the stable release, installation is one command: ```bash -sudo EASYMESH_OFFLINE=1 easymesh -sudo EASYMESH_OFFLINE=1 easymesh 2.6.4 +curl -fsSL https://github.com/behifyn/behify-easymesh/releases/latest/download/install.sh | sudo bash ``` -Re-running `install.sh` updates the packaged scripts, documentation, and local EasyTier cores without downloading Xray or changing the running EasyMesh service. Existing relay definitions, generated relay configuration, isolated Xray releases, and `behify-relay.service` state are preserved. - -## Architecture-safe core selection - -Local core packages are selected from the detected system architecture: - -- `x86_64` or `amd64`: `easytier-linux-x86_64` -- `aarch64` or `arm64`: `easytier-linux-aarch64` - -Existing ARMv7 package selection remains supported where matching local binaries are present. Unknown architectures stop safely and are never redirected to an x86_64 package. - -Running `sudo easymesh 2.6.4` when another core version is installed offers a core-only upgrade. Both candidate binaries are copied to temporary files and executed for architecture and version validation before the current service is stopped. After confirmation, the script backs up and replaces only `easytier-core` and `easytier-cli`; the existing `easymesh.service` file, its `ExecStart`, and the mesh configuration are preserved. A failed installation or service restart automatically restores the backup. - -## Relay / Port Routing - -Menu option `[12] Relay / Port Routing` provides an optional Dokodemo-Door relay. The end user does not need 3x-ui, Hiddify Manager, or an existing Xray installation. Behify downloads and runs a dedicated Xray binary that is isolated from any panel-managed Xray already installed on the server. - -Xray is downloaded only after explicit confirmation. The manager accepts only a stable, non-draft, non-prerelease release from the official `XTLS/Xray-core` GitHub repository. The archive must include an official GitHub SHA-256 digest, and checksum or archive-path validation failure stops installation before the binary is executed or activated. - -Relay modes are `tcp`, `udp`, and `both`. TCP is the default and is appropriate for normal TCP-based VLESS, VMess, Trojan, Reality, WebSocket, and similar transports. UDP performs direct UDP forwarding only. `both` creates separate TCP and UDP listeners on the same numerical port; it is not required merely because an application carries UDP inside a TCP connection. +This URL targets the latest stable GitHub release. The installer exits after printing `Run: sudo easymesh`; it does not open the TUI automatically. -For example, a UDP relay can listen on public port `443` and forward it to EasyTier mesh address `10.144.144.1` port `443`: +To install and then launch the stable release in one command: -```text -Name: mesh-udp-443 -Protocol: udp -Listen address: 0.0.0.0 -Listen port: 443 -Destination IP: 10.144.144.1 -Destination port: 443 +```bash +curl -fsSL https://github.com/behifyn/behify-easymesh/releases/latest/download/install.sh -o /tmp/behify-install.sh && sudo bash /tmp/behify-install.sh && sudo easymesh ``` -Dedicated relay paths: +The exact v1.0.0 bootstrap can also be installed directly: -```text -/opt/behify-easymesh/relay/bin/behify-relayd -/opt/behify-easymesh/relay/releases//behify-relayd -/etc/behify-easymesh/relay/relays.json -/etc/behify-easymesh/relay/config.json -/etc/systemd/system/behify-relay.service +```bash +curl -fsSL https://github.com/behifyn/behify-easymesh/releases/download/v1.0.0/install.sh | sudo bash ``` -The downloaded asset remains the official XTLS/Xray-core release and retains all release, architecture, archive-path, and SHA-256 verification. After extraction it is staged and executed only as `behify-relayd`; the relay service never runs a path whose basename is `xray`. - -Reinstalling migrates a valid legacy isolated binary from `/opt/behify-easymesh/relay/xray/current/xray` without network access or another download. The migration preserves relay definitions, generated configuration, Xray version, and relay-service active/enabled state. An active relay is restarted only after the neutral binary and owned unit are validated, then its process name and TCP/UDP listeners are verified. Failure restores the previous runtime, unit, known test drop-in, and service state. - -The temporary `10-neutral-binary.conf` workaround is removed only when its complete content exactly matches the Behify test workaround and the canonical unit already uses `behify-relayd`. Unknown administrator drop-ins are retained. - -TCP and UDP conflicts are checked separately with `ss`. Sensitive ports such as SSH port `22` produce an additional confirmation warning. EasyTier route checks are advisory: a route warning does not overwrite or remove the existing relay configuration. - -Every configuration change backs up the current relay files, generates and validates temporary JSON, tests it with the dedicated Xray binary, and then atomically installs it. Only `behify-relay.service` is restarted. If validation, installation, or service startup fails, the previous relay configuration is restored automatically. The relay service uses `Wants=easymesh.service`; it does not contain or modify mesh IP, secret, peer, or `ExecStart` configuration. - -The Add Relay flow shows a confirmation summary before committing anything, explains and asks before installing the isolated Xray, verifies every requested TCP/UDP listening socket, and prints an explicit success, cancellation, validation failure, or rollback result. The relay dashboard uses local state only; GitHub is contacted only after selecting Xray installation or update. - -When the last enabled relay is removed, the validated empty configuration is retained and `behify-relay.service` is stopped and disabled. The relay manager remains installed so another relay can be added later. - -Check installed/running core version: +The pipe forms are convenient but do not independently verify the bootstrap. The recommended stable flow is download-first verification: ```bash -/root/easytier/easytier-core --version -PID=$(systemctl show -p MainPID --value easymesh.service); sudo /proc/$PID/exe --version +version=v1.0.0 +curl -fLO "https://github.com/behifyn/behify-easymesh/releases/download/$version/install.sh" +curl -fLO "https://github.com/behifyn/behify-easymesh/releases/download/$version/SHA256SUMS" +grep ' install.sh$' SHA256SUMS | sha256sum -c - +sudo bash install.sh ``` -The main menu keeps runtime checks concise (`Core` and `Service`). Menu option `[13] Diagnostics` shows architecture, requested/default/installed/running versions, PID, executable paths, and restart count. Peer, route, and peer-center views use a wide terminal layout and add `--no-trunc` only when the installed `easytier-cli` supports it, preserving compatibility with v2.0.3. - -Core, service, and relay-component removals require an explicit `y` confirmation. Watchdog destinations must be valid IPv4 or IPv6 addresses; latency thresholds and check intervals are range-checked before the root-owned monitor script is written. +The versioned `online-install-v1.0.0.sh` has identical bytes to `install.sh`. Both detect the host architecture, download the matching immutable package, verify its SHA-256, validate archive paths, and invoke the offline package installer. -Smoke/offline test package: +For a strict offline install, transfer the matching package and `SHA256SUMS` to the target: ```bash -unzip behify-easymesh-test.zip -cd behify-easymesh-test -sudo EASYMESH_OFFLINE=1 easymesh +grep 'behify-easymesh-v1.0.0-linux-x86_64.tar.gz$' SHA256SUMS | sha256sum -c - +tar -xzf behify-easymesh-v1.0.0-linux-x86_64.tar.gz +cd behify-easymesh-v1.0.0-linux-x86_64 +sudo EASYMESH_OFFLINE=1 bash install.sh ``` -After installation: +Use the `aarch64` package name on ARM64. GitHub's automatic source archives are not offline installers. + +## Use + ```bash sudo easymesh +easymesh --version ``` -Offline Core - -The project is intended to include EasyTier core binaries locally under: +Opening the menu never downloads or replaces EasyTier. **Connect to the Mesh Network** creates or replaces the mesh configuration. Its public defaults are encryption enabled, multi-thread disabled, and IPv6 enabled as in the existing behavior. The generated 32-character secret is highlighted once in the interactive terminal; pressing Enter accepts it, or a custom secret can be typed visibly and edited before Enter. The program does not repeat a custom value after entry. -core/v2.0.3/ +The root-only files `/etc/behify-easymesh/mesh.env` and `/etc/behify-easymesh/easytier.toml` are mode `0600`. The managed service reads the secret through the private config, keeps it out of process arguments, and uses WARN console logging to avoid EasyTier's INFO-level effective-config output. Menu option 5 reveals the stored secret only when explicitly selected. -The default selected core is `v2.0.3`, which is the currently smoke-tested version. +See [Configuration](docs/configuration.md) for mesh and relay settings, and [Operations](docs/operations.md) for upgrades, rollback, service paths, diagnostics, and removal. -Local `v2.6.4` packages for x86_64 and aarch64 are stored under: +## Connectivity Notes -core/v2.6.4/ - -Future versions may include newer EasyTier core builds while keeping older versions as fallback. - -Future stability test candidates are documented only and are not enabled by default: - -```text ---enable-kcp-proxy ---enable-quic-proxy ---compression zstd ---multi-thread -``` +One node may initiate by entering the other node's reachable address while the listening/reverse node leaves the peer field blank. If A cannot initiate to B, try B to A and check UDP reachability, host firewalls, provider firewalls, NAT, and another supported protocol. A direct P2P path may appear after only one side initiates; Behify does not automatically add reverse peers or change EasyTier routing. -Attribution +Peer and Peer-Center views use EasyTier's terminal formatting. Routes uses `watch` no-wrap mode when available and otherwise falls back to normal wrapping. -Based on Easy-Mesh by Musixal. LICENSE and NOTICE retained. +## Security +Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md). Never include a live mesh secret or server credential in public reports. ---- +The published RC3 passed validate-and-build, native x86_64 and aarch64, package, strict-offline, and real-systemd CI gates. It also passed real installation on two x86_64 servers with both services active and enabled, DIRECT/P2P routing, and bidirectional ping with 0% packet loss. Stable v1.0.0 promotes that tested runtime and behavior without networking or service changes. diff --git a/README_FA.md b/README_FA.md deleted file mode 100644 index 70c2e9f..0000000 --- a/README_FA.md +++ /dev/null @@ -1,140 +0,0 @@ -# Behify EasyMesh - -نسخه خصوصی و ویرایش‌شده Behify از EasyMesh برای نصب آفلاین روی سرور و استفاده شخصی/داخلی. - -## وضعیت پروژه - -این مخزن در حال حاضر یک نسخه خصوصی و تغییر داده‌شده بر پایه پروژه اصلی Easy-Mesh است. - -تمرکز فعلی: - -- استفاده از نسخه ۲ اسکریپت به عنوان نسخه اصلی -- آماده‌سازی نصب آفلاین -- نگه‌داری فایل‌های EasyTier Core داخل خود پروژه -- تغییر برندینگ نمایشی به Behify -- بهبود مرحله‌ای پایداری و نصب - -## نصب - -بعد از انتقال پروژه به سرور: - -```bash -sudo bash install.sh -sudo easymesh -``` - -استفاده از EasyTier v2.6.4: - -```bash -sudo easymesh 2.6.4 -sudo easymesh --core v2.6.4 -``` - -حالت آفلاین سخت‌گیرانه: - -```bash -sudo EASYMESH_OFFLINE=1 easymesh -sudo EASYMESH_OFFLINE=1 easymesh 2.6.4 -``` - -## انتخاب امن هسته بر اساس معماری - -بسته محلی هسته بر اساس معماری شناسایی‌شده سیستم انتخاب می‌شود: - -- `x86_64` یا `amd64`: `easytier-linux-x86_64` -- `aarch64` یا `arm64`: `easytier-linux-aarch64` - -پشتیبانی موجود ARMv7 در صورت وجود باینری محلی متناظر حفظ شده است. معماری ناشناخته با خطای امن متوقف می‌شود و هرگز به بسته x86_64 هدایت نمی‌شود. - -اگر نسخه دیگری نصب باشد، اجرای `sudo easymesh 2.6.4` ارتقای فقط هسته را پیشنهاد می‌دهد. پیش از توقف سرویس، هر دو باینری کاندیدا در فایل‌های موقت کپی و برای تطبیق معماری و نسخه اجرا می‌شوند. پس از تأیید، اسکریپت فقط `easytier-core` و `easytier-cli` را پشتیبان‌گیری و جایگزین می‌کند؛ فایل موجود `easymesh.service`، مقدار `ExecStart` و تنظیمات شبکه بدون تغییر می‌مانند. در صورت شکست نصب یا راه‌اندازی سرویس، نسخه پشتیبان به‌صورت خودکار بازیابی می‌شود. - -## رله و مسیریابی پورت - -گزینه `[12] Relay / Port Routing` یک رله اختیاری Dokodemo-Door فراهم می‌کند. کاربر نهایی به 3x-ui، Hiddify Manager یا نصب قبلی Xray نیاز ندارد. Behify یک باینری اختصاصی Xray را دانلود و اجرا می‌کند که از هر Xray مدیریت‌شده توسط پنل روی سرور کاملاً جدا است. - -دانلود Xray فقط پس از تأیید صریح کاربر انجام می‌شود. مدیر رله فقط نسخه پایدار، غیر draft و غیر prerelease را از مخزن رسمی GitHub یعنی `XTLS/Xray-core` می‌پذیرد. آرشیو باید دارای SHA-256 رسمی ارائه‌شده توسط GitHub باشد و نبودن یا ناهماهنگی checksum یا نامعتبر بودن مسیرهای آرشیو، نصب را پیش از اجرا یا فعال‌سازی باینری متوقف می‌کند. - -حالت‌های رله `tcp`، `udp` و `both` هستند. برای نمونه، یک رله UDP می‌تواند روی پورت عمومی `443` گوش دهد و ترافیک را به آدرس مش EasyTier برابر با `10.144.144.1` و پورت `443` بفرستد: - -```text -Name: mesh-udp-443 -Protocol: udp -Listen address: 0.0.0.0 -Listen port: 443 -Destination IP: 10.144.144.1 -Destination port: 443 -``` - -مسیرهای اختصاصی رله: - -```text -/opt/behify-easymesh/relay/bin/behify-relayd -/opt/behify-easymesh/relay/releases//behify-relayd -/etc/behify-easymesh/relay/relays.json -/etc/behify-easymesh/relay/config.json -/etc/systemd/system/behify-relay.service -``` - -فایل دانلودشده همچنان باینری رسمی XTLS/Xray-core است و تمام بررسی‌های نسخه پایدار، معماری، مسیرهای آرشیو و SHA-256 حفظ می‌شوند. پس از استخراج، باینری فقط با نام خنثی `behify-relayd` آماده و اجرا می‌شود و سرویس رله هیچ فایلی با basename برابر `xray` اجرا نمی‌کند. - -نصب مجدد، باینری معتبر قدیمی در مسیر `/opt/behify-easymesh/relay/xray/current/xray` را بدون دسترسی شبکه یا دانلود دوباره به runtime جدید منتقل می‌کند. تعریف‌ها، تنظیمات تولیدشده، نسخه Xray و وضعیت active/enabled سرویس حفظ می‌شوند. در صورت شکست، runtime، unit، drop-in آزمایشی شناخته‌شده و وضعیت قبلی سرویس بازیابی می‌شوند. drop-in موقت فقط در صورت تطبیق کامل با workaround شناخته‌شده Behify حذف می‌شود و drop-inهای ناشناس مدیر سیستم باقی می‌مانند. - -تداخل پورت‌های TCP و UDP به‌صورت جداگانه با `ss` بررسی می‌شود. پورت‌های حساس مانند پورت SSH یعنی `22` هشدار تأیید جداگانه دارند. بررسی مسیر EasyTier فقط هشداردهنده است؛ ناموفق بودن این بررسی تنظیمات موجود رله را بازنویسی یا حذف نمی‌کند. - -در هر تغییر، از فایل‌های فعلی رله پشتیبان گرفته می‌شود، JSON موقت تولید و اعتبارسنجی می‌شود و تنظیمات با باینری اختصاصی Xray آزمایش می‌شوند. سپس فایل‌ها به‌صورت اتمیک جایگزین می‌شوند و فقط `behify-relay.service` راه‌اندازی مجدد می‌شود. اگر اعتبارسنجی، نصب یا اجرای سرویس ناموفق باشد، تنظیمات قبلی رله به‌صورت خودکار بازیابی می‌شود. سرویس رله از `Wants=easymesh.service` استفاده می‌کند و هیچ IP، secret، peer یا تنظیمات `ExecStart` مربوط به مش را در خود ندارد و تغییر نمی‌دهد. - -پس از حذف آخرین رله فعال، تنظیمات خالی اعتبارسنجی‌شده نگه‌داری می‌شود و `behify-relay.service` متوقف و غیرفعال می‌شود. مدیر رله نصب باقی می‌ماند تا در آینده بتوان رله جدیدی اضافه کرد. - -بررسی نسخه هسته نصب‌شده/در حال اجرا: - -```bash -/root/easytier/easytier-core --version -PID=$(systemctl show -p MainPID --value easymesh.service); sudo /proc/$PID/exe --version -``` - -تست بسته آفلاین: - -```bash -unzip behify-easymesh-test.zip -cd behify-easymesh-test -sudo EASYMESH_OFFLINE=1 easymesh -``` - -بعد از نصب: -```bash -sudo easymesh -``` -هسته آفلاین - -فایل‌های هسته EasyTier باید داخل مسیر زیر قرار بگیرند: - -core/v2.0.3/ - -نسخه پیش‌فرض انتخاب‌شده هسته `v2.0.3` است و همین نسخه فعلا smoke-test شده است. - -بسته‌های محلی `v2.6.4` برای x86_64 و aarch64 در مسیر زیر قرار دارند: - -core/v2.6.4/ - -گزینه‌های پیشرفته زیر فقط برای تست پایداری آینده مستند شده‌اند و به صورت پیش‌فرض فعال نیستند: - -```text ---enable-kcp-proxy ---enable-quic-proxy ---compression zstd ---multi-thread -``` - -## بهبود رابط و ایمنی اجرا - -در افزودن رله، `tcp` حالت پیش‌فرض است. حالت `udp` فقط UDP را مستقیم عبور می‌دهد و حالت `both` دو listener جداگانه TCP و UDP روی یک شماره پورت می‌سازد. پیش از ذخیره، خلاصه تنظیمات نمایش داده می‌شود و در صورت نیاز به Xray ایزوله، دانلود فقط پس از تأیید صریح انجام می‌شود. پس از راه‌اندازی سرویس، همه socketهای درخواستی بررسی می‌شوند و نتیجه موفق، لغو، خطای اعتبارسنجی یا rollback به‌طور واضح نمایش داده می‌شود. - -داشبورد رله فقط وضعیت محلی Xray، تعداد رله‌ها و وضعیت سرویس را نشان می‌دهد و هنگام باز شدن منو هیچ درخواست GitHub انجام نمی‌دهد. بررسی شبکه فقط با انتخاب نصب یا به‌روزرسانی Xray انجام می‌شود. نصب مجدد بسته، تعریف رله‌ها، فایل تنظیمات تولیدشده، Xray ایزوله و وضعیت `behify-relay.service` را حفظ می‌کند. - -منوی اصلی فقط نسخه هسته و وضعیت سرویس را به‌صورت خلاصه نمایش می‌دهد. گزینه `[13] Diagnostics` جزئیات معماری، نسخه‌های پیش‌فرض، انتخاب‌شده، نصب‌شده و در حال اجرا، PID، مسیر فایل‌های اجرایی و تعداد restart را نمایش می‌دهد. نمایش peer و route فقط در صورت پشتیبانی باینری از `--no-trunc` استفاده می‌کند تا سازگاری با v2.0.3 حفظ شود. - -حذف هسته، سرویس و مؤلفه رله به تأیید صریح `y` نیاز دارد. برای watchdog نیز IP مقصد و بازه عددی آستانه تأخیر و فاصله بررسی، پیش از نوشتن اسکریپت root اعتبارسنجی می‌شوند. - -## Attribution - -Based on Easy-Mesh by Musixal. LICENSE and NOTICE retained. diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..11c1067 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,17 @@ +# Security Policy + +## Supported Version + +Security fixes are provided for the current `1.0.x` release line. Older development tags and branches are unsupported. + +## Reporting a Vulnerability + +Do not open a public issue for a vulnerability that could put deployed systems at risk. Send a private report to the repository owner through GitHub Security Advisories or the private Behify contact channel. + +Include the affected version, architecture, deployment context, reproduction steps, impact, and any proposed mitigation. Avoid including live mesh secrets, server credentials, or public exploit details. + +## Scope + +Reports may cover the Behify installer, mesh-management script, isolated relay manager, generated systemd units, configuration handling, upgrade/rollback behavior, and release-package integrity. + +EasyTier and Xray-core are separate upstream components. Vulnerabilities in unmodified upstream binaries should also be reported to their respective upstream projects. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md new file mode 100644 index 0000000..aea3ff5 --- /dev/null +++ b/THIRD_PARTY_NOTICES.md @@ -0,0 +1,29 @@ +# Third-Party Notices + +Behify EasyMesh is source-available under the custom license in [`LICENSE`](LICENSE). The custom license includes a restriction on use in video-sharing content and is not an OSI-approved open-source license. + +## Easy-Mesh + +Behify EasyMesh is directly derived from Easy-Mesh by Musixal. The original project attribution and custom license are retained in [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE). Behify EasyMesh is independently maintained and is not an official EasyTier project. + +Project: https://github.com/Musixal/Easy-Mesh + +## EasyTier + +Release packages include unmodified `easytier-core` and `easytier-cli` binaries from EasyTier v2.6.4. EasyTier is a separate upstream project licensed under the GNU Lesser General Public License v3.0. + +Project: https://github.com/EasyTier/EasyTier + +Corresponding source commit: https://github.com/EasyTier/EasyTier/archive/8428a89d2dabc94c97d370ec607c6ca142473626.tar.gz + +SHA-256: `37fb83c61d16e5a1a88354eb96d0caf526748ffd92364ac22ffad09316bb337c` + +The release process publishes the exact v2.6.4 source archive as a separate release asset. See [`licenses/EasyTier-LGPL-3.0.txt`](licenses/EasyTier-LGPL-3.0.txt). + +## Xray-core + +The optional isolated relay component downloads Xray-core from its upstream release channel only when the administrator explicitly installs or updates that component. Xray-core is licensed under the Mozilla Public License 2.0. + +Project: https://github.com/XTLS/Xray-core + +See [`licenses/Xray-core-MPL-2.0.txt`](licenses/Xray-core-MPL-2.0.txt). diff --git a/core/v2.0.3/easytier-linux-armv7/easytier-cli b/core/v2.0.3/easytier-linux-armv7/easytier-cli deleted file mode 100644 index 1904963..0000000 Binary files a/core/v2.0.3/easytier-linux-armv7/easytier-cli and /dev/null differ diff --git a/core/v2.0.3/easytier-linux-armv7/easytier-core b/core/v2.0.3/easytier-linux-armv7/easytier-core deleted file mode 100644 index a4d6a11..0000000 Binary files a/core/v2.0.3/easytier-linux-armv7/easytier-core and /dev/null differ diff --git a/core/v2.0.3/easytier-linux-armv7hf/easytier-cli b/core/v2.0.3/easytier-linux-armv7hf/easytier-cli deleted file mode 100644 index 7feed43..0000000 Binary files a/core/v2.0.3/easytier-linux-armv7hf/easytier-cli and /dev/null differ diff --git a/core/v2.0.3/easytier-linux-armv7hf/easytier-core b/core/v2.0.3/easytier-linux-armv7hf/easytier-core deleted file mode 100644 index e0b6cd9..0000000 Binary files a/core/v2.0.3/easytier-linux-armv7hf/easytier-core and /dev/null differ diff --git a/core/v2.0.3/easytier-linux-x86_64/easytier-cli b/core/v2.0.3/easytier-linux-x86_64/easytier-cli deleted file mode 100644 index 1d4b6e2..0000000 Binary files a/core/v2.0.3/easytier-linux-x86_64/easytier-cli and /dev/null differ diff --git a/core/v2.0.3/easytier-linux-x86_64/easytier-core b/core/v2.0.3/easytier-linux-x86_64/easytier-core deleted file mode 100644 index 4774ef5..0000000 Binary files a/core/v2.0.3/easytier-linux-x86_64/easytier-core and /dev/null differ diff --git a/core/v2.0.3/readme.MD b/core/v2.0.3/readme.MD deleted file mode 100644 index 3138441..0000000 --- a/core/v2.0.3/readme.MD +++ /dev/null @@ -1 +0,0 @@ -new core diff --git a/core/v2.6.4/easytier-linux-aarch64/easytier-cli b/core/v2.6.4/easytier-linux-aarch64/easytier-cli deleted file mode 100755 index bdcf62d..0000000 Binary files a/core/v2.6.4/easytier-linux-aarch64/easytier-cli and /dev/null differ diff --git a/core/v2.6.4/easytier-linux-aarch64/easytier-core b/core/v2.6.4/easytier-linux-aarch64/easytier-core deleted file mode 100755 index b6c2db1..0000000 Binary files a/core/v2.6.4/easytier-linux-aarch64/easytier-core and /dev/null differ diff --git a/core/v2.6.4/easytier-linux-x86_64/easytier-cli b/core/v2.6.4/easytier-linux-x86_64/easytier-cli deleted file mode 100644 index bb73bbe..0000000 Binary files a/core/v2.6.4/easytier-linux-x86_64/easytier-cli and /dev/null differ diff --git a/core/v2.6.4/easytier-linux-x86_64/easytier-core b/core/v2.6.4/easytier-linux-x86_64/easytier-core deleted file mode 100644 index 9e94392..0000000 Binary files a/core/v2.6.4/easytier-linux-x86_64/easytier-core and /dev/null differ diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 0000000..9d22bdb --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,25 @@ +# Configuration + +## Mesh + +Run `sudo easymesh` and select **Connect to the Mesh Network**. Behify validates addresses, ports, hostnames, and secrets before staging a replacement. + +Defaults: + +- Encryption: enabled and recommended. Disabling it shows the risk and requires explicit confirmation. +- Multi-thread: disabled. It can improve throughput on some systems but may increase instability or packet reordering on some paths. +- IPv6: enabled, preserving the existing behavior. + +Behify generates a secret with `openssl rand -hex 16` and highlights the 32-character value once in the interactive terminal. Press Enter to use it, or type a custom secret visibly so it can be edited before Enter. A custom value is not printed again by the program. Because both generated and custom secrets are visible during setup, use a private terminal and avoid screen recording. Menu option 5 remains the explicit post-configuration reveal path. + +Sensitive settings are stored in `/etc/behify-easymesh/mesh.env`; EasyTier reads the secret through `/etc/behify-easymesh/easytier.toml`. Both files are root-only mode `0600`. The systemd unit and process arguments do not contain the secret. + +## Peer Direction + +For two nodes, one side can initiate with the other node's reachable address while the listening/reverse side leaves peer addresses blank. If that direction fails, try the reverse direction and check UDP reachability, host and provider firewalls, NAT, and another supported protocol. EasyTier may establish direct P2P after one side initiates. Behify does not automatically add reverse peers or alter routing. + +## Relay + +Menu option **Relay / Port Routing** manages the isolated `behify-relay.service`. It supports TCP, UDP, or Both and does not require or modify 3x-ui, x-ui, Hiddify, or a system Xray installation. + +Definitions are stored in `/etc/behify-easymesh/relay/relays.json`; generated Xray configuration is `/etc/behify-easymesh/relay/config.json`. Port conflicts and route reachability are checked before activation, and failed changes roll back. diff --git a/docs/historical/smoke-test-v0.1.0.md b/docs/historical/smoke-test-v0.1.0.md new file mode 100644 index 0000000..660755a --- /dev/null +++ b/docs/historical/smoke-test-v0.1.0.md @@ -0,0 +1,11 @@ +# Historical Smoke Test - v0.1.0 + +This archived report records pre-v1 validation with the legacy EasyTier v2.0.3 runtime. That runtime and its installation procedure are unsupported in Behify EasyMesh v1.0.0 and are retained here only as historical evidence. + +## Recorded Result + +- Core: `easytier-core 2.0.3-861d6584` +- CLI: `easytier-cli 2.0.3-861d6584` +- Scope: two-server UDP mesh smoke test + +Do not use this report as an installation guide. Current installations must use an architecture-specific v1.0.0 Release asset containing EasyTier v2.6.4. diff --git a/docs/historical/smoke-test-v0.1.3-v2.6.4.md b/docs/historical/smoke-test-v0.1.3-v2.6.4.md new file mode 100644 index 0000000..a0e3133 --- /dev/null +++ b/docs/historical/smoke-test-v0.1.3-v2.6.4.md @@ -0,0 +1,36 @@ +# Historical Smoke Test - v0.1.3 with EasyTier v2.6.4 + +Date: 2026-07-09 + +This archived report records the pre-v1 test that established EasyTier v2.6.4 as the supported runtime. The old version-selector command is no longer supported; v1 release packages install v2.6.4 directly. + +## Tested Runtime + +```text +easytier-core 2.6.4-8428a89d +easytier-cli 2.6.4-8428a89d +``` + +## Topology + +```text +Server 1: kharej, 10.144.144.1/24 +Server 2: dakhel, 10.144.144.2/24 +Protocol: udp +Connection: p2p +``` + +## Result + +```text +Packet loss: 0% +Latency: ~26-27 ms +iperf3: ~160 Mbps forward, ~50 Mbps reverse +``` + +Equivalent v1 offline installation starts from the matching architecture Release package: + +```bash +sudo EASYMESH_OFFLINE=1 bash install.sh +sudo easymesh +``` diff --git a/docs/operations.md b/docs/operations.md new file mode 100644 index 0000000..727b67a --- /dev/null +++ b/docs/operations.md @@ -0,0 +1,55 @@ +# Operations + +## Paths and Services + +| Purpose | Path or unit | +| --- | --- | +| Application | `/opt/behify-easymesh` | +| Command | `/usr/local/bin/easymesh` | +| EasyTier runtime | `/root/easytier/easytier-core`, `/root/easytier/easytier-cli` | +| Mesh service | `/etc/systemd/system/easymesh.service` | +| Private mesh environment | `/etc/behify-easymesh/mesh.env` | +| Private EasyTier config | `/etc/behify-easymesh/easytier.toml` | +| Mesh backups | `/etc/behify-easymesh/backups/` | +| Installer backups | `/opt/behify-easymesh-backups/` | +| Isolated relay service | `behify-relay.service` | + +## Upgrade and Rollback + +Before upgrading a pre-v1 installation, inspect the known legacy maintenance locations without executing or deleting them: + +```bash +sudo test ! -e /root/easytier/reset.sh || sudo stat /root/easytier/reset.sh +sudo crontab -l 2>/dev/null | grep -F '/root/easytier/reset.sh' || true +``` + +The installer refuses an unattended upgrade while either legacy marker exists. For a recognized RC1 service, RC2 and later candidates preserve `mesh.env` and its network options while migrating only the service's secret transport to the root-only EasyTier config. Candidate runtime and config validation happen before activation. Active/inactive and enabled/disabled states are preserved; failure restores the previous application, runtime pair, service file, private config, command path, and active state. + +## Diagnostics + +```bash +/root/easytier/easytier-core --version +/root/easytier/easytier-cli --version +PID=$(systemctl show -p MainPID --value easymesh.service) +sudo /proc/$PID/exe --version +systemctl status easymesh.service +journalctl -u easymesh.service -n 100 --no-pager +``` + +Do not paste service output into a public report without reviewing it. Managed RC2 and later services keep the mesh secret out of the unit, process arguments, normal status output, and new WARN-level journal output. The installer intentionally does not erase historical journal entries; when auditing an upgraded host, scope the journal query to the time after the new managed service started. + +For manual throughput testing, install `iperf3` separately. Retransmissions or packet reordering depend on the path and environment and are not by themselves proof of an EasyMesh defect. + +## Removal + +Normal uninstall preserves configuration and service files: + +```bash +sudo /opt/behify-easymesh/uninstall.sh +``` + +Explicit purge requires typing `PURGE` and removes verified Behify-owned mesh configuration, units, and backups. The isolated relay is removed only from its own menu. + +```bash +sudo /opt/behify-easymesh/uninstall.sh --purge +``` diff --git a/docs/release-checklist-v1.0.0-rc.1.md b/docs/release-checklist-v1.0.0-rc.1.md new file mode 100644 index 0000000..874175d --- /dev/null +++ b/docs/release-checklist-v1.0.0-rc.1.md @@ -0,0 +1,50 @@ +# Behify EasyMesh v1.0.0-rc.1 Release Checklist + +This checklist is for the release owner. It does not replace automated CI. + +## Repository + +- Confirm the release branch is based on the approved `main` commit. +- Confirm `LICENSE` and `NOTICE` are unchanged. +- Confirm no EasyTier or Xray executable is tracked in Git. +- Confirm the working tree is clean before running `release/build-release.sh`. + +## Automated Validation + +```bash +bash tests/run-all.sh release-output +git diff --check +git diff main...HEAD --check +``` + +CI must complete Bash syntax checks, ShellCheck, regressions, package construction, strict-offline installation, package checksum validation, native EasyTier execution, and disposable real-systemd tests on both supported architectures. + +## Manual Linux Gates + +- Install the x86_64 package on a disposable systemd host and verify fresh install, repeated install, active-service upgrade, inactive-service upgrade, and rollback after a forced service failure. +- Install the aarch64 package on a native ARM64 systemd host and run both bundled binaries with `--version` before opening the menu. +- Confirm `/usr/local/bin/easymesh` resolves to `/opt/behify-easymesh/easymesh`. +- Confirm `easymesh --version` reports Behify `v1.0.0-rc.1` and EasyTier `v2.6.4`. +- Confirm an offline install succeeds while outbound network access is blocked. +- Confirm the menu starts without a network attempt or runtime replacement. +- Join a disposable two-node mesh and verify peer, route, and Peer-Center terminal views. +- Verify the existing two-server UDP/P2P scenario and document whether direct or fallback multi-hop routing is observed. +- Inspect `/root/easytier/reset.sh` and the matching root-cron entry without executing or deleting either one. The RC installer refuses affected upgrades; cleanup requires separate authorization. +- Exercise relay add/remove/rollback on a disposable host and verify system Xray, x-ui/3x-ui, and Hiddify paths remain untouched. + +## Release Assets + +- `behify-easymesh-v1.0.0-rc.1-linux-x86_64.tar.gz` +- `behify-easymesh-v1.0.0-rc.1-linux-aarch64.tar.gz` +- `online-install-v1.0.0-rc.1.sh` +- `SHA256SUMS` +- `easytier-v2.6.4-source.tar.gz` + +Verify every published byte against the locally generated `SHA256SUMS`. Do not use GitHub's automatic source archives as offline installers. + +## Optional Post-RC Improvements + +- Signed checksums or provenance attestations +- SBOM generation +- Dependabot configuration +- Branch protection and repository security settings diff --git a/docs/release-checklist-v1.0.0-rc.2.md b/docs/release-checklist-v1.0.0-rc.2.md new file mode 100644 index 0000000..542de19 --- /dev/null +++ b/docs/release-checklist-v1.0.0-rc.2.md @@ -0,0 +1,41 @@ +# Behify EasyMesh v1.0.0-rc.2 Release Checklist + +## Repository + +- Confirm the branch and approved release commit. +- Confirm `LICENSE` and `NOTICE` match the immutable integrity baseline. +- Confirm no EasyTier or Xray executable is tracked in Git. +- Build only from a clean committed tree. + +## Automated Gates + +```bash +bash tests/run-all.sh release-output +git diff --check +git diff main...HEAD --check +``` + +CI must pass syntax checks, ShellCheck, regressions, package validation, strict-offline installation, native EasyTier execution, and disposable real-systemd upgrade/rollback tests on both x86_64 and aarch64. Skipped native jobs are not validated. + +The systemd tests must confirm that a unique fake secret is absent from the service unit, process arguments, `systemctl` output, and journal after RC1 migration. They must also confirm root-only config permissions and preservation of service state and existing mesh options. + +## Exact Assets + +- `behify-easymesh-v1.0.0-rc.2-linux-x86_64.tar.gz` +- `behify-easymesh-v1.0.0-rc.2-linux-aarch64.tar.gz` +- `online-install-v1.0.0-rc.2.sh` +- `install.sh` +- `SHA256SUMS` +- `easytier-v2.6.4-source.tar.gz` + +Verify every asset with `SHA256SUMS` and confirm `install.sh` is byte-identical to the versioned bootstrap. Do not publish from a dirty tree or reuse an earlier release-output directory. + +## Manual Linux Checks + +- Confirm the generated secret is shown exactly once and Enter selects it. +- Confirm custom secret entry is hidden and not repeated afterward. +- Confirm option 5 reveals the stored secret only when explicitly selected. +- Confirm encryption, multi-thread, and IPv6 defaults. +- Confirm peer, route, and Peer-Center displays on narrow and wide SSH terminals. +- Confirm one-way initiation and the reverse-direction troubleshooting path on disposable nodes. +- Review real service status, journal, and `/proc/$PID/cmdline` for secret exposure. diff --git a/docs/release-checklist-v1.0.0-rc.3.md b/docs/release-checklist-v1.0.0-rc.3.md new file mode 100644 index 0000000..888e024 --- /dev/null +++ b/docs/release-checklist-v1.0.0-rc.3.md @@ -0,0 +1,39 @@ +# Behify EasyMesh v1.0.0-rc.3 Release Checklist + +## Repository and CI + +- Confirm the release commit is on `release/v1.0.0-prep` and the working tree is clean. +- Confirm `LICENSE` and `NOTICE` match the immutable integrity baseline. +- Confirm EasyTier remains v2.6.4 and no runtime executable is tracked in Git. +- Require successful syntax, ShellCheck, regression, package, strict-offline, native x86_64, native aarch64, and real-systemd jobs. +- Build and upload only the exact artifacts produced by the successful RC3 workflow run. + +## Exact Assets + +- `behify-easymesh-v1.0.0-rc.3-linux-x86_64.tar.gz` +- `behify-easymesh-v1.0.0-rc.3-linux-aarch64.tar.gz` +- `online-install-v1.0.0-rc.3.sh` +- `install.sh` +- `SHA256SUMS` +- `easytier-v2.6.4-source.tar.gz` + +Verify every asset with `SHA256SUMS` and confirm `install.sh` is byte-identical to the versioned bootstrap. Do not modify the RC2 tag or assets. + +## Manual UX Checks + +- Confirm Connect to Mesh shows only the concise peer/UDP hint before its prompts. +- Confirm the generated secret is bold cyan, is exactly 32 lowercase hex characters, and Enter selects it. +- Confirm custom secret input is visible and editable, accepted once, and not printed again by the program. +- Confirm there is no hidden secret read, repeat verification, or second secret confirmation. +- Confirm option 5 still reveals the stored secret only when explicitly selected. +- Confirm encryption and multi-thread warnings appear only at their respective prompts. +- Confirm installation exits after printing `Run: sudo easymesh` without launching the TUI. + +## Two-Server RC3 Gate + +- Install or upgrade RC3 on two disposable servers using the exact published assets. +- Confirm active and enabled service state, restart recovery, direct P2P, and bidirectional ping. +- Re-run the secret audit against the unit, process arguments, status output, and only new journal entries. +- Confirm both private config files are mode `0600` and owned by `root:root`. +- Treat old pre-RC2 journal entries as historical evidence; do not erase them automatically. +- Do not publish stable v1.0.0 until these RC3 checks pass. diff --git a/docs/release-checklist-v1.0.0.md b/docs/release-checklist-v1.0.0.md new file mode 100644 index 0000000..225378c --- /dev/null +++ b/docs/release-checklist-v1.0.0.md @@ -0,0 +1,27 @@ +# Behify EasyMesh v1.0.0 Release Checklist + +## Source and Validation + +- Confirm the release commit is on `release/v1.0.0-prep` and the working tree is clean. +- Confirm `BEHIFY_EASYMESH_VERSION=1.0.0` and `EASYTIER_VERSION=v2.6.4` in `versions.env`. +- Confirm `LICENSE`, `NOTICE`, runtime scripts, installer behavior, networking, routing, relay behavior, service design, and secret handling match the validated RC3 source. +- Require successful Bash syntax, ShellCheck, static regressions, package checks, strict-offline tests, native x86_64, native aarch64, and real-systemd tests. +- Build stable artifacts from the clean stable-promotion commit; do not reuse RC3 artifacts. + +## Exact Assets + +- `behify-easymesh-v1.0.0-linux-x86_64.tar.gz` +- `behify-easymesh-v1.0.0-linux-aarch64.tar.gz` +- `online-install-v1.0.0.sh` +- `install.sh` +- `SHA256SUMS` +- `easytier-v2.6.4-source.tar.gz` + +Reject missing, extra, stale, or differently named files. Verify every listed asset with `SHA256SUMS` and confirm `install.sh` is byte-identical to `online-install-v1.0.0.sh`. + +## Publication Guardrails + +- Confirm the published v1.0.0-rc.3 tag and release remain unchanged. +- Create no stable tag or release until the stable commit's CI run passes every required job. +- Verify the stable tag resolves exactly to the tested commit before publication. +- Publish only the six verified assets produced by that successful workflow. diff --git a/docs/release-notes-v1.0.0-rc.1.md b/docs/release-notes-v1.0.0-rc.1.md new file mode 100644 index 0000000..5d6f588 --- /dev/null +++ b/docs/release-notes-v1.0.0-rc.1.md @@ -0,0 +1,28 @@ +# Behify EasyMesh v1.0.0-rc.1 + +This prerelease prepares the first stable Behify EasyMesh release and pins EasyTier v2.6.4 for Linux x86_64 and aarch64. + +## Included + +- Verified architecture-specific packages assembled from pinned official EasyTier archives. +- Transactional application and runtime installation with rollback and service-state preservation. +- Root-only mesh secret storage and staged systemd configuration validation. +- Ownership-aware uninstall and explicit purge boundaries. +- Existing isolated Dokodemo-Door relay architecture and identities unchanged. + +## Validation + +The release artifacts must be built from the tagged commit and pass the GitHub Actions RC workflow. Each package must execute its matching EasyTier core and CLI natively and complete disposable real-systemd install, upgrade, state-preservation, and rollback checks before the draft release is approved. + +## Operational Limitation + +Pre-v1 installations may contain `/root/easytier/reset.sh` and a root-cron entry that performs global EasyTier process termination. The RC installer refuses to upgrade while either known legacy marker exists. Inspect them without executing or deleting them: + +```bash +sudo test ! -e /root/easytier/reset.sh || sudo stat /root/easytier/reset.sh +sudo crontab -l 2>/dev/null | grep -F '/root/easytier/reset.sh' || true +``` + +Cleanup requires separate operational authorization. Initial RC testing should use a disposable or noncritical node. + +This RC is not the stable v1.0.0 release. The repository remains private during draft validation, so anonymous installation is unavailable. diff --git a/docs/release-notes-v1.0.0-rc.2.md b/docs/release-notes-v1.0.0-rc.2.md new file mode 100644 index 0000000..c335e17 --- /dev/null +++ b/docs/release-notes-v1.0.0-rc.2.md @@ -0,0 +1,16 @@ +# Behify EasyMesh v1.0.0-rc.2 + +RC2 contains six focused changes: + +1. Managed mesh services keep the network secret out of process arguments and suppress EasyTier's INFO-level effective-config output. +2. New 32-character generated secrets are displayed once during interactive setup; custom input remains hidden. +3. Encryption defaults to enabled, multi-thread defaults to disabled, and IPv6 retains its existing default. +4. The dashboard shows one concise EasyTier version row. +5. Troubleshooting now explains one-way peer initiation, reverse attempts, firewall checks, and direct P2P behavior. +6. Release assets include a checksummed `install.sh` alias that is byte-identical to the versioned online bootstrap. + +This remains a prerelease, not stable v1.0.0. Real deployment testing remains pending. + +Pre-v1 installations with `/root/easytier/reset.sh` or the matching root cron entry still require separate, supervised cleanup before upgrade. The installer does not execute or remove those legacy maintenance items. + +Behify EasyMesh is based on [Easy-Mesh by Musixal](https://github.com/Musixal/Easy-Mesh). `LICENSE` and `NOTICE` are retained. diff --git a/docs/release-notes-v1.0.0-rc.3.md b/docs/release-notes-v1.0.0-rc.3.md new file mode 100644 index 0000000..d24395f --- /dev/null +++ b/docs/release-notes-v1.0.0-rc.3.md @@ -0,0 +1,16 @@ +# Behify EasyMesh v1.0.0-rc.3 + +RC3 is a focused UX and release-readiness candidate built on the validated RC2 runtime and security design. + +1. Connect to Mesh now opens with one concise peer/UDP hint instead of a multi-line explanation. +2. The generated 32-character network secret is highlighted in bold cyan and Enter accepts it directly. +3. Custom secret input is intentionally visible and editable; there is no hidden input, repeat verification, or second confirmation. +4. Stable and tagged-candidate installation commands are documented clearly. The installer exits after printing `Run: sudo easymesh` and does not launch the TUI automatically. +5. EasyTier remains pinned to v2.6.4. Routing, relay, and networking semantics are unchanged. +6. RC2's root-only secret storage, private config transport, secret-free process arguments, and WARN-level logging remain intact. + +RC2 passed a real two-node x86_64 deployment, upgrade, restart, direct-P2P, and secret-exposure audit. RC3 still requires the same smoke test on two disposable servers before stable v1.0.0 can be considered. + +RC2's published tag and assets remain immutable. RC3 must be built from its own clean commit and published as a separate prerelease. + +Behify EasyMesh is based on [Easy-Mesh by Musixal](https://github.com/Musixal/Easy-Mesh). `LICENSE` and `NOTICE` are retained. diff --git a/docs/release-notes-v1.0.0.md b/docs/release-notes-v1.0.0.md new file mode 100644 index 0000000..fb7ea3a --- /dev/null +++ b/docs/release-notes-v1.0.0.md @@ -0,0 +1,17 @@ +# Behify EasyMesh v1.0.0 + +Behify EasyMesh v1.0.0 promotes the published v1.0.0-rc.3 candidate to the first stable release without changing its runtime behavior. + +## Validated Release + +- EasyTier remains pinned to v2.6.4. +- CI passed validate-and-build, package validation, strict-offline installation, native x86_64, native aarch64, and disposable real-systemd installation, upgrade, and rollback tests. +- RC3 was installed on two real x86_64 servers with both `easymesh.service` instances active and enabled. +- Direct P2P/DIRECT routing and bidirectional ping passed with 0% packet loss. +- Transactional installation, service-state preservation, rollback, architecture validation, and exact release checksums remain unchanged. +- Root-only secret storage, config-based secret transport, secret-free process arguments, and WARN-level managed-service logging remain unchanged. +- The isolated Dokodemo-Door relay and existing EasyTier networking and routing behavior remain unchanged. + +Stable v1.0.0 changes release metadata and documentation only. It does not change the RC3 runtime, installer, service design, networking, routing, relay, or secret handling. + +Behify EasyMesh is based on [Easy-Mesh by Musixal](https://github.com/Musixal/Easy-Mesh). `LICENSE` and `NOTICE` are retained. diff --git a/docs/smoke-test-v0.1.0.md b/docs/smoke-test-v0.1.0.md deleted file mode 100644 index b72d4b9..0000000 --- a/docs/smoke-test-v0.1.0.md +++ /dev/null @@ -1,70 +0,0 @@ -# Smoke Test - v0.1.0 Offline Install - -Date: 2026-07-09 - -## Scope - -This smoke test validates the Behify EasyMesh offline-first installation flow and a basic two-server UDP mesh connection. - -## Tested Core - -```text -easytier-core 2.0.3-861d6584 -easytier-cli 2.0.3-861d6584 -``` - -`v2.0.3` is the currently smoke-tested default core version. - -`v2.6.4` support is prepared through `EASYMESH_CORE_VERSION`, but strict offline testing requires adding matching local binaries under `core/v2.6.4/`. - -## Server 1 - -```text -Hostname: serverkh -Mesh IP: 10.144.144.1/24 -Tunnel protocol: udp -Service: easymesh.service -Status: active/running -``` - -## Server 2 - -```text -Hostname: dakhel -Mesh IP: 10.144.144.2/24 -Tunnel protocol: udp -Service: easymesh.service -Status: active/running -``` - -## Results - -```text -Peer discovery: OK -Route discovery: OK -UDP tunnel: OK -P2P connection: OK -Packet loss: 0% -Latency: ~28-29 ms -``` - -## Validation Commands - -```bash -unzip behify-easymesh-test.zip -cd behify-easymesh-test -``` - -```bash -systemctl status easymesh.service --no-pager -/root/easytier/easytier-cli peer -/root/easytier/easytier-cli route -ping -c 4 10.144.144.1 -ping -c 4 10.144.144.2 -``` - -## Notes - -The offline-first installation successfully installed EasyTier core binaries from the local package. - -The banner contact lines fit inside the menu box, and peer/route views show a friendly message when the service is not running. diff --git a/docs/smoke-test-v0.1.3-v2.6.4.md b/docs/smoke-test-v0.1.3-v2.6.4.md deleted file mode 100644 index 8891f4c..0000000 --- a/docs/smoke-test-v0.1.3-v2.6.4.md +++ /dev/null @@ -1,49 +0,0 @@ -# Smoke Test - v0.1.3 EasyTier v2.6.4 - -Date: 2026-07-09 - -## Scope - -This smoke test validates strict offline installation with EasyTier v2.6.4 and a basic two-server UDP mesh connection. - -## Command Used - -```bash -sudo EASYMESH_OFFLINE=1 EASYMESH_CORE_VERSION=v2.6.4 ./easymesh -``` - -## Tested Core - -```text -easytier-core 2.6.4-8428a89d -easytier-cli 2.6.4-8428a89d -``` - -## Server 1 - -```text -Hostname: kharej -Mesh IP: 10.144.144.1/24 -Tunnel protocol: udp -Connection: p2p -Service: easymesh.service -``` - -## Server 2 - -```text -Hostname: dakhel -Mesh IP: 10.144.144.2/24 -Tunnel protocol: udp -Connection: p2p -Service: easymesh.service -``` - -## Results - -```text -Protocol: udp -Connection: p2p -Packet loss: 0% -Latency: ~26-27 ms -``` diff --git a/easymesh b/easymesh index b03bbbd..dec2ec2 100644 --- a/easymesh +++ b/easymesh @@ -7,71 +7,67 @@ WHITE="\033[1;37m" RESET="\033[0m" MAGENTA="\033[0;35m" -DEFAULT_CORE_VERSION="v2.0.3" -CORE_VERSION="${EASYMESH_CORE_VERSION:-$DEFAULT_CORE_VERSION}" -CORE_VERSION_EXPLICIT=0 +SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" +VERSION_FILE="$SCRIPT_DIR/versions.env" +RUNTIME_MANIFEST="$SCRIPT_DIR/manifest.txt" + +if [[ ! -r "$VERSION_FILE" ]]; then + printf 'Behify EasyMesh version metadata is missing: %s\n' "$VERSION_FILE" >&2 + exit 1 +fi + +# shellcheck source=versions.env +. "$VERSION_FILE" +if [[ ! "$BEHIFY_EASYMESH_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]] || \ + [[ ! "$EASYTIER_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + printf 'Behify EasyMesh version metadata is invalid.\n' >&2 + exit 1 +fi +readonly BEHIFY_EASYMESH_VERSION EASYTIER_VERSION +EASYTIER_VERSION_NUMBER="${EASYTIER_VERSION#v}" show_usage() { printf '%s\n' \ "Usage:" \ " sudo easymesh" \ - " sudo easymesh 2.6.4" \ - " sudo easymesh --core v2.6.4" \ - "" \ - "Environment:" \ - " EASYMESH_OFFLINE=1" \ - " EASYMESH_CORE_VERSION=v2.6.4" \ + " sudo easymesh --version" \ "" \ "Notes:" \ - " Default core remains v2.0.3." \ - " v2.6.4 can be selected explicitly for testing." \ + " EasyTier v2.6.4 is the only supported runtime." \ + " Runtime installation and upgrades are handled by install.sh." \ + " Encryption is enabled by default; disabling it requires confirmation." \ + " Multi-thread mode is disabled by default." \ + " IPv6 keeps the existing enabled-by-default behavior." \ "" \ "Runtime checks:" \ " /root/easytier/easytier-core --version" \ " PID=\$(systemctl show -p MainPID --value easymesh.service); sudo /proc/\$PID/exe --version" } -normalize_core_version() { - local version="$1" - if [[ "$version" == v* ]]; then - printf '%s\n' "$version" - else - printf 'v%s\n' "$version" - fi -} - -is_core_version_arg() { - [[ "$1" =~ ^v?[0-9]+(\.[0-9]+){2}$ ]] -} - if [[ $# -gt 0 ]]; then case "$1" in --help|-h) show_usage exit 0 ;; - --core) - if [[ -z "$2" || $# -gt 2 ]] || ! is_core_version_arg "$2"; then + --version) + [[ $# -eq 1 ]] || { show_usage exit 1 - fi - CORE_VERSION="$(normalize_core_version "$2")" - CORE_VERSION_EXPLICIT=1 + } + printf 'Behify EasyMesh v%s\n' "$BEHIFY_EASYMESH_VERSION" + printf 'EasyTier %s\n' "$EASYTIER_VERSION" + exit 0 ;; *) - if [[ $# -eq 1 ]] && is_core_version_arg "$1"; then - CORE_VERSION="$(normalize_core_version "$1")" - CORE_VERSION_EXPLICIT=1 - else - show_usage - exit 1 - fi + show_usage + exit 1 ;; esac fi # Check if the script is run as root -if [[ $EUID -ne 0 ]]; then +if [[ "${EASYMESH_SOURCE_ONLY:-0}" != "1" && $EUID -ne 0 ]]; then echo "This script must be run as root" sleep 1 exit 1 @@ -130,12 +126,6 @@ colorize() { echo -e "${style_code}${color_code}${text}${reset}" } -is_arm_hard_float() { - ldd /bin/ls 2>/dev/null | grep -qi 'armhf' || \ - [ -e /lib/ld-linux-armhf.so.3 ] || \ - [ -e /lib/arm-linux-gnueabihf/ld-linux-armhf.so.3 ] -} - resolve_core_arch_directory() { local arch="${1:-$(uname -m)}" @@ -146,19 +136,22 @@ resolve_core_arch_directory() { aarch64|arm64) printf 'easytier-linux-aarch64\n' ;; - armv7l) - if is_arm_hard_float; then - printf 'easytier-linux-armv7hf\n' - else - printf 'easytier-linux-armv7\n' - fi - ;; *) return 1 ;; esac } +resolve_release_architecture() { + local arch="${1:-$(uname -m)}" + + case "$arch" in + x86_64|amd64) printf 'x86_64\n' ;; + aarch64|arm64) printf 'aarch64\n' ;; + *) return 1 ;; + esac +} + get_binary_version_number() { local binary="$1" @@ -166,481 +159,439 @@ get_binary_version_number() { "$binary" --version 2>/dev/null | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 } -validate_local_core_candidate() { - local candidate_dir="$1" - local system_arch="$2" - local temp_dir temp_core temp_cli core_output cli_output core_version cli_version - local selected_number="${CORE_VERSION#v}" +manifest_value() { + local key="$1" - if [[ ! -f "$candidate_dir/easytier-core" || ! -f "$candidate_dir/easytier-cli" ]]; then - return 1 - fi + [[ -f "$RUNTIME_MANIFEST" ]] || return 1 + awk -F= -v key="$key" '$1 == key { count++; value=substr($0, index($0, "=") + 1) } END { if (count != 1) exit 1; print value }' "$RUNTIME_MANIFEST" +} - temp_dir=$(mktemp -d) || return 1 - temp_core="$temp_dir/easytier-core" - temp_cli="$temp_dir/easytier-cli" +verify_installed_runtime() { + local architecture manifest_arch core_hash cli_hash actual_core_hash actual_cli_hash + local core_version cli_version - if ! cp "$candidate_dir/easytier-core" "$temp_core" || \ - ! cp "$candidate_dir/easytier-cli" "$temp_cli" || \ - ! chmod +x "$temp_core" "$temp_cli"; then - rm -rf "$temp_dir" + for required_command in uname awk sha256sum systemctl; do + command -v "$required_command" >/dev/null 2>&1 || { + printf 'Required command is missing: %s. Install it with your operating system package manager.\n' "$required_command" >&2 + return 1 + } + done + if [[ "$(uname -s)" != "Linux" ]]; then + printf 'Unsupported operating system: %s. Behify EasyMesh v1 supports Linux only.\n' "$(uname -s)" >&2 return 1 fi - - core_output=$("$temp_core" --version 2>&1) - if [[ $? -ne 0 ]]; then - colorize red "Local EasyTier core candidate cannot execute." bold - colorize red "Detected architecture: $system_arch" bold - colorize red "Selected package: $candidate_dir" bold - colorize red "$core_output" bold - rm -rf "$temp_dir" - return 2 + if ! architecture=$(resolve_release_architecture); then + printf 'Unsupported architecture: %s. Behify EasyMesh v1 supports only x86_64 and aarch64.\n' "$(uname -m)" >&2 + return 1 fi - - cli_output=$("$temp_cli" --version 2>&1) - if [[ $? -ne 0 ]]; then - colorize red "Local EasyTier CLI candidate cannot execute." bold - colorize red "Detected architecture: $system_arch" bold - colorize red "Selected package: $candidate_dir" bold - colorize red "$cli_output" bold - rm -rf "$temp_dir" - return 2 + if [[ ! -f /root/easytier/easytier-core || -L /root/easytier/easytier-core || ! -x /root/easytier/easytier-core || + ! -f /root/easytier/easytier-cli || -L /root/easytier/easytier-cli || ! -x /root/easytier/easytier-cli ]]; then + printf 'EasyTier v2.6.4 is not installed. Run the verified Behify EasyMesh installer.\n' >&2 + return 1 fi - - core_version=$(printf '%s\n' "$core_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1) - cli_version=$(printf '%s\n' "$cli_output" | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1) - if [[ "$core_version" != "$selected_number" || "$cli_version" != "$selected_number" ]]; then - colorize red "Local core package version does not match selected core $CORE_VERSION." bold - colorize red "Detected architecture: $system_arch" bold - colorize red "Selected package: $candidate_dir" bold - colorize red "Candidate versions: core=${core_version:-unknown}, cli=${cli_version:-unknown}" bold - rm -rf "$temp_dir" - return 2 + if [[ ! -f "$RUNTIME_MANIFEST" ]]; then + printf 'Runtime manifest is missing: %s. Reinstall from a verified release package.\n' "$RUNTIME_MANIFEST" >&2 + return 1 fi - VALIDATED_CORE="$temp_core" - VALIDATED_CLI="$temp_cli" - VALIDATED_TEMP_DIR="$temp_dir" - return 0 -} - -install_validated_core_files() { - local dest_dir="$1" - local staged_core="$dest_dir/.easytier-core.new.$$" - local staged_cli="$dest_dir/.easytier-cli.new.$$" - - mkdir -p "$dest_dir" || return 1 - install -m 0755 "$VALIDATED_CORE" "$staged_core" || return 1 - install -m 0755 "$VALIDATED_CLI" "$staged_cli" || { - rm -f "$staged_core" + manifest_arch=$(manifest_value architecture) || return 1 + core_hash=$(manifest_value easytier_core_sha256) || return 1 + cli_hash=$(manifest_value easytier_cli_sha256) || return 1 + [[ "$manifest_arch" == "$architecture" ]] || { + printf 'Installed package architecture mismatch: expected %s, found %s.\n' "$architecture" "$manifest_arch" >&2 return 1 } - if ! mv -f "$staged_core" "$dest_dir/easytier-core"; then - rm -f "$staged_core" "$staged_cli" + [[ "$core_hash" =~ ^[0-9a-f]{64}$ && "$cli_hash" =~ ^[0-9a-f]{64}$ ]] || return 1 + actual_core_hash=$(sha256sum /root/easytier/easytier-core | awk '{print $1}') || return 1 + actual_cli_hash=$(sha256sum /root/easytier/easytier-cli | awk '{print $1}') || return 1 + [[ "$actual_core_hash" == "$core_hash" && "$actual_cli_hash" == "$cli_hash" ]] || { + printf 'Installed EasyTier files do not match the verified package manifest. Reinstall before continuing.\n' >&2 return 1 - fi - if ! mv -f "$staged_cli" "$dest_dir/easytier-cli"; then - rm -f "$staged_cli" + } + + core_version=$(get_binary_version_number /root/easytier/easytier-core) || return 1 + cli_version=$(get_binary_version_number /root/easytier/easytier-cli) || return 1 + [[ "$core_version" == "$EASYTIER_VERSION_NUMBER" && "$cli_version" == "$EASYTIER_VERSION_NUMBER" ]] || { + printf 'Unsupported EasyTier runtime. Required: %s; core: %s; cli: %s.\n' \ + "$EASYTIER_VERSION" "${core_version:-unknown}" "${cli_version:-unknown}" >&2 return 1 - fi + } } -restore_core_backup() { - local backup_dir="$1" - local service_file="$2" - local service_was_active="$3" - local restore_core="/root/easytier/.easytier-core.rollback.$$" - local restore_cli="/root/easytier/.easytier-cli.rollback.$$" - - systemctl stop easymesh.service &>/dev/null || true - - if install -m 0755 "$backup_dir/easytier-core" "$restore_core" && \ - install -m 0755 "$backup_dir/easytier-cli" "$restore_cli" && \ - mv -f "$restore_core" /root/easytier/easytier-core && \ - mv -f "$restore_cli" /root/easytier/easytier-cli; then - if [[ "$service_was_active" == "1" && -f "$service_file" ]]; then - if systemctl start easymesh.service; then - colorize green "Rollback completed and the previous service was restarted." bold - else - colorize red "Binaries were restored, but the previous service could not be restarted." bold - fi +generate_random_secret() { + openssl rand -hex 16 +} + +#Var +EASY_CLIENT='/root/easytier/easytier-cli' +EASY_CORE='/root/easytier/easytier-core' +SERVICE_FILE="/etc/systemd/system/easymesh.service" +MESH_CONFIG_DIR="/etc/behify-easymesh" +MESH_ENV_FILE="$MESH_CONFIG_DIR/mesh.env" +MESH_CORE_CONFIG="$MESH_CONFIG_DIR/easytier.toml" +MAIN_CORE_VERSION="" + +is_valid_ip_address() { + command -v python3 >/dev/null 2>&1 && + python3 -c 'import ipaddress, sys; ipaddress.ip_address(sys.argv[1])' "$1" >/dev/null 2>&1 +} + +is_valid_hostname() { + local value="$1" + + [[ ${#value} -ge 1 && ${#value} -le 253 ]] && + [[ "$value" =~ ^[A-Za-z0-9]([A-Za-z0-9._-]*[A-Za-z0-9])?$ ]] +} + +is_valid_network_secret() { + local value="$1" + + [[ ${#value} -ge 12 && ${#value} -le 128 ]] && + [[ "$value" =~ ^[A-Za-z0-9][A-Za-z0-9._~@+=,:/-]*$ ]] +} + +trim_ascii_space() { + local value="$1" + + value="${value#"${value%%[![:space:]]*}"}" + value="${value%"${value##*[![:space:]]}"}" + printf '%s\n' "$value" +} + +build_peer_arguments() { + local raw_addresses="$1" protocol="$2" port="$3" + local address formatted + local -a addresses processed + + PEER_ARGUMENTS="" + [[ -n "$raw_addresses" ]] || return 0 + [[ ${#raw_addresses} -le 4096 ]] || return 1 + IFS=',' read -r -a addresses <<< "$raw_addresses" + [[ ${#addresses[@]} -le 64 ]] || return 1 + for address in "${addresses[@]}"; do + address=$(trim_ascii_space "$address") + [[ -n "$address" ]] || return 1 + if [[ "$address" == \[*\] ]]; then + address="${address:1:${#address}-2}" + fi + is_valid_ip_address "$address" || return 1 + if [[ "$address" == *:* ]]; then + formatted="[$address]" else - colorize green "Rollback completed. Previous core binaries were restored." bold + formatted="$address" fi - return 0 - fi + processed+=("${protocol}://${formatted}:${port}") + done + PEER_ARGUMENTS="--peers ${processed[*]}" +} - rm -f "$restore_core" "$restore_cli" - colorize red "Automatic rollback failed. Backup retained at: $backup_dir" bold - return 1 +write_mesh_environment() { + local destination="$1" + + umask 077 + { + printf 'EASYMESH_IP="%s"\n' "$IP_ADDRESS" + printf 'EASYMESH_PEERS="%s"\n' "$PEER_ARGUMENTS" + printf 'EASYMESH_HOSTNAME="%s"\n' "$HOSTNAME" + printf 'EASYMESH_NETWORK_SECRET="%s"\n' "$NETWORK_SECRET" + printf 'EASYMESH_DEFAULT_PROTOCOL="%s"\n' "$DEFAULT_PROTOCOL" + printf 'EASYMESH_LISTENERS="%s"\n' "$LISTENERS" + printf 'EASYMESH_MULTI_THREAD="%s"\n' "$MULTI_THREAD_OPTION" + printf 'EASYMESH_ENCRYPTION="%s"\n' "$ENCRYPTION_OPTION" + printf 'EASYMESH_IPV6="%s"\n' "$IPV6_OPTION" + } > "$destination" + chmod 0600 "$destination" } -upgrade_installed_core() { - local candidate_dir="$1" - local system_arch="$2" - local dest_dir="/root/easytier" - local service_file="/etc/systemd/system/easymesh.service" - local selected_number="${CORE_VERSION#v}" - local installed_core installed_cli backup_dir service_was_active=0 pid running_version +write_mesh_core_config() { + local destination="$1" - installed_core=$(get_binary_version_number "$dest_dir/easytier-core" || true) - installed_cli=$(get_binary_version_number "$dest_dir/easytier-cli" || true) + umask 077 + cat > "$destination" <<'EOF' +[network_identity] +network_name = "default" +network_secret = "${EASYMESH_NETWORK_SECRET}" +EOF + chmod 0600 "$destination" +} - if [[ "$installed_core" == "$selected_number" && "$installed_cli" == "$selected_number" ]]; then - colorize green "Selected EasyMesh Core is already installed ($CORE_VERSION)." bold - return 0 - fi +write_mesh_service() { + local destination="$1" - colorize yellow "Installed Core: ${installed_core:-unknown}" bold - colorize yellow "Selected Core: $CORE_VERSION" bold - colorize yellow "Detected architecture: $system_arch" bold - read -r -p "Upgrade the installed core to $CORE_VERSION? [y/N] " confirm - if [[ ! "$confirm" =~ ^[Yy]$ ]]; then - colorize yellow "Core upgrade cancelled. Existing installation was not changed." bold - return 0 - fi - - validate_local_core_candidate "$candidate_dir" "$system_arch" - case $? in - 0) ;; - 1) - colorize red "Local core package is incomplete: $candidate_dir" bold - return 1 - ;; - *) return 1 ;; - esac + cat > "$destination" <<'EOF' +# Managed by Behify EasyMesh +[Unit] +Description=EasyMesh Network Service +After=network.target - if [[ ! -f "$dest_dir/easytier-core" || ! -f "$dest_dir/easytier-cli" ]]; then - colorize red "Safe upgrade requires both installed binaries so they can be backed up." bold - rm -rf "$VALIDATED_TEMP_DIR" - return 1 - fi +[Service] +EnvironmentFile=/etc/behify-easymesh/mesh.env +ExecStart=/root/easytier/easytier-core --config-file /etc/behify-easymesh/easytier.toml --console-log-level warn -i ${EASYMESH_IP} $EASYMESH_PEERS --hostname ${EASYMESH_HOSTNAME} --default-protocol ${EASYMESH_DEFAULT_PROTOCOL} $EASYMESH_LISTENERS $EASYMESH_MULTI_THREAD $EASYMESH_ENCRYPTION $EASYMESH_IPV6 +Restart=on-failure - backup_dir="/root/easymesh-core-backup-$(date +%Y%m%d-%H%M%S)" - if [[ -e "$backup_dir" ]] || ! mkdir "$backup_dir" || \ - ! cp "$dest_dir/easytier-core" "$backup_dir/easytier-core" || \ - ! cp "$dest_dir/easytier-cli" "$backup_dir/easytier-cli"; then - colorize red "Could not create a complete core backup. Upgrade aborted." bold - rm -rf "$VALIDATED_TEMP_DIR" - return 1 - fi +[Install] +WantedBy=multi-user.target +EOF + chmod 0644 "$destination" +} - if [[ -f "$service_file" ]] && systemctl is-active --quiet easymesh.service; then - service_was_active=1 - fi +validate_staged_mesh_service() { + local staged_service="$1" staged_config="${2:-}" - if [[ "$service_was_active" == "1" ]] && ! systemctl stop easymesh.service; then - colorize red "Could not stop easymesh.service. Installed core was not changed." bold - rm -rf "$VALIDATED_TEMP_DIR" - return 1 + if command -v systemd-analyze >/dev/null 2>&1; then + systemd-analyze verify "$staged_service" >/dev/null 2>&1 || return 1 + else + grep -Fqx 'EnvironmentFile=/etc/behify-easymesh/mesh.env' "$staged_service" && + grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' "$staged_service" && + grep -Fq -- '--console-log-level warn' "$staged_service" && + grep -Fqx 'Restart=on-failure' "$staged_service" || return 1 fi + [[ -z "$staged_config" ]] || + EASYMESH_NETWORK_SECRET="$NETWORK_SECRET" "$EASY_CORE" \ + --config-file "$staged_config" --console-log-level warn --check-config >/dev/null 2>&1 +} - if ! install_validated_core_files "$dest_dir" || \ - [[ "$(get_binary_version_number "$dest_dir/easytier-core" || true)" != "$selected_number" ]] || \ - [[ "$(get_binary_version_number "$dest_dir/easytier-cli" || true)" != "$selected_number" ]]; then - colorize red "Core installation verification failed. Restoring the previous binaries." bold - rm -rf "$VALIDATED_TEMP_DIR" - restore_core_backup "$backup_dir" "$service_file" "$service_was_active" - return 1 - fi - rm -rf "$VALIDATED_TEMP_DIR" +mesh_configuration_paths_are_safe() { + [[ ! -L "$MESH_CONFIG_DIR" && ! -L "$MESH_CONFIG_DIR/backups" && + ! -L "$MESH_ENV_FILE" && ! -L "$MESH_CORE_CONFIG" && ! -L "$SERVICE_FILE" ]] || return 1 + [[ ! -e "$MESH_CONFIG_DIR" || -d "$MESH_CONFIG_DIR" ]] || return 1 + [[ ! -e "$MESH_CONFIG_DIR/backups" || -d "$MESH_CONFIG_DIR/backups" ]] || return 1 + [[ ! -e "$MESH_ENV_FILE" || -f "$MESH_ENV_FILE" ]] || return 1 + [[ ! -e "$MESH_CORE_CONFIG" || -f "$MESH_CORE_CONFIG" ]] || return 1 + [[ ! -e "$SERVICE_FILE" || -f "$SERVICE_FILE" ]] || return 1 + [[ ! -f "$SERVICE_FILE" ]] || is_behify_mesh_service_file +} - if [[ -f "$service_file" ]]; then - if ! systemctl start easymesh.service || ! systemctl is-active --quiet easymesh.service; then - colorize red "The existing service failed to start with the selected core. Rolling back." bold - restore_core_backup "$backup_dir" "$service_file" "$service_was_active" - return 1 - fi +restore_mesh_configuration() { + local backup_dir="$1" service_existed="$2" env_existed="$3" config_existed="$4" + local was_active="$5" was_enabled="$6" - pid=$(systemctl show -p MainPID --value easymesh.service 2>/dev/null) - if [[ ! "$pid" =~ ^[1-9][0-9]*$ ]]; then - colorize red "The service has no valid running PID after upgrade. Rolling back." bold - restore_core_backup "$backup_dir" "$service_file" "$service_was_active" - return 1 - fi - running_version=$(get_binary_version_number "/proc/$pid/exe" || true) - if [[ "$running_version" != "$selected_number" ]]; then - colorize red "Running core version verification failed. Rolling back." bold - restore_core_backup "$backup_dir" "$service_file" "$service_was_active" - return 1 - fi + systemctl stop easymesh.service >/dev/null 2>&1 || true + if [[ "$service_existed" == "1" ]]; then + install -m 0644 "$backup_dir/easymesh.service" "$SERVICE_FILE" || return 1 + else + rm -f "$SERVICE_FILE" fi - - colorize green "EasyMesh Core upgraded safely to $CORE_VERSION." bold - colorize green "Backup: $backup_dir" bold - if [[ -f "$service_file" ]]; then - colorize green "Service Status: $(systemctl is-active easymesh.service 2>/dev/null || true)" bold + if [[ "$env_existed" == "1" ]]; then + install -m 0600 "$backup_dir/mesh.env" "$MESH_ENV_FILE" || return 1 else - colorize green "Service Status: missing" bold + rm -f "$MESH_ENV_FILE" + fi + if [[ "$config_existed" == "1" ]]; then + install -m 0600 "$backup_dir/easytier.toml" "$MESH_CORE_CONFIG" || return 1 + else + rm -f "$MESH_CORE_CONFIG" + fi + systemctl daemon-reload >/dev/null 2>&1 || return 1 + if [[ "$was_enabled" == "1" ]]; then + systemctl enable easymesh.service >/dev/null 2>&1 || return 1 + else + systemctl disable easymesh.service >/dev/null 2>&1 || true + fi + if [[ "$was_active" == "1" ]]; then + systemctl start easymesh.service >/dev/null 2>&1 || return 1 fi - return 0 } -install_easytier() { - local DEST_DIR="/root/easytier" - local FILE1="easytier-core" - local FILE2="easytier-cli" - local SCRIPT_DIR LOCAL_CORE_BASE ARCH LOCAL_ARCH_DIR LOCAL_CORE_DIR LOCAL_CORE LOCAL_CLI URL - local validation_status - - SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd)" - LOCAL_CORE_BASE="$SCRIPT_DIR/core/$CORE_VERSION" - #URL_X86="https://github.com/EasyTier/EasyTier/releases/download/v1.1.0/easytier-x86_64-unknown-linux-musl-v1.1.0.zip" - #URL_ARM_SOFT="https://github.com/EasyTier/EasyTier/releases/download/v1.1.0/easytier-armv7-unknown-linux-musleabi-v1.1.0.zip" - #URL_ARM_HARD="https://github.com/EasyTier/EasyTier/releases/download/v1.1.0/easytier-armv7-unknown-linux-musleabihf-v1.1.0.zip" - - #New Version - URL_X86="https://github.com/Musixal/Easy-Mesh/raw/main/core/v2.0.3/easytier-linux-x86_64/" - URL_ARM_SOFT="https://github.com/Musixal/Easy-Mesh/raw/main/core/v2.0.3/easytier-linux-armv7/" - URL_ARM_HARD="https://github.com/Musixal/Easy-Mesh/raw/main/core/v2.0.3/easytier-linux-armv7hf/" - - ARCH=$(uname -m) - if ! LOCAL_ARCH_DIR=$(resolve_core_arch_directory "$ARCH"); then - colorize red "Unsupported architecture: $ARCH" bold - colorize red "No core package was selected and the installed core was not changed." bold - exit 1 - fi - LOCAL_CORE_DIR="$LOCAL_CORE_BASE/$LOCAL_ARCH_DIR" - LOCAL_CORE="$LOCAL_CORE_DIR/$FILE1" - LOCAL_CLI="$LOCAL_CORE_DIR/$FILE2" +activate_mesh_configuration() { + local staged_env="$1" staged_config="$2" staged_service="$3" + local backup_dir service_existed=0 env_existed=0 config_existed=0 was_active=0 was_enabled=0 + local new_env="$MESH_CONFIG_DIR/.mesh.env.new.$$" + local new_config="$MESH_CONFIG_DIR/.easytier.toml.new.$$" + local new_service="${SERVICE_FILE}.new.$$" - if [[ "$CORE_VERSION_EXPLICIT" == "1" ]] && \ - { [[ -e "$DEST_DIR/$FILE1" ]] || [[ -e "$DEST_DIR/$FILE2" ]]; }; then - upgrade_installed_core "$LOCAL_CORE_DIR" "$ARCH" || exit 1 - return 0 + mesh_configuration_paths_are_safe || { + colorize red "Refusing to replace a symlinked path or an unrecognized easymesh.service." bold + return 1 + } + backup_dir=$(mktemp -d "$MESH_CONFIG_DIR/backups/mesh-$(date +%Y%m%d-%H%M%S).XXXXXX") || return 1 + chmod 0700 "$backup_dir" + if [[ -f "$SERVICE_FILE" ]]; then + service_existed=1 + cp -p "$SERVICE_FILE" "$backup_dir/easymesh.service" || return 1 fi - - if [ -x "$DEST_DIR/$FILE1" ] && [ -x "$DEST_DIR/$FILE2" ]; then - colorize green "Behify EasyMesh Core Installed" bold - return 0 + if [[ -f "$MESH_ENV_FILE" ]]; then + env_existed=1 + cp -p "$MESH_ENV_FILE" "$backup_dir/mesh.env" || return 1 + fi + if [[ -f "$MESH_CORE_CONFIG" ]]; then + config_existed=1 + cp -p "$MESH_CORE_CONFIG" "$backup_dir/easytier.toml" || return 1 fi + systemctl is-active --quiet easymesh.service && was_active=1 + systemctl is-enabled --quiet easymesh.service && was_enabled=1 - if [[ "$LOCAL_ARCH_DIR" == "easytier-linux-x86_64" ]]; then - URL=$URL_X86 - elif [[ "$ARCH" == "armv7l" ]]; then - if [[ "$LOCAL_ARCH_DIR" == "easytier-linux-armv7hf" ]]; then - URL=$URL_ARM_HARD - else - URL=$URL_ARM_SOFT - fi + if [[ "$was_active" == "1" ]] && ! systemctl stop easymesh.service >/dev/null 2>&1; then + colorize red "Could not stop the existing mesh service. Configuration was not changed." bold + return 1 fi - if [ -f "$LOCAL_CORE" ] && [ -f "$LOCAL_CLI" ]; then - validate_local_core_candidate "$LOCAL_CORE_DIR" "$ARCH" - validation_status=$? - if [[ "$validation_status" == "0" ]] && install_validated_core_files "$DEST_DIR" && \ - "$DEST_DIR/$FILE1" --version &> /dev/null && "$DEST_DIR/$FILE2" --version &> /dev/null; then - rm -rf "$VALIDATED_TEMP_DIR" - colorize green "EasyMesh Core installed from local offline package" bold - return 0 - fi - [[ -n "$VALIDATED_TEMP_DIR" ]] && rm -rf "$VALIDATED_TEMP_DIR" - colorize red "Local EasyMesh Core package is invalid or failed version validation.\n" bold - colorize red "Detected architecture: $ARCH" bold - colorize red "Selected package: $LOCAL_CORE_DIR" bold - if [[ "$validation_status" == "2" ]]; then - exit 1 - fi - if [ "$EASYMESH_OFFLINE" = "1" ]; then - colorize red "EASYMESH_OFFLINE=1 is set. Download fallback is disabled.\n" bold - exit 1 - fi - colorize yellow "Falling back to GitHub download...\n" bold - elif [[ "$EASYMESH_OFFLINE" == "1" || "$LOCAL_ARCH_DIR" == "easytier-linux-aarch64" ]]; then - colorize red "Local EasyMesh Core package not found for selected core version: $CORE_VERSION\n" bold - colorize red "Architecture: $ARCH\n" bold - colorize red "Expected path: $LOCAL_CORE_DIR\n" bold - if [[ "$EASYMESH_OFFLINE" == "1" ]]; then - colorize red "EASYMESH_OFFLINE=1 is set. Download fallback is disabled.\n" bold + if ! install -m 0600 "$staged_env" "$new_env" || + ! install -m 0600 "$staged_config" "$new_config" || + ! install -m 0644 "$staged_service" "$new_service" || + ! mv -f "$new_env" "$MESH_ENV_FILE" || + ! mv -f "$new_config" "$MESH_CORE_CONFIG" || + ! chmod 0600 "$MESH_ENV_FILE" "$MESH_CORE_CONFIG" || + ! mv -f "$new_service" "$SERVICE_FILE" || + ! systemctl daemon-reload >/dev/null 2>&1 || + ! systemctl enable easymesh.service >/dev/null 2>&1 || + ! systemctl start easymesh.service >/dev/null 2>&1 || + ! systemctl is-active --quiet easymesh.service; then + rm -f "$new_env" "$new_config" "$new_service" + colorize red "Mesh configuration failed. Restoring the previous configuration and service state." bold + if restore_mesh_configuration "$backup_dir" "$service_existed" "$env_existed" "$config_existed" "$was_active" "$was_enabled"; then + colorize yellow "Previous mesh configuration restored." bold else - colorize red "No compatible aarch64 download fallback is configured.\n" bold + colorize red "Rollback needs manual attention. Backup: $backup_dir" bold fi - exit 1 + return 1 fi - mkdir -p $DEST_DIR &> /dev/null - colorize yellow "Downloading Behify EasyMesh Core...\n" - curl -Ls "$URL/easytier-cli" -o "$DEST_DIR/easytier-cli" - curl -Ls "$URL/easytier-core" -o "$DEST_DIR/easytier-core" + colorize green "Behify EasyMesh Network Service Started." bold + colorize green "Configuration backup: $backup_dir" bold +} +prompt_network_secret() { + local generated_secret entered_secret - if [ -f "$DEST_DIR/$FILE1" ] && [ -f "$DEST_DIR/$FILE2" ]; then - chmod +x "$DEST_DIR/easytier-cli" - chmod +x "$DEST_DIR/easytier-core" - colorize green "Behify EasyMesh Core Installed Successfully...\n" bold - sleep 1 - return 0 - else - colorize red "Failed to install Behify EasyMesh Core...\n" bold - exit 1 + if [[ "${EASYMESH_SOURCE_ONLY:-0}" != "1" ]] && { [[ ! -t 0 ]] || [[ ! -t 1 ]]; }; then + colorize red "Network secret setup requires an interactive terminal." bold + return 1 fi + generated_secret=$(generate_random_secret) || return 1 + printf 'Generated Network Secret:\n' + colorize cyan "$generated_secret" bold + read -r -p "Press Enter to use it, or enter a custom secret: " entered_secret || return 1 + NETWORK_SECRET=${entered_secret:-$generated_secret} } +prompt_encryption_option() { + local choice confirm + colorize cyan "Encryption is enabled by default and recommended." + read -r -p "[-] Enable EasyTier tunnel encryption? [Y/n]: " choice || return 1 + case "$choice" in + ""|[Yy]*) ENCRYPTION_OPTION="" ;; + [Nn]*) + colorize yellow "Without EasyTier encryption, tunnel traffic is unencrypted. Use this only on trusted paths or for payloads already protected end to end." bold + read -r -p "Disable EasyTier tunnel encryption? [y/N]: " confirm || return 1 + if [[ "$confirm" =~ ^[Yy] ]]; then + ENCRYPTION_OPTION="--disable-encryption" + else + ENCRYPTION_OPTION="" + fi + ;; + *) colorize red "Enter yes or no." bold; return 1 ;; + esac +} -# Call the function -install_easytier +prompt_multi_thread_option() { + local choice -generate_random_secret() { - openssl rand -hex 6 + colorize cyan "Multi-thread can improve performance on some systems, but may increase instability or packet reordering on some paths." + read -r -p "[-] Enable multi-thread mode? [y/N]: " choice || return 1 + case "$choice" in + [Yy]*) MULTI_THREAD_OPTION="--multi-thread" ;; + ""|[Nn]*) MULTI_THREAD_OPTION="" ;; + *) colorize red "Enter yes or no." bold; return 1 ;; + esac } -#Var -EASY_CLIENT='/root/easytier/easytier-cli' -SERVICE_FILE="/etc/systemd/system/easymesh.service" -MAIN_CORE_VERSION="" - -connect_network_pool(){ - clear - colorize cyan "Connect to the Mesh Network" bold - echo - colorize yellow "Leave the peer addresses blank to enable reverse mode. -Ws and wss modes are not recommended for iran's network environment. -Try disable multi-thread mode if your mesh network is unstable. -UDP mode is more stable rather than tcp mode. - " - echo - read -p "[-] Enter Peer IPv4/IPv6 Addresses (separate multiple addresses by ','): " PEER_ADDRESSES - - read -p "[*] Enter Local IPv4 Address (e.g., 10.144.144.1): " IP_ADDRESS - if [ -z $IP_ADDRESS ]; then - colorize red "Null value. aborting..." - sleep 2 - return 1 - fi - - read -r -p "[*] Enter Hostname (e.g., Hetnzer): " HOSTNAME - if [ -z $HOSTNAME ]; then - colorize red "Null value. aborting..." - sleep 2 - return 1 - fi - - read -p "[-] Enter Tunnel Port (Default 2090): " PORT - if [ -z $PORT ]; then - colorize red "Default port is 2090..." - PORT='2090' - fi - - echo '' - NETWORK_SECRET=$(generate_random_secret) - colorize cyan "[✓] Generated Network Secret: $NETWORK_SECRET" bold - while true; do - read -p "[*] Enter Network Secret (recommend using a strong password): " NETWORK_SECRET - if [[ -n $NETWORK_SECRET ]]; then - break - else - colorize red "Network secret cannot be empty. Please enter a valid secret.\n" - fi - done - - +connect_network_pool() { + local PEER_ADDRESSES IP_ADDRESS HOSTNAME PORT NETWORK_SECRET + local PROTOCOL_CHOICE DEFAULT_PROTOCOL ENCRYPTION_OPTION + local MULTI_THREAD_OPTION IPV6_CHOICE IPV6_OPTION + local PEER_ARGUMENTS LISTENERS staging_dir + + clear + colorize cyan "Connect to the Mesh Network" bold + echo + colorize yellow "Tip: leave Peer blank for reverse mode; UDP is recommended." + echo + + for required_command in python3 openssl; do + command -v "$required_command" >/dev/null 2>&1 || { + colorize red "$required_command is required for safe mesh configuration." bold + press_key + return 1 + } + done + read -r -p "[-] Enter Peer IPv4/IPv6 Addresses (separate multiple addresses by ','): " PEER_ADDRESSES || return 1 + read -r -p "[*] Enter Local IPv4/IPv6 Address (e.g., 10.144.144.1): " IP_ADDRESS || return 1 + is_valid_ip_address "$IP_ADDRESS" || { + colorize red "Local address must be a valid IPv4 or IPv6 address." bold + press_key + return 1 + } + read -r -p "[*] Enter Hostname (e.g., server-1): " HOSTNAME || return 1 + is_valid_hostname "$HOSTNAME" || { + colorize red "Hostname must contain only letters, digits, dots, underscores, or hyphens." bold + press_key + return 1 + } + read -r -p "[-] Enter Tunnel Port (Default 2090): " PORT || return 1 + PORT=${PORT:-2090} + is_integer_in_range "$PORT" 1 65535 || { + colorize red "Tunnel port must be an integer from 1 to 65535." bold + press_key + return 1 + } + + prompt_network_secret || return 1 + is_valid_network_secret "$NETWORK_SECRET" || { + colorize red "Secret must be 12-128 safe characters and start with a letter or digit." bold + press_key + return 1 + } - echo '' colorize green "[-] Select Default Protocol:" bold - echo "1) tcp" - echo "2) udp" - echo "3) ws" - echo "4) wss" - read -p "[*] Select your desired protocol (e.g., 1 for tcp): " PROTOCOL_CHOICE - - case $PROTOCOL_CHOICE in + printf '%s\n' '1) tcp' '2) udp' '3) ws' '4) wss' + read -r -p "[*] Select your desired protocol (1-4): " PROTOCOL_CHOICE || return 1 + case "$PROTOCOL_CHOICE" in 1) DEFAULT_PROTOCOL="tcp" ;; 2) DEFAULT_PROTOCOL="udp" ;; 3) DEFAULT_PROTOCOL="ws" ;; 4) DEFAULT_PROTOCOL="wss" ;; - *) colorize red "Invalid choice. Defaulting to tcp." ; DEFAULT_PROTOCOL="tcp" ;; + *) colorize red "Invalid protocol choice." bold; press_key; return 1 ;; esac - - echo - read -p "[-] Enable encryption? (yes/no): " ENCRYPTION_CHOICE - case $ENCRYPTION_CHOICE in - [Nn]*) - ENCRYPTION_OPTION="--disable-encryption" - colorize yellow "Encryption is disabled" - ;; - *) - ENCRYPTION_OPTION="" - colorize yellow "Encryption is enabled" - ;; - esac - - echo - - read -p "[-] Enable multi-thread? (yes/no): " MULTI_THREAD - case $MULTI_THREAD in - [Nn]*) - MULTI_THREAD="" - colorize yellow "Multi-thread is disabled" - ;; - *) - MULTI_THREAD="--multi-thread" - colorize yellow "Multi-thread is enabled" - ;; - esac - - echo - - read -p "[-] Enable IPv6? (yes/no): " IPV6_MODE - case $IPV6_MODE in - [Nn]*) - IPV6_MODE="--disable-ipv6" - colorize yellow "IPv6 is disabled" - ;; - *) - IPV6_MODE="" - colorize yellow "IPv6 is enabled" - ;; - esac - - echo - - IFS=',' read -ra ADDR_ARRAY <<< "$PEER_ADDRESSES" - PROCESSED_ADDRESSES=() - for ADDRESS in "${ADDR_ARRAY[@]}"; do - ADDRESS=$(echo $ADDRESS | xargs) - - if [[ "$ADDRESS" == *:* ]]; then - if [[ "$ADDRESS" != \[*\] ]]; then - ADDRESS="[$ADDRESS]" - fi - fi - - if [ ! -z "$ADDRESS" ]; then - PROCESSED_ADDRESSES+=("${DEFAULT_PROTOCOL}://${ADDRESS}:${PORT}") - fi - done - - JOINED_ADDRESSES=$(IFS=' '; echo "${PROCESSED_ADDRESSES[*]}") - - if [ ! -z "$JOINED_ADDRESSES" ]; then - PEER_ADDRESS="--peers ${JOINED_ADDRESSES}" - fi - - LISTENERS="--listeners ${DEFAULT_PROTOCOL}://[::]:${PORT} ${DEFAULT_PROTOCOL}://0.0.0.0:${PORT}" - - SERVICE_FILE="/etc/systemd/system/easymesh.service" - -cat > $SERVICE_FILE < /dev/null - sudo systemctl enable easymesh.service &> /dev/null - sudo systemctl start easymesh.service &> /dev/null + mesh_configuration_paths_are_safe || { + colorize red "Refusing to use a symlinked configuration path or an unrecognized easymesh.service." bold + press_key + return 1 + } + mkdir -p "$MESH_CONFIG_DIR" "$MESH_CONFIG_DIR/backups" || return 1 + chmod 0700 "$MESH_CONFIG_DIR" "$MESH_CONFIG_DIR/backups" + staging_dir=$(mktemp -d "$MESH_CONFIG_DIR/.mesh-config.XXXXXX") || return 1 + if ! write_mesh_environment "$staging_dir/mesh.env" || + ! write_mesh_core_config "$staging_dir/easytier.toml" || + ! write_mesh_service "$staging_dir/easymesh.service" || + ! validate_staged_mesh_service "$staging_dir/easymesh.service" "$staging_dir/easytier.toml"; then + rm -rf "$staging_dir" + colorize red "Generated mesh configuration did not pass validation. Nothing was activated." bold + press_key + return 1 + fi - colorize green "Behify EasyMesh Network Service Started.\n" bold - press_key + activate_mesh_configuration "$staging_dir/mesh.env" "$staging_dir/easytier.toml" "$staging_dir/easymesh.service" + local result=$? + rm -rf "$staging_dir" + press_key + return "$result" } @@ -688,7 +639,7 @@ display_peers() display_routes(){ is_mesh_cli_ready || return 1 - colorize yellow "Route output is wide. Use a wide terminal or less -S for best view." bold + colorize yellow "Route output is wide; watch no-wrap mode is used when supported." bold watch_mesh_view route } @@ -706,7 +657,7 @@ restart_easymesh_service() { return 1 fi colorize yellow " Restarting Behify EasyMesh service...\n" bold - sudo systemctl restart easymesh.service &> /dev/null + systemctl restart easymesh.service &> /dev/null if [[ $? -eq 0 ]]; then colorize green " Behify EasyMesh service restarted successfully." bold else @@ -716,6 +667,12 @@ restart_easymesh_service() { read -p " Press Enter to continue..." } +is_behify_mesh_service_file() { + [[ -f "$SERVICE_FILE" ]] && + { grep -Fqx '# Managed by Behify EasyMesh' "$SERVICE_FILE" || + grep -Fq 'ExecStart=/root/easytier/easytier-core ' "$SERVICE_FILE"; } +} + remove_easymesh_service() { local confirm echo @@ -724,6 +681,11 @@ remove_easymesh_service() { sleep 1 return 1 fi + if ! is_behify_mesh_service_file; then + colorize red "Refusing to remove an unrecognized easymesh.service file." bold + press_key + return 1 + fi colorize yellow "This will stop, disable, and remove $SERVICE_FILE." bold read -r -p "Remove the EasyMesh service? [y/N] " confirm || confirm="" [[ "$confirm" =~ ^[Yy]$ ]] || { @@ -731,7 +693,7 @@ remove_easymesh_service() { return 0 } colorize yellow " Stopping Behify EasyMesh service..." bold - sudo systemctl stop easymesh.service &> /dev/null + systemctl stop easymesh.service &> /dev/null if [[ $? -eq 0 ]]; then colorize green " Behify EasyMesh service stopped successfully.\n" else @@ -741,7 +703,7 @@ remove_easymesh_service() { fi colorize yellow " Disabling Behify EasyMesh service..." bold - sudo systemctl disable easymesh.service &> /dev/null + systemctl disable easymesh.service &> /dev/null if [[ $? -eq 0 ]]; then colorize green " Behify EasyMesh service disabled successfully.\n" else @@ -751,7 +713,7 @@ remove_easymesh_service() { fi colorize yellow " Removing Behify EasyMesh service..." bold - sudo rm /etc/systemd/system/easymesh.service &> /dev/null + rm -f "$SERVICE_FILE" &> /dev/null if [[ $? -eq 0 ]]; then colorize green " Behify EasyMesh service removed successfully.\n" else @@ -761,7 +723,7 @@ remove_easymesh_service() { fi colorize yellow " Reloading systemd daemon..." bold - sudo systemctl daemon-reload + systemctl daemon-reload if [[ $? -eq 0 ]]; then colorize green " Systemd daemon reloaded successfully.\n" else @@ -774,17 +736,24 @@ remove_easymesh_service() { } show_network_secret() { + local network_secret + + if [[ "${EASYMESH_SOURCE_ONLY:-0}" != "1" ]] && { [[ ! -t 0 ]] || [[ ! -t 1 ]]; }; then + colorize red "Network secret display requires an interactive terminal." bold + return 1 + fi echo '' - if [[ -f $SERVICE_FILE ]]; then - NETWORK_SECRET=$(grep -oP '(?<=--network-secret )[^ ]+' $SERVICE_FILE) - - if [[ -n $NETWORK_SECRET ]]; then - colorize cyan " Network Secret Key: $NETWORK_SECRET" bold + if [[ -r "$MESH_ENV_FILE" ]]; then + network_secret=$(awk -F= '$1 == "EASYMESH_NETWORK_SECRET" { value=substr($0, index($0, "=") + 1); gsub(/^"|"$/, "", value); print value }' "$MESH_ENV_FILE") + if [[ -n "$network_secret" ]]; then + colorize cyan " Network Secret Key: $network_secret" bold else colorize red " Network Secret key not found" bold fi + elif [[ -f "$SERVICE_FILE" ]]; then + colorize yellow " This service uses a legacy unit-embedded secret. Reconnect to migrate it to root-only storage." bold else - colorize red " Behify EasyMesh service does not exists." bold + colorize red " Behify EasyMesh service does not exists." bold fi echo '' read -p " Press Enter to continue..." @@ -799,7 +768,7 @@ view_service_status() { return 1 fi clear - sudo systemctl status easymesh.service + systemctl status easymesh.service } set_watchdog(){ @@ -866,7 +835,7 @@ start_watchdog(){ fi touch /etc/monitor.sh /etc/monitor.log &> /dev/null -cat << EOF | sudo tee /etc/monitor.sh > /dev/null +cat > /etc/monitor.sh << EOF #!/bin/bash # Configuration @@ -879,7 +848,7 @@ LOG_FILE="/etc/monitor.log" # Function to restart the service restart_service() { local restart_time=\$(date +"%Y-%m-%d %H:%M:%S") - sudo systemctl restart "\$SERVICE_NAME" + systemctl restart "\$SERVICE_NAME" if [ \$? -eq 0 ]; then echo "\$restart_time: Service \$SERVICE_NAME restarted successfully." >> "\$LOG_FILE" else @@ -998,9 +967,9 @@ view_logs() { # Function to add cron-tab job add_cron_job() { - echo - - local service_name="easymesh.service" + local service_name="easymesh.service" temp_crontab restart_time time_choice + local marker="# behify-easymesh-service-restart" + echo # Prompt user to choose a restart time interval colorize cyan "Select the restart time interval:" bold @@ -1045,35 +1014,23 @@ add_cron_job() { esac - # remove cronjob created by this script - delete_cron_job > /dev/null 2>&1 - - # Path to reset file - local reset_path="/root/easytier/reset.sh" - - #add cron job to kill the running easymesh processes - cat << EOF > "$reset_path" -#! /bin/bash -pids=\$(pgrep easytier) -sudo kill -9 \$pids -sudo systemctl daemon-reload -sudo systemctl restart $service_name -EOF - - # make it +x - chmod +x "$reset_path" - - # Save existing crontab to a temporary file - crontab -l > /tmp/crontab.tmp - - # Append the new cron job to the temporary file - echo "$restart_time $reset_path #$service_name" >> /tmp/crontab.tmp - - # Install the modified crontab from the temporary file - crontab /tmp/crontab.tmp - - # Remove the temporary file - rm /tmp/crontab.tmp + command -v crontab >/dev/null 2>&1 || { + colorize red "crontab is not installed; no maintenance job was changed." bold + return 1 + } + temp_crontab=$(mktemp) || return 1 + if crontab -l > "$temp_crontab" 2>/dev/null; then + sed -i "/#easymesh\.service$/d; /# behify-easymesh-service-restart$/d" "$temp_crontab" + else + : > "$temp_crontab" + fi + printf '%s /bin/systemctl restart %s %s\n' "$restart_time" "$service_name" "$marker" >> "$temp_crontab" + if ! crontab "$temp_crontab"; then + rm -f "$temp_crontab" + colorize red "Could not install the service restart schedule." bold + return 1 + fi + rm -f "$temp_crontab" echo colorize green "Cron-job added successfully to restart the service '$service_name'." bold @@ -1081,13 +1038,26 @@ EOF } delete_cron_job() { + local service_name="easymesh.service" temp_crontab + echo - local service_name="easymesh.service" - local reset_path="/root/easytier/reset.sh" - - crontab -l | grep -v "#$service_name" | crontab - - rm -f "$reset_path" >/dev/null 2>&1 - + command -v crontab >/dev/null 2>&1 || { + colorize yellow "crontab is not installed." bold + return 0 + } + temp_crontab=$(mktemp) || return 1 + if ! crontab -l > "$temp_crontab" 2>/dev/null; then + rm -f "$temp_crontab" + colorize yellow "No crontab is installed for this user." bold + return 0 + fi + sed -i "/#easymesh\.service$/d; /# behify-easymesh-service-restart$/d" "$temp_crontab" + if ! crontab "$temp_crontab"; then + rm -f "$temp_crontab" + colorize red "Could not update crontab." bold + return 1 + fi + rm -f "$temp_crontab" colorize green "Cron job for $service_name deleted successfully." bold sleep 2 @@ -1115,20 +1085,6 @@ set_cronjob(){ } -check_core_status(){ - DEST_DIR="/root/easytier" - FILE1="easytier-core" - FILE2="easytier-cli" - - if [ -f "$DEST_DIR/$FILE1" ] && [ -f "$DEST_DIR/$FILE2" ]; then - colorize green "Behify EasyMesh Core Installed" bold - return 0 - else - colorize red "Behify EasyMesh Core not found" bold - return 1 - fi -} - get_installed_core_version() { local core_path="/root/easytier/easytier-core" local version @@ -1199,8 +1155,7 @@ show_diagnostics() { colorize cyan "EasyMesh Diagnostics" bold printf 'System architecture: %s\n' "$system_arch" printf 'Architecture package: %s\n' "$core_package" - printf 'Default core version: %s\n' "$DEFAULT_CORE_VERSION" - printf 'Requested core version: %s\n' "$CORE_VERSION" + printf 'Supported core version: %s\n' "$EASYTIER_VERSION" printf 'Installed core version: %s\n' "$(get_installed_core_version)" printf 'Running core version: %s\n' "$(get_running_core_version)" printf 'Service status: %s\n' "$(get_service_status)" @@ -1212,29 +1167,17 @@ show_diagnostics() { press_key } -# New function to remove core -remove_easymesh_core(){ - local confirm - echo - - if [[ ! -d '/root/easytier' ]]; then - colorize red " Behify EasyMesh directory not found." bold - sleep 2 - return 1 - fi - colorize yellow "This will permanently delete /root/easytier and both EasyTier binaries." bold - read -r -p "Remove the EasyMesh core? [y/N] " confirm || confirm="" - [[ "$confirm" =~ ^[Yy]$ ]] || { - colorize yellow "Core removal cancelled." bold - return 0 - } - - rm -rf /root/easytier &> /dev/null - MAIN_CORE_VERSION="not-installed" - - colorize green " Behify EasyMesh core deleted successfully." bold - sleep 2 +run_uninstaller() { + local uninstaller="$SCRIPT_DIR/uninstall.sh" + if [[ ! -x "$uninstaller" ]]; then + colorize red "The managed uninstaller is missing. Reinstall the release package first." bold + press_key + return 1 + fi + if "$uninstaller"; then + exit 0 + fi } relay_port_routing() { @@ -1267,11 +1210,11 @@ display_menu() { print_box_line "Behify EasyMesh" print_box_line "VPN Network Solution" printf " +--------------------------------------+\n" - print_box_line "Core: $(get_main_core_version)" + print_box_line "EasyTier: $(get_main_core_version)" print_box_line "Service: $(get_service_status)" print_box_line "Telegram: @behify1" print_box_line "Channel: BehifyVPN" - print_box_line "Build: Behify private" + print_box_line "Version: v$BEHIFY_EASYMESH_VERSION" printf " +--------------------------------------+\n" echo '' colorize cyan "Menu options:" bold @@ -1285,7 +1228,7 @@ display_menu() { colorize reset " [8] Cron-job setting" colorize yellow " [9] Restart Service" colorize red " [10] Remove Service" - colorize magenta " [11] Remove Core" + colorize magenta " [11] Uninstall Behify EasyMesh" colorize cyan " [12] Relay / Port Routing" colorize cyan " [13] Diagnostics" @@ -1310,7 +1253,7 @@ read_option() { 8) set_cronjob ;; 9) restart_easymesh_service ;; 10) remove_easymesh_service ;; - 11) remove_easymesh_core ;; + 11) run_uninstaller ;; 12) relay_port_routing ;; 13) show_diagnostics ;; 0) exit 0 ;; @@ -1319,6 +1262,10 @@ read_option() { } # Main script +if [[ "${EASYMESH_SOURCE_ONLY:-0}" == "1" ]]; then + return 0 2>/dev/null || exit 0 +fi +verify_installed_runtime || exit 1 initialize_main_core_version while true do diff --git a/install.sh b/install.sh index e958612..d14a188 100644 --- a/install.sh +++ b/install.sh @@ -1,55 +1,530 @@ #!/usr/bin/env bash -set -euo pipefail +set -Eeuo pipefail -APP_NAME="easymesh" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -SOURCE_FILE="$SCRIPT_DIR/easymesh" -RELAY_MANAGER_FILE="$SCRIPT_DIR/relay-manager" -INSTALL_DIR="/opt/behify-easymesh" -COMMAND_PATH="/usr/local/bin/easymesh" - -if [[ $EUID -ne 0 ]]; then - echo "Please run as root:" - echo "sudo bash install.sh" - exit 1 -fi +TEST_ROOT="${BEHIFY_TEST_ROOT:-}" +TEST_MODE="${BEHIFY_TEST_MODE:-0}" -if [[ ! -f "$SOURCE_FILE" || ! -f "$RELAY_MANAGER_FILE" ]]; then - echo "Error: $SOURCE_FILE or $RELAY_MANAGER_FILE not found." - exit 1 -fi +root_path() { + printf '%s%s\n' "$TEST_ROOT" "$1" +} + +INSTALL_DIR="$(root_path /opt/behify-easymesh)" +BACKUP_PARENT="$(root_path /opt/behify-easymesh-backups)" +COMMAND_PATH="$(root_path /usr/local/bin/easymesh)" +CORE_DIR="$(root_path /root/easytier)" +SERVICE_FILE="$(root_path /etc/systemd/system/easymesh.service)" +MESH_CONFIG_DIR="$(root_path /etc/behify-easymesh)" +MESH_ENV_FILE="$MESH_CONFIG_DIR/mesh.env" +MESH_CORE_CONFIG="$MESH_CONFIG_DIR/easytier.toml" +MANIFEST_FILE="$SCRIPT_DIR/manifest.txt" +FILES_MANIFEST="$SCRIPT_DIR/files.sha256" +VERSION_FILE="$SCRIPT_DIR/versions.env" +CANDIDATE_CORE="$SCRIPT_DIR/core/easytier-core" +CANDIDATE_CLI="$SCRIPT_DIR/core/easytier-cli" +WORK_DIR="" +BACKUP_DIR="" +APP_BACKED_UP=0 +APP_INSTALLED=0 +CORE_CHANGED=0 +CORE_DIR_EXISTED=0 +CORE_NEW_PATH="" +CLI_NEW_PATH="" +COMMAND_BACKED_UP=0 +COMMAND_INSTALLED=0 +SERVICE_WAS_ACTIVE=0 +SERVICE_WAS_ENABLED=0 +MESH_SERVICE_CHANGED=0 +MESH_CONFIG_CHANGED=0 +MESH_CONFIG_EXISTED=0 +TRANSACTION_STARTED=0 + +die() { + printf 'Error: %s\n' "$*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "Required command '$1' is missing. Install it with your operating system package manager and retry." +} + +cleanup() { + [[ -z "$WORK_DIR" || ! -d "$WORK_DIR" ]] || rm -rf -- "$WORK_DIR" +} + +safe_remove_install_dir() { + local expected="${TEST_ROOT}/opt/behify-easymesh" + + [[ -n "$expected" && "$INSTALL_DIR" == "$expected" ]] || return 1 + [[ ! -e "$INSTALL_DIR" ]] || rm -rf -- "$INSTALL_DIR" +} + +restore_transaction() { + local rollback_ok=1 + + set +e + trap - ERR + if [[ "$SERVICE_WAS_ACTIVE" == "1" && -f "$SERVICE_FILE" ]]; then + systemctl stop easymesh.service >/dev/null 2>&1 || true + fi + if [[ "$MESH_SERVICE_CHANGED" == "1" ]]; then + cp -a -- "$BACKUP_DIR/mesh/easymesh.service" "$SERVICE_FILE" || rollback_ok=0 + fi + if [[ "$MESH_CONFIG_CHANGED" == "1" ]]; then + if [[ "$MESH_CONFIG_EXISTED" == "1" ]]; then + cp -a -- "$BACKUP_DIR/mesh/easytier.toml" "$MESH_CORE_CONFIG" || rollback_ok=0 + else + rm -f -- "$MESH_CORE_CONFIG" + fi + fi + if [[ "$CORE_CHANGED" == "1" ]]; then + rm -f -- "$CORE_NEW_PATH" "$CLI_NEW_PATH" "$CORE_DIR/easytier-core" "$CORE_DIR/easytier-cli" + [[ ! -e "$BACKUP_DIR/core/easytier-core" && ! -L "$BACKUP_DIR/core/easytier-core" ]] || cp -a -- "$BACKUP_DIR/core/easytier-core" "$CORE_DIR/easytier-core" || rollback_ok=0 + [[ ! -e "$BACKUP_DIR/core/easytier-cli" && ! -L "$BACKUP_DIR/core/easytier-cli" ]] || cp -a -- "$BACKUP_DIR/core/easytier-cli" "$CORE_DIR/easytier-cli" || rollback_ok=0 + if [[ "$CORE_DIR_EXISTED" == "0" ]]; then + rmdir "$CORE_DIR" 2>/dev/null || true + fi + fi + if [[ "$APP_INSTALLED" == "1" ]]; then + safe_remove_install_dir || rollback_ok=0 + fi + if [[ "$APP_BACKED_UP" == "1" ]]; then + mv -- "$BACKUP_DIR/application" "$INSTALL_DIR" || rollback_ok=0 + fi + if [[ "$COMMAND_INSTALLED" == "1" ]]; then + rm -f -- "$COMMAND_PATH" + fi + if [[ "$COMMAND_BACKED_UP" == "1" ]]; then + mv -- "$BACKUP_DIR/command-easymesh" "$COMMAND_PATH" || rollback_ok=0 + fi + if [[ "$MESH_SERVICE_CHANGED" == "1" || "$MESH_CONFIG_CHANGED" == "1" ]]; then + systemctl daemon-reload >/dev/null 2>&1 || rollback_ok=0 + fi + if [[ "$SERVICE_WAS_ACTIVE" == "1" && -f "$SERVICE_FILE" ]]; then + systemctl start easymesh.service >/dev/null 2>&1 || rollback_ok=0 + fi + if [[ "$SERVICE_WAS_ENABLED" == "1" && -f "$SERVICE_FILE" ]]; then + systemctl is-enabled --quiet easymesh.service || rollback_ok=0 + fi + if [[ "$rollback_ok" == "1" ]]; then + printf 'Rollback completed. The previous installation and service state were restored.\n' >&2 + else + printf 'Rollback needs manual attention. Backup retained at: %s\n' "$BACKUP_DIR" >&2 + fi +} + +on_error() { + local line="$1" + + if [[ "$TRANSACTION_STARTED" == "1" ]]; then + printf 'Installation failed at line %s. Starting rollback.\n' "$line" >&2 + restore_transaction + fi + exit 1 +} + +trap cleanup EXIT +trap 'on_error "$LINENO"' ERR + +resolve_architecture() { + local machine + + if [[ "$TEST_MODE" == "1" && -n "$TEST_ROOT" && -n "${BEHIFY_TEST_ARCH:-}" ]]; then + machine="$BEHIFY_TEST_ARCH" + else + machine=$(uname -m) + fi + case "$machine" in + x86_64|amd64) printf 'x86_64\n' ;; + aarch64|arm64) printf 'aarch64\n' ;; + *) die "Unsupported architecture '$machine'. Behify EasyMesh v1 supports only Linux x86_64 and aarch64." ;; + esac +} + +manifest_value() { + local key="$1" + + awk -F= -v key="$key" ' + $1 == key { count++; value=substr($0, index($0, "=") + 1) } + END { if (count != 1) exit 1; print value } + ' "$MANIFEST_FILE" +} + +verify_elf_architecture() { + local binary="$1" architecture="$2" magic machine expected + + magic=$(od -An -tx1 -N4 "$binary" | tr -d ' \n') + [[ "$magic" == "7f454c46" ]] || return 1 + machine=$(od -An -tu2 -j18 -N2 "$binary" | tr -d ' \n') + case "$architecture" in + x86_64) expected=62 ;; + aarch64) expected=183 ;; + *) return 1 ;; + esac + [[ "$machine" == "$expected" ]] +} + +reported_version() { + local binary="$1" + + "$binary" --version 2>/dev/null | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 +} + +verify_candidate_runtime() { + local architecture="$1" expected_core_hash="$2" expected_cli_hash="$3" + local staged_core="$WORK_DIR/easytier-core" staged_cli="$WORK_DIR/easytier-cli" + + [[ -f "$CANDIDATE_CORE" && ! -L "$CANDIDATE_CORE" ]] || die "Package is missing a regular core/easytier-core file." + [[ -f "$CANDIDATE_CLI" && ! -L "$CANDIDATE_CLI" ]] || die "Package is missing a regular core/easytier-cli file." + [[ "$(sha256sum "$CANDIDATE_CORE" | awk '{print $1}')" == "$expected_core_hash" ]] || die "easytier-core hash mismatch." + [[ "$(sha256sum "$CANDIDATE_CLI" | awk '{print $1}')" == "$expected_cli_hash" ]] || die "easytier-cli hash mismatch." + install -m 0755 "$CANDIDATE_CORE" "$staged_core" + install -m 0755 "$CANDIDATE_CLI" "$staged_cli" + if [[ "$TEST_MODE" != "1" || "${BEHIFY_TEST_VALIDATE_ELF:-0}" == "1" ]]; then + verify_elf_architecture "$staged_core" "$architecture" || die "easytier-core ELF architecture does not match $architecture." + verify_elf_architecture "$staged_cli" "$architecture" || die "easytier-cli ELF architecture does not match $architecture." + elif [[ -z "$TEST_ROOT" ]]; then + die "BEHIFY_TEST_MODE requires BEHIFY_TEST_ROOT." + fi + [[ "$(reported_version "$staged_core")" == "2.6.4" ]] || die "easytier-core did not report version 2.6.4." + [[ "$(reported_version "$staged_cli")" == "2.6.4" ]] || die "easytier-cli did not report version 2.6.4." +} + +verify_package() { + local architecture="$1" manifest_arch manifest_version easytier_version files_hash + local core_hash cli_hash + + [[ -f "$MANIFEST_FILE" && ! -L "$MANIFEST_FILE" ]] || die "Release manifest is missing. Install from an architecture-specific Behify Release archive." + [[ -f "$FILES_MANIFEST" && ! -L "$FILES_MANIFEST" ]] || die "Package file manifest is missing." + manifest_version=$(manifest_value behify_easymesh_version) || die "Release manifest has no unique Behify version." + easytier_version=$(manifest_value easytier_version) || die "Release manifest has no unique EasyTier version." + manifest_arch=$(manifest_value architecture) || die "Release manifest has no unique architecture." + core_hash=$(manifest_value easytier_core_sha256) || die "Release manifest has no unique core hash." + cli_hash=$(manifest_value easytier_cli_sha256) || die "Release manifest has no unique CLI hash." + files_hash=$(manifest_value files_manifest_sha256) || die "Release manifest has no unique file-manifest hash." + [[ "$manifest_version" == "$BEHIFY_EASYMESH_VERSION" ]] || die "Package version '$manifest_version' does not match versions.env." + [[ "$easytier_version" == "v2.6.4" ]] || die "Unsupported EasyTier package version '$easytier_version'." + [[ "$manifest_arch" == "$architecture" ]] || die "Package architecture '$manifest_arch' does not match system architecture '$architecture'." + [[ "$core_hash" =~ ^[0-9a-f]{64}$ && "$cli_hash" =~ ^[0-9a-f]{64}$ && "$files_hash" =~ ^[0-9a-f]{64}$ ]] || die "Release manifest contains an invalid SHA-256 value." + [[ "$(sha256sum "$FILES_MANIFEST" | awk '{print $1}')" == "$files_hash" ]] || die "Package file-manifest hash mismatch." + (cd "$SCRIPT_DIR" && sha256sum --quiet -c files.sha256) || die "Package file validation failed." + verify_candidate_runtime "$architecture" "$core_hash" "$cli_hash" +} + +unsafe_legacy_maintenance_detected() { + if [[ -e "$CORE_DIR/reset.sh" || -L "$CORE_DIR/reset.sh" ]]; then + return 0 + fi + command -v crontab >/dev/null 2>&1 && + crontab -l 2>/dev/null | grep -Fq '/root/easytier/reset.sh' +} -install -d "$INSTALL_DIR" -cp "$SOURCE_FILE" "$INSTALL_DIR/easymesh" -cp -a "$SCRIPT_DIR/core" "$INSTALL_DIR/" -cp "$SCRIPT_DIR/README.md" "$SCRIPT_DIR/README_FA.md" \ - "$SCRIPT_DIR/LICENSE" "$SCRIPT_DIR/NOTICE" "$INSTALL_DIR/" +stage_application() { + local destination="$WORK_DIR/application" item -install -d "$INSTALL_DIR/relay" -install -m 0755 "$RELAY_MANAGER_FILE" "$INSTALL_DIR/relay/relay-manager" + mkdir -p "$destination/relay" + if [[ -d "$INSTALL_DIR/relay" ]]; then + cp -a "$INSTALL_DIR/relay/." "$destination/relay/" + fi + for item in easymesh install.sh uninstall.sh relay-manager versions.env manifest.txt files.sha256 README.md README.fa.md CHANGELOG.md SECURITY.md THIRD_PARTY_NOTICES.md LICENSE NOTICE; do + [[ -f "$SCRIPT_DIR/$item" ]] || die "Required package file is missing: $item" + if [[ "$item" == "relay-manager" ]]; then + install -m 0755 "$SCRIPT_DIR/$item" "$destination/relay/relay-manager" + else + cp -p "$SCRIPT_DIR/$item" "$destination/$item" + fi + done + for item in docs licenses; do + [[ ! -d "$SCRIPT_DIR/$item" ]] || cp -a "$SCRIPT_DIR/$item" "$destination/" + done + chmod 0755 "$destination/easymesh" "$destination/install.sh" "$destination/uninstall.sh" + printf 'behify-easymesh:%s\n' "$(manifest_value behify_easymesh_version)" > "$destination/.install-owner" + chmod 0644 "$destination/.install-owner" +} -# This local-only migration never downloads Xray and preserves relay service state. -"$INSTALL_DIR/relay/relay-manager" --migrate-runtime +installed_runtime_matches() { + local expected_core_hash="$1" expected_cli_hash="$2" -if [[ -d "$SCRIPT_DIR/docs" ]]; then - cp -a "$SCRIPT_DIR/docs" "$INSTALL_DIR/" + [[ -f "$CORE_DIR/easytier-core" && ! -L "$CORE_DIR/easytier-core" && -x "$CORE_DIR/easytier-core" ]] || return 1 + [[ -f "$CORE_DIR/easytier-cli" && ! -L "$CORE_DIR/easytier-cli" && -x "$CORE_DIR/easytier-cli" ]] || return 1 + [[ "$(sha256sum "$CORE_DIR/easytier-core" | awk '{print $1}')" == "$expected_core_hash" ]] || return 1 + [[ "$(sha256sum "$CORE_DIR/easytier-cli" | awk '{print $1}')" == "$expected_cli_hash" ]] || return 1 + [[ "$(reported_version "$CORE_DIR/easytier-core")" == "2.6.4" ]] || return 1 + [[ "$(reported_version "$CORE_DIR/easytier-cli")" == "2.6.4" ]] +} + +service_is_owned() { + [[ -f "$SERVICE_FILE" && ! -L "$SERVICE_FILE" ]] && + { grep -Fqx '# Managed by Behify EasyMesh' "$SERVICE_FILE" || + grep -Fq 'ExecStart=/root/easytier/easytier-core ' "$SERVICE_FILE"; } +} + +capture_service_state() { + [[ -f "$SERVICE_FILE" ]] || return 0 + service_is_owned || die "Refusing to control an unrecognized or symlinked easymesh.service file." + systemctl is-active --quiet easymesh.service && SERVICE_WAS_ACTIVE=1 + systemctl is-enabled --quiet easymesh.service && SERVICE_WAS_ENABLED=1 + return 0 +} + +backup_existing_installation() { + mkdir -p "$BACKUP_DIR/core" "$BACKUP_DIR/mesh" + chmod 0700 "$BACKUP_DIR" "$BACKUP_DIR/core" "$BACKUP_DIR/mesh" + [[ ! -d "$CORE_DIR" ]] || CORE_DIR_EXISTED=1 + if [[ -e "$INSTALL_DIR" ]]; then + mv -- "$INSTALL_DIR" "$BACKUP_DIR/application" + APP_BACKED_UP=1 + fi + [[ ! -e "$CORE_DIR/easytier-core" && ! -L "$CORE_DIR/easytier-core" ]] || cp -a -- "$CORE_DIR/easytier-core" "$BACKUP_DIR/core/easytier-core" + [[ ! -e "$CORE_DIR/easytier-cli" && ! -L "$CORE_DIR/easytier-cli" ]] || cp -a -- "$CORE_DIR/easytier-cli" "$BACKUP_DIR/core/easytier-cli" + [[ ! -f "$SERVICE_FILE" ]] || cp -a -- "$SERVICE_FILE" "$BACKUP_DIR/mesh/easymesh.service" + if [[ -f "$MESH_CORE_CONFIG" ]]; then + cp -a -- "$MESH_CORE_CONFIG" "$BACKUP_DIR/mesh/easytier.toml" + MESH_CONFIG_EXISTED=1 + fi + if [[ -e "$COMMAND_PATH" || -L "$COMMAND_PATH" ]]; then + if [[ -L "$COMMAND_PATH" && "$(readlink "$COMMAND_PATH")" == "/opt/behify-easymesh/easymesh" ]] || + { [[ "$TEST_MODE" == "1" && -f "$COMMAND_PATH" ]] && grep -Fqx '# Behify EasyMesh test command link' "$COMMAND_PATH"; }; then + : # The managed link is replaced atomically after application activation. + else + mv -- "$COMMAND_PATH" "$BACKUP_DIR/command-easymesh" + COMMAND_BACKED_UP=1 + printf 'Existing command path was not Behify-owned and was backed up to: %s\n' "$BACKUP_DIR/command-easymesh" + fi + fi +} + +migrate_mesh_service_confidentiality() { + local staged_service="$WORK_DIR/easymesh.service" staged_config="$WORK_DIR/easytier.toml" + local new_service="${SERVICE_FILE}.new.$$" new_config="${MESH_CORE_CONFIG}.new.$$" + + mesh_migration_error() { + printf 'Error: %s\n' "$*" >&2 + return 1 + } + + [[ -f "$SERVICE_FILE" ]] || return 0 + if grep -Fq -- '--network-secret ${EASYMESH_NETWORK_SECRET}' "$SERVICE_FILE"; then + [[ ! -L "$MESH_CONFIG_DIR" && ! -L "$MESH_ENV_FILE" && ! -L "$MESH_CORE_CONFIG" ]] || + mesh_migration_error "Refusing to migrate a symlinked mesh configuration path." + [[ -f "$MESH_ENV_FILE" ]] || mesh_migration_error "Managed easymesh.service needs $MESH_ENV_FILE before its secret-safe migration." + if [[ "$TEST_MODE" != "1" || "$(uname -s)" == "Linux" ]]; then + [[ "$(stat -c '%a' "$MESH_ENV_FILE")" == "600" ]] || + mesh_migration_error "Managed mesh.env must be mode 0600 before migration." + fi + if [[ "$TEST_MODE" != "1" ]]; then + [[ "$(stat -c '%u' "$MESH_ENV_FILE")" == "0" ]] || + mesh_migration_error "Managed mesh.env must be owned by root before migration." + fi + grep -Fqx 'EnvironmentFile=/etc/behify-easymesh/mesh.env' "$SERVICE_FILE" || + mesh_migration_error "Managed easymesh.service uses an unsupported secret-bearing layout; it was not modified." + sed \ + -e 's|ExecStart=/root/easytier/easytier-core |ExecStart=/root/easytier/easytier-core --config-file /etc/behify-easymesh/easytier.toml --console-log-level warn |' \ + -e 's| --network-secret ${EASYMESH_NETWORK_SECRET}||' \ + "$SERVICE_FILE" > "$staged_service" + elif grep -Fq -- '--network-secret' "$SERVICE_FILE"; then + mesh_migration_error "Managed easymesh.service uses an unsupported secret-bearing layout; it was not modified." + elif grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' "$SERVICE_FILE" && + grep -Fq -- '--console-log-level warn' "$SERVICE_FILE"; then + cp -p -- "$SERVICE_FILE" "$staged_service" + else + return 0 + fi + + umask 077 + cat > "$staged_config" <<'EOF' +[network_identity] +network_name = "default" +network_secret = "${EASYMESH_NETWORK_SECRET}" +EOF + grep -Fqx 'EnvironmentFile=/etc/behify-easymesh/mesh.env' "$staged_service" || + mesh_migration_error "Migrated mesh service lost its environment file." + grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' "$staged_service" || + mesh_migration_error "Migrated mesh service did not select the private EasyTier config." + grep -Fq -- '--console-log-level warn' "$staged_service" || + mesh_migration_error "Migrated mesh service did not enforce WARN console logging." + ! grep -Fq -- '--network-secret' "$staged_service" || + mesh_migration_error "Migrated mesh service still exposes a network-secret argument." + if [[ -e "$MESH_CORE_CONFIG" ]] && ! cmp -s "$staged_config" "$MESH_CORE_CONFIG"; then + mesh_migration_error "Existing EasyTier config is not the Behify-managed minimal config; it was not modified." + fi + EASYMESH_NETWORK_SECRET=BehifyConfigValidationSecret "$WORK_DIR/easytier-core" \ + --config-file "$staged_config" --console-log-level warn --check-config >/dev/null 2>&1 || + mesh_migration_error "Migrated EasyTier configuration did not pass candidate runtime validation." + + if cmp -s "$staged_service" "$SERVICE_FILE" && [[ -f "$MESH_CORE_CONFIG" ]] && + cmp -s "$staged_config" "$MESH_CORE_CONFIG" && [[ "$(stat -c '%a' "$MESH_CORE_CONFIG")" == "600" ]]; then + return 0 + fi + [[ "$SERVICE_WAS_ACTIVE" != "1" ]] || systemctl stop easymesh.service >/dev/null 2>&1 + mkdir -p "$MESH_CONFIG_DIR" + chmod 0700 "$MESH_CONFIG_DIR" + install -m 0600 "$staged_config" "$new_config" + install -m 0644 "$staged_service" "$new_service" + mv -f -- "$new_config" "$MESH_CORE_CONFIG" + chmod 0600 "$MESH_CORE_CONFIG" + MESH_CONFIG_CHANGED=1 + mv -f -- "$new_service" "$SERVICE_FILE" + MESH_SERVICE_CHANGED=1 + systemctl daemon-reload >/dev/null 2>&1 +} + +activate_runtime() { + local expected_core_hash="$1" expected_cli_hash="$2" + + if installed_runtime_matches "$expected_core_hash" "$expected_cli_hash"; then + printf 'EasyTier v2.6.4 runtime is already verified; binaries were not replaced.\n' + return 0 + fi + [[ "$SERVICE_WAS_ACTIVE" != "1" ]] || systemctl stop easymesh.service >/dev/null 2>&1 + if [[ ! -d "$CORE_DIR" ]]; then + install -d -m 0755 "$CORE_DIR" + fi + CORE_NEW_PATH="$CORE_DIR/.easytier-core.new.$$" + CLI_NEW_PATH="$CORE_DIR/.easytier-cli.new.$$" + CORE_CHANGED=1 + install -m 0755 "$WORK_DIR/easytier-core" "$CORE_NEW_PATH" + install -m 0755 "$WORK_DIR/easytier-cli" "$CLI_NEW_PATH" + mv -f -- "$CORE_NEW_PATH" "$CORE_DIR/easytier-core" + if [[ "$TEST_MODE" == "1" && -n "$TEST_ROOT" && "${BEHIFY_TEST_FAIL_AFTER_CORE:-0}" == "1" ]]; then + return 97 + fi + mv -f -- "$CLI_NEW_PATH" "$CORE_DIR/easytier-cli" + installed_runtime_matches "$expected_core_hash" "$expected_cli_hash" +} + +activate_application() { + local new_link="${COMMAND_PATH}.new.$$" + + mv -- "$WORK_DIR/application" "$INSTALL_DIR" + APP_INSTALLED=1 + mkdir -p "$(dirname "$COMMAND_PATH")" + if [[ "$TEST_MODE" == "1" && "${BEHIFY_TEST_NATIVE_LINK:-0}" != "1" ]]; then + { + printf '%s\n' '#!/usr/bin/env bash' '# Behify EasyMesh test command link' + printf 'exec %q "$@"\n' "$INSTALL_DIR/easymesh" + } > "$new_link" + chmod 0755 "$new_link" + else + ln -s "/opt/behify-easymesh/easymesh" "$new_link" + fi + mv -f -- "$new_link" "$COMMAND_PATH" + COMMAND_INSTALLED=1 +} + +validate_service_after_upgrade() { + local pid running_version enabled_now=0 initial_pid="" initial_restarts="" current_restarts + local attempt + + service_validation_diagnostics() { + local service_state journal_output + + service_state=$(systemctl show easymesh.service \ + -p ActiveState -p SubState -p Result -p ExecMainCode -p ExecMainStatus \ + -p MainPID -p NRestarts --no-pager 2>&1 || true) + printf 'Service validation state:\n%s\n' "$service_state" | \ + sed -E 's/([Ss]ecret[=:[:space:]]*)[^[:space:]]+/\1[redacted]/g' + if command -v journalctl >/dev/null 2>&1; then + journal_output=$(journalctl -u easymesh.service -n 20 --no-pager -o cat 2>&1 || true) + printf 'Service validation journal (last 20 lines):\n%s\n' "$journal_output" | \ + sed -E 's/([Ss]ecret[=:[:space:]]*)[^[:space:]]+/\1[redacted]/g' + fi + } + + service_validation_failed() { + printf 'Service validation failed: %s\n' "$1" >&2 + service_validation_diagnostics >&2 + return 1 + } + + [[ -f "$SERVICE_FILE" ]] || return 0 + if [[ "$SERVICE_WAS_ACTIVE" == "1" ]]; then + systemctl start easymesh.service >/dev/null 2>&1 || service_validation_failed 'systemctl start failed.' + for attempt in 1 2 3 4; do + systemctl is-active --quiet easymesh.service || service_validation_failed 'service did not remain active.' + pid=$(systemctl show -p MainPID --value easymesh.service 2>/dev/null || true) + [[ "$pid" =~ ^[1-9][0-9]*$ ]] || service_validation_failed 'service did not report a running MainPID.' + current_restarts=$(systemctl show -p NRestarts --value easymesh.service 2>/dev/null || true) + [[ "$current_restarts" =~ ^[0-9]+$ ]] || service_validation_failed 'service did not report a numeric restart count.' + if [[ -z "$initial_pid" ]]; then + initial_pid="$pid" + initial_restarts="$current_restarts" + else + [[ "$pid" == "$initial_pid" ]] || service_validation_failed 'service MainPID changed during startup validation.' + [[ "$current_restarts" == "$initial_restarts" ]] || service_validation_failed 'service restarted during startup validation.' + fi + if [[ "$TEST_MODE" != "1" ]]; then + [[ -x "/proc/$pid/exe" ]] || service_validation_failed 'service MainPID executable is unavailable.' + running_version=$(reported_version "/proc/$pid/exe") + [[ "$running_version" == "2.6.4" ]] || service_validation_failed 'service MainPID did not report EasyTier 2.6.4.' + fi + [[ "$attempt" == "4" ]] || sleep 1 + done + else + ! systemctl is-active --quiet easymesh.service + fi + systemctl is-enabled --quiet easymesh.service && enabled_now=1 + [[ "$enabled_now" == "$SERVICE_WAS_ENABLED" ]] +} + +if [[ -n "$TEST_ROOT" ]]; then + [[ "$TEST_MODE" == "1" ]] || die "BEHIFY_TEST_ROOT is accepted only with BEHIFY_TEST_MODE=1." + [[ "$TEST_ROOT" == /tmp/* || "$TEST_ROOT" == /var/tmp/* ]] || die "BEHIFY_TEST_ROOT must be below /tmp or /var/tmp." + [[ "$TEST_ROOT" != *'/../'* && "$TEST_ROOT" != */.. ]] || die "BEHIFY_TEST_ROOT must not contain parent traversal." +fi +if [[ -z "$TEST_ROOT" && $EUID -ne 0 ]]; then + die "Run this package installer as root: sudo EASYMESH_OFFLINE=1 bash install.sh" +fi +if [[ "$TEST_MODE" == "1" && -z "$TEST_ROOT" ]]; then + die "BEHIFY_TEST_MODE requires BEHIFY_TEST_ROOT." fi +if [[ "$TEST_MODE" != "1" && "$(uname -s)" != "Linux" ]]; then + die "Unsupported operating system '$(uname -s)'. Behify EasyMesh v1 supports Linux only." +fi +for command_name in awk grep sha256sum od tr head mktemp install cp mv readlink date chmod mkdir rm ln dirname uname systemctl sed sleep cmp stat openssl python3; do + require_command "$command_name" +done +[[ -f "$VERSION_FILE" && ! -L "$VERSION_FILE" ]] || die "Package version metadata is missing." +# shellcheck source=versions.env +. "$VERSION_FILE" +[[ "$BEHIFY_EASYMESH_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]] || die "Package version metadata is invalid." +[[ "$EASYTIER_VERSION" == "v2.6.4" ]] || die "Package EasyTier version metadata is invalid." +[[ ! -L "$INSTALL_DIR" ]] || die "Refusing to use a symlinked Behify installation directory." +[[ ! -L "$CORE_DIR" && ! -L "$BACKUP_PARENT" ]] || die "Refusing to use a symlinked runtime or backup root." +[[ ! -L "$INSTALL_DIR/relay" ]] || die "Refusing to migrate a symlinked Behify relay directory." +if unsafe_legacy_maintenance_detected; then + die "Unsafe pre-v1 EasyTier maintenance was detected. Inspect /root/easytier/reset.sh and 'sudo crontab -l' without executing or deleting them, then obtain separate cleanup authorization before upgrading." +fi + +ARCHITECTURE=$(resolve_architecture) +WORK_DIR=$(mktemp -d "${TMPDIR:-/tmp}/behify-easymesh-install.XXXXXX") +chmod 0700 "$WORK_DIR" +verify_package "$ARCHITECTURE" +stage_application +capture_service_state + +if [[ "$TEST_MODE" != "1" && -d "/opt/behify-easymesh/relay" ]]; then + "$WORK_DIR/application/relay/relay-manager" --migrate-runtime || die "Existing isolated relay runtime migration failed; main installation was not changed." +fi + +BACKUP_DIR="$BACKUP_PARENT/install-$(date +%Y%m%d-%H%M%S)-$$" +mkdir -p "$BACKUP_PARENT" +TRANSACTION_STARTED=1 +backup_existing_installation +EXPECTED_CORE_HASH=$(manifest_value easytier_core_sha256) +EXPECTED_CLI_HASH=$(manifest_value easytier_cli_sha256) +activate_runtime "$EXPECTED_CORE_HASH" "$EXPECTED_CLI_HASH" +activate_application +migrate_mesh_service_confidentiality +validate_service_after_upgrade +TRANSACTION_STARTED=0 -chmod +x "$INSTALL_DIR/easymesh" - -rm -f "$COMMAND_PATH" -ln -s "$INSTALL_DIR/easymesh" "$COMMAND_PATH" - -echo "Behify EasyMesh installed successfully." -echo "Installed path: $INSTALL_DIR" -echo "Command path: $COMMAND_PATH -> $INSTALL_DIR/easymesh" -echo "Existing relay definitions, isolated Xray files, and service state were preserved." -echo -echo "Run:" -echo "sudo easymesh" -echo "sudo easymesh 2.6.4" -echo -echo "Strict offline:" -echo "sudo EASYMESH_OFFLINE=1 easymesh" -echo "sudo EASYMESH_OFFLINE=1 easymesh 2.6.4" +printf '%s\n' \ + 'Behify EasyMesh installed successfully.' \ + 'Installed path: /opt/behify-easymesh' \ + 'Command path: /usr/local/bin/easymesh -> /opt/behify-easymesh/easymesh' \ + "Runtime: EasyTier v2.6.4 ($ARCHITECTURE)" \ + "Backup: ${BACKUP_DIR#$TEST_ROOT}" \ + '' \ + 'Run: sudo easymesh' diff --git a/licenses/Easy-Mesh-ATTRIBUTION.md b/licenses/Easy-Mesh-ATTRIBUTION.md new file mode 100644 index 0000000..ab993f0 --- /dev/null +++ b/licenses/Easy-Mesh-ATTRIBUTION.md @@ -0,0 +1,7 @@ +# Easy-Mesh Attribution + +Behify EasyMesh is derived from Easy-Mesh by Musixal: + +https://github.com/Musixal/Easy-Mesh + +The original custom license and notice are retained unchanged at the repository root as `LICENSE` and `NOTICE`. Based on Easy-Mesh by Musixal. LICENSE and NOTICE retained. diff --git a/licenses/EasyTier-LGPL-3.0.txt b/licenses/EasyTier-LGPL-3.0.txt new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/licenses/EasyTier-LGPL-3.0.txt @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/licenses/Xray-core-MPL-2.0.txt b/licenses/Xray-core-MPL-2.0.txt new file mode 100644 index 0000000..a612ad9 --- /dev/null +++ b/licenses/Xray-core-MPL-2.0.txt @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/release/build-release.sh b/release/build-release.sh new file mode 100644 index 0000000..2aad128 --- /dev/null +++ b/release/build-release.sh @@ -0,0 +1,217 @@ +#!/usr/bin/env bash + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +ASSET_METADATA="$REPO_ROOT/release/easytier-v2.6.4-assets.env" +VERSION_METADATA="$REPO_ROOT/versions.env" +OUTPUT_DIR="${1:-$REPO_ROOT/release-output}" +CACHE_DIR="${BEHIFY_RELEASE_CACHE:-$REPO_ROOT/.release-cache}" +STAGING_DIR="" +BUILT_PACKAGE="" + +die() { + printf 'Error: %s\n' "$*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "Required command '$1' is missing. Install it and retry." +} + +cleanup() { + [[ -z "$STAGING_DIR" || ! -d "$STAGING_DIR" ]] || rm -rf -- "$STAGING_DIR" +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +# shellcheck source=../versions.env +. "$VERSION_METADATA" +# shellcheck source=easytier-v2.6.4-assets.env +. "$ASSET_METADATA" +[[ "$BEHIFY_EASYMESH_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$ ]] || die "Invalid Behify release version metadata." +[[ "$EASYTIER_VERSION" == "v2.6.4" ]] || die "Unexpected EasyTier release version metadata." +for command_name in curl sha256sum unzip tar awk grep od tr find sort install mktemp sed git cp chmod mkdir rm mv basename cmp; do + require_command "$command_name" +done +if [[ "${BEHIFY_ALLOW_DIRTY_BUILD:-0}" != "1" ]] && [[ -n "$(git -C "$REPO_ROOT" status --porcelain --untracked-files=normal)" ]]; then + die "Release builds require a clean Git working tree. Commit or stash changes first." +fi + +[[ ! -L "$OUTPUT_DIR" ]] || die "Refusing to write release artifacts through a symlinked output directory." +mkdir -p "$CACHE_DIR" "$OUTPUT_DIR" +find "$OUTPUT_DIR" -mindepth 1 -maxdepth 1 -type f -delete +find "$OUTPUT_DIR" -mindepth 1 -maxdepth 1 -type l -delete +if find "$OUTPUT_DIR" -mindepth 1 -maxdepth 1 -print -quit | grep -q .; then + die "Release output contains a directory or unsupported entry that was not removed." +fi +STAGING_DIR=$(mktemp -d "${TMPDIR:-/tmp}/behify-easymesh-release.XXXXXX") +chmod 0700 "$STAGING_DIR" + +fetch_verified() { + local url="$1" expected_hash="$2" destination="$3" partial + + partial="${destination}.part" + + if [[ -f "$destination" ]] && [[ "$(sha256sum "$destination" | awk '{print $1}')" == "$expected_hash" ]]; then + printf 'Using verified cache: %s\n' "$(basename "$destination")" + return 0 + fi + rm -f -- "$partial" + curl --fail --location --proto '=https' --tlsv1.2 --output "$partial" "$url" + [[ "$(sha256sum "$partial" | awk '{print $1}')" == "$expected_hash" ]] || { + rm -f -- "$partial" + die "SHA-256 mismatch for $url" + } + mv -f -- "$partial" "$destination" +} + +verify_elf_architecture() { + local binary="$1" architecture="$2" magic machine expected + + magic=$(od -An -tx1 -N4 "$binary" | tr -d ' \n') + [[ "$magic" == "7f454c46" ]] || die "Not an ELF executable: $binary" + machine=$(od -An -tu2 -j18 -N2 "$binary" | tr -d ' \n') + case "$architecture" in + x86_64) expected=62 ;; + aarch64) expected=183 ;; + *) die "Unsupported build architecture: $architecture" ;; + esac + [[ "$machine" == "$expected" ]] || die "ELF architecture mismatch for $binary" +} + +reported_version() { + "$1" --version 2>/dev/null | head -n 1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -n 1 +} + +host_architecture() { + [[ "$(uname -s)" == "Linux" ]] || { + printf 'unsupported\n' + return 0 + } + case "$(uname -m)" in + x86_64|amd64) printf 'x86_64\n' ;; + aarch64|arm64) printf 'aarch64\n' ;; + *) printf 'unsupported\n' ;; + esac +} + +copy_release_files() { + local package_dir="$1" item + + for item in easymesh install.sh uninstall.sh relay-manager versions.env README.md README.fa.md CHANGELOG.md SECURITY.md THIRD_PARTY_NOTICES.md LICENSE NOTICE; do + install -m 0644 "$REPO_ROOT/$item" "$package_dir/$item" + done + chmod 0755 "$package_dir/easymesh" "$package_dir/install.sh" "$package_dir/uninstall.sh" "$package_dir/relay-manager" + cp -a "$REPO_ROOT/docs" "$REPO_ROOT/licenses" "$package_dir/" +} + +generate_file_manifest() { + local package_dir="$1" relative_file + + : > "$package_dir/files.sha256" + while IFS= read -r relative_file; do + (cd "$package_dir" && sha256sum "$relative_file") >> "$package_dir/files.sha256" + done < <(cd "$package_dir" && find . -type f ! -name manifest.txt ! -name files.sha256 -print | LC_ALL=C sort) +} + +build_architecture_package() { + local architecture="$1" asset url archive_hash core_hash cli_hash upstream_dir + local archive extract_dir package_name package_dir files_hash package_archive + + case "$architecture" in + x86_64) + asset="$EASYTIER_X86_64_ASSET" + url="$EASYTIER_X86_64_URL" + archive_hash="$EASYTIER_X86_64_ARCHIVE_SHA256" + core_hash="$EASYTIER_X86_64_CORE_SHA256" + cli_hash="$EASYTIER_X86_64_CLI_SHA256" + upstream_dir=easytier-linux-x86_64 + ;; + aarch64) + asset="$EASYTIER_AARCH64_ASSET" + url="$EASYTIER_AARCH64_URL" + archive_hash="$EASYTIER_AARCH64_ARCHIVE_SHA256" + core_hash="$EASYTIER_AARCH64_CORE_SHA256" + cli_hash="$EASYTIER_AARCH64_CLI_SHA256" + upstream_dir=easytier-linux-aarch64 + ;; + *) die "Unsupported build architecture: $architecture" ;; + esac + + archive="$CACHE_DIR/$asset" + fetch_verified "$url" "$archive_hash" "$archive" + unzip -Z1 "$archive" | awk '/(^|\/)\.\.($|\/)|^\// { exit 1 }' || die "Unsafe path in $asset" + extract_dir="$STAGING_DIR/upstream-$architecture" + mkdir -p "$extract_dir" + unzip -p "$archive" "$upstream_dir/easytier-core" > "$extract_dir/easytier-core" + unzip -p "$archive" "$upstream_dir/easytier-cli" > "$extract_dir/easytier-cli" + chmod 0755 "$extract_dir/easytier-core" "$extract_dir/easytier-cli" + [[ "$(sha256sum "$extract_dir/easytier-core" | awk '{print $1}')" == "$core_hash" ]] || die "Extracted core hash mismatch for $architecture" + [[ "$(sha256sum "$extract_dir/easytier-cli" | awk '{print $1}')" == "$cli_hash" ]] || die "Extracted CLI hash mismatch for $architecture" + verify_elf_architecture "$extract_dir/easytier-core" "$architecture" + verify_elf_architecture "$extract_dir/easytier-cli" "$architecture" + if [[ "$(host_architecture)" == "$architecture" ]]; then + [[ "$(reported_version "$extract_dir/easytier-core")" == "2.6.4" ]] || die "Core version mismatch for $architecture" + [[ "$(reported_version "$extract_dir/easytier-cli")" == "2.6.4" ]] || die "CLI version mismatch for $architecture" + printf 'Native runtime validation passed for %s.\n' "$architecture" + else + printf 'Static ELF/hash validation passed for %s; native execution remains a release gate.\n' "$architecture" + fi + + package_name="behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-${architecture}" + package_dir="$STAGING_DIR/$package_name" + mkdir -p "$package_dir/core" + copy_release_files "$package_dir" + install -m 0755 "$extract_dir/easytier-core" "$package_dir/core/easytier-core" + install -m 0755 "$extract_dir/easytier-cli" "$package_dir/core/easytier-cli" + generate_file_manifest "$package_dir" + files_hash=$(sha256sum "$package_dir/files.sha256" | awk '{print $1}') + cat > "$package_dir/manifest.txt" < "$ONLINE_INSTALLER" +chmod 0755 "$ONLINE_INSTALLER" +cp -p "$ONLINE_INSTALLER" "$OUTPUT_DIR/install.sh" +cmp -s "$ONLINE_INSTALLER" "$OUTPUT_DIR/install.sh" || die "Stable installer alias does not match the versioned bootstrap." + +fetch_verified "$EASYTIER_SOURCE_URL" "$EASYTIER_SOURCE_SHA256" "$CACHE_DIR/$EASYTIER_SOURCE_ASSET" +cp -p "$CACHE_DIR/$EASYTIER_SOURCE_ASSET" "$OUTPUT_DIR/$EASYTIER_SOURCE_ASSET" + +( + cd "$OUTPUT_DIR" + sha256sum \ + "$(basename "$X86_PACKAGE")" \ + "$(basename "$AARCH64_PACKAGE")" \ + "$(basename "$ONLINE_INSTALLER")" \ + "install.sh" \ + "$EASYTIER_SOURCE_ASSET" > SHA256SUMS +) +printf 'Release artifacts written to: %s\n' "$OUTPUT_DIR" diff --git a/release/easytier-v2.6.4-assets.env b/release/easytier-v2.6.4-assets.env new file mode 100644 index 0000000..7908756 --- /dev/null +++ b/release/easytier-v2.6.4-assets.env @@ -0,0 +1,16 @@ +EASYTIER_X86_64_ASSET=easytier-linux-x86_64-v2.6.4.zip +EASYTIER_X86_64_URL=https://github.com/EasyTier/EasyTier/releases/download/v2.6.4/easytier-linux-x86_64-v2.6.4.zip +EASYTIER_X86_64_ARCHIVE_SHA256=61b659eaedba658fa66fe47d17e1426cdd77e5d02fa15fed447bb4357c09dfd6 +EASYTIER_X86_64_CORE_SHA256=35b854e0c5853c02733870488c977200f6aa06d09c08d5dc7f9f9e150e7be8d5 +EASYTIER_X86_64_CLI_SHA256=8da66644ec50dcf618fccf76780991f52dfbe2939797abc0efe0ef4d4c4d3383 + +EASYTIER_AARCH64_ASSET=easytier-linux-aarch64-v2.6.4.zip +EASYTIER_AARCH64_URL=https://github.com/EasyTier/EasyTier/releases/download/v2.6.4/easytier-linux-aarch64-v2.6.4.zip +EASYTIER_AARCH64_ARCHIVE_SHA256=f533ec25a7ea714e09f645615012200278058525795cc3bb690ff011aec1a70f +EASYTIER_AARCH64_CORE_SHA256=7bd504e87cad52edacb9ea84ece7397d0afb911101de623a3dfc4a4dbd9e6bb6 +EASYTIER_AARCH64_CLI_SHA256=11cd0c957e41057a49e2580ce6490d7bef8bde0a04602e5bd80a4b17cb30b984 + +EASYTIER_SOURCE_COMMIT=8428a89d2dabc94c97d370ec607c6ca142473626 +EASYTIER_SOURCE_ASSET=easytier-v2.6.4-source.tar.gz +EASYTIER_SOURCE_URL=https://github.com/EasyTier/EasyTier/archive/8428a89d2dabc94c97d370ec607c6ca142473626.tar.gz +EASYTIER_SOURCE_SHA256=37fb83c61d16e5a1a88354eb96d0caf526748ffd92364ac22ffad09316bb337c diff --git a/release/online-install.sh.in b/release/online-install.sh.in new file mode 100644 index 0000000..931ea12 --- /dev/null +++ b/release/online-install.sh.in @@ -0,0 +1,79 @@ +#!/usr/bin/env bash + +set -euo pipefail + +BEHIFY_VERSION="@BEHIFY_VERSION@" +RELEASE_BASE_URL="https://github.com/behifyn/behify-easymesh/releases/download/v${BEHIFY_VERSION}" +X86_64_SHA256="@X86_64_PACKAGE_SHA256@" +AARCH64_SHA256="@AARCH64_PACKAGE_SHA256@" +TEMP_DIR="" + +die() { + printf 'Error: %s\n' "$*" >&2 + exit 1 +} + +require_command() { + command -v "$1" >/dev/null 2>&1 || die "Required command '$1' is missing. Install it with your operating system package manager and retry." +} + +cleanup() { + [[ -z "$TEMP_DIR" || ! -d "$TEMP_DIR" ]] || rm -rf -- "$TEMP_DIR" +} +trap cleanup EXIT +trap 'exit 130' INT +trap 'exit 143' TERM + +if [[ $EUID -ne 0 && "${BEHIFY_TEST_MODE:-0}" != "1" ]]; then + die "Run this installer as root: sudo bash online-install-v${BEHIFY_VERSION}.sh" +fi +for command_name in curl sha256sum tar mktemp awk grep chmod rm uname; do + require_command "$command_name" +done +if [[ "${BEHIFY_TEST_MODE:-0}" != "1" && "$(uname -s)" != "Linux" ]]; then + die "Unsupported operating system '$(uname -s)'. Behify EasyMesh v1 supports Linux only." +fi + +MACHINE=$(uname -m) +if [[ "${BEHIFY_TEST_MODE:-0}" == "1" && -n "${BEHIFY_TEST_ARCH:-}" ]]; then + MACHINE="$BEHIFY_TEST_ARCH" +fi +case "$MACHINE" in + x86_64|amd64) + ARCHITECTURE=x86_64 + EXPECTED_SHA256="$X86_64_SHA256" + ;; + aarch64|arm64) + ARCHITECTURE=aarch64 + EXPECTED_SHA256="$AARCH64_SHA256" + ;; + *) + die "Unsupported architecture '$MACHINE'. Behify EasyMesh v1 supports only Linux x86_64 and aarch64." + ;; +esac + +ASSET="behify-easymesh-v${BEHIFY_VERSION}-linux-${ARCHITECTURE}.tar.gz" +TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/behify-easymesh-online.XXXXXX") +chmod 0700 "$TEMP_DIR" +ARCHIVE="$TEMP_DIR/$ASSET" + +printf 'Downloading %s...\n' "$ASSET" +curl --fail --location --proto '=https' --tlsv1.2 \ + --output "$ARCHIVE" "$RELEASE_BASE_URL/$ASSET" || die "Package download failed. The existing installation was not changed." +printf '%s %s\n' "$EXPECTED_SHA256" "$ARCHIVE" | sha256sum --check --status - || + die "Package SHA-256 verification failed. The archive was not extracted." + +if ! tar -tzf "$ARCHIVE" > "$TEMP_DIR/archive.list"; then + die "Package archive is invalid." +fi +if awk '/(^|\/)\.\.($|\/)|^\// { exit 1 }' "$TEMP_DIR/archive.list"; then + : +else + die "Package archive contains an unsafe path." +fi +tar --extract --gzip --file "$ARCHIVE" --directory "$TEMP_DIR" --no-same-owner +PACKAGE_DIR="$TEMP_DIR/behify-easymesh-v${BEHIFY_VERSION}-linux-${ARCHITECTURE}" +[[ -f "$PACKAGE_DIR/install.sh" && ! -L "$PACKAGE_DIR/install.sh" ]] || die "Package installer is missing." + +# The package installer revalidates every internal file and performs no network access. +EASYMESH_OFFLINE=1 bash "$PACKAGE_DIR/install.sh" diff --git a/tests/installer-regression.sh b/tests/installer-regression.sh new file mode 100644 index 0000000..52fdeb8 --- /dev/null +++ b/tests/installer-regression.sh @@ -0,0 +1,287 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-installer-test.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT +package_dir="$test_root/package" +mock_bin="$test_root/mock-bin" +network_log="$test_root/network.log" +mkdir -p "$mock_bin" +cp "$repo_root/tests/mock-systemctl.sh" "$mock_bin/systemctl" +chmod 0755 "$mock_bin/systemctl" +cat > "$mock_bin/curl" <> '$network_log' +exit 90 +EOF +cat > "$mock_bin/wget" <> '$network_log' +exit 90 +EOF +cat > "$mock_bin/crontab" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == "-l" && "${MOCK_LEGACY_CRON:-0}" == "1" ]]; then + printf '0 * * * * /root/easytier/reset.sh # legacy EasyMesh\n' + exit 0 +fi +exit 1 +EOF +chmod 0755 "$mock_bin/curl" "$mock_bin/wget" "$mock_bin/crontab" +bash "$repo_root/tests/make-test-package.sh" "$package_dir" x86_64 + +run_installer() { + local root="$1" + shift + mkdir -p "$root" "$root/systemctl-state" + MOCK_SYSTEMCTL_STATE="$root/systemctl-state" \ + MOCK_INSTALL_ROOT="$root" \ + BEHIFY_TEST_ROOT="$root" \ + BEHIFY_TEST_MODE=1 \ + BEHIFY_TEST_ARCH=x86_64 \ + EASYMESH_OFFLINE=1 \ + PATH="$mock_bin:$PATH" \ + "$@" bash "$package_dir/install.sh" +} + +write_legacy_mesh_configuration() { + local root="$1" extra_argument="${2:-}" + + mkdir -p "$root/etc/behify-easymesh" "$root/etc/systemd/system" + cat > "$root/etc/behify-easymesh/mesh.env" <<'EOF' +EASYMESH_IP="10.254.254.1" +EASYMESH_PEERS="" +EASYMESH_HOSTNAME="behify-test" +EASYMESH_NETWORK_SECRET="BehifyRc2InstallerSecretMarker" +EASYMESH_DEFAULT_PROTOCOL="tcp" +EASYMESH_LISTENERS="--listeners tcp://127.0.0.1:29999" +EASYMESH_MULTI_THREAD="" +EASYMESH_ENCRYPTION="" +EASYMESH_IPV6="--disable-ipv6" +EOF + chmod 0600 "$root/etc/behify-easymesh/mesh.env" + cat > "$root/etc/systemd/system/easymesh.service" <"$test_root/unsupported.log" 2>&1; then + printf 'Unsupported architecture was accepted.\n' >&2 + exit 1 +fi +grep -Fq 'supports only Linux x86_64 and aarch64' "$test_root/unsupported.log" +[[ ! -e "$unsupported_root/opt/behify-easymesh" ]] + +invalid_package="$test_root/invalid-package" +cp -a "$package_dir" "$invalid_package" +rm -f "$invalid_package/core/easytier-cli" +invalid_root="$test_root/invalid-root" +if MOCK_SYSTEMCTL_STATE="$invalid_root/state" MOCK_INSTALL_ROOT="$invalid_root" \ + BEHIFY_TEST_ROOT="$invalid_root" BEHIFY_TEST_MODE=1 BEHIFY_TEST_ARCH=x86_64 \ + PATH="$mock_bin:$PATH" bash "$invalid_package/install.sh" >"$test_root/invalid.log" 2>&1; then + printf 'Incomplete package was accepted.\n' >&2 + exit 1 +fi +[[ ! -e "$invalid_root/opt/behify-easymesh" ]] + +unowned_root="$test_root/unowned-service" +mkdir -p "$unowned_root/etc/systemd/system" "$unowned_root/opt/behify-easymesh" "$unowned_root/systemctl-state" +printf '%s\n' '[Service]' 'ExecStart=/usr/bin/unrelated' > "$unowned_root/etc/systemd/system/easymesh.service" +printf 'preserve application\n' > "$unowned_root/opt/behify-easymesh/sentinel" +if run_installer "$unowned_root" env >"$test_root/unowned.log" 2>&1; then + printf 'Installer accepted an unrecognized service file.\n' >&2 + exit 1 +fi +grep -Fq 'Refusing to control an unrecognized or symlinked easymesh.service file.' "$test_root/unowned.log" +[[ -f "$unowned_root/opt/behify-easymesh/sentinel" ]] +[[ ! -e "$unowned_root/root/easytier/easytier-core" ]] + +legacy_script_root="$test_root/legacy-script" +mkdir -p "$legacy_script_root/root/easytier" +printf '#!/usr/bin/env bash\nexit 0\n' > "$legacy_script_root/root/easytier/reset.sh" +if run_installer "$legacy_script_root" env >"$test_root/legacy-script.log" 2>&1; then + printf 'Installer accepted a legacy reset.sh setup.\n' >&2 + exit 1 +fi +grep -Fq 'Unsafe pre-v1 EasyTier maintenance was detected.' "$test_root/legacy-script.log" +[[ ! -e "$legacy_script_root/opt/behify-easymesh" ]] + +legacy_cron_root="$test_root/legacy-cron" +if run_installer "$legacy_cron_root" env MOCK_LEGACY_CRON=1 >"$test_root/legacy-cron.log" 2>&1; then + printf 'Installer accepted a legacy root-cron setup.\n' >&2 + exit 1 +fi +grep -Fq 'Unsafe pre-v1 EasyTier maintenance was detected.' "$test_root/legacy-cron.log" +[[ ! -e "$legacy_cron_root/opt/behify-easymesh" ]] + +symlink_install_root="$test_root/symlink-install-root" +mkdir -p "$symlink_install_root/opt" "$symlink_install_root/unrelated" +if ln -s "$symlink_install_root/unrelated" "$symlink_install_root/opt/behify-easymesh" 2>/dev/null && + [[ -L "$symlink_install_root/opt/behify-easymesh" ]]; then + if run_installer "$symlink_install_root" env >"$test_root/symlink-install.log" 2>&1; then + printf 'Installer accepted a symlinked application directory.\n' >&2 + exit 1 + fi + grep -Fq 'Refusing to use a symlinked Behify installation directory.' "$test_root/symlink-install.log" + [[ ! -e "$symlink_install_root/root/easytier/easytier-core" ]] +elif [[ "$(uname -s)" == "Linux" ]]; then + printf 'Could not create the application-directory symlink required by the Linux regression.\n' >&2 + exit 1 +else + printf 'Application-directory symlink check skipped: native symlinks are unavailable.\n' +fi + +fresh_root="$test_root/fresh" +run_installer "$fresh_root" env > "$test_root/fresh-install.log" +[[ -x "$fresh_root/opt/behify-easymesh/easymesh" ]] +[[ -x "$fresh_root/root/easytier/easytier-core" ]] +grep -Fqx '# Behify EasyMesh test command link' "$fresh_root/usr/local/bin/easymesh" +grep -Fqx 'Run: sudo easymesh' "$test_root/fresh-install.log" +! grep -Fq 'Menu options:' "$test_root/fresh-install.log" +[[ ! -s "$network_log" ]] +first_core_hash=$(sha256sum "$fresh_root/root/easytier/easytier-core" | awk '{print $1}') +run_installer "$fresh_root" env +[[ "$(sha256sum "$fresh_root/root/easytier/easytier-core" | awk '{print $1}')" == "$first_core_hash" ]] +[[ ! -s "$network_log" ]] + +upgrade_root="$test_root/upgrade" +mkdir -p "$upgrade_root/root/easytier" "$upgrade_root/systemctl-state" +printf '#!/usr/bin/env bash\nprintf "easytier-core 9.9.9-test\\n"\n' > "$upgrade_root/root/easytier/easytier-core" +printf '#!/usr/bin/env bash\nprintf "easytier-cli 9.9.9-test\\n"\n' > "$upgrade_root/root/easytier/easytier-cli" +chmod 0755 "$upgrade_root/root/easytier/easytier-core" "$upgrade_root/root/easytier/easytier-cli" +write_legacy_mesh_configuration "$upgrade_root" +old_mesh_env_hash=$(sha256sum "$upgrade_root/etc/behify-easymesh/mesh.env" | awk '{print $1}') +touch "$upgrade_root/systemctl-state/active" "$upgrade_root/systemctl-state/enabled" +run_installer "$upgrade_root" env +"$upgrade_root/root/easytier/easytier-core" --version | grep -Fq '2.6.4' +[[ -f "$upgrade_root/systemctl-state/active" && -f "$upgrade_root/systemctl-state/enabled" ]] +grep -Fq 'stop easymesh.service' "$upgrade_root/systemctl-state/calls.log" +grep -Fq 'start easymesh.service' "$upgrade_root/systemctl-state/calls.log" +[[ "$(sha256sum "$upgrade_root/etc/behify-easymesh/mesh.env" | awk '{print $1}')" == "$old_mesh_env_hash" ]] +if [[ "$(uname -s)" == "Linux" ]]; then + [[ "$(stat -c '%a' "$upgrade_root/etc/behify-easymesh/easytier.toml")" == '600' ]] +fi +grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' "$upgrade_root/etc/systemd/system/easymesh.service" +grep -Fq -- '--console-log-level warn' "$upgrade_root/etc/systemd/system/easymesh.service" +! grep -Fq -- '--network-secret' "$upgrade_root/etc/systemd/system/easymesh.service" +! grep -Fq 'BehifyRc2InstallerSecretMarker' "$upgrade_root/etc/systemd/system/easymesh.service" "$upgrade_root/etc/behify-easymesh/easytier.toml" +if grep -Eq '^(enable|disable) easymesh\.service$' "$upgrade_root/systemctl-state/calls.log"; then + printf 'Installer changed service enablement.\n' >&2 + exit 1 +fi +find "$upgrade_root/opt/behify-easymesh-backups" -type f -name easytier-core -print -quit | grep -q . + +make_rollback_root() { + local root="$1" + mkdir -p "$root/root/easytier" "$root/systemctl-state" "$root/opt/behify-easymesh" + printf '#!/usr/bin/env bash\nprintf "easytier-core 8.8.8-old\\n"\n' > "$root/root/easytier/easytier-core" + printf '#!/usr/bin/env bash\nprintf "easytier-cli 8.8.8-old\\n"\n' > "$root/root/easytier/easytier-cli" + chmod 0755 "$root/root/easytier/easytier-core" "$root/root/easytier/easytier-cli" + write_legacy_mesh_configuration "$root" + printf 'preserve application\n' > "$root/opt/behify-easymesh/sentinel" + touch "$root/systemctl-state/active" "$root/systemctl-state/enabled" +} + +partial_root="$test_root/partial" +make_rollback_root "$partial_root" +old_core_hash=$(sha256sum "$partial_root/root/easytier/easytier-core" | awk '{print $1}') +chmod 0711 "$partial_root/root/easytier/easytier-core" +old_core_mode=$(stat -c '%a' "$partial_root/root/easytier/easytier-core") +if run_installer "$partial_root" env BEHIFY_TEST_FAIL_AFTER_CORE=1 >"$test_root/partial.log" 2>&1; then + printf 'Forced partial activation unexpectedly succeeded.\n' >&2 + exit 1 +fi +[[ "$(sha256sum "$partial_root/root/easytier/easytier-core" | awk '{print $1}')" == "$old_core_hash" ]] +[[ "$(stat -c '%a' "$partial_root/root/easytier/easytier-core")" == "$old_core_mode" ]] +[[ -f "$partial_root/opt/behify-easymesh/sentinel" ]] +[[ -f "$partial_root/systemctl-state/active" && -f "$partial_root/systemctl-state/enabled" ]] +grep -Fq 'Rollback completed' "$test_root/partial.log" + +if [[ "$(uname -s)" == "Linux" ]]; then + migration_failure_root="$test_root/migration-failure" + make_rollback_root "$migration_failure_root" + chmod 0644 "$migration_failure_root/etc/behify-easymesh/mesh.env" + old_core_hash=$(sha256sum "$migration_failure_root/root/easytier/easytier-core" | awk '{print $1}') + old_service_hash=$(sha256sum "$migration_failure_root/etc/systemd/system/easymesh.service" | awk '{print $1}') + if run_installer "$migration_failure_root" env >"$test_root/migration-failure.log" 2>&1; then + printf 'Unsafe mesh environment permissions were accepted for migration.\n' >&2 + exit 1 + fi + grep -Fq 'Managed mesh.env must be mode 0600 before migration.' "$test_root/migration-failure.log" + grep -Fq 'Rollback completed' "$test_root/migration-failure.log" + [[ "$(sha256sum "$migration_failure_root/root/easytier/easytier-core" | awk '{print $1}')" == "$old_core_hash" ]] + [[ "$(sha256sum "$migration_failure_root/etc/systemd/system/easymesh.service" | awk '{print $1}')" == "$old_service_hash" ]] + [[ -f "$migration_failure_root/systemctl-state/active" && -f "$migration_failure_root/systemctl-state/enabled" ]] + [[ -f "$migration_failure_root/opt/behify-easymesh/sentinel" ]] +fi + +symlink_root="$test_root/symlink-rollback" +make_rollback_root "$symlink_root" +mv "$symlink_root/root/easytier/easytier-core" "$symlink_root/root/easytier/legacy-core" +mv "$symlink_root/root/easytier/easytier-cli" "$symlink_root/root/easytier/legacy-cli" +if ln -s legacy-core "$symlink_root/root/easytier/easytier-core" 2>/dev/null && + ln -s legacy-cli "$symlink_root/root/easytier/easytier-cli" 2>/dev/null && + [[ -L "$symlink_root/root/easytier/easytier-core" ]]; then + if run_installer "$symlink_root" env BEHIFY_TEST_FAIL_AFTER_CORE=1 >"$test_root/symlink.log" 2>&1; then + printf 'Symlink rollback failure injection unexpectedly succeeded.\n' >&2 + exit 1 + fi + [[ -L "$symlink_root/root/easytier/easytier-core" && -L "$symlink_root/root/easytier/easytier-cli" ]] + [[ "$(readlink "$symlink_root/root/easytier/easytier-core")" == 'legacy-core' ]] + [[ "$(readlink "$symlink_root/root/easytier/easytier-cli")" == 'legacy-cli' ]] +else + printf 'Symlink rollback check skipped: native symlinks are unavailable.\n' +fi + +service_failure_root="$test_root/service-failure" +make_rollback_root "$service_failure_root" +old_core_hash=$(sha256sum "$service_failure_root/root/easytier/easytier-core" | awk '{print $1}') +old_service_hash=$(sha256sum "$service_failure_root/etc/systemd/system/easymesh.service" | awk '{print $1}') +old_mesh_env_hash=$(sha256sum "$service_failure_root/etc/behify-easymesh/mesh.env" | awk '{print $1}') +touch "$service_failure_root/systemctl-state/fail-new-runtime" +if run_installer "$service_failure_root" env >"$test_root/service-failure.log" 2>&1; then + printf 'Forced service validation failure unexpectedly succeeded.\n' >&2 + exit 1 +fi +[[ "$(sha256sum "$service_failure_root/root/easytier/easytier-core" | awk '{print $1}')" == "$old_core_hash" ]] +[[ -f "$service_failure_root/systemctl-state/active" && -f "$service_failure_root/systemctl-state/enabled" ]] +[[ -f "$service_failure_root/opt/behify-easymesh/sentinel" ]] +[[ "$(sha256sum "$service_failure_root/etc/systemd/system/easymesh.service" | awk '{print $1}')" == "$old_service_hash" ]] +[[ "$(sha256sum "$service_failure_root/etc/behify-easymesh/mesh.env" | awk '{print $1}')" == "$old_mesh_env_hash" ]] +[[ ! -e "$service_failure_root/etc/behify-easymesh/easytier.toml" ]] + +inactive_root="$test_root/inactive" +mkdir -p "$inactive_root/systemctl-state" +write_legacy_mesh_configuration "$inactive_root" +run_installer "$inactive_root" env +[[ ! -f "$inactive_root/systemctl-state/active" && ! -f "$inactive_root/systemctl-state/enabled" ]] +grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' "$inactive_root/etc/systemd/system/easymesh.service" +if grep -Eq '^(start|stop|enable|disable) easymesh\.service$' "$inactive_root/systemctl-state/calls.log"; then + printf 'Installer changed an inactive/disabled service state.\n' >&2 + exit 1 +fi + +command_root="$test_root/command-owner" +mkdir -p "$command_root/usr/local/bin" "$command_root/systemctl-state" +printf 'unrelated command\n' > "$command_root/usr/local/bin/easymesh" +run_installer "$command_root" env +grep -Fqx '# Behify EasyMesh test command link' "$command_root/usr/local/bin/easymesh" +find "$command_root/opt/behify-easymesh-backups" -type f -name command-easymesh -exec grep -Fq 'unrelated command' {} \; + +printf 'Installer regression checks passed.\n' diff --git a/tests/license-integrity-regression.sh b/tests/license-integrity-regression.sh new file mode 100644 index 0000000..64c1452 --- /dev/null +++ b/tests/license-integrity-regression.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +readonly expected_baseline_sha="b03be037e53e635e10dd8050fa2bdad637962f50" +temp_root=$(mktemp -d) +trap 'rm -rf "$temp_root"' EXIT + +copy_license_integrity_helper() { + local target_repo=$1 + cp "$repo_root/tests/license-integrity.sh" "$target_repo/tests/license-integrity.sh" +} + +run_license_integrity() { + local target_repo=$1 + ( + cd "$target_repo" + source tests/license-integrity.sh + verify_license_notice_integrity + ) +} + +grep -Fqx 'source tests/license-integrity.sh' "$repo_root/tests/static-smoke.sh" + +full_repo="$temp_root/full-history" +git clone --quiet --no-local "$repo_root" "$full_repo" +copy_license_integrity_helper "$full_repo" + +if git -C "$full_repo" show-ref --verify --quiet refs/heads/main; then + git -C "$full_repo" branch -D main >/dev/null +fi + +if git -C "$full_repo" show-ref --verify --quiet refs/heads/main; then + printf 'Expected regression fixture to have no local main branch.\n' >&2 + exit 1 +fi + +git -C "$full_repo" cat-file -e "${expected_baseline_sha}^{commit}" +run_license_integrity "$full_repo" + +printf '\nregression change\n' >> "$full_repo/LICENSE" +if run_license_integrity "$full_repo" >"$temp_root/license-change.out" 2>&1; then + printf 'Expected a changed LICENSE to fail the integrity check.\n' >&2 + exit 1 +fi + +grep -Fq "LICENSE or NOTICE changed from immutable baseline $expected_baseline_sha." \ + "$temp_root/license-change.out" || { + printf 'Changed LICENSE did not produce the expected integrity diagnostic.\n' >&2 + exit 1 +} + +missing_baseline_repo="$temp_root/missing-baseline" +git init --quiet "$missing_baseline_repo" +mkdir -p "$missing_baseline_repo/tests" +cp "$repo_root/LICENSE" "$repo_root/NOTICE" "$missing_baseline_repo/" +copy_license_integrity_helper "$missing_baseline_repo" + +if git -C "$missing_baseline_repo" cat-file -e "${expected_baseline_sha}^{commit}" 2>/dev/null; then + printf 'Expected missing-baseline regression fixture to omit the immutable baseline.\n' >&2 + exit 1 +fi + +if run_license_integrity "$missing_baseline_repo" >"$temp_root/missing-baseline.out" 2>&1; then + printf 'Expected a missing baseline to fail the integrity check.\n' >&2 + exit 1 +fi + +grep -Fq "License integrity baseline is unavailable: $expected_baseline_sha" \ + "$temp_root/missing-baseline.out" || { + printf 'Missing baseline did not produce the expected diagnostic.\n' >&2 + exit 1 +} + +printf 'License integrity regression checks passed.\n' diff --git a/tests/license-integrity.sh b/tests/license-integrity.sh new file mode 100644 index 0000000..d5b686f --- /dev/null +++ b/tests/license-integrity.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +readonly LICENSE_BASELINE_SHA="b03be037e53e635e10dd8050fa2bdad637962f50" + +verify_license_notice_integrity() { + if ! git cat-file -e "${LICENSE_BASELINE_SHA}^{commit}" 2>/dev/null; then + printf 'License integrity baseline is unavailable: %s\n' "$LICENSE_BASELINE_SHA" >&2 + printf 'Fetch the immutable baseline before running this check.\n' >&2 + return 1 + fi + + if git diff --quiet "$LICENSE_BASELINE_SHA" -- LICENSE NOTICE; then + return 0 + else + local diff_status=$? + fi + + if [[ "$diff_status" -eq 1 ]]; then + printf 'LICENSE or NOTICE changed from immutable baseline %s.\n' "$LICENSE_BASELINE_SHA" >&2 + else + printf 'Could not compare LICENSE and NOTICE against immutable baseline %s.\n' "$LICENSE_BASELINE_SHA" >&2 + fi + return 1 +} diff --git a/tests/make-test-package.sh b/tests/make-test-package.sh new file mode 100644 index 0000000..0648d7c --- /dev/null +++ b/tests/make-test-package.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +destination="${1:?destination is required}" +architecture="${2:-x86_64}" +. "$repo_root/versions.env" + +rm -rf -- "$destination" +mkdir -p "$destination/core" +for file in easymesh install.sh uninstall.sh relay-manager versions.env README.md README.fa.md CHANGELOG.md SECURITY.md THIRD_PARTY_NOTICES.md LICENSE NOTICE; do + cp -p "$repo_root/$file" "$destination/$file" +done +cp -a "$repo_root/docs" "$repo_root/licenses" "$destination/" + +cat > "$destination/core/easytier-core" <<'EOF' +#!/usr/bin/env bash +printf 'easytier-core 2.6.4-test\n' +EOF +cat > "$destination/core/easytier-cli" <<'EOF' +#!/usr/bin/env bash +printf 'easytier-cli 2.6.4-test\n' +EOF +chmod 0755 "$destination/easymesh" "$destination/install.sh" "$destination/uninstall.sh" \ + "$destination/relay-manager" "$destination/core/easytier-core" "$destination/core/easytier-cli" + +core_hash=$(sha256sum "$destination/core/easytier-core" | awk '{print $1}') +cli_hash=$(sha256sum "$destination/core/easytier-cli" | awk '{print $1}') +: > "$destination/files.sha256" +while IFS= read -r relative_file; do + (cd "$destination" && sha256sum "$relative_file") >> "$destination/files.sha256" +done < <(cd "$destination" && find . -type f ! -name manifest.txt ! -name files.sha256 -print | LC_ALL=C sort) +files_hash=$(sha256sum "$destination/files.sha256" | awk '{print $1}') +cat > "$destination/manifest.txt" <&2 + exit 1 +fi +many_peers='10.0.0.1' +for ((i = 0; i < 64; i++)); do + many_peers+=',10.0.0.1' +done +if build_peer_arguments "$many_peers" udp 2090; then + printf 'Oversized peer list was accepted.\n' >&2 + exit 1 +fi + +IP_ADDRESS='10.144.144.1' +PEER_ARGUMENTS='--peers udp://10.144.144.2:2090' +HOSTNAME='server-1' +NETWORK_SECRET='BehifyRc2SecretMarker-123' +DEFAULT_PROTOCOL='udp' +LISTENERS='--listeners udp://[::]:2090 udp://0.0.0.0:2090' +MULTI_THREAD_OPTION='--multi-thread' +ENCRYPTION_OPTION='' +IPV6_OPTION='' +write_mesh_environment "$test_root/mesh.env" +write_mesh_core_config "$test_root/easytier.toml" +write_mesh_service "$test_root/easymesh.service" + +[[ "$(stat -c '%a' "$test_root/mesh.env")" == '600' ]] +[[ "$(stat -c '%a' "$test_root/easytier.toml")" == '600' ]] +grep -Fq 'EASYMESH_NETWORK_SECRET="BehifyRc2SecretMarker-123"' "$test_root/mesh.env" +grep -Fqx 'network_secret = "${EASYMESH_NETWORK_SECRET}"' "$test_root/easytier.toml" +grep -Fqx 'EnvironmentFile=/etc/behify-easymesh/mesh.env' "$test_root/easymesh.service" +grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' "$test_root/easymesh.service" +grep -Fq -- '--console-log-level warn' "$test_root/easymesh.service" +if grep -Fq 'BehifyRc2SecretMarker-123' "$test_root/easymesh.service" "$test_root/easytier.toml"; then + printf 'Mesh secret leaked into the service unit.\n' >&2 + exit 1 +fi +if grep -Fq -- '--network-secret' "$test_root/easymesh.service"; then + printf 'Mesh secret remains exposed through the service command line.\n' >&2 + exit 1 +fi +generated_secret=$(generate_random_secret) +[[ "$generated_secret" =~ ^[0-9a-f]{32}$ ]] +if grep -Eq '(^|[[:space:]])eval([[:space:]]|$)' "$repo_root/easymesh"; then + printf 'Mesh manager contains eval.\n' >&2 + exit 1 +fi +resolve_release_architecture x86_64 | grep -Fxq x86_64 +resolve_release_architecture aarch64 | grep -Fxq aarch64 +if resolve_release_architecture armv7l >/dev/null; then + printf 'Unsupported ARMv7 architecture was accepted.\n' >&2 + exit 1 +fi + +printf 'Mesh configuration security checks passed.\n' diff --git a/tests/mesh-config-transaction.sh b/tests/mesh-config-transaction.sh new file mode 100644 index 0000000..77d073f --- /dev/null +++ b/tests/mesh-config-transaction.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +export EASYMESH_SOURCE_ONLY=1 +# shellcheck source=../easymesh +source "$repo_root/easymesh" +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-mesh-transaction.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT + +MESH_CONFIG_DIR="$test_root/etc/behify-easymesh" +MESH_ENV_FILE="$MESH_CONFIG_DIR/mesh.env" +MESH_CORE_CONFIG="$MESH_CONFIG_DIR/easytier.toml" +SERVICE_FILE="$test_root/etc/systemd/system/easymesh.service" +state_dir="$test_root/state" +mkdir -p "$MESH_CONFIG_DIR/backups" "$(dirname "$SERVICE_FILE")" "$state_dir" +chmod 0700 "$MESH_CONFIG_DIR" "$MESH_CONFIG_DIR/backups" + +printf 'OLD_ENV=preserve\n' > "$MESH_ENV_FILE" +printf 'OLD_CONFIG=preserve\n' > "$MESH_CORE_CONFIG" +printf '%s\n' '# Managed by Behify EasyMesh' 'Description=OLD_SERVICE' > "$SERVICE_FILE" +printf 'NEW_ENV=activate\n' > "$test_root/new.env" +printf 'NEW_CONFIG=activate\n' > "$test_root/new.toml" +printf '%s\n' '# Managed by Behify EasyMesh' 'Description=NEW_SERVICE' > "$test_root/new.service" +touch "$state_dir/active" "$state_dir/enabled" + +systemctl() { + printf '%s\n' "$*" >> "$state_dir/calls.log" + case "${1:-}" in + is-active) [[ -f "$state_dir/active" ]] ;; + is-enabled) [[ -f "$state_dir/enabled" ]] ;; + stop) rm -f "$state_dir/active" ;; + start) + if [[ -f "$state_dir/fail-new" ]] && grep -Fq 'NEW_SERVICE' "$SERVICE_FILE"; then + return 1 + fi + touch "$state_dir/active" + ;; + enable) touch "$state_dir/enabled" ;; + disable) rm -f "$state_dir/enabled" ;; + daemon-reload) ;; + *) ;; + esac +} + +touch "$state_dir/fail-new" +if activate_mesh_configuration "$test_root/new.env" "$test_root/new.toml" "$test_root/new.service"; then + printf 'Forced mesh service failure unexpectedly succeeded.\n' >&2 + exit 1 +fi +grep -Fqx 'OLD_ENV=preserve' "$MESH_ENV_FILE" +grep -Fqx 'OLD_CONFIG=preserve' "$MESH_CORE_CONFIG" +grep -Fq 'Description=OLD_SERVICE' "$SERVICE_FILE" +[[ -f "$state_dir/active" && -f "$state_dir/enabled" ]] +find "$MESH_CONFIG_DIR/backups" -type f -name mesh.env -print -quit | grep -q . +find "$MESH_CONFIG_DIR/backups" -type f -name easytier.toml -print -quit | grep -q . + +rm -f "$state_dir/fail-new" +activate_mesh_configuration "$test_root/new.env" "$test_root/new.toml" "$test_root/new.service" +grep -Fqx 'NEW_ENV=activate' "$MESH_ENV_FILE" +grep -Fqx 'NEW_CONFIG=activate' "$MESH_CORE_CONFIG" +grep -Fq 'Description=NEW_SERVICE' "$SERVICE_FILE" +[[ -f "$state_dir/active" && -f "$state_dir/enabled" ]] + +printf '%s\n' '[Service]' 'ExecStart=/usr/bin/unrelated' > "$SERVICE_FILE" +cp "$SERVICE_FILE" "$test_root/unrelated.before" +if activate_mesh_configuration "$test_root/new.env" "$test_root/new.toml" "$test_root/new.service"; then + printf 'Unrecognized service file was overwritten.\n' >&2 + exit 1 +fi +cmp -s "$SERVICE_FILE" "$test_root/unrelated.before" + +printf 'Mesh configuration transaction checks passed.\n' diff --git a/tests/mesh-interactive-regression.sh b/tests/mesh-interactive-regression.sh new file mode 100644 index 0000000..444d06d --- /dev/null +++ b/tests/mesh-interactive-regression.sh @@ -0,0 +1,112 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +export EASYMESH_SOURCE_ONLY=1 +# shellcheck source=../easymesh +source "$repo_root/easymesh" +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-mesh-interactive.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT + +generated_marker=0123456789abcdef0123456789abcdef +custom_marker=CustomSecret-Rc3-Marker +generate_random_secret() { + printf '%s\n' "$generated_marker" +} + +NETWORK_SECRET='' +prompt_network_secret <<< '' > "$test_root/generated.out" 2>&1 +[[ "$NETWORK_SECRET" == "$generated_marker" ]] +[[ "$(grep -Foc "$generated_marker" "$test_root/generated.out")" == '1' ]] +grep -Fqx 'Generated Network Secret:' "$test_root/generated.out" +styled_secret=$(printf '\033[1m\033[36m%s\033[0m' "$generated_marker") +grep -Fqx "$styled_secret" "$test_root/generated.out" + +NETWORK_SECRET='' +prompt_network_secret <<< "$custom_marker" > "$test_root/custom.out" 2>&1 +[[ "$NETWORK_SECRET" == "$custom_marker" ]] +! grep -Fq "$custom_marker" "$test_root/custom.out" + +secret_prompt_source=$(sed -n '/^prompt_network_secret()/,/^}/p' "$repo_root/easymesh") +[[ "$(grep -Ec '^[[:space:]]*read[[:space:]]' <<< "$secret_prompt_source")" == '1' ]] +! grep -Eq 'read[[:space:]].*-[^[:space:]]*s([^[:alnum:]_]|$)' <<< "$secret_prompt_source" +grep -Fq 'Press Enter to use it, or enter a custom secret: ' <<< "$secret_prompt_source" +! grep -Fq 'Use this secret?' <<< "$secret_prompt_source" + +read_arguments='' +read_value='' +read() { + read_arguments="$*" + printf -v "${!#}" '%s' "$read_value" +} +read_value=$custom_marker +NETWORK_SECRET='' +prompt_network_secret > "$test_root/visible-custom.out" 2>&1 +[[ "$NETWORK_SECRET" == "$custom_marker" ]] +[[ " $read_arguments " != *' -s '* ]] +[[ "$read_arguments" == *'Press Enter to use it, or enter a custom secret: '* ]] +unset -f read + +EASYMESH_SOURCE_ONLY=0 +NETWORK_SECRET='unchanged' +if prompt_network_secret <<< '' > "$test_root/noninteractive-generate.out" 2>&1; then + printf 'Non-interactive generated-secret display was accepted.\n' >&2 + exit 1 +fi +[[ "$NETWORK_SECRET" == 'unchanged' ]] +! grep -Fq "$generated_marker" "$test_root/noninteractive-generate.out" +EASYMESH_SOURCE_ONLY=1 + +ENCRYPTION_OPTION=unset +prompt_encryption_option <<< '' >/dev/null +[[ -z "$ENCRYPTION_OPTION" ]] +ENCRYPTION_OPTION=unset +prompt_encryption_option <<< 'yes' >/dev/null +[[ -z "$ENCRYPTION_OPTION" ]] +ENCRYPTION_OPTION=unset +prompt_encryption_option < <(printf 'no\nyes\n') >/dev/null +[[ "$ENCRYPTION_OPTION" == '--disable-encryption' ]] +ENCRYPTION_OPTION=unset +prompt_encryption_option < <(printf 'no\nno\n') >/dev/null +[[ -z "$ENCRYPTION_OPTION" ]] + +MULTI_THREAD_OPTION=unset +prompt_multi_thread_option <<< '' >/dev/null +[[ -z "$MULTI_THREAD_OPTION" ]] +MULTI_THREAD_OPTION=unset +prompt_multi_thread_option <<< 'no' >/dev/null +[[ -z "$MULTI_THREAD_OPTION" ]] +MULTI_THREAD_OPTION=unset +prompt_multi_thread_option <<< 'yes' >/dev/null +[[ "$MULTI_THREAD_OPTION" == '--multi-thread' ]] + +grep -Fq 'case "$IPV6_CHOICE"' "$repo_root/easymesh" +grep -Fq '[Nn]*) IPV6_OPTION="--disable-ipv6"' "$repo_root/easymesh" +grep -Fq '*) IPV6_OPTION=""' "$repo_root/easymesh" + +MESH_ENV_FILE="$test_root/mesh.env" +printf 'EASYMESH_NETWORK_SECRET="%s"\n' "$custom_marker" > "$MESH_ENV_FILE" +show_network_secret <<< '' > "$test_root/reveal.out" 2>&1 +[[ "$(grep -Foc "$custom_marker" "$test_root/reveal.out")" == '1' ]] +EASYMESH_SOURCE_ONLY=0 +if show_network_secret <<< '' > "$test_root/noninteractive-reveal.out" 2>&1; then + printf 'Non-interactive stored-secret display was accepted.\n' >&2 + exit 1 +fi +! grep -Fq "$custom_marker" "$test_root/noninteractive-reveal.out" +EASYMESH_SOURCE_ONLY=1 + +clear() { :; } +get_main_core_version() { printf '2.6.4\n'; } +get_service_status() { printf 'inactive\n'; } +display_menu > "$test_root/menu.out" +[[ "$(grep -Fc 'EasyTier:' "$test_root/menu.out")" == '1' ]] +! grep -Fq 'Installed:' "$test_root/menu.out" +! grep -Fq "$custom_marker" "$test_root/menu.out" + +connect_source=$(sed -n '/^connect_network_pool()/,/^}/p' "$repo_root/easymesh") +grep -Fq 'Tip: leave Peer blank for reverse mode; UDP is recommended.' <<< "$connect_source" +! grep -Fq "Ws and wss modes are not recommended" <<< "$connect_source" + +printf 'Mesh interactive regression checks passed.\n' diff --git a/tests/mock-systemctl.sh b/tests/mock-systemctl.sh new file mode 100644 index 0000000..c6ff076 --- /dev/null +++ b/tests/mock-systemctl.sh @@ -0,0 +1,42 @@ +#!/usr/bin/env bash + +set -u + +state_dir=${MOCK_SYSTEMCTL_STATE:?} +mkdir -p "$state_dir" +printf '%s\n' "$*" >> "$state_dir/calls.log" + +case "${1:-}" in + is-active) + if [[ -f "$state_dir/transient-new-runtime-failure" ]]; then + rm -f "$state_dir/transient-new-runtime-failure" "$state_dir/active" + exit 1 + fi + [[ -f "$state_dir/active" ]] + ;; + is-enabled) + [[ -f "$state_dir/enabled" ]] + ;; + stop) + rm -f "$state_dir/active" + ;; + start|restart) + if [[ -f "$state_dir/fail-new-runtime" ]] && grep -Fq '2.6.4' "$MOCK_INSTALL_ROOT/root/easytier/easytier-core" 2>/dev/null; then + touch "$state_dir/transient-new-runtime-failure" + fi + touch "$state_dir/active" + ;; + enable) + touch "$state_dir/enabled" + ;; + disable) + rm -f "$state_dir/enabled" + ;; + daemon-reload) + ;; + show) + printf '12345\n' + ;; + *) + ;; +esac diff --git a/tests/native-package-smoke.sh b/tests/native-package-smoke.sh new file mode 100644 index 0000000..b228c03 --- /dev/null +++ b/tests/native-package-smoke.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +output_dir="${1:?release output directory is required}" +architecture="${2:?architecture is required}" +. "$repo_root/versions.env" + +case "$(uname -m):$architecture" in + x86_64:x86_64|amd64:x86_64|aarch64:aarch64|arm64:aarch64) ;; + *) + printf 'Native runner architecture %s does not match package %s.\n' "$(uname -m)" "$architecture" >&2 + exit 1 + ;; +esac + +archive="$output_dir/behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-${architecture}.tar.gz" +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-native-package.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT +tar -xzf "$archive" -C "$test_root" +package_dir="$test_root/behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-${architecture}" + +"$package_dir/core/easytier-core" --version | grep -Fq '2.6.4' +"$package_dir/core/easytier-cli" --version | grep -Fq '2.6.4' + +mock_bin="$test_root/network-bin" +install_root="$test_root/rootfs" +mkdir -p "$mock_bin" "$install_root/systemctl-state" +cp "$repo_root/tests/mock-systemctl.sh" "$mock_bin/systemctl" +for command_name in curl wget; do + cat > "$mock_bin/$command_name" <<'EOF' +#!/usr/bin/env bash +printf 'Unexpected network command during offline installation.\n' >&2 +exit 99 +EOF +done +chmod 0755 "$mock_bin/systemctl" "$mock_bin/curl" "$mock_bin/wget" + +for pass in 1 2; do + MOCK_SYSTEMCTL_STATE="$install_root/systemctl-state" \ + MOCK_INSTALL_ROOT="$install_root" \ + BEHIFY_TEST_ROOT="$install_root" \ + BEHIFY_TEST_MODE=1 \ + BEHIFY_TEST_ARCH="$architecture" \ + BEHIFY_TEST_VALIDATE_ELF=1 \ + BEHIFY_TEST_NATIVE_LINK=1 \ + EASYMESH_OFFLINE=1 \ + PATH="$mock_bin:$PATH" \ + bash "$package_dir/install.sh" +done + +"$install_root/root/easytier/easytier-core" --version | grep -Fq '2.6.4' +"$install_root/root/easytier/easytier-cli" --version | grep -Fq '2.6.4' +[[ -L "$install_root/usr/local/bin/easymesh" ]] +[[ "$(readlink "$install_root/usr/local/bin/easymesh")" == '/opt/behify-easymesh/easymesh' ]] +printf 'Native %s package and strict-offline installation checks passed.\n' "$architecture" diff --git a/tests/neutral-runtime-regression.sh b/tests/neutral-runtime-regression.sh index 4fdc722..8ba5e88 100644 --- a/tests/neutral-runtime-regression.sh +++ b/tests/neutral-runtime-regression.sh @@ -28,6 +28,15 @@ RELAY_KNOWN_NEUTRAL_DROPIN="$RELAY_SERVICE_DROPIN_DIR/10-neutral-binary.conf" execution_log="$test_root/executed-runtime-paths.log" export execution_log +simulate_staged_copy_failure=0 +install() { + if [[ "$simulate_staged_copy_failure" == "1" && "${1:-}" == "-m" && \ + "${2:-}" == "0755" && "${3:-}" == "$RELAY_LEGACY_XRAY_BINARY" ]]; then + return 1 + fi + command install "$@" +} + install -d "$RELAY_LEGACY_XRAY_CURRENT" "$RELAY_CONFIG_DIR" \ "$(dirname "$RELAY_SERVICE_FILE")" "$RELAY_SERVICE_DROPIN_DIR" cat > "$RELAY_LEGACY_XRAY_BINARY" <<'FAKE_XRAY' @@ -117,7 +126,7 @@ fi [[ -f "$RELAY_SERVICE_DROPIN_DIR/99-admin.conf" ]] find "$RELAY_RELEASES_DIR" -type f -name behify-relayd -print -quit | grep -q . grep -Fq 'XRAY_CANDIDATE_BINARY="$temp_dir/unpacked/behify-relayd"' "$repo_root/relay-manager" -grep -Fq '"$INSTALL_DIR/relay/relay-manager" --migrate-runtime' "$repo_root/install.sh" +grep -Fq '"$WORK_DIR/application/relay/relay-manager" --migrate-runtime' "$repo_root/install.sh" if find "$RELAY_RELEASES_DIR" -type f -name xray -print -quit | grep -q .; then printf 'Update or migration staging retained an xray runtime basename.\n' >&2 exit 1 @@ -313,14 +322,9 @@ RELAY_KNOWN_NEUTRAL_DROPIN="$RELAY_SERVICE_DROPIN_DIR/10-neutral-binary.conf" command install -d "$RELAY_LEGACY_XRAY_CURRENT" command cp "$validation_candidate" "$RELAY_LEGACY_XRAY_BINARY" command chmod 0755 "$RELAY_LEGACY_XRAY_BINARY" -install() { - if [[ "${1:-}" == "-m" && "${2:-}" == "0755" && "${3:-}" == "$RELAY_LEGACY_XRAY_BINARY" ]]; then - return 1 - fi - command install "$@" -} +simulate_staged_copy_failure=1 expect_validation_failure 'Staged copy failed' migrate_legacy_relay_runtime -unset -f install +simulate_staged_copy_failure=0 rollback_root="$test_root/rollback" RELAY_ROOT="$rollback_root/opt/behify-easymesh/relay" diff --git a/tests/online-installer-failure.sh b/tests/online-installer-failure.sh new file mode 100644 index 0000000..725cf98 --- /dev/null +++ b/tests/online-installer-failure.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +. "$repo_root/versions.env" +installer="${1:-$repo_root/release-output/online-install-v${BEHIFY_EASYMESH_VERSION}.sh}" +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-online-test.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT +mock_bin="$test_root/bin" +mkdir -p "$mock_bin" + +cat > "$mock_bin/curl" <<'EOF' +#!/usr/bin/env bash +exit 55 +EOF +chmod 0755 "$mock_bin/curl" +if BEHIFY_TEST_MODE=1 BEHIFY_TEST_ARCH=x86_64 PATH="$mock_bin:$PATH" \ + bash "$installer" >"$test_root/download.log" 2>&1; then + printf 'Online installer accepted a failed package download.\n' >&2 + exit 1 +fi +grep -Fq 'Package download failed. The existing installation was not changed.' "$test_root/download.log" + +cat > "$mock_bin/curl" <<'EOF' +#!/usr/bin/env bash +output='' +while [[ $# -gt 0 ]]; do + if [[ "$1" == '--output' ]]; then + output="$2" + shift 2 + else + shift + fi +done +printf 'corrupt archive\n' > "$output" +EOF +cat > "$mock_bin/tar" <> '$test_root/tar.log' +exit 91 +EOF +chmod 0755 "$mock_bin/curl" "$mock_bin/tar" +if BEHIFY_TEST_MODE=1 BEHIFY_TEST_ARCH=x86_64 PATH="$mock_bin:$PATH" \ + bash "$installer" >"$test_root/checksum.log" 2>&1; then + printf 'Online installer accepted a corrupt package.\n' >&2 + exit 1 +fi +grep -Fq 'Package SHA-256 verification failed. The archive was not extracted.' "$test_root/checksum.log" +[[ ! -e "$test_root/tar.log" ]] + +if BEHIFY_TEST_MODE=1 BEHIFY_TEST_ARCH=armv7l PATH="$mock_bin:$PATH" \ + bash "$installer" >"$test_root/architecture.log" 2>&1; then + printf 'Online installer accepted an unsupported architecture.\n' >&2 + exit 1 +fi +grep -Fq 'supports only Linux x86_64 and aarch64' "$test_root/architecture.log" + +printf 'Online installer failure checks passed.\n' diff --git a/tests/release-package-regression.sh b/tests/release-package-regression.sh new file mode 100644 index 0000000..c651f2e --- /dev/null +++ b/tests/release-package-regression.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +output_dir="${1:-$repo_root/release-output}" +. "$repo_root/versions.env" +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-package-test.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT + +[[ -f "$output_dir/SHA256SUMS" ]] +(cd "$output_dir" && sha256sum --quiet -c SHA256SUMS) + +expected_assets=$(printf '%s\n' \ + "SHA256SUMS" \ + "behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-aarch64.tar.gz" \ + "behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-x86_64.tar.gz" \ + "easytier-v2.6.4-source.tar.gz" \ + "install.sh" \ + "online-install-v${BEHIFY_EASYMESH_VERSION}.sh") +actual_assets=$(find "$output_dir" -mindepth 1 -maxdepth 1 -type f -printf '%f\n' | LC_ALL=C sort) +[[ "$actual_assets" == "$expected_assets" ]] || { + printf 'Release output does not contain the exact expected asset set.\n' >&2 + diff -u <(printf '%s\n' "$expected_assets") <(printf '%s\n' "$actual_assets") >&2 || true + exit 1 +} + +manifest_value() { + local manifest="$1" key="$2" + awk -F= -v key="$key" '$1 == key { print substr($0, index($0, "=") + 1) }' "$manifest" +} + +verify_package() { + local architecture="$1" expected_machine expected_core_hash expected_cli_hash + local package_name="behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-$architecture" + local archive="$output_dir/$package_name.tar.gz" destination="$test_root/$architecture" + local package_dir manifest machine core_count + + mkdir -p "$destination" + tar -xzf "$archive" -C "$destination" + package_dir="$destination/$package_name" + manifest="$package_dir/manifest.txt" + [[ -f "$package_dir/install.sh" && -f "$package_dir/uninstall.sh" ]] + [[ -f "$package_dir/LICENSE" && -f "$package_dir/NOTICE" ]] + [[ -f "$package_dir/THIRD_PARTY_NOTICES.md" ]] + [[ -f "$package_dir/licenses/EasyTier-LGPL-3.0.txt" ]] + [[ -f "$package_dir/licenses/Xray-core-MPL-2.0.txt" ]] + [[ "$(manifest_value "$manifest" behify_easymesh_version)" == "$BEHIFY_EASYMESH_VERSION" ]] + [[ "$(manifest_value "$manifest" easytier_version)" == "$EASYTIER_VERSION" ]] + [[ "$(manifest_value "$manifest" architecture)" == "$architecture" ]] + [[ "$(sha256sum "$package_dir/files.sha256" | awk '{print $1}')" == "$(manifest_value "$manifest" files_manifest_sha256)" ]] + (cd "$package_dir" && sha256sum --quiet -c files.sha256) + expected_core_hash=$(manifest_value "$manifest" easytier_core_sha256) + expected_cli_hash=$(manifest_value "$manifest" easytier_cli_sha256) + [[ "$(sha256sum "$package_dir/core/easytier-core" | awk '{print $1}')" == "$expected_core_hash" ]] + [[ "$(sha256sum "$package_dir/core/easytier-cli" | awk '{print $1}')" == "$expected_cli_hash" ]] + case "$architecture" in + x86_64) expected_machine=62 ;; + aarch64) expected_machine=183 ;; + *) return 1 ;; + esac + machine=$(od -An -tu2 -j18 -N2 "$package_dir/core/easytier-core" | tr -d ' \n') + [[ "$machine" == "$expected_machine" ]] + machine=$(od -An -tu2 -j18 -N2 "$package_dir/core/easytier-cli" | tr -d ' \n') + [[ "$machine" == "$expected_machine" ]] + core_count=$(find "$package_dir/core" -type f | wc -l | tr -d ' ') + [[ "$core_count" == '2' ]] + if find "$package_dir" -type f \( -name xray -o -name behify-relayd \) -print -quit | grep -q .; then + printf 'Release package unexpectedly contains an Xray executable.\n' >&2 + exit 1 + fi +} + +verify_package x86_64 +verify_package aarch64 + +if find "$output_dir" -mindepth 1 -maxdepth 1 -type f \ + -name 'behify-easymesh-v*-linux-*.tar.gz' \ + ! -name "behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-x86_64.tar.gz" \ + ! -name "behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-aarch64.tar.gz" \ + -print -quit | grep -q .; then + printf 'Release output contains a stale architecture package.\n' >&2 + exit 1 +fi +if find "$output_dir" -mindepth 1 -maxdepth 1 -type f -name 'online-install-v*.sh' \ + ! -name "online-install-v${BEHIFY_EASYMESH_VERSION}.sh" -print -quit | grep -q .; then + printf 'Release output contains a stale online installer.\n' >&2 + exit 1 +fi + +x86_hash=$(sha256sum "$output_dir/behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-x86_64.tar.gz" | awk '{print $1}') +aarch64_hash=$(sha256sum "$output_dir/behify-easymesh-v${BEHIFY_EASYMESH_VERSION}-linux-aarch64.tar.gz" | awk '{print $1}') +online_installer="$output_dir/online-install-v${BEHIFY_EASYMESH_VERSION}.sh" +cmp -s "$online_installer" "$output_dir/install.sh" +grep -Eq ' [* ]install\.sh$' "$output_dir/SHA256SUMS" +grep -Fq "BEHIFY_VERSION=\"$BEHIFY_EASYMESH_VERSION\"" "$online_installer" +grep -Fq "X86_64_SHA256=\"$x86_hash\"" "$online_installer" +grep -Fq "AARCH64_SHA256=\"$aarch64_hash\"" "$online_installer" +source_hash=$(awk -F= '$1 == "EASYTIER_SOURCE_SHA256" { print $2 }' "$repo_root/release/easytier-v2.6.4-assets.env") +[[ "$(sha256sum "$output_dir/easytier-v2.6.4-source.tar.gz" | awk '{print $1}')" == "$source_hash" ]] +tar -tzf "$output_dir/easytier-v2.6.4-source.tar.gz" | sed -n '1p' | \ + grep -Fxq 'EasyTier-8428a89d2dabc94c97d370ec607c6ca142473626/' + +if git -C "$repo_root" ls-files 'core/**' | grep -q .; then + printf 'EasyTier runtime binaries remain tracked under core/.\n' >&2 + exit 1 +fi + +printf 'Release package regression checks passed.\n' diff --git a/tests/run-all.sh b/tests/run-all.sh new file mode 100644 index 0000000..a9a5668 --- /dev/null +++ b/tests/run-all.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +output_dir="${1:-$repo_root/release-output}" +. "$repo_root/versions.env" + +bash "$repo_root/tests/static-smoke.sh" +if [[ -f "$output_dir/SHA256SUMS" ]]; then + bash "$repo_root/tests/release-package-regression.sh" "$output_dir" + bash "$repo_root/tests/online-installer-failure.sh" "$output_dir/online-install-v${BEHIFY_EASYMESH_VERSION}.sh" + bash "$repo_root/tests/online-installer-failure.sh" "$output_dir/install.sh" +else + printf 'Release-output tests skipped because no built artifacts were supplied.\n' +fi diff --git a/tests/static-smoke.sh b/tests/static-smoke.sh index d2ad4ed..b6a4e5d 100644 --- a/tests/static-smoke.sh +++ b/tests/static-smoke.sh @@ -4,21 +4,28 @@ set -euo pipefail repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) cd "$repo_root" +source tests/license-integrity.sh -bash -n easymesh -bash -n relay-manager -bash -n install.sh +while IFS= read -r script; do + bash -n "$script" +done < <(find . -type f \( -name '*.sh' -o -name '*.sh.in' \) -print | LC_ALL=C sort) +bash -n easymesh relay-manager -grep -Fqx 'DEFAULT_CORE_VERSION="v2.0.3"' easymesh +grep -Fqx 'BEHIFY_EASYMESH_VERSION=1.0.0' versions.env +grep -Fqx 'EASYTIER_VERSION=v2.6.4' versions.env grep -Fqx 'SERVICE_FILE="/etc/systemd/system/easymesh.service"' easymesh grep -Fqx 'RELAY_SERVICE_NAME="behify-relay.service"' relay-manager grep -Fqx 'RELAY_BINARY="$RELAY_BIN_DIR/behify-relayd"' relay-manager -grep -Fqx 'INSTALL_DIR="/opt/behify-easymesh"' install.sh -grep -Fqx 'COMMAND_PATH="/usr/local/bin/easymesh"' install.sh - +grep -Fq 'root_path /opt/behify-easymesh' install.sh +grep -Fq 'root_path /usr/local/bin/easymesh' install.sh +grep -Fq 'root_path /root/easytier' install.sh grep -Fq 'verify_relay_sockets' relay-manager grep -Fq 'relay_service_is_owned' relay-manager +if git ls-files 'core/**' | grep -q .; then + printf 'Tracked EasyTier runtime files remain under core/.\n' >&2 + exit 1 +fi if grep -Eq 'COLUMNS=|stty size|tput cols|cols=80' easymesh; then printf 'Mesh views still override EasyTier terminal-width handling.\n' >&2 exit 1 @@ -27,15 +34,49 @@ if grep -Eq 'EASY_CLIENT_NO_TRUNC|easy_client_supports_no_trunc|--no-trunc' easy printf 'Interactive mesh views still disable EasyTier default truncation.\n' >&2 exit 1 fi +if grep -Eq 'v2\.0\.[35]|EASYMESH_CORE_VERSION|install_easytier|Musixal/Easy-Mesh/raw|pgrep[[:space:]]+easytier|kill[[:space:]]+-9' easymesh install.sh; then + printf 'Legacy runtime selection, download, or global process logic remains.\n' >&2 + exit 1 +fi +if grep -Eq 'curl|wget' easymesh; then + printf 'Opening the mesh menu can still reach a download path.\n' >&2 + exit 1 +fi +if grep -Eq 'curl|wget' install.sh; then + printf 'Offline package installer contains a network command.\n' >&2 + exit 1 +fi +if grep -Eq '/releases/(latest|download/latest)|/main/' release/build-release.sh release/easytier-v2.6.4-assets.env; then + printf 'Release tooling references a moving upstream target.\n' >&2 + exit 1 +fi +verify_license_notice_integrity grep -Fq 'client_command="$quoted_client $view"' easymesh grep -Fq 'local -a watch_options=(-n1 -t)' easymesh grep -Fq 'watch "${watch_options[@]}" -- "$client_command"' easymesh grep -Eq 'watch_mesh_view[[:space:]]+peer$' easymesh grep -Eq 'watch_mesh_view[[:space:]]+route$' easymesh grep -Eq 'watch_mesh_view[[:space:]]+peer-center$' easymesh +grep -Fq 'EnvironmentFile=/etc/behify-easymesh/mesh.env' easymesh +grep -Fq -- '--config-file /etc/behify-easymesh/easytier.toml' easymesh +grep -Fq -- '--console-log-level warn' easymesh +if grep -Eq 'ExecStart=.*--network-secret' easymesh; then + printf 'The managed mesh service still exposes its secret through process arguments.\n' >&2 + exit 1 +fi +if grep -Eq 'ExecStart=.*\$NETWORK_SECRET' easymesh; then + printf 'A literal mesh secret variable remains in the service writer.\n' >&2 + exit 1 +fi bash tests/mesh-watch-regression.sh +bash tests/license-integrity-regression.sh bash tests/temp-path-regression.sh bash tests/neutral-runtime-regression.sh +bash tests/mesh-config-security.sh +bash tests/mesh-config-transaction.sh +bash tests/mesh-interactive-regression.sh +bash tests/installer-regression.sh +bash tests/uninstall-ownership.sh printf 'Static smoke checks passed.\n' diff --git a/tests/systemd-release-smoke.sh b/tests/systemd-release-smoke.sh new file mode 100644 index 0000000..4cd4e0f --- /dev/null +++ b/tests/systemd-release-smoke.sh @@ -0,0 +1,175 @@ +#!/usr/bin/env bash + +set -Eeuo pipefail + +archive="${1:?architecture package is required}" +architecture="${2:?architecture is required}" +service_file=/etc/systemd/system/easymesh.service +runtime_dir=/root/easytier +install_dir=/opt/behify-easymesh +backup_dir=/opt/behify-easymesh-backups +command_path=/usr/local/bin/easymesh +mesh_config_dir=/etc/behify-easymesh +mesh_env_file=$mesh_config_dir/mesh.env +mesh_core_config=$mesh_config_dir/easytier.toml +secret_marker=BehifyRc2NativeSecretMarker +work_dir="" +resources_created=0 + +die() { + printf 'Error: %s\n' "$*" >&2 + exit 1 +} + +cleanup() { + set +e + if [[ "$resources_created" == "1" ]]; then + systemctl stop easymesh.service >/dev/null 2>&1 || true + systemctl disable easymesh.service >/dev/null 2>&1 || true + rm -f -- "$service_file" + systemctl daemon-reload >/dev/null 2>&1 || true + [[ ! -L "$command_path" || "$(readlink "$command_path")" != '/opt/behify-easymesh/easymesh' ]] || rm -f -- "$command_path" + rm -rf -- "$runtime_dir" "$install_dir" "$backup_dir" "$mesh_config_dir" + fi + [[ -z "$work_dir" || ! -d "$work_dir" ]] || rm -rf -- "$work_dir" +} +trap cleanup EXIT + +[[ $EUID -eq 0 ]] || die "Run this disposable-host test as root." +[[ "$(uname -s)" == "Linux" ]] || die "This test requires Linux." +case "$(uname -m):$architecture" in + x86_64:x86_64|amd64:x86_64|aarch64:aarch64|arm64:aarch64) ;; + *) die "Runner architecture $(uname -m) does not match $architecture." ;; +esac +systemctl show --property=Version >/dev/null 2>&1 || die "A running systemd instance is required." +for path in "$service_file" "$runtime_dir" "$install_dir" "$backup_dir" "$command_path" "$mesh_config_dir"; do + [[ ! -e "$path" && ! -L "$path" ]] || die "Disposable runner path is not clean: $path" +done + +work_dir=$(mktemp -d "${TMPDIR:-/tmp}/behify-systemd-smoke.XXXXXX") +tar -xzf "$archive" -C "$work_dir" +package_dir=$(find "$work_dir" -mindepth 1 -maxdepth 1 -type d -name 'behify-easymesh-*' -print -quit) +[[ -n "$package_dir" ]] || die "Package directory was not extracted." +resources_created=1 + +EASYMESH_OFFLINE=1 bash "$package_dir/install.sh" +"$runtime_dir/easytier-core" --version | grep -Fq '2.6.4' +"$runtime_dir/easytier-cli" --version | grep -Fq '2.6.4' +[[ -L "$command_path" && "$(readlink "$command_path")" == '/opt/behify-easymesh/easymesh' ]] + +write_fake_runtime() { + cat > "$runtime_dir/easytier-core" <<'EOF' +#!/usr/bin/env bash +if [[ "${1:-}" == "--version" ]]; then + printf 'easytier-core 9.9.9-ci\n' + exit 0 +fi +while :; do sleep 60; done +EOF + cat > "$runtime_dir/easytier-cli" <<'EOF' +#!/usr/bin/env bash +printf 'easytier-cli 9.9.9-ci\n' +EOF + chmod 0755 "$runtime_dir/easytier-core" "$runtime_dir/easytier-cli" +} + +write_service() { + local extra_argument="${1:-}" + + install -d -m 0700 "$mesh_config_dir" + cat > "$mesh_env_file" < "$service_file" < "$audit_dir/process-argv.txt" +systemctl cat easymesh.service > "$audit_dir/systemctl-cat.txt" +systemctl status easymesh.service --no-pager > "$audit_dir/systemctl-status.txt" +systemctl show easymesh.service --no-pager > "$audit_dir/systemctl-show.txt" +journalctl -u easymesh.service --no-pager -o cat > "$audit_dir/journal.txt" +if grep -R -Fq "$secret_marker" "$audit_dir" "$service_file" "$mesh_core_config"; then + die "Network secret marker leaked through service, argv, journal, or status output." +fi + +# Inactive and enabled must remain inactive and enabled. +systemctl stop easymesh.service +systemctl enable easymesh.service +write_fake_runtime +EASYMESH_OFFLINE=1 bash "$package_dir/install.sh" +! systemctl is-active --quiet easymesh.service +systemctl is-enabled --quiet easymesh.service +"$runtime_dir/easytier-core" --version | grep -Fq '2.6.4' + +# A post-activation service failure must restore both old binaries and the active state. +systemctl disable easymesh.service +write_fake_runtime +old_core_hash=$(sha256sum "$runtime_dir/easytier-core" | awk '{print $1}') +old_cli_hash=$(sha256sum "$runtime_dir/easytier-cli" | awk '{print $1}') +write_service --behify-invalid-ci-option +systemctl start easymesh.service +invalid_fixture_log="$work_dir/invalid-fixture.log" +if "$package_dir/core/easytier-core" --behify-invalid-ci-option --version >"$invalid_fixture_log" 2>&1; then + die "Negative service fixture does not make the candidate runtime fail." +fi +grep -Eiq 'unknown|unexpected|invalid' "$invalid_fixture_log" || die "Negative service fixture did not report an invalid argument." +negative_install_log="$work_dir/negative-install.log" +if EASYMESH_OFFLINE=1 bash "$package_dir/install.sh" >"$negative_install_log" 2>&1; then + cat "$negative_install_log" >&2 + die "Installer accepted a runtime whose managed service could not start." +fi +grep -Fq 'Service validation failed:' "$negative_install_log" +grep -Fq 'Service validation state:' "$negative_install_log" +[[ "$(sha256sum "$runtime_dir/easytier-core" | awk '{print $1}')" == "$old_core_hash" ]] +[[ "$(sha256sum "$runtime_dir/easytier-cli" | awk '{print $1}')" == "$old_cli_hash" ]] +systemctl is-active --quiet easymesh.service +! systemctl is-enabled --quiet easymesh.service +printf 'Disposable real-systemd %s install, state, and rollback checks passed.\n' "$architecture" diff --git a/tests/uninstall-ownership.sh b/tests/uninstall-ownership.sh new file mode 100644 index 0000000..5d59823 --- /dev/null +++ b/tests/uninstall-ownership.sh @@ -0,0 +1,92 @@ +#!/usr/bin/env bash + +set -euo pipefail + +repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +test_root=$(mktemp -d "${TMPDIR:-/tmp}/behify-uninstall-test.XXXXXX") +trap 'rm -rf -- "$test_root"' EXIT +. "$repo_root/versions.env" + +grep -Fq 'if [[ "$TEST_MODE" == "1" && -n "$TEST_ROOT" && "${BEHIFY_TEST_CONFIRM:-0}" == "1" ]]' "$repo_root/uninstall.sh" +grep -Fq 'if [[ "$PURGE" == "0" && "$TEST_CONFIRM" != "1" ]]' "$repo_root/uninstall.sh" +package_dir="$test_root/package" +mock_bin="$test_root/mock-bin" +mkdir -p "$mock_bin" +cp "$repo_root/tests/mock-systemctl.sh" "$mock_bin/systemctl" +chmod 0755 "$mock_bin/systemctl" +bash "$repo_root/tests/make-test-package.sh" "$package_dir" x86_64 + +run_uninstaller() { + local root="$1" + shift + mkdir -p "$root/systemctl-state" + MOCK_SYSTEMCTL_STATE="$root/systemctl-state" \ + MOCK_INSTALL_ROOT="$root" \ + BEHIFY_TEST_ROOT="$root" \ + BEHIFY_TEST_MODE=1 \ + BEHIFY_TEST_CONFIRM=1 \ + PATH="$mock_bin:$PATH" \ + bash "$repo_root/uninstall.sh" "$@" +} + +protected_root="$test_root/protected" +mkdir -p "$protected_root/opt/behify-easymesh/relay" "$protected_root/root/easytier" \ + "$protected_root/usr/local/bin" "$protected_root/etc/systemd/system" \ + "$protected_root/etc/behify-easymesh" "$protected_root/etc/xray" +printf 'behify-easymesh:%s\n' "$BEHIFY_EASYMESH_VERSION" > "$protected_root/opt/behify-easymesh/.install-owner" +cp "$package_dir/manifest.txt" "$protected_root/opt/behify-easymesh/manifest.txt" +cp "$repo_root/README.md" "$protected_root/opt/behify-easymesh/README.md" +printf 'relay state\n' > "$protected_root/opt/behify-easymesh/relay/state" +cp "$package_dir/core/easytier-core" "$protected_root/root/easytier/easytier-core" +cp "$package_dir/core/easytier-cli" "$protected_root/root/easytier/easytier-cli" +printf 'unrelated command\n' > "$protected_root/usr/local/bin/easymesh" +printf '[Service]\nExecStart=/usr/bin/unrelated\n' > "$protected_root/etc/systemd/system/easymesh.service" +printf 'secret config\n' > "$protected_root/etc/behify-easymesh/mesh.env" +printf 'private core config\n' > "$protected_root/etc/behify-easymesh/easytier.toml" +printf 'system xray\n' > "$protected_root/etc/xray/config.json" +run_uninstaller "$protected_root" +[[ -f "$protected_root/root/easytier/easytier-core" ]] +[[ -f "$protected_root/usr/local/bin/easymesh" ]] +[[ -f "$protected_root/etc/systemd/system/easymesh.service" ]] +[[ -f "$protected_root/etc/behify-easymesh/mesh.env" ]] +[[ -f "$protected_root/etc/behify-easymesh/easytier.toml" ]] +[[ -f "$protected_root/opt/behify-easymesh/relay/state" ]] +[[ -f "$protected_root/etc/xray/config.json" ]] +if grep -Eq '^(stop|disable) easymesh\.service$' "$protected_root/systemctl-state/calls.log" 2>/dev/null; then + printf 'Unrecognized service was controlled.\n' >&2 + exit 1 +fi + +purge_root="$test_root/purge" +mkdir -p "$purge_root/opt/behify-easymesh/relay" "$purge_root/root/easytier" \ + "$purge_root/usr/local/bin" "$purge_root/etc/systemd/system" \ + "$purge_root/etc/behify-easymesh/backups" "$purge_root/etc/behify-easymesh/relay" \ + "$purge_root/etc/xray" +printf 'behify-easymesh:%s\n' "$BEHIFY_EASYMESH_VERSION" > "$purge_root/opt/behify-easymesh/.install-owner" +cp "$package_dir/manifest.txt" "$purge_root/opt/behify-easymesh/manifest.txt" +cp "$package_dir/core/easytier-core" "$purge_root/root/easytier/easytier-core" +cp "$package_dir/core/easytier-cli" "$purge_root/root/easytier/easytier-cli" +chmod 0755 "$purge_root/root/easytier/easytier-core" "$purge_root/root/easytier/easytier-cli" +printf '%s\n' '#!/usr/bin/env bash' '# Behify EasyMesh test command link' > "$purge_root/usr/local/bin/easymesh" +printf '%s\n' '# Managed by Behify EasyMesh' > "$purge_root/etc/systemd/system/easymesh.service" +printf '%s\n' '[Service]' 'ExecStart=/bin/bash /etc/monitor.sh' > "$purge_root/etc/systemd/system/easymesh-watchdog.service" +printf 'monitor\n' > "$purge_root/etc/monitor.sh" +printf 'log\n' > "$purge_root/etc/monitor.log" +printf 'secret\n' > "$purge_root/etc/behify-easymesh/mesh.env" +printf 'private core config\n' > "$purge_root/etc/behify-easymesh/easytier.toml" +printf 'backup\n' > "$purge_root/etc/behify-easymesh/backups/config" +printf 'relay definitions\n' > "$purge_root/etc/behify-easymesh/relay/relays.json" +printf 'relay runtime\n' > "$purge_root/opt/behify-easymesh/relay/state" +printf 'system xray\n' > "$purge_root/etc/xray/config.json" +run_uninstaller "$purge_root" --purge --yes +[[ ! -e "$purge_root/root/easytier/easytier-core" ]] +[[ ! -e "$purge_root/usr/local/bin/easymesh" ]] +[[ ! -e "$purge_root/etc/systemd/system/easymesh.service" ]] +[[ ! -e "$purge_root/etc/systemd/system/easymesh-watchdog.service" ]] +[[ ! -e "$purge_root/etc/behify-easymesh/mesh.env" ]] +[[ ! -e "$purge_root/etc/behify-easymesh/easytier.toml" ]] +[[ -f "$purge_root/etc/behify-easymesh/relay/relays.json" ]] +[[ -f "$purge_root/opt/behify-easymesh/relay/state" ]] +[[ -f "$purge_root/etc/xray/config.json" ]] + +printf 'Uninstall ownership checks passed.\n' diff --git a/uninstall.sh b/uninstall.sh new file mode 100644 index 0000000..aaf64fc --- /dev/null +++ b/uninstall.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash + +set -euo pipefail + +TEST_ROOT="${BEHIFY_TEST_ROOT:-}" +TEST_MODE="${BEHIFY_TEST_MODE:-0}" +PURGE=0 +ASSUME_YES=0 +TEST_CONFIRM=0 + +root_path() { + printf '%s%s\n' "$TEST_ROOT" "$1" +} + +INSTALL_DIR="$(root_path /opt/behify-easymesh)" +COMMAND_PATH="$(root_path /usr/local/bin/easymesh)" +CORE_DIR="$(root_path /root/easytier)" +MESH_CONFIG_DIR="$(root_path /etc/behify-easymesh)" +SERVICE_FILE="$(root_path /etc/systemd/system/easymesh.service)" +WATCHDOG_FILE="$(root_path /etc/systemd/system/easymesh-watchdog.service)" +MONITOR_SCRIPT="$(root_path /etc/monitor.sh)" +MONITOR_LOG="$(root_path /etc/monitor.log)" +OWNER_FILE="$INSTALL_DIR/.install-owner" +MANIFEST_FILE="$INSTALL_DIR/manifest.txt" +MESH_SERVICE_OWNED=0 + +die() { + printf 'Error: %s\n' "$*" >&2 + exit 1 +} + +case "${1:-}" in + "") ;; + --purge) PURGE=1 ;; + *) die "Usage: sudo /opt/behify-easymesh/uninstall.sh [--purge]" ;; +esac +if [[ "${2:-}" == "--yes" && "$TEST_MODE" == "1" && -n "$TEST_ROOT" ]]; then + ASSUME_YES=1 +elif [[ $# -gt 1 ]]; then + die "Usage: sudo /opt/behify-easymesh/uninstall.sh [--purge]" +fi + +if [[ -n "$TEST_ROOT" ]]; then + [[ "$TEST_MODE" == "1" ]] || die "BEHIFY_TEST_ROOT is accepted only with BEHIFY_TEST_MODE=1." + [[ "$TEST_ROOT" == /tmp/* || "$TEST_ROOT" == /var/tmp/* ]] || die "BEHIFY_TEST_ROOT must be below /tmp or /var/tmp." + [[ "$TEST_ROOT" != *'/../'* && "$TEST_ROOT" != */.. ]] || die "BEHIFY_TEST_ROOT must not contain parent traversal." +fi +if [[ -z "$TEST_ROOT" && $EUID -ne 0 ]]; then + die "Run the uninstaller as root." +fi +if [[ "$TEST_MODE" == "1" && -z "$TEST_ROOT" ]]; then + die "BEHIFY_TEST_MODE requires BEHIFY_TEST_ROOT." +fi +if [[ "$TEST_MODE" == "1" && -n "$TEST_ROOT" && "${BEHIFY_TEST_CONFIRM:-0}" == "1" ]]; then + TEST_CONFIRM=1 +fi +for command_name in grep awk sha256sum mktemp sed rm rmdir systemctl readlink; do + command -v "$command_name" >/dev/null 2>&1 || die "Required command '$command_name' is missing; no files were removed." +done +[[ -f "$OWNER_FILE" ]] || die "Installation ownership marker is missing; refusing to remove files." +[[ ! -L "$INSTALL_DIR" ]] || die "Installation directory is a symlink; refusing to remove files." +grep -Eq '^behify-easymesh:[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z][0-9A-Za-z.-]*)?$' "$OWNER_FILE" || die "Installation ownership marker is invalid." + +manifest_value() { + local key="$1" + + [[ -f "$MANIFEST_FILE" ]] || return 1 + awk -F= -v key="$key" '$1 == key { count++; value=substr($0, index($0, "=") + 1) } END { if (count != 1) exit 1; print value }' "$MANIFEST_FILE" +} + +service_is_owned() { + [[ -f "$SERVICE_FILE" && ! -L "$SERVICE_FILE" ]] && + { grep -Fqx '# Managed by Behify EasyMesh' "$SERVICE_FILE" || + grep -Fq 'ExecStart=/root/easytier/easytier-core ' "$SERVICE_FILE"; } +} + +watchdog_is_owned() { + [[ -f "$WATCHDOG_FILE" && ! -L "$WATCHDOG_FILE" ]] && grep -Fq 'ExecStart=/bin/bash /etc/monitor.sh' "$WATCHDOG_FILE" +} + +remove_managed_cron() { + local temp_crontab + + command -v crontab >/dev/null 2>&1 || return 0 + temp_crontab=$(mktemp) || return 1 + if crontab -l > "$temp_crontab" 2>/dev/null; then + sed -i "/#easymesh\.service$/d; /# behify-easymesh-service-restart$/d" "$temp_crontab" + crontab "$temp_crontab" + fi + rm -f "$temp_crontab" +} + +remove_runtime_if_owned() { + local core_hash cli_hash installed_core_hash installed_cli_hash + + if [[ -L "$CORE_DIR" ]]; then + printf 'Preserved EasyTier runtime: runtime directory is a symlink.\n' + return 0 + fi + core_hash=$(manifest_value easytier_core_sha256 2>/dev/null || true) + cli_hash=$(manifest_value easytier_cli_sha256 2>/dev/null || true) + [[ "$core_hash" =~ ^[0-9a-f]{64}$ && "$cli_hash" =~ ^[0-9a-f]{64}$ ]] || { + printf 'Preserved EasyTier runtime: package hashes were unavailable.\n' + return 0 + } + [[ ! -f "$CORE_DIR/easytier-core" ]] || installed_core_hash=$(sha256sum "$CORE_DIR/easytier-core" | awk '{print $1}') + [[ ! -f "$CORE_DIR/easytier-cli" ]] || installed_cli_hash=$(sha256sum "$CORE_DIR/easytier-cli" | awk '{print $1}') + if [[ ! -L "$CORE_DIR/easytier-core" && ! -L "$CORE_DIR/easytier-cli" && + "${installed_core_hash:-}" == "$core_hash" && "${installed_cli_hash:-}" == "$cli_hash" ]]; then + rm -f -- "$CORE_DIR/easytier-core" "$CORE_DIR/easytier-cli" + rmdir "$CORE_DIR" 2>/dev/null || true + printf 'Removed the Behify-installed EasyTier runtime pair.\n' + else + printf 'Preserved EasyTier runtime: installed files are not both Behify-owned.\n' + fi +} + +remove_managed_application_files() { + local path top_level + local -a paths=( + easymesh install.sh uninstall.sh versions.env manifest.txt files.sha256 + README.md README.fa.md README_FA.md CHANGELOG.md SECURITY.md THIRD_PARTY_NOTICES.md + LICENSE NOTICE + docs/configuration.md docs/operations.md + docs/release-checklist-v1.0.0-rc.1.md docs/release-notes-v1.0.0-rc.1.md + docs/release-checklist-v1.0.0-rc.2.md docs/release-notes-v1.0.0-rc.2.md + docs/historical/smoke-test-v0.1.0.md docs/historical/smoke-test-v0.1.3-v2.6.4.md + licenses/Easy-Mesh-ATTRIBUTION.md licenses/EasyTier-LGPL-3.0.txt licenses/Xray-core-MPL-2.0.txt + ) + + for path in "${paths[@]}"; do + top_level=${path%%/*} + if [[ "$path" == */* && -L "$INSTALL_DIR/$top_level" ]]; then + printf 'Preserved symlinked application subtree: %s\n' "$INSTALL_DIR/$top_level" + continue + fi + rm -f -- "$INSTALL_DIR/$path" + done + rm -f -- "$OWNER_FILE" + rmdir "$INSTALL_DIR/docs/historical" "$INSTALL_DIR/docs" "$INSTALL_DIR/licenses" 2>/dev/null || true + rmdir "$INSTALL_DIR" 2>/dev/null || true +} + +if [[ "$PURGE" == "0" && "$TEST_CONFIRM" != "1" ]]; then + read -r -p 'Uninstall Behify EasyMesh and preserve configuration? [y/N] ' confirmation || confirmation="" + [[ "$confirmation" =~ ^[Yy]$ ]] || die "Uninstall cancelled." +elif [[ "$PURGE" == "1" && "$ASSUME_YES" != "1" ]]; then + printf '%s\n' \ + 'Purge removes Behify-owned mesh service files, mesh configuration, watchdog files, and rollback backups.' \ + 'The isolated relay component and its service/configuration are preserved and must be removed from its own menu.' + read -r -p 'Type PURGE to continue: ' confirmation || confirmation="" + [[ "$confirmation" == "PURGE" ]] || die "Purge cancelled." +fi + +service_is_owned && MESH_SERVICE_OWNED=1 +if [[ "$MESH_SERVICE_OWNED" == "1" ]]; then + systemctl stop easymesh.service >/dev/null 2>&1 || true + systemctl disable easymesh.service >/dev/null 2>&1 || true + printf 'Stopped and disabled Behify-owned easymesh.service.\n' +else + printf 'Preserved easymesh.service: missing or not verified as Behify-owned.\n' +fi +if watchdog_is_owned; then + systemctl stop easymesh-watchdog.service >/dev/null 2>&1 || true + systemctl disable easymesh-watchdog.service >/dev/null 2>&1 || true + printf 'Stopped and disabled Behify-owned easymesh-watchdog.service.\n' +fi +remove_managed_cron +if [[ ! -f "$SERVICE_FILE" || "$MESH_SERVICE_OWNED" == "1" ]]; then + remove_runtime_if_owned +else + printf 'Preserved EasyTier runtime because easymesh.service is not verified as Behify-owned.\n' +fi + +if [[ -L "$COMMAND_PATH" && "$(readlink "$COMMAND_PATH")" == "/opt/behify-easymesh/easymesh" ]] || + { [[ "$TEST_MODE" == "1" && -f "$COMMAND_PATH" ]] && grep -Fqx '# Behify EasyMesh test command link' "$COMMAND_PATH"; }; then + rm -f -- "$COMMAND_PATH" + printf 'Removed /usr/local/bin/easymesh.\n' +else + printf 'Preserved /usr/local/bin/easymesh: missing or not Behify-owned.\n' +fi + +if [[ "$PURGE" == "1" ]]; then + if [[ "$MESH_SERVICE_OWNED" == "1" ]]; then + rm -f -- "$SERVICE_FILE" + printf 'Removed Behify-owned easymesh.service.\n' + fi + if watchdog_is_owned; then + rm -f -- "$WATCHDOG_FILE" "$MONITOR_SCRIPT" "$MONITOR_LOG" + printf 'Removed Behify-owned watchdog files.\n' + fi + if [[ -L "$MESH_CONFIG_DIR" || -L "$MESH_CONFIG_DIR/mesh.env" || -L "$MESH_CONFIG_DIR/easytier.toml" || -L "$MESH_CONFIG_DIR/backups" ]]; then + printf 'Preserved mesh configuration because a managed configuration path is a symlink.\n' + elif [[ ! -f "$SERVICE_FILE" || "$MESH_SERVICE_OWNED" == "1" ]]; then + rm -f -- "$MESH_CONFIG_DIR/mesh.env" "$MESH_CONFIG_DIR/easytier.toml" + if [[ -d "$MESH_CONFIG_DIR/backups" ]]; then + rm -rf -- "$MESH_CONFIG_DIR/backups" + fi + printf 'Removed Behify-owned mesh configuration and backups.\n' + else + printf 'Preserved mesh configuration because easymesh.service is not verified as Behify-owned.\n' + fi + rmdir "$MESH_CONFIG_DIR" 2>/dev/null || true + systemctl daemon-reload >/dev/null 2>&1 || true +else + printf 'Preserved mesh configuration and service files. Use --purge for explicit removal.\n' +fi + +remove_managed_application_files +printf '%s\n' \ + 'Behify EasyMesh application files were removed.' \ + 'The isolated relay directory, relay configuration, behify-relay.service, and unrelated Xray installations were preserved.' diff --git a/versions.env b/versions.env new file mode 100644 index 0000000..3b1b1d3 --- /dev/null +++ b/versions.env @@ -0,0 +1,2 @@ +BEHIFY_EASYMESH_VERSION=1.0.0 +EASYTIER_VERSION=v2.6.4