Roland's config files page |
|
Configuration files for FreeBSD, or Linux, or any other UNIX-like systems, come in two flavors;
System files
These files live in /etc or /var or /usr/local/etc and control the
running of the system and additional software, for all users.
User files
Also called dotfiles, because their name starts with a dot, which makes the ls(1) program ignore them by default. These files contain program settings that are specific to the user in which home directory they are found.
Sometimes you need to make changes to these files, but making a mistake can lead to trouble varying from a program that doesn’t work correctly to a system that won’t boot! So if you make changes to these configuration files, you should always keep a copy of the original. Now, this is something that is easy to forget. That is why I keep my configuration files under revision control.
This is not meant as en exhaustive treatise on revision control. Enough of that is available on the internet. But a short introduction is in order to make you understand why it is worthwhile to use this.
A revision control system will record different versions of a file. This action is called ‘checking in’ or ‘committing’ a file. So if you edit a file, and you discover that you have made a mistake, you can always return to any of the previous version that you have saved. Even better, these systems can also show you the difference the current file and previous versions. A check-in or commit is usually accompanied by a short message that you type in describing the changes. You can list these messages to see how a file has developed over time.
My revision control system of choice is git, for various reasons that I shall not go into. But for configuration files something simple like rcs would do as well. For FreeBSD users, the latter is part of the FreeBSD base system.
System configuration files are usually stored in /etc, or under /var (or,
on FreeBSD at least, also /usr/local/etc for applications that are not part
of the base system but in ports). Usually only the root user can edit
files in these directories. But since root has complete access to the whole
system, a mistaken command can have dire consequences.
For that reason I generally use another location to keep edited configuration
files, a directory in my home-directory that I call setup. Since I have
multiple machines running FreeBSD, each machine gets its own subdirectory. In
my case, this subdirectory also serves as the so-called repository for the
revision control system. But some revision control systems have separate
repositories, e.g. stored in databases.
When I started using a separate setup directory, the following workflow grew
The second step was something that needed automation; I had to keep track of where every file was supposed to be and what ownership and permissions were needed. Some files even need a program to be run after a new version is installed.
Sometimes when I upgrade my OS to a newer version, that introduces changes as
well. So I needed a way to check if actually installed files differed from the
version I had in the setup directory.
To accomplish both things, I wrote a couple of perl scripts that could
check which config files had changed, and could install them in the right
places. These scripts are located in the setup directory. Both of these
scripts use a data file that tells them the files that need to be checked,
what their permissions should be, their proper location and optionally the
commands that need to be executed after they have been installed. Given the
abovementioned differences between system and user files, two different
filelists are used, depending on if the scripts are called by a normal user or
by root. Below is an excerpt of the list for root, filelist.root. The other
file should be called filelist.USER, where USER is your username.
etc/named.conf 644 /var/named/etc/namedb/named.conf
etc/newsyslog.conf 644 /etc/newsyslog.conf
etc/ntp.conf 644 /etc/ntp.conf /etc/rc.d/ntpd restart
etc/periodic.conf 644 /etc/periodic.conf
etc/pf.conf 644 /etc/pf.conf /etc/rc.d/pf reload
etc/pkgtools.conf 644 /usr/local/etc/pkgtools.conf
etc/portmaster.rc 644 /etc/portmaster.rc
etc/profile 644 /etc/profile
etc/rc.conf 644 /etc/rc.conf
etc/resolv.conf 644 /etc/resolv.conf
The configuration files are listed one on every line. The first column is the path of the file relative to the setup directory. The second column is the permissions that the file should have when installed at the target location. The third column is the target location where the file should be installed. Any further text on a line is used as commands to be executed after the files have been installed.
This works pretty well, except for my emacs configuration file .emacs.elc
file, which needs to be byte-compiled with emacs before installation. So I
still have a separate install script for that. I’ve toyed with the idea to
extend the scripts, but in my opinion the efforts outweigh the gains.
The first script is called check.pl, and checks for differences between the
files in the setup directory and the installed files. The check is done by
comparing MD5 checksums of the two files.
The normal mode of operation for check.pl is to just list the files that
differ. If the -d (for ‘details’) flag is given when check.pl is invoked, it
also prints a unified diff between the two files. The -l (for ‘list’) flag
lists all files and whether they are changed or not. The -h option merely
produces a short help message and then exits the script.
check.pl script
#!/usr/bin/perl
# Check for changes in files according to instructions in 'filelist.$USER'.
# Time-stamp: <2010-08-13 15:20:51 rsmith>
# $Id: 2088790e9bde79f4476a003adc6c9b58dc8f1aa5 $
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to check.pl. This work is published from
# Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/
use Getopt::Std;
getopts('ldh');
if ($opt_h == 1) {
print "Usage: ./check.pl [-hld]\n";
print "-l\t(long); lists all files, not just the different ones.\n";
print "-d\t(diff); Print the diff(1) output for different files.\n";
exit 0;
}
$name = `id -u -n`;
open(RF, "filelist.$name") || die "Cannot open list file 'filelist.$name'.";
while(<RF>) {
chomp;
if (/^#/) {next}; # skip comment lines.
($src,$perm,$dest) = split;
if (-r $dest) {
$msrc = `md5 -q $src`;
$mdest = `md5 -q $dest`;
if ($msrc ne $mdest) {
printf "\e[31m%s differs from %s\n\e[0m", $src, $dest;
if ($opt_d == 1) {
system "diff", "-u", $dest, $src;
}
} elsif ($opt_l == 1) {
printf "\e[32m%s matches %s\n\e[0m", $src, $dest;
}
} else {
printf "\e[41m\e[30m%s cannot be read.\n\e[0m", $dest;
}
}
close(RF);
Running this script tells me which files differ from the ones in the repository. I can then take appropriate action; merge any changes into the repository, or call install.pl to restore the version from the repository.
After using check.pl to check if there are no system changes to be merged
into the repository, the following install.pl is used to carry out the
installation. It only installs files that are different, not just
everything. If post-install commands are provided, they are run;
install.pl script
#!/usr/bin/perl
# Install files according to instructions in 'filelist.$USER'.
# Time-stamp: <2010-08-13 15:19:41 rsmith>
# $Id: 38a7b30e9198dd1c548ac90f7a554e8feaa1a682 $
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to install.pl. This work is published from
# Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/
$name = `id -u -n`;
open(RF, "filelist.$name") || die "Cannot open list file 'filelist.$name'.";
while(<RF>) {
chomp;
if (/^#/) {next}; # Skip comment lines.
@cmds = split;
$src = shift @cmds;
$perm = shift @cmds;
$dest = shift @cmds;
# Skip identical files.
if (-e $dest) {
$msrc = `md5 -q $src`;
$mdest = `md5 -q $dest`;
if ($msrc eq $mdest) {next};
}
# Create directories if necessary
$tdir = `dirname $dest`;
chomp $tdir;
if (! -d $tdir) {
print "\e[31m";
$rv = system "install", "-d", $tdir;
$rv>>8;
if ($rv == 0) {
printf "\e[32mCreated %s.\n\e[39m", $tdir;
} else {print "\e[39m";}
}
# Install the file
print "\e[31m";
$rv = system "install", "-p", "-m", $perm, $src, $dest;
$rv>>8;
if ($rv == 0) {
printf "\e[32mInstalled %s as %s.\n\e[39m", $src, $dest;
# Execute post-install commands.
if (@cmds) {
$rv = system @cmds;
$rv>>8;
if ($rv != 0) {
printf "\e[31mPost-install commands for %s failed.\n\e[39m",
$src;
}
}
} else {print "\e[39";}
}
close(RF);
With the scripts and the revision control, my workflow is now any of these;
./check.pl -d to see if any system files have changed e.g. after
the base system or any of the ports have been updated.Or
./install.pl to roll out the changes.At any stage it is now easy for me to check what has changed, and why.
Suppose I want my system to be a gateway. One way of doing this is to set
gateway_enable="YES" in /etc/rc.conf. So I go to my local repository of
config files, and I edit rc.conf. After editing, I review the changes that
I’ve made by comparing them to the version that is in the git revision
control system;
> git diff
diff --git a/etc/rc.conf b/etc/rc.conf
index fa025c1..9c523ad 100644
--- a/etc/rc.conf
+++ b/etc/rc.conf
@@ -1,6 +1,6 @@
# file: /etc/rc.conf - System configuration information
# host: slackbox.erewhon.net
-# Time-stamp: <2010-08-15 23:01:28 rsmith>
+# Time-stamp: <2010-08-19 22:36:43 rsmith>
# $Id$
hostname="slackbox.erewhon.net"
@@ -16,6 +16,7 @@ devfs_system_ruleset="slackbox_usb"
ifconfig_lo0="inet 127.0.0.1"
ifconfig_age0="inet 10.0.0.150/24 polling media 100baseTX mediaopt full-duplex"
ifconfig_rl0="inet 192.168.0.1/24 polling media 100baseTX mediaopt full-duplex"
+gateway_enable="YES"
router_enable="YES"
defaultrouter="10.0.0.138"
tcp_extensions="NO"
This looks OK. The Time-stamp was changed by my editor (emacs) when I saved
the file. Since this is a really simple change, I can commit it without testing.
> git commit -am "Enable gateway in rc.conf"
Checking out etc/rc.conf to update Id.
[master 485ba6b] Enable gateway in rc.conf
1 files changed, 2 insertions(+), 1 deletions(-)
With the command git log I can see a short summary of my changes;
commit 485ba6b8b208311fb8e6a4debe8879088cf28567
Author: Roland Smith <rsmith@xs4all.nl>
Date: Thu Aug 19 22:47:36 2010 +0200
Enable gateway in rc.conf
commit 93d13f6aacbefc872ca980ac228064c2d2894038
Author: Roland Smith <rsmith@xs4all.nl>
Date: Wed Aug 18 23:54:16 2010 +0200
Fix typo in dhcpd.conf.
...
Now I go and check the difference with the real version in /etc
by running check.pl
# ./check.pl -d
etc/rc.conf differs from /etc/rc.conf
--- /etc/rc.conf 2010-08-16 00:00:19.000000000 +0200
+++ etc/rc.conf 2010-08-19 22:47:36.000000000 +0200
@@ -1,7 +1,7 @@
# file: /etc/rc.conf - System configuration information
# host: slackbox.erewhon.net
-# Time-stamp: <2010-08-15 23:01:28 rsmith>
-# $Id: fa025c16642a4aed686603a3824644fbdaf03f42 $
+# Time-stamp: <2010-08-19 22:36:43 rsmith>
+# $Id: 9c523adb9d067f37393efc540096b06b1c816f8c $
hostname="slackbox.erewhon.net"
@@ -16,6 +16,7 @@
ifconfig_lo0="inet 127.0.0.1"
ifconfig_age0="inet 10.0.0.150/24 polling media 100baseTX mediaopt full-duplex"
ifconfig_rl0="inet 192.168.0.1/24 polling media 100baseTX mediaopt full-duplex"
+gateway_enable="YES"
router_enable="YES"
defaultrouter="10.0.0.138"
tcp_extensions="NO"
This is also the correct change. The $Id was updated by git when I
chacked in the file. Now I can install the file in its proper location;
# ./install.pl
Installed etc/rc.conf as /etc/rc.conf.
That’s all folks!
—– Copyright © 2010, Roland Smith rsmith@xs4all.nl

This <span xmlns:dc=“http://purl.org/dc/elements/1.1/” href=“http://purl.org/dc/dcmitype/Text” rel=“dc:type”>work is licensed under a Creative Commons Attribution 3.0 Unported License