UCF(1)

๐ŸŒธ UCF(1) โ€” the Debian GNU/Linux manual page, but make it bestie ๐Ÿ’…

okay so. ucf stands for Update Configuration File, and fr fr it's the girl who keeps your config files from getting steamrolled. it lives in the Debian GNU/Linux manual, section 1, and it has main character energy because like every package maintainer on Debian depends on her. periodt. ๐Ÿ‘‘


โœจ NAME โœจ

ucf โ€” Update Configuration File: preserve user changes in configuration files ๐Ÿซถ

like. literally that. she updates your config without ghosting your edits. iconic behaviour only.


โœจ SYNOPSIS โœจ

ucf [options] <New File> <Destination>
ucf [options] --purge <Destination>

two moods, two forms. that's it, that's the whole vibe check.


โœจ DESCRIPTION โœจ

okay so bestie, here's the tea. โ˜•

this utility gives you a whole means of asking the user whether or not to accept new versions of configuration files that the package maintainer ships โ€” and it comes with a bunch of heuristics designed to minimize interaction time, because nobody wants to answer forty prompts at 2am during an upgrade. it uses debconf to talk to the user, as per Debian policy. rules are rules, and we follow them ๐Ÿ’…

in that SYNOPSIS up there: New file is the configuration file as provided by the package โ€” either shipped with the package, or generated by the maintainer scripts on the fly. and Destination is the location (usually under /etc) where the real configuration file lives, and is potentially modified by the end user. the whole point is protecting that one. ๐Ÿฅบ

now, since the files being edited would be real files and not symbolic links, ucf follows and resolves symbolic links before acting. as far as possible, ucf also attempts to preserve the ownership and permissions of the New file as it is copied to the new location. she's careful like that.


this script attempts to provide conffile-like handling for files installed under /etc that are not shipped in a Debian package, but handled by the postinst instead. Debian policy states that files under /etc which are configuration files must preserve user changes โ€” and this applies to files handled by maintainer scripts as well. so using ucf, one may ship a bunch of default configuration files somewhere in /usr (/usr/share/<pkg> is a good location, js) and maintain files in /etc, preserving user changes and in general offering the same facilities while upgrading that dpkg normally provides for โ€œconffilesโ€. slay. ๐Ÿ”ฅ

additionally, this script provides facilities for transitioning a file that had not been provided conffile-like protection so it can come under this schema, and it attempts to minimize questions asked at install time. and ngl, the transitioning facility is better than the one offered by dpkg while transitioning a file from a non-conffile to conffile status. not me hyping the tool but it's true. ๐Ÿ˜Œ

the second form in the SYNOPSIS above is for purging information about the configuration file when the package is purged โ€” and it is critical for allowing smooth reinstallations. do not skip it. i'm serious. >:(

during the course of operations, when working with configuration files, ucf optionally creates copies of versions of the configuration file in question. for example, a file with the suffix ucf-old holds the old version of a configuration file replaced by ucf. also, copies of the configuration file with the suffixes ucf-new and ucf-dist may be created. and the maintainer scripts should consider purging copies of the configuration file with these extensions during purge. ๐Ÿงน


โœจ OPTIONS โœจ

  • -h, --help โ€” prints a short usage message. your lil cheat sheet ๐Ÿฅฐ
  • -n, --no-action โ€” dry run, bestie. prints the actions that would be taken if the script is invoked, but takes no action. a little look, no touch. ๐Ÿ‘€
  • -d[n], --debug=[n] โ€” set the debug level to the (optional) level n (n defaults to 1). โš ๏ธ please note there must be no spaces before the optional digit n. this turns on copious debugging information. copious. like, a lot.
  • -p, --purge โ€” removes all vestiges of the file from the state hashfile. this is required to allow a package to be reinstalled after it is purged; since otherwise, the real configuration file is removed, but it remains in the hash file; and on reinstall no action is taken, since the md5sum of the new file matches that in the hashfile. in short: remember to use this option in the postrm for every configuration file managed by ucf when the package is being purged (assuming ucf itself exists).
