Skip to content

MODS Sample Advanced CGI

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

Description

This is a Package for Synology created with 'Mods Packager'.

It illustrates how to display a web application, based on php pages, in a DMS'iFrame without any dependency on the Package Init_3rdparty. (One difference is that Init_3rdparty runs the php scripts as root while here they are run as http).

It's based on a post of Rob Van Aarle: the idea is to use php-cgi to execute all php page instead of php-fpm. For that purpose, Rob wrote a central cgi (a bash script) that handle all requests for any php page in the same folder. Redirecting all requests for php pages to that central cgi is made with an .htaccess file. The cgi script retrieves the name of the requested pages and executes them with php-cgi.

It contains 4 items:

  • Advanced CGI DSM: this is going to call a page helloworld.php via the 'webmanager' (see Notice). The call will be redirected to a router.cgi script which will execute the php page called and display it in a DSM iframe.
  • Advanced CGI WEB: this is going to call a page helloworld.php via the 'webstation' (see Notice). The call will be redirected to a router.cgi script which will execute the php page and display it in a new window.
  • Advanced CGI DSM Check: this is going to call a page index.html via the 'webmanager'. The call will be redirected to a page enabled.html if redirection is working fine. Index.html tells the user that redirection is disabled while enabled.html tells the user that redirection is working fine.
  • Advanced CGI WEB Check: this is going to call a page index.html via the 'webstation'. The call will be redirected to a page enabled.html if redirection is working fine. Index.html tells the user that redirection is disabled while enabled.html tells the user that redirection is working fine.

Notice

  1. The package of Rob was made for DSM < 6.x, which was running apache as a web server. Since DSM 6.x, Synology has replaced apache with nginx. As a consequence, htaccess doesn't work anymore. To implement the idea of Rob with a DSM >= 6.x, I had to use conf files for nginx instead of htaccess
  2. For illustration purpose, my package is working both with apache and nginx. To test it on DSM >= 6.x, simply define apache as default web server in the Web Station (DSM Main Menu > Web Station > General Settings > HTTP back-end server = apache). Doing this, only the webstation will be using apache. The DSM will still use nginx. So, php pages accessed via the 'webmanager' will be handled by nginx while php pages accessed via the 'webstation' will be handled by apache.
  3. The page helloworld.php displays which server, apache or nginx, is running.
  4. Clicking on the server name in the page helloworld.php, you can open the index.html page. As the redirection is working, you get the enabled.html page.
  5. If you are in the index.html page, it means that the redirection is not working. Clicking on the link "Back" in that page will call the page helloworld.php. This one will be executed normally (not via the cgi script as redirection is not working).
  6. If you are in the enabled.html page, it means that the redirection is working. Clicking on the link "Back" in that page will call the page helloworld.php. This one will be executed via the cgi script as redirection is working.
  • Pages are access via the 'webmanager' when deployed in /var/packages//target/... and accessed via ://:/webman/3rdparty//... (embedded or not in a DSM iframe).
  • Pages are access via the 'webstation' when deployed in /var/services/web//... and accessed via ://://... (embedded or not in a DSM iframe).

The item "Advanced CGI WEB" can be tested with the webstation configured either to use nginx or to use apache.

Making of

.htaccess for apache

To support apache, the package comes with a very simple .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]

You can see the rule to rewrite index.html into enabled.html. This is how one can easily check, using the item Advanced CGI Web Check; that the .htaccess is working when your webstation is configured to use apache.

You can next see that all exsiting php pages are passed to a script router.cgi as suggested by Rob. What's important here is the flag QSA to pass the query string and the original URL to the script.

Config file for nginx (DSM)

As said above, the 'webmanager' (DSM) is using nginx. Its config is located in /etc/nginx/nginx.conf In that config, one can see that the webmanager, which has its own server config obviously, is including conf files like /usr/syno/share/nginx/conf.d/dsm.*.conf One can also see that for cgi files, it's including 'scgi_params' and using 'synoscgi' to execute the file. This is how DSM itself works (as based on a index.cgi: /usr/syno/synoman/webman/index.cgi)

server {
	listen 5050 default_server;
	listen [::]:5050 default_server;
	<...>
	include app.d/dsm.*.conf;
	include /usr/syno/share/nginx/conf.d/dsm.*.conf;
	include conf.d/dsm.*.conf;
	<...>
	location ~ \.cgi {
		include             scgi_params;
		scgi_read_timeout   3600s;
		scgi_pass           synoscgi;
	}
	<...>
}

Based on that, a conf file as here under can be used to redirect all call to php pages in the package to a router.cgi script.

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;
}

