suthep 0.1.0 → 0.2.0-beta.1
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 +172 -71
- package/dist/commands/deploy.js +251 -37
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/down.js +179 -0
- package/dist/commands/down.js.map +1 -0
- package/dist/commands/redeploy.js +59 -0
- package/dist/commands/redeploy.js.map +1 -0
- package/dist/commands/up.js +213 -0
- package/dist/commands/up.js.map +1 -0
- package/dist/index.js +36 -1
- package/dist/index.js.map +1 -1
- package/dist/utils/certbot.js +40 -3
- package/dist/utils/certbot.js.map +1 -1
- package/dist/utils/config-loader.js +30 -0
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/deployment.js +49 -16
- package/dist/utils/deployment.js.map +1 -1
- package/dist/utils/docker.js +396 -25
- package/dist/utils/docker.js.map +1 -1
- package/dist/utils/nginx.js +167 -8
- package/dist/utils/nginx.js.map +1 -1
- package/docs/README.md +25 -49
- package/docs/english/01-introduction.md +84 -0
- package/docs/english/02-installation.md +200 -0
- package/docs/english/03-quick-start.md +256 -0
- package/docs/english/04-configuration.md +358 -0
- package/docs/english/05-commands.md +363 -0
- package/docs/english/06-examples.md +456 -0
- package/docs/english/07-troubleshooting.md +417 -0
- package/docs/english/08-advanced.md +411 -0
- package/docs/english/README.md +48 -0
- package/docs/thai/01-introduction.md +84 -0
- package/docs/thai/02-installation.md +200 -0
- package/docs/thai/03-quick-start.md +256 -0
- package/docs/thai/04-configuration.md +358 -0
- package/docs/thai/05-commands.md +363 -0
- package/docs/thai/06-examples.md +456 -0
- package/docs/thai/07-troubleshooting.md +417 -0
- package/docs/thai/08-advanced.md +411 -0
- package/docs/thai/README.md +48 -0
- package/example/README.md +286 -53
- package/example/suthep-complete.yml +103 -0
- package/example/suthep-docker-only.yml +71 -0
- package/example/suthep-no-docker.yml +51 -0
- package/example/suthep-path-routing.yml +62 -0
- package/example/suthep.example.yml +89 -0
- package/package.json +1 -1
- package/src/commands/deploy.ts +322 -50
- package/src/commands/down.ts +240 -0
- package/src/commands/redeploy.ts +78 -0
- package/src/commands/up.ts +271 -0
- package/src/index.ts +62 -1
- package/src/types/config.ts +25 -24
- package/src/utils/certbot.ts +68 -6
- package/src/utils/config-loader.ts +40 -0
- package/src/utils/deployment.ts +61 -36
- package/src/utils/docker.ts +634 -30
- package/src/utils/nginx.ts +187 -4
- package/suthep-0.1.0-beta.1.tgz +0 -0
- package/suthep-0.1.1.tgz +0 -0
- package/suthep.example.yml +34 -0
- package/suthep.yml +39 -0
- package/test +0 -0
- package/docs/api-reference.md +0 -545
- package/docs/architecture.md +0 -367
- package/docs/commands.md +0 -273
- package/docs/configuration.md +0 -347
- package/docs/examples.md +0 -537
- package/docs/getting-started.md +0 -197
- package/docs/troubleshooting.md +0 -441
- package/example/docker-compose.yml +0 -72
- package/example/suthep.yml +0 -31
package/dist/utils/nginx.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { execa } from "execa";
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import path from "path";
|
|
4
|
-
function generateNginxConfig(service, withHttps) {
|
|
4
|
+
function generateNginxConfig(service, withHttps, portOverride) {
|
|
5
5
|
const serverNames = service.domains.join(" ");
|
|
6
|
-
const
|
|
6
|
+
const primaryDomain = service.domains[0];
|
|
7
|
+
const domainSafe = primaryDomain.replace(/\./g, "_").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
8
|
+
const upstreamName = `${domainSafe}_${service.name}`;
|
|
9
|
+
const servicePath = service.path || "/";
|
|
10
|
+
const port = portOverride || service.port;
|
|
7
11
|
let config = `# Nginx configuration for ${service.name}
|
|
8
12
|
|
|
9
13
|
`;
|
|
10
14
|
config += `upstream ${upstreamName} {
|
|
11
15
|
`;
|
|
12
|
-
config += ` server localhost:${
|
|
16
|
+
config += ` server localhost:${port} max_fails=3 fail_timeout=30s;
|
|
13
17
|
`;
|
|
14
18
|
config += ` keepalive 32;
|
|
15
19
|
`;
|
|
@@ -42,12 +46,12 @@ function generateNginxConfig(service, withHttps) {
|
|
|
42
46
|
config += ` server_name ${serverNames};
|
|
43
47
|
|
|
44
48
|
`;
|
|
45
|
-
const
|
|
49
|
+
const primaryDomain2 = service.domains[0];
|
|
46
50
|
config += ` # SSL Configuration
|
|
47
51
|
`;
|
|
48
|
-
config += ` ssl_certificate /etc/letsencrypt/live/${
|
|
52
|
+
config += ` ssl_certificate /etc/letsencrypt/live/${primaryDomain2}/fullchain.pem;
|
|
49
53
|
`;
|
|
50
|
-
config += ` ssl_certificate_key /etc/letsencrypt/live/${
|
|
54
|
+
config += ` ssl_certificate_key /etc/letsencrypt/live/${primaryDomain2}/privkey.pem;
|
|
51
55
|
`;
|
|
52
56
|
config += ` ssl_protocols TLSv1.2 TLSv1.3;
|
|
53
57
|
`;
|
|
@@ -81,7 +85,7 @@ function generateNginxConfig(service, withHttps) {
|
|
|
81
85
|
`;
|
|
82
86
|
config += ` # Proxy settings
|
|
83
87
|
`;
|
|
84
|
-
config += ` location
|
|
88
|
+
config += ` location ${servicePath} {
|
|
85
89
|
`;
|
|
86
90
|
config += ` proxy_pass http://${upstreamName};
|
|
87
91
|
`;
|
|
@@ -126,6 +130,149 @@ function generateNginxConfig(service, withHttps) {
|
|
|
126
130
|
`;
|
|
127
131
|
return config;
|
|
128
132
|
}
|
|
133
|
+
function generateMultiServiceNginxConfig(services, domain, withHttps, portOverrides) {
|
|
134
|
+
const upstreams = [];
|
|
135
|
+
const locations = [];
|
|
136
|
+
const healthChecks = [];
|
|
137
|
+
const sortedServices = [...services].sort((a, b) => {
|
|
138
|
+
const pathA = (a.path || "/").length;
|
|
139
|
+
const pathB = (b.path || "/").length;
|
|
140
|
+
return pathB - pathA;
|
|
141
|
+
});
|
|
142
|
+
const usedUpstreamNames = /* @__PURE__ */ new Set();
|
|
143
|
+
for (const service of sortedServices) {
|
|
144
|
+
const domainSafe = domain.replace(/\./g, "_").replace(/[^a-zA-Z0-9_]/g, "_");
|
|
145
|
+
let upstreamName = `${domainSafe}_${service.name}`;
|
|
146
|
+
let counter = 1;
|
|
147
|
+
const originalUpstreamName = upstreamName;
|
|
148
|
+
while (usedUpstreamNames.has(upstreamName)) {
|
|
149
|
+
upstreamName = `${originalUpstreamName}_${counter}`;
|
|
150
|
+
counter++;
|
|
151
|
+
}
|
|
152
|
+
usedUpstreamNames.add(upstreamName);
|
|
153
|
+
const servicePath = service.path || "/";
|
|
154
|
+
const port = portOverrides?.get(service.name) || service.port;
|
|
155
|
+
upstreams.push(`upstream ${upstreamName} {`);
|
|
156
|
+
upstreams.push(` server localhost:${port} max_fails=3 fail_timeout=30s;`);
|
|
157
|
+
upstreams.push(` keepalive 32;`);
|
|
158
|
+
upstreams.push(`}`);
|
|
159
|
+
if (servicePath === "/") {
|
|
160
|
+
locations.push(` # Service: ${service.name}`);
|
|
161
|
+
locations.push(` location / {`);
|
|
162
|
+
} else {
|
|
163
|
+
locations.push(` # Service: ${service.name}`);
|
|
164
|
+
locations.push(` location ${servicePath} {`);
|
|
165
|
+
}
|
|
166
|
+
locations.push(` proxy_pass http://${upstreamName};`);
|
|
167
|
+
locations.push(` proxy_http_version 1.1;`);
|
|
168
|
+
locations.push(` proxy_set_header Upgrade $http_upgrade;`);
|
|
169
|
+
locations.push(` proxy_set_header Connection 'upgrade';`);
|
|
170
|
+
locations.push(` proxy_set_header Host $host;`);
|
|
171
|
+
locations.push(` proxy_set_header X-Real-IP $remote_addr;`);
|
|
172
|
+
locations.push(` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`);
|
|
173
|
+
locations.push(` proxy_set_header X-Forwarded-Proto $scheme;`);
|
|
174
|
+
locations.push(` proxy_cache_bypass $http_upgrade;`);
|
|
175
|
+
locations.push(` proxy_connect_timeout 60s;`);
|
|
176
|
+
locations.push(` proxy_send_timeout 60s;`);
|
|
177
|
+
locations.push(` proxy_read_timeout 60s;`);
|
|
178
|
+
locations.push(` }`);
|
|
179
|
+
if (service.healthCheck) {
|
|
180
|
+
healthChecks.push(` # Health check for ${service.name}`);
|
|
181
|
+
healthChecks.push(` location ${service.healthCheck.path} {`);
|
|
182
|
+
healthChecks.push(` proxy_pass http://${upstreamName};`);
|
|
183
|
+
healthChecks.push(` access_log off;`);
|
|
184
|
+
healthChecks.push(` }`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
let config = `# Nginx configuration for ${domain}
|
|
188
|
+
`;
|
|
189
|
+
config += `# Multiple services on the same domain
|
|
190
|
+
|
|
191
|
+
`;
|
|
192
|
+
config += upstreams.join("\n") + "\n\n";
|
|
193
|
+
if (withHttps) {
|
|
194
|
+
config += `server {
|
|
195
|
+
`;
|
|
196
|
+
config += ` listen 80;
|
|
197
|
+
`;
|
|
198
|
+
config += ` listen [::]:80;
|
|
199
|
+
`;
|
|
200
|
+
config += ` server_name ${domain};
|
|
201
|
+
|
|
202
|
+
`;
|
|
203
|
+
config += ` # Redirect all HTTP to HTTPS
|
|
204
|
+
`;
|
|
205
|
+
config += ` return 301 https://$server_name$request_uri;
|
|
206
|
+
`;
|
|
207
|
+
config += `}
|
|
208
|
+
|
|
209
|
+
`;
|
|
210
|
+
config += `server {
|
|
211
|
+
`;
|
|
212
|
+
config += ` listen 443 ssl http2;
|
|
213
|
+
`;
|
|
214
|
+
config += ` listen [::]:443 ssl http2;
|
|
215
|
+
`;
|
|
216
|
+
config += ` server_name ${domain};
|
|
217
|
+
|
|
218
|
+
`;
|
|
219
|
+
config += ` # SSL Configuration
|
|
220
|
+
`;
|
|
221
|
+
config += ` ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
|
|
222
|
+
`;
|
|
223
|
+
config += ` ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;
|
|
224
|
+
`;
|
|
225
|
+
config += ` ssl_protocols TLSv1.2 TLSv1.3;
|
|
226
|
+
`;
|
|
227
|
+
config += ` ssl_ciphers HIGH:!aNULL:!MD5;
|
|
228
|
+
`;
|
|
229
|
+
config += ` ssl_prefer_server_ciphers on;
|
|
230
|
+
|
|
231
|
+
`;
|
|
232
|
+
} else {
|
|
233
|
+
config += `server {
|
|
234
|
+
`;
|
|
235
|
+
config += ` listen 80;
|
|
236
|
+
`;
|
|
237
|
+
config += ` listen [::]:80;
|
|
238
|
+
`;
|
|
239
|
+
config += ` server_name ${domain};
|
|
240
|
+
|
|
241
|
+
`;
|
|
242
|
+
}
|
|
243
|
+
config += ` # Logging
|
|
244
|
+
`;
|
|
245
|
+
config += ` access_log /var/log/nginx/${domain}_access.log;
|
|
246
|
+
`;
|
|
247
|
+
config += ` error_log /var/log/nginx/${domain}_error.log;
|
|
248
|
+
|
|
249
|
+
`;
|
|
250
|
+
config += ` # Client settings
|
|
251
|
+
`;
|
|
252
|
+
config += ` client_max_body_size 100M;
|
|
253
|
+
|
|
254
|
+
`;
|
|
255
|
+
config += ` # Service locations
|
|
256
|
+
`;
|
|
257
|
+
config += locations.join("\n") + "\n\n";
|
|
258
|
+
if (healthChecks.length > 0) {
|
|
259
|
+
config += ` # Health check endpoints
|
|
260
|
+
`;
|
|
261
|
+
config += healthChecks.join("\n") + "\n";
|
|
262
|
+
}
|
|
263
|
+
config += `}
|
|
264
|
+
`;
|
|
265
|
+
return config;
|
|
266
|
+
}
|
|
267
|
+
async function writeNginxConfig(configName, configPath, configContent) {
|
|
268
|
+
const configFilePath = path.join(configPath, `${configName}.conf`);
|
|
269
|
+
const exists = await fs.pathExists(configFilePath);
|
|
270
|
+
if (exists) {
|
|
271
|
+
await fs.remove(configFilePath);
|
|
272
|
+
}
|
|
273
|
+
await fs.writeFile(configFilePath, configContent);
|
|
274
|
+
return exists;
|
|
275
|
+
}
|
|
129
276
|
async function enableSite(siteName, configPath) {
|
|
130
277
|
const availablePath = path.join(configPath, `${siteName}.conf`);
|
|
131
278
|
const enabledPath = availablePath.replace("sites-available", "sites-enabled");
|
|
@@ -146,9 +293,21 @@ async function reloadNginx(reloadCommand) {
|
|
|
146
293
|
throw new Error(`Failed to reload Nginx: ${error instanceof Error ? error.message : error}`);
|
|
147
294
|
}
|
|
148
295
|
}
|
|
296
|
+
async function disableSite(siteName, configPath) {
|
|
297
|
+
const enabledPath = path.join(
|
|
298
|
+
configPath.replace("sites-available", "sites-enabled"),
|
|
299
|
+
`${siteName}.conf`
|
|
300
|
+
);
|
|
301
|
+
if (await fs.pathExists(enabledPath)) {
|
|
302
|
+
await fs.remove(enabledPath);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
149
305
|
export {
|
|
306
|
+
disableSite,
|
|
150
307
|
enableSite,
|
|
308
|
+
generateMultiServiceNginxConfig,
|
|
151
309
|
generateNginxConfig,
|
|
152
|
-
reloadNginx
|
|
310
|
+
reloadNginx,
|
|
311
|
+
writeNginxConfig
|
|
153
312
|
};
|
|
154
313
|
//# sourceMappingURL=nginx.js.map
|
package/dist/utils/nginx.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nginx.js","sources":["../../src/utils/nginx.ts"],"sourcesContent":["import { execa } from 'execa'\nimport fs from 'fs-extra'\nimport path from 'path'\nimport type { ServiceConfig } from '../types/config'\n\n/**\n * Generate Nginx server block configuration for a service\n */\nexport function generateNginxConfig(service: ServiceConfig, withHttps: boolean): string {\n const serverNames = service.domains.join(' ')\n const upstreamName = `${service.name}_backend`\n\n let config = `# Nginx configuration for ${service.name}\\n\\n`\n\n // Upstream configuration\n config += `upstream ${upstreamName} {\\n`\n config += ` server localhost:${service.port} max_fails=3 fail_timeout=30s;\\n`\n config += ` keepalive 32;\\n`\n config += `}\\n\\n`\n\n if (withHttps) {\n // HTTP server - redirect to HTTPS\n config += `server {\\n`\n config += ` listen 80;\\n`\n config += ` listen [::]:80;\\n`\n config += ` server_name ${serverNames};\\n\\n`\n config += ` # Redirect all HTTP to HTTPS\\n`\n config += ` return 301 https://$server_name$request_uri;\\n`\n config += `}\\n\\n`\n\n // HTTPS server\n config += `server {\\n`\n config += ` listen 443 ssl http2;\\n`\n config += ` listen [::]:443 ssl http2;\\n`\n config += ` server_name ${serverNames};\\n\\n`\n\n // SSL configuration\n const primaryDomain = service.domains[0]\n config += ` # SSL Configuration\\n`\n config += ` ssl_certificate /etc/letsencrypt/live/${primaryDomain}/fullchain.pem;\\n`\n config += ` ssl_certificate_key /etc/letsencrypt/live/${primaryDomain}/privkey.pem;\\n`\n config += ` ssl_protocols TLSv1.2 TLSv1.3;\\n`\n config += ` ssl_ciphers HIGH:!aNULL:!MD5;\\n`\n config += ` ssl_prefer_server_ciphers on;\\n\\n`\n } else {\n // HTTP only server\n config += `server {\\n`\n config += ` listen 80;\\n`\n config += ` listen [::]:80;\\n`\n config += ` server_name ${serverNames};\\n\\n`\n }\n\n // Logging\n config += ` # Logging\\n`\n config += ` access_log /var/log/nginx/${service.name}_access.log;\\n`\n config += ` error_log /var/log/nginx/${service.name}_error.log;\\n\\n`\n\n // Client settings\n config += ` # Client settings\\n`\n config += ` client_max_body_size 100M;\\n\\n`\n\n // Proxy settings\n config += ` # Proxy settings\\n`\n config += ` location / {\\n`\n config += ` proxy_pass http://${upstreamName};\\n`\n config += ` proxy_http_version 1.1;\\n`\n config += ` proxy_set_header Upgrade $http_upgrade;\\n`\n config += ` proxy_set_header Connection 'upgrade';\\n`\n config += ` proxy_set_header Host $host;\\n`\n config += ` proxy_set_header X-Real-IP $remote_addr;\\n`\n config += ` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\\n`\n config += ` proxy_set_header X-Forwarded-Proto $scheme;\\n`\n config += ` proxy_cache_bypass $http_upgrade;\\n`\n config += ` proxy_connect_timeout 60s;\\n`\n config += ` proxy_send_timeout 60s;\\n`\n config += ` proxy_read_timeout 60s;\\n`\n config += ` }\\n`\n\n // Health check endpoint (if configured)\n if (service.healthCheck) {\n config += `\\n # Health check endpoint\\n`\n config += ` location ${service.healthCheck.path} {\\n`\n config += ` proxy_pass http://${upstreamName};\\n`\n config += ` access_log off;\\n`\n config += ` }\\n`\n }\n\n config += `}\\n`\n\n return config\n}\n\n/**\n * Enable an Nginx site by creating a symbolic link\n */\nexport async function enableSite(siteName: string, configPath: string): Promise<void> {\n const availablePath = path.join(configPath, `${siteName}.conf`)\n const enabledPath = availablePath.replace('sites-available', 'sites-enabled')\n\n // Create sites-enabled directory if it doesn't exist\n await fs.ensureDir(path.dirname(enabledPath))\n\n // Remove existing symlink if present\n if (await fs.pathExists(enabledPath)) {\n await fs.remove(enabledPath)\n }\n\n // Create symlink\n await execa('sudo', ['ln', '-sf', availablePath, enabledPath])\n}\n\n/**\n * Test and reload Nginx configuration\n */\nexport async function reloadNginx(reloadCommand: string): Promise<void> {\n try {\n // Test configuration first\n await execa('sudo', ['nginx', '-t'])\n\n // Reload Nginx\n const parts = reloadCommand.split(' ')\n if (parts.length > 0) {\n // Simple execution of provided command\n await execa(parts[0], parts.slice(1), { shell: true })\n }\n } catch (error) {\n throw new Error(`Failed to reload Nginx: ${error instanceof Error ? error.message : error}`)\n }\n}\n\n/**\n * Disable an Nginx site\n */\nexport async function disableSite(siteName: string, configPath: string): Promise<void> {\n const enabledPath = path.join(\n configPath.replace('sites-available', 'sites-enabled'),\n `${siteName}.conf`\n )\n\n if (await fs.pathExists(enabledPath)) {\n await fs.remove(enabledPath)\n }\n}\n"],"names":[],"mappings":";;;AAQO,SAAS,oBAAoB,SAAwB,WAA4B;AACtF,QAAM,cAAc,QAAQ,QAAQ,KAAK,GAAG;AAC5C,QAAM,eAAe,GAAG,QAAQ,IAAI;AAEpC,MAAI,SAAS,6BAA6B,QAAQ,IAAI;AAAA;AAAA;AAGtD,YAAU,YAAY,YAAY;AAAA;AAClC,YAAU,wBAAwB,QAAQ,IAAI;AAAA;AAC9C,YAAU;AAAA;AACV,YAAU;AAAA;AAAA;AAEV,MAAI,WAAW;AAEb,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU,mBAAmB,WAAW;AAAA;AAAA;AACxC,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU;AAAA;AAAA;AAGV,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU,mBAAmB,WAAW;AAAA;AAAA;AAGxC,UAAM,gBAAgB,QAAQ,QAAQ,CAAC;AACvC,cAAU;AAAA;AACV,cAAU,6CAA6C,aAAa;AAAA;AACpE,cAAU,iDAAiD,aAAa;AAAA;AACxE,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU;AAAA;AAAA;AAAA,EACZ,OAAO;AAEL,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU;AAAA;AACV,cAAU,mBAAmB,WAAW;AAAA;AAAA;AAAA,EAC1C;AAGA,YAAU;AAAA;AACV,YAAU,iCAAiC,QAAQ,IAAI;AAAA;AACvD,YAAU,gCAAgC,QAAQ,IAAI;AAAA;AAAA;AAGtD,YAAU;AAAA;AACV,YAAU;AAAA;AAAA;AAGV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU,6BAA6B,YAAY;AAAA;AACnD,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AACV,YAAU;AAAA;AAGV,MAAI,QAAQ,aAAa;AACvB,cAAU;AAAA;AAAA;AACV,cAAU,gBAAgB,QAAQ,YAAY,IAAI;AAAA;AAClD,cAAU,6BAA6B,YAAY;AAAA;AACnD,cAAU;AAAA;AACV,cAAU;AAAA;AAAA,EACZ;AAEA,YAAU;AAAA;AAEV,SAAO;AACT;AAKA,eAAsB,WAAW,UAAkB,YAAmC;AACpF,QAAM,gBAAgB,KAAK,KAAK,YAAY,GAAG,QAAQ,OAAO;AAC9D,QAAM,cAAc,cAAc,QAAQ,mBAAmB,eAAe;AAG5E,QAAM,GAAG,UAAU,KAAK,QAAQ,WAAW,CAAC;AAG5C,MAAI,MAAM,GAAG,WAAW,WAAW,GAAG;AACpC,UAAM,GAAG,OAAO,WAAW;AAAA,EAC7B;AAGA,QAAM,MAAM,QAAQ,CAAC,MAAM,OAAO,eAAe,WAAW,CAAC;AAC/D;AAKA,eAAsB,YAAY,eAAsC;AACtE,MAAI;AAEF,UAAM,MAAM,QAAQ,CAAC,SAAS,IAAI,CAAC;AAGnC,UAAM,QAAQ,cAAc,MAAM,GAAG;AACrC,QAAI,MAAM,SAAS,GAAG;AAEpB,YAAM,MAAM,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,GAAG,EAAE,OAAO,MAAM;AAAA,IACvD;AAAA,EACF,SAAS,OAAO;AACd,UAAM,IAAI,MAAM,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,KAAK,EAAE;AAAA,EAC7F;AACF;"}
|
|
1
|
+
{"version":3,"file":"nginx.js","sources":["../../src/utils/nginx.ts"],"sourcesContent":["import { execa } from 'execa'\nimport fs from 'fs-extra'\nimport path from 'path'\nimport type { ServiceConfig } from '../types/config'\n\n/**\n * Generate Nginx server block configuration for a service\n */\nexport function generateNginxConfig(\n service: ServiceConfig,\n withHttps: boolean,\n portOverride?: number\n): string {\n const serverNames = service.domains.join(' ')\n // Use primary domain for upstream naming to ensure uniqueness\n const primaryDomain = service.domains[0]\n const domainSafe = primaryDomain.replace(/\\./g, '_').replace(/[^a-zA-Z0-9_]/g, '_')\n const upstreamName = `${domainSafe}_${service.name}`\n const servicePath = service.path || '/'\n const port = portOverride || service.port\n\n let config = `# Nginx configuration for ${service.name}\\n\\n`\n\n // Upstream configuration\n config += `upstream ${upstreamName} {\\n`\n config += ` server localhost:${port} max_fails=3 fail_timeout=30s;\\n`\n config += ` keepalive 32;\\n`\n config += `}\\n\\n`\n\n if (withHttps) {\n // HTTP server - redirect to HTTPS\n config += `server {\\n`\n config += ` listen 80;\\n`\n config += ` listen [::]:80;\\n`\n config += ` server_name ${serverNames};\\n\\n`\n config += ` # Redirect all HTTP to HTTPS\\n`\n config += ` return 301 https://$server_name$request_uri;\\n`\n config += `}\\n\\n`\n\n // HTTPS server\n config += `server {\\n`\n config += ` listen 443 ssl http2;\\n`\n config += ` listen [::]:443 ssl http2;\\n`\n config += ` server_name ${serverNames};\\n\\n`\n\n // SSL configuration\n const primaryDomain = service.domains[0]\n config += ` # SSL Configuration\\n`\n config += ` ssl_certificate /etc/letsencrypt/live/${primaryDomain}/fullchain.pem;\\n`\n config += ` ssl_certificate_key /etc/letsencrypt/live/${primaryDomain}/privkey.pem;\\n`\n config += ` ssl_protocols TLSv1.2 TLSv1.3;\\n`\n config += ` ssl_ciphers HIGH:!aNULL:!MD5;\\n`\n config += ` ssl_prefer_server_ciphers on;\\n\\n`\n } else {\n // HTTP only server\n config += `server {\\n`\n config += ` listen 80;\\n`\n config += ` listen [::]:80;\\n`\n config += ` server_name ${serverNames};\\n\\n`\n }\n\n // Logging\n config += ` # Logging\\n`\n config += ` access_log /var/log/nginx/${service.name}_access.log;\\n`\n config += ` error_log /var/log/nginx/${service.name}_error.log;\\n\\n`\n\n // Client settings\n config += ` # Client settings\\n`\n config += ` client_max_body_size 100M;\\n\\n`\n\n // Proxy settings\n config += ` # Proxy settings\\n`\n config += ` location ${servicePath} {\\n`\n config += ` proxy_pass http://${upstreamName};\\n`\n config += ` proxy_http_version 1.1;\\n`\n config += ` proxy_set_header Upgrade $http_upgrade;\\n`\n config += ` proxy_set_header Connection 'upgrade';\\n`\n config += ` proxy_set_header Host $host;\\n`\n config += ` proxy_set_header X-Real-IP $remote_addr;\\n`\n config += ` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\\n`\n config += ` proxy_set_header X-Forwarded-Proto $scheme;\\n`\n config += ` proxy_cache_bypass $http_upgrade;\\n`\n config += ` proxy_connect_timeout 60s;\\n`\n config += ` proxy_send_timeout 60s;\\n`\n config += ` proxy_read_timeout 60s;\\n`\n config += ` }\\n`\n\n // Health check endpoint (if configured)\n if (service.healthCheck) {\n config += `\\n # Health check endpoint\\n`\n config += ` location ${service.healthCheck.path} {\\n`\n config += ` proxy_pass http://${upstreamName};\\n`\n config += ` access_log off;\\n`\n config += ` }\\n`\n }\n\n config += `}\\n`\n\n return config\n}\n\n/**\n * Generate Nginx configuration for multiple services on the same domain\n * Groups services by domain and creates location blocks for each service path\n */\nexport function generateMultiServiceNginxConfig(\n services: ServiceConfig[],\n domain: string,\n withHttps: boolean,\n portOverrides?: Map<string, number>\n): string {\n const upstreams: string[] = []\n const locations: string[] = []\n const healthChecks: string[] = []\n\n // Sort services by path length (longest first) to ensure specific paths are matched before general ones\n const sortedServices = [...services].sort((a, b) => {\n const pathA = (a.path || '/').length\n const pathB = (b.path || '/').length\n return pathB - pathA\n })\n\n // Track upstream names to ensure uniqueness within the same domain config\n const usedUpstreamNames = new Set<string>()\n\n for (const service of sortedServices) {\n // Create unique upstream name: domain_service_name to avoid conflicts\n // Replace dots and special chars in domain for valid nginx upstream name\n const domainSafe = domain.replace(/\\./g, '_').replace(/[^a-zA-Z0-9_]/g, '_')\n let upstreamName = `${domainSafe}_${service.name}`\n\n // Ensure uniqueness (in case same service name appears multiple times)\n let counter = 1\n const originalUpstreamName = upstreamName\n while (usedUpstreamNames.has(upstreamName)) {\n upstreamName = `${originalUpstreamName}_${counter}`\n counter++\n }\n usedUpstreamNames.add(upstreamName)\n\n const servicePath = service.path || '/'\n const port = portOverrides?.get(service.name) || service.port\n\n // Generate upstream for each service on the same domain\n upstreams.push(`upstream ${upstreamName} {`)\n upstreams.push(` server localhost:${port} max_fails=3 fail_timeout=30s;`)\n upstreams.push(` keepalive 32;`)\n upstreams.push(`}`)\n\n // Generate location block\n if (servicePath === '/') {\n // Root path - use exact match or default\n locations.push(` # Service: ${service.name}`)\n locations.push(` location / {`)\n } else {\n // Specific path - use prefix match\n locations.push(` # Service: ${service.name}`)\n locations.push(` location ${servicePath} {`)\n }\n\n locations.push(` proxy_pass http://${upstreamName};`)\n locations.push(` proxy_http_version 1.1;`)\n locations.push(` proxy_set_header Upgrade $http_upgrade;`)\n locations.push(` proxy_set_header Connection 'upgrade';`)\n locations.push(` proxy_set_header Host $host;`)\n locations.push(` proxy_set_header X-Real-IP $remote_addr;`)\n locations.push(` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`)\n locations.push(` proxy_set_header X-Forwarded-Proto $scheme;`)\n locations.push(` proxy_cache_bypass $http_upgrade;`)\n locations.push(` proxy_connect_timeout 60s;`)\n locations.push(` proxy_send_timeout 60s;`)\n locations.push(` proxy_read_timeout 60s;`)\n locations.push(` }`)\n\n // Health check endpoint\n if (service.healthCheck) {\n healthChecks.push(` # Health check for ${service.name}`)\n healthChecks.push(` location ${service.healthCheck.path} {`)\n healthChecks.push(` proxy_pass http://${upstreamName};`)\n healthChecks.push(` access_log off;`)\n healthChecks.push(` }`)\n }\n }\n\n let config = `# Nginx configuration for ${domain}\\n`\n config += `# Multiple services on the same domain\\n\\n`\n\n // Add upstreams\n config += upstreams.join('\\n') + '\\n\\n'\n\n if (withHttps) {\n // HTTP server - redirect to HTTPS\n config += `server {\\n`\n config += ` listen 80;\\n`\n config += ` listen [::]:80;\\n`\n config += ` server_name ${domain};\\n\\n`\n config += ` # Redirect all HTTP to HTTPS\\n`\n config += ` return 301 https://$server_name$request_uri;\\n`\n config += `}\\n\\n`\n\n // HTTPS server\n config += `server {\\n`\n config += ` listen 443 ssl http2;\\n`\n config += ` listen [::]:443 ssl http2;\\n`\n config += ` server_name ${domain};\\n\\n`\n\n // SSL configuration\n config += ` # SSL Configuration\\n`\n config += ` ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;\\n`\n config += ` ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;\\n`\n config += ` ssl_protocols TLSv1.2 TLSv1.3;\\n`\n config += ` ssl_ciphers HIGH:!aNULL:!MD5;\\n`\n config += ` ssl_prefer_server_ciphers on;\\n\\n`\n } else {\n // HTTP only server\n config += `server {\\n`\n config += ` listen 80;\\n`\n config += ` listen [::]:80;\\n`\n config += ` server_name ${domain};\\n\\n`\n }\n\n // Logging\n config += ` # Logging\\n`\n config += ` access_log /var/log/nginx/${domain}_access.log;\\n`\n config += ` error_log /var/log/nginx/${domain}_error.log;\\n\\n`\n\n // Client settings\n config += ` # Client settings\\n`\n config += ` client_max_body_size 100M;\\n\\n`\n\n // Location blocks\n config += ` # Service locations\\n`\n config += locations.join('\\n') + '\\n\\n'\n\n // Health check endpoints\n if (healthChecks.length > 0) {\n config += ` # Health check endpoints\\n`\n config += healthChecks.join('\\n') + '\\n'\n }\n\n config += `}\\n`\n\n return config\n}\n\n/**\n * Check if an Nginx config file exists\n */\nexport async function configExists(configName: string, configPath: string): Promise<boolean> {\n const configFilePath = path.join(configPath, `${configName}.conf`)\n return await fs.pathExists(configFilePath)\n}\n\n/**\n * Write Nginx configuration file, deleting existing file if it exists and creating new one\n */\nexport async function writeNginxConfig(\n configName: string,\n configPath: string,\n configContent: string\n): Promise<boolean> {\n const configFilePath = path.join(configPath, `${configName}.conf`)\n const exists = await fs.pathExists(configFilePath)\n\n // If config file exists, delete it first\n if (exists) {\n await fs.remove(configFilePath)\n }\n\n // Create new config file\n await fs.writeFile(configFilePath, configContent)\n\n return exists // Return true if file existed (was deleted and recreated)\n}\n\n/**\n * Enable an Nginx site by creating a symbolic link\n */\nexport async function enableSite(siteName: string, configPath: string): Promise<void> {\n const availablePath = path.join(configPath, `${siteName}.conf`)\n const enabledPath = availablePath.replace('sites-available', 'sites-enabled')\n\n // Create sites-enabled directory if it doesn't exist\n await fs.ensureDir(path.dirname(enabledPath))\n\n // Remove existing symlink if present\n if (await fs.pathExists(enabledPath)) {\n await fs.remove(enabledPath)\n }\n\n // Create symlink\n await execa('sudo', ['ln', '-sf', availablePath, enabledPath])\n}\n\n/**\n * Test and reload Nginx configuration\n */\nexport async function reloadNginx(reloadCommand: string): Promise<void> {\n try {\n // Test configuration first\n await execa('sudo', ['nginx', '-t'])\n\n // Reload Nginx\n const parts = reloadCommand.split(' ')\n if (parts.length > 0) {\n // Simple execution of provided command\n await execa(parts[0], parts.slice(1), { shell: true })\n }\n } catch (error) {\n throw new Error(`Failed to reload Nginx: ${error instanceof Error ? error.message : error}`)\n }\n}\n\n/**\n * Disable an Nginx site\n */\nexport async function disableSite(siteName: string, configPath: string): Promise<void> {\n const enabledPath = path.join(\n configPath.replace('sites-available', 'sites-enabled'),\n `${siteName}.conf`\n )\n\n if (await fs.pathExists(enabledPath)) {\n await fs.remove(enabledPath)\n }\n}\n"],"names":["primaryDomain"],"mappings":"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"}
|
package/docs/README.md
CHANGED
|
@@ -1,62 +1,38 @@
|
|
|
1
1
|
# Suthep Documentation
|
|
2
2
|
|
|
3
|
-
Welcome to the Suthep documentation!
|
|
3
|
+
Welcome to the Suthep documentation! Choose your preferred language:
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Available Languages
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
3. [Commands Reference](./commands.md) - Detailed command documentation
|
|
10
|
-
4. [Architecture](./architecture.md) - How Suthep works under the hood
|
|
11
|
-
5. [Examples](./examples.md) - Real-world deployment examples
|
|
12
|
-
6. [Troubleshooting](./troubleshooting.md) - Common issues and solutions
|
|
13
|
-
7. [API Reference](./api-reference.md) - Internal utilities and functions
|
|
7
|
+
- 🇬🇧 [English Documentation](./english/README.md) - English user guide
|
|
8
|
+
- 🇹🇭 [Thai Documentation](./thai/README.md) - คู่มือผู้ใช้ภาษาไทย
|
|
14
9
|
|
|
15
|
-
##
|
|
10
|
+
## Documentation Structure
|
|
16
11
|
|
|
17
|
-
|
|
12
|
+
Both language versions include:
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
1. **Introduction** - Overview of Suthep and its features
|
|
15
|
+
2. **Installation** - Step-by-step installation guide
|
|
16
|
+
3. **Quick Start** - Get started in minutes
|
|
17
|
+
4. **Configuration Guide** - Complete configuration reference
|
|
18
|
+
5. **Commands Reference** - All available commands
|
|
19
|
+
6. **Examples** - Real-world deployment examples
|
|
20
|
+
7. **Troubleshooting** - Common issues and solutions
|
|
21
|
+
8. **Advanced Topics** - Advanced configuration and optimization
|
|
26
22
|
|
|
27
|
-
## Quick
|
|
23
|
+
## Quick Links
|
|
28
24
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
### English
|
|
26
|
+
- [Start Here](./english/README.md)
|
|
27
|
+
- [Quick Start Guide](./english/03-quick-start.md)
|
|
28
|
+
- [Configuration Reference](./english/04-configuration.md)
|
|
33
29
|
|
|
34
|
-
|
|
35
|
-
|
|
30
|
+
### Thai
|
|
31
|
+
- [เริ่มต้นที่นี่](./thai/README.md)
|
|
32
|
+
- [คู่มือเริ่มต้นใช้งาน](./thai/03-quick-start.md)
|
|
33
|
+
- [คู่มือการตั้งค่า](./thai/04-configuration.md)
|
|
36
34
|
|
|
37
|
-
|
|
38
|
-
suthep setup
|
|
35
|
+
---
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
suthep deploy
|
|
42
|
-
```
|
|
37
|
+
For the main project README, see [../README.md](../README.md)
|
|
43
38
|
|
|
44
|
-
## Requirements
|
|
45
|
-
|
|
46
|
-
- Node.js 16+
|
|
47
|
-
- Nginx (installed via `suthep setup`)
|
|
48
|
-
- Certbot (installed via `suthep setup`)
|
|
49
|
-
- Docker (optional, for Docker-based services)
|
|
50
|
-
- sudo access (for Nginx and Certbot operations)
|
|
51
|
-
|
|
52
|
-
## Cost Optimization
|
|
53
|
-
|
|
54
|
-
Suthep helps save costs on VMs by:
|
|
55
|
-
- Efficiently managing multiple services on a single server
|
|
56
|
-
- Automatic reverse proxy setup reduces manual configuration time
|
|
57
|
-
- Zero-downtime deployments reduce service interruptions
|
|
58
|
-
- Health checks ensure service reliability
|
|
59
|
-
|
|
60
|
-
## License
|
|
61
|
-
|
|
62
|
-
[MIT](../LICENSE)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Introduction to Suthep
|
|
2
|
+
|
|
3
|
+
## What is Suthep?
|
|
4
|
+
|
|
5
|
+
Suthep is a command-line tool designed to simplify the deployment of web applications and services. It automates the complex process of setting up reverse proxies, SSL certificates, and managing deployments with zero downtime.
|
|
6
|
+
|
|
7
|
+
## Why Use Suthep?
|
|
8
|
+
|
|
9
|
+
### Simplified Deployment
|
|
10
|
+
|
|
11
|
+
Traditional deployment processes require manual configuration of:
|
|
12
|
+
- Nginx reverse proxy rules
|
|
13
|
+
- SSL certificate management
|
|
14
|
+
- Docker container orchestration
|
|
15
|
+
- Health check monitoring
|
|
16
|
+
- Zero-downtime deployment strategies
|
|
17
|
+
|
|
18
|
+
Suthep handles all of this automatically with a simple YAML configuration file.
|
|
19
|
+
|
|
20
|
+
### Key Benefits
|
|
21
|
+
|
|
22
|
+
1. **Time Savings** - Deploy in minutes instead of hours
|
|
23
|
+
2. **Reduced Errors** - Automated configuration reduces human error
|
|
24
|
+
3. **Zero Downtime** - Rolling deployments ensure continuous service availability
|
|
25
|
+
4. **Easy Management** - Simple commands to deploy, update, and manage services
|
|
26
|
+
5. **Cost Effective** - Run multiple services on a single server efficiently
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
Suthep follows a simple workflow:
|
|
31
|
+
|
|
32
|
+
1. **Configure** - Create a `suthep.yml` configuration file
|
|
33
|
+
2. **Setup** - Install prerequisites (Nginx, Certbot) with `suthep setup`
|
|
34
|
+
3. **Deploy** - Deploy your services with `suthep deploy`
|
|
35
|
+
|
|
36
|
+
Behind the scenes, Suthep:
|
|
37
|
+
- Generates Nginx configuration files
|
|
38
|
+
- Obtains SSL certificates from Let's Encrypt
|
|
39
|
+
- Manages Docker containers
|
|
40
|
+
- Performs health checks
|
|
41
|
+
- Handles zero-downtime deployments
|
|
42
|
+
|
|
43
|
+
## Use Cases
|
|
44
|
+
|
|
45
|
+
Suthep is ideal for:
|
|
46
|
+
|
|
47
|
+
- **Small to Medium Applications** - Deploy multiple services on a single server
|
|
48
|
+
- **Microservices** - Manage multiple services with different domains
|
|
49
|
+
- **Docker Applications** - Deploy containerized applications easily
|
|
50
|
+
- **API Services** - Set up reverse proxies for API endpoints
|
|
51
|
+
- **Web Applications** - Deploy web apps with automatic HTTPS
|
|
52
|
+
|
|
53
|
+
## What You'll Learn
|
|
54
|
+
|
|
55
|
+
In this guide, you'll learn:
|
|
56
|
+
|
|
57
|
+
- How to install and set up Suthep
|
|
58
|
+
- How to create and configure deployment files
|
|
59
|
+
- How to use all available commands
|
|
60
|
+
- How to deploy different types of services
|
|
61
|
+
- How to troubleshoot common issues
|
|
62
|
+
- Advanced configuration options
|
|
63
|
+
|
|
64
|
+
## Prerequisites
|
|
65
|
+
|
|
66
|
+
Before using Suthep, you should have:
|
|
67
|
+
|
|
68
|
+
- **Node.js 16+** installed
|
|
69
|
+
- **sudo/administrator access** on your server
|
|
70
|
+
- **Domain names** pointing to your server (for HTTPS)
|
|
71
|
+
- **Basic knowledge** of YAML configuration files
|
|
72
|
+
- **Docker** (optional, only if deploying Docker containers)
|
|
73
|
+
|
|
74
|
+
## Next Steps
|
|
75
|
+
|
|
76
|
+
Ready to get started? Continue to:
|
|
77
|
+
|
|
78
|
+
- [Installation Guide](./02-installation.md) - Install Suthep on your system
|
|
79
|
+
- [Quick Start Guide](./03-quick-start.md) - Deploy your first service
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
**Previous:** [README](./README.md) | **Next:** [Installation →](./02-installation.md)
|
|
84
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# Installation Guide
|
|
2
|
+
|
|
3
|
+
This guide will walk you through installing Suthep on your system.
|
|
4
|
+
|
|
5
|
+
## System Requirements
|
|
6
|
+
|
|
7
|
+
Before installing Suthep, ensure your system meets these requirements:
|
|
8
|
+
|
|
9
|
+
- **Node.js** version 16 or higher
|
|
10
|
+
- **npm**, **yarn**, or **pnpm** package manager
|
|
11
|
+
- **sudo/administrator privileges** (required for Nginx and Certbot setup)
|
|
12
|
+
- **Linux** or **macOS** operating system
|
|
13
|
+
|
|
14
|
+
## Installing Suthep
|
|
15
|
+
|
|
16
|
+
Suthep can be installed globally using any Node.js package manager.
|
|
17
|
+
|
|
18
|
+
### Using npm
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install -g suthep
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Using yarn
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn global add suthep
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Using pnpm
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pnpm add -g suthep
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Verify Installation
|
|
37
|
+
|
|
38
|
+
After installation, verify that Suthep is installed correctly:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
suthep --version
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
You should see the version number (e.g., `0.1.0-beta.1`).
|
|
45
|
+
|
|
46
|
+
You can also check the help menu:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
suthep --help
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Installing Prerequisites
|
|
53
|
+
|
|
54
|
+
Suthep requires Nginx and Certbot to function. You can install these automatically using the `setup` command:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
suthep setup
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This command will:
|
|
61
|
+
- Install Nginx (if not already installed)
|
|
62
|
+
- Install Certbot (if not already installed)
|
|
63
|
+
- Configure system dependencies
|
|
64
|
+
|
|
65
|
+
### Manual Installation (Optional)
|
|
66
|
+
|
|
67
|
+
If you prefer to install prerequisites manually:
|
|
68
|
+
|
|
69
|
+
#### Nginx Installation
|
|
70
|
+
|
|
71
|
+
**Ubuntu/Debian:**
|
|
72
|
+
```bash
|
|
73
|
+
sudo apt-get update
|
|
74
|
+
sudo apt-get install -y nginx
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**CentOS/RHEL:**
|
|
78
|
+
```bash
|
|
79
|
+
sudo yum install -y nginx
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**macOS:**
|
|
83
|
+
```bash
|
|
84
|
+
brew install nginx
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### Certbot Installation
|
|
88
|
+
|
|
89
|
+
**Ubuntu/Debian:**
|
|
90
|
+
```bash
|
|
91
|
+
sudo apt-get update
|
|
92
|
+
sudo apt-get install -y certbot python3-certbot-nginx
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**CentOS/RHEL:**
|
|
96
|
+
```bash
|
|
97
|
+
sudo yum install -y certbot python3-certbot-nginx
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**macOS:**
|
|
101
|
+
```bash
|
|
102
|
+
brew install certbot
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Docker (Optional)
|
|
106
|
+
|
|
107
|
+
If you plan to deploy Docker containers, install Docker:
|
|
108
|
+
|
|
109
|
+
**Ubuntu/Debian:**
|
|
110
|
+
```bash
|
|
111
|
+
sudo apt-get update
|
|
112
|
+
sudo apt-get install -y docker.io
|
|
113
|
+
sudo systemctl start docker
|
|
114
|
+
sudo systemctl enable docker
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**macOS:**
|
|
118
|
+
```bash
|
|
119
|
+
brew install docker
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Or download Docker Desktop from [docker.com](https://www.docker.com/products/docker-desktop).
|
|
123
|
+
|
|
124
|
+
## Post-Installation
|
|
125
|
+
|
|
126
|
+
After installation, you're ready to:
|
|
127
|
+
|
|
128
|
+
1. **Initialize your first configuration:**
|
|
129
|
+
```bash
|
|
130
|
+
suthep init
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
2. **Or continue to the Quick Start guide:**
|
|
134
|
+
See [Quick Start Guide](./03-quick-start.md)
|
|
135
|
+
|
|
136
|
+
## Troubleshooting Installation
|
|
137
|
+
|
|
138
|
+
### Command Not Found
|
|
139
|
+
|
|
140
|
+
If you get a "command not found" error:
|
|
141
|
+
|
|
142
|
+
1. **Check Node.js installation:**
|
|
143
|
+
```bash
|
|
144
|
+
node --version
|
|
145
|
+
npm --version
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
2. **Verify global bin path:**
|
|
149
|
+
```bash
|
|
150
|
+
npm config get prefix
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
3. **Add npm global bin to PATH** (if needed):
|
|
154
|
+
```bash
|
|
155
|
+
export PATH="$PATH:$(npm config get prefix)/bin"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Permission Errors
|
|
159
|
+
|
|
160
|
+
If you encounter permission errors:
|
|
161
|
+
|
|
162
|
+
1. **Use sudo for global installation:**
|
|
163
|
+
```bash
|
|
164
|
+
sudo npm install -g suthep
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
2. **Or configure npm to use a different directory:**
|
|
168
|
+
```bash
|
|
169
|
+
mkdir ~/.npm-global
|
|
170
|
+
npm config set prefix '~/.npm-global'
|
|
171
|
+
export PATH=~/.npm-global/bin:$PATH
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Nginx/Certbot Installation Issues
|
|
175
|
+
|
|
176
|
+
If `suthep setup` fails:
|
|
177
|
+
|
|
178
|
+
1. **Check your package manager:**
|
|
179
|
+
- Ubuntu/Debian: Ensure `apt-get` is available
|
|
180
|
+
- CentOS/RHEL: Ensure `yum` is available
|
|
181
|
+
- macOS: Ensure Homebrew is installed
|
|
182
|
+
|
|
183
|
+
2. **Run setup with verbose output:**
|
|
184
|
+
```bash
|
|
185
|
+
suthep setup --verbose
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
3. **Install prerequisites manually** (see Manual Installation above)
|
|
189
|
+
|
|
190
|
+
## Next Steps
|
|
191
|
+
|
|
192
|
+
Now that Suthep is installed:
|
|
193
|
+
|
|
194
|
+
- [Quick Start Guide](./03-quick-start.md) - Deploy your first service
|
|
195
|
+
- [Configuration Guide](./04-configuration.md) - Learn about configuration options
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
**Previous:** [Introduction](./01-introduction.md) | **Next:** [Quick Start →](./03-quick-start.md)
|
|
200
|
+
|