๐Ÿ“Œ note: ucf does not actually touch the file on disk in this operation, so any file removals are still the responsibility of the calling package. she's not your cleanup crew, she's just forgetting the receipts.
  • -v, --verbose โ€” makes the script be very very verbose about setting internal variables. she will not stop talking. ๐Ÿ’€
  • -P foo, --package foo โ€” don't follow dpkg-divert diversions by package foo when updating configuration files.
  • -s foo, --src-dir foo โ€” set the source directory (historical md5sums are expected to live in files and subdirectories of this directory) to foo. by default, the directory the new_file lives in is assumed to be the source directory. setting this option overrides settings in the environment variable UCF_SOURCE_DIR, and in the configuration file variable conf_source_dir. priority order, babe. ๐Ÿ“‹
  • --root /path/to/chroot โ€” sets the root directory. defaults to the value of the DPKG_ROOT environment variable if it is exported non-empty, and "/" otherwise. the path passed as <Destination> will be prefixed with the path given by this option. similarly, the default state dir path /var/lib/ucf will be prefixed with the path from this option. โœ‹ passing the state directory path explicitly via --state-dir or the UCF_STATE_DIR environment variable will not receive this prefix. lastly, with this option set, configuration settings will be read from /etc/ucf.conf in the given chroot path. the paths passed as <New File> or --sum-file are not prefixed with the chroot path. get the prefixes right or your whole setup is giving chaos. ๐ŸŒช๏ธ
  • --sum-file foo โ€” force the historical md5sums to be read from this file, rather than defaulting to living in the source directory. setting this option overrides settings in the environment variable UCF_OLD_MDSUM_FILE, and in the configuration file variable conf_old_mdsum_file.
  • --three-way โ€” this turns on the option, during installation, for the user to be offered a chance to see a merge of the changes between the old maintainer version and the new maintainer version into the local copy of the configuration file. if the user likes what they see, they can ask to have these changes merged in. ๐Ÿช„ this allows one to get new upstream changes merged in even while retaining local modifications to the configuration file. this is accomplished by taking the configuration file and stashing it in a cache area during registration, and using diff3 during the install (the stashed file name is a munged version of the full path of the configuration file to avoid namespace clashes). so, yeah, it's giving merge-conflict-resolution but make it civilized.
  • --debconf-ok โ€” indicates that it is ok for ucf to use an already running debconf instance for prompting. (it has always been ok to use ucf when debconf is not running โ€” it shall invoke debconf as needed.)
  • --debconf-template foo โ€” instruct ucf to use the named multiselect debconf template instead of the normal ucf-provided debconf template. the caller is responsible for ensuring that the named template exists and has a list of choices matching those for the default ucf template, and should set Choices-C: ${CHOICES} to ensure the returned values match those from the default template. ๐Ÿ“ note that the choices must be different according to whether the --three-way option is also set. don't mix those up, bestie.
  • --state-dir /path/to/dir โ€” set the state directory to /path/to/dir instead of the default /var/lib/ucf. used mostly for testing. ๐Ÿงช
  • -Z โ€” set SELinux security context of destination file to default type. security girlies rise ๐ŸŒˆ๐Ÿ”’

โœจ USAGE โœจ

the most common case usage is pretty simple: a single line invocation in the postinst on configure, and another single line in the postrm to tell ucf to forget about the configuration file on purge (using the --purge option) is all that is needed (assuming ucf is still on the system). two lines. that's the whole recipe. ๐Ÿ“

it is recommended that you also register any file being managed by ucf with the ucf registry โ€” this associates the configuration file with the package it belongs to. this is done with a simple call to ucfr. users may then query the association between a configuration file and the package using the tool ucfq. please see the appropriate manual pages for details. (yes, there are more pages. go read them, iykyk ๐Ÿ“š)

packages which employ debhelper can simplify creating the necessary maintscript fragments by using the dh_ucf helper. lazy girlboss move, love that for you ๐Ÿ’…

if a file maintained by maintainer scripts is being transitioned from an unprotected status to the protection afforded by the script, the maintainer can help ease the transition by reducing the questions that may be asked at installation time. specifically, questions should not be asked if the file in question is an unmodified version that was one shipped in a previous version of this package; and the maintainer can help by telling the script about the historical md5sums that published versions of this file contained. ๐Ÿ•ฐ๏ธ

the way to do this is to either create a file called <New file>.md5sum, with one md5sum on each line (the file names you use are ignored, except for the entry named default), or create a directory, called <New file>.md5sum.d, which should contain any number of files, each containing a single line, namely, the md5sum of a previous version of <New file>. the names of these files are not important, with one exception: the file called default is treated specially. for example, the author personally uses either package version numbers or release code names, like 7.6.3, or potato. ๐Ÿฅ” if none of the historical md5sums match, we are almost certain that either the historical record of md5sums is not complete, or the user has changed the configuration file. no in-between, bestie.


๐ŸŒ™ The default historical md5sum ๐ŸŒ™

the exception to the rule about names mentioned earlier is that if no md5sums match, and if the file <New file>.md5sum.d/default exists, or if there is a line corresponding to a default file in <New file>.md5sum, it shall be used as the default md5sum of the previous version of the package assumed to have been installed on this machine. ๐ŸŽ€ as you can see, unless there are a limited number of previously released packages (like just one), the maintainer is also making an informed guess โ€” but the option is provided to the maintainer. bestie's gotta guess sometimes, and that's okay.

if the file <New file>.md5sum, or the directory <New file>.md5sum.d does not exist, or none of the md5sums match, we test the installed <Destination> file to see whether it is the same as the <New file>. if not, we ask the user whether they want us to replace the file. simple as. ๐Ÿคท


