How to upgrade to IHP v1.1.0 on Shipnix
The IHP tooling has been greatly improved. It's time to upgrade.
22 July 2023
The new version of IHP, v1.1.0, is packed with new features and improvements .
The most notable change is the transition to Nix Flakes and devenv , a big overhaul to the overall IHP experience.
As IHP now uses Nix flakes directly, deployments are also faster and less error prone.
To keep using Shipnix with the new IHP version, you need to do some changes as described in this article.
If you are provisioning your IHP app to Shipnix for the first time, you don't need to follow this guide as Shipnix is all set for IHP v1.1.0.
Backup your data
Do not skip this step
The upgrade should be painless if you closely follow the steps, but since we are doing a big upgrade, it's best to be on the safe side.
Take care to backup your production data and store it on your machine before you deploy the update.
To download a database dump on your local machine, run this command to have a backup.sql
just in case:
ssh ship@myhostname "pg_dump postgres://shipadmin@127.0.0.1:5432/defaultdb -a --inserts --column-inserts --disable-triggers | sed -e '/^--/d'" > backup.sql
First step: Upgrade IHP
The first thing you need to do is follow the upgrade guide to IHP 1.1.0.
You will notice that is has a conflict with your current Shipnix setup: IHP now uses a flake.nix
, and you already have a one that looks different.
Not to worry. You can overwrite the current flake.nix
with what the IHP upgrade guide instructs and return to this guide when you are done.
flake.nix
Now that you have upgraded IHP and verified that everything works fine in your development environment, there are just a couple of changes you need to do in your new flake.nix
.
Remember to replace flake.nixosConfigurations. "test-server-one"
with your Shipnix server name.
{
inputs = {
ihp. url = "github:digitallyinduced/ihp/v1.1" ;
nixpkgs. follows = "ihp/nixpkgs" ;
flake- parts. follows = "ihp/flake-parts" ;
devenv. follows = "ihp/devenv" ;
systems. follows = "ihp/systems" ;
} ;
- outputs = inputs@ { ihp, flake- parts, systems, . . . } :
+ outputs = inputs@ { ihp, flake- parts, systems, nixpkgs, self, . . . } :
flake- parts. lib. mkFlake { inherit inputs; } {
systems = import systems;
imports = [ ihp. flakeModules. default ] ;
perSystem = { pkgs, . . . } : {
ihp = {
enable = true ;
projectPath = ./. ;
packages = with pkgs; [
] ;
haskellPackages = p: with p; [
p. ihp
cabal- install
base
wai
text
hlint
] ;
} ;
} ;
+ flake. nixosConfigurations. "test-server-one" = nixpkgs. lib. nixosSystem {
+ system = "x86_64-linux" ;
+ specialArgs = inputs // {
+ environment = "production" ;
+ ihp- migrate = self. packages. x86_64- linux. migrate;
+ ihpApp = self. packages. x86_64- linux. default;
+ } ;
+ modules = [
+ ./nixos/configuration.nix
+ ] ;
+ } ;
} ;
}
nixos/scripts/provision
The provision script won't be used on your running server, so this is for keeping your server reproducible.
set -uo pipefail
if [ "$EUID " -ne 0 ]
then echo "This script must be run as root" > &2
exit 1
fi
cd /home/ship/server &&
set -o allexport; source /etc/shipnix/.env; set +o allexport &&
echo "Performing initial database operations..." &&
psql postgres://postgres@127.0.0.1:5432/defaultdb -c "ALTER ROLE shipadmin CREATEDB CREATEROLE SUPERUSER" &&
psql postgres://postgres@127.0.0.1:5432/defaultdb -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO shipadmin;" &&
psql postgres://postgres@127.0.0.1:5432/defaultdb -c "GRANT ALL PRIVILEGES ON SCHEMA public TO shipadmin;" &&
echo "Dropping database schema public..." &&
psql $DATABASE_URL -c "drop schema public cascade; create schema public;" &&
echo "Creating schema migrations table..." &&
psql $DATABASE_URL -c "CREATE TABLE IF NOT EXISTS schema_migrations (revision BIGINT NOT NULL UNIQUE);" &&
echo "Importing IHPSchema.sql..." &&
nix build .
psql $DATABASE_URL < result/IHPSchema.sql
echo "Importing app schema..." &&
psql $DATABASE_URL < Application/Schema.sql &&
if [ [ -f Application/Fixtures.sql ] ] ; then
echo "Importing Fixtures.sql..." &&
psql $DATABASE_URL < Application/Fixtures.sql
else
echo "No Fixtures.sql found, skipping..."
fi
If you have any custom provisioning actions in your previous provision script, make sure to re-add these in the new ones.
Do not execute the provision manually unless you are deliberately resetting your server. Doing this will wipe your data.
nixos/scripts/before-rebuild
The new before-rebuild
script will have your migrations run a lot quicker as we later will add the migrate script as a system package.
set -euo pipefail
set -o allexport; source /etc/shipnix/.env; set +o allexport
cd /home/ship/server
if command -v migrate > /dev/null 2 >&1 && [ -d "Application/Migration" ] ; then
echo "Running database migrations..."
migrate
else
echo "Skipping..."
fi
If you added your own custom commands that needs to be run outside of the nixos-rebuild, remember to re-add these where appropriate.
nixos/scripts/after-rebuild
Your after-rebuild
script will likely be unchanged, unless you run a very old Shipnix configuration, but to be safe, it should look like this:
set -e
echo "Running after-rebuild script"
sudo systemctl restart ship.service
And if you are using IHP jobs, make sure to uncomment the command that restarts the ship_jobs.service
after a successful deployment.
nixos/ship.nix
Your new ship.nix
can be replaced with the snippet below, although the changes are minimal.
Only make sure to put your unique credential public key into users.users.ship.openssh.authorizedKeys.keys
and users.users.root.openssh.authorizedKeys.keys
. It's just a small safety measure in case you remove the key from your authorized_keys
file by accident so Shipnix can still reach your server.
You can find this public key by going to Server providers
in the Shipnix UI and then to your DigitalOcean credentials.
{ config, pkgs, modulesPath, lib, . . . } :
{
nix = {
package = pkgs. nixUnstable;
extraOptions = ''
experimental-features = nix-command flakes ca-derivations
'' ;
settings = {
trusted- users = [ "root" "ship" "nix-ssh" ] ;
} ;
} ;
programs. git. enable = true ;
programs. git. config = {
advice. detachedHead = false ;
} ;
services. openssh = {
enable = true ;
passwordAuthentication = false ;
} ;
users. users. ship = {
isNormalUser = true ;
extraGroups = [ "wheel" "nginx" ] ;
openssh. authorizedKeys. keyFiles = [ ./authorized_keys ] ;
openssh. authorizedKeys. keys = [
"ssh-rsa YOUR UNIQUE CREDENTIAL PUBLIC KEY ship@tite-ship"
] ;
} ;
users. users. root. openssh. authorizedKeys. keyFiles = [ ./authorized_keys ] ;
users. users. root. openssh. authorizedKeys. keys = [
"ssh-rsa YOUR UNIQUE CREDENTIAL PUBLIC KEY ship@tite-ship"
] ;
security. sudo. extraRules = [
{
users = [ "ship" ] ;
commands = [
{
command = "ALL" ;
options = [ "NOPASSWD" "SETENV" ] ;
}
] ;
}
] ;
}
nixos/configuration.nix
In your configuration.nix, you need to add the ihp-migrate
package that is passed from the flake.nix
into the arguments at the top, and add it to environment.systemPackages
.
The IHP binary cache is also added to trusted and extra-trusted to have a declarative rule to trust the IHP binary cache whenever needed.
direnv
is no longer needed, as it was admittedly a bad practice to use this. Now with the improved IHP tooling we no longer need hacks like this.
You might also see if there are other settings you want to take from this. We recommend looking at nix.gc
and nix.settings.auto-optimise-store
to preserve disk space, but otherwise, you should be good to go with the highlighted changes.
- { config, pkgs, modulesPath, lib, environment, . . . } :
+ { config, pkgs, modulesPath, lib, environment, ihp- migrate, . . . } :
{
imports = lib. optional ( builtins . pathExists ./do-userdata.nix ) ./do-userdata.nix ++ [
( modulesPath + "/virtualisation/digital-ocean-config.nix" )
./ship.nix
./site.nix
] ;
+ nix. settings. substituters = [ "https://digitallyinduced.cachix.org" ] ;
+ nix. settings. trusted- substituters = [ "https://digitallyinduced.cachix.org" ] ;
+ nix. settings. extra- trusted- substituters = [ "https://digitallyinduced.cachix.org" ] ;
+ nix. settings. extra- trusted- public- keys = [ "digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE=" ] ;
+ nix. settings. trusted- public- keys = [ "digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE=" ] ;
swapDevices = [ { device = "/swapfile" ; size = 2048 ; } ] ;
environment. systemPackages = with pkgs; [
bash
jc
- direnv
+ ihp- migrate
] ;
environment. shellInit = "set -o allexport; source /etc/shipnix/.env; set +o allexport" ;
nix. settings. sandbox = false ;
nix. gc = {
automatic = true ;
dates = "weekly" ;
options = "--delete-older-than 30d" ;
} ;
nix. settings. auto- optimise- store = true ;
networking. firewall. enable = true ;
networking. firewall. allowedTCPPorts = [ 80 443 22 ] ;
programs. vim. defaultEditor = true ;
services. fail2ban. enable = true ;
system. stateVersion = "22.11" ;
}
Do not change system.stateVersion . It should be constant at the value it had when you provisioned your server.
nixos/site.nix
The site.nix
is largely similar to before.
The notable change is that we don't import the ihpApp
directly, but it's passed in from the flake.nix
.
PostgresSQL notes
We also recommend to update the services.postgresql
declaration and especially that you should explicitly set the services.postgresql.package
to to be the same as what's currently on your server.
To find out what your current PostgresSQL version is, request it from your server with ssh:
$ ssh ship@yourserverhost.com "postgres --version"
> postgres ( PostgreSQL) 14.8
So if you get version 14 something like above, set it to pkgs.postgresql_14
.
This helps with upgrading your postgres version later, and keeps duplicate servers consistent with the same postgres version.
Upgrading to another major Postgres version will require you to do some manual steps as outlined in the NixOS manual .
If you prefer not to deal with databases yourself, consider switching to a managed database provider. This has potential additional cost, but outsources the concern to a specialized service.
So with all that out of the way, here's the recommended changes for the site.nix
:
- { config, lib, pkgs, environment, . . . } :
+ { config, lib, pkgs, environment, ihpApp, . . . } :
let
- ihpApp = import ../. ;
httpsEnabled = true ;
jobsEnabled = true ;
in
{
services. cron = {
enable = true ;
systemCronJobs = [
] ;
} ;
security. acme. defaults. email = "yourname@email.com" ;
security. acme. acceptTerms = httpsEnabled;
services. nginx = {
enable = true ;
enableReload = true ;
recommendedProxySettings = true ;
recommendedGzipSettings = true ;
recommendedOptimisation = true ;
recommendedTlsSettings = true ;
} ;
services. nginx. virtualHosts = {
"localhost" = {
serverAliases = [ ] ;
enableACME = httpsEnabled;
forceSSL = httpsEnabled;
locations = {
"/" = {
proxyPass = "http://localhost:8000" ;
proxyWebsockets = true ;
extraConfig =
"proxy_ssl_server_name on;" +
"proxy_pass_header Authorization;" ;
} ;
} ;
} ;
} ;
services. postgresql = {
enable = true ;
+ package = pkgs. postgresql_14;
ensureDatabases = [ "defaultdb" ] ;
ensureUsers = [
{
name = "shipadmin" ;
ensurePermissions = {
"ALL TABLES IN SCHEMA public" = "ALL PRIVILEGES" ;
} ;
}
] ;
enableTCPIP = false ;
+ authentication = ''
+ local all all trust
+ host all all 127.0.0.1/32 trust
+ host all all ::1/128 trust
+ host all all 0.0.0.0/0 reject
+ '' ;
} ;
systemd. services. ship = {
description = "IHP service" ;
enable = true ;
after = [
"network.target"
"postgresql.service"
] ;
wantedBy = [
"multi-user.target"
] ;
serviceConfig = {
Type = "simple" ;
User = "ship" ;
Restart = "always" ;
WorkingDirectory = "$ { ihpApp} /lib" ;
EnvironmentFile = /etc/shipnix/.env ;
ExecStart = "$ { ihpApp} /bin/RunProdServer" ;
} ;
} ;
systemd. services. ship_jobs = {
description = "IHP job watcher" ;
enable = jobsEnabled;
after = [ "ship.service" ] ;
wantedBy = [
"multi-user.target"
] ;
serviceConfig = {
Type = "simple" ;
User = "ship" ;
Restart = "always" ;
WorkingDirectory = "$ { ihpApp} /lib" ;
EnvironmentFile = /etc/shipnix/.env ;
ExecStart = '' $ { ihpApp} /bin/RunJobs '' ;
} ;
} ;
}
Ensure database permissions
This should in most cases not be necessary, but to be safe that the shipadmin user has the proper permissions to run IHP, you can SSH into your server and run these commands.
psql postgres://postgres@127.0.0.1:5432/defaultdb -c "ALTER ROLE shipadmin CREATEDB CREATEROLE SUPERUSER"
psql postgres://postgres@127.0.0.1:5432/defaultdb -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO shipadmin;"
psql postgres://postgres@127.0.0.1:5432/defaultdb -c "GRANT ALL PRIVILEGES ON SCHEMA public TO shipadmin;"
IHP requires high permission levels for your database user. This ensures that the IHP database user has the permissions it needs to function properly.
Commit and deploy
With this, you can commit your changes, and your IHP v1.1.0 app should be upgraded and working with Shipnix ✨