Automate Ghost updates with systemd on Ubuntu

Secure your self-hosted Ghost blog with Ubuntu Livepatch, unattended upgrades, and systemd.

Automate Ghost updates with systemd on Ubuntu

Ghost is a lightweight open source content management system (CMS) built on Node.js which I prefer to Wordpress and Medium. Ghost was the result of a successful Kickstarter. This blog is hosted on Ghost.

There are official desktop apps for posting from Windows and Linux:

Managed Hosting

The non-profit Ghost Foundation will host your Ghost blog for you starting at $29/month with a lot of great features. If you do not want to bother with maintaining your own server, this may be a good option for you. It also provides the Ghost Foundation with sustainable revenue as an open-source project.

Self-Hosting

However you also have the option of self-hosting. If you self-host you should consider supporting Ghost for at least $5/month on OpenCollective.

With self-hosting comes the challenge of keeping your system up-to-date. This is a tutorial on keeping Ghost and it's tooling up to date on a self-hosted Ubuntu 18.04 Linux server.

VPS

I host my Ghost instance on a $5/month VPS hosted by Vultr with Ubuntu 18.04 LTS. LTS stands for long term support. Ubuntu 18.04, from April 2018, will continue getting security support through April 2023 for free. The next LTS version is Ubuntu 20.04 coming in just a few months.

Kernel Security

You should have Livepatch enabled on production Ubuntu machines. Livepatch applies critical kernel patches to Ubuntu without rebooting. It is free for up to 3 machines and inexpensive compared to options from Red Hat and Oracle.

Unattended System Upgrades

You should enable unattended upgrades in Ubuntu:

$ sudo apt install unattended-upgrades

All files mentioned are hosted here. Pull requests welcome.

Then create the following file at /etc/apt/apt.conf.d/autoupdate:

Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "false";
Unattended-Upgrade::Automatic-Reboot "true";
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";

Test that unattended upgrades is working with:

$ sudo unattended-upgrades --dry-run --debug

If it worked, you should see this:

Securing Ghost

This blog assumes you have configured a working Ghost blog at /var/www/ghost on Ubuntu Linux and your administrative user is ghostadmin. The name and location of your blog and your administrative user may differ.

systemd is a collection of tools for managing and running Linux. systemd's primary component is a system and service manager. systemd can be used to schedule services to run at launch, specific times, or based on specific events. It serves as a modern replacement for sysvinit scripts and cron jobs.

systemd is configured using unit files, located in /etc/systemd/system:

systemd unit files are based on the XDG .desktop file format from FreeDesktop, which is based on the Windows .ini format.

We need three key files in place for us to keep Ghost and it's tooling up to date.

First, we need a service to update npm and ghost-cli as root.

So we create /etc/systemd/system/update_npm.service:

[Unit]
Description=Update npm and ghost-cli automatically with systemd

[Service]
Type=oneshot
ExecStartPre=/usr/bin/npm install -g npm
ExecStart=/usr/bin/npm i -g ghost-cli@latest

We set Type to oneshot because this is not a service that will remain running, it executes and then exits. No user is specified so the service will be run as root. We use ExecStartPre to update our system-wide npm. All ExecStartPre commands will be executed in order before ExecStart. We then use ExecStart to update the ghost-cli client with npm.

Set permissions on update_npm.service:

$ sudo chmod 0644 /etc/systemd/system/update_npm.service

Next, we need to update Ghost as our administrative user.

So we create /etc/systemd/system/update_ghost.service:

[Unit]
Description=Update ghost instance automatically with systemd
Wants=update_npm.service
After=update_npm.service

[Service]
Type=oneshot
User=1000
WorkingDirectory=/var/www/ghost
ExecStart=/usr/bin/ghost update --no-prompt --auto

User is the UID of the user we want the service to be executed as, in this case, 1000 corresponds to our administrative user. Note 1000 is not the ghost user created by the Ghost installer.

We specify the directory to run ghost update in via WorkingDirectory. We add --no-prompt to tell the Ghost update utility not to prompt for user input and --auto to try and complete as many tasks as possible automatically.

Here we use Wants to say that this service relies on update_npm.service and to run this service only after starting update_npm.service. This way when update_ghost.service is called, update_npm.service will be run first, so we are always running the latest npm and version of ghost-cli when we run ghost update.

Set permissions on update_ghost.service:

$ sudo chmod 0644 /etc/systemd/system/update_ghost.service

Notice we do not 'enable' these .service files like you would other systemd services. Services set to run on a timer do not require enablement or an 'install' target, so enabling them would fail.

But we do need to create and enable the timer. So we create /etc/systemd/system/update_ghost.timer:

[Unit]
Description=Update ghost instance automatically with systemd after boot and weekly

[Timer]
OnBootSec=5min
OnUnitActiveSec=1w

[Install]
WantedBy=timers.target

Under [Timer] we set our timer to run 5 minutes after any boot and again every week.

Now we enable the timer we have created:

$ sudo systemctl enable update_ghost.timer

Finally, we need to give our admin user permissions to check, stop, and start the Ghost service unattended. Why? /usr/bin/ghost update --no-prompt --auto still calls systemd with sudo to start and stop Ghost. We need to enable our administrative user to call those commands with sudo without manually entering the password, otherwise our automated updates would fail.

So we edit our sudoers file with $ visudo and add:

ghostadmin ALL=(ALL) NOPASSWD: /bin/systemctl is-active ghost_website
ghostadmin ALL=(ALL) NOPASSWD: /bin/systemctl stop ghost_website
ghostadmin ALL=(ALL) NOPASSWD: /bin/systemctl start ghost_website

Note that ghost_website will depend on the name of your ghost website. If you forgot, use $ ghost ls. These are the bare minimum commands we need to give to ghostadmin to perform it's functions unattended, which is a good security practice.

We can see our timer set here:

$ systemctl list-timers --all

You can test that update_npm.service runs with:

$ sudo service update_npm start

$ sudo journalctl --unit=update_npm.service -n 10 --no-pager

You can then test that update_ghost runs with:

$ sudo service update_service start

$ sudo journalctl --unit=update_ghost.service -n 32 --no-pager

You can verify update_npm.service was called by update_ghost.service by checking the update_npm log and checking the time stamps:

$ sudo service update_npm start

$ sudo journalctl --unit=update_npm.service -n 3 --no-pager

$ sudo service update_ghost start

$ sudo journalctl --unit=update_npm.service -n 3 --no-pager

You can see from the time stamp that update_npm.service was called again by running update_ghost.service by the time stamp.

Conclusion

We have:

  • Latest Ubuntu 18.04 LTS with Livepatch and unattended upgrades enabled
  • Latest npm and Ghost
  • systemd configured to automatically update npm and Ghost after reboots and weekly

Feedback?

@unixterminal on Twitter.

Additional Reading

systemd-ghost on GitHub

ArchWiki - systemd/Timers

systemd.timer

Ghost Documentation

Ghost Security