create-dacosta-proj 1.0.20 → 1.0.22
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/package.json
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// Global Tools
|
|
2
|
+
|
|
3
|
+
require('dotenv').config({ quiet: true });
|
|
4
|
+
|
|
5
|
+
// Packages / Helpers
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const { NodeSSH } = require('node-ssh');
|
|
10
|
+
const { files } = require('./files');
|
|
11
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
12
|
+
|
|
13
|
+
// Get All Servers
|
|
14
|
+
|
|
15
|
+
let servers = Object.keys(process.env).filter(key => key.startsWith('DEPLOY_') && key.endsWith('_IP')).length;
|
|
16
|
+
servers = new Array(servers).fill().map((_,server) => {
|
|
17
|
+
return {
|
|
18
|
+
ip: process.env[`DEPLOY_${server+1}_IP`],
|
|
19
|
+
password: process.env[`DEPLOY_${server+1}_PASSWORD`],
|
|
20
|
+
path: process.env[`DEPLOY_${server+1}_PATH`]
|
|
21
|
+
};
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Collect Remote Directories
|
|
25
|
+
|
|
26
|
+
function collectRemoteDirs(localDir, remoteDir) {
|
|
27
|
+
const dirs = [];
|
|
28
|
+
for (const entry of fs.readdirSync(localDir, { withFileTypes: true })) {
|
|
29
|
+
if (entry.isDirectory()) {
|
|
30
|
+
const sub = path.posix.join(remoteDir, entry.name);
|
|
31
|
+
dirs.push(sub);
|
|
32
|
+
dirs.push(...collectRemoteDirs(path.join(localDir, entry.name), sub));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return dirs;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Deploy to Server
|
|
39
|
+
|
|
40
|
+
async function deployTo(server) {
|
|
41
|
+
|
|
42
|
+
console.log(`\n→ ${server.ip}`);
|
|
43
|
+
|
|
44
|
+
const ssh = new NodeSSH();
|
|
45
|
+
await ssh.connect({
|
|
46
|
+
host: server.ip,
|
|
47
|
+
username: 'root',
|
|
48
|
+
password: server.password,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
await ssh.execCommand(`mkdir -p ${server.path}`);
|
|
52
|
+
|
|
53
|
+
for (const item of files) {
|
|
54
|
+
|
|
55
|
+
const localPath = path.join(rootDir, item);
|
|
56
|
+
const remotePath = path.posix.join(server.path, item);
|
|
57
|
+
|
|
58
|
+
if (!fs.existsSync(localPath)) {
|
|
59
|
+
console.warn(` ⚠ ${item} — not found, skipping`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const stat = fs.statSync(localPath);
|
|
64
|
+
|
|
65
|
+
await ssh.execCommand(`rm -rf ${remotePath}`);
|
|
66
|
+
|
|
67
|
+
if (stat.isDirectory()) {
|
|
68
|
+
const allDirs = [remotePath, ...collectRemoteDirs(localPath, remotePath)];
|
|
69
|
+
const mkdirArgs = allDirs.map(d => `'${d}'`).join(' ');
|
|
70
|
+
await ssh.execCommand(`mkdir -p ${mkdirArgs}`);
|
|
71
|
+
|
|
72
|
+
const failures = [];
|
|
73
|
+
const success = await ssh.putDirectory(localPath, remotePath, {
|
|
74
|
+
recursive: true,
|
|
75
|
+
concurrency: 1,
|
|
76
|
+
tick: (local, remote, error) => {
|
|
77
|
+
const rel = path.relative(rootDir, local);
|
|
78
|
+
if (error) {
|
|
79
|
+
failures.push(`${rel} — ${error.message}`);
|
|
80
|
+
console.error(` ✗ ${rel} — ${error.message}`);
|
|
81
|
+
} else {
|
|
82
|
+
console.log(` ✓ ${rel}`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
if (!success) throw new Error(`Upload failed for ${item} on ${server.ip}:\n ${failures.join('\n ')}`);
|
|
87
|
+
} else {
|
|
88
|
+
await ssh.putFile(localPath, remotePath);
|
|
89
|
+
console.log(` ✓ ${item}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const devrefPath = path.posix.join(server.path, 'devref.json');
|
|
95
|
+
await ssh.execCommand(`printf 'false' > ${devrefPath}`);
|
|
96
|
+
console.log(' ✓ devref.json (created)');
|
|
97
|
+
|
|
98
|
+
ssh.dispose();
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Start Deploy
|
|
103
|
+
|
|
104
|
+
(async () => {
|
|
105
|
+
|
|
106
|
+
for (const server of servers) {
|
|
107
|
+
await deployTo(server);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log('\nDeploy complete!');
|
|
111
|
+
process.exit();
|
|
112
|
+
|
|
113
|
+
})();
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Global Tools
|
|
2
|
+
|
|
3
|
+
require('dotenv').config({ quiet: true });
|
|
4
|
+
|
|
5
|
+
// Packages / Helpers
|
|
6
|
+
|
|
7
|
+
const { NodeSSH } = require('node-ssh');
|
|
8
|
+
|
|
9
|
+
// Get All Servers
|
|
10
|
+
|
|
11
|
+
const servers = Object.keys(process.env)
|
|
12
|
+
.filter(key => /^DEPLOY_\d+_IP$/.test(key))
|
|
13
|
+
.length;
|
|
14
|
+
|
|
15
|
+
const targets = new Array(servers).fill().map((_, server) => {
|
|
16
|
+
const pm2 = (process.env[`DEPLOY_${server+1}_PM2_NAME`] || '')
|
|
17
|
+
.split(';')
|
|
18
|
+
.map(p => p.trim())
|
|
19
|
+
.filter(Boolean);
|
|
20
|
+
return {
|
|
21
|
+
ip: process.env[`DEPLOY_${server+1}_IP`],
|
|
22
|
+
password: process.env[`DEPLOY_${server+1}_PASSWORD`],
|
|
23
|
+
path: process.env[`DEPLOY_${server+1}_PATH`],
|
|
24
|
+
src: process.env[`DEPLOY_${server+1}_PM2_SRC`],
|
|
25
|
+
args: process.env[`DEPLOY_${server+1}_PM2_ARGS`],
|
|
26
|
+
pm2,
|
|
27
|
+
};
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Build a shell command that loads nvm first
|
|
31
|
+
|
|
32
|
+
function withNvm(cmd) {
|
|
33
|
+
return `. ~/.nvm/nvm.sh && ${cmd}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Restart on Server
|
|
37
|
+
|
|
38
|
+
async function restartOn(server) {
|
|
39
|
+
|
|
40
|
+
console.log(`\n→ ${server.ip}`);
|
|
41
|
+
|
|
42
|
+
if (!server.pm2.length) {
|
|
43
|
+
console.warn(' ⚠ no pm2 processes configured, skipping');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const ssh = new NodeSSH();
|
|
48
|
+
await ssh.connect({
|
|
49
|
+
host: server.ip,
|
|
50
|
+
username: 'root',
|
|
51
|
+
password: server.password,
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
const install = await ssh.execCommand(withNvm(`cd ${server.path} && npm install --no-audit --no-fund`));
|
|
55
|
+
if (install.stdout) console.log(install.stdout.split('\n').map(l => ` ${l}`).join('\n'));
|
|
56
|
+
if (install.code === 0) {
|
|
57
|
+
console.log(' ✓ npm install');
|
|
58
|
+
} else {
|
|
59
|
+
console.error(` ✗ npm install — ${install.stderr || install.stdout}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
for (const name of server.pm2) {
|
|
63
|
+
|
|
64
|
+
const exists = await ssh.execCommand(withNvm(`pm2 describe ${name} > /dev/null 2>&1`));
|
|
65
|
+
|
|
66
|
+
if (exists.code === 0) {
|
|
67
|
+
const result = await ssh.execCommand(withNvm(`pm2 restart ${name}`));
|
|
68
|
+
if (result.code === 0) {
|
|
69
|
+
console.log(` ✓ ${name} (restarted)`);
|
|
70
|
+
} else {
|
|
71
|
+
console.error(` ✗ ${name} — ${result.stderr || result.stdout}`);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
const result = await ssh.execCommand(
|
|
75
|
+
withNvm(`${server.args ? `${server.args} ` : ''}pm2 start ${server.src} --name "${name}" --log-date-format "YYYY-MM-DD HH:mm"`),
|
|
76
|
+
{ cwd: server.path }
|
|
77
|
+
);
|
|
78
|
+
if (result.code === 0) {
|
|
79
|
+
console.log(` ✓ ${name} (created)`);
|
|
80
|
+
} else {
|
|
81
|
+
console.error(` ✗ ${name} — ${result.stderr || result.stdout}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
ssh.dispose();
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Start Restart
|
|
92
|
+
|
|
93
|
+
(async () => {
|
|
94
|
+
|
|
95
|
+
for (const server of targets) {
|
|
96
|
+
await restartOn(server);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('\nRestart complete!');
|
|
100
|
+
process.exit();
|
|
101
|
+
|
|
102
|
+
})();
|
package/template/.env.template
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
# Project
|
|
2
2
|
|
|
3
3
|
PROJECT_ID=
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
|
|
5
|
+
# Deployment
|
|
6
|
+
|
|
7
|
+
DEPLOY_1_IP=
|
|
8
|
+
DEPLOY_1_PASSWORD=
|
|
9
|
+
DEPLOY_1_PATH=
|
|
10
|
+
DEPLOY_1_PM2_NAME=
|
|
11
|
+
# EXAMPLE 1: DEPLOY_1_PM2_NAME=nm-bot
|
|
12
|
+
# EXAMPLE 2: DEPLOY_1_PM2_NAME=nm-tiktok-video-1;nm-tiktok-live-1
|
|
13
|
+
DEPLOY_1_PM2_SRC=src/index.js
|
|
14
|
+
DEPLOY_1_PM2_ARGS= # Optional
|
|
15
|
+
# EXAMPLE: DEPLOY_1_PM2_ARGS=PROJECT_INSTANCE_ID=1 PROJECT_INSTANCE_TYPE=video
|
|
7
16
|
|
|
8
17
|
# Supabase
|
|
9
18
|
|
package/template/package.json
CHANGED
|
@@ -4,19 +4,21 @@
|
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "nodemon src/index.js",
|
|
7
|
-
"start": "node src/index.js"
|
|
7
|
+
"start": "node src/index.js",
|
|
8
|
+
"deploy": "node .deploy/deploy.js",
|
|
9
|
+
"restart": "node .deploy/restart.js"
|
|
8
10
|
},
|
|
9
11
|
"dependencies": {
|
|
10
12
|
"@supabase/supabase-js": "^2.101.1",
|
|
11
13
|
"cors": "^2.8.6",
|
|
12
14
|
"dotenv": "^17.3.1",
|
|
13
15
|
"module-alias": "^2.3.4",
|
|
14
|
-
"nanoid": "^5.1.6",
|
|
15
16
|
"redis": "^5.11.0",
|
|
16
17
|
"simple-supabase": "^2.0.10"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
19
20
|
"eslint": "^9.39.3",
|
|
21
|
+
"node-ssh": "^13.2.1",
|
|
20
22
|
"nodemon": "^3.1.13"
|
|
21
23
|
},
|
|
22
24
|
"_moduleAliases": {
|