[__single_binary_service] Many improvements + runit support

Amongst other things compressed files can be of a type other than .tar.gz (it
remains the default) and we now properly support runit services, FreeBSD and
Devuan.
This commit is contained in:
evilham 2021-10-30 15:36:49 +02:00
parent 3e77fbbb43
commit 1af7e960fa
6 changed files with 152 additions and 74 deletions

View file

@ -1,21 +0,0 @@
#!/bin/sh -e
STATE="$(cat "${__object}/parameter/state")"
if [ "${STATE}" != "present" ]; then
exit
fi
ETC_DIR="/etc"
SERVICE_NAME="${__object_id}"
CONFIG_FILE_DEST="${ETC_DIR}/${SERVICE_NAME}.conf"
BIN_DIR="/usr/local/bin"
VERSION_FILE="${BIN_DIR}/.${SERVICE_NAME}.cdist.version"
# We only restart here if there was a config change
# but there was not a version change
if grep -qE "^__file${CONFIG_FILE_DEST}" "${__messages_in}" && \
grep -qvE "^__file${VERSION_FILE}" "${__messages_in}"; then
echo "service ${SERVICE_NAME} restart"
fi

27
man.rst
View file

@ -23,10 +23,8 @@ binaries in `/usr/local/bin`.
If a `--config-file-source` is provided, it will be placed under: If a `--config-file-source` is provided, it will be placed under:
`/etc/${__object_id}.conf`. `/etc/${__object_id}.conf`.
TODO (patches welcome!): This type supports services managed by `__runit(7)` when `systemd` is not
- It currently only supports `.tar.gz` archives. the init system being used.
- It currently only supports systemd units.
- Does not handle properly BSD-systems (wheel group, /usr/local/etc, systemd)
REQUIRED PARAMETERS REQUIRED PARAMETERS
@ -72,6 +70,13 @@ user
If this user is not `root` and `--do-not-manage-user` is not present, If this user is not `root` and `--do-not-manage-user` is not present,
this user will be created or removed as per the `--state` parameter. this user will be created or removed as per the `--state` parameter.
user-home-dir
Does not have an effect if `--do-not-manage-user` is used or `--user` is
`root`.
The home directory of the service user. It will be created.
Defaults to `/nonexistent`, in this case the home directory will not be
created.
group group
The group under which the service will run. Defaults to `--user`. The group under which the service will run. Defaults to `--user`.
@ -95,6 +100,13 @@ service-exec
Defaults to `/usr/local/bin/BINARY_NAME` where `BINARY_NAME` is the Defaults to `/usr/local/bin/BINARY_NAME` where `BINARY_NAME` is the
resulting value of `--binary`. resulting value of `--binary`.
service-definition
The service definition to be used as an override.
Note that this type decides dinammically between runit and systemd, and
you can currently only define either a systemd unit or a runit script here.
Use this parameter only for testing and get in touch to discuss how your
particular use-case can be supported by the type.
service-description service-description
The service description to be used in, e.g. the systemd unit file. The service description to be used in, e.g. the systemd unit file.
Defaults to `cdist-managed '${__object_id}' service`. Defaults to `cdist-managed '${__object_id}' service`.
@ -106,6 +118,13 @@ unpack-args
subdirectories; that can be worked around with subdirectories; that can be worked around with
`--unpack-args '--tar-strip 1'`. `--unpack-args '--tar-strip 1'`.
unpack-extension
Only has an effect if `--unpack` is used.
The file extension of the file to unpack, defaults to `.tar.gz`.
working-directory
If set, the working directory with which the service will be started.
OPTIONAL MULTIPLE PARAMETERS OPTIONAL MULTIPLE PARAMETERS
---------------------------- ----------------------------

141
manifest
View file

