Skip to content

Commit 3ac7960

Browse files
committed
🚀
0 parents  commit 3ac7960

13 files changed

+1177
-0
lines changed

LICENSE

+674
Large diffs are not rendered by default.

README.md

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# HestiaCP add multiple NodeJS apps using QuickApp Installer.
2+
3+
You can add multiple websites to your HestiaCP using diferent ports to each one.
4+
5+
When you create create the app with the installer it automatically create:
6+
* **/home/%USER%/%DOMAIN%/private/nodeapp** directory
7+
* Config for **nginx** to use the selected port
8+
* **ecosystem.config.js** with the necessary command to connect **pm2** and run your app Ex. `npm run start`
9+
* **.nvmrc** file with node version if you use NVM
10+
11+
## How to install
12+
13+
1. Install Node: (Using one of these options)
14+
* [NodeJS](https://github.com/nodesource/distributions)
15+
* [NVM](https://github.com/nvm-sh/nvm#installing-and-updating)
16+
2. Install [PM2](https://pm2.keymetrics.io/)
17+
3. Clone this repository:
18+
```bash
19+
cd ~/tmp
20+
git clone https://github.com/JLFJ91/hestiacp-nodejs.git
21+
cd hestiacp-nodejs
22+
```
23+
24+
4. Use **install.sh**
25+
```bash
26+
sudo chmod 755 install.sh
27+
sudo ./install.sh
28+
```
29+
30+
5. 🚀 You are ready to install an App!!!
31+
32+
## How to use
33+
34+
1. Create new **user** (If you have one no need to create)
35+
2. User needs bash access for app to work, go to **User edit** > **advanced options** > **SSH Access** > **bash**
36+
3. **Add** new web (Ex. acme.com)
37+
4. Go to **edit** this new web and go to **Quick Install App**
38+
5. Select **NodeJS**
39+
* **Node Version**: If you manage node with nvm, it put a `.nvmrc file in root of nodeapp with selected version. If you installed node without nvm you can remove this file.
40+
41+
* **Start Script**: It creates a `ecosystem.config.js` file in root of nodeapp with the script that you fill (it should be the one you have in your `package.json`) for PM2 can manage the app.
42+
43+
* **Port**: You can manage multiple apps with different ports, put different port for each app you have (Ex. 3000).
44+
It creates `.env` file in root of nodeapp with the selected port, if your app don't use this `.env` file you can remove.
45+
46+
* **PHP Version**: This is only for HestiaCP you can put any value (**NOT IMPORTANT**)
47+
6. Go to Edit web > Advanced Options > Proxy Template > NodeJS
48+
7. Upload your app with filemanager, clone with git... in `/home/<user>/<domain.com>/private/nodeapp`
49+
8. 🎉 Congratulations you're done!!!
50+
51+
## FAQ
52+
53+
### How to change the port if i have a web running
54+
55+
First change proxy template to default, reconfigure the app using the QuickInstall and finally change the proxy template to NodeJS.
56+
57+
### I want to remove the domain
58+
59+
Remove it normally, open the filemanager and remove hestiacp_nodejs_config/web/<domain.com>.

bin/v-add-pm2-app

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/bin/bash
2+
# info: add web to PM2
3+
# options: USER DOMAIN ENTRYPOINT
4+
#
5+
# example: v-add-pm2-app admin wonderland.com
6+
#
7+
# This function adds an installed app to pm2
8+
#----------------------------------------------------------#
9+
# Variables & Functions #
10+
#----------------------------------------------------------#
11+
12+
# Argument definition
13+
user=$1
14+
domain=$2
15+
entrypoint=$3
16+
17+
# Includes
18+
# shellcheck source=/etc/hestiacp/hestia.conf
19+
source /etc/hestiacp/hestia.conf
20+
# shellcheck source=/usr/local/hestia/func/main.sh
21+
source $HESTIA/func/main.sh
22+
# shellcheck source=/usr/local/hestia/func/domain.sh
23+
source $HESTIA/func/domain.sh
24+
# load config file
25+
source_conf "$HESTIA/conf/hestia.conf"
26+
27+
# Additional argument formatting
28+
format_domain
29+
30+
#----------------------------------------------------------#
31+
# Verifications #
32+
#----------------------------------------------------------#
33+
34+
check_args '3' "$#" 'USER DOMAIN ENTRYPOINT'
35+
is_format_valid 'user' 'domain'
36+
is_object_valid 'user' 'USER' "$user"
37+
is_object_unsuspended 'user' 'USER' "$user"
38+
is_package_full 'WEB_DOMAINS'
39+
40+
is_dir_symlink "$HOMEDIR/$user/web"
41+
is_dir_symlink "$HOMEDIR/$user/web/$domain"
42+
43+
is_base_domain_owner "$domain"
44+
45+
# Perform verification if read-only mode is enabled
46+
check_hestia_demo_mode
47+
48+
#----------------------------------------------------------#
49+
# Action #
50+
#----------------------------------------------------------#
51+
52+
pm2 start $HOMEDIR/$user/web/$domain/private/nodeapp/$entrypoint
53+
pm2 save
54+
55+
#----------------------------------------------------------#
56+
# Hestia #
57+
#----------------------------------------------------------#
58+
59+
# Logging
60+
$BIN/v-log-action "$user" "Info" "Web" "Added web to PM2 (Name: $domain)."
61+
log_event "$OK" "$ARGUMENTS"
62+
63+
exit

install.sh

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/bash
2+
3+
BLUE="\e[34m"
4+
GREEN="\e[32m"
5+
ENDCOLOR="\e[0m"
6+
START="[${GREEN}JLFdzDev/${ENDCOLOR}${BLUE}hestiacp-nodejs${ENDCOLOR}]"
7+
8+
echo -e "${GREEN} ____ ______ __ ____
9+
/ / / / ____/___/ /___ / __ \___ _ __
10+
__ / / / / /_ / __ /_ / / / / / _ \ | / /
11+
/ /_/ / /___/ __/ / /_/ / / /_/ /_/ / __/ |/ /
12+
\____/_____/_/ \__,_/ /___/_____/\___/|___/${ENDCOLOR}"
13+
14+
echo -e "${BLUE}┬ ┬┌─┐┌─┐┌┬┐┬┌─┐┌─┐┌─┐ ┌┐┌┌─┐┌┬┐┌─┐ ┬┌─┐
15+
├─┤├┤ └─┐ │ │├─┤│ ├─┘───││││ │ ││├┤ │└─┐
16+
┴ ┴└─┘└─┘ ┴ ┴┴ ┴└─┘┴ ┘└┘└─┘─┴┘└─┘└┘└─┘${ENDCOLOR}"
17+
18+
echo -e "───────────────────────────────────────────────"
19+
20+
sudo cp -r quickinstall-app/NodeJs /usr/local/hestia/web/src/app/WebApp/Installers/
21+
echo -e "${START} Copy QuickInstall App ✅"
22+
23+
sudo cp templates/* /usr/local/hestia/data/templates/web/nginx
24+
echo -e "${START} Copy Templates ✅"
25+
26+
sudo chmod 644 /usr/local/hestia/data/templates/web/nginx/NodeJS.tpl
27+
sudo chmod 644 /usr/local/hestia/data/templates/web/nginx/NodeJS.stpl
28+
29+
sudo chmod -R 644 /usr/local/hestia/web/src/app/WebApp/Installers/NodeJs/
30+
sudo chmod 755 /usr/local/hestia/web/src/app/WebApp/Installers/NodeJs
31+
sudo chmod 755 /usr/local/hestia/web/src/app/WebApp/Installers/NodeJs/NodeJsUtils
32+
sudo chmod 755 /usr/local/hestia/web/src/app/WebApp/Installers/NodeJs/templates
33+
sudo chmod 755 /usr/local/hestia/web/src/app/WebApp/Installers/NodeJs/templates/nginx
34+
sudo chmod 755 /usr/local/hestia/web/src/app/WebApp/Installers/NodeJs/templates/web
35+
echo -e "${START} Templates and QuickInstall App Permissions changed ✅"
36+
37+
sudo cp bin/v-add-pm2-app /usr/local/hestia/bin
38+
sudo chmod 755 /usr/local/hestia/bin/v-add-pm2-app
39+
echo -e "${START} Add pm2 manager to /usr/local/hestia/bin ✅"
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
3+
namespace Hestia\WebApp\Installers\NodeJs;
4+
5+
use \Hestia\WebApp\Installers\BaseSetup as BaseSetup;
6+
use \Hestia\WebApp\Installers\NodeJs\NodeJsUtils\NodeJsPaths as NodeJsPaths;
7+
use \Hestia\WebApp\Installers\NodeJs\NodeJsUtils\NodeJsUtil as NodeJsUtil;
8+
use Hestia\System\HestiaApp;
9+
10+
class NodeJsSetup extends BaseSetup {
11+
12+
protected const TEMPLATE_PROXY_VARS = ['%nginx_port%'];
13+
protected const TEMPLATE_ENTRYPOINT_VARS = ['%app_name%', '%app_start_script%', '%app_cwd%'];
14+
15+
protected $nodeJsPaths;
16+
protected $nodeJsUtils;
17+
protected $appInfo = [
18+
'name' => 'NodeJs',
19+
'group' => 'node',
20+
'enabled' => true,
21+
'version' => '1.0.0',
22+
'thumbnail' => 'nodejs.png'
23+
];
24+
protected $appname = 'NodeJs';
25+
protected $config = [
26+
'form' => [
27+
'node_version' => [
28+
'type' => 'select',
29+
'options' => ['v20.10.0', 'v18.18.2', 'v16.20.2'],
30+
],
31+
'start_script' => ['type'=>'text', 'placeholder'=>'npm run start'],
32+
'port' => ['type' => 'text', 'placeholder' => '3000'],
33+
],
34+
'database' => false,
35+
'server' => [
36+
'php' => [
37+
'supported' => [ '7.2','7.3','7.4','8.0','8.1','8.2' ],
38+
]
39+
],
40+
];
41+
42+
public function __construct($domain, HestiaApp $appcontext) {
43+
parent::__construct($domain, $appcontext);
44+
45+
$this->nodeJsPaths = new NodeJsPaths($appcontext);
46+
$this->nodeJsUtils = new NodeJsUtil($appcontext);
47+
}
48+
49+
public function install(array $options = null) {
50+
$this->createAppDir();
51+
$this->createConfDir();
52+
$this->createAppEntryPoint($options);
53+
$this->createAppNvmVersion($options);
54+
$this->createAppEnv($options);
55+
$this->createPublicHtmlConfigFile();
56+
$this->createAppProxyTemplates($options);
57+
$this->createAppConfig($options);
58+
$this->pm2StartApp();
59+
60+
return true;
61+
}
62+
63+
public function createAppEntryPoint(array $options = null) {
64+
$templateReplaceVars = [$this->domain, trim($options['start_script']), $this->nodeJsPaths->getAppDir($this->domain)];
65+
66+
$data = $this->nodeJsUtils->parseTemplate($this->nodeJsPaths->getAppEntrypointTemplate(), self::TEMPLATE_ENTRYPOINT_VARS, $templateReplaceVars);
67+
$tmpFile = $this->saveTempFile(implode($data));
68+
69+
return $this->nodeJsUtils->moveFile(
70+
$tmpFile,
71+
$this->nodeJsPaths->getAppEntryPoint($this->domain)
72+
);
73+
}
74+
75+
public function createAppNvmVersion($options) {
76+
$tmpFile = $this->saveTempFile($options['node_version']);
77+
78+
return $this->nodeJsUtils->moveFile(
79+
$tmpFile,
80+
$this->nodeJsPaths->getAppDir($this->domain, '.nvmrc')
81+
);
82+
}
83+
84+
public function createAppEnv($options) {
85+
$data = 'PORT="'. trim($options['port']) . '"';
86+
87+
$tmpFile = $this->saveTempFile($data);
88+
89+
return $this->nodeJsUtils->moveFile(
90+
$tmpFile,
91+
$this->nodeJsPaths->getAppDir($this->domain, '.env')
92+
);
93+
}
94+
95+
public function createAppProxyTemplates(array $options = null) {
96+
$tplReplace = [trim($options['port'])];
97+
98+
$proxyData = $this->nodeJsUtils->parseTemplate(
99+
$this->nodeJsPaths->getNodeJsProxyTemplate(),
100+
self::TEMPLATE_PROXY_VARS,
101+
$tplReplace
102+
);
103+
$proxyFallbackData = $this->nodeJsUtils->parseTemplate(
104+
$this->nodeJsPaths->getNodeJsProxyFallbackTemplate(),
105+
self::TEMPLATE_PROXY_VARS,
106+
$tplReplace
107+
);
108+
109+
$tmpProxyFile = $this->saveTempFile(implode($proxyData));
110+
$tmpProxyFallbackFile = $this->saveTempFile(implode($proxyFallbackData));
111+
112+
$this->nodeJsUtils->moveFile(
113+
$tmpProxyFile,
114+
$this->nodeJsPaths->getAppProxyConfig($this->domain)
115+
);
116+
$this->nodeJsUtils->moveFile(
117+
$tmpProxyFallbackFile,
118+
$this->nodeJsPaths->getAppProxyFallbackConfig($this->domain)
119+
);
120+
}
121+
122+
public function createAppConfig(array $options = null) {
123+
$config = 'PORT=' . trim($options['port']) . '|START_SCRIPT="' . trim($options['start_script']) . '"|NODE_VERSION=' . trim($options['node_version']);
124+
$file = $this->saveTempFile($config);
125+
126+
return $this->nodeJsUtils->moveFile(
127+
$file,
128+
$this->nodeJsPaths->getConfigFile($this->domain)
129+
);
130+
}
131+
132+
public function createPublicHtmlConfigFile() {
133+
// This file is created for hestia to detect that there is an installed app when you try to install other app
134+
$this->appcontext->runUser('v-add-fs-file', [$this->getDocRoot('app.conf')]);
135+
}
136+
137+
public function createAppDir() {
138+
$this->nodeJsUtils->createDir($this->nodeJsPaths->getAppDir($this->domain));
139+
}
140+
141+
public function createConfDir() {
142+
$this->nodeJsUtils->createDir($this->nodeJsPaths->getConfigDir());
143+
$this->nodeJsUtils->createDir($this->nodeJsPaths->getConfigDir('/web'));
144+
$this->nodeJsUtils->createDir($this->nodeJsPaths->getDomainConfigDir($this->domain));
145+
}
146+
147+
public function pm2StartApp() {
148+
return $this->appcontext->runUser('v-add-pm2-app', [$this->domain, $this->nodeJsPaths->getAppEntryPointFileName()]);
149+
}
150+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<?php
2+
3+
namespace Hestia\WebApp\Installers\NodeJs\NodeJsUtils;
4+
5+
use Hestia\System\HestiaApp;
6+
use Hestia\System\Util;
7+
8+
class NodeJsPaths {
9+
private const APP_DIR = 'private/nodeapp';
10+
private const CONFIG_DIR = 'hestiacp_nodejs_config';
11+
private const APP_CONFIG_FILE_NAME = '.conf';
12+
private const APP_ENTRYPOINT_NAME = 'ecosystem.config.js';
13+
private const APP_ENTRYPOINT_TEMPLATE = __DIR__ . '/../templates/web/entrypoint.tpl';
14+
private const APP_PROXY_CONFIG_FILE_NAME = 'nodejs-app.conf';
15+
private const APP_PROXY_FALLBACK_CONFIG_FILE_NAME = 'nodejs-app-fallback.conf';
16+
private const NODEJS_PROXY_CONFIG_TEMPLATE = __DIR__ . '/../templates/nginx/nodejs-app.tpl';
17+
private const NODEJS_PROXY_FALLBACK_CONFIG_TEMPLATE = __DIR__ . '/../templates/nginx/nodejs-app-fallback.tpl';
18+
19+
protected $appcontext;
20+
21+
public function __construct(HestiaApp $appcontext) {
22+
$this->appcontext = $appcontext;
23+
}
24+
public function getAppDir(string $domain, string $relativePath = null): string {
25+
$domainPath = $this->appcontext->getWebDomainPath($domain);
26+
27+
if (empty($domainPath) || !is_dir($domainPath)) {
28+
throw new \Exception("Error finding domain folder ($domainPath)");
29+
}
30+
31+
return Util::join_paths($domainPath, self::APP_DIR, $relativePath);
32+
}
33+
34+
public function getConfigDir(string $relativePath = null): string {
35+
$userHome = $this->appcontext->getUserHomeDir();
36+
37+
if (empty($userHome) || !is_dir($userHome)) {
38+
throw new \Exception("Error finding user home ($userHome)");
39+
}
40+
41+
return Util::join_paths($userHome, self::CONFIG_DIR, $relativePath);
42+
}
43+
44+
public function getDomainConfigDir(string $domain, string $relativePath = null): string {
45+
return Util::join_paths($this->getConfigDir('/web/' . $domain), $relativePath);
46+
}
47+
48+
public function getConfigFile(string $domain): string {
49+
return $this->getDomainConfigDir($domain, self::APP_CONFIG_FILE_NAME);
50+
}
51+
52+
public function getAppEntryPoint(string $domain): string {
53+
return $this->getAppDir($domain, self::APP_ENTRYPOINT_NAME);
54+
}
55+
56+
public function getAppEntryPointFileName(): string {
57+
return self::APP_ENTRYPOINT_NAME;
58+
}
59+
60+
public function getAppProxyConfig(string $domain): string {
61+
return $this->getDomainConfigDir($domain, self::APP_PROXY_CONFIG_FILE_NAME);
62+
}
63+
64+
public function getAppProxyFallbackConfig(string $domain): string {
65+
return $this->getDomainConfigDir($domain, self::APP_PROXY_FALLBACK_CONFIG_FILE_NAME);
66+
}
67+
68+
public function getNodeJsProxyTemplate() {
69+
return self::NODEJS_PROXY_CONFIG_TEMPLATE;
70+
}
71+
72+
public function getNodeJsProxyFallbackTemplate() {
73+
return self::NODEJS_PROXY_FALLBACK_CONFIG_TEMPLATE;
74+
}
75+
76+
public function getAppEntrypointTemplate() {
77+
return self::APP_ENTRYPOINT_TEMPLATE;
78+
}
79+
}

0 commit comments

Comments
 (0)