Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@ vendor/*
# Include the vendored copy of wp-now in our repo
!vendor/wp-now

# CLI npm artifacts
cli/vendor/
cli/package-lock.json

# Build output
dist

Expand Down
66 changes: 66 additions & 0 deletions cli/commands/site/start.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import path from 'path';
import { __ } from '@wordpress/i18n';
import { PreviewCommandLoggerAction as LoggerAction } from 'common/logger-actions';
import {
isDaemonRunning,
startDaemon,
isProxyProcessRunning,
startProxyProcess,
} from 'cli/lib/pm2-manager';
import { isRunningAsRoot, getElevatedPrivilegesMessage } from 'cli/lib/sudo-exec';
import { Logger, LoggerError } from 'cli/logger';
import { StudioArgv } from 'cli/types';

export async function runCommand(): Promise< void > {
const logger = new Logger< LoggerAction >();

try {
// Step 1: Ensure PM2 daemon is running
if ( ! isDaemonRunning() ) {
logger.reportStart( LoggerAction.LOAD, __( 'Starting PM2 daemon...' ) );
await startDaemon();
logger.reportSuccess( __( 'PM2 daemon started' ) );
}

// Step 2: Check if proxy is already running
const isRunning = await isProxyProcessRunning();
if ( isRunning ) {
logger.reportSuccess( __( 'HTTP proxy already running' ) );
return;
}

// Step 3: Check for elevated privileges
if ( ! isRunningAsRoot() ) {
throw new Error( getElevatedPrivilegesMessage() );
}

// Step 4: Start proxy via PM2
logger.reportStart( LoggerAction.LOAD, __( 'Starting HTTP proxy server...' ) );

// Get the proxy daemon path (cli/proxy-daemon.ts compiled to dist)
// __dirname is dist/cli when running the bundled CLI
const proxyDaemonPath = path.resolve( __dirname, 'proxy-daemon.js' );

await startProxyProcess( proxyDaemonPath );

logger.reportSuccess( __( 'HTTP proxy server started' ) );
} catch ( error ) {
if ( error instanceof LoggerError ) {
logger.reportError( error );
} else {
const loggerError = new LoggerError( __( 'Failed to start site infrastructure' ), error );
logger.reportError( loggerError );
}
process.exit( 1 );
}
}

export const registerCommand = ( yargs: StudioArgv ) => {
return yargs.command( {
command: 'start',
describe: __( 'Start the HTTP proxy for custom domains (requires sudo)' ),
handler: async () => {
await runCommand();
},
} );
};
2 changes: 2 additions & 0 deletions cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { registerCommand as registerDeleteCommand } from 'cli/commands/preview/d
import { registerCommand as registerListCommand } from 'cli/commands/preview/list';
import { registerCommand as registerUpdateCommand } from 'cli/commands/preview/update';
import { registerCommand as registerSiteListCommand } from 'cli/commands/site/list';
import { registerCommand as registerSiteStartCommand } from 'cli/commands/site/start';
import { readAppdata } from 'cli/lib/appdata';
import { loadTranslations } from 'cli/lib/i18n';
import { bumpAggregatedUniqueStat } from 'cli/lib/stats';
Expand Down Expand Up @@ -71,6 +72,7 @@ async function main() {
hidden: true,
} );
registerSiteListCommand( sitesYargs );
registerSiteStartCommand( sitesYargs );
sitesYargs.demandCommand( 1, __( 'You must provide a valid command' ) );
} );
}
Expand Down
13 changes: 2 additions & 11 deletions src/lib/certificate-manager.ts → cli/lib/certificate-manager.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { shell } from 'electron';
import { execFile } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import { domainToASCII } from 'node:url';
import { promisify } from 'node:util';
import * as Sentry from '@sentry/electron/main';
import sudo from '@vscode/sudo-prompt';
import forge from 'node-forge';
import { getUserDataCertificatesPath } from 'src/storage/paths';
import { getAppdataDirectory } from 'cli/lib/appdata';

const execFilePromise = promisify( execFile );

Expand Down Expand Up @@ -68,7 +66,7 @@ function createNameConstraintsExtension( domains: string[] ) {
const CA_NAME = 'WordPress Studio CA';
const CA_CERT_VALIDITY_DAYS = 3650; // 10 years
const SITE_CERT_VALIDITY_DAYS = 825; // a little over 2 years
const CERT_DIRECTORY = getUserDataCertificatesPath();
const CERT_DIRECTORY = path.join( getAppdataDirectory(), 'certificates' );
const CA_CERT_PATH = path.join( CERT_DIRECTORY, 'studio-ca.crt' );
const CA_KEY_PATH = path.join( CERT_DIRECTORY, 'studio-ca.key' );

Expand Down Expand Up @@ -149,10 +147,6 @@ export async function ensureRootCA(): Promise< { cert: string; key: string } > {
return { cert: certPem, key: keyPem };
}

export async function openCertificate() {
shell.showItemInFolder( CA_CERT_PATH );
}

/**
* Checks if the root CA certificate is already trusted by the system
* @returns A promise that resolves to true if the certificate is trusted, false otherwise
Expand Down Expand Up @@ -223,7 +217,6 @@ export async function trustRootCA(): Promise< void > {
console.error( 'Unsupported platform for automatic certificate trust:', platform );
}
} catch ( error ) {
Sentry.captureException( error );
console.error( 'Failed to trust root CA:', error );
throw error;
}
Expand Down Expand Up @@ -307,7 +300,6 @@ export async function generateSiteCertificate(

return { cert: certPem, key: keyPem };
} catch ( error ) {
Sentry.captureException( error );
console.error( `Failed to generate certificate for ${ domain }:`, error );
throw error;
}
Expand All @@ -332,7 +324,6 @@ export function deleteSiteCertificate( domain: string ): boolean {

return deletedFiles;
} catch ( error ) {
Sentry.captureException( error );
console.error( `Failed to delete certificate for ${ domain }:`, error );
return false;
}
Expand Down
Loading
Loading