Start Aurora Native runtime foundation
This commit is contained in:
@@ -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.
|
||||||
@@ -11,6 +11,7 @@ import { registerCreateIpc } from './ipc/create'
|
|||||||
import { registerWindowIpc } from './ipc/window'
|
import { registerWindowIpc } from './ipc/window'
|
||||||
import { registerSecretsIpc } from './ipc/secrets'
|
import { registerSecretsIpc } from './ipc/secrets'
|
||||||
import { registerRemoteIpc } from './ipc/remote'
|
import { registerRemoteIpc } from './ipc/remote'
|
||||||
|
import { registerRuntimeIpc } from './ipc/runtime'
|
||||||
import { killAllRunningCommands, powerOffAllProjects } from './commandRunner'
|
import { killAllRunningCommands, powerOffAllProjects } from './commandRunner'
|
||||||
import { startAutomaticUpdates } from './updater'
|
import { startAutomaticUpdates } from './updater'
|
||||||
|
|
||||||
@@ -70,6 +71,7 @@ app.whenReady().then(() => {
|
|||||||
registerWindowIpc()
|
registerWindowIpc()
|
||||||
registerSecretsIpc()
|
registerSecretsIpc()
|
||||||
registerRemoteIpc()
|
registerRemoteIpc()
|
||||||
|
registerRuntimeIpc()
|
||||||
|
|
||||||
createWindow()
|
createWindow()
|
||||||
startAutomaticUpdates()
|
startAutomaticUpdates()
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { ipcMain } from 'electron'
|
||||||
|
import { getRuntimeStatus } from '../nativeRuntime'
|
||||||
|
|
||||||
|
export function registerRuntimeIpc(): void {
|
||||||
|
ipcMain.handle('runtime:status', () => getRuntimeStatus())
|
||||||
|
}
|
||||||
@@ -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/))
|
||||||
|
})
|
||||||
@@ -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 }
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
AuroraSiteCredentials,
|
AuroraSiteCredentials,
|
||||||
AuroraRemoteSiteProfile,
|
AuroraRemoteSiteProfile,
|
||||||
AuroraRemoteSiteStatus,
|
AuroraRemoteSiteStatus,
|
||||||
|
AuroraRuntimeStatus,
|
||||||
AuroraStackOptions,
|
AuroraStackOptions,
|
||||||
EnvironmentUpdate,
|
EnvironmentUpdate,
|
||||||
LogDataEvent,
|
LogDataEvent,
|
||||||
@@ -155,6 +156,9 @@ const api = {
|
|||||||
pull: (operationId: string, name: string): Promise<void> =>
|
pull: (operationId: string, name: string): Promise<void> =>
|
||||||
ipcRenderer.invoke('remote:pull', operationId, name)
|
ipcRenderer.invoke('remote:pull', operationId, name)
|
||||||
},
|
},
|
||||||
|
runtime: {
|
||||||
|
status: (): Promise<AuroraRuntimeStatus> => ipcRenderer.invoke('runtime:status')
|
||||||
|
},
|
||||||
zoom: {
|
zoom: {
|
||||||
in: (): Promise<number> => ipcRenderer.invoke('window:zoomIn'),
|
in: (): Promise<number> => ipcRenderer.invoke('window:zoomIn'),
|
||||||
out: (): Promise<number> => ipcRenderer.invoke('window:zoomOut'),
|
out: (): Promise<number> => ipcRenderer.invoke('window:zoomOut'),
|
||||||
|
|||||||
@@ -1,4 +1,26 @@
|
|||||||
export type ProjectStatus = 'running' | 'stopped' | 'paused' | 'starting' | 'stopping' | string
|
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 {
|
export interface AuroraProjectSummary {
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
Reference in New Issue
Block a user