Notice first the rewriting of all html pages, which mimics the redirect done in the .htaccess for the index.html page. Notice also @MODS_CGI@, which is a simple tag that will be replaced by the name of the package during its installation. Notice finally how the conf file here above is handling php pages in the folder /usr/syno/synoman/webman/3rdparty/ (which is actually a symbolic link onto /var/packages//target/<...> the path where the content of the package is deployed). Those php pages are accessed via the 'webmanager' (DSM) with urls like ://:/webman/3rdparty//

Notice that I am "breaking" after the "rewrite". I may do this only because I did include 'scgi_params' and call 'synoscgi' in this location block. This being done in the server block, I could also simply use a location block like:

location ~ ^/webman/3rdparty/@MODS_CGI@/.*\.php {
  root /usr/syno/synoman;
  rewrite .*\.php /webman/3rdparty/@MODS_CGI@/router.cgi last;
}

With the flag 'last' on the directive 'rewrite', nginx will continue and match the location ~ .cgi in the parent block.

The installation script of the package will add a symbolic link on that file dsm.cgi.conf into /usr/syno/share/nginx/conf.d/dsm..conf. This is how the DSM will be instructed to rediret all php pages inthe package to the script router.cgi which will be executed by the command 'synoscgi' (usr/syno/sbin/synoscgi).

Config file for nginx (WEB)

The 'webstation' can also use nginx, depending what is configured via DSM MAin Menu > Web Station > General Settings. Its config is then also located in /etc/nginx/nginx.conf

In that config, one can see that the webstation, which has also its own server config, is including conf files like /usr/syno/share/nginx/conf.d/www.*.conf And opposite the DSM's server config, one can see that there is not handling foreseen for the cgi files !

server {
	listen 80 default_server;
	listen [::]:80 default_server;
	<...>
	include app.d/www.*.conf;
	include /usr/syno/share/nginx/conf.d/www.*.conf;
	include conf.d/www.*.conf;
	<...>
}

Based on that, a conf file as here under can be used to redirect all call to php pages in a website created in the web root to a router.cgi script.

location ~ ^/@MODS_CGI@/.*\.html {
  root /var/services/web;
  rewrite .*\.html /@MODS_CGI@_Site/enabled.html break;
}
location ~ ^/@MODS_CGI@/.*\.php {
  root /var/services/web;
  include scgi_params;
  rewrite .*\.php /@MODS_CGI@_Site/router.cgi break;
  scgi_pass synoscgi;
}

Notice again the rewriting of all html pages and the tag @MODS_CGI@ which will be replaced be the name of the package. Here, the rewriting of php pages will be done only for pages in a folder '_Site' directly under the web root.

As there is not handling of the cgi scripts in the parent block, I have no choice but "break" after the "rewrite", include 'scgi_params' and finally call 'synoscgi'.

The installation script of the package will add a symbolic link on that file www.cgi.conf into /usr/syno/share/nginx/conf.d/www..conf. It will also create a symbolic link in the web root on the package.

Router CGI

Here is the script used as router.cgi

#!/bin/sh

if [ "$REQUEST_URI" == "$SCRIPT_NAME" ]; then
  # this occurs if one call router.cgi directly
  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

  /usr/local/bin/php56-cgi -d open_basedir=none $SCRIPT_FILENAME $SCRIPT_PARAMETERS 2>&1
fi

Self-explanatory ;)

for php7.3, use this instead of /usr/local/bin/php56-cgi :

/usr/local/bin/php73-cgi -c /usr/local/etc/php73/cli/php.ini -d open_basedir=none $SCRIPT_FILENAME $SCRIPT_PARAMETERS 2>&1

Installation Scripts

As said above, package's installation script is creating various symbolic links and replacing the tag @MODS_CGI@ in the conf file for nginx. It is also restarting nginx to take the new conf files into account.

For illustration purpose, as made sometimes in my other packages, the standard output stream is redirected into a log file as well as the standard error stream. if any errors occurs during the installation, the error log is displayed to the user, merged with the standard log and the setup fails.

#!/bin/sh

LOG="/var/log/MODS_AdvancedTestCGI"
ERRLOG="/var/log/MODS_AdvancedTestCGI_ERR"
rm -f "$ERRLOG"

#close the stream and redirect them to a custom or standard Syno Log
exec 1<&-
exec 2<&-
exec 1>>$LOG
exec 2>>$ERRLOG

echo `date` "Granting access on log file for the webserver"
chmod o+w $LOG

DEST="$SYNOPKG_PKGDEST/ui/*.*"
chown http $DEST

