@akash-chowdhury-24/deployhub 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +215 -5
- package/package.json +4 -3
- package/src/commands/doctor.js +168 -30
- package/src/commands/init.js +62 -222
- package/src/commands/rollback.js +1 -1
- package/src/core/config.js +15 -0
- package/src/deployment/deployment-env.js +625 -0
- package/src/deployment/init-helpers.js +389 -0
- package/src/deployment/init-prompts.js +432 -0
- package/src/deployment/providers/azure-vm.js +85 -2
- package/src/deployment/providers/docker.js +101 -8
- package/src/deployment/providers/ec2.js +88 -2
- package/src/deployment/providers/gcp-vm.js +89 -2
- package/src/deployment/providers/kubernetes.js +104 -7
- package/src/deployment/providers/ssh.js +21 -4
- package/src/utils/github-actions.js +24 -37
- package/src/{rollback → utils/rollback}/engine.js +6 -6
package/README.md
CHANGED
|
@@ -585,6 +585,207 @@ You can enable **multiple providers** — DeployHub uploads to all of them in pa
|
|
|
585
585
|
|
|
586
586
|
---
|
|
587
587
|
|
|
588
|
+
## Choosing a deployment method
|
|
589
|
+
|
|
590
|
+
DeployHub supports six deployment targets. Pick based on what infrastructure you already have — DeployHub does not provision servers, VMs, or clusters for you.
|
|
591
|
+
|
|
592
|
+
| Method | Best for | You need already |
|
|
593
|
+
|--------|----------|------------------|
|
|
594
|
+
| **ssh** | Any Linux VPS or bare-metal server you control | Server with SSH, key pair, app runtime |
|
|
595
|
+
| **docker** | Containerized apps (Dockerfile or docker-compose.yml) | Docker locally or on a remote host |
|
|
596
|
+
| **ec2** | AWS users with an existing EC2 instance | Running EC2 instance, security group, key pair |
|
|
597
|
+
| **azure-vm** | Azure users with an existing virtual machine | Running Azure VM, NSG allowing SSH |
|
|
598
|
+
| **gcp-vm** | GCP users with an existing Compute Engine VM | Running VM, firewall rule for SSH, metadata SSH key |
|
|
599
|
+
| **kubernetes** | Teams with an existing K8s cluster | Cluster, kubectl access, manifests in repo |
|
|
600
|
+
|
|
601
|
+
---
|
|
602
|
+
|
|
603
|
+
## Deployment method guides
|
|
604
|
+
|
|
605
|
+
Each method below follows the same structure: **prerequisites** (before `deployhub init`), **what DeployHub automates**, **after init** (matches terminal output), and a **variable reference**.
|
|
606
|
+
|
|
607
|
+
### SSH
|
|
608
|
+
|
|
609
|
+
**Prerequisites (before `deployhub init`):**
|
|
610
|
+
- [ ] A Linux server with SSH enabled
|
|
611
|
+
- [ ] Private SSH key file (.pem/.key) and public key in `authorized_keys`
|
|
612
|
+
- [ ] Port 22 open in firewall for your IP
|
|
613
|
+
- [ ] App runtime on server (Node.js, Python, etc.) for backends
|
|
614
|
+
|
|
615
|
+
**What DeployHub automates:**
|
|
616
|
+
- Complete `.env.example` with commented variables
|
|
617
|
+
- SSH key permission check (offers to `chmod 600`)
|
|
618
|
+
- SSH connectivity test during `init`
|
|
619
|
+
- Artifact upload, extract, app restart (PM2, gunicorn, etc.)
|
|
620
|
+
|
|
621
|
+
**After `init`:**
|
|
622
|
+
1. Ensure port 22 is open in your server firewall
|
|
623
|
+
2. Copy `.env.example` → `.env`; set `SSH_HOST`, `SSH_USER`, `SSH_KEY_PATH`
|
|
624
|
+
3. Add GitHub Secrets: `SSH_HOST`, `SSH_USER`, `SSH_KEY` (paste private key for CI)
|
|
625
|
+
4. Run `deployhub doctor`
|
|
626
|
+
5. `git push origin main`
|
|
627
|
+
|
|
628
|
+
| Variable | Description | Example | Where to get it |
|
|
629
|
+
|----------|-------------|---------|-----------------|
|
|
630
|
+
| `SSH_HOST` | Server IP or hostname | `203.0.113.10` | Your hosting provider dashboard |
|
|
631
|
+
| `SSH_USER` | SSH login user | `ubuntu` | AMI/image docs (Ubuntu→ubuntu, Amazon Linux→ec2-user) |
|
|
632
|
+
| `SSH_KEY_PATH` | Path to private key file | `~/.ssh/my-key.pem` | Downloaded when server was created |
|
|
633
|
+
| `SSH_SSH_PORT` | SSH port (optional) | `22` | Server SSH config |
|
|
634
|
+
| `SSH_DEPLOY_PATH` | Remote deploy directory | `/var/www/my-app` | Your server layout |
|
|
635
|
+
| `SSH_APP_NAME` | PM2 process name (backend) | `my-api` | Your choice |
|
|
636
|
+
| `SSH_PORT` | App listen port (backend) | `3000` | Your app config |
|
|
637
|
+
| `SSH_KEY` | Private key contents (CI only) | `-----BEGIN...` | Same key as `SSH_KEY_PATH` |
|
|
638
|
+
|
|
639
|
+
### Docker
|
|
640
|
+
|
|
641
|
+
**Prerequisites:**
|
|
642
|
+
- [ ] Docker installed (`docker --version` works)
|
|
643
|
+
- [ ] `Dockerfile` or `docker-compose.yml` in project
|
|
644
|
+
- [ ] Registry account if pushing private images
|
|
645
|
+
|
|
646
|
+
**What DeployHub automates:**
|
|
647
|
+
- `.env.example` for image name, registry, remote `DOCKER_HOST`
|
|
648
|
+
- Docker daemon connectivity test during `init`
|
|
649
|
+
- `docker compose up` or build/push/run during deploy
|
|
650
|
+
|
|
651
|
+
**After `init`:**
|
|
652
|
+
1. Set `DOCKER_IMAGE_NAME` in `.env`
|
|
653
|
+
2. For private registries: set `DOCKER_REGISTRY_USERNAME` and `DOCKER_REGISTRY_TOKEN`
|
|
654
|
+
3. For remote Docker: set `DOCKER_HOST` (e.g. `ssh://ubuntu@203.0.113.10`)
|
|
655
|
+
4. Run `deployhub doctor`, then `git push origin main`
|
|
656
|
+
|
|
657
|
+
| Variable | Description | Example | Where to get it |
|
|
658
|
+
|----------|-------------|---------|-----------------|
|
|
659
|
+
| `DOCKER_IMAGE_NAME` | Image repository path | `myorg/myapp` | Your registry naming |
|
|
660
|
+
| `DOCKER_IMAGE_TAG` | Image tag | `latest` | Version or `latest` |
|
|
661
|
+
| `DOCKER_REGISTRY_URL` | Registry URL (optional) | `https://ghcr.io` | Registry docs |
|
|
662
|
+
| `DOCKER_REGISTRY_USERNAME` | Registry user | `myuser` | Registry account |
|
|
663
|
+
| `DOCKER_REGISTRY_TOKEN` | Registry password/token | *(secret)* | Docker Hub / GHCR PAT |
|
|
664
|
+
| `DOCKER_HOST` | Remote daemon (optional) | `ssh://ubuntu@host` | Remote Docker setup |
|
|
665
|
+
|
|
666
|
+
### AWS EC2
|
|
667
|
+
|
|
668
|
+
**Prerequisites:**
|
|
669
|
+
- [ ] EC2 instance launched in AWS Console (DeployHub does not create it)
|
|
670
|
+
- [ ] Key pair `.pem` downloaded at launch
|
|
671
|
+
- [ ] Security group: inbound SSH (22) from your IP
|
|
672
|
+
- [ ] App runtime on instance for backends
|
|
673
|
+
|
|
674
|
+
**What DeployHub automates:**
|
|
675
|
+
- EC2-specific `.env.example` (SSH + optional AWS API vars)
|
|
676
|
+
- SSH key validation and connectivity test
|
|
677
|
+
- OS user suggestion from AMI hint (ubuntu, ec2-user)
|
|
678
|
+
- Optional public IP lookup via `EC2_INSTANCE_ID` + AWS CLI
|
|
679
|
+
|
|
680
|
+
**After `init`:**
|
|
681
|
+
1. AWS Console → EC2 → Security Groups → Inbound rules → SSH port 22 from My IP
|
|
682
|
+
2. Copy `.env.example` → `.env`; set `SSH_KEY_PATH`, `SSH_HOST` (or `EC2_INSTANCE_ID` + AWS creds)
|
|
683
|
+
3. GitHub Secrets: `SSH_HOST`, `SSH_USER`, `SSH_KEY`, plus `AWS_*` if using instance ID lookup
|
|
684
|
+
4. Run `deployhub doctor`, then `git push origin main`
|
|
685
|
+
|
|
686
|
+
| Variable | Description | Example | Where to get it |
|
|
687
|
+
|----------|-------------|---------|-----------------|
|
|
688
|
+
| `SSH_HOST` | Instance public IP/DNS | `54.123.45.67` | EC2 Console → Instances |
|
|
689
|
+
| `SSH_USER` | SSH user for AMI | `ec2-user` | AMI documentation |
|
|
690
|
+
| `SSH_KEY_PATH` | Path to .pem key | `~/.ssh/ec2-key.pem` | Downloaded at instance launch |
|
|
691
|
+
| `EC2_INSTANCE_ID` | Instance ID (optional) | `i-0abc123...` | EC2 Console |
|
|
692
|
+
| `AWS_ACCESS_KEY_ID` | AWS key for API lookup | `AKIA...` | IAM → Users → Security credentials |
|
|
693
|
+
| `AWS_SECRET_ACCESS_KEY` | AWS secret | *(secret)* | Same as above |
|
|
694
|
+
| `AWS_REGION` | Instance region | `us-east-1` | EC2 Console top bar |
|
|
695
|
+
|
|
696
|
+
### Azure VM
|
|
697
|
+
|
|
698
|
+
**Prerequisites:**
|
|
699
|
+
- [ ] Azure VM created in Portal (DeployHub does not provision it)
|
|
700
|
+
- [ ] NSG rule allowing inbound SSH (port 22)
|
|
701
|
+
- [ ] SSH public key on the VM
|
|
702
|
+
- [ ] App runtime for backends
|
|
703
|
+
|
|
704
|
+
**What DeployHub automates:**
|
|
705
|
+
- Azure VM `.env.example` with SSH + optional Azure API vars
|
|
706
|
+
- Auto-detects subscription ID via `az` CLI if logged in
|
|
707
|
+
- SSH key validation and connectivity test
|
|
708
|
+
|
|
709
|
+
**After `init`:**
|
|
710
|
+
1. Azure Portal → VM → Networking → allow SSH (22) from your IP
|
|
711
|
+
2. Copy `.env.example` → `.env`; set `SSH_HOST`, `SSH_USER`, `SSH_KEY_PATH`
|
|
712
|
+
3. For CI: add `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` as GitHub Secrets
|
|
713
|
+
4. Run `deployhub doctor`, then `git push origin main`
|
|
714
|
+
|
|
715
|
+
| Variable | Description | Example | Where to get it |
|
|
716
|
+
|----------|-------------|---------|-----------------|
|
|
717
|
+
| `SSH_HOST` | VM public IP | `20.1.2.3` | Azure Portal → VM overview |
|
|
718
|
+
| `SSH_USER` | SSH username | `azureuser` | Chosen at VM creation |
|
|
719
|
+
| `SSH_KEY_PATH` | Private key path | `~/.ssh/azure.pem` | Your key file |
|
|
720
|
+
| `AZURE_SUBSCRIPTION_ID` | Subscription (optional) | `uuid` | `az account show` |
|
|
721
|
+
| `AZURE_RESOURCE_GROUP` | Resource group | `my-app-rg` | Portal → Resource groups |
|
|
722
|
+
| `AZURE_VM_NAME` | VM name | `my-vm` | Portal → Virtual machines |
|
|
723
|
+
|
|
724
|
+
### GCP VM
|
|
725
|
+
|
|
726
|
+
**Prerequisites:**
|
|
727
|
+
- [ ] Compute Engine VM created (DeployHub does not create it)
|
|
728
|
+
- [ ] Firewall rule allowing `tcp:22` (default `default-allow-ssh` may exist)
|
|
729
|
+
- [ ] SSH public key in **Metadata → SSH Keys** (GCP uses metadata keys, not launch key pairs like AWS)
|
|
730
|
+
- [ ] App runtime for backends
|
|
731
|
+
|
|
732
|
+
**What DeployHub automates:**
|
|
733
|
+
- GCP VM `.env.example` with SSH + optional GCP API vars
|
|
734
|
+
- Auto-detects project ID via `gcloud` if authenticated
|
|
735
|
+
- SSH key validation and connectivity test
|
|
736
|
+
|
|
737
|
+
**After `init`:**
|
|
738
|
+
1. GCP Console → VPC → Firewall → ensure SSH (tcp:22) allowed from your IP
|
|
739
|
+
2. Add SSH public key: Console → Compute Engine → Metadata → SSH Keys
|
|
740
|
+
3. Copy `.env.example` → `.env`; set `SSH_HOST`, `SSH_USER`, `SSH_KEY_PATH`
|
|
741
|
+
4. Run `deployhub doctor`, then `git push origin main`
|
|
742
|
+
|
|
743
|
+
| Variable | Description | Example | Where to get it |
|
|
744
|
+
|----------|-------------|---------|-----------------|
|
|
745
|
+
| `SSH_HOST` | External IP | `34.56.78.90` | Compute Engine → VM instances |
|
|
746
|
+
| `SSH_USER` | SSH username | `your_google_username` | GCP OS Login or metadata |
|
|
747
|
+
| `SSH_KEY_PATH` | Private key path | `~/.ssh/gcp-key` | Your local key pair |
|
|
748
|
+
| `SSH_SSH_PORT` | SSH connection port (optional) | `22` | Server SSH config |
|
|
749
|
+
| `SSH_DEPLOY_PATH` | Remote deploy directory (optional) | `/var/www/my-app` | Your server layout |
|
|
750
|
+
| `SSH_APP_NAME` | PM2 process name (backend) | `my-api` | Your choice |
|
|
751
|
+
| `SSH_PORT` | App listen port (backend) | `3000` | Your app config |
|
|
752
|
+
| `SSH_KEY` | Private key contents (CI only) | `-----BEGIN...` | Same key as `SSH_KEY_PATH` |
|
|
753
|
+
| `GCP_PROJECT_ID` | Project ID (optional) | `my-project-123` | `gcloud config get-value project` |
|
|
754
|
+
| `GCP_ZONE` | VM zone (optional) | `us-central1-a` | VM instance details |
|
|
755
|
+
| `GCP_INSTANCE_NAME` | Instance name (optional) | `my-vm` | Compute Engine list |
|
|
756
|
+
| `GCP_KEY_FILE` | Service account JSON (optional, CI) | `/path/to/key.json` | IAM → Service Accounts → Keys |
|
|
757
|
+
|
|
758
|
+
### Kubernetes
|
|
759
|
+
|
|
760
|
+
**Prerequisites:**
|
|
761
|
+
- [ ] Existing Kubernetes cluster (DeployHub does not provision clusters)
|
|
762
|
+
- [ ] `kubectl` installed and configured
|
|
763
|
+
- [ ] Kubernetes manifests (`.yaml` or `k8s/` directory) in your repo
|
|
764
|
+
- [ ] Cluster reachable from CI (kubeconfig secret or cloud auth)
|
|
765
|
+
|
|
766
|
+
**What DeployHub automates:**
|
|
767
|
+
- Lists `kubectl` contexts during `init` for easy selection
|
|
768
|
+
- Auto-detects `~/.kube/config`
|
|
769
|
+
- Complete `.env.example` for kubeconfig, context, namespace
|
|
770
|
+
- Cluster connectivity test during `init`
|
|
771
|
+
|
|
772
|
+
**After `init`:**
|
|
773
|
+
1. Verify context: `kubectl config get-contexts`
|
|
774
|
+
2. Create namespace if needed: `kubectl create namespace my-app`
|
|
775
|
+
3. For private registries: create `imagePullSecret` and set `KUBE_IMAGE_PULL_SECRET`
|
|
776
|
+
4. Copy `.env.example` → `.env`; add kubeconfig/auth to GitHub Secrets for CI
|
|
777
|
+
5. Run `deployhub doctor`, then `git push origin main`
|
|
778
|
+
|
|
779
|
+
| Variable | Description | Example | Where to get it |
|
|
780
|
+
|----------|-------------|---------|-----------------|
|
|
781
|
+
| `KUBECONFIG` | Path to kubeconfig | `~/.kube/config` | Default kubectl config |
|
|
782
|
+
| `KUBE_CONTEXT` | Context name | `my-cluster` | `kubectl config get-contexts` |
|
|
783
|
+
| `KUBE_NAMESPACE` | Target namespace | `my-app` | `kubectl get namespaces` |
|
|
784
|
+
| `DOCKER_IMAGE_NAME` | Container image | `ghcr.io/org/app` | Your registry |
|
|
785
|
+
| `KUBE_IMAGE_PULL_SECRET` | Pull secret name | `regcred` | `kubectl create secret docker-registry` |
|
|
786
|
+
|
|
787
|
+
---
|
|
788
|
+
|
|
588
789
|
## Minimal `deployhub.config.json` examples
|
|
589
790
|
|
|
590
791
|
### Storage only — React
|
|
@@ -640,7 +841,7 @@ Prefer `deployhub init` over hand-writing config — it sets adapters, workflow,
|
|
|
640
841
|
|---------|-----|
|
|
641
842
|
| `Deploy requires storage upload` | Add at least one storage provider in config |
|
|
642
843
|
| AWS / GDrive check fails in `doctor` | Run `deployhub storage add <provider>` and match GitHub Secrets |
|
|
643
|
-
| SSH deploy fails | Verify `
|
|
844
|
+
| SSH deploy fails | Verify `SSH_KEY_PATH` points to your private `.pem` file (or `SSH_KEY` in CI); user can write to deploy path; port 22 open |
|
|
644
845
|
| Wrong output uploaded | Fix `buildOutput` in config (`dist` vs `build` vs `.next`) |
|
|
645
846
|
| Tests fail in CI | Set `"pipeline": { "test": false }` temporarily, or fix tests |
|
|
646
847
|
| Monorepo subfolders | Edit `buildCommand` paths in `deployhub.config.json` after init |
|
|
@@ -692,16 +893,25 @@ Add these secrets in your repository (Settings → Secrets and variables → Act
|
|
|
692
893
|
| `DROPBOX_ACCESS_TOKEN` | Dropbox |
|
|
693
894
|
| `FTP_HOST`, `FTP_USER`, `FTP_PASSWORD` | FTP storage |
|
|
694
895
|
|
|
695
|
-
### Server deployment (SSH, EC2, VMs)
|
|
896
|
+
### Server deployment (SSH, EC2, VMs, Docker, Kubernetes)
|
|
696
897
|
|
|
697
898
|
| Secret | Used for |
|
|
698
899
|
|--------|----------|
|
|
699
|
-
| `SSH_HOST` | Target server hostname |
|
|
900
|
+
| `SSH_HOST` | Target server hostname or IP |
|
|
700
901
|
| `SSH_USER` | SSH username |
|
|
701
|
-
| `
|
|
902
|
+
| `SSH_KEY_PATH` | Local path to private key (`.env` only) |
|
|
903
|
+
| `SSH_KEY` | Private key contents (GitHub Actions / CI) |
|
|
904
|
+
| `SSH_SSH_PORT` | SSH connection port (default 22) |
|
|
702
905
|
| `SSH_DEPLOY_PATH` | Remote directory (optional if set in config) |
|
|
703
906
|
| `SSH_APP_NAME` | PM2 process name for backends |
|
|
704
|
-
| `SSH_PORT` | App port on server (
|
|
907
|
+
| `SSH_PORT` | App port on server (backend) |
|
|
908
|
+
| `EC2_INSTANCE_ID`, `AWS_*` | Optional EC2 dynamic IP lookup |
|
|
909
|
+
| `AZURE_SUBSCRIPTION_ID`, `AZURE_RESOURCE_GROUP`, `AZURE_VM_NAME` | Optional Azure VM IP lookup |
|
|
910
|
+
| `GCP_PROJECT_ID`, `GCP_ZONE`, `GCP_INSTANCE_NAME`, `GCP_KEY_FILE` | Optional GCP VM IP lookup |
|
|
911
|
+
| `DOCKER_IMAGE_NAME`, `DOCKER_REGISTRY_*`, `DOCKER_HOST` | Docker deployment |
|
|
912
|
+
| `KUBECONFIG`, `KUBE_CONTEXT`, `KUBE_NAMESPACE` | Kubernetes deployment |
|
|
913
|
+
|
|
914
|
+
See [Deployment method guides](#deployment-method-guides) for full per-method variable tables with examples.
|
|
705
915
|
|
|
706
916
|
## `deployhub doctor` Output
|
|
707
917
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@akash-chowdhury-24/deployhub",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "Zero-configuration deployment and artifact manager",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/cli/index.js",
|
|
@@ -35,8 +35,9 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"start": "node src/cli/index.js",
|
|
37
37
|
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
38
|
-
"
|
|
39
|
-
"
|
|
38
|
+
"verify:bundle-sources": "node scripts/verify-bundle-sources.mjs",
|
|
39
|
+
"prepublishOnly": "npm test && npm run verify:bundle-sources",
|
|
40
|
+
"build:bundle": "node scripts/verify-bundle-sources.mjs && node scripts/build-bundle.mjs",
|
|
40
41
|
"build:binaries": "npm run build:bundle && node scripts/prefetch-pkg-cache.mjs && pkg dist/deployhub.cjs --targets node20-linux-x64,node20-macos-x64,node20-macos-arm64,node20-win-x64 --output dist/deployhub --compress GZip"
|
|
41
42
|
},
|
|
42
43
|
"pkg": {
|
package/src/commands/doctor.js
CHANGED
|
@@ -9,6 +9,11 @@ import { getDeploymentProvider } from '../deployment/index.js';
|
|
|
9
9
|
import { PROVIDER_ENV_MAP } from '../utils/github-actions.js';
|
|
10
10
|
import { printDoctorFooter } from '../utils/author.js';
|
|
11
11
|
import { createLocalProvider } from '../storage/providers/local.js';
|
|
12
|
+
import {
|
|
13
|
+
getDeploymentEnvKeys,
|
|
14
|
+
getDeploymentSecretKeys,
|
|
15
|
+
} from '../deployment/deployment-env.js';
|
|
16
|
+
import { testSshConnectivity, validateSshKeyForDoctor, testSshHostReachability } from '../deployment/init-helpers.js';
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* @typedef {{ name: string, pass: boolean, message: string }} CheckResult
|
|
@@ -48,14 +53,165 @@ function resolveBackendFramework(config) {
|
|
|
48
53
|
return config.backend?.framework || config.framework || 'express';
|
|
49
54
|
}
|
|
50
55
|
|
|
56
|
+
/** @type {Set<string>} */
|
|
57
|
+
const SSH_DEPLOY_TYPES = new Set(['ssh', 'ec2', 'azure-vm', 'gcp-vm']);
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* @param {import('../core/config.js').DeployHubConfig} config
|
|
53
61
|
* @param {string} envName
|
|
62
|
+
* @param {Record<string, unknown>} envConfig
|
|
54
63
|
* @returns {Promise<CheckResult[]>}
|
|
55
64
|
*/
|
|
56
|
-
async function
|
|
65
|
+
async function runDeploymentChecks(config, envName, envConfig) {
|
|
66
|
+
const deployType = envConfig.type;
|
|
67
|
+
if (!deployType || typeof deployType !== 'string') return [];
|
|
68
|
+
|
|
69
|
+
/** @type {CheckResult[]} */
|
|
70
|
+
const checks = [];
|
|
71
|
+
const requiredKeys = getDeploymentEnvKeys(deployType, config);
|
|
72
|
+
|
|
73
|
+
checks.push(
|
|
74
|
+
await runCheck(`${deployType} env vars`, async () => {
|
|
75
|
+
const missing = requiredKeys.filter((k) => {
|
|
76
|
+
if (k === 'SSH_KEY_PATH') {
|
|
77
|
+
return !process.env.SSH_KEY_PATH && !process.env.SSH_KEY;
|
|
78
|
+
}
|
|
79
|
+
return !process.env[k];
|
|
80
|
+
});
|
|
81
|
+
if (missing.length > 0) {
|
|
82
|
+
return {
|
|
83
|
+
name: `${deployType} env vars`,
|
|
84
|
+
pass: false,
|
|
85
|
+
message: `Missing required variables: ${missing.join(', ')} — copy .env.example to .env and fill in values (see inline comments).`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
name: `${deployType} env vars`,
|
|
90
|
+
pass: true,
|
|
91
|
+
message: 'All required deployment variables present',
|
|
92
|
+
};
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
if (SSH_DEPLOY_TYPES.has(deployType)) {
|
|
97
|
+
const host = envConfig.host || process.env.SSH_HOST;
|
|
98
|
+
const user = envConfig.user || process.env.SSH_USER;
|
|
99
|
+
const keyPath = envConfig.keyPath || process.env.SSH_KEY_PATH;
|
|
100
|
+
const sshPort = Number(process.env.SSH_SSH_PORT || envConfig.sshPort) || 22;
|
|
101
|
+
|
|
102
|
+
checks.push(
|
|
103
|
+
await runCheck('SSH key', async () => {
|
|
104
|
+
const result = await validateSshKeyForDoctor(
|
|
105
|
+
keyPath ? String(keyPath) : undefined,
|
|
106
|
+
process.env.SSH_KEY
|
|
107
|
+
);
|
|
108
|
+
return {
|
|
109
|
+
name: 'SSH key',
|
|
110
|
+
pass: result.ok,
|
|
111
|
+
message: result.message,
|
|
112
|
+
};
|
|
113
|
+
})
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
checks.push(
|
|
117
|
+
await runCheck('SSH host reachability', async () => {
|
|
118
|
+
if (!host) {
|
|
119
|
+
return {
|
|
120
|
+
name: 'SSH host reachability',
|
|
121
|
+
pass: false,
|
|
122
|
+
message: 'SSH_HOST is required — set it in .env to your server IP or hostname.',
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const result = await testSshHostReachability(String(host), sshPort);
|
|
126
|
+
return {
|
|
127
|
+
name: 'SSH host reachability',
|
|
128
|
+
pass: result.ok,
|
|
129
|
+
message: result.message,
|
|
130
|
+
};
|
|
131
|
+
})
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const isBackend = config.projectType === 'backend' || config.projectType === 'both';
|
|
135
|
+
if (isBackend) {
|
|
136
|
+
const backendChecks = await runBackendProcessChecks(config, envName, deployType);
|
|
137
|
+
checks.push(...backendChecks);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (deployType === 'docker') {
|
|
142
|
+
checks.push(
|
|
143
|
+
await runCheck('Docker daemon', async () => {
|
|
144
|
+
try {
|
|
145
|
+
const provider = getDeploymentProvider('docker', config, envName);
|
|
146
|
+
await provider.testConnection();
|
|
147
|
+
return { name: 'Docker daemon', pass: true, message: 'Docker daemon reachable' };
|
|
148
|
+
} catch (err) {
|
|
149
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
150
|
+
return {
|
|
151
|
+
name: 'Docker daemon',
|
|
152
|
+
pass: false,
|
|
153
|
+
message: `Docker not reachable — ${msg}. Install Docker or set DOCKER_HOST for a remote daemon.`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (deployType === 'kubernetes') {
|
|
161
|
+
checks.push(
|
|
162
|
+
await runCheck('Kubernetes cluster', async () => {
|
|
163
|
+
try {
|
|
164
|
+
const provider = getDeploymentProvider('kubernetes', config, envName);
|
|
165
|
+
await provider.testConnection();
|
|
166
|
+
const ctx = process.env.KUBE_CONTEXT || 'current';
|
|
167
|
+
const ns = process.env.KUBE_NAMESPACE || config.project || 'default';
|
|
168
|
+
return {
|
|
169
|
+
name: 'Kubernetes cluster',
|
|
170
|
+
pass: true,
|
|
171
|
+
message: `kubectl cluster-info OK (context: ${ctx}, namespace: ${ns})`,
|
|
172
|
+
};
|
|
173
|
+
} catch (err) {
|
|
174
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
175
|
+
return {
|
|
176
|
+
name: 'Kubernetes cluster',
|
|
177
|
+
pass: false,
|
|
178
|
+
message: `kubectl cluster-info failed — ${msg}. Check KUBECONFIG path and KUBE_CONTEXT.`,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (deployType === 'ec2' && process.env.EC2_INSTANCE_ID) {
|
|
186
|
+
checks.push(
|
|
187
|
+
await runCheck('EC2 API', async () => {
|
|
188
|
+
const missing = ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION'].filter(
|
|
189
|
+
(k) => !process.env[k]
|
|
190
|
+
);
|
|
191
|
+
if (missing.length > 0) {
|
|
192
|
+
return {
|
|
193
|
+
name: 'EC2 API',
|
|
194
|
+
pass: false,
|
|
195
|
+
message: `EC2_INSTANCE_ID is set but missing: ${missing.join(', ')} — needed for dynamic IP lookup.`,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return { name: 'EC2 API', pass: true, message: 'AWS credentials present for EC2 lookup' };
|
|
199
|
+
})
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return checks;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* @param {import('../core/config.js').DeployHubConfig} config
|
|
208
|
+
* @param {string} envName
|
|
209
|
+
* @param {string} [deployType]
|
|
210
|
+
* @returns {Promise<CheckResult[]>}
|
|
211
|
+
*/
|
|
212
|
+
async function runBackendProcessChecks(config, envName, deployType = 'ssh') {
|
|
57
213
|
const framework = resolveBackendFramework(config);
|
|
58
|
-
const provider = getDeploymentProvider(
|
|
214
|
+
const provider = getDeploymentProvider(deployType, config, envName);
|
|
59
215
|
|
|
60
216
|
if (!provider.runRemoteCheck) {
|
|
61
217
|
return [];
|
|
@@ -338,27 +494,8 @@ export function registerDoctorCommand(program) {
|
|
|
338
494
|
const env = config.environments[envName];
|
|
339
495
|
if (!env) continue;
|
|
340
496
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
await runCheck('SSH target', async () => {
|
|
344
|
-
const provider = getDeploymentProvider(env.type, config, envName);
|
|
345
|
-
await provider.testConnection();
|
|
346
|
-
const host = env.host || process.env.SSH_HOST;
|
|
347
|
-
return {
|
|
348
|
-
name: 'SSH target',
|
|
349
|
-
pass: true,
|
|
350
|
-
message: `Can reach ${host || 'host'}`,
|
|
351
|
-
};
|
|
352
|
-
})
|
|
353
|
-
);
|
|
354
|
-
|
|
355
|
-
const isBackend =
|
|
356
|
-
config.projectType === 'backend' || config.projectType === 'both';
|
|
357
|
-
if (isBackend && env.type === 'ssh') {
|
|
358
|
-
const backendChecks = await runBackendProcessChecks(config, envName);
|
|
359
|
-
results.push(...backendChecks);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
497
|
+
const deployChecks = await runDeploymentChecks(config, envName, env);
|
|
498
|
+
results.push(...deployChecks);
|
|
362
499
|
}
|
|
363
500
|
|
|
364
501
|
results.push(
|
|
@@ -405,16 +542,17 @@ export function registerDoctorCommand(program) {
|
|
|
405
542
|
}
|
|
406
543
|
for (const envName of config.deploy || []) {
|
|
407
544
|
const env = config.environments[envName];
|
|
408
|
-
if (!env) continue;
|
|
409
|
-
|
|
410
|
-
if (env.type) {
|
|
411
|
-
const keys = PROVIDER_ENV_MAP[env.type] || [];
|
|
412
|
-
required.push(...keys);
|
|
413
|
-
}
|
|
545
|
+
if (!env?.type) continue;
|
|
546
|
+
required.push(...getDeploymentSecretKeys(env.type, config));
|
|
414
547
|
}
|
|
415
548
|
|
|
416
549
|
const unique = [...new Set(required)];
|
|
417
|
-
const missing = unique.filter((k) =>
|
|
550
|
+
const missing = unique.filter((k) => {
|
|
551
|
+
if (k === 'SSH_KEY') {
|
|
552
|
+
return !process.env.SSH_KEY && !process.env.SSH_KEY_PATH;
|
|
553
|
+
}
|
|
554
|
+
return !process.env[k];
|
|
555
|
+
});
|
|
418
556
|
if (missing.length > 0) {
|
|
419
557
|
return {
|
|
420
558
|
name: 'Secrets',
|