From Fedora Project Wiki
No edit summary
No edit summary
 
(28 intermediate revisions by 2 users not shown)
Line 2: Line 2:


== Summary ==
== Summary ==
Replace RPM useradd/groupadd shell script fragments in spec files which dedicated RPM macros.
Replace RPM useradd/groupadd shell script fragments in spec files with dedicated RPM macros.


Example RPM spec file. Test with:
== Owner ==
* Name: [[User:harald| Harald Hoyer]]
* Name: [[User:kay| Kay Sievers]]
 
== Current status ==
* Targeted release: [[Releases/30 | Fedora 30 ]]
* Tracker bug: <will be assigned by the Wrangler>
 
== Detailed Description ==
Storing the user and group information a RPM package provides in structured data, opens the possibility to process this data with external tools in a programmatic way, instead of running opaque shell scripts at package installation time.
 
This is part of a long-term effort to gradually convert RPM shell scripts to information available in the RPM database or the /usr filesystem. Ideally, the operating system image creation process will create a /usr tree which contains all the information to instantiate a new bootable system.
The RPM scripts only run at package installation time and their result cannot be reliably reproduced on the already installed system, when the content of /etc  and /var is programmatically recreated from the content in /usr.
 
Because the macros accept all the important useradd/groupadd CLI options, we can extend the macros, if
* rpm gains native fields for user and group
* systemd-sysusers gains more functionality
* we move to a completely different mechanism
* or we can provide compat macros for old Fedora/RHEL versions expanding to the old useradd/groupadd calls
 
