Skip to content

Commit

Permalink
Merge pull request #1792 from digitallyinduced/nixos-deploy
Browse files Browse the repository at this point in the history
deploy-to-nixos Command
  • Loading branch information
mpscholten authored Aug 23, 2023
2 parents a09a590 + 8ec8532 commit 6d23adc
Show file tree
Hide file tree
Showing 13 changed files with 755 additions and 20 deletions.
75 changes: 75 additions & 0 deletions Guide/deployment.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,81 @@
```

## Deploying with `deploy-to-nixos`

IHP comes with a standard command called `deploy-to-nixos`. This tool is a little wrapper around `nixos-rebuild` and allows you to deploy IHP apps to a NixOS server. With `deploy-to-nixos` you can manage your servers in a fully declarative way and keep the full configuration in your git repository.

AWS EC2 is a good choice for deploying IHP in a professional setup.

### Creating a new EC2 Instance

Start a new EC2 instance and use the official NixOS AMI `NixOS-23.05.426.afc48694f2a-x86_64-linux`. You can find the latest NixOS AMI at https://nixos.org/download#nixos-amazon

Make sure to attach SSH keys to the instance at creation time.

### Connecting to the EC2 Instance

After you've created the instance, configure your local SSH settings to point to the instance.

In your `~/.ssh/config` you typically add something like this:

```
Host ihp-app
HostName ec2-.....compute.amazonaws.com
User root
IdentityFile /Users/marc/.ssh/ihp-app.pem
```

Now you can connect to the instance using `ssh ihp-app`.

### Configuring the Instance

In your `flake.nix` add the following configuration:

```nix
flake.nixosConfigurations."ihp-app" = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
specialArgs = inputs;
modules = [
"${nixpkgs}/nixos/modules/virtualisation/amazon-image.nix"
ihp.nixosModules.appWithPostgres
({ ... }: {
security.acme.defaults.email = "me@example.com";
services.ihp = {
domain = "myihpapp.com";
migrations = ./Application/Migration;
schema = ./Application/Schema.sql;
fixtures = ./Application/Fixtures.sql;
sessionSecret = "xxx";
};
# Add swap to avoid running out of memory during builds
# Useful if your server have less than 4GB memory
swapDevices = [ { device = "/swapfile"; size = 8192; } ];
# This should reflect the nixos version from the NixOS AMI initally installed
# After the initial install, it should not be changed. Otherwise e.g. the postgres
# server might need a manual data migration if NixOS changes the default postgres version
system.stateVersion = "23.05";
})
];
};
```

In the first line the `"ihp-app"` needs to be the same as your SSH name from the previous section.


### Deploying the App

Now you can deploy the app using `deploy-to-nixos` (which is just a small wrapper around nixos-rebuild):

```bash
deploy-to-nixos ihp-app
```

This will connect to the server via SSH and apply the NixOS configuration to the server.

## Deploying with Shipnix

Shipnix is a service for deploying NixOS web servers on DigitalOcean with first-class support for IHP.
Expand Down
3 changes: 2 additions & 1 deletion NixSupport/mkGhcCompiler.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
, dontHaddockPackages ? []
, manualOverrides ? _: _: { }
, haskellPackagesDir ? ./haskell-packages
, filter
, ... }:

let
Expand All @@ -20,7 +21,7 @@ let
};
makePackageSet = dir: pkgs.lib.mapAttrs' (toPackage dir) (builtins.readDir dir);
in {
"ihp" = ((haskellPackagesNew.callPackage "${toString ihp}/ihp.nix") { });
"ihp" = ((haskellPackagesNew.callPackage "${toString ihp}/ihp.nix") { inherit filter; });
} // (makePackageSet haskellPackagesDir) // (makePackageSet "${ihp}/NixSupport/haskell-packages/.");

makeOverrides =
Expand Down
20 changes: 20 additions & 0 deletions NixSupport/nixosModules/app.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Running an IHP web server + Worker
{ config, pkgs, modulesPath, lib, ihp, ... }:
let cfg = config.services.ihp;
in
{
imports = [
ihp.nixosModules.options
ihp.nixosModules.services_app
ihp.nixosModules.services_worker
ihp.nixosModules.services_migrate
];

# Speed up builds with the IHP binary cache
nix.settings.substituters = [ "https://digitallyinduced.cachix.org" ];
nix.settings.trusted-public-keys = [ "digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE=" ];

# Pin the nixpkgs to the IHP nixpkgs
nix.registry.nixpkgs.flake = nixpkgs;
}

100 changes: 100 additions & 0 deletions NixSupport/nixosModules/appWithPostgres.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Running IHP app + a local Postgres connected to it
{ config, pkgs, modulesPath, lib, ihp, ... }:
let cfg = config.services.ihp;
in
{
imports = [
ihp.nixosModules.options
ihp.nixosModules.services_app
ihp.nixosModules.services_worker
ihp.nixosModules.services_migrate
];

# Speed up builds with the IHP binary cache
nix.settings.substituters = [ "https://digitallyinduced.cachix.org" ];
nix.settings.trusted-public-keys = [ "digitallyinduced.cachix.org-1:y+wQvrnxQ+PdEsCt91rmvv39qRCYzEgGQaldK26hCKE=" ];

# Add swap to avoid running out of memory during builds
swapDevices = [ { device = "/swapfile"; size = 8192; } ];

# Vim and psql commands are helpful when accessing the server
environment.systemPackages = with pkgs; [ vim postgresql ];
programs.vim.defaultEditor = true;

system.stateVersion = "23.05";

# Allow public access
networking.firewall.enable = true;
networking.firewall.allowedTCPPorts = [ 80 22 ];

# Enable Letsencrypt
# TODO security.acme.defaults.email = email;
security.acme.acceptTerms = true;

# Add a loadbalancer
services.nginx = {
enable = true;
enableReload = true;
recommendedProxySettings = true;
recommendedGzipSettings = true;
recommendedOptimisation = true;
recommendedTlsSettings = true;
};

# Setup the domain
services.nginx.virtualHosts = {
"${cfg.domain}" = {
serverAliases = [ ];
enableACME = cfg.httpsEnabled;
forceSSL = cfg.httpsEnabled;
locations = {
"/" = {
proxyPass = "http://localhost:8000";
proxyWebsockets = true;
extraConfig =
# required when the target is also TLS server with multiple hosts
"proxy_ssl_server_name on;" +
# required when the server wants to use HTTP Authentication
"proxy_pass_header Authorization;";
};
};
};
};

# Postgres
services.postgresql = {
enable = true;
ensureDatabases = [ cfg.databaseName ];
ensureUsers = [
{
name = cfg.databaseUser;
ensurePermissions = {
"DATABASE ${cfg.databaseName}" = "ALL PRIVILEGES";
};
}
];
initialScript = pkgs.writeText "ihp-initScript" ''
CREATE TABLE IF NOT EXISTS schema_migrations (revision BIGINT NOT NULL UNIQUE);
\i ${ihp}/lib/IHP/IHPSchema.sql
\i ${cfg.schema}
\i ${cfg.fixtures}
'';
};

services.ihp.databaseUrl = ""; # TODO: Set this to some real value

# Enable automatic GC to avoid the disk from filling up
#
# https://github.com/digitallyinduced/ihp/pull/1792#pullrequestreview-1570755863
#
# " It's was a recurring problem on Shipnix that people ran out of disk space and the database service crashed without this"
nix.gc = {
automatic = true;
dates = "weekly";
options = "--delete-older-than 30d";
};

# Saves disk space by detecting and handling identical contents in the Nix Store
nix.settings.auto-optimise-store = true;
}

79 changes: 79 additions & 0 deletions NixSupport/nixosModules/options.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Running IHP app + a local Postgres connected to it
{ config, pkgs, modulesPath, lib, ... }:
with lib;
{
options.services.ihp = {
enable = mkEnableOption "IHP";
domain = mkOption {
type = types.str;
default = "localhost";
};

baseUrl = mkOption {
type = types.str;
default = "https://${config.services.ihp.domain}";
};

migrations = mkOption {
type = types.path;
};

schema = mkOption {
type = types.path;
};

fixtures = mkOption {
type = types.path;
};

httpsEnabled = mkOption {
type = types.bool;
default = true;
};

databaseName = mkOption {
type = types.str;
default = "app";
};

databaseUser = mkOption {
type = types.str;
default = "ihp";
};

databaseUrl = mkOption {
type = types.str;
};

# https://ihp.digitallyinduced.com/Guide/database-migrations.html#skipping-old-migrations
minimumRevision = mkOption {
type = types.int;
default = 0;
};

ihpEnv = mkOption {
type = types.str;
default = "Production";
};

appPort = mkOption {
type = types.int;
default = 8000;
};

requestLoggerIPAddrSource = mkOption {
type = types.str;
default = "FromHeader";
};

sessionSecret = mkOption {
type = types.str;
};

additionalEnvVars = mkOption {
type = types.attrs;
default = {};
};
};
}

30 changes: 30 additions & 0 deletions NixSupport/nixosModules/services/app.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{ config, pkgs, modulesPath, lib, ihpApp, ... }:
let
cfg = config.services.ihp;
in
{
systemd.services.app = {
description = "IHP App";
enable = true;
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
Restart = "always";
WorkingDirectory = "${ihpApp}/lib";
ExecStart = "${ihpApp}/bin/RunProdServer";
};
environment =
let
defaultEnv = {
PORT = "${toString cfg.appPort}";
IHP_ENV = cfg.ihpEnv;
IHP_BASEURL = cfg.baseUrl;
IHP_REQUEST_LOGGER_IP_ADDR_SOURCE = cfg.requestLoggerIPAddrSource;
DATABASE_RUL = cfg.databaseUrl;
IHP_SESSION_SECRET = cfg.sessionSecret;
};
in
defaultEnv // cfg.additionalEnvVars;
};
}
27 changes: 27 additions & 0 deletions NixSupport/nixosModules/services/migrate.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{ config, pkgs, ihp, ... }:
let cfg = config.services.ihp;
in
{
systemd.services.migrate =
let migrateApp = pkgs.stdenv.mkDerivation {
name = "migrate-app";
src = cfg.migrations;
buildPhase = ''
mkdir -p $out/Application/Migration
cp $src/* $out/Application/Migration
'';
};
in {
serviceConfig = {
Type = "oneshot";
};
script = ''
cd ${migrateApp}
${ihp.apps.x86_64-linux.migrate.program}
'';
environment = {
DATABASE_URL = cfg.databaseUrl;
MINIMUM_REVISION = "${toString cfg.minimumRevision}";
};
};
}
29 changes: 29 additions & 0 deletions NixSupport/nixosModules/services/worker.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{ config, pkgs, ihpApp, lib, ... }:
let
cfg = config.services.ihp;
in
{
systemd.services.worker = {
enable = true;
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
Type = "simple";
Restart = "always";
WorkingDirectory = "${ihpApp}/lib";
ExecStart = "${ihpApp}/bin/RunJobs";
};
environment =
let
defaultEnv = {
PORT = "${toString cfg.appPort}";
IHP_ENV = cfg.ihpEnv;
IHP_BASEURL = cfg.baseUrl;
IHP_REQUEST_LOGGER_IP_ADDR_SOURCE = cfg.requestLoggerIPAddrSource;
DATABASE_RUL = cfg.databaseUrl;
IHP_SESSION_SECRET = cfg.sessionSecret;
};
in
defaultEnv // cfg.additionalEnvVars;
};
}
Loading

0 comments on commit 6d23adc

Please sign in to comment.