# set the name of the package in the nginx config
sed -i -e "s|@MODS_CGI@|$SYNOPKG_PKGNAME|g" "$SYNOPKG_PKGDEST/ui/www.cgi.conf"
sed -i -e "s|@MODS_CGI@|$SYNOPKG_PKGNAME|g" "$SYNOPKG_PKGDEST/ui/dsm.cgi.conf"

# link the nginx config to redirect pages accessed on admin port
rm -f /usr/syno/share/nginx/conf.d/dsm.$SYNOPKG_PKGNAME.conf
ln -s $SYNOPKG_PKGDEST/ui/dsm.cgi.conf /usr/syno/share/nginx/conf.d/dsm.$SYNOPKG_PKGNAME.conf

# link the nginx config to redirect pages accessed on webstation port
rm -f /usr/syno/share/nginx/conf.d/www.$SYNOPKG_PKGNAME.conf
ln -s $SYNOPKG_PKGDEST/ui/www.cgi.conf /usr/syno/share/nginx/conf.d/www.$SYNOPKG_PKGNAME.conf 

# create a "symbolink website"
ln -s $SYNOPKG_PKGDEST/ui "/var/services/web/$SYNOPKG_PKGNAME"
chown -h http:http "/var/services/web/$SYNOPKG_PKGNAME"

sudo synoservicecfg --reload nginx

if [ -s "$ERRLOG" ]; then
  echo `date` "----------------------------------------------------"
  cat $ERRLOG
  echo `date` "----------------------------------------------------"
  # make the log pretty to be displayed by the Catalog Manager
  echo `date` "Prettifying the POST INSTALL log file"
  sed -i 's/$/<br>/' "$ERRLOG"
  cat $ERRLOG >> $SYNOPKG_TEMP_LOGFILE
  exit 1
fi

exit 0

The standard log created above can be opened via the link "View Log" (accessible when opening the package in the DSM Package Center). This is doable thanks to the Start-Stop Script which returns the log path.

#!/bin/sh
LOG=/var/log/MODS_AdvancedTestCGI
case $1 in
  start)
    echo `date` "Advanced Test CGI is installed properly & started" >> $LOG
    exit 0
    ;;
  stop)
    echo `date` "Advanced Test CGI is stopped" >> $LOG
    exit 0
    ;;
  status)
    exit 0
    ;;
  log)
    echo $LOG
    exit 0
    ;;
esac

Finally, an uninstallation scripts removes the symbolic links created by the package.

#!/bin/sh
rm -f /usr/syno/share/nginx/conf.d/dsm.$SYNOPKG_PKGNAME.conf
rm -f /usr/syno/share/nginx/conf.d/www.$SYNOPKG_PKGNAME.conf
rm -f "/var/services/web/$SYNOPKG_PKGNAME"_Site

exit 0

Illustration

Execute php pages via a cgi script

Known issues

script is downloaded instead of being executed

If your page is downloaded when you want to open it,

  1. Start first by cleaning the cache of your browser (clear browsing data), re-login into the DSM and try again

  2. If it still does not work, be sure that your HTTP Backed-Server (in the General Settings of your Webstation) is Nginx.

  3. If you can confirm that Nginx is the engine in use, restart this one with the command: synoservicecfg --restart nginx Next, try again the step 1

  4. Be sure that the scripts run fine by executing this command in a SSH console run as root: /usr/local/bin/php73-cgi -c /usr/local/etc/php73/cli/php.ini -d open_basedir=none /var/packages/MODS_Sample_Simple_CGI/target/ui/index.php

  5. Be sure that all dsm.*.conf files under /usr/syno/share/nginx/conf.d/ are loaded by Nginx. This can be checked via: cat /etc/nginx/nginx.conf | grep syno

You should see: include /usr/syno/share/nginx/conf.d/dsm.*.conf;

IMPORTANT: sometimes after an upgrade of DSM version, all your dsm.*.conf are are deleted. As a consequence, you get this error in /var/log/nginx/error.log

10529#10529: *124323 connect() to unix:/run/php-fpm/php-fpm-3rdparty.sock failed (2: No such file or directory) while connecting to upstream, client: xxx.xxx.xxx.xxx, server: _, request: "GET /webman/3rdparty/<Your Package>/<Your page> HTTP/1.1", upstream: "fastcgi://unix:/run/php-fpm/php-fpm-3rdparty.sock:", host: "<Your Address>:<Admin Port>", referrer: "http://<Your Address>:<Admin Port>/"

  1. Be sure that you have referenced (or copied) your custom nginx config into /usr/syno/share/nginx/conf.d/