@ -1,22 +1,43 @@
#!/bin/sh -e #!/bin/sh -e
SERVICE_NAME="${__object_id}"
OS="$(cat "${__global}/explorer/os")" OS="$(cat "${__global}/explorer/os")"
case "${OS}" in case "${OS}" in
debian) debian|devuan)
SUPER_USER_GROUP=root SUPER_USER_GROUP=root
ETC_DIR="/etc"
;; ;;
*bsd) *bsd)
SUPER_USER_GROUP=wheel SUPER_USER_GROUP=wheel
ETC_DIR="/usr/local/etc"
;; ;;
*) *)
echo "Your OS '${OS}' is currently not supported." >&2 echo "Your OS '${OS}' is currently not supported." >&2
exit 1 exit 1
;; ;;
esac esac
INIT="$(cat "${__global}/explorer/init")"
case "${INIT}" in
systemd)
service_definition_require="__systemd_unit/${SERVICE_NAME}.service"
service_command="service ${SERVICE_NAME} %s"
;;
runit|sysvinit)
# We will use runit to manage these services
__runit
export require="__runit"
service_definition_require="__runit_service/${SERVICE_NAME}"
service_command="sv %s ${SERVICE_NAME}"
;;
*)
echo "Init system ${INIT}' is currently not supported." >&2
exit 1
;;
esac
BIN_DIR="/usr/local/bin" BIN_DIR="/usr/local/bin"
ETC_DIR="/etc"
# Ensure the target bin dir exists # Ensure the target bin dir exists
# Care, we never want to remove it :-D # Care, we never want to remove it :-D
@ -29,10 +50,13 @@ STATE="$(cat "${__object}/parameter/state")"
USER="$(cat "${__object}/parameter/user")" USER="$(cat "${__object}/parameter/user")"
GROUP="$(cat "${__object}/parameter/group" 2>/dev/null || true)" GROUP="$(cat "${__object}/parameter/group" 2>/dev/null || true)"
if [ -z "${GROUP}" ]; then if [ -z "${GROUP}" ]; then
if [ "${USER}" != "root" ]; then
GROUP="${USER}" GROUP="${USER}"
else
GROUP="${SUPER_USER_GROUP}"
fi
fi fi
SERVICE_NAME="${__object_id}"
BINARY="$(cat "${__object}/parameter/binary" 2>/dev/null || true)" BINARY="$(cat "${__object}/parameter/binary" 2>/dev/null || true)"
if [ -z "${BINARY}" ]; then if [ -z "${BINARY}" ]; then
@ -62,22 +86,34 @@ fi
SERVICE_DEFINITION="$(cat "${__object}/parameter/service-definition" 2>/dev/null || true)" SERVICE_DEFINITION="$(cat "${__object}/parameter/service-definition" 2>/dev/null || true)"
WORKING_DIRECTORY_PATH="$(cat "${__object}/parameter/working-directory" 2>/dev/null || true)"
if [ -n "${WORKING_DIRECTORY_PATH}" ]; then
WORKING_DIRECTORY_SYSTEMD="WorkingDirectory=${WORKING_DIRECTORY_PATH}"
WORKING_DIRECTORY_RUNIT="cd '${WORKING_DIRECTORY_PATH}'"
fi
DOWNLOAD_URL="$(cat "${__object}/parameter/url")" DOWNLOAD_URL="$(cat "${__object}/parameter/url")"
CHECKSUM="$(cat "${__object}/parameter/checksum")" CHECKSUM="$(cat "${__object}/parameter/checksum")"
SHOULD_VERSION="$(cat "${__object}/parameter/version")" SHOULD_VERSION="$(cat "${__object}/parameter/version")"
# Create a user for the service if it is not root # Create a user for the service if it is not root
USER_HOME_DIR="/root"
if [ "${USER}" != "root" ] && \ if [ "${USER}" != "root" ] && \
[ ! -f "${__object}/parameter/do-not-manage-user" ]; then [ ! -f "${__object}/parameter/do-not-manage-user" ]; then
if [ "${STATE}" = "absent" ]; then if [ "${STATE}" = "absent" ]; then
# When removing, ensure user is not being used # When removing, ensure user is not being used
user_require="__systemd_unit/${SERVICE_NAME}.service" user_require="${service_definition_require}"
fi
USER_HOME_DIR="$(cat "${__object}/parameter/user-home-dir")"
if [ "${USER_HOME_DIR}" != "/nonexistent" ]; then
USER_CREATE_HOME="--create-home"
fi fi
require="${require} ${user_require}" __user "${USER}" \ require="${require} ${user_require}" __user "${USER}" \
--system \ --system \
--state "${STATE}" \ --state "${STATE}" \
--home /nonexistent \ --home "${USER_HOME_DIR}" \
--comment "cdist-managed ${SERVICE_NAME} user" --comment "cdist-managed ${SERVICE_NAME} user" \
${USER_CREATE_HOME}
# Track dependencies # Track dependencies
service_require="${service_require} __user/${USER}" service_require="${service_require} __user/${USER}"
fi fi
@ -100,8 +136,8 @@ fi
INIT="$(cat "${__global}/explorer/init")" # This should setup the object in $service_definition_require
# TODO: Support non-systemd # See above.
case "${INIT}" in case "${INIT}" in
systemd) systemd)
if [ -z "${SERVICE_DEFINITION}" ]; then if [ -z "${SERVICE_DEFINITION}" ]; then
@ -117,6 +153,7 @@ User=${USER}
Group=${GROUP} Group=${GROUP}
ExecStart=${SERVICE_EXEC} ExecStart=${SERVICE_EXEC}
Restart=always Restart=always
${WORKING_DIRECTORY_SYSTEMD}
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
@ -129,14 +166,28 @@ EOF
--enablement-state "enabled" <<EOF --enablement-state "enabled" <<EOF
${SERVICE_DEFINITION} ${SERVICE_DEFINITION}
EOF EOF
service_require="${service_require} __systemd_unit/${SERVICE_NAME}.service"
;; ;;
*) runit|sysvinit)
echo "Init system ${INIT}' is currently not supported." >&2 if [ -z "${SERVICE_DEFINITION}" ]; then
exit 1 SERVICE_DEFINITION="$(cat <<EOF
#!/bin/sh -e
${WORKING_DIRECTORY_RUNIT}
export HOME="\$(getent passwd '${USER}' | cut -d: -f6)"
export USER="${USER}"
export GROUP="${GROUP}"
exec chpst -u "${USER}:${GROUP}" ${SERVICE_EXEC}
EOF
)"
fi
__runit_service "${SERVICE_NAME}" \
--state "${STATE}" \
--log \
--source - <<EOF
${SERVICE_DEFINITION}
EOF
;; ;;
esac esac
service_require="${service_require} ${service_definition_require}"
# Proceed after user and service description have been prepared # Proceed after user and service description have been prepared
export require="${require} ${service_require}" export require="${require} ${service_require}"
@ -144,8 +195,27 @@ export require="${require} ${service_require}"
VERSION_FILE="${BIN_DIR}/.${SERVICE_NAME}.cdist.version" VERSION_FILE="${BIN_DIR}/.${SERVICE_NAME}.cdist.version"
IS_VERSION="$(cat "${__object}/explorer/explorer-version")" IS_VERSION="$(cat "${__object}/explorer/explorer-version")"
if [ "${SHOULD_VERSION}" != "${IS_VERSION}" ] && \
[ "${STATE}" = "present" ]; then if [ "${STATE}" = "absent" ]; then
# Perform cleanup of generated files
for bin_file in ${BINARY} ${EXTRA_BINARIES}; do
__file "${BIN_DIR}/${bin_file}" --state "absent"
done
__file "${VERSION_FILE}" --state "absent"
__file "${CONFIG_FILE_DEST}" --state "absent"
fi
if [ "${STATE}" != "present" ]; then
exit
fi
sv_cmd() {
# This is intentional
# shellcheck disable=SC2059
printf "${service_command}" "$1"
}
if [ "${SHOULD_VERSION}" != "${IS_VERSION}" ]; then
# We are installing the service and there has been a version change # We are installing the service and there has been a version change
# (or it is first-time install) # (or it is first-time install)
TMP_PATH="/tmp/${SERVICE_NAME}-${SHOULD_VERSION}" TMP_PATH="/tmp/${SERVICE_NAME}-${SHOULD_VERSION}"
@ -153,34 +223,40 @@ if [ "${SHOULD_VERSION}" != "${IS_VERSION}" ] && \
# This is what will stop the service, replace the binaries and # This is what will stop the service, replace the binaries and
# start the service again # start the service again
perform_service_upgrade="$(cat <<EOF perform_service_upgrade="$(cat <<EOF
service ${SERVICE_NAME} stop || true $(sv_cmd stop) || true
for bin_file in ${BINARY} ${EXTRA_BINARIES}; do if [ -f '${TMP_PATH}' ]; then
chown root:${SUPER_USER_GROUP} '${TMP_PATH}'
chmod 0555 '${TMP_PATH}'
cp -af '${TMP_PATH}' '${BIN_DIR}/${BINARY}'
else
for bin_file in ${BINARY} ${EXTRA_BINARIES}; do
bin_path="${TMP_PATH}/\${bin_file}" bin_path="${TMP_PATH}/\${bin_file}"
chown root:${SUPER_USER_GROUP} "\${bin_path}" chown root:${SUPER_USER_GROUP} "\${bin_path}"
chmod 0555 "\${bin_path}" chmod 0555 "\${bin_path}"
cp -af "\${bin_path}" "${BIN_DIR}/\${bin_file}" cp -af "\${bin_path}" "${BIN_DIR}/\${bin_file}"
done done
service ${SERVICE_NAME} start || true fi
$(sv_cmd start) || true
EOF EOF
)" )"
if [ -f "${__object}/parameter/unpack" ]; then if [ -f "${__object}/parameter/unpack" ]; then
# TODO: Support files other than .tar.gz UNPACK_EXTENSION="$(cat "${__object}/parameter/unpack-extension")"
UNPACK_ARGS="$(cat "${__object}/parameter/unpack-args" \ UNPACK_ARGS="$(cat "${__object}/parameter/unpack-args" \
2>/dev/null || true)" 2>/dev/null || true)"
# Download packed file # Download packed file
__download "${TMP_PATH}.tar.gz" \ __download "${TMP_PATH}${UNPACK_EXTENSION}" \
--url "${DOWNLOAD_URL}" \ --url "${DOWNLOAD_URL}" \
--download remote \ --download remote \
--sum "${CHECKSUM}" --sum "${CHECKSUM}"
# Unpack file and also perform service upgrade # Unpack file and also perform service upgrade
# shellcheck disable=SC2086 # shellcheck disable=SC2086
require="__download${TMP_PATH}.tar.gz" \ require="__download${TMP_PATH}${UNPACK_EXTENSION}" \
__unpack "${TMP_PATH}.tar.gz" \ __unpack "${TMP_PATH}${UNPACK_EXTENSION}" \
${UNPACK_ARGS} \ ${UNPACK_ARGS} \
--destination "${TMP_PATH}" --destination "${TMP_PATH}"
version_bump_require="__unpack${TMP_PATH}.tar.gz" version_bump_require="__unpack${TMP_PATH}${UNPACK_EXTENSION}"
else else
# Create temp directory # Create temp directory
__directory "${TMP_PATH}" __directory "${TMP_PATH}"
@ -196,18 +272,17 @@ EOF
# Perform update of cdist-managed version file # Perform update of cdist-managed version file
# And also perform service upgrade # And also perform service upgrade
# This is a bug if service_upgrade fails >,<
printf "%s" "${SHOULD_VERSION}" | \ printf "%s" "${SHOULD_VERSION}" | \
require="${version_bump_require}" __file \ require="${version_bump_require}" __file \
"${VERSION_FILE}" \ "${VERSION_FILE}" \
--onchange "${perform_service_upgrade}" \ --onchange "${perform_service_upgrade}" \
--source "-" --source "-"
fi else
# We only restart here if there was a config change
if [ "${STATE}" = "absent" ]; then # but there was not a version change
# Perform cleanup of generated files require="${service_require}" __check_messages \
for bin_file in ${BINARY} ${EXTRA_BINARIES}; do "single_binary_service_${__object_id}" \
__file "${BIN_DIR}/${bin_file}" --state "absent" --pattern "^__file${CONFIG_FILE_DEST}" \
done --execute "$(sv_cmd restart)"
__file "${VERSION_FILE}" --state "absent"
__file "${CONFIG_FILE_DEST}" --state "absent"
fi fi

View file

@ -0,0 +1 @@
.tar.gz

View file

@ -0,0 +1 @@
/nonexistent

View file

@ -7,4 +7,7 @@ service-args
service-exec service-exec
service-description service-description
service-definition service-definition
unpack-extension
unpack-args unpack-args
user-home-dir
working-directory