git-push-deploy-cli 0.1.1 → 0.2.0

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.
Files changed (42) hide show
  1. package/README.md +72 -38
  2. package/dist/commands/deploy.d.ts +9 -2
  3. package/dist/commands/deploy.d.ts.map +1 -1
  4. package/dist/commands/deploy.js +26 -21
  5. package/dist/commands/deploy.js.map +1 -1
  6. package/dist/commands/init.d.ts +4 -3
  7. package/dist/commands/init.d.ts.map +1 -1
  8. package/dist/commands/init.js +116 -13
  9. package/dist/commands/init.js.map +1 -1
  10. package/dist/commands/install.d.ts +21 -0
  11. package/dist/commands/install.d.ts.map +1 -0
  12. package/dist/commands/install.js +174 -0
  13. package/dist/commands/install.js.map +1 -0
  14. package/dist/commands/logs.d.ts.map +1 -1
  15. package/dist/commands/logs.js +5 -3
  16. package/dist/commands/logs.js.map +1 -1
  17. package/dist/commands/release.d.ts +2 -0
  18. package/dist/commands/release.d.ts.map +1 -1
  19. package/dist/commands/release.js +7 -5
  20. package/dist/commands/release.js.map +1 -1
  21. package/dist/commands/stage.d.ts +5 -0
  22. package/dist/commands/stage.d.ts.map +1 -1
  23. package/dist/commands/stage.js +58 -22
  24. package/dist/commands/stage.js.map +1 -1
  25. package/dist/commands/status.d.ts.map +1 -1
  26. package/dist/commands/status.js +5 -1
  27. package/dist/commands/status.js.map +1 -1
  28. package/dist/config/loader.d.ts +14 -0
  29. package/dist/config/loader.d.ts.map +1 -1
  30. package/dist/config/loader.js +18 -0
  31. package/dist/config/loader.js.map +1 -1
  32. package/dist/config/types.d.ts +47 -9
  33. package/dist/config/types.d.ts.map +1 -1
  34. package/dist/config/types.js +24 -1
  35. package/dist/config/types.js.map +1 -1
  36. package/dist/index.js +16 -9
  37. package/dist/index.js.map +1 -1
  38. package/dist/utils/shell.d.ts +1 -0
  39. package/dist/utils/shell.d.ts.map +1 -1
  40. package/dist/utils/shell.js +4 -3
  41. package/dist/utils/shell.js.map +1 -1
  42. package/package.json +2 -2
package/README.md CHANGED
@@ -4,10 +4,10 @@ Git Push Deploy - A CLI for git-based deployments with PM2 support. Push to depl
4
4
 
5
5
  ## Features
6
6
 
7
- - **Git-based deployment**: Push to bare repo, SSH triggers install
8
- - **PM2 integration**: Automatic process restarts
9
- - **Monorepo support**: Stage multiple packages for deployment
10
- - **SSH orchestration**: Everything runs from your dev machine
7
+ - **Git-based deployment**: Push to bare repo, server hook handles install
8
+ - **PM2 integration**: Automatic process restarts with user isolation
9
+ - **Lazy initialization**: Deploy repo created on first `gpd stage`
10
+ - **SSH orchestration**: Server setup from your dev machine
11
11
  - **Config-driven**: Define services in `.git-deploy.json`
12
12
 
13
13
  ## Installation