Example test.spec file processing:
<pre>
<pre>
rpmbuild --define "_sourcedir $(pwd)" --define "_specdir $(pwd)" --define "_builddir $(pwd)" --define "_srcrpmdir $(pwd)" --define "_rpmdir $(pwd)" -ba test.spec && rpm -qp --scripts noarch/*.rpm
$ rpmbuild --define "_sourcedir $(pwd)" --define "_specdir $(pwd)" --define "_builddir $(pwd)" --define "_srcrpmdir $(pwd)" --define "_rpmdir $(pwd)" -ba test.spec && rpm -qp --scripts noarch/*.rpm && rpm -qp --provides  noarch/*.rpm
 
Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.XfQ6P6
+ umask 022
+ cd /home/kay
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.KFSrt0
+ umask 022
+ cd /home/kay
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.XC996T
+ umask 022
+ cd /home/kay
+ '[' /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64 '!=' / ']'
+ rm -rf /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
++ dirname /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
+ mkdir -p /home/kay/rpmbuild/BUILDROOT
+ mkdir /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
+ mkdir -p /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64/usr/lib/sysusers.d/
+ cat
+ mkdir -p /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64/usr/lib/sysusers.d/
+ cat
+ mkdir -p /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64/usr/lib/sysusers.d/
+ cat
+ /usr/lib/rpm/check-buildroot
+ /usr/lib/rpm/redhat/brp-ldconfig
+ /usr/lib/rpm/brp-compress
+ /usr/lib/rpm/brp-strip /usr/bin/strip
+ /usr/lib/rpm/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump
+ /usr/lib/rpm/brp-strip-static-archive /usr/bin/strip
+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1 1
+ /usr/lib/rpm/brp-python-hardlink
+ /usr/lib/rpm/redhat/brp-mangle-shebangs
Processing files: test-1-1.noarch
Provides: group(group1) group(group2) group(group3) group(group4) test = 1-1 user(user1) user(user2)
Requires(interp): /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Processing files: test-sub-1-1.noarch
Provides: group(subgroup1) test-sub = 1-1
Requires(interp): /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Processing files: foo-1-1.noarch
Provides: foo = 1-1 group(foo1)
Requires(interp): /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
Wrote: /home/kay/test-1-1.src.rpm
Wrote: /home/kay/noarch/test-1-1.noarch.rpm
Wrote: /home/kay/noarch/test-sub-1-1.noarch.rpm
Wrote: /home/kay/noarch/foo-1-1.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.4EIRZN
+ umask 022
+ cd /home/kay
+ /usr/bin/rm -rf /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
+ exit 0
</pre>
</pre>


It has created the following RPMs:
<pre>
<pre>
#################################################################
$ tree noarch/
# macros.sysusers
noarch/
#################################################################
├── foo-1-1.noarch.rpm
%define sysusers_useradd(n:S:c:d:g:G:lmMNors:u:UZ:) %{lua:
├── test-1-1.noarch.rpm
  local package = rpm.expand("%{?-S*}")
└── test-sub-1-1.noarch.rpm
  local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
  if package ~= "" then
      name = name .. "_" .. package
  end
 
  local oldlines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")


  local group = rpm.expand("%{?-g*}")
$ rpm -ihv noarch/*
  local gecko = rpm.expand("%{?-c*}%{?!-c*:-}")
Verifying...                         ################################# [100%]
   local home = rpm.expand("%{?-d*}%{!-d*:-}")
Preparing...                          ################################# [100%]
  local shell = rpm.expand("%{?-s*}%{!-s*:-}")
Creating group subgroup1 with gid 968.
  local uid = rpm.expand("%{?-u*}%{!-u*:-}")
Updating / installing...
  local user = rpm.expand("%{1}")
   1:test-sub-1-1                    ################################# [ 33%]
  local moregroups = rpm.expand("%{?-G*}")
Creating group group1 with gid 967.
    
Creating group group2 with gid 966.
   newline = "%{quote:u\\t" .. user .. "\\t" .. uid .. "\\t" .. gecko .. "\\t" .. home .. "\\t" .. shell .. "}"
Creating group group3 with gid 13.
Creating group group4 with gid 965.
Creating group user1 with gid 964.
Creating user user1 (User 1) with uid 964 and gid 964.
Creating group user2 with gid 963.
Creating user user2 (User 2) with uid 963 and gid 963.
   2:test-1-1                        ################################# [ 67%]
Creating group foo1 with gid 962.
   3:foo-1-1                          ################################# [100%]
</pre>


  if group ~= "" then
The macros created sysusers files which are installed on the system:
      newline = newline .. "%{quote:m\\t" .. user .. "\\t" .. group .. "}"
<pre>
  end
$ cat /usr/lib/sysusers.d/test.conf
g group1 11 -
g group2 12 -
g group3 13 -
g group4 - -
u user1 100 "User 1" /var/user1 /sbin/nologin
m user1 group1
m user1 group3
m user1 group4
u user2 - "User 2" /var/user2 /sbin/nologin
m user2 group2
m user2 group3
m user2 group4


  for group in string.gmatch(moregroups, "[^,]*,?") do
$ cat /usr/lib/sysusers.d/test-sub.conf
      if string.sub(group,-1) == "," then group = string.sub(group, 0, -2) end
g subgroup1 - -
      newline = newline .. "%{quote:m\\t" .. user .. "\\t" .. group .. "}"
  end


  if oldlines == "" then
$ cat /usr/lib/sysusers.d/foo.conf
      rpm.define("sysusers_useradd_" .. name .. " " .. newline)
g foo1 - -
  else
</pre>
      rpm.define("sysusers_useradd_" .. name .. " " .. oldlines .. newline)
  end
}


%define sysusers_groupadd(n:S:g:rfoB:N:) %{lua:
The macros added the scripts to the RPM package which run at installation time:
  local package = rpm.expand("%{?-S*}")
<pre>
  local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
$ rpm -qp --scripts noarch/*.rpm
  if package ~= "" then
preinstall scriptlet (using /bin/sh):
      name = name .. "_" .. package
systemd-sysusers --replace=/usr/lib/sysusers.d/foo.conf - <<EOF
  end
g foo1 - -
  local oldlines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
EOF
preinstall scriptlet (using /bin/sh):
systemd-sysusers --replace=/usr/lib/sysusers.d/test.conf - <<EOF
g group1 11 -
g group2 12 -
g group3 13 -
g group4 - -
u user1 100 "User 1" /var/user1 /sbin/nologin
m user1 group1
m user1 group3
m user1 group4
u user2 - "User 2" /var/user2 /sbin/nologin
m user2 group2
m user2 group3
m user2 group4
EOF
preinstall scriptlet (using /bin/sh):
systemd-sysusers --replace=/usr/lib/sysusers.d/test-sub.conf - <<EOF
g subgroup1 - -
EOF
</pre>


  local gid = rpm.expand("%{?-g*}%{!-g*:-}")
The macros added Provides: to the packages, for all provided users and groups, so other packages can depend on them and pin the packages which provide the users and groups:
  local group = rpm.expand("%{1}")
<pre>
$ rpm -qp --provides  noarch/*.rpm
foo = 1-1
group(foo1)
group(group1)
group(group2)
group(group3)
group(group4)
test = 1-1
user(user1)
user(user2)
group(subgroup1)
test-sub = 1-1
</pre>


  newline = "%{quote:g\\t" .. group .. "\\t" .. gid .. "\\t-}"
/usr/lib/rpm/macros.d/macros.sysusers:
<pre>
#################################################################
# macros.sysusers
#################################################################
%sysusers_useradd(n:S:c:d:g:G:lmMNors:u:UZ:) %{lua:
    -- -n package name
    -- -S subpackage name
    -- -* useradd options
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    if package ~= "" then
        name = name .. "_" .. package
    end
\
    local group = rpm.expand("%{?-g*}")
    local gecko = rpm.expand("%{?-c*}%{?!-c*:-}")
    local home = rpm.expand("%{?-d*}%{!-d*:-}")
    local shell = rpm.expand("%{?-s*}%{!-s*:-}")
    local uid = rpm.expand("%{?-u*}%{!-u*:-}")
    local user = rpm.expand("%{1}")
    local moregroups = rpm.expand("%{?-G*}")
\
    -- Add an ASCII unit separator instead of a newline
    local newlines = "u\\t" .. user .. "\\t" .. uid .. "\\t" .. gecko .. "\\t" .. home .. "\\t" .. shell .. "\\31"
\
    if group ~= "" then
        newlines = newlines .. "m\\t" .. user .. "\\t" .. group .. "\\31"
    end
\
    for group in string.gmatch(moregroups, "[^,]+") do
        newlines = newlines .. "m\\t" .. user .. "\\t" .. group .. "\\31"
    end
\
    local oldlines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    rpm.define("sysusers_useradd_" .. name .. " " .. oldlines .. newlines)
\
    print("Provides: user(" .. user .. ")\\n")
}
 
%sysusers_groupadd(n:S:g:rfoB:N:) %{lua:
    -- -n package name
    -- -S subpackage name
    -- -* groupadd options
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    if package ~= "" then
        name = name .. "_" .. package
    end
\
    local gid = rpm.expand("%{?-g*}%{!-g*:-}")
    local group = rpm.expand("%{1}")
\
    local oldlines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    -- Add an ASCII unit separator instead of a newline
    local newline = "g\\t" .. group .. "\\t" .. gid .. "\\t-\\31"
    rpm.define("sysusers_useradd_" .. name .. " " .. oldlines .. newline)
\
    print("Provides: group(" .. group .. ")\\n")
}


  if oldlines == "" then
%sysusers_pre(n:S:) %{lua:
      rpm.define("sysusers_useradd_" .. name .. " " .. newline)
    -- -n package name
  else
    -- -S subpackage name
      rpm.define("sysusers_useradd_" .. name .. " " .. oldlines .. newline)
\
  end
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    local filename = name
    if package ~= "" then
        filename = name .. "-" .. package
        name = name .. "_" .. package
    end
\
    print("systemd-sysusers --replace=" .. rpm.expand("%{_sysusersdir}/") .. filename .. ".conf - <<EOF\\n")
    local lines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    -- Split string at the ASCII unit separator
    for line in string.gmatch(lines, "[^\\31]+") do
        print(line .. "\\n")
    end
    print("EOF\\n")
}
}


%define sysusers_pre(n:S:) %{lua:
%sysusers_install(n:S:) %{lua:
  local package = rpm.expand("%{?-S*}")
    -- -n package name
  local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    -- -S subpackage name
  local filename = name
\
  if package ~= "" then
    local package = rpm.expand("%{?-S*}")
      filename = name .. "-" .. package
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
      name = name .. "_" .. package
    local filename = name
  end
    if package ~= "" then
  local lines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
        filename = name .. "-" .. package
  print("systemd-sysusers --replace=" .. rpm.expand("%{_sysusersdir}/") .. filename .. ".conf - <<EOF\\n")
        name = name .. "_" .. package
  for line in string.gmatch(lines, "\\31[^\\31]*\\31") do print(string.sub(line, 2, -2) .."\\n") end
    end
  print("EOF\\n")
\
    print("mkdir -p " .. rpm.expand("%{buildroot}") .. rpm.expand("%{_sysusersdir}/") .. "\\n")
    print("cat >" .. rpm.expand("%{buildroot}") .. rpm.expand("%{_sysusersdir}/") .. filename .. ".conf <<EOF\\n")
    local lines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    -- Split string at the ASCII unit separator
    for line in string.gmatch(lines, "[^\\31]+") do
        print(line .. "\\n")
    end
    print("EOF\\n")
}
}


%define sysusers_install(n:S:) %{lua:
%sysusers_files(n:S:) %{lua:
  local package = rpm.expand("%{?-S*}")
    -- -n package name
  local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    -- -S subpackage name
  local filename = name
\
  if package ~= "" then
    local package = rpm.expand("%{?-S*}")
      filename = name .. "-" .. package
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
      name = name .. "_" .. package
    local filename = name
  end
    if package ~= "" then
  local lines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
      filename = name .. "-" .. package
  print("mkdir -p " .. rpm.expand("%{buildroot}") .. rpm.expand("%{_sysusersdir}/") .. "\\n")
      name = name .. "_" .. package
  print("cat >" .. rpm.expand("%{buildroot}") .. rpm.expand("%{_sysusersdir}/") .. filename .. ".conf <<EOF\\n")
    end
  for line in string.gmatch(lines, "\\31[^\\31]*\\31") do print(string.sub(line, 2, -2) .."\\n") end
\
  print("EOF\\n")
    print(rpm.expand("%{_sysusersdir}/") .. filename .. ".conf\\n")
}
}
</pre>


test.spec
<pre>
#################################################################
#################################################################
# test.spec
# test.spec
Line 107: Line 314:
BuildArch:      noarch
BuildArch:      noarch


%sysusers_groupadd -r -g 11 group1
%define GROUPNAME1 group1
%sysusers_groupadd -r -g 12 group2
%define USERNAME1  user1
%sysusers_groupadd -r -g 13 group3
 
%sysusers_groupadd -r -g 14 group4
%sysusers_groupadd -g 11 %{GROUPNAME1}
%sysusers_useradd -r -g group1 -G group3,group4 -u 100 -d /var/user1 -s /sbin/nologin -c %{quote:"User 1"} user1
%sysusers_groupadd -g 12 group2
%sysusers_useradd -r -g group2 -G group3,group4 -u 101 -d /var/user2 -s /sbin/nologin -c %{quote:"User 2"} user2
%sysusers_groupadd -g 13 group3
%sysusers_groupadd group4
%sysusers_useradd -g group1 -G group3,group4 -u 100 -d /var/user1 -s /sbin/nologin -c %{quote:"User 1"} %{USERNAME1}
%sysusers_useradd -g group2 -G group3,group4 -d /var/user2 -s /sbin/nologin -c %{quote:"User 2"} user2


%description
%description


%package sub
%package sub
Summary:       sub
Summary:         sub
%sysusers_groupadd -S sub -r -g 21 subgroup1
%sysusers_groupadd -S sub subgroup1
%description sub
%description sub


%package -n foo
%package -n foo
Summary:       foo
Summary:         foo
%sysusers_groupadd -n foo -r -g 31 foo1
%sysusers_groupadd -n foo foo1
%description -n foo
%description -n foo


Line 145: Line 355:


%files
%files
%{_sysusersdir}/%{name}.conf
%sysusers_files


%files sub
%files sub
%{_sysusersdir}/%{name}-sub.conf
%sysusers_files -S sub


%files -n foo
%files -n foo
%{_sysusersdir}/foo.conf
%sysusers_files -n foo
</pre>
</pre>
== Owner ==
* Name: [[User:harald| Harald Hoyer]]
* Name: [[User:kay| Kay Sievers]]
== Current status ==
* Targeted release: [[Releases/30 | Fedora 30 ]]
* Tracker bug: <will be assigned by the Wrangler>
== Detailed Description ==
Storing the user and group information in structured data allows to process this data with external tools in a programmatic way instead of running opaque shell scripts.


== Benefit to Fedora ==
== Benefit to Fedora ==
 
As a long-term goal, with enough structured data in the installed system, it would carry the needed information to reset itself to a factory default. The system users are one piece of the puzzle to provide the infrastructure to do that.
<!-- What is the benefit to the distribution?  Will the software we generate be improved? How will the process of creating Fedora releases be improved?
 
      Be sure to include the following areas if relevant:
      If this is a major capability update, what has changed?
          For example: This change introduces Python 5 that runs without the Global Interpreter Lock and is fully multithreaded.
      If this is a new functionality, what capabilities does it bring?
          For example: This change allows package upgrades to be performed automatically and rolled-back at will.
      Does this improve some specific package or set of packages?
          For example: This change modifies a package to use a different language stack that reduces install size by removing dependencies.
      Does this improve specific Spins or Editions?
          For example: This change modifies the default install of Fedora Workstation to be more in line with the base install of Fedora Server.
      Does this make the distribution more efficient?
          For example: This change replaces thousands of individual %post scriptlets in packages with one script that runs at the end.
      Is this an improvement to maintainer processes?
          For example: Gating Fedora packages on automatic QA tests will make rawhide more stable and allow changes to be implemented more smoothly.
      Is this an improvement targeted as specific contributors?
          For example: Ensuring that a minimal set of tools required for contribution to Fedora are installed by default eases the onboarding of new contributors.
 
    When a Change has multiple benefits, it's better to list them all.
 
    Consider these Change pages from previous editions as inspiration:
    https://fedoraproject.org/wiki/Changes/Annobin (low-level and technical, invisible to users)
    https://fedoraproject.org/wiki/Changes/ParallelInstallableDebuginfo (low-level, but visible to advanced users)
    https://fedoraproject.org/wiki/Changes/VirtualBox_Guest_Integration (primarily a UX change)
    https://fedoraproject.org/wiki/Changes/NoMoreAlpha (an improvement to distro processes)
    https://fedoraproject.org/wiki/Changes/perl5.26 (major upgrade to a popular software stack, visible to users of that stack)
-->


== Scope ==
== Scope ==
* Proposal owners:
* Proposal owners:
<!-- What work do the feature owners have to accomplish to complete the feature in time for release?  Is it a large change affecting many parts of the distribution or is it a very isolated change? What are those changes?-->
There are currently 378 (2018-11-03) spec files in the Fedora repository which call useradd/groupadd. This feature is about the mass-conversion of all these spec files.


* Other developers: N/A (not a System Wide Change) <!-- REQUIRED FOR SYSTEM WIDE CHANGES -->
* Other developers: N/A (not a System Wide Change) <!-- REQUIRED FOR SYSTEM WIDE CHANGES -->
Line 209: Line 381:


* Policies and guidelines: N/A (not a System Wide Change) <!-- REQUIRED FOR SYSTEM WIDE CHANGES -->
* Policies and guidelines: N/A (not a System Wide Change) <!-- REQUIRED FOR SYSTEM WIDE CHANGES -->
<!-- Do the packaging guidelines or other documents need to be updated for this feature?  If so, does it need to happen before or after the implementation is done?  If a FPC ticket exists, add a link here. -->
The packaging guidelines need to be updated to document the use of the sysusers macros instead of useradd/groupadd shell snippets.


* Trademark approval: N/A (not needed for this Change)
* Trademark approval: N/A (not needed for this Change)

Latest revision as of 16:26, 15 November 2018

Streamline useradd/groupadd calls in RPM spec files

Summary

Replace RPM useradd/groupadd shell script fragments in spec files with dedicated RPM macros.

Owner

Current status

  • Targeted release: Fedora 30
  • Tracker bug: <will be assigned by the Wrangler>

Detailed Description

Storing the user and group information a RPM package provides in structured data, opens the possibility to process this data with external tools in a programmatic way, instead of running opaque shell scripts at package installation time.

This is part of a long-term effort to gradually convert RPM shell scripts to information available in the RPM database or the /usr filesystem. Ideally, the operating system image creation process will create a /usr tree which contains all the information to instantiate a new bootable system. The RPM scripts only run at package installation time and their result cannot be reliably reproduced on the already installed system, when the content of /etc and /var is programmatically recreated from the content in /usr.

Because the macros accept all the important useradd/groupadd CLI options, we can extend the macros, if

  • rpm gains native fields for user and group
  • systemd-sysusers gains more functionality
  • we move to a completely different mechanism
  • or we can provide compat macros for old Fedora/RHEL versions expanding to the old useradd/groupadd calls

Example test.spec file processing:

$ rpmbuild --define "_sourcedir $(pwd)" --define "_specdir $(pwd)" --define "_builddir $(pwd)" --define "_srcrpmdir $(pwd)" --define "_rpmdir $(pwd)" -ba test.spec && rpm -qp --scripts noarch/*.rpm && rpm -qp --provides  noarch/*.rpm

Executing(%prep): /bin/sh -e /var/tmp/rpm-tmp.XfQ6P6
+ umask 022
+ cd /home/kay
+ exit 0
Executing(%build): /bin/sh -e /var/tmp/rpm-tmp.KFSrt0
+ umask 022
+ cd /home/kay
+ exit 0
Executing(%install): /bin/sh -e /var/tmp/rpm-tmp.XC996T
+ umask 022
+ cd /home/kay
+ '[' /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64 '!=' / ']'
+ rm -rf /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
++ dirname /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
+ mkdir -p /home/kay/rpmbuild/BUILDROOT
+ mkdir /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
+ mkdir -p /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64/usr/lib/sysusers.d/
+ cat
+ mkdir -p /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64/usr/lib/sysusers.d/
+ cat
+ mkdir -p /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64/usr/lib/sysusers.d/
+ cat
+ /usr/lib/rpm/check-buildroot
+ /usr/lib/rpm/redhat/brp-ldconfig
+ /usr/lib/rpm/brp-compress
+ /usr/lib/rpm/brp-strip /usr/bin/strip
+ /usr/lib/rpm/brp-strip-comment-note /usr/bin/strip /usr/bin/objdump
+ /usr/lib/rpm/brp-strip-static-archive /usr/bin/strip
+ /usr/lib/rpm/brp-python-bytecompile /usr/bin/python 1 1
+ /usr/lib/rpm/brp-python-hardlink
+ /usr/lib/rpm/redhat/brp-mangle-shebangs
Processing files: test-1-1.noarch
Provides: group(group1) group(group2) group(group3) group(group4) test = 1-1 user(user1) user(user2)
Requires(interp): /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Processing files: test-sub-1-1.noarch
Provides: group(subgroup1) test-sub = 1-1
Requires(interp): /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Processing files: foo-1-1.noarch
Provides: foo = 1-1 group(foo1)
Requires(interp): /bin/sh
Requires(rpmlib): rpmlib(CompressedFileNames) <= 3.0.4-1 rpmlib(FileDigests) <= 4.6.0-1 rpmlib(PayloadFilesHavePrefix) <= 4.0-1
Requires(pre): /bin/sh
Checking for unpackaged file(s): /usr/lib/rpm/check-files /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
Wrote: /home/kay/test-1-1.src.rpm
Wrote: /home/kay/noarch/test-1-1.noarch.rpm
Wrote: /home/kay/noarch/test-sub-1-1.noarch.rpm
Wrote: /home/kay/noarch/foo-1-1.noarch.rpm
Executing(%clean): /bin/sh -e /var/tmp/rpm-tmp.4EIRZN
+ umask 022
+ cd /home/kay
+ /usr/bin/rm -rf /home/kay/rpmbuild/BUILDROOT/test-1-1.x86_64
+ exit 0

It has created the following RPMs:

$ tree noarch/
noarch/
├── foo-1-1.noarch.rpm
├── test-1-1.noarch.rpm
└── test-sub-1-1.noarch.rpm

$ rpm -ihv noarch/*
Verifying...                          ################################# [100%]
Preparing...                          ################################# [100%]
Creating group subgroup1 with gid 968.
Updating / installing...
   1:test-sub-1-1                     ################################# [ 33%]
Creating group group1 with gid 967.
Creating group group2 with gid 966.
Creating group group3 with gid 13.
Creating group group4 with gid 965.
Creating group user1 with gid 964.
Creating user user1 (User 1) with uid 964 and gid 964.
Creating group user2 with gid 963.
Creating user user2 (User 2) with uid 963 and gid 963.
   2:test-1-1                         ################################# [ 67%]
Creating group foo1 with gid 962.
   3:foo-1-1                          ################################# [100%]

The macros created sysusers files which are installed on the system:

$ cat /usr/lib/sysusers.d/test.conf
g	group1	11	-
g	group2	12	-
g	group3	13	-
g	group4	-	-
u	user1	100	"User 1"	/var/user1	/sbin/nologin
m	user1	group1
m	user1	group3
m	user1	group4
u	user2	-	"User 2"	/var/user2	/sbin/nologin
m	user2	group2
m	user2	group3
m	user2	group4

$ cat /usr/lib/sysusers.d/test-sub.conf 
g	subgroup1	-	-

$ cat /usr/lib/sysusers.d/foo.conf 
g	foo1	-	-

The macros added the scripts to the RPM package which run at installation time:

$ rpm -qp --scripts noarch/*.rpm
preinstall scriptlet (using /bin/sh):
systemd-sysusers --replace=/usr/lib/sysusers.d/foo.conf - <<EOF
g	foo1	-	-
EOF
preinstall scriptlet (using /bin/sh):
systemd-sysusers --replace=/usr/lib/sysusers.d/test.conf - <<EOF
g	group1	11	-
g	group2	12	-
g	group3	13	-
g	group4	-	-
u	user1	100	"User 1"	/var/user1	/sbin/nologin
m	user1	group1
m	user1	group3
m	user1	group4
u	user2	-	"User 2"	/var/user2	/sbin/nologin
m	user2	group2
m	user2	group3
m	user2	group4
EOF
preinstall scriptlet (using /bin/sh):
systemd-sysusers --replace=/usr/lib/sysusers.d/test-sub.conf - <<EOF
g	subgroup1	-	-
EOF

The macros added Provides: to the packages, for all provided users and groups, so other packages can depend on them and pin the packages which provide the users and groups:

$ rpm -qp --provides  noarch/*.rpm
foo = 1-1
group(foo1)
group(group1)
group(group2)
group(group3)
group(group4)
test = 1-1
user(user1)
user(user2)
group(subgroup1)
test-sub = 1-1

/usr/lib/rpm/macros.d/macros.sysusers:

#################################################################
# macros.sysusers
#################################################################
%sysusers_useradd(n:S:c:d:g:G:lmMNors:u:UZ:) %{lua:
    -- -n package name
    -- -S subpackage name
    -- -* useradd options
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    if package ~= "" then
        name = name .. "_" .. package
    end
\
    local group = rpm.expand("%{?-g*}")
    local gecko = rpm.expand("%{?-c*}%{?!-c*:-}")
    local home = rpm.expand("%{?-d*}%{!-d*:-}")
    local shell = rpm.expand("%{?-s*}%{!-s*:-}")
    local uid = rpm.expand("%{?-u*}%{!-u*:-}")
    local user = rpm.expand("%{1}")
    local moregroups = rpm.expand("%{?-G*}")
\
    -- Add an ASCII unit separator instead of a newline
    local newlines = "u\\t" .. user .. "\\t" .. uid .. "\\t" .. gecko .. "\\t" .. home .. "\\t" .. shell .. "\\31"
\
    if group ~= "" then
        newlines = newlines .. "m\\t" .. user .. "\\t" .. group .. "\\31"
    end
\
    for group in string.gmatch(moregroups, "[^,]+") do
        newlines = newlines .. "m\\t" .. user .. "\\t" .. group .. "\\31"
    end
\
    local oldlines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    rpm.define("sysusers_useradd_" .. name .. " " .. oldlines .. newlines)
\
    print("Provides: user(" .. user .. ")\\n")
}

%sysusers_groupadd(n:S:g:rfoB:N:) %{lua:
    -- -n package name
    -- -S subpackage name
    -- -* groupadd options
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    if package ~= "" then
        name = name .. "_" .. package
    end
\
    local gid = rpm.expand("%{?-g*}%{!-g*:-}")
    local group = rpm.expand("%{1}")
\
    local oldlines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    -- Add an ASCII unit separator instead of a newline
    local newline = "g\\t" .. group .. "\\t" .. gid .. "\\t-\\31"
    rpm.define("sysusers_useradd_" .. name .. " " .. oldlines .. newline)
\
    print("Provides: group(" .. group .. ")\\n")
}

%sysusers_pre(n:S:) %{lua:
    -- -n package name
    -- -S subpackage name
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    local filename = name
    if package ~= "" then
        filename = name .. "-" .. package
        name = name .. "_" .. package
    end
\
    print("systemd-sysusers --replace=" .. rpm.expand("%{_sysusersdir}/") .. filename .. ".conf - <<EOF\\n")
    local lines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    -- Split string at the ASCII unit separator
    for line in string.gmatch(lines, "[^\\31]+") do
        print(line .. "\\n")
    end
    print("EOF\\n")
}

%sysusers_install(n:S:) %{lua:
    -- -n package name
    -- -S subpackage name
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    local filename = name
    if package ~= "" then
        filename = name .. "-" .. package
        name = name .. "_" .. package
    end
\
    print("mkdir -p " .. rpm.expand("%{buildroot}") .. rpm.expand("%{_sysusersdir}/") .. "\\n")
    print("cat >" .. rpm.expand("%{buildroot}") .. rpm.expand("%{_sysusersdir}/") .. filename .. ".conf <<EOF\\n")
    local lines = rpm.expand("%{?sysusers_useradd_" .. name .. "}")
    -- Split string at the ASCII unit separator
    for line in string.gmatch(lines, "[^\\31]+") do
        print(line .. "\\n")
    end
    print("EOF\\n")
}

%sysusers_files(n:S:) %{lua:
    -- -n package name
    -- -S subpackage name
\
    local package = rpm.expand("%{?-S*}")
    local name = rpm.expand("%{?!-n*:%{name}}%{?-n*}")
    local filename = name
    if package ~= "" then
       filename = name .. "-" .. package
       name = name .. "_" .. package
    end
\
    print(rpm.expand("%{_sysusersdir}/") .. filename .. ".conf\\n")
}

test.spec

#################################################################
# test.spec
#################################################################
Name:           test
Version:        1
Release:        1
Summary:        test
License:        MIT
BuildArch:      noarch

%define GROUPNAME1 group1
%define USERNAME1  user1

%sysusers_groupadd -g 11 %{GROUPNAME1}
%sysusers_groupadd -g 12 group2
%sysusers_groupadd -g 13 group3
%sysusers_groupadd group4
%sysusers_useradd -g group1 -G group3,group4 -u 100 -d /var/user1 -s /sbin/nologin -c %{quote:"User 1"} %{USERNAME1}
%sysusers_useradd -g group2 -G group3,group4 -d /var/user2 -s /sbin/nologin -c %{quote:"User 2"} user2

%description

%package sub
Summary:         sub
%sysusers_groupadd -S sub subgroup1
%description sub

%package -n foo
Summary:         foo
%sysusers_groupadd -n foo foo1
%description -n foo

%pre
%sysusers_pre

%pre sub
%sysusers_pre -S sub

%pre -n foo
%sysusers_pre -n foo

%prep

%build

%install
%sysusers_install
%sysusers_install -n foo
%sysusers_install -S sub

%files
%sysusers_files

%files sub
%sysusers_files -S sub

%files -n foo
%sysusers_files -n foo

Benefit to Fedora

As a long-term goal, with enough structured data in the installed system, it would carry the needed information to reset itself to a factory default. The system users are one piece of the puzzle to provide the infrastructure to do that.

Scope

  • Proposal owners:

There are currently 378 (2018-11-03) spec files in the Fedora repository which call useradd/groupadd. This feature is about the mass-conversion of all these spec files.

  • Other developers: N/A (not a System Wide Change)
  • Policies and guidelines: N/A (not a System Wide Change)

The packaging guidelines need to be updated to document the use of the sysusers macros instead of useradd/groupadd shell snippets.

  • Trademark approval: N/A (not needed for this Change)

Upgrade/compatibility impact

N/A (not a System Wide Change)

How To Test

N/A (not a System Wide Change)

User Experience

Dependencies

N/A (not a System Wide Change)

Contingency Plan

  • Contingency mechanism: (What to do? Who will do it?) N/A (not a System Wide Change)
  • Contingency deadline: N/A (not a System Wide Change)
  • Blocks release? N/A (not a System Wide Change), Yes/No
  • Blocks product? product

Documentation

N/A (not a System Wide Change)

Release Notes