I have written a lot of software which I deploy upon my own systems. Typically I deploy these things via puppet, but some things are more ad-hoc. For the ad-hoc deployments I tend to rely upon ssh
to deploy them.
I have a bunch of shell-scripts, each called .deploy
, which carries out the necessary steps. Here is an example script which deploys my puppet-summary
service upon a host:
#!/bin/sh
HOST=master.steve.org.uk
RELEASE=1.2
# download
ssh -t ${HOST} "wget --quiet -O /srv/puppet-summary/puppet-summary-linux-amd64-${RELEASE} https://github.com/skx/puppet-summary/releases/download/release-${RELEASE}/puppet-summary-linux-amd64"
# symlink
ssh -t ${HOST} "ln -sf /srv/puppet-summary/puppet-summary-linux-amd64-${RELEASE} /srv/puppet-summary/puppet-summary"
# make executable
ssh -t ${HOST} "chmod 755 /srv/puppet-summary/puppet-summary*"
# restart
ssh -t ${HOST} "systemctl restart puppet-summary.service"
As you can see this script is very obvious:
- Download a binary release from a github-page.
- Symlinks a fixed name to point to this numbered-release.
- Ensures the download is executable.
- Then restarts a service.
This whole process is pretty painless, but it assumes prior-setup. For example it assumes that the systemd unit-file is in-place, and any corresponding users, directories, and configuration-files.
I wanted to replace it with something simple to understand, and that replacement system had to have the ability to do two things:
- Copy a file from my local system to the remote host.
- Run a command upon the remote host.
That would let me have a local tree of configuration-files, and allow them to be uploaded, and then carry out similar steps to the example above.
Obviously the simplest way to go would be to use fabric
, ansible
, salt
, or even puppet
. That would be too easy.
So I wondered could I write a script like this:
# Copy the service-file into place
CopyFile puppet-summary.service /lib/systemd/system/puppet-summary.service
IfChanged "systemctl daemon-reload"
IfChanged "systemctl enable puppet-summary.service"
# Fetch the binary
Run "wget --quiet -O /srv/puppet-summary/puppet-summary-linux-amd64-${RELEASE} https://github.com/skx/puppet-summary/releases/download/release-${RELEASE}/puppet-summary-linux-amd64"
# Run the rest here ..
Run "ln -sf .."
..
Run "systemctl restart puppet-summary.service"
Turns out that connecting to a remote-host, via SSH, and using that single connection to either upload/download files or run commands is very simple in golang. So I'm quite placed with that.
I've only added three primitives, and the ability to set/expand variables:
CopyFile [local-file-name] [remote-file-name]
- This sets a flag if the remote file was missing, or the contents changed.
IfChanged
- Run a command only if the file-copy flag was set.
Run
- Run a command. Unconditionally.
I suspect if I needed much more than that I should use something else. But having dealt with Ansible deployments in the past I'm very glad I didn't use that.
(It makes a lot of sense to have the repositories for deploying application A inside the repository of project A. Another reason to not use ansible
, etc. Though I guess fabric
would work well with that, as recipes are typically contained in ./fabfile.py
. Of course .. python.)
https://steve.fi/
So the missing link: