Start Aurora Native runtime foundation

This commit is contained in:
reaper
2026-08-22 02:55:04 -05:00
parent 8b23e5f590
commit 854ae3a2ad
7 changed files with 138 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
# Aurora Native Runtime
Aurora Native is a per-platform PHP development engine that does not require Docker. The existing container engine remains supported while native runtime bundles are developed and verified.
## Runtime targets
- Linux: x64 and arm64
- macOS: Intel and Apple silicon
- Windows: x64, with arm64 evaluated after the first stable runtime
Each signed runtime bundle contains a `runtime.json` manifest plus versioned executables for PHP, web servers, databases, Node.js, and application tooling. Bundles install below Aurora's user-data directory and never modify system PHP or database installations.
## 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.
## Delivery sequence
1. Runtime manifest, platform detection, checksum verification, and engine abstraction.
2. Native process supervisor and loopback port allocator.
3. Linux x64 bundle with PHP 8.4, nginx, and MariaDB 11.8.
4. WordPress provisioning, lifecycle, logs, database import/export, and Adminer.
5. macOS arm64/x64 and Windows x64 bundles.
6. Additional PHP/database versions, Apache, Drupal, Node.js, and developer services.
Native project creation must stay disabled until the platform bundle passes executable, service-health, database, routing, and cleanup checks. Existing projects default to the container engine for backward compatibility.
+2
View File
@@ -11,6 +11,7 @@ import { registerCreateIpc } from './ipc/create'
import { registerWindowIpc } from './ipc/window'
import { registerSecretsIpc } from './ipc/secrets'
import { registerRemoteIpc } from './ipc/remote'
import { registerRuntimeIpc } from './ipc/runtime'
import { killAllRunningCommands, powerOffAllProjects } from './commandRunner'
import { startAutomaticUpdates } from './updater'
@@ -70,6 +71,7 @@ app.whenReady().then(() => {
registerWindowIpc()
registerSecretsIpc()
registerRemoteIpc()
registerRuntimeIpc()
createWindow()
startAutomaticUpdates()
+6
View File
@@ -0,0 +1,6 @@
import { ipcMain } from 'electron'
import { getRuntimeStatus } from '../nativeRuntime'
export function registerRuntimeIpc(): void {
ipcMain.handle('runtime:status', () => getRuntimeStatus())
}
+10
View File
@@ -0,0 +1,10 @@
import { describe, expect, it } from 'vitest'
import { validateNativeRuntimeManifest } from './nativeRuntime'
const valid = { schema: 1, runtimeVersion: '0.1.0', platform: 'linux', arch: 'x64', components: [{ id: 'php', version: '8.4.12', executable: 'php/8.4/bin/php', sha256: 'a'.repeat(64) }] }
describe('Aurora Native runtime manifest', () => {
it('accepts a signed-component-shaped platform manifest', () => expect(validateNativeRuntimeManifest(valid)).toMatchObject(valid))
it('rejects executable paths that escape the runtime', () => expect(() => validateNativeRuntimeManifest({ ...valid, components: [{ ...valid.components[0], executable: '../php' }] })).toThrow(/Unsafe executable path/))
it('rejects malformed component checksums', () => expect(() => validateNativeRuntimeManifest({ ...valid, components: [{ ...valid.components[0], sha256: 'bad' }] })).toThrow(/checksum/))
})
+68
View File
@@ -0,0 +1,68 @@
import { app } from 'electron'
import { execFile } from 'child_process'
import { access, readFile } from 'fs/promises'
import { isAbsolute, join, resolve, sep } from 'path'
import { promisify } from 'util'
import type { AuroraNativeRuntimeManifest, AuroraRuntimeStatus } from '../shared/types'
const execFileAsync = promisify(execFile)
const supportedPlatforms = new Set(['linux', 'darwin', 'win32'])
const supportedArchitectures = new Set(['x64', 'arm64'])
const componentIds = new Set(['php', 'nginx', 'apache', 'mariadb', 'mysql', 'postgres', 'node', 'composer', 'wp-cli', 'drush'])
export function validateNativeRuntimeManifest(value: unknown): AuroraNativeRuntimeManifest {
if (!value || typeof value !== 'object') throw new Error('Runtime manifest must be an object.')
const manifest = value as Record<string, unknown>
if (manifest.schema !== 1) throw new Error('Unsupported native runtime manifest schema.')
if (typeof manifest.runtimeVersion !== 'string' || !/^\d+\.\d+\.\d+(?:[-+][a-zA-Z0-9.-]+)?$/.test(manifest.runtimeVersion)) throw new Error('Invalid native runtime version.')
if (typeof manifest.platform !== 'string' || !supportedPlatforms.has(manifest.platform)) throw new Error('Unsupported native runtime platform.')
if (typeof manifest.arch !== 'string' || !supportedArchitectures.has(manifest.arch)) throw new Error('Unsupported native runtime architecture.')
if (!Array.isArray(manifest.components) || manifest.components.length === 0) throw new Error('Native runtime manifest has no components.')
const components = manifest.components.map((item) => {
if (!item || typeof item !== 'object') throw new Error('Invalid native runtime component.')
const component = item as Record<string, unknown>
if (typeof component.id !== 'string' || !componentIds.has(component.id)) throw new Error('Unknown native runtime component.')
if (typeof component.version !== 'string' || !component.version.trim()) throw new Error(`Missing version for ${component.id}.`)
if (typeof component.executable !== 'string' || !component.executable || isAbsolute(component.executable) || component.executable.split(/[\\/]/).includes('..')) throw new Error(`Unsafe executable path for ${component.id}.`)
if (typeof component.sha256 !== 'string' || !/^[a-f0-9]{64}$/i.test(component.sha256)) throw new Error(`Invalid checksum for ${component.id}.`)
return component as unknown as AuroraNativeRuntimeManifest['components'][number]
})
return { schema: 1, runtimeVersion: manifest.runtimeVersion, platform: manifest.platform as AuroraNativeRuntimeManifest['platform'], arch: manifest.arch as AuroraNativeRuntimeManifest['arch'], components }
}
function runtimeRoot(): string {
return join(app.getPath('userData'), 'runtimes', `${process.platform}-${process.arch}`)
}
async function nativeStatus(): Promise<AuroraRuntimeStatus['native']> {
if (!supportedPlatforms.has(process.platform) || !supportedArchitectures.has(process.arch)) return { available: false, platform: process.platform, arch: process.arch, components: [], reason: 'This platform does not have an Aurora Native runtime target yet.' }
const root = runtimeRoot()
try {
const manifest = validateNativeRuntimeManifest(JSON.parse(await readFile(join(root, 'runtime.json'), 'utf8')))
if (manifest.platform !== process.platform || manifest.arch !== process.arch) throw new Error('The installed runtime targets a different platform.')
const canonicalRoot = resolve(root)
for (const component of manifest.components) {
const executable = resolve(root, component.executable)
if (executable !== canonicalRoot && !executable.startsWith(`${canonicalRoot}${sep}`)) throw new Error(`Unsafe executable path for ${component.id}.`)
await access(executable)
}
return { available: true, platform: process.platform, arch: process.arch, runtimeVersion: manifest.runtimeVersion, components: manifest.components }
} catch (error) {
return { available: false, platform: process.platform, arch: process.arch, components: [], reason: error instanceof Error && !error.message.includes('ENOENT') ? error.message : 'Aurora Native runtime is not installed yet.' }
}
}
async function containerStatus(): Promise<AuroraRuntimeStatus['container']> {
for (const provider of ['docker', 'podman'] as const) {
try {
const { stdout } = await execFileAsync(provider, ['--version'], { timeout: 5000 })
return { available: true, provider, version: stdout.trim() }
} catch { /* try next provider */ }
}
return { available: false, provider: null, reason: 'Docker or Podman was not detected.' }
}
export async function getRuntimeStatus(): Promise<AuroraRuntimeStatus> {
const [container, native] = await Promise.all([containerStatus(), nativeStatus()])
return { selectedEngine: native.available ? 'native' : 'container', container, native }
}
+4
View File
@@ -11,6 +11,7 @@ import type {
AuroraSiteCredentials,
AuroraRemoteSiteProfile,
AuroraRemoteSiteStatus,
AuroraRuntimeStatus,
AuroraStackOptions,
EnvironmentUpdate,
LogDataEvent,
@@ -155,6 +156,9 @@ const api = {
pull: (operationId: string, name: string): Promise<void> =>
ipcRenderer.invoke('remote:pull', operationId, name)
},
runtime: {
status: (): Promise<AuroraRuntimeStatus> => ipcRenderer.invoke('runtime:status')
},
zoom: {
in: (): Promise<number> => ipcRenderer.invoke('window:zoomIn'),
out: (): Promise<number> => ipcRenderer.invoke('window:zoomOut'),
+22
View File
@@ -1,4 +1,26 @@
export type ProjectStatus = 'running' | 'stopped' | 'paused' | 'starting' | 'stopping' | string
export type AuroraRuntimeEngine = 'container' | 'native'
export interface AuroraNativeRuntimeComponent {
id: 'php' | 'nginx' | 'apache' | 'mariadb' | 'mysql' | 'postgres' | 'node' | 'composer' | 'wp-cli' | 'drush'
version: string
executable: string
sha256: string
}
export interface AuroraNativeRuntimeManifest {
schema: 1
runtimeVersion: string
platform: 'linux' | 'darwin' | 'win32'
arch: 'x64' | 'arm64'
components: AuroraNativeRuntimeComponent[]
}
export interface AuroraRuntimeStatus {
selectedEngine: AuroraRuntimeEngine
container: { available: boolean; provider: 'docker' | 'podman' | null; version?: string; reason?: string }
native: { available: boolean; platform: string; arch: string; runtimeVersion?: string; components: AuroraNativeRuntimeComponent[]; reason?: string }
}
export interface AuroraProjectSummary {
name: string