diff --git a/resources/js/electron-plugin/src/server/php.ts b/resources/js/electron-plugin/src/server/php.ts index 92208e3a..2240073a 100644 --- a/resources/js/electron-plugin/src/server/php.ts +++ b/resources/js/electron-plugin/src/server/php.ts @@ -1,17 +1,17 @@ -import {mkdirSync, statSync, writeFileSync, existsSync} from 'fs' -import {copySync} from 'fs-extra' -import Store from 'electron-store' -import {promisify} from 'util' -import {join} from 'path' -import {app} from 'electron' -import {exec, execFile, spawn} from 'child_process' +import { mkdirSync, statSync, writeFileSync, existsSync } from 'fs'; +import { copySync } from 'fs-extra'; +import Store from 'electron-store'; +import { promisify } from 'util'; +import { join } from 'path'; +import { app } from 'electron'; +import { exec, execFile, spawn } from 'child_process'; import state from "./state"; import getPort from 'get-port'; import { ProcessResult } from "./ProcessResult"; -const storagePath = join(app.getPath('userData'), 'storage') -const databasePath = join(app.getPath('userData'), 'database') -const databaseFile = join(databasePath, 'database.sqlite') +const storagePath = join(app.getPath('userData'), 'storage'); +const databasePath = join(app.getPath('userData'), 'database'); +const databaseFile = join(databasePath, 'database.sqlite'); const argumentEnv = getArgumentEnv(); const appPath = getAppPath(); @@ -23,18 +23,24 @@ async function getPhpPort() { } async function retrievePhpIniSettings() { - const env = { - NATIVEPHP_RUNNING: 'true', - NATIVEPHP_STORAGE_PATH: storagePath, - NATIVEPHP_DATABASE_PATH: databaseFile, - }; - - const phpOptions = { - cwd: appPath, - env - }; - - return await promisify(execFile)(state.php, ['artisan', 'native:php-ini'], phpOptions); + const env = { + NATIVEPHP_RUNNING: 'true', + NATIVEPHP_STORAGE_PATH: storagePath, + NATIVEPHP_DATABASE_PATH: databaseFile, + }; + + const phpOptions = { + cwd: appPath, + env, + }; + + let command = ['artisan', 'native:php-ini']; + + if (runningSecureBuild()) { + command.unshift(join(appPath, 'build', '__nativephp_app_bundle')); + } + + return await promisify(execFile)(state.php, command, phpOptions); } async function retrieveNativePHPConfig() { @@ -46,25 +52,39 @@ async function retrieveNativePHPConfig() { const phpOptions = { cwd: appPath, - env + env, }; - return await promisify(execFile)(state.php, ['artisan', 'native:config'], phpOptions); + let command = ['artisan', 'native:config']; + + if (runningSecureBuild()) { + command.unshift(join(appPath, 'build', '__nativephp_app_bundle')); + } + + return await promisify(execFile)(state.php, command, phpOptions); } function callPhp(args, options, phpIniSettings = {}) { - let defaultIniSettings = { - 'memory_limit': '512M', - 'curl.cainfo': state.caCert, - 'openssl.cafile': state.caCert + if (args[0] === 'artisan' && runningSecureBuild()) { + args.unshift(join(appPath, 'build', '__nativephp_app_bundle')); } + let defaultIniSettings = { + 'memory_limit': '512M', + 'curl.cainfo': state.caCert, + 'openssl.cafile': state.caCert, + }; + let iniSettings = Object.assign(defaultIniSettings, phpIniSettings); Object.keys(iniSettings).forEach(key => { - args.unshift('-d', `${key}=${iniSettings[key]}`); + args.unshift('-d', `${key}=${iniSettings[key]}`); }); + if (parseInt(process.env.SHELL_VERBOSITY) > 0) { + console.log('Calling PHP', state.php, args); + } + return spawn( state.php, args, @@ -72,7 +92,7 @@ function callPhp(args, options, phpIniSettings = {}) { cwd: options.cwd, env: { ...process.env, - ...options.env + ...options.env, }, } ); @@ -87,6 +107,7 @@ function getArgumentEnv() { } = { }; + envArgs.forEach(arg => { const [key, value] = arg.slice(6).split('='); env[key] = value; @@ -96,26 +117,27 @@ function getArgumentEnv() { } function getAppPath() { - let appPath = join(__dirname, '../../resources/app/').replace('app.asar', 'app.asar.unpacked') + let appPath = join(__dirname, '../../resources/app/').replace('app.asar', 'app.asar.unpacked'); if (process.env.NODE_ENV === 'development' || argumentEnv.TESTING == 1) { appPath = process.env.APP_PATH || argumentEnv.APP_PATH; } + return appPath; } function ensureAppFoldersAreAvailable() { if (! existsSync(storagePath) || process.env.NODE_ENV === 'development') { - copySync(join(appPath, 'storage'), storagePath) + copySync(join(appPath, 'storage'), storagePath); } - mkdirSync(databasePath, {recursive: true}) + mkdirSync(databasePath, {recursive: true}); // Create a database file if it doesn't exist try { - statSync(databaseFile) + statSync(databaseFile); } catch (error) { - writeFileSync(databaseFile, '') + writeFileSync(databaseFile, ''); } } @@ -127,12 +149,12 @@ function startQueueWorker(secret, apiPort, phpIniSettings = {}) { NATIVEPHP_DATABASE_PATH: databaseFile, NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, NATIVEPHP_RUNNING: true, - NATIVEPHP_SECRET: secret + NATIVEPHP_SECRET: secret, }; const phpOptions = { cwd: appPath, - env + env, }; return callPhp(['artisan', 'queue:work'], phpOptions, phpIniSettings); @@ -143,99 +165,113 @@ function startScheduler(secret, apiPort, phpIniSettings = {}) { const phpOptions = { cwd: appPath, - env + env, }; return callPhp(['artisan', 'schedule:run'], phpOptions, phpIniSettings); } function getPath(name: string) { - try { - // @ts-ignore - return app.getPath(name); - } catch (error) { - return ''; - } + try { + // @ts-ignore + return app.getPath(name); + } catch (error) { + return ''; + } } function getDefaultEnvironmentVariables(secret, apiPort) { - return { - APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production', - APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false', - NATIVEPHP_STORAGE_PATH: storagePath, - NATIVEPHP_DATABASE_PATH: databaseFile, - NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, - NATIVEPHP_RUNNING: true, - NATIVEPHP_SECRET: secret, - NATIVEPHP_USER_HOME_PATH: getPath('home'), - NATIVEPHP_APP_DATA_PATH: getPath('appData'), - NATIVEPHP_USER_DATA_PATH: getPath('userData'), - NATIVEPHP_DESKTOP_PATH: getPath('desktop'), - NATIVEPHP_DOCUMENTS_PATH: getPath('documents'), - NATIVEPHP_DOWNLOADS_PATH: getPath('downloads'), - NATIVEPHP_MUSIC_PATH: getPath('music'), - NATIVEPHP_PICTURES_PATH: getPath('pictures'), - NATIVEPHP_VIDEOS_PATH: getPath('videos'), - NATIVEPHP_RECENT_PATH: getPath('recent'), - }; + return { + APP_ENV: process.env.NODE_ENV === 'development' ? 'local' : 'production', + APP_DEBUG: process.env.NODE_ENV === 'development' ? 'true' : 'false', + LARAVEL_STORAGE_PATH: storagePath, + NATIVEPHP_STORAGE_PATH: storagePath, + NATIVEPHP_DATABASE_PATH: databaseFile, + NATIVEPHP_API_URL: `http://localhost:${apiPort}/api/`, + NATIVEPHP_RUNNING: true, + NATIVEPHP_SECRET: secret, + NATIVEPHP_USER_HOME_PATH: getPath('home'), + NATIVEPHP_APP_DATA_PATH: getPath('appData'), + NATIVEPHP_USER_DATA_PATH: getPath('userData'), + NATIVEPHP_DESKTOP_PATH: getPath('desktop'), + NATIVEPHP_DOCUMENTS_PATH: getPath('documents'), + NATIVEPHP_DOWNLOADS_PATH: getPath('downloads'), + NATIVEPHP_MUSIC_PATH: getPath('music'), + NATIVEPHP_PICTURES_PATH: getPath('pictures'), + NATIVEPHP_VIDEOS_PATH: getPath('videos'), + NATIVEPHP_RECENT_PATH: getPath('recent'), + }; +} + +function runningSecureBuild() { + return existsSync(join(appPath, 'build', '__nativephp_app_bundle')) } function serveApp(secret, apiPort, phpIniSettings): Promise { return new Promise(async (resolve, reject) => { const appPath = getAppPath(); - console.log('Starting PHP server...', `${state.php} artisan serve`, appPath, phpIniSettings) + console.log('Starting PHP server...', `${state.php} artisan serve`, appPath, phpIniSettings); ensureAppFoldersAreAvailable(); - console.log('Making sure app folders are available') + console.log('Making sure app folders are available'); const env = getDefaultEnvironmentVariables(secret, apiPort); const phpOptions = { cwd: appPath, - env + env, }; const store = new Store(); // Make sure the storage path is linked - as people can move the app around, we // need to run this every time the app starts - callPhp(['artisan', 'storage:link', '--force'], phpOptions, phpIniSettings) + if (! runningSecureBuild()) { + callPhp(['artisan', 'storage:link', '--force'], phpOptions, phpIniSettings) + } // Migrate the database - if (store.get('migrated_version') !== app.getVersion() && process.env.NODE_ENV !== 'development') { - console.log('Migrating database...') - callPhp(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings) - store.set('migrated_version', app.getVersion()) + if (shouldMigrateDatabase(store)) { + console.log('Migrating database...'); + callPhp(['artisan', 'migrate', '--force'], phpOptions, phpIniSettings); + store.set('migrated_version', app.getVersion()); } if (process.env.NODE_ENV === 'development') { - console.log('Skipping Database migration while in development.') - console.log('You may migrate manually by running: php artisan native:migrate') + console.log('Skipping Database migration while in development.'); + console.log('You may migrate manually by running: php artisan native:migrate'); } const phpPort = await getPhpPort(); - const serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php') + let serverPath = join(appPath, 'build', '__nativephp_app_bundle'); + + if (! runningSecureBuild()) { + console.log('* * * Running from source * * *'); + serverPath = join(appPath, 'vendor', 'laravel', 'framework', 'src', 'Illuminate', 'Foundation', 'resources', 'server.php'); + } + const phpServer = callPhp(['-S', `127.0.0.1:${phpPort}`, serverPath], { cwd: join(appPath, 'public'), - env - }, phpIniSettings) + env, + }, phpIniSettings); - const portRegex = /Development Server \(.*:([0-9]+)\) started/gm + const portRegex = /Development Server \(.*:([0-9]+)\) started/gm; phpServer.stdout.on('data', (data) => { - const match = portRegex.exec(data.toString()) + const match = portRegex.exec(data.toString()); + if (match) { - console.log("PHP Server started on port: ", match[1]) - const port = parseInt(match[1]) + console.log("PHP Server started on port: ", match[1]); + const port = parseInt(match[1]); resolve({ port, - process: phpServer - }) + process: phpServer, + }); } - }) + }); phpServer.stderr.on('data', (data) => { const error = data.toString(); @@ -246,7 +282,7 @@ function serveApp(secret, apiPort, phpIniSettings): Promise { console.log("PHP Server started on port: ", port); resolve({ port, - process: phpServer + process: phpServer, }); } else { // 27 is the length of the php -S output preamble @@ -262,9 +298,14 @@ function serveApp(secret, apiPort, phpIniSettings): Promise { }); phpServer.on('error', (error) => { - reject(error) - }) + reject(error); + }); }) } -export {startQueueWorker, startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings} +function shouldMigrateDatabase(store) { + return store.get('migrated_version') !== app.getVersion() + && process.env.NODE_ENV !== 'development'; +} + +export { startQueueWorker, startScheduler, serveApp, getAppPath, retrieveNativePHPConfig, retrievePhpIniSettings };