Add NodeJS server as an alternative to PHP

This adds:
- Two entries in the manifest:
    - A choice for the NodeJS version
    - A port (provisionned in any case due to ynh limitations, but this should not matter)
- Services and configs:
    - Systemctl configs to run the NodeJS server
    - A watcher service and path to restart NodeJS upon file update
    - A custom NGinx config because it is incompatible with the default one
- Docs:
    - More info in the description and admin

The install/remove/backup/restore have been adapted and tested.
The upgrade script is updated but not tested
The change_url script does not change

It is not possible to have both PHP and NodeJS to keep the scripts simple.
This commit is contained in:
Antoine Lima 2024-03-10 14:43:30 +01:00
parent 9defce59be
commit 9c6143df27
No known key found for this signature in database
GPG key ID: 5D1E65E3DEB73410
16 changed files with 261 additions and 4 deletions

14
conf/nginx-nodejs.conf Normal file
View file

@ -0,0 +1,14 @@
#sub_path_only rewrite ^__PATH__$ __PATH__/ permanent;
location / {
proxy_pass http://127.0.0.1:__PORT__/;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Ssl on;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Scheme https;
proxy_buffering off;
# Include SSOWAT user panel.
include conf.d/yunohost_panel.conf.inc;
}

8
conf/nodejs-watcher.path Normal file
View file

@ -0,0 +1,8 @@
[Path]
Unit=__APP__-nodejs-watcher.service
# Trigger on creation, deletion or change to a file
PathChanged=__INSTALL_DIR__
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,12 @@
[Unit]
Description=__APP__ NodeJS restarter
After=network.target
StartLimitIntervalSec=10
StartLimitBurst=5
[Service]
Type=oneshot
ExecStart=/usr/bin/systemctl restart __APP__-nodejs.service
[Install]
WantedBy=multi-user.target

53
conf/nodejs.service Normal file
View file

@ -0,0 +1,53 @@
[Unit]
Description=__APP__ NodeJS Server
After=network.target
[Service]
Type=simple
User=__APP__
Group=__APP__
WorkingDirectory=__INSTALL_DIR__/www
StandardOutput=append:/var/log/__APP__-nodejs.log
StandardError=inherit
Environment=__YNH_NODE_LOAD_PATH__
Environment=PORT=__PORT__
ExecStartPre=__YNH_NPM__ install
ExecStartPre=__YNH_NPM__ run build
ExecStart=__YNH_NPM__ run start
# Sandboxing options to harden security
# Depending on specificities of your service/app, you may need to tweak these
# .. but this should be a good baseline
# Details for these options: https://www.freedesktop.org/software/systemd/man/systemd.exec.html
NoNewPrivileges=yes
PrivateTmp=yes
PrivateDevices=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 AF_NETLINK
RestrictNamespaces=yes
RestrictRealtime=yes
DevicePolicy=closed
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
ProtectSystem=full
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
LockPersonality=yes
SystemCallArchitectures=native
SystemCallFilter=~@clock @debug @module @mount @obsolete @reboot @setuid @swap @cpu-emulation @privileged
# Denying access to capabilities that should not be relevant for webapps
# Doc: https://man7.org/linux/man-pages/man7/capabilities.7.html
CapabilityBoundingSet=~CAP_RAWIO CAP_MKNOD
CapabilityBoundingSet=~CAP_AUDIT_CONTROL CAP_AUDIT_READ CAP_AUDIT_WRITE
CapabilityBoundingSet=~CAP_SYS_BOOT CAP_SYS_TIME CAP_SYS_MODULE CAP_SYS_PACCT
CapabilityBoundingSet=~CAP_LEASE CAP_LINUX_IMMUTABLE CAP_IPC_LOCK
CapabilityBoundingSet=~CAP_BLOCK_SUSPEND CAP_WAKE_ALARM
CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG
CapabilityBoundingSet=~CAP_MAC_ADMIN CAP_MAC_OVERRIDE
CapabilityBoundingSet=~CAP_NET_ADMIN CAP_NET_BROADCAST CAP_NET_RAW
CapabilityBoundingSet=~CAP_SYS_ADMIN CAP_SYS_PTRACE CAP_SYSLOG
[Install]
WantedBy=multi-user.target

