Skip to content
Yves Martin edited this page Mar 1, 2018 · 13 revisions

Generic service support in spksrc

Context

A third party application packaged for Synology often provide a web user interface and users expect to get simple access to it thanks to a service shortcut.

A packager may consider running such a user interface as root is a non-affordable security risk and will push effort to get it run as non-privileged user.

As maintainer facing a large upgrade, work on packages created with copy-paste pattern of many similar files are both time consumming and error prone; so generated files from generic support appear as a requirement.

Concept

Thanks to a small set of spk Makefile variables, a package designer can benefit from generic installer and start-stop-status scripts, with possibility to adapt default behavior thanks to hooks as shell functions.

Provided support

  • User creation (SERVICE_USER) and removal to run backgroup service process
  • Share folder creation based on wizard variable (SERVICE_WIZARD_SHARE)
  • Group creation for managing access to share folder (SERVICE_WIZARD_GROUP)
  • Service port (SERVICE_PORT) configuration for firewall and DSM shortcut
  • Logging support in both installer and start-stop-status script
  • Hooks in package specific script (SERVICE_SETUP)
  • A specific start-stop-status script is provided for package with STARTABLE=no

Support is enabled as far as a SERVICE_ variable is set in spk Makefile.

Example

Package spk/demoservice is provided as non-arch example of how to design your package and wizard screens.

Replacement of defaults

If INSTALLER_SCRIPT is set with a installer script, it replaces generic one.

If SSS_SCRIPT is set with a specific start-stop script, it replaces generic one.

In any case, scripts/service-setup is generated from package variables and SERVICE_SETUP, if provided.

Package migration

For package that has been built without this new support, refer to migration guidelines.

Reference documentation

Configuration

DSM UI integration

An application shortcut is available when both SPK_ICON and SERVICE_PORT are set in Makefile.

A single URL link is built from:

  • DISPLAY_NAME
  • DESCRIPTION
  • SERVICE_PORT
  • SERVICE_PORT_PROTOCOL, defaults to http
  • SERVICE_URL, defaults to /
  • SERVICE_PORT_ALL_USERS, defaults to true

Generated ${DSM_UI_DIR}/config and icons are included in package.tgz. If not set in Makefile, DSM_UI_DIR default value is app.

Setting NO_SERVICE_SHORTCUT prevents this file generation, typically when SERVICE_PORT is not intended to be browsed.

For information, ADMIN_PORT, ADMIN_PROTOCOL and ADMIN_URL are also available to generate administration link visible from Package Center entry.

Service user creation

To prevent user account name collision when migrating from previous busybox usage, a prefix is added to package name to create service account:

  • sc-USER for DSM 6 privilege framework
  • svc-USER for DSM 5 synouser account

For usage in scripts, variable EFF_USER contains effective account name with prefix.

DSM 6 privilege

conf/privilege is generated with username=sc-SPK_NAME if SERVICE_USER variable is set, by defaults to auto.

In rare case package requires an alternate service account name, SERVICE_USER value other than auto can be used.

For DSM 5 compatibility, installer script will create a system account with synouser but prefixed with svc-. Script start-stop-status invokes su to start SERVICE_COMMAND as non-priviliged user.

In case script start-stop-status has to run as root on DSM 6, package can provide its own conf/privilege file as far as CONF_DIR variable is set.

Worker: service configuration for port

conf/resource/SPK_NAME.sc is generated except if FWPORTS_FILE is provide in Makefile. By defaults, it configure SERVICE_PORT as tcp with port forwarding enabled.

[SPK_NAME]
title="(SERVICE_PORT_TITLE:SPK_NAME)"
desc="(DISPLAY_NAME:SPK_NAME)"
port_forward="yes"
dst.ports="SERVICE_PORT/tcp"

If that default behaviour do not suit your need (for instance to declare two ports), provide a package specific file relative path as FWPORTS_FILE variable. File is then copied as conf/resource/SPK_NAME.sc and handled by installer script as standard one.

Scripts

Both generic installer and start-stop-status scripts source service-setup script which is generated aggregating following variables and SERVICE_SETUP script if set in spk Makefile

GROUP and SHARE_PATH are set from wizard variable names declared in Makefile variables SERVICE_WIZARD_GROUP and SERVICE_WIZARD_SHARE

# Base service USER to run background process prefixed according to DSM
USER="demoservice"
PRIV_PREFIX=sc-
SYNOUSER_PREFIX=svc-
if [ ${SYNOPKG_DSM_VERSION_MAJOR} -lt 6 ]; then EFF_USER="${SYNOUSER_PREFIX}${USER}"; else EFF_USER="${PRIV_PREFIX}${USER}"; fi

# Group name from UI if provided
if [ -n "${wizard_group}" ]; then GROUP="${wizard_group}"; fi

# Share download location from UI if provided
if [ -n "${wizard_download_dir}" ]; then SHARE_PATH="${wizard_download_dir}"; fi

# Service port
SERVICE_PORT=8888
# start-stop-status script redirect stdout/stderr to LOG_FILE
LOG_FILE="${SYNOPKG_PKGDEST}/var/${SYNOPKG_PKGNAME}.log"

# Service command has to deliver its pid into PID_FILE
PID_FILE="${SYNOPKG_PKGDEST}/var/${SYNOPKG_PKGNAME}.pid"

# Service command to execute (either with shell or as is)
SERVICE_COMMAND="${SYNOPKG_PKGDEST}/bin/demoservice.sh ${SERVICE_PORT} ${PID_FILE}"

Installer

SERVICE_SETUP script can provide shell function invoked as hook when installer steps are run by DSM: service_preinst, service_postinst, service_preuninst, service_postuninst, service_preupgrade, service_save, service_restore, service_postupgrade.

For DSM 5 compatibility, scripts have to be designed to run with busybox.

installer script logs to /tmp/${SPK_NAME}_install.log and if successfully installed file is copied as /var/packages/${SPK_NAME}/target/var/${SPK_NAME}_install.log and also available at /usr/local/${SPK_NAME}/var/${SPK_NAME}_install.log

By defaults on DSM 5, only var folder is writable by service account. If required, add required commands in service_postinst shell function in package specific SERVICE_SETUP script.

Wizard variables are stored in /var/packages/${SPK_NAME}/etc/installer-variables so that they can be retreived and used in uninstall or upgrade.

Service startup

start-stop-status generic script starts SERVICE_COMMAND as non-privileged user, account name defaults to package name SPK_NAME except if SERVICE_USER is different from auto.

Package status feedback relies on pid file /var/packages/${SPK_NAME}/target/var/${SPK_NAME}.pid available as PID_FILE shell variable.

Service process is expected to fork in background and to provides PID_FILE containing its own main process PID. Two means are available to do so:

  • if application supports such PID file generation, service_postinst shell function should configure service from PID_FILE variable

  • PID_FILE variable has to be provided as command argument in SERVICE_COMMAND so that process writes down its own PID there, at least from shell special variable $!

Process standard output and error streams are aggregated into log file /var/packages/${SPK_NAME}/target/var/${SPK_NAME}.log which is readable thanks to Package Center "View Log" package action.

Script is verbose by default. This behavior can be switched off setting variables in SERVICE_SETUP script:

  • SVC_NO_REDIRECT=y prevents process streams to be collected in package log
  • SVC_QUIET=y prevents logging start/stop date and action
  • SVC_KEEP_LOG=y prevent log content to be cleared before each startup
  • SERVICE_SHELL can be used to replace default /bin/sh on DSM 5 only

Advanced use cases

Run service process in background

In service-setup.sh, set SVC_BACKGROUND=y in addition to SERVICE_COMMAND to get it started as background process (from shell thanks to &)

In case process is not able to write its own PID in a file thanks to command line option, then set also SVC_WRITE_PID=y so that generic start-stop-status script generates PID_FILE itself. This option only make sense if SVC_BACKGROUND=y is set.

Custom service process startup

In case application binary does not support background execution, a work-around is to execute command from service_prestart instead of setting SERVICE_COMMAND. This prevents to create an additional script to do so.

SERVICE_USER         = auto
SERVICE_SETUP        = src/service-setup.sh
STARTABLE            = yes

In src/service-setup.sh, replace generic command execution with shell background execution and PID_FILE generation. LOG_FILE can be use to collect stderr and stdout. Example from demoservice:

service_prestart ()
{
    # Replace generic service startup, fork process in background
    echo "Starting python -m SimpleHTTPServer ${SERVICE_PORT} at ${SYNOPKG_PKGDEST}" >> ${LOG_FILE}
    COMMAND="python -m SimpleHTTPServer ${SERVICE_PORT}"
    if [ $SYNOPKG_DSM_VERSION_MAJOR -lt 6 ]; then
        su ${EFF_USER} -s /bin/sh -c "cd ${SYNOPKG_PKGDEST}; ${COMMAND}" >> ${LOG_FILE} 2>&1 &
    else
        cd ${SYNOPKG_PKGDEST};
        ${COMMAND} >> ${LOG_FILE} 2>&1 &
    fi
    echo "$!" > "${PID_FILE}"
}

Run service process as root

In case application binary has to start as root and forks to non-privileged user from parameter (with setuid syscall), it is possible to keep benefit of SERVICE_USER support.

CONF_DIR = src/conf
SERVICE_USER         = auto
SERVICE_SETUP        = src/service-setup.sh
STARTABLE            = yes

Provide a specific src/conf/privilege, using package name in sc-USER, defaults remain package so that files are own by service user:

{
	"defaults":{
		"run-as": "package"
	},
	"username": "sc-USER",
	"ctrl-script": [{
		"action": "preinst",
		"run-as": "root"
	}, {
		"action": "postinst",
		"run-as": "root"
	}, {
		"action": "preuninst",
		"run-as": "root"
	}, {
		"action": "postuninst",
		"run-as": "root"
	}, {
		"action": "preupgrade",
		"run-as": "root"
	}, {
		"action": "postupgrade",
		"run-as": "root"
	}, {
		"action": "start",
		"run-as": "root"
	}, {
		"action": "stop",
		"run-as": "root"
	}]
}

In SERVICE_SETUP, execute application specific startup command:

service_prestart ()
{
    COMMAND="${SYNOPKG_PKGDEST}/bin/appservice --background --user ${EFF_USER} --pidfile ${PID_FILE}"
    # Run as root in both DSM 5 and 6
    ${COMMAND} >> ${LOG_FILE} 2>&1
}

Service running thanks to BusyBox start-stop-daemon

It is possible to replace generic start-stop-status support by usage of BusyBox start-stop-daemon (for instance provided by Python package).

In package Makefile, provide following variables:

  • SERVICE_EXE is process executable absolute path
  • SERVICE_OPTIONS is optional set of command line options to pass to process
  • Do not set SERVICE_COMMAND
Clone this wiki locally