@@ -23,17 +23,25 @@ npm install -g git-push-deploy-cli
23
23
  ```json
24
24
  {
25
25
  "services": {
26
- "my-api": {
27
- "packages": ["my-api"],
28
- "mainPackage": "my-api",
29
- "deployRepo": "../deploy-my-api",
30
- "processName": "my-api",
26
+ "my-api-staging": {
27
+ "sourceDir": "my-api",
28
+ "deployRepo": "deploy/staging",
29
+ "artifacts": ["dist/index.js", "package.json", "ecosystem.config.cjs"],
30
+ "processManager": "pm2",
31
+ "processName": "my-api-staging",
31
32
  "pm2Home": "/opt/myapp/.pm2",
32
- "artifacts": ["dist", "package.json", "package-lock.json", "ecosystem.config.cjs"],
33
+ "pm2User": "myapp",
34
+ "environment": "staging",
35
+ "env": {
36
+ "PORT": 5000,
37
+ "NODE_ENV": "staging"
38
+ },
33
39
  "server": {
34
40
  "host": "user@myserver",
35
- "targetDir": "/opt/myapp/my-api",
36
- "bareRepo": "/git/deploy-my-api"
41
+ "sshOptions": "-p 22",
42
+ "targetDir": "/opt/myapp/staging/my-api",
43
+ "bareRepo": "/git/deploy-myapp/staging/my-api",
44
+ "group": "deploy-myapp"
37
45
  }
38
46
  }
39
47
  }
@@ -43,23 +51,25 @@ npm install -g git-push-deploy-cli
43
51
  ### 2. Initialize server (once per service)
44
52
 
45
53
  ```bash
46
- gpd init my-api
54
+ gpd init my-api-staging
47
55
  ```
48
56
 
49
57
  This creates via SSH:
50
- - Bare git repository at `/git/deploy-my-api`
51
- - Clone at `/opt/myapp/my-api`
58
+ - Bare git repository at `/git/deploy-myapp/staging/my-api`
59
+ - Target directory at `/opt/myapp/staging/my-api`
60
+ - Post-receive hook that calls `gpd install`
52
61
 
53
62
  ### 3. Deploy
54
63
 
55
64
  ```bash
56
- gpd deploy my-api
65
+ gpd deploy my-api-staging
57
66
  ```
58
67
 
59
68
  This:
60
- 1. Copies build artifacts to local deploy repo
61
- 2. Commits and pushes to bare repo on server
62
- 3. SSH: `git pull && npm install && pm2 restart`
69
+ 1. Creates deploy repo (if needed) at `my-api/deploy/staging/`
70
+ 2. Copies build artifacts to deploy repo
71
+ 3. Commits and pushes to bare repo on server
72
+ 4. Server hook: `git checkout && npm install && pm2 restart`
63
73
 
64
74
  ## Commands
65
75
 
@@ -68,15 +78,16 @@ This:
68
78
  | `gpd status` | Show all configured services |
69
79
  | `gpd stage <service>` | Copy build artifacts to deploy repo |
70
80
  | `gpd release <service>` | Commit and push deploy repo |
71
- | `gpd deploy <service>` | Stage + release + SSH install |
72
- | `gpd init <service>` | Initialize bare repo and clone on server |
81
+ | `gpd deploy <service>` | Stage + release (hook handles install) |
82
+ | `gpd init <service>` | Initialize server (bare repo, target dir, hook) |
83
+ | `gpd install <service>` | Server-side install (called by hook) |
73
84
  | `gpd logs <service>` | Show PM2 logs from server via SSH |
74
85
 
75
86
  ## Options
76
87
 
77
88
  ```bash
78
89
  gpd deploy my-api -m "custom commit message"
79
- gpd deploy my-api --skip-remote # Only stage and release, no SSH
90
+ gpd deploy my-api --skip-push # Only stage, do not push
80
91
  gpd logs my-api -f # Follow logs
81
92
  gpd logs my-api -n 100 # Show last 100 lines
82
93
  ```
@@ -85,48 +96,71 @@ gpd logs my-api -n 100 # Show last 100 lines
85
96
 
86
97
  ```typescript
