From 5235ef79f18616649651c7dc8ac62bb6302be838 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Mon, 27 Feb 2023 21:43:12 +0100 Subject: [PATCH 01/20] redesign shared folder handling - avoid use of wizard variable `wizard_volume` in installer.functions - dsm 7 preinst function must not validate volume of shared folder, as shares are fully provided by DSM - update evaluation of SHARED_PATH on DSM 7 - simplify EFF_USER definition (we do not support to run DSM 5 packages on DSM 6 anymore) --- mk/spksrc.service.installer.dsm7 | 8 -------- mk/spksrc.service.installer.functions | 8 ++------ mk/spksrc.service.mk | 28 +++++++++++++++++---------- 3 files changed, 20 insertions(+), 24 deletions(-) diff --git a/mk/spksrc.service.installer.dsm7 b/mk/spksrc.service.installer.dsm7 index 0a17d4bd86e..be1e450a1fe 100644 --- a/mk/spksrc.service.installer.dsm7 +++ b/mk/spksrc.service.installer.dsm7 @@ -148,14 +148,6 @@ preinst () call_func "validate_preinst" call_func "service_preinst" install_log - # Check volume exists - if [ -n "${SHARE_PATH}" ]; then - if [ ! -d "${SHARE_VOLUME}" ]; then - echo "ERROR: Volume ${SHARE_VOLUME} does not exist." | $TEE 1>&2 - exit 1 - fi - fi - exit 0 } diff --git a/mk/spksrc.service.installer.functions b/mk/spksrc.service.installer.functions index d252322f175..156de01701e 100644 --- a/mk/spksrc.service.installer.functions +++ b/mk/spksrc.service.installer.functions @@ -1,4 +1,3 @@ - ### common installer functions and variables for synocommunity generic service installer # # functions are common for all DSM versions @@ -48,11 +47,8 @@ initialize_variables () # Extract share volume and share name from download location if provided if [ -n "${SHARE_PATH}" ]; then - if [ -n "${wizard_volume}" ]; then - SHARE_PATH="${wizard_volume}/${SHARE_PATH}" - fi - SHARE_VOLUME=$(echo "${SHARE_PATH}" | awk -F/ '{print "/"$2}') - SHARE_NAME=$(echo "${SHARE_PATH}" | awk -F/ '{print $3}') + SHARE_VOLUME=$(echo "$(realpath ${SHARE_PATH})" | awk -F/ '{print "/"$2}') + SHARE_NAME=$(echo $(realpath ${SHARE_PATH}) | awk -F/ '{print $3}') fi } diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index cc7fb8eaef8..573ba938118 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -25,8 +25,8 @@ # SERVICE_CERT_RELOAD (optional) package-relative path to a script for reloading the service after certificate changes # SERVICE_TYPE service type for dsm-ui config file, default = "url" # SERVICE_WIZARD_GROUP (optional) use name of wizard-variable to define the GROUP -# SERVICE_WIZARD_SHARE (optional) use name of wizard-varible to define SHARE_PATH (uses DSM data share worker for DSM 7) -# USE_DATA_SHARE_WORKER (optional) use DSM data share worker for SERVICE_WIZARD_SHARE and DSM 6 too +# SERVICE_WIZARD_SHARE (optional) use name of wizard-varible to define folder name of SHARE_PATH (uses DSM data share worker for DSM 7) +# USE_DATA_SHARE_WORKER (optional) use DSM data share worker for SERVICE_WIZARD_SHARE for DSM 6 too # SERVICE_USER (optional) runtime user account for generic service support. # "auto" is the only value supported with DSM 7 and defines sc-${SPK_NAME} as service user. # SPK_GROUP (optional) defines the group to use in privilege resource file @@ -123,15 +123,11 @@ $(DSM_SCRIPTS_DIR)/service-setup: @echo 'fi' >> $@ @echo '' >> $@ ifneq ($(strip $(SERVICE_USER)),) -ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) - @echo USER=\"sc-$(SPK_USER)\" >> $@ + @echo USER=\"$(SPK_USER)\" >> $@ +ifeq ($(call version_ge, ${TCVERSION}, 6.0),1) @echo EFF_USER=\"sc-$(SPK_USER)\" >> $@ else - @echo "# Base service USER to run background process prefixed according to DSM" >> $@ - @echo USER=\"$(SPK_USER)\" >> $@ - @echo "PRIV_PREFIX=sc-" >> $@ - @echo "SYNOUSER_PREFIX=svc-" >> $@ - @echo 'if [ -n "$${SYNOPKG_DSM_VERSION_MAJOR}" ] && [ "$${SYNOPKG_DSM_VERSION_MAJOR}" -lt 6 ]; then EFF_USER="$${SYNOUSER_PREFIX}$${USER}"; else EFF_USER="$${PRIV_PREFIX}$${USER}"; fi' >> $@ + @echo EFF_USER=\"svc-$(SPK_USER)\" >> $@ endif @echo '' >> $@ endif @@ -142,7 +138,19 @@ ifneq ($(strip $(SERVICE_WIZARD_GROUP)),) endif ifneq ($(strip $(SERVICE_WIZARD_SHARE)),) @echo "# DSM shared folder location from UI if provided" >> $@ - @echo 'if [ -n "$${$(SERVICE_WIZARD_SHARE)}" ]; then SHARE_PATH="$${$(SERVICE_WIZARD_SHARE)}"; fi' >> $@ + @echo 'if [ -n "$${$(SERVICE_WIZARD_SHARE)}" ]; then' >> $@ +ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) + @echo ' SHARE_PATH=$$(realpath "/var/packages/$${SYNOPKG_PKGNAME}/shares/$${$(SERVICE_WIZARD_SHARE)}")' >> $@ +else + @echo ' if synoshare --get "$${$(SERVICE_WIZARD_SHARE)}" &> /dev/null; then ' >> $@ + @echo ' SHARE_PATH=$$(synoshare --get "$${$(SERVICE_WIZARD_SHARE)}" | awk 'NR==4' | cut -d] -f1 | cut -d[ -f2)' >> $@ + @echo ' echo "SHARE_PATH from share [$${SHARE_PATH}]"' >> $@ + @echo ' else' >> $@ + @echo ' SHARE_PATH="$${$(SERVICE_WIZARD_SHARE)}"' >> $@ + @echo ' echo "SHARE_PATH [$${SHARE_PATH}] is not an existing shared folder"' >> $@ + @echo ' fi' >> $@ +endif + @echo 'fi' >> $@ @echo '' >> $@ endif ifneq ($(strip $(SERVICE_PORT)),) From 4a53d7c601b39c676c3bd372ebb49489be78860b Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 1 Oct 2023 17:52:37 +0200 Subject: [PATCH 02/20] add Makefile variable SERVICE_WIZARD_SHARENAME for new approach - introduce variable SERVICE_WIZARD_SHARENAME for redesigned share folder handling - make variables SERVICE_WIZARD_SHARE and USE_DATA_SHARE_WORKER as deprecated - update demoservice with new shared folder handling - use start.sh to start demoservice since SERVICE_COMMAND with parameters does not work on DSM with ash shell - use python3 for demoservice, when available in path - log SHARE_PATH and SHARE_NAME variables in demoservice installer log - fix function load_variables_from_file (does not work when called with sub shell by call_func) - drop support for SERVICE_EXE (deprecated since DSM 7 support) - drop support for SERVICE_WIZARD_GROUP - initialize and log variables for shared folder in function linitialize_variables - use "sc-" user prefix for DSM 5 too - remove check for shared folder existence in preinst - fix load_variables_from_file (avoid use of cut with --delimiter, to make it POSIX compatible) - avoid to add duplicate user to shared folders without resource workers (SRM, DSM5, DSM6 with SERVICE_WIZARD_SHARE) - fix save_wizard_variables to not delete installer-variables when variables are not set (i.e. for UPGRADE without wizard) --- mk/spksrc.service.installer.dsm5 | 44 +++--- mk/spksrc.service.installer.dsm6 | 74 ++++++----- mk/spksrc.service.installer.dsm7 | 5 +- mk/spksrc.service.installer.functions | 30 ++++- mk/spksrc.service.mk | 66 +++++---- mk/spksrc.service.privilege-startasroot | 34 ----- mk/spksrc.service.start-stop-daemon | 125 ------------------ spk/demoservice/Makefile | 33 ++--- spk/demoservice/src/service-setup.sh | 70 +++++++--- spk/demoservice/src/start.sh | 36 +++++ spk/demoservice/src/wizard/install_uifile | 43 +++--- spk/demoservice/src/wizard/install_uifile_fre | 43 +++--- 12 files changed, 265 insertions(+), 338 deletions(-) delete mode 100644 mk/spksrc.service.privilege-startasroot delete mode 100644 mk/spksrc.service.start-stop-daemon create mode 100644 spk/demoservice/src/start.sh diff --git a/mk/spksrc.service.installer.dsm5 b/mk/spksrc.service.installer.dsm5 index 410c894be3c..b9455b5d979 100644 --- a/mk/spksrc.service.installer.dsm5 +++ b/mk/spksrc.service.installer.dsm5 @@ -60,7 +60,7 @@ fi # Load (wizard) variables stored by postinst -call_func "load_variables_from_file" ${INST_VARIABLES} +load_variables_from_file ${INST_VARIABLES} # init variables either from ${INST_VARIABLES}, from package or from wizard call_func "initialize_variables" @@ -212,14 +212,6 @@ preinst () call_func "validate_preinst" call_func "service_preinst" - # Check volume exists - if [ -n "${SHARE_PATH}" ]; then - if [ ! -d "${SHARE_VOLUME}" ]; then - echo "ERROR: Volume ${SHARE_VOLUME} does not exist." | $TEE 1>&2 - exit 1 - fi - fi - exit 0 } @@ -278,24 +270,37 @@ postinst () # Share management if [ -n "${SHARE_PATH}" ]; then - install_log "Configuring ${SHARE_PATH}" - # Create share if does not exist - # !"#$%&’()*+,/:;<=>?@[]nˆ`{} | - if ! synoshare --get "${SHARE_NAME}" &> /dev/null; then - synoshare --add "${SHARE_NAME}" "${SHARE_DESC}" "${SHARE_PATH}" "" "" "" 1 0 2>&1 | install_log + install_log "Configuring share SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}, EFF_USER=${EFF_USER}, GROUP=${GROUP}" + # Create share if it does not exist + # invalid characters: !"#$%&’()*+,/:;<=>?@[]nˆ`{} | + if ! synoshare --get "${SHARE_NAME}" > /dev/null 2>&1; then + install_log "Create share SHARE_NAME=${SHARE_NAME}, SYNOPKG_PKGDEST_VOL=${SYNOPKG_PKGDEST_VOL}" + # mandatory arguments: + # name desc path na rw ro browseable adv_privilege + # na, rw and ro are list of user(s) and/or group(s), separated by comma + synoshare --add "${SHARE_NAME}" "Share created for package ${SYNOPKG_PKGNAME}" "${SHARE_PATH}" "" "" "" 1 0 2>&1 | install_log fi - # Add user permission if no GROUP is set in UI + # Add user permission if no GROUP is set in service-setup # GROUP permission will be added in set_syno_permissions if [ -z "$GROUP" ] && [ -n "${EFF_USER}" ]; then - synoshare --setuser "${SHARE_NAME}" RW + "${EFF_USER}" 2>&1 | install_log + # check whether user is already added to RW users + _synoshare_parameter="--getmap" + if synoshare --getmap "${SHARE_NAME}" | grep "ACL.*\[yes\]" > /dev/null 2>&1; then + _synoshare_parameter="--list_acl" + fi + if synoshare ${_synoshare_parameter} "${SHARE_NAME}" | grep "RW list" | grep -o "\[.*\]" | sed 's/[\[,]/ /g' | sed 's/\]/ /g' | grep -q " ${EFF_USER} " > /dev/null 2>&1; then + install_log "User has already RW access to share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}, _synoshare_parameter=${_synoshare_parameter}" + else + install_log "Set user for share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}" + synoshare --setuser "${SHARE_NAME}" RW + "${EFF_USER}" 2>&1 | install_log + fi fi synoshare --build 2>&1 | install_log - $MKDIR "${SHARE_PATH}" - # Permissions for folder, up to volume if [ -n "$GROUP" ]; then + install_log "Set group for share: SHARE_PATH=${SHARE_PATH}, GROUP=${GROUP}" set_syno_permissions "${SHARE_PATH}" "${GROUP}" 2>&1 | install_log fi fi @@ -330,6 +335,7 @@ preuninst () fi call_func "service_preuninst" + exit 0 } @@ -354,6 +360,7 @@ postuninst () if [ "${SYNOPKG_PKG_STATUS}" == "UNINSTALL" ]; then $RM "${INST_VARIABLES}" 2>&1 | install_log fi + exit 0 } @@ -393,5 +400,6 @@ postupgrade () # Make sure we also have the logging for this step $CP "${INST_LOG_TEMP}" "${INST_LOG}" 2>&1 | install_log + exit 0 } diff --git a/mk/spksrc.service.installer.dsm6 b/mk/spksrc.service.installer.dsm6 index e33f0cc0d62..5afcd2ef279 100644 --- a/mk/spksrc.service.installer.dsm6 +++ b/mk/spksrc.service.installer.dsm6 @@ -68,7 +68,7 @@ fi # Load (wizard) variables stored by postinst -call_func "load_variables_from_file" install_log ${INST_VARIABLES} +load_variables_from_file ${INST_VARIABLES} # init variables either from ${INST_VARIABLES}, from package or from wizard call_func "initialize_variables" @@ -148,9 +148,9 @@ set_syno_permissions () # Ensure directory resides in /volumeX before setting GROUP permissions if [ "$(echo ${VOLUME} | cut -c2-7)" = "volume" ]; then # Set read/write permissions for GROUP for folder and subfolders - if [ ! "$(synoacltool -get ""${DIRNAME}"" | grep ""group:${GROUP}:allow:rwxpdDaARWcC-:fd--"")" ]; then + if [ ! "$(synoacltool -get \"${DIRNAME}\"| grep \"group:${GROUP}:allow:rwxpdDaARWcC-:fd--\")" ]; then # First Unix permissions, but only if it's in Linux mode - if [ "$(synoacltool -get ""${DIRNAME}"" | grep -i 'Linux mode')" ]; then + if [ "$(synoacltool -get \"${DIRNAME}\"| grep \"Linux mode\")" ]; then set_unix_permissions "${DIRNAME}" # If it is linux mode (due to old package) we need to add "administrators"-group, # otherwise the folder is not accessible from File Station anymore! @@ -165,9 +165,9 @@ set_syno_permissions () # Walk up the tree and set traverse execute permissions for GROUP up to VOLUME while [ "${DIRNAME}" != "${VOLUME}" ]; do - if [ ! "$(synoacltool -get ""${DIRNAME}"" | grep ""group:${GROUP}:allow:r.x"")" ]; then + if [ ! "$(synoacltool -get \"${DIRNAME}\"| grep \"group:${GROUP}:allow:r.x\")" ]; then # Here we also need to make sure the admin can access data via File Station - if [ "$(synoacltool -get ""${DIRNAME}"" | grep -i 'Linux mode')" ]; then + if [ "$(synoacltool -get \"${DIRNAME}\"| grep \"Linux mode\")" ]; then synoacltool -add "${DIRNAME}" "group:administrators:allow:rwxpdDaARWc--:fd--" fi # Add the new group permissions @@ -224,14 +224,6 @@ preinst () call_func "validate_preinst" call_func "service_preinst" install_log - # Check volume exists - if [ -n "${SHARE_PATH}" ]; then - if [ ! -d "${SHARE_VOLUME}" ]; then - echo "ERROR: Volume ${SHARE_VOLUME} does not exist." | $TEE 1>&2 - exit 1 - fi - fi - exit 0 } @@ -273,27 +265,44 @@ postinst () synogroup --rebuild all 2>&1 | install_log fi - # Share management + # Share management (can be removed when SERVICE_WIZARD_SHARE is not used anymore) if [ -n "${SHARE_PATH}" ]; then - install_log "Configuring ${SHARE_PATH}" - # Create share if does not exist - # !"#$%&’()*+,/:;<=>?@[]nˆ`{} | - if ! synoshare --get "${SHARE_NAME}" &> /dev/null; then - synoshare --add "${SHARE_NAME}" "${SHARE_DESC}" "${SHARE_PATH}" "" "" "" 1 0 2>&1 | install_log - fi - - # Add user permission if no GROUP is set in UI - # GROUP permission will be added in set_syno_permissions - if [ -z "$GROUP" ] && [ -n "${EFF_USER}" ]; then - synoshare --setuser "${SHARE_NAME}" RW + "${EFF_USER}" 2>&1 | install_log - fi - synoshare --build 2>&1 | install_log + if [ "${SHARE_WORKER}" != "0" ]; then + install_log "Shared folder [${SHARE_PATH}] will be created by DSM." + else + install_log "Configuring share SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}, EFF_USER=${EFF_USER}, GROUP=${GROUP}" + # Create share if it does not exist + # invalid characters: !"#$%&’()*+,/:;<=>?@[]nˆ`{} | + if ! synoshare --get "${SHARE_NAME}" > /dev/null 2>&1; then + install_log "Create share SHARE_NAME=${SHARE_NAME}, SYNOPKG_PKGDEST_VOL=${SYNOPKG_PKGDEST_VOL}" + # mandatory arguments: + # name desc path na rw ro browseable adv_privilege + # na, rw and ro are list of user(s) and/or group(s), separated by comma + synoshare --add "${SHARE_NAME}" "Share created for package ${SYNOPKG_PKGNAME}" "${SHARE_PATH}" "" "" "" 1 0 2>&1 | install_log + fi - $MKDIR "${SHARE_PATH}" + # Add user permission if no GROUP is set in service-setup + # GROUP permission will be added in set_syno_permissions + if [ -z "$GROUP" ] && [ -n "${EFF_USER}" ]; then + # check whether user is already added to RW users + _synoshare_parameter="--getmap" + if synoshare --getmap "${SHARE_NAME}" | grep "ACL.*\[yes\]" > /dev/null 2>&1; then + _synoshare_parameter="--list_acl" + fi + if synoshare ${_synoshare_parameter} "${SHARE_NAME}" | grep "RW list" | grep -o "\[.*\]" | sed 's/[\[,]/ /g' | sed 's/\]/ /g' | grep -q " ${EFF_USER} " > /dev/null 2>&1; then + install_log "User has already RW access to share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}, _synoshare_parameter=${_synoshare_parameter}" + else + install_log "Set user for share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}" + synoshare --setuser "${SHARE_NAME}" RW + "${EFF_USER}" 2>&1 | install_log + fi + fi + synoshare --build 2>&1 | install_log - # Permissions for folder, up to volume - if [ -n "$GROUP" ]; then - set_syno_permissions "${SHARE_PATH}" "${GROUP}" 2>&1 | install_log + # Permissions for folder, up to volume + if [ -n "$GROUP" ]; then + install_log "Set group for share: SHARE_PATH=${SHARE_PATH}, GROUP=${GROUP}" + set_syno_permissions "${SHARE_PATH}" "${GROUP}" 2>&1 | install_log + fi fi fi @@ -317,6 +326,7 @@ preuninst () log_step "preuninst" call_func "validate_preuninst" call_func "service_preuninst" install_log + exit 0 } @@ -340,6 +350,7 @@ postuninst () if [ "${SYNOPKG_PKG_STATUS}" == "UNINSTALL" ]; then $RM "${INST_VARIABLES}" 2>&1 | install_log fi + exit 0 } @@ -358,6 +369,7 @@ preupgrade () # Beware of /. outside the quotes # Needed to copy all files including hidden ones $CP "${SYNOPKG_PKGVAR}"/. "$TMP_DIR" 2>&1 | install_log + exit 0 } diff --git a/mk/spksrc.service.installer.dsm7 b/mk/spksrc.service.installer.dsm7 index be1e450a1fe..59cbaedc218 100644 --- a/mk/spksrc.service.installer.dsm7 +++ b/mk/spksrc.service.installer.dsm7 @@ -69,7 +69,7 @@ fi # Load (wizard) variables stored by postinst -call_func "load_variables_from_file" install_log ${INST_VARIABLES} +load_variables_from_file ${INST_VARIABLES} # init variables either from ${INST_VARIABLES}, from package or from wizard call_func "initialize_variables" @@ -174,6 +174,7 @@ preuninst () log_step "preuninst" call_func "validate_preuninst" call_func "service_preuninst" install_log + exit 0 } @@ -196,6 +197,7 @@ postuninst () find /var/packages/${SYNOPKG_PKGNAME}/etc -mindepth 1 -delete -print | install_log fi fi + exit 0 } @@ -221,6 +223,7 @@ preupgrade () call_func "service_preupgrade" install_log call_func "service_save" install_log + exit 0 } diff --git a/mk/spksrc.service.installer.functions b/mk/spksrc.service.installer.functions index 156de01701e..8ff699e014b 100644 --- a/mk/spksrc.service.installer.functions +++ b/mk/spksrc.service.installer.functions @@ -45,10 +45,25 @@ initialize_variables () GROUP_DESC="SynoCommunity Package Group" fi - # Extract share volume and share name from download location if provided + # Extract share volume and share name from share path when provided, and not already defined if [ -n "${SHARE_PATH}" ]; then - SHARE_VOLUME=$(echo "$(realpath ${SHARE_PATH})" | awk -F/ '{print "/"$2}') - SHARE_NAME=$(echo $(realpath ${SHARE_PATH}) | awk -F/ '{print $3}') + install_log "Evaluate full path for SHARE_NAME [${SHARE_NAME}] and SHARE_PATH [${SHARE_PATH}]" + # migrate SHARE_PATH that holds the share name only to full share path + # this is required for installers without resource worker for file share (SRM 1, DSM 5, DSM 6 old packages) + if [ "$(echo ${SHARE_PATH} | grep ^/)" != "${SHARE_PATH}" ]; then + SHARE_NAME=${SHARE_PATH} + if synoshare --get "${SHARE_NAME}" &> /dev/null; then + SHARE_PATH=$(synoshare --get "${SHARE_NAME}" | awk 'NR==4' | cut -d] -f1 | cut -d[ -f2) + install_log "Path of existing share [${SHARE_NAME}] is [${SHARE_PATH}]" + else + install_log "SHARE_NAME is not an existing share [${SHARE_PATH}]." + fi + else + install_log "SHARE_PATH is absolute path [${SHARE_PATH}]." + fi + if [ -z "${SHARE_NAME}" ]; then + SHARE_NAME=$(echo $(abspath ${SHARE_PATH}) | awk -F/ '{print $3}') + fi fi } @@ -105,8 +120,8 @@ load_variables_from_file () if [ -n "$1" -a -r "$1" ]; then while read -r _line; do if [ "$(echo ${_line} | grep -v ^[/s]*#)" != "" ]; then - _key="$(echo ${_line} | cut --fields=1 --delimiter==)" - _value="$(echo ${_line} | cut --fields=2- --delimiter==)" + _key=${_line%%=*} + _value=${_line#*=} export "${_key}=${_value}" fi done < "$1" @@ -116,7 +131,7 @@ load_variables_from_file () save_wizard_variables () { - if [ -e "${INST_VARIABLES}" ]; then + if [ -e "${INST_VARIABLES}" -a -n "${GROUP}${SHARE_PATH}${SHARE_NAME}" ]; then $RM "${INST_VARIABLES}" fi if [ -n "${GROUP}" ]; then @@ -125,4 +140,7 @@ save_wizard_variables () if [ -n "${SHARE_PATH}" ]; then echo "SHARE_PATH=${SHARE_PATH}" >> ${INST_VARIABLES} fi + if [ -n "${SHARE_NAME}" ]; then + echo "SHARE_NAME=${SHARE_NAME}" >> ${INST_VARIABLES} + fi } diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index 573ba938118..859e6739da5 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -24,16 +24,18 @@ # SERVICE_CERT (optional) configure DSM certificate management for this service name from the firewall config file (*.sc) # SERVICE_CERT_RELOAD (optional) package-relative path to a script for reloading the service after certificate changes # SERVICE_TYPE service type for dsm-ui config file, default = "url" -# SERVICE_WIZARD_GROUP (optional) use name of wizard-variable to define the GROUP -# SERVICE_WIZARD_SHARE (optional) use name of wizard-varible to define folder name of SHARE_PATH (uses DSM data share worker for DSM 7) -# USE_DATA_SHARE_WORKER (optional) use DSM data share worker for SERVICE_WIZARD_SHARE for DSM 6 too +# SERVICE_WIZARD_GROUP (not supported anymore) name of wizard-variable to define the GROUP +# SERVICE_WIZARD_GROUP is not supported anymore (not compatible with DSM 7 and DSM 6 using resource worker) +# SERVICE_WIZARD_SHARENAME (optional) this is the name of wizard-varible to define folder name of SHARE_PATH (uses DSM data share worker for DSM 6 and DSM 7) +# SERVICE_WIZARD_SHARE (deprecated) name of wizard-varible to define SHARE_PATH +# USE_DATA_SHARE_WORKER (deprecated, optional) use DSM data share worker for SERVICE_WIZARD_SHARE for DSM 6 too # SERVICE_USER (optional) runtime user account for generic service support. # "auto" is the only value supported with DSM 7 and defines sc-${SPK_NAME} as service user. # SPK_GROUP (optional) defines the group to use in privilege resource file # SYSTEM_GROUP (optional) defines an additional group to join in privilege resource file # STARTABLE default = yes, must be "no" for packages that do not create a service (command line tools) # SERVICE_COMMAND service command, to be used with generic service support -# SERVICE_EXE (deprecated) service command, implemented with busybox start-stop-daemon +# SERVICE_EXE (not supported anymore) service command, implemented with busybox start-stop-daemon # SPK_COMMANDS (optional) list of "folder/command" to create links for in folder /usr/local # SPK_USR_LOCAL_LINKS (optional) list of "folder:command" to create links for in folder /usr/local # with 'command' in relative folder @@ -100,9 +102,9 @@ endif endif # Recommend explicit STARTABLE=no -ifeq ($(strip $(SSS_SCRIPT) $(SERVICE_COMMAND) $(SERVICE_EXE) $(STARTABLE)),) +ifeq ($(strip $(SSS_SCRIPT) $(SERVICE_COMMAND) $(STARTABLE)),) ifeq ($(strip $(SPK_COMMANDS) $(SPK_USR_LOCAL_LINKS)),) -$(error Set STARTABLE=no or provide either SERVICE_COMMAND, SERVICE_EXE, SSS_SCRIPT, SPK_COMMANDS or SPK_USR_LOCAL_LINKS) +$(error Set STARTABLE=no or provide either SERVICE_COMMAND, SSS_SCRIPT, SPK_COMMANDS or SPK_USR_LOCAL_LINKS) endif endif @@ -124,31 +126,32 @@ $(DSM_SCRIPTS_DIR)/service-setup: @echo '' >> $@ ifneq ($(strip $(SERVICE_USER)),) @echo USER=\"$(SPK_USER)\" >> $@ -ifeq ($(call version_ge, ${TCVERSION}, 6.0),1) @echo EFF_USER=\"sc-$(SPK_USER)\" >> $@ -else - @echo EFF_USER=\"svc-$(SPK_USER)\" >> $@ -endif @echo '' >> $@ endif ifneq ($(strip $(SERVICE_WIZARD_GROUP)),) - @echo "# Group name from UI if provided" >> $@ - @echo 'if [ -n "$${$(SERVICE_WIZARD_GROUP)}" ]; then GROUP="$${$(SERVICE_WIZARD_GROUP)}"; fi' >> $@ - @echo '' >> $@ + $(error "SERVICE_WIZARD_GROUP is not supported anymore.") endif ifneq ($(strip $(SERVICE_WIZARD_SHARE)),) @echo "# DSM shared folder location from UI if provided" >> $@ - @echo 'if [ -n "$${$(SERVICE_WIZARD_SHARE)}" ]; then' >> $@ + @echo 'if [ -n "$${$(SERVICE_WIZARD_SHARE)}" ]; then SHARE_PATH="$${$(SERVICE_WIZARD_SHARE)}"; SHARE_WORKER=0; fi' >> $@ +else +ifneq ($(strip $(SERVICE_WIZARD_SHARENAME)),) + @echo "# DSM name of shared folder from UI if provided" >> $@ + @echo 'if [ -n "$${$(SERVICE_WIZARD_SHARENAME)}" ]; then' >> $@ ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) - @echo ' SHARE_PATH=$$(realpath "/var/packages/$${SYNOPKG_PKGNAME}/shares/$${$(SERVICE_WIZARD_SHARE)}")' >> $@ + @echo ' SHARE_PATH=$$(abspath "/var/packages/$${SYNOPKG_PKGNAME}/shares/$${$(SERVICE_WIZARD_SHARENAME)}")' >> $@ + @echo ' install_log "SHARE_PATH from share [$${SHARE_PATH}], variable [$(SERVICE_WIZARD_SHARENAME)=$${$(SERVICE_WIZARD_SHARENAME)}]"' >> $@ else - @echo ' if synoshare --get "$${$(SERVICE_WIZARD_SHARE)}" &> /dev/null; then ' >> $@ - @echo ' SHARE_PATH=$$(synoshare --get "$${$(SERVICE_WIZARD_SHARE)}" | awk 'NR==4' | cut -d] -f1 | cut -d[ -f2)' >> $@ - @echo ' echo "SHARE_PATH from share [$${SHARE_PATH}]"' >> $@ + @echo ' if synoshare --get "$${$(SERVICE_WIZARD_SHARENAME)}" &> /dev/null; then ' >> $@ + @echo ' SHARE_PATH=$$(synoshare --get "$${$(SERVICE_WIZARD_SHARENAME)}" | awk 'NR==4' | cut -d] -f1 | cut -d[ -f2)' >> $@ + @echo ' install_log "SHARE_PATH from share [$${SHARE_PATH}], variable [$(SERVICE_WIZARD_SHARENAME)=$${$(SERVICE_WIZARD_SHARENAME)}]"' >> $@ @echo ' else' >> $@ - @echo ' SHARE_PATH="$${$(SERVICE_WIZARD_SHARE)}"' >> $@ - @echo ' echo "SHARE_PATH [$${SHARE_PATH}] is not an existing shared folder"' >> $@ + @echo ' SHARE_PATH="$${$(SERVICE_WIZARD_SHARENAME)}"' >> $@ + @echo ' install_log "SHARE_PATH [$${$(SERVICE_WIZARD_SHARENAME)}] does not yet exist."' >> $@ @echo ' fi' >> $@ +endif + @echo ' SHARE_NAME="$${$(SERVICE_WIZARD_SHARENAME)}"' >> $@ endif @echo 'fi' >> $@ @echo '' >> $@ @@ -182,13 +185,9 @@ ifneq ($(strip $(SERVICE_COMMAND)),) @echo '' >> $@ endif ifneq ($(strip $(SERVICE_EXE)),) -ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) - @echo "${RED}ERROR: SERVICE_EXE (start-stop-daemon) is unsupported in DSM7${NC}" + @echo "${RED}ERROR: SERVICE_EXE (start-stop-daemon) is not supported anymore${NC}" @echo "${GREEN}Please migrate to SERVICE_COMMAND=${NC}" @exit 1 -endif - @echo "# Service command to execute with start-stop-daemon" >> $@ - @echo 'SERVICE_EXE="$(SERVICE_EXE)"' >> $@ ifneq ($(strip $(SERVICE_OPTIONS)),) @echo 'SERVICE_OPTIONS="$(SERVICE_OPTIONS)"' >> $@ endif @@ -245,6 +244,14 @@ ifeq ($(strip $(USE_DATA_SHARE_WORKER)),yes) @jq --arg share "{{${SERVICE_WIZARD_SHARE}}}" --arg user sc-${SPK_USER} \ '."data-share" = {"shares": [{"name": $$share, "permission":{"rw":[$$user]}} ] }' $@ | sponge $@ endif +else +ifneq ($(strip $(SERVICE_WIZARD_SHARENAME)),) +# e.g. SERVICE_WIZARD_SHARENAME=wizard_sharename, for DSM 6 and DSM 7 +ifeq ($(call version_ge, ${TCVERSION}, 6.1),1) + @jq --arg share "{{${SERVICE_WIZARD_SHARENAME}}}" --arg user sc-${SPK_USER} \ + '."data-share" = {"shares": [{"name": $$share, "permission":{"rw":[$$user]}} ] }' $@ | sponge $@ +endif +endif endif ifneq ($(strip $(SERVICE_CERT)),) ifeq ($(call version_lt, ${TCVERSION}, 7.0),1) @@ -307,9 +314,6 @@ DSM_SCRIPT_FILES += start-stop-status ifeq ($(STARTABLE),no) $(DSM_SCRIPTS_DIR)/start-stop-status: $(SPKSRC_MK)spksrc.service.non-startable @$(dsm_script_copy) -else ifneq ($(strip $(SERVICE_EXE)),) -$(DSM_SCRIPTS_DIR)/start-stop-status: $(SPKSRC_MK)spksrc.service.start-stop-daemon - @$(dsm_script_copy) else $(DSM_SCRIPTS_DIR)/start-stop-status: $(SPKSRC_MK)spksrc.service.start-stop-status @$(dsm_script_copy) @@ -348,15 +352,9 @@ endif # DSM <= 6 and SERVICE_USER defined else ifneq ($(strip $(SERVICE_USER)),) -ifeq ($(strip $(SERVICE_EXE)),) $(DSM_CONF_DIR)/privilege: $(SPKSRC_MK)spksrc.service.privilege-installasroot @$(dsm_resource_copy) @$(MSG) "(privilege) spksrc.service.privilege-installasroot" -else -$(DSM_CONF_DIR)/privilege: $(SPKSRC_MK)spksrc.service.privilege-startasroot - @$(dsm_resource_copy) - @$(MSG) "(privilege) spksrc.service.privilege-startasroot" -endif ifneq ($(strip $(SYSTEM_GROUP)),) # options: http, system @jq '."join-groupname" = "$(SYSTEM_GROUP)"' $@ | sponge $@ diff --git a/mk/spksrc.service.privilege-startasroot b/mk/spksrc.service.privilege-startasroot deleted file mode 100644 index ec7285735be..00000000000 --- a/mk/spksrc.service.privilege-startasroot +++ /dev/null @@ -1,34 +0,0 @@ -{ - "defaults":{ - "run-as": "package" - }, - "username": "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" - }, { - "action": "status", - "run-as": "root" - }] -} diff --git a/mk/spksrc.service.start-stop-daemon b/mk/spksrc.service.start-stop-daemon deleted file mode 100644 index c9d9bb2e161..00000000000 --- a/mk/spksrc.service.start-stop-daemon +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/sh - -# Default display name -DNAME="${SYNOPKG_PKGNAME}" - -if [ "$SYNOPKG_DSM_VERSION_MAJOR" -lt 7 ]; then - # define SYNOPKG_PKGVAR for forward compatibility - SYNOPKG_PKGVAR="${SYNOPKG_PKGDEST}/var" -fi - -# Source package specific variable and functions -SVC_SETUP=$(dirname $0)"/service-setup" -if [ -r "${SVC_SETUP}" ]; then - . "${SVC_SETUP}" -fi - - -# Invoke shell function if available -call_func () -{ - FUNC=$1 - if type "${FUNC}" 2>/dev/null | grep -q 'function' 2>/dev/null; then - echo "Begin ${FUNC}" >> ${LOG_FILE} - eval ${FUNC} >> ${LOG_FILE} - echo "End ${FUNC}" >> ${LOG_FILE} - fi -} - - -start_daemon () -{ - if [ -z "${SVC_QUIET}" ]; then - if [ -z "${SVC_KEEP_LOG}" ]; then - date > ${LOG_FILE} - else - date >> ${LOG_FILE} - fi - fi - call_func "service_prestart" - if [ -z "${SVC_QUIET}" ]; then - echo "Starting ${DNAME} command ${SERVICE_EXE} ${SERVICE_OPTIONS} " >> ${LOG_FILE} - fi - if [ -n "${SERVICE_EXE}" ]; then - if [ -n "${SERVICE_OPTIONS}" ]; then - start-stop-daemon -o -S -b -p ${PID_FILE} -m -c ${EFF_USER} -k 002 -x "${SERVICE_EXE}" -- ${SERVICE_OPTIONS} - else - start-stop-daemon -o -S -b -p ${PID_FILE} -m -c ${EFF_USER} -k 002 -x "${SERVICE_EXE}" - fi - fi -} - -stop_daemon () -{ - start-stop-daemon -o -K -u ${EFF_USER} -x "${SERVICE_EXE}" - wait_for_status 1 20 || start-stop-daemon -K -s 9 -q -x "${SERVICE_EXE}" - call_func "service_poststop" -} - -daemon_status () -{ - start-stop-daemon -K -q -t -u ${EFF_USER} -x "${SERVICE_EXE}" - [ $? -eq 0 ] || return 1 -} - -wait_for_status () -{ - timeout=$2 - # default value: 20 seconds - counter=${timeout:=20} - while [ ${counter} -gt 0 ]; do - daemon_status - [ $? -eq $1 ] && return - let counter=counter-1 - sleep 1 - done - return 1 -} - - -case $1 in - start) - if daemon_status; then - echo "${DNAME} is already running" >> ${LOG_FILE} - exit 0 - else - echo "Starting ${DNAME} ..." >> ${LOG_FILE} - start_daemon - exit $? - fi - ;; - stop) - if daemon_status; then - echo "Stopping ${DNAME} ..." >> ${LOG_FILE} - stop_daemon - exit $? - else - echo "${DNAME} is not running" >> ${LOG_FILE} - exit 0 - fi - ;; - status) - if daemon_status; then - echo "${DNAME} is running" - exit 0 - else - echo "${DNAME} is not running" - exit 3 - fi - ;; - log) - # log output for DSM < 6 - if [ -n "${LOG_FILE}" -a -r "${LOG_FILE}" ]; then - # Shorten long logs to last 100 lines - TEMP_LOG_FILE="${SYNOPKG_PKGVAR}/${SYNOPKG_PKGNAME}_temp.log" - # Clear any previous log - echo "Full log: ${LOG_FILE}" > "${TEMP_LOG_FILE}" - tail -n100 "${LOG_FILE}" >> "${TEMP_LOG_FILE}" - echo "${TEMP_LOG_FILE}" - fi - exit 0 - ;; - *) - exit 1 - ;; -esac diff --git a/spk/demoservice/Makefile b/spk/demoservice/Makefile index 8af51983ab6..96c5a35e995 100644 --- a/spk/demoservice/Makefile +++ b/spk/demoservice/Makefile @@ -6,9 +6,9 @@ SPK_ICON = src/demoservice.png override ARCH= MAINTAINER = ymartin59 -DESCRIPTION = Demonstration package to show installer script capabilities when requiring non-root user for background service +DESCRIPTION = Demonstration package to show installer script capabilities when requiring non-root user for background service and show configuration/creation of shared folder for package user. DISPLAY_NAME = DemoService -CHANGELOG = "1. Update with new installer logs
2. Update for DSM7" +CHANGELOG = "1. Use redesigned shared folder handling.
2. Fix service command to work on SRM and DSM 5 too.
3. User Pyhton 3 when available in path." LICENSE = GPLv2 @@ -16,17 +16,11 @@ WIZARDS_DIR = src/wizard/ # 'auto' reserved value grabs SPK_NAME SERVICE_USER = auto -SERVICE_WIZARD_GROUP = wizard_group - -include ../../mk/spksrc.common.mk -ifneq ($(call version_ge, ${TCVERSION}, 7.0),1) -# SERVICE_WIZARD_SHARE is not supported for DSM7 yet -SERVICE_WIZARD_SHARE = wizard_download_dir -endif - SERVICE_SETUP = src/service-setup.sh STARTABLE = yes +SERVICE_WIZARD_SHARENAME = wizard_shared_folder_name + # Service configuration SERVICE_PORT = 8888 SERVICE_PORT_TITLE = DemoService (HTTP) @@ -35,17 +29,24 @@ SERVICE_PORT_TITLE = DemoService (HTTP) ADMIN_PORT = $(SERVICE_PORT) PRE_COPY_TARGET = demoservice_pre_copy +POST_STRIP_TARGET = demoservice_extra_install include ../../mk/spksrc.spk.mk -.PHONY: demoservice_install +.PHONY: demoservice_pre_copy # Replace standard copy/install targets, no sources, no content demoservice_pre_copy: - @$(MSG) Create README and dummy file - rm -fr $(STAGING_DIR) - mkdir $(STAGING_DIR) - mkdir --parents $(STAGING_INSTALL_PREFIX_VAR) - echo "Test file for $(INSTALL_PREFIX_VAR)" > $(STAGING_INSTALL_PREFIX_VAR)/README + @$(MSG) Create README and dummy files + mkdir -p $(STAGING_INSTALL_PREFIX_VAR) + echo "Test file for /var/packages/$(SPK_NAME)/var/README" > $(STAGING_INSTALL_PREFIX_VAR)/README + echo "INSTALL_PREFIX_VAR = $(INSTALL_PREFIX_VAR)" >> $(STAGING_INSTALL_PREFIX_VAR)/README + echo "STAGING_INSTALL_PREFIX = $(STAGING_INSTALL_PREFIX)" >> $(STAGING_INSTALL_PREFIX_VAR)/README # create dummy file (avoid empty target folder for DSM 7 packages) mkdir -p $(STAGING_INSTALL_PREFIX) echo "dummy file (to avoid empty package)" > $(STAGING_INSTALL_PREFIX)/dummy + +.PHONY: demoservice_extra_install +demoservice_extra_install: + @$(MSG) Install service script + @install -d -m 777 $(STAGING_DIR)/bin/ + @install -m 755 src/start.sh $(STAGING_DIR)/bin/ diff --git a/spk/demoservice/src/service-setup.sh b/spk/demoservice/src/service-setup.sh index 9bc5002049d..097afa08543 100644 --- a/spk/demoservice/src/service-setup.sh +++ b/spk/demoservice/src/service-setup.sh @@ -2,19 +2,12 @@ # Package specific behaviors # Sourced script by generic installer and start-stop-status scripts -SERVER_MODULE="SimpleHTTPServer" -PYTHON_VERSION=$(python --version 2>&1) -PYTHON_MAJOR_VERSION=$(echo ${PYTHON_VERSION} | cut -d ' ' -f2 | cut -d . -f1) +# pass variables to service script by env +export PID_FILE=${PID_FILE} +export SERVICE_PORT=${SERVICE_PORT} -if [ "${PYTHON_MAJOR_VERSION}" == "3" ]; then -SERVER_MODULE="http.server" -fi - - -SERVICE_COMMAND="python -m ${SERVER_MODULE} ${SERVICE_PORT}" +SERVICE_COMMAND="${SYNOPKG_PKGDEST}/bin/start.sh" SVC_CWD="${SYNOPKG_PKGVAR}" -SVC_BACKGROUND=y -SVC_WRITE_PID=y # These functions are for demonstration purpose of DSM sequence call @@ -27,6 +20,11 @@ validate_preinst () # use install_log to write to installer log file. install_log "validate_preinst ${SYNOPKG_PKG_STATUS}" + # variables not available in preinst + install_log "Variables:" + install_log "SHARE_PATH=${SHARE_PATH}" + install_log "SHARE_NAME=${SHARE_NAME}" + # writing to stdout in dsm7 shows "installation error" without exit 1 (this looks like an error of DSM7 beta) #echo "preinst validation notification" @@ -37,6 +35,11 @@ validate_preuninst () { # use install_log to write to installer log file. install_log "validate_preuninst ${SYNOPKG_PKG_STATUS}" + + # variables not available in preinst + install_log "Variables:" + install_log "SHARE_PATH=${SHARE_PATH}" + install_log "SHARE_NAME=${SHARE_NAME}" # writing to stdout in dsm7 shows "installation error" without exit 1 (this looks like an error of DSM7 beta) #echo "preuninst validation notification" @@ -49,6 +52,10 @@ validate_preupgrade () # use install_log to write to installer log file. install_log "validate_preupgrade ${SYNOPKG_PKG_STATUS}" + install_log "Variables:" + install_log "SHARE_PATH=${SHARE_PATH}" + install_log "SHARE_NAME=${SHARE_NAME}" + # writing to stdout in dsm7 shows "installation error" without exit 1 (this looks like an error of DSM7 beta) #echo "preupgrade validation notification" @@ -60,11 +67,9 @@ service_preinst () # use echo to write to the installer log file. echo "service_preinst ${SYNOPKG_PKG_STATUS}" - echo "PYTHON_VERSION: ${PYTHON_VERSION}" - echo "PYTHON_MAJOR_VERSION: ${PYTHON_MAJOR_VERSION}" - echo "SERVER_MODULE: ${SERVER_MODULE}" - echo "SERVICE_COMMAND: ${SERVICE_COMMAND}" - echo "SYNOPKG_PKGVAR: ${SYNOPKG_PKGVAR}" + echo "Variables:" + echo "SHARE_PATH=${SHARE_PATH}" + echo "SHARE_NAME=${SHARE_NAME}" } service_postinst () @@ -72,13 +77,21 @@ service_postinst () # use echo to write to the installer log file. echo "service_postinst ${SYNOPKG_PKG_STATUS}" - ln -sf ${INST_LOG} ${SYNOPKG_PKGVAR}/installer.log + echo "Variables:" + echo "SHARE_PATH=${SHARE_PATH}" + echo "SHARE_NAME=${SHARE_NAME}" + + ln -sf ${INST_LOG} ${SYNOPKG_PKGVAR}/${SYNOPKG_PKGNAME}-installer.log } service_preuninst () { # use echo to write to the installer log file. echo "service_preuninst ${SYNOPKG_PKG_STATUS}" + + echo "Variables:" + echo "SHARE_PATH=${SHARE_PATH}" + echo "SHARE_NAME=${SHARE_NAME}" } service_postuninst () @@ -91,18 +104,41 @@ service_preupgrade () { # use echo to write to the installer log file. echo "service_preupgrade ${SYNOPKG_PKG_STATUS}" + + echo "Variables:" + echo "SHARE_PATH=${SHARE_PATH}" + echo "SHARE_NAME=${SHARE_NAME}" } service_postupgrade () { # use echo to write to the installer log file. echo "service_postupgrade ${SYNOPKG_PKG_STATUS}" + + echo "Variables:" + echo "SHARE_PATH=${SHARE_PATH}" + echo "SHARE_NAME=${SHARE_NAME}" } +# REMARKS: +# installer variables are not available in the context of service start/stop +# The regular solution is to use configuration files for services + service_prestart () { # use echo to write to the service log file. echo "service_prestart: Before service start" + + # This code shows how to load and use the function 'load_variables_from_file' + # defined in the script/functions file + INST_FUNCTIONS=$(dirname $0)"/functions" + if [ -r "${INST_FUNCTIONS}" ]; then + . "${INST_FUNCTIONS}" + load_variables_from_file ${INST_VARIABLES} + echo "Variables read from ${INST_VARIABLES}" + echo "SHARE_PATH=${SHARE_PATH}" + echo "SHARE_NAME=${SHARE_NAME}" + fi } service_poststop () diff --git a/spk/demoservice/src/start.sh b/spk/demoservice/src/start.sh new file mode 100644 index 00000000000..22a5498d101 --- /dev/null +++ b/spk/demoservice/src/start.sh @@ -0,0 +1,36 @@ +#!/bin/sh + +# we need a single file to start the service and create the pid-file +# the combination of +# - SVC_BACKGROUND=y +# - SVC_WRITE_PID=y +# does not work in DSM 5 when SERVICE_COMMAND is a commend with parameters +# On DSM 5 /bin/sh is ash and not bash and '/bin/sh -c "command parameter" &' will create a new process for "command parameter" +# finally we have two processes in the background, but are not able to retrieve the PID of "command parameter" +# + +if [ -z "${PID_FILE}" ]; then +echo "ERROR: PID_FILE is not defined. This script must be run in the context of DSM service command." +exit 1 +fi + + +PYTHON=python +if [ -n "$(which python3 2> /dev/null)" ]; then +PYTHON=python3 +fi + +PYTHON_VERSION=$(${PYTHON} --version 2>&1) +PYTHON_MAJOR_VERSION=$(echo ${PYTHON_VERSION} | cut -d ' ' -f2 | cut -d . -f1) + +SERVER_MODULE="SimpleHTTPServer" +if [ "${PYTHON_MAJOR_VERSION}" == "3" ]; then +SERVER_MODULE="http.server" +fi + +echo "current user: $(id)" +echo "python version: ${PYTHON_VERSION}" +echo "service command: ${PYTHON} -m ${SERVER_MODULE} ${SERVICE_PORT}" + +${PYTHON} -m ${SERVER_MODULE} ${SERVICE_PORT} & +echo "$!" > ${PID_FILE} diff --git a/spk/demoservice/src/wizard/install_uifile b/spk/demoservice/src/wizard/install_uifile index 83cc56f0f13..2e59d8e36e3 100644 --- a/spk/demoservice/src/wizard/install_uifile +++ b/spk/demoservice/src/wizard/install_uifile @@ -1,44 +1,31 @@ [ { - "step_title": "Example configuration for demoservice", + "step_title": "Demo Configuration", "items": [ { "type": "textfield", - "desc": "Download configuration", + "desc": "Please specify a shared folder for this package. This share is created at installation when it does not already exist.", "subitems": [ { - "key": "wizard_download_dir", - "desc": "Download location", - "defaultValue": "/volume1/downloads", + "key": "wizard_shared_folder_name", + "desc": "Shared Folder", + "defaultValue": "demo-share", "validator": { "allowBlank": false, "regex": { - "expr": "/^\\\/volume[0-9]{1,2}\\\/[^<>: */?\"]*/", - "errorText": "Path should begin with /volume?/ where ? is volume number (1-99)" - } - } - }, - { - "key": "wizard_group", - "desc": "DSM group", - "defaultValue": "sc-download", - "validator": { - "allowBlank": false, - "regex": { - "expr": "/^[^<>:*/?\"]*$/", - "errorText": "Not allowed character in group name" - } + "expr": "/^[\\w _-]+$/", + "errorText": "Subdirectories are not supported." } } } ] - } - ] - }, - { - "step_title": "Attention! DSM Permissions", - "items": [ - { - "desc": "Permissions are managed with the group 'sc-download' in DSM.
The group 'users' is no longer used as of DSM 6.
Package user (= demoservice) will not appear on most UI settings.
Including the following:
- Application privilege's permission viewer
- FPT's chroot user selector
- File Stations's
- Change owner
- Shared Links Manager -> Enable secure sharing

The only exceptions are:
- Control Panel > Shared Folder > Edit > Permission > System internal user
- ACL editor
" + }, { + "desc": "" + }, { + "desc": "If you let the installer create the shared folder, it is created under the same volume as the package is installed.
If you want to use a different volume for the share, you must create the shared folder in DSM Control Panel before, and enter the name of the existing share in the field above." + }, { + "desc": "" + }, { + "desc": "This package runs as internal service user 'sc-demoservice' in DSM. The shared folder above is configured at installation time to be accessible by this user.

Please read Permission Management for details." } ] } diff --git a/spk/demoservice/src/wizard/install_uifile_fre b/spk/demoservice/src/wizard/install_uifile_fre index 91c482281fb..c2fa9494b26 100644 --- a/spk/demoservice/src/wizard/install_uifile_fre +++ b/spk/demoservice/src/wizard/install_uifile_fre @@ -1,44 +1,31 @@ [ { - "step_title": "Exemple de configuration pour demoservice", + "step_title": "Configuration de démonstration", "items": [ { "type": "textfield", - "desc": "Configuration de téléchargement", + "desc": "Veuillez spécifier un dossier partagé pour ce package. Ce partage est créé à l'installation lorsqu'il n'existe pas déjà.", "subitems": [ { - "key": "wizard_download_dir", - "desc": "Répertoire cible", - "defaultValue": "/volume1/downloads", + "key": "wizard_shared_folder_name", + "desc": "Shared Folder", + "defaultValue": "demo-share", "validator": { "allowBlank": false, "regex": { - "expr": "/^\\\/volume[0-9]{1,2}\\\/[^<>: */?\"]*/", - "errorText": "Le chemin doit commencer par /volume?/ avec ? le numéro du volume (1-99)" - } - } - }, - { - "key": "wizard_group", - "desc": "Groupe DSM", - "defaultValue": "sc-download", - "validator": { - "allowBlank": false, - "regex": { - "expr": "/^[^<>:*/?\"]*$/", - "errorText": "Caractère non autorisé dans le nom de groupe" - } + "expr": "/^[\\w _-]+$/", + "errorText": "Les sous-répertoires ne sont pas pris en charge." } } } ] - } - ] - }, - { - "step_title": "Attention! DSM Permissions", - "items": [ - { - "desc": "Les autorisations sont gérées avec le groupe 'sc-download' dans DSM.
Le groupe 'utilisateurs' n'est plus utilisé à partir de DSM 6.
L'utilisateur du paquet (= demoservice) n'apparaîtra pas sur la plupart des paramètres d'interface utilisateur. Y compris ce qui suit:
- Afficheur d'autorisation de privilège d'application
- Sélecteur d'utilisateur chroot FPT
- File Stations's- Changer de propriétaire
- Shared Links Manager -> Activer le partage sécurisé

Les seules exceptions sont:
- Panneau de configuration> Dossier partagé> Edition> Autorisation> Utilisateur interne du système
- Éditeur ACL" + }, { + "desc": "" + }, { + "desc": "Si vous laissez le programme d'installation créer le dossier partagé, il est créé sous le même volume que celui où le package est installé.
Si vous souhaitez utiliser un volume différent pour le partage, vous devez auparavant créer le dossier partagé dans le panneau de configuration DSM et saisir le nom du partage existant dans le champ ci-dessus." + }, { + "desc": "" + }, { + "desc": "Ce package s'exécute en tant qu'utilisateur de service interne 'sc-demoservice' dans DSM. Le dossier partagé ci-dessus est configuré au moment de l'installation pour être accessible par cet utilisateur.

Veuillez lire Permission Management pour plus de détails." } ] } From be597f57f05cb7fd042c199ec1b7e3078126f9d0 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 1 Oct 2023 19:56:13 +0200 Subject: [PATCH 03/20] fix evaluation of share path - use realpath (abspath is not supported on DSM) --- mk/spksrc.service.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index 859e6739da5..c4c7668cff5 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -140,7 +140,7 @@ ifneq ($(strip $(SERVICE_WIZARD_SHARENAME)),) @echo "# DSM name of shared folder from UI if provided" >> $@ @echo 'if [ -n "$${$(SERVICE_WIZARD_SHARENAME)}" ]; then' >> $@ ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) - @echo ' SHARE_PATH=$$(abspath "/var/packages/$${SYNOPKG_PKGNAME}/shares/$${$(SERVICE_WIZARD_SHARENAME)}")' >> $@ + @echo ' SHARE_PATH=$$(realpath "/var/packages/$${SYNOPKG_PKGNAME}/shares/$${$(SERVICE_WIZARD_SHARENAME)}" 2> /dev/null)' >> $@ @echo ' install_log "SHARE_PATH from share [$${SHARE_PATH}], variable [$(SERVICE_WIZARD_SHARENAME)=$${$(SERVICE_WIZARD_SHARENAME)}]"' >> $@ else @echo ' if synoshare --get "$${$(SERVICE_WIZARD_SHARENAME)}" &> /dev/null; then ' >> $@ From 6acb1fc4ed650216d177a2ddc9fe6a48173fa06e Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 1 Oct 2023 19:57:24 +0200 Subject: [PATCH 04/20] fix DSM 7 uninstaller - use realpath to remove files in etc folder --- mk/spksrc.service.installer.dsm7 | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mk/spksrc.service.installer.dsm7 b/mk/spksrc.service.installer.dsm7 index 59cbaedc218..13a2d1310c7 100644 --- a/mk/spksrc.service.installer.dsm7 +++ b/mk/spksrc.service.installer.dsm7 @@ -193,8 +193,9 @@ postuninst () find ${SYNOPKG_PKGVAR} -mindepth 1 -delete -print | install_log fi - if [ "$(ls -A /var/packages/${SYNOPKG_PKGNAME}/etc)" != "" ]; then - find /var/packages/${SYNOPKG_PKGNAME}/etc -mindepth 1 -delete -print | install_log + _etc_path=$(realpath /var/packages/${SYNOPKG_PKGNAME}/etc) + if [ "$(ls -A ${_etc_path})" != "" ]; then + find ${_etc_path} -mindepth 1 -delete -print | install_log fi fi From bcee4cb3454903ebc7ddd4e04bd9e12aae92d9d8 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 1 Oct 2023 20:08:13 +0200 Subject: [PATCH 05/20] typo --- spk/demoservice/Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spk/demoservice/Makefile b/spk/demoservice/Makefile index 96c5a35e995..8e949301a84 100644 --- a/spk/demoservice/Makefile +++ b/spk/demoservice/Makefile @@ -8,7 +8,7 @@ override ARCH= MAINTAINER = ymartin59 DESCRIPTION = Demonstration package to show installer script capabilities when requiring non-root user for background service and show configuration/creation of shared folder for package user. DISPLAY_NAME = DemoService -CHANGELOG = "1. Use redesigned shared folder handling.
2. Fix service command to work on SRM and DSM 5 too.
3. User Pyhton 3 when available in path." +CHANGELOG = "1. Use redesigned shared folder handling.
2. Fix service command to work on SRM and DSM 5 too.
3. Use Pyhton 3 when available in path." LICENSE = GPLv2 From bd975098e08f77b3234b87473f311ce1a61035da Mon Sep 17 00:00:00 2001 From: hgy59 Date: Mon, 2 Oct 2023 00:31:34 +0200 Subject: [PATCH 06/20] cleanups - avoid reading already defined variables from installer-variables - save wizard variables after creation of shared folders (SHARE_PATH is set to full path at creation time) (DSM 6, DSM 5) --- mk/spksrc.service.installer.dsm5 | 23 ++++++++++++++--------- mk/spksrc.service.installer.dsm6 | 22 +++++++++++++--------- mk/spksrc.service.installer.functions | 6 +++++- 3 files changed, 32 insertions(+), 19 deletions(-) diff --git a/mk/spksrc.service.installer.dsm5 b/mk/spksrc.service.installer.dsm5 index b9455b5d979..c1237145267 100644 --- a/mk/spksrc.service.installer.dsm5 +++ b/mk/spksrc.service.installer.dsm5 @@ -218,12 +218,6 @@ preinst () postinst () { log_step "postinst" - call_func "save_wizard_variables" - # Restrict permissions to protect sensitive options - if [ -e "${INST_VARIABLES}" ]; then - chmod go-rwx ${INST_VARIABLES} - chown ${EFF_USER} ${INST_VARIABLES} - fi # Link for backward compatibility of binaries location $LN "${SYNOPKG_PKGDEST}" "/usr/local/${SYNOPKG_PKGNAME}" 2>&1 | install_log @@ -270,15 +264,17 @@ postinst () # Share management if [ -n "${SHARE_PATH}" ]; then - install_log "Configuring share SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}, EFF_USER=${EFF_USER}, GROUP=${GROUP}" # Create share if it does not exist # invalid characters: !"#$%&’()*+,/:;<=>?@[]nˆ`{} | if ! synoshare --get "${SHARE_NAME}" > /dev/null 2>&1; then - install_log "Create share SHARE_NAME=${SHARE_NAME}, SYNOPKG_PKGDEST_VOL=${SYNOPKG_PKGDEST_VOL}" + SHARE_PATH=${SYNOPKG_PKGDEST_VOL}/${SHARE_NAME} + install_log "Create share SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}" # mandatory arguments: # name desc path na rw ro browseable adv_privilege # na, rw and ro are list of user(s) and/or group(s), separated by comma synoshare --add "${SHARE_NAME}" "Share created for package ${SYNOPKG_PKGNAME}" "${SHARE_PATH}" "" "" "" 1 0 2>&1 | install_log + else + install_log "Share already exists SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}" fi # Add user permission if no GROUP is set in service-setup @@ -290,7 +286,7 @@ postinst () _synoshare_parameter="--list_acl" fi if synoshare ${_synoshare_parameter} "${SHARE_NAME}" | grep "RW list" | grep -o "\[.*\]" | sed 's/[\[,]/ /g' | sed 's/\]/ /g' | grep -q " ${EFF_USER} " > /dev/null 2>&1; then - install_log "User has already RW access to share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}, _synoshare_parameter=${_synoshare_parameter}" + install_log "User has already RW access to share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}" else install_log "Set user for share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}" synoshare --setuser "${SHARE_NAME}" RW + "${EFF_USER}" 2>&1 | install_log @@ -307,10 +303,19 @@ postinst () $MKDIR "${SYNOPKG_PKGVAR}" 2>&1 | install_log + call_func "save_wizard_variables" + # Restrict permissions to protect sensitive options + if [ -e "${INST_VARIABLES}" ]; then + chmod go-rwx ${INST_VARIABLES} + chown ${EFF_USER} ${INST_VARIABLES} + fi + call_func "service_postinst" + call_func "service_create_links" $CP "${INST_LOG_TEMP}" "${INST_LOG}" 2>&1 | install_log + if [ -n "${LOG_FILE}" ]; then echo "Installation log: ${INST_LOG}" >> ${LOG_FILE} fi diff --git a/mk/spksrc.service.installer.dsm6 b/mk/spksrc.service.installer.dsm6 index 5afcd2ef279..e92dee34c01 100644 --- a/mk/spksrc.service.installer.dsm6 +++ b/mk/spksrc.service.installer.dsm6 @@ -230,12 +230,6 @@ preinst () postinst () { log_step "postinst" - call_func "save_wizard_variables" install_log - # Restrict permissions to protect sensitive options - if [ -e "${INST_VARIABLES}" ]; then - chmod go-rwx ${INST_VARIABLES} - chown ${EFF_USER} ${INST_VARIABLES} - fi # Link for backward compatibility of binaries location $LN "${SYNOPKG_PKGDEST}" "/usr/local/${SYNOPKG_PKGNAME}" 2>&1 | install_log @@ -270,15 +264,17 @@ postinst () if [ "${SHARE_WORKER}" != "0" ]; then install_log "Shared folder [${SHARE_PATH}] will be created by DSM." else - install_log "Configuring share SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}, EFF_USER=${EFF_USER}, GROUP=${GROUP}" # Create share if it does not exist # invalid characters: !"#$%&’()*+,/:;<=>?@[]nˆ`{} | if ! synoshare --get "${SHARE_NAME}" > /dev/null 2>&1; then - install_log "Create share SHARE_NAME=${SHARE_NAME}, SYNOPKG_PKGDEST_VOL=${SYNOPKG_PKGDEST_VOL}" + SHARE_PATH=${SYNOPKG_PKGDEST_VOL}/${SHARE_NAME} + install_log "Create share SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}" # mandatory arguments: # name desc path na rw ro browseable adv_privilege # na, rw and ro are list of user(s) and/or group(s), separated by comma synoshare --add "${SHARE_NAME}" "Share created for package ${SYNOPKG_PKGNAME}" "${SHARE_PATH}" "" "" "" 1 0 2>&1 | install_log + else + install_log "Share already exists SHARE_NAME=${SHARE_NAME}, SHARE_PATH=${SHARE_PATH}" fi # Add user permission if no GROUP is set in service-setup @@ -290,7 +286,7 @@ postinst () _synoshare_parameter="--list_acl" fi if synoshare ${_synoshare_parameter} "${SHARE_NAME}" | grep "RW list" | grep -o "\[.*\]" | sed 's/[\[,]/ /g' | sed 's/\]/ /g' | grep -q " ${EFF_USER} " > /dev/null 2>&1; then - install_log "User has already RW access to share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}, _synoshare_parameter=${_synoshare_parameter}" + install_log "User has already RW access to share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}" else install_log "Set user for share: SHARE_NAME=${SHARE_NAME}, EFF_USER=${EFF_USER}" synoshare --setuser "${SHARE_NAME}" RW + "${EFF_USER}" 2>&1 | install_log @@ -308,7 +304,15 @@ postinst () $MKDIR "${SYNOPKG_PKGVAR}" 2>&1 | install_log + call_func "save_wizard_variables" install_log + # Restrict permissions to protect sensitive options + if [ -e "${INST_VARIABLES}" ]; then + chmod go-rwx ${INST_VARIABLES} + chown ${EFF_USER} ${INST_VARIABLES} + fi + call_func "service_postinst" install_log + call_func "service_clean_tmpdir" install_log if [ -n "${LOG_FILE}" ]; then diff --git a/mk/spksrc.service.installer.functions b/mk/spksrc.service.installer.functions index 8ff699e014b..4e7890b118f 100644 --- a/mk/spksrc.service.installer.functions +++ b/mk/spksrc.service.installer.functions @@ -115,6 +115,7 @@ install_python_wheels () # function to read and export variables from a text file # empty lines and lines starting with # are ignored # we cannot 'source' the file to load the variables, when values have special characters like <, >, ... +# already defined variables are not taken from the file (e.g. variables from wizard) load_variables_from_file () { if [ -n "$1" -a -r "$1" ]; then @@ -122,7 +123,10 @@ load_variables_from_file () if [ "$(echo ${_line} | grep -v ^[/s]*#)" != "" ]; then _key=${_line%%=*} _value=${_line#*=} - export "${_key}=${_value}" + _existing_value=$(eval echo "\$${_key}") + if [ -z "${_existing_value}" ]; then + export "${_key}=${_value}" + fi fi done < "$1" fi From b6acead845d51f43640a3fecbe597fb79a375d9b Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sat, 7 Oct 2023 19:06:05 +0200 Subject: [PATCH 07/20] fix icon creation for web service packages - create icons when DSM_UI_CONFIG is defined --- mk/spksrc.icon.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mk/spksrc.icon.mk b/mk/spksrc.icon.mk index 3cdbb6546fd..9421a8fe6c2 100644 --- a/mk/spksrc.icon.mk +++ b/mk/spksrc.icon.mk @@ -14,7 +14,7 @@ ICON_COOKIE = $(WORK_DIR)/.$(COOKIE_PREFIX)icon_done # Icons are only needed with "DSM UI configuration" (see spksrc.service.mk) ifneq ($(strip $(DSM_UI_DIR)),) ifneq ($(strip $(SPK_ICON)),) -ifneq ($(or $(strip $(SERVICE_PORT)), $(strip $(ADMIN_URL))),) +ifneq ($(or $(strip $(SERVICE_PORT)), $(strip $(ADMIN_URL)), $(strip $(DSM_UI_CONFIG))),) ifeq ($(strip $(NO_SERVICE_SHORTCUT)),) ICON_DIR = $(STAGING_DIR)/$(DSM_UI_DIR)/images endif From 2761973662b9ec94705436b6c59644018e764e4a Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sat, 7 Oct 2023 21:48:45 +0200 Subject: [PATCH 08/20] add demowebservice package - add demowebservice to document how to create a web service apps - use shared folder to show how to access shared folders by the package --- spk/demowebservice/Makefile | 72 +++++++ spk/demowebservice/src/app/config | 15 ++ spk/demowebservice/src/conf_php7.4/resource | 44 ++++ spk/demowebservice/src/conf_php8.2/resource | 44 ++++ spk/demowebservice/src/demowebservice.png | Bin 0 -> 61798 bytes spk/demowebservice/src/service-setup.sh | 41 ++++ spk/demowebservice/src/web/index.php | 199 +++++++++++++++++++ spk/demowebservice/src/wizard/install_uifile | 29 +++ 8 files changed, 444 insertions(+) create mode 100644 spk/demowebservice/Makefile create mode 100644 spk/demowebservice/src/app/config create mode 100644 spk/demowebservice/src/conf_php7.4/resource create mode 100644 spk/demowebservice/src/conf_php8.2/resource create mode 100644 spk/demowebservice/src/demowebservice.png create mode 100644 spk/demowebservice/src/service-setup.sh create mode 100644 spk/demowebservice/src/web/index.php create mode 100644 spk/demowebservice/src/wizard/install_uifile diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile new file mode 100644 index 00000000000..e0ec3d7a762 --- /dev/null +++ b/spk/demowebservice/Makefile @@ -0,0 +1,72 @@ +SPK_NAME = demowebservice +SPK_VERS = 1.0 +SPK_REV = 1 +SPK_ICON = src/demowebservice.png + +DEPENDS = + +# for PHP >= 7.4 +REQUIRED_MIN_DSM = 6.1 + +override ARCH= + +MAINTAINER = hgy59 +DESCRIPTION = Demopackage to show how to create a web service \(web application\) +DISPLAY_NAME = Demo Web Service +CHANGELOG = "Initial package release" + +LICENSE = MIT + +include ../../mk/spksrc.common.mk +# use the latest php version +ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) +PHP_VERSION = 8.2 +else +PHP_VERSION = 7.4 +endif + +SPK_DEPENDS = WebStation:PHP$(PHP_VERSION):Apache2.4 + + +WIZARDS_DIR = src/wizard/ +SERVICE_WIZARD_SHARENAME = wizard_shared_folder_name + +SYSTEM_GROUP = http + +DSM_UI_DIR = app +DSM_UI_CONFIG = src/app/config + +CONF_DIR = src/conf_php$(PHP_VERSION)/ + +INSTALL_DEP_SERVICES = apache-web +START_DEP_SERVICES = apache-web +INSTUNINST_RESTART_SERVICES = apache-web + +SERVICE_USER = auto +SERVICE_SETUP = src/service-setup.sh +STARTABLE = no + +COPY_TARGET = demowebservice_install +POST_ICON_TARGET = demowebservice_post_icon + +include ../../mk/spksrc.spk.mk + +.PHONY: demowebservice_install +# the app/config file must be installed from DSM 6 to show the icon in DSM +# but it must not exist in DSM 7 packages (System error. Unable to perform this operation. Please try again later.) +demowebservice_install: + @$(MSG) Install app/config and web application +ifeq ($(call version_lt, ${TCVERSION}, 7.0),1) + @install -d -m 755 $(STAGING_DIR)/app/ + @install -m 644 $(DSM_UI_CONFIG) $(STAGING_DIR)/app/config +endif + @install -d -m 755 $(STAGING_DIR)/web/$(SPK_NAME)/ + @install -m 755 src/web/index.php $(STAGING_DIR)/web/$(SPK_NAME)/ + +.PHONY: demowebservice_post_icon +demowebservice_post_icon: + @$(MSG) Add some package icons to the web application + @install -d -m 755 $(STAGING_DIR)/web/$(SPK_NAME)/images + @for image_size in 16 32; do \ + install -m 755 $(STAGING_DIR)/app/images/$(SPK_NAME)-$${image_size}.png $(STAGING_DIR)/web/$(SPK_NAME)/images/ ; \ + done diff --git a/spk/demowebservice/src/app/config b/spk/demowebservice/src/app/config new file mode 100644 index 00000000000..339050a7f3e --- /dev/null +++ b/spk/demowebservice/src/app/config @@ -0,0 +1,15 @@ +{ + ".url": { + "com.synocommunity.packages.demowebservice": { + "type": "url", + "title": "Demo Web Service", + "desc": "Web Service Demo Package", + "icon": "images/demowebservice-{0}.png", + "protocol": "http", + "port": "80", + "url": "/demowebservice", + "allUsers": true, + "grantPrivilege": "local" + } + } +} diff --git a/spk/demowebservice/src/conf_php7.4/resource b/spk/demowebservice/src/conf_php7.4/resource new file mode 100644 index 00000000000..d382faf39fc --- /dev/null +++ b/spk/demowebservice/src/conf_php7.4/resource @@ -0,0 +1,44 @@ +{ + "data-share": { + "shares": [ + { + "name": "{{wizard_shared_folder_name}}", + "permission":{"rw":["sc-demowebservice"]} + } + ] + }, + "webservice": { + "services": [{ + "service": "demowebservice", + "display_name": "Demo Web Service", + "icon": "app/images/demowebservice-{0}.png", + "type": "apache_php", + "root": "demowebservice", + "backend": 2, + "php": { + "profile_name": "Web Service Demo Profile", + "profile_desc": "PHP 7.4 profile for Demo Web Service", + "backend": 8, + "extensions": [], + "user": "sc-demowebservice", + "group": "http" + } + }], + "portals": [ + { + "service": "demowebservice", + "type": "alias", + "name": "Demo Web Service", + "alias": "demowebservice", + "app": "com.synocommunity.packages.demowebservice" + } + ], + "pkg_dir_prepare": [{ + "source": "/var/packages/demowebservice/target/web/demowebservice", + "target": "demowebservice", + "mode": "0755", + "user": "sc-demowebservice", + "group": "http" + }] + } +} \ No newline at end of file diff --git a/spk/demowebservice/src/conf_php8.2/resource b/spk/demowebservice/src/conf_php8.2/resource new file mode 100644 index 00000000000..7c11add5336 --- /dev/null +++ b/spk/demowebservice/src/conf_php8.2/resource @@ -0,0 +1,44 @@ +{ + "data-share": { + "shares": [ + { + "name": "{{wizard_shared_folder_name}}", + "permission":{"rw":["sc-demowebservice"]} + } + ] + }, + "webservice": { + "services": [{ + "service": "demowebservice", + "display_name": "Demo Web Service", + "icon": "app/images/demowebservice-{0}.png", + "type": "apache_php", + "root": "demowebservice", + "backend": 2, + "php": { + "profile_name": "Web Service Demo Profile", + "profile_desc": "PHP 8.2 profile for Demo Web Service", + "backend": 11, + "extensions": [], + "user": "sc-demowebservice", + "group": "http" + } + }], + "portals": [ + { + "service": "demowebservice", + "type": "alias", + "name": "Demo Web Service", + "alias": "demowebservice", + "app": "com.synocommunity.packages.demowebservice" + } + ], + "pkg_dir_prepare": [{ + "source": "/var/packages/demowebservice/target/web/demowebservice", + "target": "demowebservice", + "mode": "0755", + "user": "sc-demowebservice", + "group": "http" + }] + } +} \ No newline at end of file diff --git a/spk/demowebservice/src/demowebservice.png b/spk/demowebservice/src/demowebservice.png new file mode 100644 index 0000000000000000000000000000000000000000..3053dc7835d029997af1f69a49e8369b60592b2c GIT binary patch literal 61798 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
&C_^bf#+k=R=}csrlA?*pZ0(fi@)rG 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 literal 0 HcmV?d00001 diff --git a/spk/demowebservice/src/service-setup.sh b/spk/demowebservice/src/service-setup.sh new file mode 100644 index 00000000000..f63f8b6ae5b --- /dev/null +++ b/spk/demowebservice/src/service-setup.sh @@ -0,0 +1,41 @@ + +if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then +WEB_ROOT=/var/services/web +GROUP=http +else +WEB_ROOT=/var/services/web_packages +fi + +WEB_DIR=${WEB_ROOT}/${SYNOPKG_PKGNAME} + +service_postinst () +{ + echo "Install the web app (${WEB_DIR})" + + if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then + # Install the web interface + cp -rp "${SYNOPKG_PKGDEST}/web/${SYNOPKG_PKGNAME}" ${WEB_ROOT}/ + fi + + if [ -d "${WEB_DIR}" ]; then + if [ -n "${SHARE_NAME}" ]; then + sed -e "s|@@shared_folder_name@@|${SHARE_NAME}|g" \ + -e "s|@@shared_folder_fullname@@|${SHARE_PATH}|g" \ + -i ${WEB_DIR}/index.php + else + echo "ERROR: SHARE_PATH is not defined" + fi + else + echo "ERROR: ${WEB_DIR} does not exist" + fi +} + +service_postuninst () +{ + if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then + if [ -d "${WEB_DIR}" ]; then + echo "Remove the web app (${WEB_DIR})" + rm -rf ${WEB_DIR} + fi + fi +} diff --git a/spk/demowebservice/src/web/index.php b/spk/demowebservice/src/web/index.php new file mode 100644 index 00000000000..ed365be0634 --- /dev/null +++ b/spk/demowebservice/src/web/index.php @@ -0,0 +1,199 @@ + + + + + + + + + + + + +Demo Web Service + + + + + + +

+ +

Demo Web Service

+

This is a demo web service package for synology DSM.

+

It demonstrates how to build a package to integrate with apache web server and PHP .

+For PHP less than 8.x
phpinfo()
does not support dark mode, so we use light mode for all pages.

"; +} +?> +

It also shows how to configure and use a shared folder.

+ +

The source code is located in the SynoCommunity respository under demowebservice.

+ +

List of server variables:

+

 $key_value) { print $key_name . " = " . $key_value . "
"; } ?>

+
+ +
+ +
+ +
+

Shared Folder

+

Name of the shared folder is @@shared_folder_name@@.

+

Full path of the shared folder is @@shared_folder_fullname@@.

+ +

The following list of files and folders shows that the package has access to this folder.

+

Content of the shared folder:

+

ERROR: ".$e->getMessage()."

"; + } + } + else { + echo "

The folder '".$path."' does not exist.

"; + } + + function add_list_element($dom, $parent, $fileinfo) + { + $info = $fileinfo->getFilename(); + if ( !$fileinfo->isDir() ) { + $info .= " size=[".$fileinfo->getSize()." bytes]"; + } + $info .= " access=["; + if ($fileinfo->isReadable()) { $info .= "r"; } else { $info .= "-"; } + if ($fileinfo->isWritable()) { $info .= "w"; } else { $info .= "-"; } + if ($fileinfo->isExecutable()) { $info .= "x"; } else { $info .= "-"; } + $info .= "]"; + $info .= " owner=[".$fileinfo->getOwner().":".$fileinfo->getGroup()."]" ; + + $li = $dom->createElement('li', $info); + $parent->appendChild($li); + } + + // print directory tree based on the accepted answer in: + // https://stackoverflow.com/questions/10779546/recursiveiteratoriterator-and-recursivedirectoryiterator-to-nested-html-lists + function print_folder($path) + { + $directoryIteratorFlags = FilesystemIterator::KEY_AS_PATHNAME|FilesystemIterator::CURRENT_AS_FILEINFO|FilesystemIterator::SKIP_DOTS; + $objects = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, $directoryIteratorFlags), RecursiveIteratorIterator::SELF_FIRST); + $dom = new DomDocument("1.0"); + $list = $dom->createElement("ul"); + $dom->appendChild($list); + $node = $list; + $depth = 0; + foreach ($objects as $name => $object) { + if ($objects->getDepth() == $depth) { + // the depth hasn't changed so just add another li + add_list_element($dom,$node,$object); + } + elseif ($objects->getDepth() > $depth) { + // the depth increased, the last li is a non-empty folder + $li = $node->lastChild; + $ul = $dom->createElement('ul'); + $li->appendChild($ul); + add_list_element($dom,$ul,$object); + $node = $ul; + } + else { + // the depth decreased, going up $difference directories + $difference = $depth - $objects->getDepth(); + for ($i = 0; $i < $difference; $difference--) { + $node = $node->parentNode->parentNode; + } + add_list_element($dom,$node,$object); + } + $depth = $objects->getDepth(); + } + echo $dom->saveHtml(); + } +?> +
+ + + + + + + diff --git a/spk/demowebservice/src/wizard/install_uifile b/spk/demowebservice/src/wizard/install_uifile new file mode 100644 index 00000000000..13ca10588dc --- /dev/null +++ b/spk/demowebservice/src/wizard/install_uifile @@ -0,0 +1,29 @@ +[ + { + "step_title": "Demo Web Service Configuration", + "items": [ + { + "type": "textfield", + "desc": "Please specify a shared folder for this package. This share is created at installation when it does not already exist.", + "subitems": [ + { + "key": "wizard_shared_folder_name", + "desc": "Shared Folder", + "defaultValue": "demo-share", + "validator": { + "allowBlank": false, + "regex": { + "expr": "/^[\\w _-]+$/", + "errorText": "Subdirectories are not supported." + } + } + } + ] + }, { + "desc": "" + }, { + "desc": "This package runs as internal service user 'sc-demowebservice' in DSM. The shared folder above is configured at installation time to be accessible by this user.

Please read Permission Management for details." + } + ] + } +] \ No newline at end of file From 2ac1e3e5f8c4925746007ac843f1f8461a4fc1f5 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 8 Oct 2023 00:11:45 +0200 Subject: [PATCH 09/20] update demowebserice - support DSM 5 - fully support dark mode --- mk/spksrc.service.installer.dsm5 | 8 ++-- mk/spksrc.service.installer.dsm6 | 10 ++-- spk/demowebservice/Makefile | 6 +-- spk/demowebservice/src/web/index.php | 68 +++++++++++++++++++++------- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/mk/spksrc.service.installer.dsm5 b/mk/spksrc.service.installer.dsm5 index c1237145267..b3509fb8eff 100644 --- a/mk/spksrc.service.installer.dsm5 +++ b/mk/spksrc.service.installer.dsm5 @@ -140,9 +140,9 @@ set_syno_permissions () # Ensure directory resides in /volumeX before setting GROUP permissions if [ "$(echo ${VOLUME} | cut -c2-7)" = "volume" ]; then # Set read/write permissions for GROUP for folder and subfolders - if [ ! "$(synoacltool -get \"${DIRNAME}\"| grep \"group:${GROUP}:allow:rwxpdDaARWcC-:fd--\")" ]; then + if [ ! "$(synoacltool -get ${DIRNAME} | grep ""group:${GROUP}:allow:rwxpdDaARWcC-:fd--"")" ]; then # First Unix permissions, but only if it's in Linux mode - if [ "$(synoacltool -get \"${DIRNAME}\"| grep \"Linux mode\")" ]; then + if [ "$(synoacltool -get ${DIRNAME} | grep -i 'Linux mode')" ]; then set_unix_permissions "${DIRNAME}" # If it is linux mode (due to old package) we need to add "administrators"-group, # otherwise the folder is not accessible from File Station anymore! @@ -157,9 +157,9 @@ set_syno_permissions () # Walk up the tree and set traverse execute permissions for GROUP up to VOLUME while [ "${DIRNAME}" != "${VOLUME}" ]; do - if [ ! "$(synoacltool -get \"${DIRNAME}\"| grep \"group:${GROUP}:allow:r.x\")" ]; then + if [ ! "$(synoacltool -get ""${DIRNAME}"" | grep ""group:${GROUP}:allow:r.x"")" ]; then # Here we also need to make sure the admin can access data via File Station - if [ "$(synoacltool -get \"${DIRNAME}\"| grep \"Linux mode\")" ]; then + if [ "$(synoacltool -get ""${DIRNAME}"" | grep -i 'Linux mode')" ]; then synoacltool -add "${DIRNAME}" "group:administrators:allow:rwxpdDaARWc--:fd--" fi # Add the new group permissions diff --git a/mk/spksrc.service.installer.dsm6 b/mk/spksrc.service.installer.dsm6 index e92dee34c01..d6a53eddf27 100644 --- a/mk/spksrc.service.installer.dsm6 +++ b/mk/spksrc.service.installer.dsm6 @@ -148,9 +148,9 @@ set_syno_permissions () # Ensure directory resides in /volumeX before setting GROUP permissions if [ "$(echo ${VOLUME} | cut -c2-7)" = "volume" ]; then # Set read/write permissions for GROUP for folder and subfolders - if [ ! "$(synoacltool -get \"${DIRNAME}\"| grep \"group:${GROUP}:allow:rwxpdDaARWcC-:fd--\")" ]; then + if [ ! "$(synoacltool -get ${DIRNAME} | grep ""group:${GROUP}:allow:rwxpdDaARWcC-:fd--"")" ]; then # First Unix permissions, but only if it's in Linux mode - if [ "$(synoacltool -get \"${DIRNAME}\"| grep \"Linux mode\")" ]; then + if [ "$(synoacltool -get ${DIRNAME} | grep -i 'Linux mode')" ]; then set_unix_permissions "${DIRNAME}" # If it is linux mode (due to old package) we need to add "administrators"-group, # otherwise the folder is not accessible from File Station anymore! @@ -165,9 +165,9 @@ set_syno_permissions () # Walk up the tree and set traverse execute permissions for GROUP up to VOLUME while [ "${DIRNAME}" != "${VOLUME}" ]; do - if [ ! "$(synoacltool -get \"${DIRNAME}\"| grep \"group:${GROUP}:allow:r.x\")" ]; then + if [ ! "$(synoacltool -get ""${DIRNAME}"" | grep ""group:${GROUP}:allow:r.x"")" ]; then # Here we also need to make sure the admin can access data via File Station - if [ "$(synoacltool -get \"${DIRNAME}\"| grep \"Linux mode\")" ]; then + if [ "$(synoacltool -get ""${DIRNAME}"" | grep -i 'Linux mode')" ]; then synoacltool -add "${DIRNAME}" "group:administrators:allow:rwxpdDaARWc--:fd--" fi # Add the new group permissions @@ -312,7 +312,7 @@ postinst () fi call_func "service_postinst" install_log - + call_func "service_clean_tmpdir" install_log if [ -n "${LOG_FILE}" ]; then diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile index e0ec3d7a762..3a17e0d86ea 100644 --- a/spk/demowebservice/Makefile +++ b/spk/demowebservice/Makefile @@ -5,9 +5,6 @@ SPK_ICON = src/demowebservice.png DEPENDS = -# for PHP >= 7.4 -REQUIRED_MIN_DSM = 6.1 - override ARCH= MAINTAINER = hgy59 @@ -25,8 +22,9 @@ else PHP_VERSION = 7.4 endif +ifeq ($(call version_ge, ${TCVERSION}, 6.0),1) SPK_DEPENDS = WebStation:PHP$(PHP_VERSION):Apache2.4 - +endif WIZARDS_DIR = src/wizard/ SERVICE_WIZARD_SHARENAME = wizard_shared_folder_name diff --git a/spk/demowebservice/src/web/index.php b/spk/demowebservice/src/web/index.php index ed365be0634..d3b0c01a5ae 100644 --- a/spk/demowebservice/src/web/index.php +++ b/spk/demowebservice/src/web/index.php @@ -19,7 +19,13 @@ /* Style tab links */ .tablink { - background-color: #44A; + color: black; float: left; border: none; @@ -31,7 +37,13 @@ } .tablink:hover { - background-color: #66F; + } /* Style the tab content (and add height:100% for full page content) */ @@ -78,17 +90,19 @@

Demo Web Service

This is a demo web service package for synology DSM.

It demonstrates how to build a package to integrate with apache web server and PHP .

-For PHP less than 8.x
phpinfo()
does not support dark mode, so we use light mode for all pages.

"; -} -?>

It also shows how to configure and use a shared folder.

The source code is located in the SynoCommunity respository under demowebservice.

List of server variables:

 $key_value) { print $key_name . " = " . $key_value . "
"; } ?>

+ +

on PHP less than 8.x phpinfo() does not support dark mode and we use light mode for all pages.


"; +} +?> +
@@ -101,20 +115,33 @@

Full path of the shared folder is @@shared_folder_fullname@@.

The following list of files and folders shows that the package has access to this folder.

+

Content of the shared folder:

ERROR: open_basedir restriction in effect. Path (".$path.") is not within the allowed path(s).

"; + echo "

open_basedir=".ini_get('open_basedir')."

"; + echo "

You have to add ".$path." to the php open_basedir to list the folder content.

"; + } + else + { + if (file_exists($path)) { + try { + print_folder($path); + } + catch (Exception $e) { + echo "

ERROR: ".$e->getMessage()."

"; + } } - catch (Exception $e) { - echo "

ERROR: ".$e->getMessage()."

"; + else { + echo "

The folder '".$path."' does not exist.

"; } } - else { - echo "

The folder '".$path."' does not exist.

"; - } function add_list_element($dom, $parent, $fileinfo) { @@ -186,8 +213,15 @@ function openPage(pageName,activeTabLink) { tablinks[i].style.backgroundColor = ""; } document.getElementById(pageName).style.display = "block"; - activeTabLink.style.color = 'black'; - activeTabLink.style.backgroundColor = 'white'; + } // select the element with id="defaultOpen" From 145a1d847e8ca7b4a8ef63583619f71c6456b299 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 8 Oct 2023 01:16:32 +0200 Subject: [PATCH 10/20] fix spksrc.service.mk for packages without shared folders - fix service-setup file generation (orphaned if) --- mk/spksrc.service.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index c4c7668cff5..7ce121f586e 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -152,8 +152,8 @@ else @echo ' fi' >> $@ endif @echo ' SHARE_NAME="$${$(SERVICE_WIZARD_SHARENAME)}"' >> $@ -endif @echo 'fi' >> $@ +endif @echo '' >> $@ endif ifneq ($(strip $(SERVICE_PORT)),) From 002f8e328e2f34f1a381cd20a1b53a32956a6a08 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 8 Oct 2023 01:21:06 +0200 Subject: [PATCH 11/20] update cops - remove workaround to create package icons --- cross/cops/Makefile | 2 +- spk/cops/Makefile | 10 ++----- spk/cops/src/service-setup.sh | 56 +++++++++++++++++------------------ 3 files changed, 31 insertions(+), 37 deletions(-) diff --git a/cross/cops/Makefile b/cross/cops/Makefile index bbbf34de200..2b0db810421 100644 --- a/cross/cops/Makefile +++ b/cross/cops/Makefile @@ -10,7 +10,7 @@ DEPENDS = HOMEPAGE = https://blog.slucas.fr/en/oss/calibre-opds-php-server COMMENT = Calibre OPDS and HTML PHP Server : light alternative to Calibre content server / Calibre2OPDS. -LICENSE = GPL +LICENSE = GPLv2 INSTALL_TARGET = cops_install diff --git a/spk/cops/Makefile b/spk/cops/Makefile index eeaa6c806d2..681d4fd1114 100644 --- a/spk/cops/Makefile +++ b/spk/cops/Makefile @@ -4,7 +4,7 @@ SPK_REV = 7 SPK_ICON = src/cops.png DSM_UI_DIR = app -DEPENDS = cross/$(SPK_NAME) +DEPENDS = cross/cops MAINTAINER = Diaoul DESCRIPTION = Calibre OPDS and HTML PHP Server : light alternative to Calibre content server / Calibre2OPDS. @@ -14,14 +14,13 @@ DISPLAY_NAME = COPS CHANGELOG = "1. Update to 1.4.2
2. Upgrade PHP 7.4
3. Add .htaccess migration support" include ../../mk/spksrc.common.mk -include ../../mk/spksrc.directories.mk SPK_DEPENDS=WebStation:PHP7.4:Apache2.4 SYSTEM_GROUP = http -HOMEPAGE = https://blog.slucas.fr/en/oss/calibre-opds-php-server -LICENSE = GPL +HOMEPAGE = https://blog.slucas.fr/en/oss/calibre-opds-php-server +LICENSE = GPLv2 REQUIRED_MIN_DSM = 6.0 @@ -39,8 +38,6 @@ POST_STRIP_TARGET = cops_extra_install # Pure PHP package, make sure ARCH is not defined override ARCH= -ICON_DIR = $(STAGING_DIR)/$(DSM_UI_DIR)/images - include ../../mk/spksrc.spk.mk .PHONY: cops_extra_install @@ -48,4 +45,3 @@ cops_extra_install: install -m 755 -d $(STAGING_DIR)/app install -m 644 src/app/config $(STAGING_DIR)/app/config install -m 666 src/config_local.php.synology $(STAGING_DIR)/config_local.php.synology - diff --git a/spk/cops/src/service-setup.sh b/spk/cops/src/service-setup.sh index 0e9a8a9d534..e948c155942 100644 --- a/spk/cops/src/service-setup.sh +++ b/spk/cops/src/service-setup.sh @@ -1,4 +1,3 @@ -#!/bin/sh # Package PACKAGE_NAME="com.synocommunity.packages.${SYNOPKG_PKGNAME}" @@ -33,24 +32,24 @@ validate_preinst () service_postinst () { - if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then - + if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then + # Install the web interface cp -pR "${SYNOPKG_PKGDEST}/share/${SYNOPKG_PKGNAME}" "${WEB_DIR}" - + # Configure open_basedir echo -e "[PATH=${WEB_DIR}/${SYNOPKG_PKGNAME}]\nopen_basedir = Null" > "${PHP_CONFIG_LOCATION}/${PACKAGE_NAME}.ini" - fi + fi if [ "${SYNOPKG_PKG_STATUS}" == "INSTALL" ]; then # Create a default configuration file if [ ! -f "${CFG_FILE}" ]; then - cp "${DEFAULT_CFG_FILE}" "${CFG_FILE}" - url_rewriting=$([ "${wizard_use_url_rewriting}" == "true" ] && echo "1" || echo "0") - sed -i -e "s|@calibre_dir@|${wizard_calibre_dir:=/volume1/calibre/}|g" ${CFG_FILE} - sed -i -e "s|@cops_title@|${wizard_cops_title:=COPS}|g" ${CFG_FILE} - sed -i -e "s|@use_url_rewriting@|${url_rewriting:=0}|g" ${CFG_FILE} - chmod ga+w "${CFG_FILE}" + cp "${DEFAULT_CFG_FILE}" "${CFG_FILE}" + url_rewriting=$([ "${wizard_use_url_rewriting}" == "true" ] && echo "1" || echo "0") + sed -i -e "s|@calibre_dir@|${wizard_calibre_dir:=/volume1/calibre/}|g" ${CFG_FILE} + sed -i -e "s|@cops_title@|${wizard_cops_title:=COPS}|g" ${CFG_FILE} + sed -i -e "s|@use_url_rewriting@|${url_rewriting:=0}|g" ${CFG_FILE} + chmod ga+w "${CFG_FILE}" fi if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then @@ -58,7 +57,7 @@ service_postinst () # Set permissions on directory structure (DSM 5+) set_syno_permissions "${wizard_calibre_dir}" "${GROUP}" # Set permissions on metadata.db - if [ ! "`synoacltool -get "${wizard_calibre_dir}/metadata.db"| grep "group:${GROUP}:allow:rwxpdDaARWc."`" ]; then + if [ ! "$(synoacltool -get ""${wizard_calibre_dir}/metadata.db""| grep group:${GROUP}:allow:rwxpdDaARWc.)" ]; then synoacltool -add "${wizard_calibre_dir}/metadata.db" "group:${GROUP}:allow:rwxpdDaARWc:----" > /dev/null 2>&1 fi fi @@ -68,15 +67,14 @@ service_postinst () service_postuninst () { if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then - # Remove link - rm -f "${SYNOPKG_PKGDEST}" + # Remove link + rm -f "${SYNOPKG_PKGDEST}" - # Remove open_basedir configuration - rm -f "${PHP_CONFIG_LOCATION}/${PACKAGE_NAME}.ini" + # Remove open_basedir configuration + rm -f "${PHP_CONFIG_LOCATION}/${PACKAGE_NAME}.ini" - # Remove the web interface - rm -fr "${WEB_DIR:?}/${SYNOPKG_PKGNAME}" - + # Remove the web interface + rm -fr "${WEB_DIR:?}/${SYNOPKG_PKGNAME}" fi } @@ -90,20 +88,20 @@ service_save () # Save .htaccess file mv -v "${SECURITY_SETTINGS_FILE}" "${TMP_DIR}/${SYNOPKG_PKGNAME}/" if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then - mv -v "${PHP_CONFIG_LOCATION}/${PACKAGE_NAME}.ini" "${TMP_DIR}/${SYNOPKG_PKGNAME}/" + mv -v "${PHP_CONFIG_LOCATION}/${PACKAGE_NAME}.ini" "${TMP_DIR}/${SYNOPKG_PKGNAME}/" fi } service_restore () { - # Restore some stuff - rm -f "${CFG_FILE}" - # Restore cops configuration file - mv -v "${TMP_DIR}/${SYNOPKG_PKGNAME}/${CFG_FILE_NAME}" "${CFG_FILE}" - # Restore .htaccess file - mv -v "${TMP_DIR}/${SYNOPKG_PKGNAME}/${SECURITY_SETTINGS_FILE_NAME}" "${SECURITY_SETTINGS_FILE}" - if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then + # Restore some stuff + rm -f "${CFG_FILE}" + # Restore cops configuration file + mv -v "${TMP_DIR}/${SYNOPKG_PKGNAME}/${CFG_FILE_NAME}" "${CFG_FILE}" + # Restore .htaccess file + mv -v "${TMP_DIR}/${SYNOPKG_PKGNAME}/${SECURITY_SETTINGS_FILE_NAME}" "${SECURITY_SETTINGS_FILE}" + if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then mv "${TMP_DIR}/${SYNOPKG_PKGNAME}/${PACKAGE_NAME}.ini" "${PHP_CONFIG_LOCATION}/" - fi - rm -d "${TMP_DIR}/${SYNOPKG_PKGNAME}" "${TMP_DIR}" + fi + rm -d "${TMP_DIR}/${SYNOPKG_PKGNAME}" "${TMP_DIR}" } From bd4e107c8764efb94c838f336948a4407f328029 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 8 Oct 2023 01:22:03 +0200 Subject: [PATCH 12/20] update adminer - adjust patch to current version --- .../patches/001-not-use-pgsql-extension.patch | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cross/adminer/patches/001-not-use-pgsql-extension.patch b/cross/adminer/patches/001-not-use-pgsql-extension.patch index 6fc31bf9e47..632836eed7a 100644 --- a/cross/adminer/patches/001-not-use-pgsql-extension.patch +++ b/cross/adminer/patches/001-not-use-pgsql-extension.patch @@ -1,15 +1,15 @@ # See https://github.com/SynoCommunity/spksrc/issues/2662 # Remove the extension pgsql because the extension pgsql does not work with the PostgreSQL server from DSM. The Extension pdo_pgsql works perfectly. ---- adminer/drivers/pgsql.inc.php.org 2020-01-31 10:23:04.000000000 +0000 -+++ adminer/drivers/pgsql.inc.php 2020-02-23 16:59:43.482304615 +0000 -@@ -4,146 +4,8 @@ +--- adminer/drivers/pgsql.inc.php.orig 2021-05-14 05:39:59.000000000 +0000 ++++ adminer/drivers/pgsql.inc.php 2023-10-07 23:07:43.616901493 +0000 +@@ -3,146 +3,7 @@ + if (isset($_GET["pgsql"])) { - $possible_drivers = array("PgSQL", "PDO_PgSQL"); define("DRIVER", "pgsql"); - if (extension_loaded("pgsql")) { - class Min_DB { - var $extension = "PgSQL", $_link, $_result, $_string, $_database = true, $server_info, $affected_rows, $error, $timeout; - +- - function _error($errno, $error) { - if (ini_bool("html_errors")) { - $error = html_entity_decode(strip_tags($error)); @@ -43,7 +43,7 @@ - } - - function value($val, $field) { -- return ($field["type"] == "bytea" ? pg_unescape_bytea($val) : $val); +- return ($field["type"] == "bytea" && $val !== null ? pg_unescape_bytea($val) : $val); - } - - function quoteBinary($string) { From 2ad5a43d5180d874e9fadf1a3f22ea40979cf8f3 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 8 Oct 2023 01:23:47 +0200 Subject: [PATCH 13/20] demowebservice add comment regarding DSM 5 compatibility --- spk/demowebservice/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile index 3a17e0d86ea..ab7b21c5b2c 100644 --- a/spk/demowebservice/Makefile +++ b/spk/demowebservice/Makefile @@ -22,6 +22,7 @@ else PHP_VERSION = 7.4 endif +# omit dependencies for DSM 5 and SRM ifeq ($(call version_ge, ${TCVERSION}, 6.0),1) SPK_DEPENDS = WebStation:PHP$(PHP_VERSION):Apache2.4 endif From 36d121f51a3fd601d0e6e048b623260e2121cdce Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sun, 8 Oct 2023 22:32:11 +0200 Subject: [PATCH 14/20] spksrc.service.mk: fix installation of app/config - fix installation of app/config when DSM_UI_CONFIG is defined - ensure DSM_UI_CONFIG has privilege over generated app/config (and is not affected by NO_SERVICE_SHORTCUT) - add Makefile variable SERVICE_DESC to define DESC property in app/config (the whole package description is often not ment to be shown as tooltip of the app icon) - remove the extra installation of app/config since this is fixed now (demowebservice, adminer, cops, owncloud) - add some comments in demoservice/Makefile - remove obsolete dsm-control.sh in adminer package (same is created by the framework with STARTABLE=no) - throw error when SERVICE_USER is not properly defined (only 'auto' is supported) --- mk/spksrc.service.mk | 67 +++++++++++++++++++++---------- spk/adminer/Makefile | 8 ++-- spk/adminer/src/dsm-control.sh | 19 --------- spk/cops/Makefile | 3 +- spk/demoservice/Makefile | 20 ++++++--- spk/demowebservice/Makefile | 9 +---- spk/demowebservice/src/app/config | 2 +- spk/owncloud/Makefile | 5 +-- 8 files changed, 68 insertions(+), 65 deletions(-) delete mode 100644 spk/adminer/src/dsm-control.sh diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index 7ce121f586e..ec8a89136dd 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -4,10 +4,10 @@ # scripts/start-stop-status # scripts/service-setup # conf/privilege if SERVICE_USER or DSM7 -# conf/SPK_NAME.sc if SERVICE_PORT and DSM<7 +# conf/$(SPK_NAME).sc if SERVICE_PORT and DSM<7 # conf/resource if SERVICE_CERT or DSM7 -# app/SPK_NAME.sc if SERVICE_PORT and DSM7 -# app/config if DSM_UI_DIR (may be overwritten by DSM_UI_CONFIG) +# app/$(SPK_NAME).sc if SERVICE_PORT and DSM7 +# app/config if SPK_ICON and SERVICE_PORT but not NO_SERVICE_SHORTCUT (may be overwritten by DSM_UI_CONFIG) # # Targets are executed in the following order: # service_msg_target @@ -17,18 +17,19 @@ # # Variables: # SERVICE_SETUP service-setup script file for generic installer +# SERVICE_PORT for generation of service port firewall config file (*.sc) and for dsm-ui config file # FWPORTS (optional) custom firewall port/rules file -# SERVICE_PORT service port firewall config file (*.sc) and dsm-ui config file -# SERVICE_PORT_PROTOCOL service port protocol for dsm-ui config file, default = "http" -# SERVICE_PORT_ALL_USERS service port access for all users for dsm-ui config file, default = "true" +# DSM_UI_DIR defaults to app. May be defined different for custom DSM UI integration +# # SERVICE_CERT (optional) configure DSM certificate management for this service name from the firewall config file (*.sc) # SERVICE_CERT_RELOAD (optional) package-relative path to a script for reloading the service after certificate changes -# SERVICE_TYPE service type for dsm-ui config file, default = "url" -# SERVICE_WIZARD_GROUP (not supported anymore) name of wizard-variable to define the GROUP -# SERVICE_WIZARD_GROUP is not supported anymore (not compatible with DSM 7 and DSM 6 using resource worker) +# # SERVICE_WIZARD_SHARENAME (optional) this is the name of wizard-varible to define folder name of SHARE_PATH (uses DSM data share worker for DSM 6 and DSM 7) # SERVICE_WIZARD_SHARE (deprecated) name of wizard-varible to define SHARE_PATH # USE_DATA_SHARE_WORKER (deprecated, optional) use DSM data share worker for SERVICE_WIZARD_SHARE for DSM 6 too +# SERVICE_WIZARD_GROUP (not supported anymore) name of wizard-variable to define the GROUP +# SERVICE_WIZARD_GROUP is not supported anymore (not compatible with DSM 7 and DSM 6 using resource worker) +# # SERVICE_USER (optional) runtime user account for generic service support. # "auto" is the only value supported with DSM 7 and defines sc-${SPK_NAME} as service user. # SPK_GROUP (optional) defines the group to use in privilege resource file @@ -43,8 +44,17 @@ # folder at intallation and runtime. # SSS_SCRIPT (optional) custom script file for service start/stop/status when the generic # installer generated script (SERVICE_SETUP) is not usable. -# NO_SERVICE_SHORTCUT (optional) do not create an app icon in the DSM desktop # INSTALLER_SCRIPT (deprecated) installer script file before introduction of generic installer +# +# Variables for the dsm-ui config file (app/config) the definition for the app icon in the DSM UI and its properties. +# The app icon (i.e. the app/config file) is created, when DSM_UI_CONF is defined +# or - when SERVICE_PORT is defined and NO_SERVICE_SHORTCUT is not defined +# DSM_UI_CONF (optional) custom app/config file (required for web services without SERVICE_PORT) +# NO_SERVICE_SHORTCUT (optional) do not create an app icon when SERVICE_PORT is defined +# SERVICE_PORT_PROTOCOL service port protocol for dsm-ui config file, default = "http" +# SERVICE_PORT_ALL_USERS service port access for all users for dsm-ui config file, default = "true" +# SERVICE_TYPE service type for dsm-ui config file, default = "url" +# SERVICE_DESC service desc for dsm-ui config file, i.e. the tooltip on the app icon (default = $(DESCRIPTION)) # ifeq ($(strip $(PRE_SERVICE_TARGET)),) @@ -96,8 +106,7 @@ else ifeq ($(SERVICE_USER),auto) SPK_USER = $(SPK_NAME) else ifneq ($(strip $(SERVICE_USER)),) -$(warning Only 'SERVICE_USER=auto' is compatible with DSM7) -SPK_USER = $(SERVICE_USER) +$(error Only 'SERVICE_USER=auto' is supported since DSM7) endif endif @@ -188,9 +197,9 @@ ifneq ($(strip $(SERVICE_EXE)),) @echo "${RED}ERROR: SERVICE_EXE (start-stop-daemon) is not supported anymore${NC}" @echo "${GREEN}Please migrate to SERVICE_COMMAND=${NC}" @exit 1 +endif ifneq ($(strip $(SERVICE_OPTIONS)),) @echo 'SERVICE_OPTIONS="$(SERVICE_OPTIONS)"' >> $@ -endif @echo '' >> $@ endif ifeq ($(strip $(USE_ALTERNATE_TMPDIR)),1) @@ -407,9 +416,26 @@ $(STAGING_DIR)/$(DSM_UI_DIR)/$(SPK_NAME).sc: $(filter %.sc,$(FWPORTS)) SERVICE_FILES += $(STAGING_DIR)/$(DSM_UI_DIR)/$(SPK_NAME).sc endif -# Generate DSM UI configuration +# Generate DSM UI configuration (app/config) +# prerequisites: +# - SPK_ICON is required +# - if DSM_UI_CONFIG is defined, it is used as config file +# - otherwise the config file is generated when +# SERVICE_PORT is defined and NO_SERVICE_SHORTCUT is not defined +# - the config file is generated with the SERVICE_PORT and the following variables +# - SERVICE_DESC +# - SERVICE_URL +# - SERVICE_PORT_PROTOCOL +# - SERVICE_PORT_ALL_USERS +# - SERVICE_TYPE +# default values are documentent at the top of this file ifneq ($(strip $(SPK_ICON)),) -ifneq ($(strip $(SERVICE_PORT)),) +ifneq ($(wildcard $(DSM_UI_CONFIG)),) +$(STAGING_DIR)/$(DSM_UI_DIR)/config: + $(create_target_dir) + cat $(DSM_UI_CONFIG) > $@ +SERVICE_FILES += $(STAGING_DIR)/$(DSM_UI_DIR)/config +else ifneq ($(strip $(SERVICE_PORT)),) ifeq ($(strip $(NO_SERVICE_SHORTCUT)),) # Set some defaults ifeq ($(strip $(SERVICE_URL)),) @@ -424,15 +450,13 @@ endif ifeq ($(strip $(SERVICE_TYPE)),) SERVICE_TYPE=url endif - -DESC=$(shell echo ${DESCRIPTION} | sed -e 's/\\//g' -e 's/"/\\"/g') +ifeq ($(strip $(SERVICE_DESC)),) +SERVICE_DESC=$(shell echo ${DESCRIPTION} | sed -e 's/\\//g' -e 's/"/\\"/g') +endif $(STAGING_DIR)/$(DSM_UI_DIR)/config: $(create_target_dir) -ifneq ($(wildcard $(DSM_UI_CONFIG)),) - cat $(DSM_UI_CONFIG) > $@ -else @echo '{}' | jq --arg name "${DISPLAY_NAME}" \ - --arg desc "${DESC}" \ + --arg desc "${SERVICE_DESC}" \ --arg id "com.synocommunity.packages.${SPK_NAME}" \ --arg icon "images/${SPK_NAME}-{0}.png" \ --arg prot "${SERVICE_PORT_PROTOCOL}" \ @@ -441,7 +465,6 @@ else --arg type "${SERVICE_TYPE}" \ --argjson allUsers ${SERVICE_PORT_ALL_USERS} \ '{".url":{($$id):{"title":$$name, "desc":$$desc, "icon":$$icon, "type":$$type, "protocol":$$prot, "port":$$port, "url":$$url, "allUsers":$$allUsers, "grantPrivilege":"all", "advanceGrantPrivilege":true}}}' > $@ -endif SERVICE_FILES += $(STAGING_DIR)/$(DSM_UI_DIR)/config endif endif diff --git a/spk/adminer/Makefile b/spk/adminer/Makefile index 969a4cf69dc..9a7190b7fdb 100644 --- a/spk/adminer/Makefile +++ b/spk/adminer/Makefile @@ -14,7 +14,7 @@ DISPLAY_NAME = Adminer CHANGELOG = "1. Update to v4.8.1
This package depends on Apache 2.4 and PHP 7.4 (except for DSM < 6)." HOMEPAGE = https://www.adminer.org -LICENSE = Apache License 2.0 or GPL 2 +LICENSE = Apache 2.0 or GPLv2 include ../../mk/spksrc.common.mk ## @@ -35,8 +35,7 @@ START_DEP_SERVICES = apache-web INSTUNINST_RESTART_SERVICES = apache-web SERVICE_SETUP = src/service-setup.sh -# we need a nop sss script to avoid creation of service control. -SSS_SCRIPT = src/dsm-control.sh +STARTABLE = no WIZARDS_DIR = src/wizard/ POST_STRIP_TARGET = adminer_extra_install @@ -48,6 +47,5 @@ include ../../mk/spksrc.spk.mk .PHONY: adminer_extra_install adminer_extra_install: - install -m 755 -d $(STAGING_DIR)/var $(STAGING_DIR)/app $(STAGING_DIR)/web - install -m 644 src/app/config $(STAGING_DIR)/app/config + install -m 755 -d $(STAGING_DIR)/web install -m 644 src/dot-htaccess-apache$(APACHE_VERSION) $(STAGING_DIR)/web/.htaccess diff --git a/spk/adminer/src/dsm-control.sh b/spk/adminer/src/dsm-control.sh deleted file mode 100644 index 33c02538635..00000000000 --- a/spk/adminer/src/dsm-control.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -case $1 in - start) - exit 0 - ;; - stop) - exit 0 - ;; - status) - exit 0 - ;; - log) - exit 1 - ;; - *) - exit 1 - ;; -esac diff --git a/spk/cops/Makefile b/spk/cops/Makefile index 681d4fd1114..664011a197d 100644 --- a/spk/cops/Makefile +++ b/spk/cops/Makefile @@ -42,6 +42,5 @@ include ../../mk/spksrc.spk.mk .PHONY: cops_extra_install cops_extra_install: - install -m 755 -d $(STAGING_DIR)/app - install -m 644 src/app/config $(STAGING_DIR)/app/config + @$(MSG) Install default config file install -m 666 src/config_local.php.synology $(STAGING_DIR)/config_local.php.synology diff --git a/spk/demoservice/Makefile b/spk/demoservice/Makefile index 8e949301a84..41922ed966b 100644 --- a/spk/demoservice/Makefile +++ b/spk/demoservice/Makefile @@ -7,25 +7,33 @@ override ARCH= MAINTAINER = ymartin59 DESCRIPTION = Demonstration package to show installer script capabilities when requiring non-root user for background service and show configuration/creation of shared folder for package user. -DISPLAY_NAME = DemoService -CHANGELOG = "1. Use redesigned shared folder handling.
2. Fix service command to work on SRM and DSM 5 too.
3. Use Pyhton 3 when available in path." +DISPLAY_NAME = Demo Service +CHANGELOG = "1. Use redesigned shared folder handling.
2. Fix service command to work on SRM and DSM 5.
3. Use Pyhton 3 when available in path." LICENSE = GPLv2 WIZARDS_DIR = src/wizard/ -# 'auto' reserved value grabs SPK_NAME +# 'auto' reserved value grabs SPK_NAME (only auto is supported) SERVICE_USER = auto SERVICE_SETUP = src/service-setup.sh +# The spksrc framework does not parse the service-setup.sh file for valid SERVICE_COMMAND +# so we have to tell it, that the package provides a startable service. STARTABLE = yes +# New with redesigned shared folder handling: +# The wizard must take a name for the shared folder to use or create +# Entering the full path for shared folders is not supported anymore. SERVICE_WIZARD_SHARENAME = wizard_shared_folder_name # Service configuration SERVICE_PORT = 8888 -SERVICE_PORT_TITLE = DemoService (HTTP) +SERVICE_PORT_TITLE = $(DISPLAY_NAME) (HTTP) +SERVICE_DESC = Demo Service Package -# Admin link +# Admin link (optional) +# Create a link in the package center for this package. +# Just for demonstration since an app icon in DSM is created with the same target. ADMIN_PORT = $(SERVICE_PORT) PRE_COPY_TARGET = demoservice_pre_copy @@ -41,7 +49,7 @@ demoservice_pre_copy: echo "Test file for /var/packages/$(SPK_NAME)/var/README" > $(STAGING_INSTALL_PREFIX_VAR)/README echo "INSTALL_PREFIX_VAR = $(INSTALL_PREFIX_VAR)" >> $(STAGING_INSTALL_PREFIX_VAR)/README echo "STAGING_INSTALL_PREFIX = $(STAGING_INSTALL_PREFIX)" >> $(STAGING_INSTALL_PREFIX_VAR)/README - # create dummy file (avoid empty target folder for DSM 7 packages) + @$(MSG) Create dummy file to avoid empty target folder for DSM 7 packages mkdir -p $(STAGING_INSTALL_PREFIX) echo "dummy file (to avoid empty package)" > $(STAGING_INSTALL_PREFIX)/dummy diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile index ab7b21c5b2c..9816a9cdac7 100644 --- a/spk/demowebservice/Makefile +++ b/spk/demowebservice/Makefile @@ -33,6 +33,7 @@ SERVICE_WIZARD_SHARENAME = wizard_shared_folder_name SYSTEM_GROUP = http DSM_UI_DIR = app +# the app/config file is not used on DSM 7, the app icon is created based on the resources in CONF_DIR only. DSM_UI_CONFIG = src/app/config CONF_DIR = src/conf_php$(PHP_VERSION)/ @@ -51,14 +52,8 @@ POST_ICON_TARGET = demowebservice_post_icon include ../../mk/spksrc.spk.mk .PHONY: demowebservice_install -# the app/config file must be installed from DSM 6 to show the icon in DSM -# but it must not exist in DSM 7 packages (System error. Unable to perform this operation. Please try again later.) demowebservice_install: - @$(MSG) Install app/config and web application -ifeq ($(call version_lt, ${TCVERSION}, 7.0),1) - @install -d -m 755 $(STAGING_DIR)/app/ - @install -m 644 $(DSM_UI_CONFIG) $(STAGING_DIR)/app/config -endif + @$(MSG) Install the web application @install -d -m 755 $(STAGING_DIR)/web/$(SPK_NAME)/ @install -m 755 src/web/index.php $(STAGING_DIR)/web/$(SPK_NAME)/ diff --git a/spk/demowebservice/src/app/config b/spk/demowebservice/src/app/config index 339050a7f3e..f960fd23408 100644 --- a/spk/demowebservice/src/app/config +++ b/spk/demowebservice/src/app/config @@ -3,7 +3,7 @@ "com.synocommunity.packages.demowebservice": { "type": "url", "title": "Demo Web Service", - "desc": "Web Service Demo Package", + "desc": "Web Service Demo Package (Tooltip)", "icon": "images/demowebservice-{0}.png", "protocol": "http", "port": "80", diff --git a/spk/owncloud/Makefile b/spk/owncloud/Makefile index e549077fe9b..d919f4b1991 100644 --- a/spk/owncloud/Makefile +++ b/spk/owncloud/Makefile @@ -4,7 +4,7 @@ SPK_REV = 11 SPK_ICON = src/owncloud.png DSM_UI_DIR = app -DEPENDS = cross/$(SPK_NAME) +DEPENDS = cross/owncloud SPK_DEPENDS = "WebStation:PHP7.4:Apache2.4" REQUIRED_MIN_DSM = 6.0 @@ -45,8 +45,7 @@ include ../../mk/spksrc.spk.mk .PHONY: owncloud_extra_install owncloud_extra_install: - install -m 755 -d $(STAGING_DIR)/app -d $(STAGING_DIR)/bin -d $(STAGING_DIR)/web - install -m 644 src/app/config $(STAGING_DIR)/app/config + install -m 755 -d $(STAGING_DIR)/bin -d $(STAGING_DIR)/web install -m 755 src/bin/owncloud-daemon $(STAGING_DIR)/bin/owncloud-daemon install -m 644 src/web/owncloud.conf $(STAGING_DIR)/web/owncloud.conf install -m 644 src/web/owncloud.json $(STAGING_DIR)/web/owncloud.json From 9316846f5045fe6f1c4639305535a40e85412f96 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Mon, 9 Oct 2023 22:35:01 +0200 Subject: [PATCH 15/20] spk/aria2: remove SERVICE_WIZARD_GROUP - SERVICE_WIZARD_GROUP is not supported anymore --- spk/aria2/Makefile | 1 - spk/aria2/src/service-setup.sh | 1 - spk/aria2/src/wizard/install_uifile | 18 ------------------ spk/aria2/src/wizard/install_uifile_chs | 18 ------------------ 4 files changed, 38 deletions(-) diff --git a/spk/aria2/Makefile b/spk/aria2/Makefile index 7f347f513b8..ddcec2ed099 100644 --- a/spk/aria2/Makefile +++ b/spk/aria2/Makefile @@ -29,7 +29,6 @@ WIZARDS_DIR = src/wizard # 'auto' reserved value grabs SPK_NAME SERVICE_USER = auto -SERVICE_WIZARD_GROUP = wizard_group SERVICE_WIZARD_SHARE = wizard_download_dir SERVICE_SETUP = src/service-setup.sh diff --git a/spk/aria2/src/service-setup.sh b/spk/aria2/src/service-setup.sh index 3e959980e69..57daa819060 100644 --- a/spk/aria2/src/service-setup.sh +++ b/spk/aria2/src/service-setup.sh @@ -24,7 +24,6 @@ service_preinst () exit 1 } fi - set_syno_permissions "${wizard_download_dir}" "${wizard_group}" fi } diff --git a/spk/aria2/src/wizard/install_uifile b/spk/aria2/src/wizard/install_uifile index 2a953f93966..3b5a2ae8bc6 100644 --- a/spk/aria2/src/wizard/install_uifile +++ b/spk/aria2/src/wizard/install_uifile @@ -3,24 +3,6 @@ "step_title": "Aria2 Configuration", "invalid_next_disabled": true, "items": [ - { - "type": "textfield", - "desc": "Synology DSM Settings", - "subitems": [ - { - "key": "wizard_group", - "desc": "DSM group", - "defaultValue": "sc-aria2", - "validator": { - "allowBlank": false, - "regex": { - "expr": "/^[^<>:*/?\"]*$/", - "errorText": "Not allowed character in group name" - } - } - } - ] - }, { "type": "textfield", "desc": "Aria2 Settings", diff --git a/spk/aria2/src/wizard/install_uifile_chs b/spk/aria2/src/wizard/install_uifile_chs index effc3e10e76..1bfb614d636 100644 --- a/spk/aria2/src/wizard/install_uifile_chs +++ b/spk/aria2/src/wizard/install_uifile_chs @@ -3,24 +3,6 @@ "step_title": "Aria2 配置", "invalid_next_disabled": true, "items": [ - { - "type": "textfield", - "desc": "群晖 DSM 设置", - "subitems": [ - { - "key": "wizard_group", - "desc": "用户组", - "defaultValue": "sc-aria2", - "validator": { - "allowBlank": false, - "regex": { - "expr": "/^[^<>:*/?\"]*$/", - "errorText": "用户组不应含有^[^<>:*/?\"]*等特殊字符" - } - } - } - ] - }, { "type": "textfield", "desc": "Aria2 设置", From f953b1954ca54bbe288fd4b74bba1284e5ffa983 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Mon, 9 Oct 2023 22:37:50 +0200 Subject: [PATCH 16/20] enable NO_SERVICE_SHORTCUT for DSM_UI_CONFIG - fix NO_SERVICE_SHORTCUT to be applied independent of DSM_UI_CONFIG: - it must be possible to disable creation of app/config file when app (webservice) is fully defined in conf/resource - force icon creation when DSM_UI_CONFIG is defined - NO_SERVICE_SHORTCUT must be ignored for icon creation, when DSM_UI_CONFIG is defined --- mk/spksrc.icon.mk | 4 +++- mk/spksrc.service.mk | 13 ++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/mk/spksrc.icon.mk b/mk/spksrc.icon.mk index 9421a8fe6c2..ac48bb29c7c 100644 --- a/mk/spksrc.icon.mk +++ b/mk/spksrc.icon.mk @@ -14,7 +14,9 @@ ICON_COOKIE = $(WORK_DIR)/.$(COOKIE_PREFIX)icon_done # Icons are only needed with "DSM UI configuration" (see spksrc.service.mk) ifneq ($(strip $(DSM_UI_DIR)),) ifneq ($(strip $(SPK_ICON)),) -ifneq ($(or $(strip $(SERVICE_PORT)), $(strip $(ADMIN_URL)), $(strip $(DSM_UI_CONFIG))),) +ifneq ($(strip $(DSM_UI_CONFIG)),) +ICON_DIR = $(STAGING_DIR)/$(DSM_UI_DIR)/images +else ifneq ($(or $(strip $(SERVICE_PORT)), $(strip $(ADMIN_URL))),) ifeq ($(strip $(NO_SERVICE_SHORTCUT)),) ICON_DIR = $(STAGING_DIR)/$(DSM_UI_DIR)/images endif diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index ec8a89136dd..c3a02f65721 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -47,10 +47,10 @@ # INSTALLER_SCRIPT (deprecated) installer script file before introduction of generic installer # # Variables for the dsm-ui config file (app/config) the definition for the app icon in the DSM UI and its properties. -# The app icon (i.e. the app/config file) is created, when DSM_UI_CONF is defined -# or - when SERVICE_PORT is defined and NO_SERVICE_SHORTCUT is not defined +# The app icon (i.e. the app/config file) is created, when SERVICE_PORT or DSM_UI_CONF is defined +# and can be disabled by definition of NO_SERVICE_SHORTCUT # DSM_UI_CONF (optional) custom app/config file (required for web services without SERVICE_PORT) -# NO_SERVICE_SHORTCUT (optional) do not create an app icon when SERVICE_PORT is defined +# NO_SERVICE_SHORTCUT (optional) do not create an app icon (app/config) # SERVICE_PORT_PROTOCOL service port protocol for dsm-ui config file, default = "http" # SERVICE_PORT_ALL_USERS service port access for all users for dsm-ui config file, default = "true" # SERVICE_TYPE service type for dsm-ui config file, default = "url" @@ -418,10 +418,9 @@ endif # Generate DSM UI configuration (app/config) # prerequisites: -# - SPK_ICON is required +# - SPK_ICON is required and NO_SERVICE_SHORTCUT is not defined # - if DSM_UI_CONFIG is defined, it is used as config file -# - otherwise the config file is generated when -# SERVICE_PORT is defined and NO_SERVICE_SHORTCUT is not defined +# - else SERVICE_PORT is defined: # - the config file is generated with the SERVICE_PORT and the following variables # - SERVICE_DESC # - SERVICE_URL @@ -430,13 +429,13 @@ endif # - SERVICE_TYPE # default values are documentent at the top of this file ifneq ($(strip $(SPK_ICON)),) +ifeq ($(strip $(NO_SERVICE_SHORTCUT)),) ifneq ($(wildcard $(DSM_UI_CONFIG)),) $(STAGING_DIR)/$(DSM_UI_DIR)/config: $(create_target_dir) cat $(DSM_UI_CONFIG) > $@ SERVICE_FILES += $(STAGING_DIR)/$(DSM_UI_DIR)/config else ifneq ($(strip $(SERVICE_PORT)),) -ifeq ($(strip $(NO_SERVICE_SHORTCUT)),) # Set some defaults ifeq ($(strip $(SERVICE_URL)),) SERVICE_URL=/ From 774c5a403f5125c648a71bf08faea36b0babc79c Mon Sep 17 00:00:00 2001 From: hgy59 Date: Mon, 9 Oct 2023 22:55:03 +0200 Subject: [PATCH 17/20] reduce SPK_DEPENDS for DSM 6 - document and avoid web server and PHP dependency as SPK_DEPENDS for all except DSM 7 - DSM 6: remove conf/resorce for webservice (supported on DSM 7 only) --- spk/demowebservice/Makefile | 27 ++++++++++++++++--------- spk/demowebservice/src/service-setup.sh | 1 + spk/demowebservice/src/web/index.php | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile index 9816a9cdac7..c8ebc07ad08 100644 --- a/spk/demowebservice/Makefile +++ b/spk/demowebservice/Makefile @@ -18,13 +18,14 @@ include ../../mk/spksrc.common.mk # use the latest php version ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) PHP_VERSION = 8.2 -else -PHP_VERSION = 7.4 endif -# omit dependencies for DSM 5 and SRM -ifeq ($(call version_ge, ${TCVERSION}, 6.0),1) +# only for DSM 7 we have dedicated dependencies (must match the configuration in conf/resource) +ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) SPK_DEPENDS = WebStation:PHP$(PHP_VERSION):Apache2.4 +else ifeq ($(call version_ge, ${TCVERSION}, 6.0),1) +# on DSM 6 the default web server and PHP version are used +SPK_DEPENDS = WebStation endif WIZARDS_DIR = src/wizard/ @@ -33,14 +34,22 @@ SERVICE_WIZARD_SHARENAME = wizard_shared_folder_name SYSTEM_GROUP = http DSM_UI_DIR = app -# the app/config file is not used on DSM 7, the app icon is created based on the resources in CONF_DIR only. DSM_UI_CONFIG = src/app/config - +ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) +# webservice resource definition is for DSM 7 only CONF_DIR = src/conf_php$(PHP_VERSION)/ +# The app/config file is not used on DSM 7 for web services, the app icon is created based on the resources in CONF_DIR only. +# With app/config on DSM 7 (sometimes) an error occurred: System error. Unable to perform this operation. Please try again later. +# Disable the creation of app/config file: +NO_SERVICE_SHORTCUT = y +endif -INSTALL_DEP_SERVICES = apache-web -START_DEP_SERVICES = apache-web -INSTUNINST_RESTART_SERVICES = apache-web +# apache-web dependency is not used: +# on DSM 7 the settings are ignored as dependency is defined in conf/resource +# on DSM 6 the package runs under the active web server (apache or nginx) +#INSTALL_DEP_SERVICES = apache-web +#START_DEP_SERVICES = apache-web +#INSTUNINST_RESTART_SERVICES = apache-web SERVICE_USER = auto SERVICE_SETUP = src/service-setup.sh diff --git a/spk/demowebservice/src/service-setup.sh b/spk/demowebservice/src/service-setup.sh index f63f8b6ae5b..c672a600230 100644 --- a/spk/demowebservice/src/service-setup.sh +++ b/spk/demowebservice/src/service-setup.sh @@ -14,6 +14,7 @@ service_postinst () if [ "${SYNOPKG_DSM_VERSION_MAJOR}" -lt 7 ]; then # Install the web interface + # only DSM 7+ installs the web service based on the "webservice" resource. cp -rp "${SYNOPKG_PKGDEST}/web/${SYNOPKG_PKGNAME}" ${WEB_ROOT}/ fi diff --git a/spk/demowebservice/src/web/index.php b/spk/demowebservice/src/web/index.php index d3b0c01a5ae..1d1f8084af4 100644 --- a/spk/demowebservice/src/web/index.php +++ b/spk/demowebservice/src/web/index.php @@ -89,7 +89,7 @@

Demo Web Service

This is a demo web service package for synology DSM.

-

It demonstrates how to build a package to integrate with apache web server and PHP .

+

It demonstrates how to build a package to integrate with web server and PHP.

It also shows how to configure and use a shared folder.

The source code is located in the SynoCommunity respository under demowebservice.

From 3161880e08eb6ad7fe02c660120d974ea7ddaaca Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sat, 14 Oct 2023 14:44:06 +0200 Subject: [PATCH 18/20] enhance demowebservice: - add port 8889 for Web Portal on DSM 7 - add page with remarks install wizard - reverse proxy configuration on DSM 7 does not work and is not added to resource file (#5544) --- spk/demowebservice/Makefile | 2 + spk/demowebservice/src/conf_php8.2/resource | 95 +++++++++++--------- spk/demowebservice/src/wizard/install_uifile | 20 ++++- 3 files changed, 73 insertions(+), 44 deletions(-) diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile index c8ebc07ad08..8b887ac5ef8 100644 --- a/spk/demowebservice/Makefile +++ b/spk/demowebservice/Makefile @@ -42,6 +42,8 @@ CONF_DIR = src/conf_php$(PHP_VERSION)/ # With app/config on DSM 7 (sometimes) an error occurred: System error. Unable to perform this operation. Please try again later. # Disable the creation of app/config file: NO_SERVICE_SHORTCUT = y +# create firewall rules, since we add a port based "Web Portal" +SERVICE_PORT = 8889 endif # apache-web dependency is not used: diff --git a/spk/demowebservice/src/conf_php8.2/resource b/spk/demowebservice/src/conf_php8.2/resource index 7c11add5336..c50ef83b657 100644 --- a/spk/demowebservice/src/conf_php8.2/resource +++ b/spk/demowebservice/src/conf_php8.2/resource @@ -1,44 +1,55 @@ { - "data-share": { - "shares": [ - { - "name": "{{wizard_shared_folder_name}}", - "permission":{"rw":["sc-demowebservice"]} - } - ] - }, - "webservice": { - "services": [{ - "service": "demowebservice", - "display_name": "Demo Web Service", - "icon": "app/images/demowebservice-{0}.png", - "type": "apache_php", - "root": "demowebservice", - "backend": 2, - "php": { - "profile_name": "Web Service Demo Profile", - "profile_desc": "PHP 8.2 profile for Demo Web Service", - "backend": 11, - "extensions": [], - "user": "sc-demowebservice", - "group": "http" - } - }], - "portals": [ - { - "service": "demowebservice", - "type": "alias", - "name": "Demo Web Service", - "alias": "demowebservice", - "app": "com.synocommunity.packages.demowebservice" - } - ], - "pkg_dir_prepare": [{ - "source": "/var/packages/demowebservice/target/web/demowebservice", - "target": "demowebservice", - "mode": "0755", - "user": "sc-demowebservice", - "group": "http" - }] - } + "data-share": { + "shares": [ + { + "name": "{{wizard_shared_folder_name}}", + "permission":{"rw":["sc-demowebservice"]} + } + ] + }, + "webservice": { + "services": [ + { + "service": "demowebservice", + "display_name": "Demo Web Service", + "icon": "app/images/demowebservice-{0}.png", + "type": "apache_php", + "root": "demowebservice", + "backend": 2, + "php": { + "profile_name": "Web Service Demo Profile", + "profile_desc": "PHP 8.2 profile for Demo Web Service", + "backend": 11, + "extensions": [], + "user": "sc-demowebservice", + "group": "http" + } + } + ], + "portals": [ + { + "service": "demowebservice", + "type": "alias", + "name": "Demo Web Service (Alias)", + "alias": "demowebservice", + "app": "com.synocommunity.packages.demowebservice" + }, + { + "service": "demowebservice", + "type": "server", + "name": "Demo Web Service (Port)", + "http_port": [8889], + "app": "com.synocommunity.packages.demowebservice" + } + ], + "pkg_dir_prepare": [ + { + "source": "/var/packages/demowebservice/target/web/demowebservice", + "target": "demowebservice", + "mode": "0755", + "user": "sc-demowebservice", + "group": "http" + } + ] + } } \ No newline at end of file diff --git a/spk/demowebservice/src/wizard/install_uifile b/spk/demowebservice/src/wizard/install_uifile index 13ca10588dc..19a75f7a5a9 100644 --- a/spk/demowebservice/src/wizard/install_uifile +++ b/spk/demowebservice/src/wizard/install_uifile @@ -19,11 +19,27 @@ } } ] - }, { + }, + { "desc": "" - }, { + }, + { "desc": "This package runs as internal service user 'sc-demowebservice' in DSM. The shared folder above is configured at installation time to be accessible by this user.

Please read Permission Management for details." } ] + }, + { + "step_title": "Miscellaneous Remarks", + "items": [ + { + "desc": "On DSM 6
In Web Station you can use any web server (apache or nginx) and you must configure a PHP profile for this demo to work." + }, + { + "desc": "On DSM 7
This demowebservice will be accessible by path (http://<local ip>/demowebservice) or by port (http://<local ip>:8889) on your diskstation.

The port based \"Web Portal\" allows reverse proxy configuration for this demo. Alas configuration of a reverse proxy does not work by the package installer and you would have to manually create a reverse proxy for http://localhost:8889." + }, + { + "desc": "PHP open_basedir
If this demowebservice can not display the content of the shared folder, you might have configured a non empty open_basedir in the PHP profile. To solve this, you must add the path of the shared folder. On DSM 7 a specific PHP profile with an empty open_basedir is installed, but on other systems you might face this restriction depending on the PHP profile used." + } + ] } ] \ No newline at end of file From 708c2a53a5a9b61d3986f3c46175aa9a257073a3 Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sat, 14 Oct 2023 16:13:19 +0200 Subject: [PATCH 19/20] always define EFF_USER for service-setup on DSM 7 (fixes #5041) --- mk/spksrc.service.mk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index c3a02f65721..a6d3409f5dc 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -95,20 +95,20 @@ service_msg_target: pre_service_target: service_msg_target ifeq ($(call version_ge, ${TCVERSION}, 7.0),1) -# always use SPK_USER on DSM >= 7, not only when SERVICE_USER is defined -SPK_USER = $(SPK_NAME) +# always use SPK_USER on DSM >= 7 +SERVICE_USER = auto ifneq ($(strip $(SERVICE_WIZARD_SHARE)),) # always use data share worker on DSM >= 7 USE_DATA_SHARE_WORKER = yes endif -else +endif + # SERVICE_USER=auto uses SPK_NAME ifeq ($(SERVICE_USER),auto) SPK_USER = $(SPK_NAME) else ifneq ($(strip $(SERVICE_USER)),) $(error Only 'SERVICE_USER=auto' is supported since DSM7) endif -endif # Recommend explicit STARTABLE=no ifeq ($(strip $(SSS_SCRIPT) $(SERVICE_COMMAND) $(STARTABLE)),) From a9b857187e54b558df81734a161b522bf9361bae Mon Sep 17 00:00:00 2001 From: hgy59 Date: Sat, 14 Oct 2023 16:17:53 +0200 Subject: [PATCH 20/20] creation of shared folder always requires a service user - force SERVICE_USER = auto with SERVICE_WIZARD_SHARENAME - remove SERVICE_USER = auto in demowebservice Makefile as defined by framework now (focred by SERVICE_WIZARD_SHARENAME) --- mk/spksrc.service.mk | 7 +++++++ spk/demowebservice/Makefile | 1 - 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mk/spksrc.service.mk b/mk/spksrc.service.mk index a6d3409f5dc..b9b1c5ed818 100644 --- a/mk/spksrc.service.mk +++ b/mk/spksrc.service.mk @@ -103,6 +103,13 @@ USE_DATA_SHARE_WORKER = yes endif endif +# we need the service user to define access rights for the shared folder +ifneq ($(SERVICE_WIZARD_SHARENAME),) +ifeq ($(SERVICE_USER),) +SERVICE_USER = auto +endif +endif + # SERVICE_USER=auto uses SPK_NAME ifeq ($(SERVICE_USER),auto) SPK_USER = $(SPK_NAME) diff --git a/spk/demowebservice/Makefile b/spk/demowebservice/Makefile index 8b887ac5ef8..b7825dff382 100644 --- a/spk/demowebservice/Makefile +++ b/spk/demowebservice/Makefile @@ -53,7 +53,6 @@ endif #START_DEP_SERVICES = apache-web #INSTUNINST_RESTART_SERVICES = apache-web -SERVICE_USER = auto SERVICE_SETUP = src/service-setup.sh STARTABLE = no