Build portable Linux native runtime
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
.eslintcache
|
||||
|
||||
# Build output
|
||||
out/
|
||||
|
||||
@@ -12,6 +12,8 @@ Each signed runtime bundle contains a `runtime.json` manifest plus versioned exe
|
||||
|
||||
Runtime archives are created from a staging directory with `npm run build:native-runtime -- <staging-directory> <output.tar.gz>`. The packager resolves every executable inside the staging root, calculates its SHA-256 checksum, writes the immutable `runtime.json`, excludes the build template, and creates the distributable archive. Dockside independently verifies those checksums before declaring a runtime available.
|
||||
|
||||
The first reproducible bundle target is Linux x64. Run `npm run build:native-linux-x64` on a Docker-capable build machine. Docker is used only to create the portable artifact; users of that artifact do not need Docker. The recipe pins PHP 8.5.9, nginx 1.30.4, and MariaDB 11.8.8 with their runtime libraries, runs version smoke checks outside the build container, and then invokes the normal checksum packager.
|
||||
|
||||
## Isolation model
|
||||
|
||||
Every project receives reserved loopback ports, generated service configuration, isolated database data, logs, PID files, and environment variables below `.aurora/native`. A shared Aurora router owns friendly HTTPS project hostnames. Project files remain directly accessible on the host.
|
||||
|
||||
+11
-1
@@ -6,7 +6,17 @@ import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
|
||||
import eslintPluginReactRefresh from 'eslint-plugin-react-refresh'
|
||||
|
||||
export default defineConfig(
|
||||
{ ignores: ['**/node_modules', '**/dist', '**/out'] },
|
||||
{
|
||||
ignores: [
|
||||
'**/node_modules/**',
|
||||
'**/dist/**',
|
||||
'**/out/**',
|
||||
'dist/**',
|
||||
'out/**',
|
||||
'build/**',
|
||||
'website/**'
|
||||
]
|
||||
},
|
||||
tseslint.configs.recommended,
|
||||
eslintPluginReact.configs.flat.recommended,
|
||||
eslintPluginReact.configs.flat['jsx-runtime'],
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
"build:modules": "node scripts/package-modules.cjs",
|
||||
"build:connector": "node scripts/package-connector.cjs",
|
||||
"build:native-runtime": "node scripts/package-native-runtime.cjs",
|
||||
"build:native-linux-x64": "node scripts/build-native-linux-x64.cjs",
|
||||
"check:php-releases": "node scripts/check-php-releases.mjs",
|
||||
"module:new": "node scripts/create-module.cjs",
|
||||
"postinstall": "electron-builder install-app-deps",
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM php:8.5.9-fpm-alpine3.23
|
||||
|
||||
RUN apk add --no-cache --repository=https://dl-cdn.alpinelinux.org/alpine/edge/main \
|
||||
nginx=1.30.4-r3 mariadb=11.8.8-r0 mariadb-client=11.8.8-r0 bash file pax-utils \
|
||||
&& mkdir -p /stage/root /stage/bin /stage/lib
|
||||
|
||||
COPY stage-runtime.sh /usr/local/bin/stage-runtime
|
||||
RUN chmod +x /usr/local/bin/stage-runtime && /usr/local/bin/stage-runtime
|
||||
|
||||
FROM scratch AS export
|
||||
COPY --from=0 /stage/ /
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
stage=/stage
|
||||
root="$stage/root"
|
||||
|
||||
copy_file() {
|
||||
source_path="$1"
|
||||
destination="$root$source_path"
|
||||
mkdir -p "$(dirname "$destination")"
|
||||
cp -L "$source_path" "$destination"
|
||||
}
|
||||
|
||||
copy_binary_and_libraries() {
|
||||
binary="$1"
|
||||
copy_file "$binary"
|
||||
scanelf --needed --nobanner "$binary" | awk '{ print $2 }' | tr ',' '\n' | while read -r library; do
|
||||
[ -n "$library" ] || continue
|
||||
library_path=$(find /lib /usr/lib /usr/local/lib -name "$library" -print -quit)
|
||||
[ -n "$library_path" ] || { echo "Missing library $library for $binary" >&2; exit 1; }
|
||||
copy_file "$library_path"
|
||||
done
|
||||
}
|
||||
|
||||
copy_binary_and_libraries /usr/local/bin/php
|
||||
copy_binary_and_libraries /usr/local/sbin/php-fpm
|
||||
copy_binary_and_libraries /usr/sbin/nginx
|
||||
copy_binary_and_libraries /usr/bin/mariadbd
|
||||
copy_binary_and_libraries /usr/bin/mariadb-install-db
|
||||
mkdir -p "$root/lib" "$root/usr/lib"
|
||||
cp -aL /lib/. "$root/lib/"
|
||||
cp -aL /usr/lib/. "$root/usr/lib/"
|
||||
|
||||
mkdir -p "$root/usr/local/lib/php/extensions"
|
||||
if [ -d /usr/local/lib/php/extensions ]; then cp -a /usr/local/lib/php/extensions/. "$root/usr/local/lib/php/extensions/"; fi
|
||||
mkdir -p "$root/usr/local/etc"
|
||||
cp -a /usr/local/etc/php "$root/usr/local/etc/"
|
||||
mkdir -p "$root/usr/share"
|
||||
if [ -d /usr/share/mariadb ]; then cp -a /usr/share/mariadb "$root/usr/share/"; fi
|
||||
|
||||
cat > "$stage/bin/aurora-exec" <<'EOF'
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
runtime_root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
|
||||
target="$1"
|
||||
shift
|
||||
export LD_LIBRARY_PATH="$runtime_root/root/lib:$runtime_root/root/usr/lib:$runtime_root/root/usr/local/lib"
|
||||
export PHPRC="$runtime_root/root/usr/local/etc/php"
|
||||
export PHP_INI_SCAN_DIR="$runtime_root/root/usr/local/etc/php/conf.d"
|
||||
exec "$runtime_root/root/lib/ld-musl-x86_64.so.1" --library-path "$LD_LIBRARY_PATH" "$runtime_root/root$target" "$@"
|
||||
EOF
|
||||
chmod +x "$stage/bin/aurora-exec"
|
||||
|
||||
for entry in 'php:/usr/local/bin/php' 'php-fpm:/usr/local/sbin/php-fpm' 'nginx:/usr/sbin/nginx' 'mariadbd:/usr/bin/mariadbd' 'mariadb-install-db:/usr/bin/mariadb-install-db'; do
|
||||
name=${entry%%:*}
|
||||
target=${entry#*:}
|
||||
cat > "$stage/bin/$name" <<EOF
|
||||
#!/bin/sh
|
||||
exec "\$(dirname "\$0")/aurora-exec" "$target" "\$@"
|
||||
EOF
|
||||
chmod +x "$stage/bin/$name"
|
||||
done
|
||||
|
||||
php_version=$(php -r 'echo PHP_VERSION;')
|
||||
nginx_version=$(nginx -v 2>&1 | sed 's#nginx version: nginx/##')
|
||||
mariadb_version=$(mariadbd --version | sed -n 's/.* Ver \([^ -]*\).*/\1/p')
|
||||
cat > "$stage/runtime.template.json" <<EOF
|
||||
{
|
||||
"schema": 1,
|
||||
"runtimeVersion": "0.1.0",
|
||||
"platform": "linux",
|
||||
"arch": "x64",
|
||||
"components": [
|
||||
{ "id": "php", "version": "$php_version", "executable": "bin/php-fpm" },
|
||||
{ "id": "nginx", "version": "$nginx_version", "executable": "bin/nginx" },
|
||||
{ "id": "mariadb", "version": "$mariadb_version", "executable": "bin/mariadbd" }
|
||||
]
|
||||
}
|
||||
EOF
|
||||
@@ -0,0 +1,38 @@
|
||||
'use strict'
|
||||
/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/explicit-function-return-type */
|
||||
|
||||
const { chmodSync, existsSync, mkdirSync, rmSync } = require('fs')
|
||||
const { join, resolve } = require('path')
|
||||
const { spawnSync } = require('child_process')
|
||||
|
||||
const projectRoot = resolve(__dirname, '..')
|
||||
const buildRoot = join(projectRoot, 'runtime-build', 'linux-x64')
|
||||
const stagingRoot = join(projectRoot, 'dist', 'native-runtime', 'linux-x64-stage')
|
||||
const archive = join(projectRoot, 'dist', 'native-runtime', 'aurora-native-0.1.0-linux-x64.tar.gz')
|
||||
|
||||
function run(command, args) {
|
||||
const result = spawnSync(command, args, { cwd: projectRoot, stdio: 'inherit' })
|
||||
if (result.error) throw result.error
|
||||
if (result.status !== 0) throw new Error(`${command} failed with exit code ${result.status}`)
|
||||
}
|
||||
|
||||
if (process.platform !== 'linux' || process.arch !== 'x64')
|
||||
throw new Error('This builder targets Linux x64 only.')
|
||||
mkdirSync(stagingRoot, { recursive: true })
|
||||
rmSync(stagingRoot, { recursive: true, force: true })
|
||||
mkdirSync(stagingRoot, { recursive: true })
|
||||
run('docker', ['build', '--pull', '--output', `type=local,dest=${stagingRoot}`, buildRoot])
|
||||
for (const executable of [
|
||||
'aurora-exec',
|
||||
'php',
|
||||
'php-fpm',
|
||||
'nginx',
|
||||
'mariadbd',
|
||||
'mariadb-install-db'
|
||||
]) {
|
||||
const path = join(stagingRoot, 'bin', executable)
|
||||
if (!existsSync(path)) throw new Error(`Builder did not produce ${path}`)
|
||||
chmodSync(path, 0o755)
|
||||
}
|
||||
run('node', ['scripts/smoke-native-runtime.cjs', stagingRoot])
|
||||
run('node', ['scripts/package-native-runtime.cjs', stagingRoot, archive])
|
||||
@@ -0,0 +1,40 @@
|
||||
'use strict'
|
||||
/* eslint-disable @typescript-eslint/no-require-imports */
|
||||
|
||||
const { existsSync, readFileSync } = require('fs')
|
||||
const { join, resolve } = require('path')
|
||||
const { spawnSync } = require('child_process')
|
||||
|
||||
const root = resolve(process.argv[2] || '')
|
||||
if (!root || !existsSync(join(root, 'runtime.template.json')))
|
||||
throw new Error('Usage: node scripts/smoke-native-runtime.cjs <staging-directory>')
|
||||
const template = JSON.parse(readFileSync(join(root, 'runtime.template.json'), 'utf8'))
|
||||
const checks = [
|
||||
[
|
||||
'PHP',
|
||||
join(root, 'bin/php'),
|
||||
['--version'],
|
||||
template.components.find((item) => item.id === 'php')?.version
|
||||
],
|
||||
[
|
||||
'nginx',
|
||||
join(root, 'bin/nginx'),
|
||||
['-v'],
|
||||
template.components.find((item) => item.id === 'nginx')?.version
|
||||
],
|
||||
[
|
||||
'MariaDB',
|
||||
join(root, 'bin/mariadbd'),
|
||||
['--version'],
|
||||
template.components.find((item) => item.id === 'mariadb')?.version
|
||||
]
|
||||
]
|
||||
for (const [name, command, args, version] of checks) {
|
||||
const result = spawnSync(command, args, { encoding: 'utf8' })
|
||||
const output = `${result.stdout || ''}${result.stderr || ''}`
|
||||
if (result.error || result.status !== 0)
|
||||
throw result.error || new Error(`${name} smoke check failed: ${output}`)
|
||||
if (!version || !output.includes(version))
|
||||
throw new Error(`${name} did not report expected version ${version}: ${output}`)
|
||||
process.stdout.write(`${name} ${version} OK\n`)
|
||||
}
|
||||
Reference in New Issue
Block a user