Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add NodeJS server as an alternative to PHP #136

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions conf/nginx-nodejs.conf
Original file line number Diff line number Diff line change
@@ -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 changes: 8 additions & 0 deletions conf/nodejs-watcher.path
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions conf/nodejs-watcher.service
Original file line number Diff line number Diff line change
@@ -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
54 changes: 54 additions & 0 deletions conf/nodejs.service
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions config_panel.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br>medium: Low usage, few people or/and publicly accessible. Low RAM footprint, medium processor footprint when used.<br>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]
Expand Down
13 changes: 13 additions & 0 deletions doc/ADMIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
4 changes: 4 additions & 0 deletions doc/ADMIN_fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions doc/DESCRIPTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions doc/DESCRIPTION_fr.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
14 changes: 14 additions & 0 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 ?"
Expand All @@ -64,6 +75,9 @@ ram.runtime = "50M"

[resources.install_dir]

[resources.ports]
main.default = 3000

[resources.permissions]
main.url = "/"

Expand Down
50 changes: 50 additions & 0 deletions scripts/_common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
11 changes: 11 additions & 0 deletions scripts/backup
Original file line number Diff line number Diff line change
Expand Up @@ -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
#=================================================
Expand Down
18 changes: 18 additions & 0 deletions scripts/config
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
17 changes: 17 additions & 0 deletions scripts/install
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
#=================================================
Expand Down
10 changes: 10 additions & 0 deletions scripts/remove
Original file line number Diff line number Diff line change
Expand Up @@ -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
#=================================================
Expand Down
20 changes: 19 additions & 1 deletion scripts/restore
Original file line number Diff line number Diff line change
Expand Up @@ -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

#=================================================
Expand Down
Loading