diff --git a/.env.example b/.env.example index bce9b2abcd..76a03a7fad 100644 --- a/.env.example +++ b/.env.example @@ -1,19 +1,14 @@ +# Encryption keys used during build CRYPTO_KEY= MAGIC_IV= MAGIC_SALT= NEW_CRYPTO_KEY= +# Core API endpoints NEW_DRIVE_URL= -DRIVE_API_URL= -PAYMENTS_URL= -INTERNXT_DESKTOP_HEADER_KEY= - BRIDGE_URL= -APP_SEGMENT_KEY= -APP_SEGMENT_KEY_TEST= -BUG_REPORTING_URL= +PAYMENTS_URL= NOTIFICATIONS_URL= -LOCK_REFRESH_INTERVAL= -RUDDERSTACK_KEY= -RUDDERSTACK_DATA_PLANE_URL= +# Desktop client identification +INTERNXT_DESKTOP_HEADER_KEY= diff --git a/.erb/configs/webpack.config.main.prod.ts b/.erb/configs/webpack.config.main.prod.ts index 4bc5aa287f..c6d1a82d92 100644 --- a/.erb/configs/webpack.config.main.prod.ts +++ b/.erb/configs/webpack.config.main.prod.ts @@ -38,11 +38,13 @@ const configuration: webpack.Configuration = { }, optimization: { + chunkIds: 'deterministic', minimizer: [ new TerserPlugin({ - parallel: true, + parallel: false, }), ], + moduleIds: 'deterministic', }, plugins: [ diff --git a/.erb/configs/webpack.config.renderer.prod.ts b/.erb/configs/webpack.config.renderer.prod.ts index 3a4189b3ff..5679b558ff 100644 --- a/.erb/configs/webpack.config.renderer.prod.ts +++ b/.erb/configs/webpack.config.renderer.prod.ts @@ -94,13 +94,17 @@ const configuration: webpack.Configuration = { }, optimization: { + chunkIds: 'deterministic', minimize: true, minimizer: [ new TerserPlugin({ - parallel: true, + parallel: false, + }), + new CssMinimizerPlugin({ + parallel: false, }), - new CssMinimizerPlugin(), ], + moduleIds: 'deterministic', }, plugins: [ diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 27d15fc8b8..cbf330a21a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,30 +4,24 @@ on: release: types: [published] workflow_dispatch: + inputs: + publish_to_github: + description: "Publish artifacts to GitHub release" + required: true + default: "false" + type: choice + options: + - "false" + - "true" jobs: build: - runs-on: ubuntu-latest - permissions: - contents: read - id-token: write - packages: write + runs-on: ubuntu-24.04 + env: + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - - name: Create .npmrc file - run: | - echo "@internxt:registry=https://npm.pkg.github.com/" > .npmrc - echo "//npm.pkg.github.com/:_authToken=${{ secrets.GITHUB_TOKEN }}" >> .npmrc - - - name: Install rpm build tools - run: sudo apt-get install -y rpm - - - name: Install dependencies - run: npm ci - name: Add .env run: | @@ -37,18 +31,109 @@ jobs: echo "MAGIC_SALT=${{ secrets.MAGIC_SALT }}" >> .env echo "NEW_CRYPTO_KEY=${{ secrets.NEW_CRYPTO_KEY }}" >> .env echo "NEW_DRIVE_URL=https://gateway.internxt.com/drive" >> .env - echo "DRIVE_API_URL=https://gateway.internxt.com/api" >> .env echo "PAYMENTS_URL=https://gateway.internxt.com/payments" >> .env echo "INTERNXT_DESKTOP_HEADER_KEY=${{ secrets.INTERNXT_DESKTOP_HEADER_KEY }}" >> .env echo "BRIDGE_URL=https://gateway.internxt.com/network" >> .env - echo "APP_SEGMENT_KEY=${{ secrets.APP_SEGMENT_KEY }}" >> .env - echo "APP_SEGMENT_KEY_TEST=${{ secrets.APP_SEGMENT_KEY_TEST }}" >> .env - echo "BUG_REPORTING_URL=https://desktop-bug-reporting.inxt.workers.dev" >> .env echo "NOTIFICATIONS_URL=https://notifications.internxt.com" >> .env - echo "LOCK_REFRESH_INTERVAL=20000" >> .env - - name: Build app - run: npm run build + - name: Build release container image + run: docker build -f Dockerfile.release -t internxt-release-builder:24.04 . + + - name: Build app inside container + run: | + if [[ -z "${GH_TOKEN}" ]]; then + echo "GH_TOKEN is empty in workflow context" + exit 1 + fi - - name: Publish app - run: npm run publish + docker run --rm \ + -e CI=true \ + -e GH_TOKEN \ + -e GITHUB_TOKEN \ + -v "$PWD:/workspace" \ + -w /workspace \ + internxt-release-builder:24.04 \ + bash -lc './scripts/run-release-build-in-container.sh' + + - name: Upload artifacts for smoke tests + if: success() + uses: actions/upload-artifact@v4 + with: + name: linux-release-artifacts + path: | + build/*.AppImage + build/*.deb + build/*.rpm + build/*.yml + + - name: Upload build metadata + if: always() + uses: actions/upload-artifact@v4 + with: + name: build-metadata + path: | + build-manifest.json + build-checksums.txt + + smoke-test-deb: + needs: build + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04, ubuntu-latest] + steps: + - name: Download release artifacts + uses: actions/download-artifact@v4 + with: + name: linux-release-artifacts + path: build + + - name: Install runtime dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends xvfb + + - name: Install deb package for smoke test + run: | + sudo dpkg -i --force-depends build/*.deb + test -x /opt/Internxt/internxt + + - name: Start app in headless mode + run: | + xvfb-run -a /opt/Internxt/internxt --no-sandbox --version & + APP_PID=$! + sleep 5 # Allow app to initialize + kill $APP_PID + + publish-release: + needs: smoke-test-deb + if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.publish_to_github == 'true' && startsWith(github.ref, 'refs/tags/')) }} + runs-on: ubuntu-24.04 + env: + GH_TOKEN: ${{ github.token }} + GITHUB_TOKEN: ${{ github.token }} + permissions: + contents: write + steps: + - name: Validate GitHub token availability + run: | + if [[ -z "${GH_TOKEN}" ]]; then + echo "GH_TOKEN is empty in publish-release context" + exit 1 + fi + + - name: Download tested artifacts + uses: actions/download-artifact@v4 + with: + name: linux-release-artifacts + path: build + + - name: Publish tested artifacts to GitHub release + uses: softprops/action-gh-release@v2 + with: + files: | + build/*.AppImage + build/*.deb + build/*.rpm + build/*.yml diff --git a/.github/workflows/sonar-analysis.yml b/.github/workflows/sonar-analysis.yml index 0c90ebb024..c0e66cfb78 100644 --- a/.github/workflows/sonar-analysis.yml +++ b/.github/workflows/sonar-analysis.yml @@ -22,7 +22,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libfuse2 libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xdg-utils libatspi2.0-0 libdrm2 libgbm1 libasound2t64 + sudo apt-get install -y libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xdg-utils libatspi2.0-0 libdrm2 libgbm1 libasound2t64 - name: Install dependencies run: npm ci --ignore-scripts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f84186aad6..46b7b28db3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -22,7 +22,7 @@ jobs: - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install -y libfuse2 libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xdg-utils libatspi2.0-0 libdrm2 libgbm1 libasound2t64 + sudo apt-get install -y libgtk-3-0 libnotify4 libnss3 libxss1 libxtst6 xdg-utils libatspi2.0-0 libdrm2 libgbm1 libasound2t64 - name: Install dependencies run: npm ci --ignore-scripts diff --git a/Dockerfile.release b/Dockerfile.release new file mode 100644 index 0000000000..8ad58a1821 --- /dev/null +++ b/Dockerfile.release @@ -0,0 +1,37 @@ +FROM ubuntu:24.04 + +ARG DEBIAN_FRONTEND=noninteractive +ARG NODE_VERSION=20.20.2 +ARG GO_VERSION=1.26.1 + +ENV LANG=C.UTF-8 \ + LC_ALL=C.UTF-8 \ + NODE_ENV=production + +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates \ + curl \ + build-essential \ + git \ + make \ + pkg-config \ + python3 \ + rpm \ + xz-utils \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz" -o /tmp/node.tar.xz \ + && mkdir -p /usr/local/node \ + && tar -xJf /tmp/node.tar.xz -C /usr/local/node --strip-components=1 \ + && ln -s /usr/local/node/bin/node /usr/local/bin/node \ + && ln -s /usr/local/node/bin/npm /usr/local/bin/npm \ + && ln -s /usr/local/node/bin/npx /usr/local/bin/npx \ + && rm /tmp/node.tar.xz + +RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tar.gz \ + && rm -rf /usr/local/go \ + && tar -C /usr/local -xzf /tmp/go.tar.gz \ + && ln -s /usr/local/go/bin/go /usr/local/bin/go \ + && rm /tmp/go.tar.gz + +WORKDIR /workspace \ No newline at end of file diff --git a/README.md b/README.md index e85e662a24..31d100e8db 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,26 @@ Building the `.rpm` package requires `rpmbuild`. On Ubuntu or Debian, install th sudo apt-get install rpm ``` +### Official Release Build (CI Container) + +The official release pipeline builds and publishes artifacts inside a pinned container image (`Dockerfile.release`) to reduce host drift. + +Current release flow: + +1. Build container image from `ubuntu:24.04`. +2. Run `npm ci` and `npm run publish` inside the container. +3. Upload generated artifacts (`.deb`, `.rpm`, `.AppImage`) plus build metadata. +4. Run smoke tests on the generated `.deb` without rebuilding it. + +### Smoke Test Strategy + +The release workflow includes a smoke test job that: + +1. Downloads the previously built `.deb` artifact. +2. Installs runtime dependencies for Linux GUI startup checks. +3. Installs the package and verifies `/opt/Internxt/internxt` exists. +4. Launches the binary in headless mode (`xvfb-run`) and checks startup. + ## Login Configuration Using Deeplink Create a script in the root of the project named `enable-sso.sh` and add the following content: diff --git a/beforeBuild.js b/beforeBuild.js index 21d33affa7..27a1dcdab4 100644 --- a/beforeBuild.js +++ b/beforeBuild.js @@ -7,6 +7,16 @@ module.exports = async (context) => { // this, prebuild-install downloads an Electron-v116 prebuilt compiled for // Electron 24 (V8 11.0) which segfaults under Electron 25+ (V8 11.4). process.env.npm_config_build_from_source = 'true'; + console.log( + JSON.stringify({ + tag: 'ELECTRON_REBUILD', + appDir, + arch, + electronVersion, + nodeVersion: process.version, + buildFromSource: process.env.npm_config_build_from_source, + }), + ); await electronRebuild.rebuild({ buildPath: appDir, electronVersion, arch, force: true }); return false; diff --git a/env.d.ts b/env.d.ts index 10dca8a3fc..f23e5c4be1 100644 --- a/env.d.ts +++ b/env.d.ts @@ -1,20 +1,21 @@ declare global { namespace NodeJS { interface ProcessEnv { + // Encryption keys for backups and data protection CRYPTO_KEY: string; MAGIC_IV: string; MAGIC_SALT: string; NEW_CRYPTO_KEY: string; + + // Core API endpoints NEW_DRIVE_URL: string; BRIDGE_URL: string; - APP_SEGMENT_KEY: string; - APP_SEGMENT_KEY_TEST: string; - BUG_REPORTING_URL: string; - platform: string; + PAYMENTS_URL: string; NOTIFICATIONS_URL: string; - LOCK_REFRESH_INTERVAL: string; - DRIVE_API_URL: string; + INTERNXT_DESKTOP_HEADER_KEY: string; ENABLE_ANTIVIRUS?: string; + NODE_ENV?: string; + PORT?: string; } } } diff --git a/package.json b/package.json index 9597c53787..6a1e569745 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "build": "concurrently \"npm run build:main\" \"npm run build:renderer\" \"npm run build:daemon\"", - "build:daemon": "cd packages/fuse-daemon && go build -ldflags='-s -w' -o ../../dist/fuse-daemon ./cmd/daemon", + "build:daemon": "make -C packages/fuse-daemon build", "build:main": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.main.prod.ts", "build:renderer": "cross-env NODE_ENV=production TS_NODE_TRANSPILE_ONLY=true webpack --config ./.erb/configs/webpack.config.renderer.prod.ts", "rebuild": "electron-rebuild --parallel --types prod,dev,optional --module-dir .", @@ -74,7 +74,6 @@ }, "deb": { "depends": [ - "libfuse2", "python3-nautilus" ] }, @@ -92,13 +91,10 @@ "./clamAV/**", "./src/apps/nautilus-extension/internxt-virtual-drive.py", "./dist/fuse-daemon" - ], - "publish": { - "provider": "github" - } + ] }, "devDependencies": { - "@electron/rebuild": "^3.7.2", + "@electron/rebuild": "3.7.2", "@headlessui/react": "^1.4.2", "@iconscout/react-unicons": "^1.1.6", "@internxt/eslint-config-internxt": "^1.0.9", @@ -143,8 +139,8 @@ "detect-port": "^1.3.0", "dotenv": "^10.0.0", "dotenv-webpack": "^7.0.3", - "electron": "^29.0.0", - "electron-builder": "^23.6.0", + "electron": "29.0.0", + "electron-builder": "23.6.0", "electron-debug": "^3.2.0", "electron-fetch": "^1.9.1", "electron-store": "^8.0.1", @@ -206,7 +202,7 @@ "@internxt/sdk": "1.11.17", "async": "^3.2.4", "axios": "^1.1.3", - "better-sqlite3": "^11.10.0", + "better-sqlite3": "11.10.0", "bottleneck": "^2.19.5", "check-disk-space": "^3.4.0", "diod": "^2.0.0", @@ -225,4 +221,4 @@ "engines": { "node": ">=20.0.0 <21.0.0" } -} +} \ No newline at end of file diff --git a/packages/fuse-daemon/Makefile b/packages/fuse-daemon/Makefile index cd7e985560..a03dd61cbf 100644 --- a/packages/fuse-daemon/Makefile +++ b/packages/fuse-daemon/Makefile @@ -1,7 +1,10 @@ .PHONY: build test lint +GOFLAGS ?= -mod=readonly -trimpath -buildvcs=false +LDFLAGS ?= -s -w + build: - go build -ldflags="-s -w" -o ../../dist/fuse-daemon ./cmd/daemon + go build $(GOFLAGS) -ldflags="$(LDFLAGS)" -o ../../dist/fuse-daemon ./cmd/daemon test: go test ./... diff --git a/scripts/run-release-build-in-container.sh b/scripts/run-release-build-in-container.sh new file mode 100755 index 0000000000..c8cfc7d0c7 --- /dev/null +++ b/scripts/run-release-build-in-container.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# electron-builder expects GH_TOKEN for GitHub publisher flows. +export GH_TOKEN="${GH_TOKEN:-${GITHUB_TOKEN:-}}" + +if [[ -z "${GH_TOKEN}" ]]; then + echo "GH_TOKEN/GITHUB_TOKEN is empty inside build container" + exit 1 +fi + +npm ci --include=dev + +node -e 'const packageJson = require("./package.json"); const { execSync } = require("child_process"); const run = (command) => execSync(command, { encoding: "utf8" }).trim(); const manifest = { platform: process.platform, arch: process.arch, node: process.version, npm: run("npm --version"), go: run("go version"), image: "internxt-release-builder:24.04", electron: packageJson.devDependencies.electron, electronBuilder: packageJson.devDependencies["electron-builder"], electronRebuild: packageJson.devDependencies["@electron/rebuild"], betterSqlite3: packageJson.dependencies["better-sqlite3"] }; process.stdout.write(JSON.stringify(manifest, null, 2));' > build-manifest.json + +npm run package + +find build -maxdepth 1 -type f \( -name "*.AppImage" -o -name "*.deb" -o -name "*.rpm" -o -name "*.yml" \) -print0 \ + | sort -z \ + | xargs -0 sha256sum > build-checksums.txt