Configuring my Machines with Bashtard
Bash Bashtard FreeBSD GNU+Linux Programming — Published on .
Over the past couple weeks I’ve been spending some time here and there to work on my own application for configuring my machines. Before this I’ve tried Ansible, but found it to be very convoluted to use, and requires a lot of conditionals if your machines aren’t all running the same base system.
So I made something in Bash, with a few abstractions to make certain
interactions less annoying to do manually every time. This used to be called
tyilnet
, but I’ve discussed the setup with a few people on IRC, and decided it
would be a fun project to make it a bit more agnostic, so other people could
also easily start using it. This resulted in the creation of
Bashtard, pronounced as “bash”, followed by
“tard” (as in “bastard”).
It works by simply writing Bash scripts to do the configuration, and provides abstractions for using the system’s package manager, service manager, and some utilities such as logging and dealing with configured values. Configuration values can be set on a per-host or per-OS basis. Since I run a varied base of OSs, including Gentoo, Debian, and FreeBSD, the per-OS configuration comes in very handy to me.
As for the reason to use Bash, I chose it because most of the systems I run
already have this installed, so it doesn’t add a dependency most of the time.
I would’ve liked to do it in POSIX sh, but I feel that when you’re reaching a
certain level of complexity, Bash offers some very nice features which can make
your code cleaner, or less likely to contain bugs. Features such as [[ ]]
,
local
, and arrays come to mind.
I’ve been kindly asked to guide potential new users to writing their first Bashtard script, known as a playbook, so if you want to know about how it works in practice, keep on reading. If you’re satisfied with your current configuration management system, this might not be quite as interesting to you, so be warned.
The first steps for a new user would obviously to install Bashtard, as it’s not
in any OSs package repositories yet. A Makefile
is supplied in the repository,
which should make this easy enough.
git clone https://git.tyil.nl/bashtard
cd bashtard
sudo make install
hash -r
Once installed, it needs some initialization.
bashtard init
This will create the basic structure in /etc/bashtard
, including a
playbooks.d
. Inside this playbooks.d
directory, any directory is considered
to be a playbook, which requires a description.txt
and a playbook.bash
.
cd /etc/bashtard/playbooks.d
mkdir ssh
cd ssh
echo "OpenSSH configuration" > description.txt
$EDITOR playbook.bash
The playbook.bash
needs to contain 3 functions which are used by bashtard
, a
playbook_add()
, playbook_sync()
, and playbook_del()
. These will be called
by the bashtard
subcommand add
, sync
, and del
respectively.
I generally start with the playbook_sync()
function first, since this is the
function that’ll ensure all the configurations are kept in sync with my desires.
I want to have my own sshd_config
, which needs some templating for the
Subsystem sftp
line. There’s a file_template
function provided by bashtard,
which does some very simple templating. I’ll pass it the sftp
variable to use.
playbook_sync() {
file_template sshd_config \
"sftp=$(config "ssh.sftp")" \
> /etc/ssh/sshd_config
}
Now to create the actual template. The file_template
function looks for
templates in the share
directory inside the playbook directory.
mkdir share
$EDITOR share/sshd_config
Since I already know what I want my sshd_config
to look like from previous
installed systems, I’ll just use that, but with a variable for the Subsystem sftp
value.
# Connectivity
Port 22
AddressFamily any
ListenAddress 0.0.0.0
ListenAddress ::
# Fluff
PrintMotd yes
# SFTP
Subsystem sftp ${sftp}
# Authentication
AuthorizedKeysFile /etc/ssh/authorized_keys .ssh/authorized_keys
PermitRootLogin no
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication no
# Allow tyil
Match User tyil
PubkeyAuthentication yes
# Allow public key authentication over VPN
Match Address 10.57.0.0/16
PubkeyAuthentication yes
PermitRootLogin prohibit-password
The ${sftp}
placeholder will be filled with whatever value is returned by
config "ssh.sftp"
. And for this to work properly, we will need to define the
variable somewhere. These are written to the etc
directory inside a playbook.
You can specify defaults in a file called defaults
, and this can be
overwritten by OS-specific values, which in turn can be overwritten by
host-specific values.
mkdir etc
$EDITOR etc/defaults
The format for these files is a very simple key=value
. It splits on the first
=
to determine what the key and value are. This means you can use =
in your
values, but not your keys.
ssh.sftp=/usr/lib/openssh/sftp-server
This value is correct for Debian and derivatives, but not for my Gentoo or FreeBSD systems, so I’ve created OS-specific configuration files for these.
mkdir etc/os.d
cat etc/os.d/linux-gentoo
ssh.sftp=/usr/lib64/misc/sftp-server
cat etc/os.d/freebsd
ssh.sftp=/usr/lib64/misc/sftp-server
My sshd_config
template also specifies the use of a Motd
, so that needs to
be created as well. This can again be done using the template
function.
file_template "motd" \
"fqdn=${BASHTARD_PLATFORM[fqdn]}" \
"time=$(date -u "+%FT%T")" \
> /etc/motd
The motd
template gets saved at share/motd
.
████████╗██╗ ██╗██╗██╗ ███╗ ██╗███████╗████████╗
╚══██╔══╝╚██╗ ██╔╝██║██║ ████╗ ██║██╔════╝╚══██╔══╝
██║ ╚████╔╝ ██║██║ ██╔██╗ ██║█████╗ ██║
██║ ╚██╔╝ ██║██║ ██║╚██╗██║██╔══╝ ██║
██║ ██║ ██║███████╗██╗██║ ╚████║███████╗ ██║
╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝ ╚═╝
Welcome to ${fqdn}, last updated on ${time}.
Lastly, we want to ensure the SSH daemon gets reloaded after every sync, so
let’s add that to the playbook_sync()
function as well.
svc reload "sshd"
The svc
utility looks for a configuration value that starts with svc.
,
followed by the service you’re trying to act upon, so in this case that would be
svc.sshd
. We can add this to our configuration files in etc
. Across all my
machines, sshd
seems to work as the value, so I only need to add one line to
etc/defaults
.
svc.sshd=sshd
This should take care of all the things I want automatically synced. The
playbook_add()
function is intended for all one-time setup required for any
playbook. In this case that means the SSH daemon’s service needs to be
activated, since it is not active by default on all my setups.
playbook_add() {
svc enable "sshd"
svc start "sshd"
}
However, add
does not call a sync
, and I don’t want my SSH service to run
with default configuration until a sync
is initialized. So before enabling and
starting the service, I will call sync
manually, by running a playbook_sync
first. This in turn, however, poses another problem, as playbook_sync()
wants
to reload the service, which it can’t do unless it is already running. To fix
this, I’ll add an if
statement to skip reloading if bashtard
is running the
add
command.
playbook_add() {
playbook_sync
svc enable "sshd"
svc start "sshd"
}
playbook_sync() {
...
[[ $BASHTARD_COMMAND == "add" ]] && return
svc reload "sshd"
}
Now, bashtard add sshd
will run the playbook_add()
function, which calls the
playbook_sync()
function before enabling and starting the sshd
service. All
that is left is the playbook_del()
function, which only really needs to stop
and disable the service. The templated files can be removed here as well if
desired, of course.
playbook_del() {
svc stop "sshd"
svc disable "sshd"
}
Lastly, I configured my crond
to run bashtard sync
every 20 minutes, so
whenever I update my configurations, it can take up to 20 minutes to propagate
to all my machines. Having an abstraction to deal with cron
(or SystemD timers
where applicable) in Bashtard is something I’d like to add, but I have no
concrete plans on how to do this, yet.
You can find the full playbook.bash
source on
git.tyil.nl.