Skip to content
André Colomb edited this page Mar 20, 2022 · 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
  • TLS certificate (SERVICE_CERT) integration with DSM's certificate management
  • 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 is provided in the Makefile. By default, it configures 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 variable. File is then copied as conf/resource/SPK_NAME.sc and handled by installer script as standard one.

Worker: service configuration for managed certificates integration

This feature is reverse-engineered and undocumented in Synology's Package Developer Guide. Starting with DSM 7.0, packages using it even fail to install because they are using a function intended "only for Synology packages". Therefore, this functionality is ignored when building a DSM >= 7.0 package.

A configuration block is added to conf/resource if SERVICE_CERT is provided in the Makefile. The value must be one of the service names defined in the FWPORTS file, or the generated equivalent.

When a new certificate is assigned to the service by DSM (user change or renewal), DSM can execute a script to cause the service to reload its certificate. Such a script is service-specific and needs to be provided for each package individually. The path given in the Makefile as SERVICE_CERT_RELOAD is relative to the package directory layout after installation, so you need copy it to the STAGING_DIR in an *_extra_install target as appropriate.

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 can be set from wizard variable names declared in Makefile variables SERVICE_WIZARD_GROUP and SERVICE_WIZARD_SHARE or enforced in SERVICE_SETUP script.

When an application requires a storage location, use SHARE_PATH and GROUP=sc-download

Open work-ARCH-TC/scripts/service-setup to read generated content if interested in.

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. All output of above service_* functions goes to the installer log file. To notify the user and optionally abort the installer with exit 1 there are some validate_* function provided: validate_preinst, validate_preunint and validate_preupgrade. The user notificytion work with DSM>=6 only.

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

The installer script and DSM package installer logs to /var/log/packages/${SPK_NAME}.log. Only for DSM<=5 the 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.

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.

Logging

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

Advanced tuning options

Following options can be set in SERVICE_SETUP script:

  • SERVICE_SHELL can be used to replace default /bin/sh on DSM 5 only
  • SVC_WAIT_TIMEOUT is the delay for script to wait for PID_FILE to appear at process startup (and disappear when stopping). Default value is 20 seconds

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