diff --git a/conf/nginx-nodejs.conf b/conf/nginx-nodejs.conf
new file mode 100644
index 0000000..537cba5
--- /dev/null
+++ b/conf/nginx-nodejs.conf
@@ -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;
+}
diff --git a/conf/nodejs-watcher.path b/conf/nodejs-watcher.path
new file mode 100644
index 0000000..dcc2af0
--- /dev/null
+++ b/conf/nodejs-watcher.path
@@ -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
diff --git a/conf/nodejs-watcher.service b/conf/nodejs-watcher.service
new file mode 100644
index 0000000..7492d53
--- /dev/null
+++ b/conf/nodejs-watcher.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=__APP__ NodeJS restarter
+After=network.target
+StartLimitIntervalSec=10
+StartLimitBurst=5
+
+[Service]
+Type=oneshot
+ExecStart=/usr/bin/systemctl restart __APP__-nodejs.service
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
diff --git a/conf/nodejs.service b/conf/nodejs.service
new file mode 100644
index 0000000..20a6b08
--- /dev/null
+++ b/conf/nodejs.service
@@ -0,0 +1,54 @@
+[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__
+Environment=NODE_ENV=production
+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
diff --git a/config_panel.toml b/config_panel.toml
index b229f0f..3aa910d 100644
--- a/config_panel.toml
+++ b/config_panel.toml
@@ -52,6 +52,15 @@ name = "My Webapp configuration"
default = "low"
help = "low: Personal usage, behind the sso. No RAM footprint when not used, but the impact on the processor can be high if many users are using the service.
medium: Low usage, few people or/and publicly accessible. Low RAM footprint, medium processor footprint when used.
high: High usage, frequently visited website. High RAM footprint, but lower on processor usage and quickly responding."
+ [main.nodejs]
+ name = "NodeJS configuration"
+
+ [main.nodejs.nodeversion]
+ ask = "NodeJS version"
+ type = "select"
+ choices = ["none", "18", "20", "21"]
+ default = "none"
+
# TODO: Add protected_path as tags, which are created as permission "label (path)", so admin can protect a specific path
# [main.permissions]
# [main.permissions.proteced_path]
diff --git a/doc/ADMIN.md b/doc/ADMIN.md
index 454a9bb..b5b7547 100644
--- a/doc/ADMIN.md
+++ b/doc/ADMIN.md
@@ -29,3 +29,16 @@ 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`.
+
+{% if nodeversion != 'none' %}
+
+### Interfacing with NodeJS
+
+A `package.json` should be available within the `/var/www/__APP__/www`. It is used to `npm install`, `npm run build` then `npm run start`. As such, it should at least define the dependencies and provide the `build` and `install` scripts.
+
+You should then start a server in `/var/www/__APP__/www/index.js`.
+It should listen on the port provided through the `PORT` environment with `process.env.PORT` or statically with __PORT__.
+
+The server should reload its files after they change, but due to systemd's limitations, it only works for top level folders/files.
+If your server does not display the right things, restart the `__APP__-nodejs` service.
+{% endif %}
diff --git a/doc/ADMIN_fr.md b/doc/ADMIN_fr.md
index 3f3b568..92c4431 100644
--- a/doc/ADMIN_fr.md
+++ b/doc/ADMIN_fr.md
@@ -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.
diff --git a/doc/DESCRIPTION.md b/doc/DESCRIPTION.md
index de48a9d..d0c2051 100644
--- a/doc/DESCRIPTION.md
+++ b/doc/DESCRIPTION.md
@@ -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.
diff --git a/doc/DESCRIPTION_fr.md b/doc/DESCRIPTION_fr.md
index 2bf7f20..8027f0e 100644
--- a/doc/DESCRIPTION_fr.md
+++ b/doc/DESCRIPTION_fr.md
@@ -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.
diff --git a/manifest.toml b/manifest.toml
index 2be35b6..c95674f 100644
--- a/manifest.toml
+++ b/manifest.toml
@@ -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 = "/"
diff --git a/scripts/_common.sh b/scripts/_common.sh
index d400d3d..f6b0dde 100644
--- a/scripts/_common.sh
+++ b/scripts/_common.sh
@@ -122,3 +122,53 @@ ynh_system_user_del_group() {
gpasswd -d "$username" "$group"
done
}
+
+
+ynh_setup_my_nodeapp() {
+ # Declare an array to define the options of this helper.
+ local legacy_args=ai
+ local -A args_array=([a]=app= [i]=install_dir=)
+ local app
+ local install_dir
+
+ ynh_handle_getopts_args "$@"
+
+ 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_systemd_action --service_name="${app}-nodejs"
+ ynh_systemd_action --service_name="${app}-nodejs-watcher"
+ ynh_systemd_action --service_name="${app}-nodejs-watcher.path"
+
+ # 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
+
+ # Subsequent npm install will write to this folder (as it is within $app's home)
+ # As such we prepare it with fitting rights
+ mkdir -p "$install_dir/.npm"
+ chown $app:$app "$install_dir/.npm"
+}
+
+ynh_remove_my_nodeapp() {
+ # Declare an array to define the options of this helper.
+ local legacy_args=a
+ local -A args_array=([a]=app=)
+ local app
+
+ ynh_handle_getopts_args "$@"
+
+ 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"
+
+ ynh_remove_nodejs
+}
diff --git a/scripts/backup b/scripts/backup
index 4e0ff8c..42bb75c 100644
--- a/scripts/backup
+++ b/scripts/backup
@@ -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
#=================================================
diff --git a/scripts/config b/scripts/config
index 21b6f17..4089396 100644
--- a/scripts/config
+++ b/scripts/config
@@ -105,6 +105,11 @@ ynh_app_config_validate() {
exit 0
fi
fi
+
+ if [ "${changed[nodeversion]}" == "true" ] && [ $nodeversion != "none" ] && [ $phpversion != "none" ]
+ then
+ ynh_die --message="You cannot have both PHP and NodeJS, choose only one."
+ fi
}
ynh_app_config_apply() {
@@ -152,6 +157,19 @@ ynh_app_config_apply() {
then
ynh_add_fpm_config --phpversion=$phpversion --usage=$fpm_usage --footprint=$fpm_footprint
fi
+
+ if [ "${changed[nodeversion]}" == "true" ]
+ then
+ if [ "$nodeversion" != "none" ]
+ then
+ ynh_install_nodejs --nodejs_version=$nodeversion
+ ynh_use_nodejs
+ export port=$(ynh_app_setting_get $app port)
+ ynh_setup_my_nodeapp --app=$app --install_dir=$install_dir
+ else
+ ynh_remove_my_nodeapp --app=$app
+ fi
+ fi
}
ynh_app_config_run $1
diff --git a/scripts/install b/scripts/install
index 9ef3cba..78ef054 100644
--- a/scripts/install
+++ b/scripts/install
@@ -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
@@ -134,6 +135,22 @@ 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_setup_my_nodeapp --app=$app --install_dir=$install_dir
+fi
+
#=================================================
# END OF SCRIPT
#=================================================
diff --git a/scripts/remove b/scripts/remove
index f14eaee..3a5bf61 100644
--- a/scripts/remove
+++ b/scripts/remove
@@ -46,6 +46,16 @@ 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..."
+
+if [ $nodeversion != "none" ]
+then
+ ynh_remove_my_nodeapp --app=$app
+fi
+
#=================================================
# END OF SCRIPT
#=================================================
diff --git a/scripts/restore b/scripts/restore
index cd4f1af..4e79a5f 100644
--- a/scripts/restore
+++ b/scripts/restore
@@ -81,18 +81,36 @@ 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"
+ mkdir -p "$install_dir"/.npm
+ chown -R $app:$app "$install_dir"/.npm
+fi
+
#=================================================
# GENERIC FINALIZATION
#=================================================
# RELOAD NGINX AND PHP-FPM
#=================================================
-ynh_script_progression --message="Reloading NGINX web server and PHP-FPM..."
+ynh_script_progression --message="Reloading NGINX and the server..."
if [ $phpversion != "none" ]
then
ynh_systemd_action --service_name=php${phpversion}-fpm --action=reload
fi
+if [ $nodeversion != "none" ]
+then
+ ynh_systemd_action --service_name=$app-nodejs --action=reload
+fi
+
ynh_systemd_action --service_name=nginx --action=reload
#=================================================
diff --git a/scripts/upgrade b/scripts/upgrade
index ef64b1e..b81cd30 100644
--- a/scripts/upgrade
+++ b/scripts/upgrade
@@ -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
@@ -156,6 +171,22 @@ setfacl -m g:$app:r-x "$install_dir"
setfacl -m g:www-data:r-x "$install_dir"
chmod 750 "$install_dir"
+#=================================================
+# 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_setup_my_nodeapp --app=$app --install_dir=$install_dir
+fi
+
#=================================================
# DEACTIVE MAINTENANCE MODE
#=================================================
diff --git a/sources/www/index.js b/sources/www/index.js
new file mode 100644
index 0000000..82f2545
--- /dev/null
+++ b/sources/www/index.js
@@ -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("
Your node application have to listen on port ${port}. Alternatively, you can get port var from envirronment with the following:
process.env.PORT;`); + +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); +}); diff --git a/sources/www/package.json b/sources/www/package.json new file mode 100644 index 0000000..0c9a8bf --- /dev/null +++ b/sources/www/package.json @@ -0,0 +1,11 @@ +{ + "name": "www", + "version": "1.0.0", + "description": "dummy app", + "author": "", + "scripts": { + "start": "node index.js", + "build": "exit 0" + }, + "license": "ISC" +}