87
98
  interface ServiceConfig {
88
- packages: string[]; // Packages to deploy (monorepo)
89
- mainPackage: string; // Package with ecosystem.config.cjs
90
- deployRepo: string; // Local deploy repo path
99
+ sourceDir: string; // Project directory (e.g., "my-api")
100
+ deployRepo: string; // Deploy repo path, relative to sourceDir
101
+ artifacts: string[]; // Files/dirs to copy
102
+ processManager: 'pm2'; // Process manager type
91
103
  processName: string; // PM2 process name
92
104
  pm2Home?: string; // PM2_HOME on server
93
- artifacts?: string[]; // Files to copy (default: dist, package.json, etc.)
105
+ pm2User?: string; // User to run PM2 as (sudo -u)
106
+ environment?: string; // staging | production
107
+ env?: Record<string, any>; // Environment variables for .env file
94
108
  server: {
95
109
  host: string; // SSH host (user@hostname)
96
- targetDir: string; // Clone directory on server
110
+ sshOptions?: string; // SSH options (e.g., "-p 6771 -4")
111
+ targetDir: string; // Target directory on server
97
112
  bareRepo: string; // Bare repo path on server
113
+ group?: string; // Unix group for permissions
98
114
  };
99
115
  }
100
116
  ```
101
117
 
118
+ ## Architecture
119
+
120
+ ```
121
+ Workspace Server
122
+ ───────── ──────
123
+ my-api/
124
+ ├── src/ /git/deploy-myapp/staging/my-api/
125
+ ├── dist/ └── hooks/post-receive
126
+ └── deploy/ ↓
127
+ └── staging/ /opt/myapp/staging/my-api/
128
+ ├── .git → ssh://... ├── dist/index.js
129
+ ├── dist/ ├── package.json
130
+ └── package.json ├── node_modules/
131
+ └── .env (generated)
132
+ ```
133
+
102
134
  ## Workflow
103
135
 
104
136
  ```
105
137
  Dev Machine Server
106
138
  ─────────── ──────
107
- gpd deploy my-api
139
+ gpd deploy my-api-staging
108
140
 
109
141
  ├─ gpd stage
110
- │ └─ Copy artifacts → ../deploy-my-api/
111
-
112
- ├─ gpd release
113
- │ └─ git push ──────────────────→ /git/deploy-my-api (bare)
142
+ │ └─ Copy artifacts → my-api/deploy/staging/
114
143
 