View file

@ -29,3 +29,7 @@ Once logged in, under the Web directory you will see a `www` folder which contai
### Customizing the nginx configuration
If you want to add tweak the nginx configuration for this app, it is recommended to edit `/etc/nginx/conf.d/__DOMAIN__.d/__ID__.d/WHATEVER_NAME.conf` (ensure that the file has the `.conf` extension) and reload the nginx after making sure that the configuration is valid using `nginx -t`.
### Listening the right port for NodeJS
The listen port is available to the node process throught the environment variable `PORT`. Make sure that your main `.js` file retrieves it with `process.env.PORT` as its value is not predictable.

View file

@ -29,3 +29,7 @@ Après vous être connecté, sous le répertoire Web vous verrez un dossier `www
### Personnaliser la configuration nginx
Si vous souhaitez ajuster la configuration nginx pour cette app, il est recommandé d'éditer `/etc/nginx/conf.d/__DOMAIN__.d/__ID__.d/WHATEVER_NAME.conf` (assurez-vous que le fichier a l'extension `.conf`) puis rechargez nginx après vous être assuré que la configuration est valide à l'aide de `nginx -t`.
### Écouter le bon port dans NodeJS
Le port d'écoute est accessible par le processus node au travers de la variable d'environment `PORT`. Veillez à ce que votre fichier `.js` principal le récupère bien avec `process.env.PORT` car sa valeur n'est pas prédictible.

View file

@ -4,4 +4,6 @@ It can also create a MySQL database - which will be backed up and restored with
PHP-FPM version can also be selected among `none`, `7.4`, `8.0`, `8.1` and `8.2`.
Finally, NodeJS can alternatively be used instead of PHP, with versions `18`, `20` or `21`.
**Once installed, go to the chosen URL to know the user, domain and port you will have to use for the SFTP access.** The password is one you chosen during the installation. Under the Web directory, you will see a `www` folder which contains the public files served by this app. You can put all the files of your custom Web application inside.

View file

@ -4,4 +4,6 @@ Elle peut également créer une base de données MySQL - qui sera sauvegardée e
La version de PHP-FPM peut aussi être choisie, parmi `none`, `7.4`, `8.0`, `8.1` et `8.2`.
Un serveur NodeJS peut finalement être utilisé à la place de PHP, avec les versions `18`, `20` ou `21`.
**Une fois installé, rendez-vous sur l'URL choisie pour connaître l'utilisateur, le domaine et le port que vous devrez utiliser pour l'accès SFTP.** Le mot de passe est celui que vous avez choisi lors de l'installation. Sous le répertoire Web, vous verrez un dossier `www` qui contient les fichiers publics servis par cette application. Vous pouvez mettre tous les fichiers de votre application Web personnalisée à l'intérieur.

View file

@ -48,10 +48,21 @@ ram.runtime = "50M"
[install.phpversion]
ask.en = "Choose a PHP version you want to use for your app"
ask.fr = "Choisissez une version PHP que vous souhaitez utiliser pour votre application"
help.en = "You can only choose NodeJS or PHP, not both"
help.fr = "Vous ne pouvez avoir que NodeJS ou PHP, pas les deux"
type = "select"
choices = ["none", "7.4", "8.0", "8.1", "8.2"]
default = "8.0"
[install.nodeversion]
ask.en = "Choose a NodeJS version you want to use for your app"
ask.fr = "Choisissez une version NodeJS que vous souhaitez utiliser pour votre application"
help.en = "You can only choose NodeJS or PHP, not both"
help.fr = "Vous ne pouvez avoir que NodeJS ou PHP, pas les deux"
type = "select"
choices = ["none", "18", "20", "21"]
default = "none"
[install.database]
ask.en = "Do you need a database?"
ask.fr = "Avez-vous besoin d'une base de données ?"
@ -64,6 +75,9 @@ ram.runtime = "50M"
[resources.install_dir]
[resources.ports]
main.default = 3000
[resources.permissions]
main.url = "/"

View file

@ -36,6 +36,17 @@ then
ynh_backup --src_path="/etc/php/${phpversion}/fpm/pool.d/$app.conf"
fi
#=================================================
# BACKUP THE NodeJS CONFIGURATION
#=================================================
if [ $nodeversion != "none" ]
then
ynh_backup --src_path="/etc/systemd/system/${app}-nodejs.service"
ynh_backup --src_path="/etc/systemd/system/${app}-nodejs-watcher.service"
ynh_backup --src_path="/etc/systemd/system/${app}-nodejs-watcher.path"
fi
#=================================================
# BACKUP THE MYSQL DATABASE
#=================================================

View file

@ -23,6 +23,7 @@ ssh_port=$(grep "^Port" /etc/ssh/sshd_config | awk '{print $2}')
ynh_script_progression --message="Validating installation parameters..." --weight=2
[ $with_sftp -eq 0 ] || [ "$password" != "" ] || ynh_die --message="You need to set a password to enable SFTP"
[ $phpversion != "none" ] || [ $nodeversion != "none" ] || ynh_die --message="Either PHP or NodeJS can be used, not both"
#=================================================
# STORE SETTINGS FROM MANIFEST
@ -68,8 +69,17 @@ then
fi
# Create a dedicated NGINX config
ynh_add_nginx_config
ynh_add_config --template="example-custom-nginx-config.conf" --destination="$nginx_extra_conf_dir/sample.conf"
# Use a custom nginx config when using nodejs as it is incompatible with the html/php one
if [ $nodeversion == "none" ]
then
ynh_add_nginx_config
ynh_add_config --template="example-custom-nginx-config.conf" --destination="$nginx_extra_conf_dir/sample.conf"
else
# Add the config manually because yunohost does not support custom nginx confs
ynh_add_config --template="nginx-nodejs.conf" --destination="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_store_file_checksum --file="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_systemd_action --service_name=nginx --action=reload
fi
#=================================================
# CREATE DEDICATED USER
@ -134,6 +144,34 @@ then
ynh_add_fpm_config --usage=$fpm_usage --footprint=$fpm_footprint --phpversion=$phpversion
fi
#=================================================
# NodeJS CONFIGURATION
#=================================================
if [ $nodeversion != "none" ]
then
ynh_script_progression --message="Configuring NodeJS..." --weight=3
ynh_install_nodejs --nodejs_version=$nodeversion
ynh_use_nodejs
ynh_add_config --template="../sources/www/package.json" --destination="$install_dir/www/package.json"
ynh_add_config --template="../sources/www/index.js" --destination="$install_dir/www/index.js"
ynh_add_systemd_config --service="${app}-nodejs" --template="nodejs.service"
ynh_add_systemd_config --service="${app}-nodejs-watcher" --template="nodejs-watcher.service"
ynh_add_config --template="nodejs-watcher.path" --destination="/etc/systemd/system/${app}-nodejs-watcher.path"
systemctl enable "${app}-nodejs-watcher.path" --quiet
systemctl daemon-reload
yunohost service add "${app}-nodejs" --description="$app NodeJS Server" --log="/var/log/$app-nodejs.log"
ynh_add_config --template="nginx-nodejs.conf" --destination="$nginx_extra_conf_dir/nodejs.conf"
ynh_systemd_action --service_name="${app}-nodejs"
ynh_systemd_action --service_name="${app}-nodejs-watcher"
ynh_systemd_action --service_name="${app}-nodejs-watcher.path"
fi
#=================================================
# END OF SCRIPT
#=================================================

View file

@ -46,6 +46,17 @@ ynh_script_progression --message="Removing PHP-FPM configuration..."
# Remove the dedicated PHP-FPM config
ynh_remove_fpm_config
#=================================================
# REMOVE NodeJS CONFIGURATION
#=================================================
ynh_script_progression --message="Removing NodeJS configuration..."
yunohost service remove "${app}-nodejs"
ynh_remove_systemd_config --service="${app}-nodejs"
ynh_remove_systemd_config --service="${app}-nodejs-watcher"
ynh_secure_remove --file="/etc/systemd/system/${app}-nodejs-watcher.path"
#=================================================
# END OF SCRIPT
#=================================================

View file

@ -81,6 +81,17 @@ then
ynh_restore_file --origin_path="/etc/php/${phpversion}/fpm/pool.d/$app.conf"
fi
#=================================================
# RESTORE THE NodeJS CONFIGURATION
#=================================================
if [ $nodeversion != "none" ]
then
ynh_restore_file --origin_path="/etc/systemd/system/${app}-nodejs.service"
ynh_restore_file --origin_path="/etc/systemd/system/${app}-nodejs-watcher.service"
ynh_restore_file --origin_path="/etc/systemd/system/${app}-nodejs-watcher.path"
fi
#=================================================
# GENERIC FINALIZATION
#=================================================

View file

@ -68,6 +68,12 @@ if [ -z "$phpversion" ]; then
ynh_app_setting_set --app=$app --key=phpversion --value=$phpversion
fi
# If phpversion doesn't exist, create it. We assume it is the default system one.
if [ -z "$nodeversion" ]; then
nodeversion="none"
ynh_app_setting_set --app=$app --key=nodeversion --value=$nodeversion
fi
# Delete old user
if [ -n "$(ynh_app_setting_get --app=$app --key=user)" ]
then
@ -103,8 +109,17 @@ then
fi
# Create a dedicated NGINX config
ynh_add_nginx_config
ynh_add_config --template="example-custom-nginx-config.conf" --destination="$nginx_extra_conf_dir/sample.conf"
# Use a custom nginx config when using nodejs as it is incompatible with the html/php one
if [ $nodeversion == "none" ]
then
ynh_add_nginx_config
ynh_add_config --template="example-custom-nginx-config.conf" --destination="$nginx_extra_conf_dir/sample.conf"
else
# Add the config manually because yunohost does not support custom nginx confs
ynh_add_config --template="nginx-nodejs.conf" --destination="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_store_file_checksum --file="/etc/nginx/conf.d/$domain.d/$app.conf"
ynh_systemd_action --service_name=nginx --action=reload
fi
#=================================================
# CREATE DEDICATED USER
@ -142,6 +157,34 @@ then
ynh_add_fpm_config --usage=$fpm_usage --footprint=$fpm_footprint --phpversion=$phpversion
fi
#=================================================
# NodeJS CONFIGURATION
#=================================================
if [ $nodeversion != "none" ]
then
ynh_script_progression --message="Updating NodeJS..." --weight=3
ynh_install_nodejs --nodejs_version=$nodeversion
ynh_use_nodejs
ynh_add_config --template="../sources/www/package.json" --destination="$install_dir/www/package.json"
ynh_add_config --template="../sources/www/index.js" --destination="$install_dir/www/index.js"
ynh_add_systemd_config --service="${app}-nodejs" --template="nodejs.service"
ynh_add_systemd_config --service="${app}-nodejs-watcher" --template="nodejs-watcher.service"
ynh_add_config --template="nodejs-watcher.path" --destination="/etc/systemd/system/${app}-nodejs-watcher.path"
systemctl enable "${app}-nodejs-watcher.path" --quiet
systemctl daemon-reload
yunohost service add "${app}-nodejs" --description="$app NodeJS Server" --log="/var/log/$app-nodejs.log"
ynh_add_config --template="nginx-nodejs.conf" --destination="$nginx_extra_conf_dir/nodejs.conf"
ynh_systemd_action --service_name="${app}-nodejs"
ynh_systemd_action --service_name="${app}-nodejs-watcher"
ynh_systemd_action --service_name="${app}-nodejs-watcher.path"
fi
#=================================================
# GENERIC FINALIZATION
#=================================================

19
sources/www/index.js Normal file
View file

@ -0,0 +1,19 @@
const http = require('node:http');
const fs = require('fs');
const index = fs.readFileSync('index.html').toString();
const host = '127.0.0.1';
var port = process.env.PORT;
port = (typeof port !== 'undefined') ? port : 3000;
const file = index.replace("<hr/>", `<hr/> <h2> Port configuration </h2> <p>Your node application have to listen on port ${port}. Alternatively, you can get port var from envirronment with the following: </p> <pre> process.env.PORT; </pre>`);
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(file);
});
server.listen(port, host, () => {
console.log('Web server running at http://%s:%s', host, port);
});

11
sources/www/package.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "www",
"version": "1.0.0",
"description": "dummy app",
"author": "",
"scripts": {
"start": "node index.js",
"build": "exit 0"
},
"license": "ISC"
}