mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-14 00:26:51 +08:00
chore: cli to create self signed ca to dev with domain (#12466)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Introduced a CLI command to manage local Certificate Authority (CA) and generate SSL certificates for development domains. - Added example and template files for Nginx and OpenSSL configurations to support local development with SSL. - Provided new DNS and Nginx configuration files for enhanced local development setup. - **Documentation** - Added a README with step-by-step instructions for setting up development containers and managing certificates on MacOS with OrbStack. - **Chores** - Updated ignore patterns to exclude additional development files and directories. - Enhanced example Docker Compose files with commented service configurations and new volume definitions. - Removed the Elasticsearch example Docker Compose file. - **Refactor** - Extended utility and command classes with new methods to support file operations and command execution. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -3,6 +3,7 @@ import { Cli } from 'clipanion';
|
||||
|
||||
import { BuildCommand } from './build';
|
||||
import { BundleCommand } from './bundle';
|
||||
import { CertCommand } from './cert';
|
||||
import { CleanCommand } from './clean';
|
||||
import type { CliContext } from './context';
|
||||
import { DevCommand } from './dev';
|
||||
@@ -23,6 +24,7 @@ cli.register(CleanCommand);
|
||||
cli.register(BuildCommand);
|
||||
cli.register(DevCommand);
|
||||
cli.register(BundleCommand);
|
||||
cli.register(CertCommand);
|
||||
|
||||
await cli.runExit(process.argv.slice(2), {
|
||||
workspace: new Workspace(),
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
import { type Path, ProjectRoot } from '@affine-tools/utils/path';
|
||||
|
||||
import { Command, Option } from './command';
|
||||
|
||||
const CERT_DIR = ProjectRoot.join('.docker/dev/certs');
|
||||
const CA_DIR = CERT_DIR.join('ca');
|
||||
const TEMPLATES_DIR = ProjectRoot.join('.docker/dev/templates');
|
||||
const NGINX_CONF_DIR = ProjectRoot.join('.docker/dev/nginx/conf.d');
|
||||
const CA_PEM_PATH = CA_DIR.join('affine-self-signed.pem');
|
||||
const CA_KEY_PATH = CA_DIR.join('affine-self-signed.key');
|
||||
|
||||
const CA_ORG = 'AFFiNE Dev CA Self Signed Org';
|
||||
const CA_NAME = 'AFFiNE Dev CA Self Signed CN';
|
||||
|
||||
export class CertCommand extends Command {
|
||||
static override paths = [['cert']];
|
||||
|
||||
install = Option.Boolean('--install', {
|
||||
description: 'Install the CA',
|
||||
});
|
||||
|
||||
domain = Option.String('--domain', {
|
||||
description:
|
||||
'Generate certificates for given domain. e.g. "dev.affine.fail"',
|
||||
});
|
||||
|
||||
uninstall = Option.Boolean('--uninstall', {
|
||||
description: 'Uninstall the CA',
|
||||
});
|
||||
|
||||
async execute() {
|
||||
if (this.install) {
|
||||
this.installCa();
|
||||
} else if (this.uninstall) {
|
||||
this.uninstallCa(CA_PEM_PATH);
|
||||
} else if (this.domain) {
|
||||
this.createCert(this.domain);
|
||||
}
|
||||
}
|
||||
|
||||
private createCert(domain: string) {
|
||||
if (!this.checkInstalled(CA_PEM_PATH)) {
|
||||
this.logger.error(
|
||||
'CA not installed. Please run `yarn affine cert --install` first.'
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const domainDir = CERT_DIR.join(domain);
|
||||
domainDir.rm({ recursive: true });
|
||||
domainDir.mkdir();
|
||||
NGINX_CONF_DIR.mkdir();
|
||||
|
||||
const keyPath = domainDir.join('key');
|
||||
const crtPath = domainDir.join('crt');
|
||||
const csrPath = domainDir.join('csr');
|
||||
const confPath = domainDir.join('conf');
|
||||
const nginxConfPath = NGINX_CONF_DIR.join(`${domain}.conf`);
|
||||
|
||||
const configTemp = TEMPLATES_DIR.join('openssl.conf')
|
||||
.readAsFile()
|
||||
.toString('utf-8');
|
||||
const config = configTemp.replaceAll('DEV_DOMAIN', domain);
|
||||
|
||||
confPath.writeFile(config);
|
||||
this.exec(`openssl genrsa -out ${keyPath} 2048`);
|
||||
this.exec(
|
||||
`openssl req -new -key ${keyPath} -out ${csrPath} -config ${confPath} -subj "/C=/ST=/O=/localityName=/commonName=${domain}/organizationalUnitName=/emailAddress=${domain}@affine.pro/"`
|
||||
);
|
||||
this.exec(
|
||||
`openssl x509 -req -days 1024 -in ${csrPath} -CA ${CA_PEM_PATH} -CAkey ${CA_KEY_PATH} -CAcreateserial -out ${crtPath} -extensions v3_req -extfile ${confPath}`
|
||||
);
|
||||
|
||||
const nginxConfTemp = TEMPLATES_DIR.join('nginx.conf')
|
||||
.readAsFile()
|
||||
.toString('utf-8');
|
||||
const nginxConf = nginxConfTemp.replaceAll('DEV_DOMAIN', domain);
|
||||
nginxConfPath.writeFile(nginxConf);
|
||||
}
|
||||
|
||||
private installCa() {
|
||||
if (this.checkInstalled(CA_PEM_PATH)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove old ca
|
||||
CA_PEM_PATH.rm();
|
||||
CA_KEY_PATH.rm();
|
||||
CA_DIR.mkdir();
|
||||
|
||||
this.exec(
|
||||
`openssl req -new -newkey rsa:2048 -days 1024 -nodes -x509 -subj "/C=/ST=/O=${CA_ORG}/localityName=/commonName=${CA_NAME}/organizationalUnitName=Developers/emailAddress=dev@affine.pro/" -keyout ${CA_KEY_PATH} -out ${CA_PEM_PATH}`
|
||||
);
|
||||
this.trustCa(CA_PEM_PATH);
|
||||
}
|
||||
|
||||
private trustCa(pem: Path) {
|
||||
this.logger.info(`Trusting AFFiNE Dev Self Signed CA`);
|
||||
this.exec(
|
||||
`sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ${pem}`
|
||||
);
|
||||
}
|
||||
|
||||
private uninstallCa(pem: Path) {
|
||||
if (!this.checkInstalled(pem)) {
|
||||
this.logger.error('CA not installed');
|
||||
return;
|
||||
}
|
||||
|
||||
this.exec(
|
||||
`sudo security delete-certificate -c ${CA_NAME} /Library/Keychains/System.keychain`
|
||||
);
|
||||
}
|
||||
|
||||
private checkInstalled(pem: Path) {
|
||||
if (!pem.exists()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ret = this.exec(`security verify-cert -c ${pem}`);
|
||||
|
||||
return ret.includes('...certificate verification successful.');
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AliasToPackage } from '@affine-tools/utils/distribution';
|
||||
import { Logger } from '@affine-tools/utils/logger';
|
||||
import { exec, execAsync, spawn } from '@affine-tools/utils/process';
|
||||
import { type PackageName, Workspace } from '@affine-tools/utils/workspace';
|
||||
import { Command as BaseCommand, Option } from 'clipanion';
|
||||
import inquirer from 'inquirer';
|
||||
@@ -8,9 +9,11 @@ import * as t from 'typanion';
|
||||
import type { CliContext } from './context';
|
||||
|
||||
export abstract class Command extends BaseCommand<CliContext> {
|
||||
// @ts-expect-error hack: Get the command name
|
||||
cmd = this.constructor.paths[0][0];
|
||||
|
||||
get logger() {
|
||||
// @ts-expect-error hack: Get the command name
|
||||
return new Logger(this.constructor.paths[0][0]);
|
||||
return new Logger(this.cmd);
|
||||
}
|
||||
|
||||
get workspace() {
|
||||
@@ -20,6 +23,10 @@ export abstract class Command extends BaseCommand<CliContext> {
|
||||
set workspace(workspace: Workspace) {
|
||||
this.context.workspace = workspace;
|
||||
}
|
||||
|
||||
exec = exec.bind(null, this.cmd);
|
||||
execAsync = execAsync.bind(null, this.cmd);
|
||||
spawn = spawn.bind(null, this.cmd);
|
||||
}
|
||||
|
||||
export abstract class PackageCommand extends Command {
|
||||
|
||||
Reference in New Issue
Block a user