115
- └─ SSH ────────────────────────────→ cd /opt/myapp/my-api
116
- git pull
117
- npm install --omit=dev
118
- pm2 restart my-api
144
+ └─ gpd release
145
+ └─ git push ───────────────→ /git/deploy-myapp/staging/my-api
146
+
147
+ └─ post-receive hook:
148
+ gpd install my-api-staging
149
+
150
+ ├─ git checkout -f
151
+ ├─ .env generation
152
+ ├─ npm install --omit=dev
153
+ └─ pm2 restart
119
154
  ```
120
155
 
121
156
  ## Server Prerequisites
122
157
 
123
158
  - Node.js + npm
124
159
  - PM2 (`npm install -g pm2`)
160
+ - gpd CLI (`npm install -g git-push-deploy-cli`)
125
161
  - SSH access from dev machine
126
162
  - Git
127
163
 
128
- No gpd installation needed on the server!
129
-
130
164
  ## License
131
165
 
132
166
  MIT
@@ -1,9 +1,16 @@
1
1
  interface DeployOptions {
2
2
  message?: string;
3
- skipRemote?: boolean;
3
+ skipPush?: boolean;
4
4
  }
5
5
  /**
6
- * Deploy command - stage, release, and trigger remote install
6
+ * Deploy command - stage artifacts and push to server
7
+ *
8
+ * New architecture:
9
+ * 1. Stage: Copy build artifacts to deploy repo (with lazy init)
10
+ * 2. Release: Commit and push to bare repo on server
11
+ * 3. Server hook handles: git checkout, npm install, pm2 restart
12
+ *
13
+ * No more SSH install from client - the post-receive hook does everything!
7
14
  */
8
15
  export declare function deployCommand(serviceName: string, options?: DeployOptions): Promise<void>;
9
16
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAMA,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4BnG"}
1
+ {"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAKA,UAAU,aAAa;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBnG"}
@@ -2,31 +2,36 @@ import chalk from 'chalk';
2
2
  import { stageCommand } from './stage.js';
3
3
  import { releaseCommand } from './release.js';
4
4
  import { getServiceConfig } from '../config/loader.js';
5
- import { sshExec } from '../utils/shell.js';
6
5
  /**
7
- * Deploy command - stage, release, and trigger remote install
6
+ * Deploy command - stage artifacts and push to server
7
+ *
8
+ * New architecture:
9
+ * 1. Stage: Copy build artifacts to deploy repo (with lazy init)
10
+ * 2. Release: Commit and push to bare repo on server
11
+ * 3. Server hook handles: git checkout, npm install, pm2 restart
12
+ *
13
+ * No more SSH install from client - the post-receive hook does everything!
8
14
  */
9
15
  export async function deployCommand(serviceName, options = {}) {
10
- // 1. Stage artifacts
16
+ const config = getServiceConfig(serviceName);
17
+ console.log(chalk.blue.bold(`Deploying ${serviceName}...`));
18
+ console.log(chalk.gray(` Environment: ${config.environment || 'production'}`));
19
+ console.log(chalk.gray(` Server: ${config.server.host}`));
20
+ console.log('');
21
+ // 1. Stage artifacts to deploy repo
11
22
  await stageCommand(serviceName);
12
- // 2. Commit and push
13
- await releaseCommand(serviceName, options);
14
- // 3. Trigger remote install via SSH
15
- if (!options.skipRemote) {
16
- const config = getServiceConfig(serviceName);
17
- const { host, targetDir } = config.server;
18
- const { mainPackage, processName, pm2Home } = config;
19
- console.log(chalk.blue(`Installing on ${host}...`));
20
- const pm2Env = pm2Home ? `PM2_HOME=${pm2Home} ` : '';
21
- const remoteCmd = [
22
- `cd ${targetDir}`,
23
- 'git pull --ff-only',
24
- `cd ${mainPackage}`,
25
- 'npm install --omit=dev',
26
- `${pm2Env}pm2 restart ecosystem.config.cjs`
27
- ].join(' && ');
28
- sshExec(host, remoteCmd);
29
- console.log(chalk.green(`✓ Deployed ${serviceName} to ${host}`));
23
+ console.log('');
24
+ // 2. Commit and push (triggers server-side hook)
25
+ if (!options.skipPush) {
26
+ await releaseCommand(serviceName, options);
27
+ console.log('');
28
+ console.log(chalk.green.bold(`✓ Deployed ${serviceName}`));
29
+ console.log(chalk.gray(' The server hook will handle: git checkout, npm install, pm2 restart'));
30
+ console.log(chalk.gray(` Check logs: gpd logs ${serviceName}`));
31
+ }
32
+ else {
33
+ console.log(chalk.yellow('Skipped push (--skip-push). Run manually:'));
34
+ console.log(chalk.white(` gpd release ${serviceName}`));
30
35
  }
31
36
  }
32
37
  //# sourceMappingURL=deploy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAO5C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,WAAmB,EAAE,UAAyB,EAAE;IAClF,qBAAqB;IACrB,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;IAEhC,qBAAqB;IACrB,MAAM,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;IAE3C,oCAAoC;IACpC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;QACxB,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;QAC7C,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1C,MAAM,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAErD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,IAAI,KAAK,CAAC,CAAC,CAAC;QAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,YAAY,OAAO,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;QACrD,MAAM,SAAS,GAAG;YAChB,MAAM,SAAS,EAAE;YACjB,oBAAoB;YACpB,MAAM,WAAW,EAAE;YACnB,wBAAwB;YACxB,GAAG,MAAM,kCAAkC;SAC5C,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEf,OAAO,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;QAEzB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,WAAW,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAOvD;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,WAAmB,EAAE,UAAyB,EAAE;IAClF,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAE7C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,aAAa,WAAW,KAAK,CAAC,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,MAAM,CAAC,WAAW,IAAI,YAAY,EAAE,CAAC,CAAC,CAAC;IAChF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,oCAAoC;IACpC,MAAM,YAAY,CAAC,WAAW,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,iDAAiD;IACjD,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,MAAM,cAAc,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,WAAW,EAAE,CAAC,CAAC,CAAC;QAC3D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,uEAAuE,CAAC,CAAC,CAAC;QACjG,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0BAA0B,WAAW,EAAE,CAAC,CAAC,CAAC;IACnE,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,2CAA2C,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAC,CAAC;IAC3D,CAAC;AACH,CAAC"}
@@ -1,11 +1,12 @@
1
1
  interface InitOptions {
2
2
  }
3
3
  /**
4
- * Init command - initialize bare repo and clone on remote server via SSH
4
+ * Init command - initialize bare repo and target directory on remote server via SSH
5
5
  *
6
6
  * Creates:
7
- * 1. Bare repo at server.bareRepo (e.g., /git/sym/deploy-kairox-api)
8
- * 2. Clone at server.targetDir (e.g., /opt/kairox/kairox-api)
7
+ * 1. Bare repo at server.bareRepo (e.g., /git/sym/deploy-kairox/staging/kairox-api)
8
+ * 2. Target directory at server.targetDir (e.g., /opt/kairox/staging/kairox-api)
9
+ * 3. Post-receive hook that calls `gpd install <service>`
9
10
  */
10
11
  export declare function initCommand(serviceName: string, _options?: InitOptions): Promise<void>;
11
12
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,UAAU,WAAW;CAEpB;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BhG"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAIA,UAAU,WAAW;CAEpB;AA8DD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,WAAW,EAAE,MAAM,EAAE,QAAQ,GAAE,WAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA6EhG"}
@@ -2,29 +2,132 @@ import chalk from 'chalk';
2
2
  import { getServiceConfig } from '../config/loader.js';
3
3
  import { sshExec } from '../utils/shell.js';
4
4
  /**
5
- * Init command - initialize bare repo and clone on remote server via SSH
5
+ * Generate post-receive hook content
6
+ * The hook simply calls `gpd install <service>` which handles all the logic
7
+ */
8
+ function generatePostReceiveHook(serviceName, config) {
9
+ const { targetDir, bareRepo, pm2User, pm2Home } = config;
10
+ // Build the install command with sudo if pm2User is specified
11
+ let installCmd = 'gpd install ' + serviceName;
12
+ if (pm2User) {
13
+ // Run gpd as the pm2User
14
+ installCmd = `sudo -u ${pm2User} ${installCmd}`;
15
+ }
16
+ return `#!/bin/bash
17
+ set -e
18
+
19
+ # GPD post-receive hook for ${serviceName}
20
+ # Generated by: gpd init ${serviceName}
21
+
22
+ SERVICE="${serviceName}"
23
+ TARGET_DIR="${targetDir}"
24
+ GIT_DIR="${bareRepo}"
25
+ LOG_FILE="/var/log/gpd-${serviceName}.log"
26
+
27
+ log() {
28
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
29
+ }
30
+
31
+ log "=== Deployment started for $SERVICE ==="
32
+
33
+ while read oldrev newrev refname; do
34
+ BRANCH=$(echo "$refname" | sed 's|refs/heads/||')
35
+ log "Received push to branch: $BRANCH"
36
+
37
+ if [ "$BRANCH" != "main" ] && [ "$BRANCH" != "master" ]; then
38
+ log "Ignoring branch $BRANCH (only main/master triggers deploy)"
39
+ continue
40
+ fi
41
+
42
+ # Export variables for gpd install
43
+ export GPD_TARGET_DIR="$TARGET_DIR"
44
+ export GPD_GIT_DIR="$GIT_DIR"
45
+ export GPD_SERVICE="$SERVICE"
46
+ ${pm2Home ? `export PM2_HOME="${pm2Home}"` : ''}
47
+
48
+ # Run gpd install (handles checkout, npm install, pm2 restart)
49
+ log "Running: ${installCmd}"
50
+ ${installCmd} 2>&1 | tee -a "$LOG_FILE"
51
+ done
52
+
53
+ log "=== Deployment completed ==="
54
+ `;
55
+ }
56
+ /**
57
+ * Init command - initialize bare repo and target directory on remote server via SSH
6
58
  *
7
59
  * Creates:
8
- * 1. Bare repo at server.bareRepo (e.g., /git/sym/deploy-kairox-api)
9
- * 2. Clone at server.targetDir (e.g., /opt/kairox/kairox-api)
60
+ * 1. Bare repo at server.bareRepo (e.g., /git/sym/deploy-kairox/staging/kairox-api)
61
+ * 2. Target directory at server.targetDir (e.g., /opt/kairox/staging/kairox-api)
62
+ * 3. Post-receive hook that calls `gpd install <service>`
10
63
  */
11
64
  export async function initCommand(serviceName, _options = {}) {
12
65
  console.log(chalk.blue(`Initializing ${serviceName}...`));
13
66
  const config = getServiceConfig(serviceName);
14
- const { host, bareRepo, targetDir } = config.server;
67
+ const { host, bareRepo, targetDir, sshOptions, group } = config.server;
68
+ const { pm2User, pm2Home } = config;
15
69
  console.log(chalk.gray(` Host: ${host}`));
16
70
  console.log(chalk.gray(` Bare repo: ${bareRepo}`));
17
71
  console.log(chalk.gray(` Target dir: ${targetDir}`));
18
- // 1. Create bare repo
72
+ if (group)
73
+ console.log(chalk.gray(` Group: ${group}`));
74
+ if (pm2User)
75
+ console.log(chalk.gray(` PM2 user: ${pm2User}`));
76
+ // 1. Create group if specified
77
+ if (group) {
78
+ console.log(chalk.gray(` Creating group ${group}...`));
79
+ const createGroupCmd = `sudo groupadd -f ${group} && sudo usermod -aG ${group} $(whoami)`;
80
+ sshExec(host, createGroupCmd, { sshOptions });
81
+ }
82
+ // 2. Create bare repo with shared group access
19
83
  console.log(chalk.gray(` Creating bare repo...`));
20
- const createBareCmd = `mkdir -p ${bareRepo} && cd ${bareRepo} && git init --bare`;
21
- sshExec(host, createBareCmd);
22
- // 2. Create target directory and clone
23
- console.log(chalk.gray(` Creating clone at target...`));
24
- const parentDir = targetDir.split('/').slice(0, -1).join('/');
25
- const cloneName = targetDir.split('/').pop();
26
- const cloneCmd = `mkdir -p ${parentDir} && cd ${parentDir} && git clone ${bareRepo} ${cloneName} 2>/dev/null || (cd ${targetDir} && git pull)`;
27
- sshExec(host, cloneCmd);
84
+ const bareRepoParent = bareRepo.split('/').slice(0, -1).join('/');
85
+ let createBareCmd = `sudo mkdir -p ${bareRepoParent}`;
86
+ if (group) {
87
+ createBareCmd += ` && sudo chgrp ${group} ${bareRepoParent} && sudo chmod g+rwxs ${bareRepoParent}`;
88
+ }
89
+ createBareCmd += ` && cd ${bareRepoParent} && git init --bare --shared=group ${bareRepo.split('/').pop()}`;
90
+ if (group) {
91
+ createBareCmd += ` && sudo chgrp -R ${group} ${bareRepo}`;
92
+ }
93
+ sshExec(host, createBareCmd, { sshOptions });
94
+ // 3. Create target directory
95
+ console.log(chalk.gray(` Creating target directory...`));
96
+ const targetParent = targetDir.split('/').slice(0, -1).join('/');
97
+ let createTargetCmd = `sudo mkdir -p ${targetDir}`;
98
+ if (pm2User) {
99
+ createTargetCmd += ` && sudo chown ${pm2User}:${group || pm2User} ${targetDir}`;
100
+ }
101
+ if (group) {
102
+ createTargetCmd += ` && sudo chgrp -R ${group} ${targetParent} && sudo chmod g+rwxs ${targetParent}`;
103
+ }
104
+ sshExec(host, createTargetCmd, { sshOptions });
105
+ // 4. Create post-receive hook
106
+ console.log(chalk.gray(` Creating post-receive hook...`));
107
+ const hookContent = generatePostReceiveHook(serviceName, {
108
+ targetDir,
109
+ bareRepo,
110
+ pm2User,
111
+ pm2Home
112
+ });
113
+ // Escape for shell (use base64 to avoid quote issues)
114
+ const hookBase64 = Buffer.from(hookContent).toString('base64');
115
+ const hookPath = `${bareRepo}/hooks/post-receive`;
116
+ const createHookCmd = `echo '${hookBase64}' | base64 -d | sudo tee ${hookPath} > /dev/null && sudo chmod +x ${hookPath}`;
117
+ if (group) {
118
+ sshExec(host, `${createHookCmd} && sudo chgrp ${group} ${hookPath}`, { sshOptions });
119
+ }
120
+ else {
121
+ sshExec(host, createHookCmd, { sshOptions });
122
+ }
123
+ // 5. Create log file
124
+ console.log(chalk.gray(` Creating log file...`));
125
+ const logFile = `/var/log/gpd-${serviceName}.log`;
126
+ let createLogCmd = `sudo touch ${logFile} && sudo chmod 666 ${logFile}`;
127
+ if (group) {
128
+ createLogCmd += ` && sudo chgrp ${group} ${logFile}`;
129
+ }
130
+ sshExec(host, createLogCmd, { sshOptions });
28
131
  console.log(chalk.green(`✓ Initialized ${serviceName}`));
29
132
  console.log('');
30
133
  console.log(chalk.gray('Server setup complete. Now you can deploy:'));
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAM5C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,WAAmB,EAAE,WAAwB,EAAE;IAC/E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,WAAW,KAAK,CAAC,CAAC,CAAC;IAE1D,MAAM,MAAM,GAAG,gBAAgB,CAAC,WAAW,CAAC,CAAC;IAC7C,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC;IAEpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;IAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,QAAQ,EAAE,CAAC,CAAC,CAAC;IACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,SAAS,EAAE,CAAC,CAAC,CAAC;IAEtD,sBAAsB;IACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAC;IACnD,MAAM,aAAa,GAAG,YAAY,QAAQ,UAAU,QAAQ,qBAAqB,CAAC;IAClF,OAAO,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;IAE7B,uCAAuC;IACvC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;IAC7C,MAAM,QAAQ,GAAG,YAAY,SAAS,UAAU,SAAS,iBAAiB,QAAQ,IAAI,SAAS,uBAAuB,SAAS,eAAe,CAAC;IAC/I,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IAExB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,gBAAgB,WAAW,EAAE,CAAC,CAAC,CAAC;AAC1D,CAAC"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ interface InstallOptions {
2
+ configPath?: string;
3
+ }
4
+ /**
5
+ * Install command - runs on server after git push (called by post-receive hook)
6
+ *
7
+ * Environment variables from hook:
8
+ * - GPD_TARGET_DIR: Where to install (e.g., /opt/kairox/staging/kairox-api)
9
+ * - GPD_GIT_DIR: Bare repo path (e.g., /git/sym/deploy-kairox/staging/kairox-api)
10
+ * - GPD_SERVICE: Service name (e.g., kairox-api-staging)
11
+ * - PM2_HOME: PM2 home directory (optional)
12
+ *
13
+ * Steps:
14
+ * 1. git checkout from bare repo to target dir
15
+ * 2. Generate .env file from config
16
+ * 3. npm install --omit=dev
17
+ * 4. PM2 restart
18
+ */
19
+ export declare function installCommand(serviceName: string, options?: InstallOptions): Promise<void>;
20
+ export {};
21
+ //# sourceMappingURL=install.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../../src/commands/install.ts"],"names":[],"mappings":"AAMA,UAAU,cAAc;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAoDD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,GAAE,cAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAiHrG"}