Skip to content

DSM CGI Router 6.x

Valéry Letroye edited this page Feb 18, 2021 · 7 revisions

Principle

By default, in order to run, Web Applications such as WordPress must be deployed under the www folder (web root): /<volumex>/web. Next, they can be accessed via http(s)://<synology>/<package>.

But Packages being deployed under /var/packages/<package>/target (with a symbolic link under /<volumex>/\@appstore/<package>), their php scripts may not be run by the default web server of Synology (apache or nginx).

To be able to run websites outside the www folder, one used to need the Package Init_3rdparty and the Synology Web Station with apache or nginx. With those, the websites were accessible via http(s)://<synology>:<adminport>/webman/3rdparty/<package>.

Instead of using Init_3rdparty, I am using a trick: a CGI router based on an idea of Rob Van Aarle: the use of the command 'php-cgi' to execute all php pages instead of 'php-fpm'. To implement his idea, Rob wrote a central CGI (a bash script) that handles all requests to any php page in the same folder:

  • To redirect all requests for a php to such a central cgi, Rob was using an .htaccess file when using apache. I was able to apply the same principle, with a config file, when using nginx (the default since DSM 6.x).
  • The cgi script retrieves the name of the requested pages and executes them with 'php-cgi'.

The only difference is that Init_3rdparty runs the php scripts as 'root', while the CGI router runs them as 'http', which is anyway preferred from a security Perspective. In addition, using a CGI router remove also the dependency on the Web Station!

  • In order to use a central CGI router with nginx, a config file must be copied or linked under the etc/conf/ folder of nginx, during installation (it's the config file going to route all calls to php scripts in the package folder to the CGI script). This requires root access. It was not an issue with DSM 6.x and older as Packages were installed as root per default. But since DSM 7.x, Packages are installed with their own technical account. Fortunately, there are workarounds (See DSM CGI Router 7.x)

  • There is no need to copy or link a config file under the nginx config folder if you are using one CGI router per php scripts to be called (See here under).

  • In order to use a CGI router with apache, a .htaccess file must be made available. Notice that the default http backend server of Synology is nginx since DSM 6.x. So, theoretically, this flavor of the CGI router should not be used anymore (And to be honest, I didn't test it since a while).

Simple version

If you don't run a complete website but only have a few php scripts, you can create a CGI script for each one (or one CGI script with a parameter). Doing so, you don't need an .htaccess or a config file copied or linked under the nginx config folder to route any call of a php page to your central CGI.

Ex.: to call a php script named helloworld.php, with php73, such a CGI script will look like this:

#!/bin/sh

# Set redirect_status to 1 to get php cgi working.
REDIRECT_STATUS=1 export REDIRECT_STATUS`

# Define the name of the php page to be executed
SCRIPT_FILENAME=$(pwd)/helloworld.php export SCRIPT_FILENAME

cmd=(/usr/local/bin/php73-cgi -c /usr/local/etc/php73/cli/php.ini -d open_basedir=none "$SCRIPT_FILENAME" "$SCRIPT_PARAMETERS")
"${cmd[@]}" 2>&1

You can fine tune this CGI depending on the php version installed on your Synology :

cmd56=(/usr/local/bin/php56-cgi -c /etc/php/php.ini -d open_basedir=none "$SCRIPT_FILENAME" "$SCRIPT_PARAMETERS" 2>&1)
cmd70=(/usr/local/bin/php70-cgi -c /usr/local/etc/php70/php.ini -d open_basedir=none "$SCRIPT_FILENAME" "$SCRIPT_PARAMETERS" 2>&1)
cmd73=(/usr/local/bin/php73-cgi -c /usr/local/etc/php73/cli/php.ini -d open_basedir=none "$SCRIPT_FILENAME" "$SCRIPT_PARAMETERS" 2>&1)
cmd74=(/usr/local/bin/php74-cgi -c /usr/local/etc/php74/cli/php.ini -d open_basedir=none "$SCRIPT_FILENAME" "$SCRIPT_PARAMETERS" 2>&1)

See MODS Sample - Basic CGI for illustration purpose.

Advanced version

With the Advanced version all calls to any php pages under the package deployment folder are redirected to a central CGI.

In the past, when only apache was available as http backend server on Synology, the redirection was done using an .htaccess file:

# Turn on rewrite engine.
RewriteEngine on

DirectoryIndex index.html
ErrorDocument 500 "<center>Oups.... that page couldn't be executed.</center>"
ErrorDocument 404 "<center>Oups.... that page couldn't be found.</center>"

RewriteRule (.*)/index.html $1/enabled.html [L,QSA]

# Rewrite existing php files to the router script.
# Apache on the Synology NAS automatically redirects url
# containing '../' to the corresponding real path, before
# the router script is executed, so it's impossible to
# execute system files.
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule ^(.*\.php)$ router.cgi [PT,L,QSA,NE]

Since nginx is available on Synology, the redirection is done with a config file which must be made available under /usr/syno/share/nginx/conf.d/

This config file must declare the package as a new website (location) and call the CGI to process any request to html or php pages. Here is such a config file:

location ~ ^/webman/3rdparty/@MODS_CGI@/.*\.html {
  root /usr/syno/synoman;
  rewrite .*\.html /webman/3rdparty/@MODS_CGI@/enabled.html break;
}
location ~ ^/webman/3rdparty/@MODS_CGI@/.*\.php {
  root /usr/syno/synoman;
  include scgi_params;
  rewrite .*\.php /webman/3rdparty/@MODS_CGI@/router.cgi break;
  scgi_pass synoscgi;
}

In this config file, @MODS_CGI@ must be replaced by the name of the package, during its installation (I.e.: by the "postinst" script of the package), with, e.g. (assuming that the confgi file is name dsm.<package>.conf):

sed -i -e "s|@MODS_CGI@|$SYNOPKG_PKGNAME|g" "/usr/syno/share/nginx/conf.d/dsm.$SYNOPKG_PKGNAME.conf"

The CGI scripts look like this:

#!/bin/sh

if [ "$REQUEST_URI" == "$SCRIPT_NAME" ]; then
  echo -e "HTTP/1.1 200 OK\n\n"
else
  # Set redirect_status to 1 to get php cgi working.
  REDIRECT_STATUS=1 export REDIRECT_STATUS
  
  # Fix several $_SERVER globals.
  PHP_SELF=$REQUEST_URI export PHP_SELF
  SCRIPT_NAME=$REQUEST_URI export SCRIPT_NAME
  
  # Generate the request url without the Query String parameters
  SCRIPT_FILENAME=$DOCUMENT_ROOT${REQUEST_URI%\?$QUERY_STRING}

  # Prepare the Query String parameters
  SCRIPT_PARAMETERS=${QUERY_STRING//[&]/ }

  SCRIPT_FILENAME=`realpath $SCRIPT_FILENAME` export SCRIPT_FILENAME
  
  cmd=(/usr/local/bin/php73-cgi -c /usr/local/etc/php73/cli/php.ini -d open_basedir=none "$SCRIPT_FILENAME" "$SCRIPT_PARAMETERS")
  "${cmd[@]}" 2>&1
fi

See MODS Sample - Advanced CGI for illustration purpose.