--- /dev/null
+# Copyright 1999-2017 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+# $Id$
+
+# @ECLASS: sys-user.eclass
+# @MAINTAINER:
+# Michael Orlitzky <mjo@gentoo.org>
+# @BLURB: handle installation and removal of system users.
+# @DESCRIPTION:
+# This eclass does most of the work for the sys-user/ packages that
+# supply system user accounts.
+
+inherit user
+
+EXPORT_FUNCTIONS src_unpack src_prepare src_configure src_compile src_install src_test pkg_preinst pkg_postinst pkg_prerm
+
+# This is hard-coded to the package name. If you want a different
+# username, use a different package name. This is a nice way to prevent
+# different people from claiming the same username.
+SYS_USER_NAME="${PN}"
+
+# @ECLASS-VARIABLE: SYS_USER_GROUPS
+# @DESCRIPTION:
+# etc.
+: ${SYS_USER_GROUPS:=${PN}}
+
+# @ECLASS-VARIABLE: SYS_USER_UID
+# @DESCRIPTION:
+# etc. (use -1 to get next available using user.eclass)
+: ${SYS_USER_UID:=-1}
+
+# In many cases, if the UID of a user changes, packages depending on it
+# will want to rebuild. We always use SLOT=0, because you can't install
+# the same user twice. Then we use the UID as our subslot so that
+# subslot deps can be used to rebuild packages when our UID changes.
+SLOT="0/${SYS_USER_UID}"
+
+# @ECLASS-VARIABLE: SYS_USER_HOME
+# @DESCRIPTION:
+# etc. (use -1 to get user.eclass default)
+: ${SYS_USER_HOME:=-1}
+
+# @ECLASS-VARIABLE: SYS_USER_SHELL
+# @DESCRIPTION:
+# etc. (use -1 to get user.eclass default)
+: ${SYS_USER_SHELL:=-1}
+
+case ${EAPI} in
+ 6) ;;
+ *)
+ die "${ECLASS} is not compatible with EAPI=${EAPI}"
+esac
+
+# Depend on any groups we might need.
+for _group in ${SYS_USER_GROUPS}; do
+ DEPEND+=" sys-group/${_group} "
+ RDEPEND+=" sys-group/${_group}:= "
+done
+unset _group
+
+S="${WORKDIR}"
+
+sys-user_src_unpack() { :; }
+sys-user_src_prepare() { :; }
+sys-user_src_configure() { :; }
+sys-user_src_compile() { :; }
+sys-user_src_test() { :; }
+
+sys-user_next_uid() {
+ local euid;
+ for (( euid = 101; euid <= 999; euid++ )); do
+ [[ -z $(egetent passwd "${euid}") ]] && break
+ done
+ if (( "${euid}" == 999 )); then
+ die "out of available UIDs!"
+ else
+ echo "${euid}"
+ fi
+}
+
+sys-user_src_prepare() {
+ eapply_user # whatever
+
+ if [[ -n $(egetent passwd "${SYS_USER_NAME}") ]]; then
+ # UPGRADE PATH: This user already exists, so if the eclass
+ # consumer doesn't care about some settings, we can reuse the
+ # pre-existing ones.
+ #
+ # This is also useful for sys-user package upgrades, becaused it
+ # prevents us from incrementing the UID pointlessly on a
+ # reinstall. Usually that will prevent rebuilds of depending
+ # packages, and is crucial to our ability to use subslot deps to
+ # cause rebuilds when the UID changes. We don't want the UID to
+ # change if the subslot doesn't change, and the subslot for "I
+ # don't care about the UID" will always be "-1", so the UID
+ # shouldn't generally change either when SYS_USER_UID=-1.
+ if (( "${SYS_USER_UID}" == -1 )); then
+ SYS_USER_UID=$(id --real --user "${SYS_USER_NAME}")
+ fi
+
+ if (( "${SYS_USER_HOME}" == -1 )); then
+ SYS_USER_HOME=$(egethome "${SYS_USER_NAME}")
+ fi
+
+ if (( "${SYS_USER_SHELL}" == -1 )); then
+ SYS_USER_SHELL=$(egetshell "${SYS_USER_NAME}")
+
+ if [[ ${SYS_USER_SHELL} == */false ]] || \
+ [[ ${SYS_USER_SHELL} == */nologin ]]; then
+ # WHYYYYY? enewuser complains if we try to set a default
+ # shell explicitly.
+ SYS_USER_SHELL="-1"
+ fi
+ fi
+ elif (( "${SYS_USER_UID}" == -1 )); then
+ # There is no pre-existing user (i.e. this isn't along the
+ # upgrade path), and the consumer says he doesn't care about the
+ # UID, so pick the next one.
+ SYS_USER_UID=$(sys-user_next_uid)
+ fi
+
+ # We do something with this in src_install.
+ touch "${T}/${SYS_USER_UID}" || die
+}
+
+sys-user_src_install() {
+ # Install a placeholder file to /var/lib/sys-user/$uid. This will
+ # cause collisions if two packages try to install users with the
+ # same UID. The same problem potentially exists with the username,
+ # but as long as SYS_USER_NAME is hard-coded to $PN, that shouldn't
+ # be possible.
+ #
+ # Beware, this only works if SYS_USER_UID is guaranteed to have a
+ # real UID and not, for example, -1. That is taken care of in
+ # src_prepare() for now.
+ insinto "/var/lib/sys-user"
+ doins "${T}/${SYS_USER_UID}"
+
+ # TODO: do we want to try to create the user's home directory within
+ # the package manager so that it can be cleaned up later? The
+ # obvious problem with that plan is that we need to be careful not
+ # to give the new user ownership of e.g. /dev/null.
+}
+
+sys-user_pkg_preinst() {
+ if [[ -z $(egetent passwd "${SYS_USER_NAME}") ]]; then
+ # The user does not already exist. This is the nice and easy
+ # case because no matter how we got here, we want to go ahead
+ # and create the (new) user.
+ enewuser "${SYS_USER_NAME}" \
+ "${SYS_USER_UID}" \
+ "${SYS_USER_SHELL}" \
+ "${SYS_USER_HOME}" \
+ "${SYS_USER_GROUPS}" \
+ || die "failed to add user ${SYS_USER_NAME}"
+ elif [[ -n "${REPLACING_VERSIONS}" ]]; then
+ # This is an upgrade from an existing sys-user package. This
+ # case is a little bit weird. If we do it in preinst(), then it
+ # will happen before the "old" user is removed in
+ # pkg_prerm(). Except the old user and the new user are the
+ # same, so if we overwrite the existing user here, then prerm
+ # for the version that created it will clobber our new entry.
+ #
+ # We also can't just LEAVE the old user there, because then no
+ # upgrade happens.
+ #
+ # Uh, let's do this case in pkg_postinst so that it happens
+ # after the old version's prerm.
+ :
+ else
+ # UPGRADE PATH: Ok, the user exists but this isn't an upgrade of
+ # a sys-user package. This is the upgrade path from the old
+ # style of user/group management to the new style. What can we
+ # do? We could make it policy that old users must be compatible
+ # with the new ones, but that entails hard-coding UIDs that
+ # don't need to be hard-coded.
+ #
+ # Instead lets see if the new user is compatible with the old
+ # (it usually will be), and then only bail out if there's a real
+ # problem.
+ local oldhome=$(egethome "${SYS_USER_NAME}")
+ local oldshell=$(egetshell "${SYS_USER_NAME}")
+ local olduid=$(id --real --user "${SYS_USER_NAME}")
+
+ if [[ "${oldhome}" -ne "${SYS_USER_HOME}" ]]; then
+ die "home directory conflict for new user ${SYS_USER_HOME}"
+ fi
+
+ if [[ "${oldhshell}" -ne "${SYS_USER_SHELL}" ]]; then
+ die "shell conflict for new user ${SYS_USER_HOME}"
+ fi
+
+ if [[ "${olduid}" -ne "${SYS_USER_UID}" ]]; then
+ die "UID conflict for new user ${SYS_USER_NAME}"
+ fi
+
+ # The user already exists, so all we have left to do is to try
+ # to append SYS_USER_GROUPS to the existing groups. The "usermod"
+ # tool expects a comma-separated list, so change our spaces to
+ # commas. This does succeed if you append duplicates.
+ usermod --append --groups "${SYS_USER_GROUPS// /,}" \
+ || die "failed to append groups to existing user ${SYS_USER_NAME}"
+ fi
+}
+
+sys-user_pkg_postinst() {
+ if [[ -n "${REPLACING_VERSIONS}" ]]; then
+ # This is an upgrade from a previous version of a sys-user
+ # package. This case has to be handled carefully to make sure
+ # that the pkg_prerm() of the old version doesn't remove the user
+ # that this new version is going to add. At this point, in our
+ # pkg_postinst(), the old version's pkg_prerm() phase should have
+ # already happened.
+ if [[ -n $(egetent passwd "${SYS_USER_NAME}") ]]; then
+ die "User ${SYS_USER_NAME} already exists during an upgrade."
+ else
+ enewuser "${SYS_USER_NAME}" \
+ "${SYS_USER_UID}" \
+ "${SYS_USER_SHELL}" \
+ "${SYS_USER_HOME}" \
+ "${SYS_USER_GROUPS}" \
+ || die "failed to add user ${SYS_USER_NAME}"
+ fi
+ fi
+}
+
+sys-user_pkg_prerm() {
+ if [[ -z $(egetent passwd "${SYS_USER_NAME}") ]]; then
+ # We have successfully done nothing.
+ ewarn "Tried to remove nonexistent user ${SYS_USER_NAME}."
+ else
+ userdel "${SYS_USER_NAME}" || \
+ die "failed to remove user ${SYS_USER_NAME}"
+ einfo "Removed user ${SYS_USER_NAME} from the system."
+ fi
+}