Monorepo for Aesthetic.Computer
aesthetic.computer
1import { execSync } from 'child_process';
2import fs from 'fs-extra';
3import path from 'path';
4import { fileURLToPath } from 'url';
5
6// --- Configuration ---
7// Read GCP Project ID from nanos config file
8const __filename = fileURLToPath(import.meta.url);
9const __dirname = path.dirname(__filename);
10const nanosGcpConfigPath = path.resolve(__dirname, '../nanos/config-gcp.json');
11let GCLOUD_PROJECT;
12try {
13 const nanosConfig = fs.readJsonSync(nanosGcpConfigPath);
14 GCLOUD_PROJECT = nanosConfig.CloudConfig.ProjectID;
15 if (!GCLOUD_PROJECT) {
16 throw new Error('ProjectID not found in nanos/config-gcp.json');
17 }
18} catch (error) {
19 console.error(`Error reading GCLOUD_PROJECT from ${nanosGcpConfigPath}:`, error);
20 process.exit(1);
21}
22
23// Region for Cloud Run deployment
24const GCLOUD_REGION = 'us-west1';
25// Name for the Cloud Run service
26const SERVICE_NAME = 'remote-chromium';
27// Docker image name (without the registry path)
28const IMAGE_NAME = 'remote-chromium';
29// Path to the Dockerfile directory
30const DOCKERFILE_DIR = path.resolve(__dirname);
31// GCP Artifact Registry repository name (replace if you have a specific one)
32const ARTIFACT_REGISTRY_REPO = 'docker-repo'; // A common default, or choose your own
33
34// Construct the full image URI for Artifact Registry
35const IMAGE_URI = `${GCLOUD_REGION}-docker.pkg.dev/${GCLOUD_PROJECT}/${ARTIFACT_REGISTRY_REPO}/${IMAGE_NAME}:latest`;
36
37// --- Helper Functions ---
38function runCommand(command, errorMessage) {
39 try {
40 console.log(`Executing: ${command}`);
41 const output = execSync(command, { stdio: 'inherit' });
42 console.log(output ? output.toString() : '');
43 } catch (error) {
44 console.error(`Error ${errorMessage}:`, error.stderr ? error.stderr.toString() : error.message);
45 process.exit(1);
46 }
47}
48
49async function ensureGcloudAuthenticated() {
50 try {
51 execSync('gcloud auth print-access-token', { stdio: 'ignore' });
52 console.log('gcloud is already authenticated.');
53 } catch (error) {
54 console.log('gcloud authentication required. Please log in.');
55 runCommand('gcloud auth login', 'authenticating with gcloud');
56 runCommand('gcloud auth application-default login', 'setting up application default credentials');
57 }
58}
59
60async function configureDockerForGCR() {
61 console.log('Configuring Docker to use gcloud as a credential helper...');
62 runCommand(`gcloud auth configure-docker ${GCLOUD_REGION}-docker.pkg.dev`, 'configuring Docker for Artifact Registry');
63}
64
65// --- Main Orchestration Steps ---
66async function main() {
67 const mode = process.argv[2]; // Get the mode from command line arguments
68
69 if (mode === 'dev') {
70 console.log('Starting local development environment...');
71 // 1. Build the Docker image for local development
72 const localImageName = `${IMAGE_NAME}:local`;
73 console.log(`Building Docker image: ${localImageName}`);
74 runCommand(`docker build -t ${localImageName} ${DOCKERFILE_DIR}`, 'building Docker image for local dev');
75
76 // 2. Run the Docker container locally
77 console.log(`Running Docker container ${localImageName} locally on port 9222...`);
78 // Ensure no other container is using port 9222 or use a different port
79 runCommand(`docker run --rm -p 9222:9222 ${localImageName}`, 'running local Docker container');
80 console.log(`Local Chromium instance should be accessible for remote debugging on ws://localhost:9222`);
81
82 } else if (mode === 'deploy') {
83 console.log('Starting Chromium deployment to GCP Cloud Run...');
84
85 // 0. Ensure gcloud is authenticated and Docker is configured
86 await ensureGcloudAuthenticated();
87 await configureDockerForGCR();
88
89 // 1. Build the Docker image
90 console.log(`Building Docker image: ${IMAGE_URI}`);
91 runCommand(`docker build -t ${IMAGE_URI} ${DOCKERFILE_DIR}`, 'building Docker image');
92
93 // 2. Push the Docker image to GCP Artifact Registry
94 // Ensure the Artifact Registry repository exists.
95 // You might need to create it first:
96 // gcloud artifacts repositories create ${ARTIFACT_REGISTRY_REPO} --repository-format=docker --location=${GCLOUD_REGION} --description="Docker repository"
97 console.log(`Pushing image to Artifact Registry: ${IMAGE_URI}`);
98 runCommand(`docker push ${IMAGE_URI}`, 'pushing image to Artifact Registry');
99
100 // 3. Deploy to Cloud Run
101 console.log(`Deploying ${SERVICE_NAME} to Cloud Run in ${GCLOUD_REGION}...`);
102 const deployCommand = `gcloud run deploy ${SERVICE_NAME} \
103 --image ${IMAGE_URI} \
104 --platform managed \
105 --region ${GCLOUD_REGION} \
106 --port 9222 \
107 --allow-unauthenticated \
108 --project ${GCLOUD_PROJECT}`;
109 // Add --set-env-vars or other configurations as needed
110 // For example, to ensure it uses a specific user data dir if needed by screenshot.mjs interaction:
111 // --set-env-vars="USER_DATA_DIR=/tmp/chromium-user-data-cloudrun"
112
113 runCommand(deployCommand, 'deploying to Cloud Run');
114
115 console.log('Fetching service URL...');
116 runCommand(`gcloud run services describe ${SERVICE_NAME} --platform managed --region ${GCLOUD_REGION} --format='value(status.url)' --project ${GCLOUD_PROJECT}`, 'fetching service URL');
117
118 console.log(
119 `\nDeployment complete! Your remote Chromium instance should be accessible at the URL printed above.`
120 );
121 console.log(
122 `You can connect to it via WebSocket for screenshot.mjs using the wss:// version of the host (port will be 443 implicitly).`
123 );
124 } else {
125 console.error('Invalid mode. Please use "dev" or "deploy".');
126 console.log('Usage: node conductor.mjs [dev|deploy]');
127 process.exit(1);
128 }
129}
130
131main().catch(error => {
132 console.error('Deployment script failed:', error);
133 process.exit(1);
134});