an additional facility is also offered: optionally, ucf can store one old version of the maintainer's copy of the configuration file, and, on upgrade, calculate the changes made in the maintainer's version of the configuration file, and apply that patch to the local version of the file (on user request, of course). there is also a preview facility where the user can inspect the results of such a merge, before asking the action to be taken. lil try-before-you-buy moment. ๐Ÿ˜Œโœจ


to be continued, bestie โ€” part 2 is coming and it's gonna eat ๐Ÿ’–

๐ŸŒˆ ENVIRONMENT VARIABLES ๐ŸŒˆ

okay bestie, this is where ucf takes โœจvibesโœจ from your environment, and honestly it's giving "your shell has opinions and we're gonna respect them."

  • UCF_FORCE_CONFFNEW โ€” if this variable is set, it forces the new file to always overwrite the installed destination file. like, no asking, no debating, new file wins, periodt. ๐Ÿ’…
  • UCF_FORCE_CONFFOLD โ€” if this one's set, it silently retains the installed file. the old one stays, no drama, no prompts, just peace. ๐Ÿ•Š๏ธ
  • UCF_FORCE_CONFFMISS โ€” this one is only applicable when the installed destination file does not exist (maybe a user yeeted it at some point). it forces ucf to recreate the missing file. and real talk โ€” the default behaviour is to honor the user's wishes and not recreate the locally deleted file. so this var is you going "bestie I know I deleted it, put it back." ๐Ÿ”
๐Ÿซถ also! when ucf creates an inferior shell, it populates the variables UCF_CONFFILE_NEW and UCF_CONFFILE_OLD for you, which are super useful for inspecting the changes. go look at the diff, it's literally right there.

the confmiss, confnew, confold, confdef and confask flags of the DPKG_FORCE variable are also supported ๐Ÿ’–. see dpkg(1) for further information, because that's where the lore lives.

when the DPKG_ROOT variable is set and not empty, ucf will operate on the chroot in the path provided by this environment variable. see the --root option for the details. ๐Ÿ โœจ


๐Ÿ“ FILES ๐Ÿ“

here's the receipts of what this script touches:

  • this script creates the file new_file.md5sum, and it may copy the file (presumably shipped with the package) <New file> to its destination, <Destination>.
  • /var/lib/ucf/hashfile, and /var/lib/ucf/hashfile.X, where X is a small integer, where previous versions of the hashfile are stored. (.X files are the throwback archives, iykyk ๐Ÿ—‚๏ธ)
  • /etc/ucf.conf

๐Ÿ’ซ EXAMPLES ๐Ÿ’ซ

okay so imagine the package foo wants to use ucf to handle user interaction for configuration file foo.conf, and a version of it is provided in the package as /usr/share/foo/configuration. a simple invocation of ucf in the post inst file is literally ALL you need, no cap:

ucf /usr/share/foo/configuration /etc/foo.conf

on purge, one should tell ucf to forget about the file (see detailed examples in /usr/share/doc/ucf/examples):

ucf --purge /etc/foo.conf
๐Ÿงš heads up bestie: please note that purge can also be used to make ucf forget the previous state of the files, and when the package is next installed or updated, ucf will ask the user to replace the current configuration file. do this if you want to change your decision to not update to a maintainer provided version of the configuration file. it's the "undo my stubbornness" button, fr fr. ๐Ÿ”„

the motivation for this script was to provide conffile like handling for start files for emacs lisp packages (for example, /etc/emacs21/site-start.d/50psgml-init.el). these start files are not shipped with the package โ€” instead, they are installed during the post installation configuration phase by the script

/usr/lib/emacsen-common/emacs-package-install $package_name

this script is meant to be invoked by the packages install script at /usr/lib/emacsen-common/packages/install/$package_name for each flavour of installed emacsen, by calling it with the proper values of new file (/usr/share/emacs/site-lisp/<pkg>/<pkg-init.el>) and dest file (/etc/emacs21/site-start.d/50<pkg-init.el>), and it should do the rest. the script just handles it. main character energy tbh. ๐Ÿ‘‘


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

go read these, they're the supporting cast and they ate:


๐Ÿ’– AUTHOR ๐Ÿ’–

this manual page was written by Manoj Srivastava <srivasta@debian.org>, for the Debian GNU/Linux system. thank you for your service, legend. ๐Ÿซกโœจ


and that's a wrap on UCF(1) bestie ๐ŸŽ€ this page came to you from the Debian crew, dated May 30 2008 โ€” yeah she's an elder but she still slays ๐Ÿ’… go configure something and text me how it went. bye mwah ๐Ÿ’œ๐Ÿฆ‹๐ŸŒธ

๐Ÿ“ฆ where this page comes from