[Nix-dev] Tips on deploying a Scala Play application

4levels 4levels at gmail.com
Wed Jul 6 17:10:33 CEST 2016


Hi Teo,

I knew I was getting off course with my conclusions, thanks for clarifying
this!

I'll try to give you an overview, I don't mind adding you to our private
Bitbucket repo if you'd like to see all files and folders.
I still don't know where I should add the statements to have the play
project deployed.  All I have for now is the project's directory in a
subfolder, src/play.mancloud.eu

I still have many questions regarding nixo(p)s internals:
- how does the sequence of the nixops modify files matter?
- when to use with *import <nixpkgs>;* or *{ stdenv, lib, config, pkgs, ...
}:* and what are the differences
- ..

I'll try to give you an explanation of how the deploy scripts are composed
below.  There's still a lot of room for improvements and regrouping of
statements as I'm still a nix beginner..


*nixops info* output
Nix expressions: vultr.nix defaults-local.nix defaults.nix
servers-local.nix keys-vm01.nix platform-local.nix

*vultr.nix* contains Vultr specifics + the collectd setup

{
  defaults = {
    deployment = {
      targetEnv = "none";
    };
    fileSystems."/" =
    {
      device = "/dev/vda1";
      options = [ "noatime" "nodiratime" "discard" ];
    };
    swapDevices = [
      {
        device = "/dev/vda2";
      }
    ];
    boot.initrd.availableKernelModules = [ "ata_piix" "uhci_hcd"
"virtio_pci" "virtio_blk" ];
    boot.loader.grub.enable = true;
    boot.loader.grub.version = 2;
    boot.loader.grub.device = "/dev/vda";
    services.collectd.extraConfig = ''
<Plugin df>
  Device "/dev/vda1"
  MountPoint "/"
</Plugin>
<Plugin interface>
    Interface "enp0s3"
    IgnoreSelected false
</Plugin>
    '';
  };
}

*defaults-local.nix* contains some tweaks for my local vm as well as the
copied call to parsets (which I renamed to mancloud-play)

with import <nixpkgs>;
{
  defaults =
  { stdenv, lib, config, pkgs, ... }:
  let
    wrapper = pkgs.callPackage ./mancloud-play-wrapper.nix {
      mancloud-play = import ./mancloud-play-package.nix;
      port = 9000;
    };
  in
  {
    programs.bash.promptInit = ''
      # Provide a nice prompt if the terminal supports it.
      if [ "$TERM" != "dumb" -o -n "$INSIDE_EMACS" ]; then
        PROMPT_COLOR="1;34m"
        let $UID && PROMPT_COLOR="1;34m"
        PS1="\n\[\033[$PROMPT_COLOR\][\u@\h:\w]\\$\[\033[0m\] "
        if test "$TERM" = "xterm"; then
          PS1="\[\033]3;\h:\u:\w\007\]$PS1"
        fi
      fi
    '';
    systemd.services = {
      inherit (wrapper.systemd.services) mancloud-play;
    };
  };
}

*defaults.nix* contains quite some services (key deployments, hostfile
generation based on active projects, the mancloud (PHP) package, PHP-FPM
tweaks and an alert service to monitor our queues, users, nginx, mysql,
collectd, fail2ban, ..  I've removed quite some config as this file is
currently 540 lines long

with import <nixpkgs/lib>;
{
  defaults =
  { config, pkgs, lib, nodes, ... }:
  let
	serverKeys = keys:
	  lib.genAttrs keys (n:
		{
		  text = lib.removeSuffix "\n" (builtins.readFile (./keys +
"/${builtins.replaceStrings ["@"] ["-"] n}") );
		  group = "keys";
		  permissions = "0640";
		}
	  )
	;
	....

  in
  {
    deployment.keys = serverKeys [
      "mancloud.amazon.iam.key_id"
      "mancloud.amazon.iam.access_key"
      "mancloud.amazon.iam.passphrase"
      "mancloud._test.password"
      "phpmyadmin.password"
      "phpmyadmin.secret"
    ];
    environment.systemPackages = with pkgs; [
      wget
      unzip
      gitMinimal
      tmux
      mariadb
      php
      duplicity
      nodejs
      redis
      phpPackages.composer
      phpPackages.redis
      letsencrypt
    ];
    ....
  };
}

*servers-local.nix* contains the specific machine declarations (locally 1
machine vm01) like ssl config, nfs mounts, libvirt related network config
and some overrides from the defaults (stiil some reorganisation needed).
It imports the extra service configuration (here diem-service.nix)

with import <nixpkgs/lib>;
let
  sslCert = ./src/ssl-cert.pem;
  sslKey = ./src/ssl-key.pem;
  sslCA = ./src/ssl-ca.pem;
  sslDHParam = ./src/ssl-dhparam.pem;
  nginxSslConfig = ''
    ${builtins.readFile ./src/nginx-ssl.conf}
    ssl_certificate      ${sslCert};
    ssl_certificate_key  ${sslKey};
    ssl_trusted_certificate ${sslCA};
    ssl_dhparam ${sslDHParam};
  '';

in
{
  vm01 =
    { config, pkgs, nodes, ... }:
    {
      imports = [ ./diem-service.nix ];
      deployment = {
        targetHost = "192.168.121.50";
      };
      environment.systemPackages = with pkgs; [
        strace
        gitAndTools.git-crypt
      ];
      services.mysql.enable = true;
      networking.hostName = "vm01"; # Define your hostname.
      networking.enableIPv6 = false;
      networking.extraHosts = "192.168.121.1 d01 d01.local";
      networking.interfaces = {
        enp0s3 = {
          ip4 = [ { address = "192.168.121.50"; prefixLength = 24; } ];
        };
        enp0s9 = {
          ip4 = [ { address = "192.168.0.98"; prefixLength = 24; } ];
        };
      };
      networking.nameservers = [ "192.168.121.1" ];
      networking.defaultGateway = "192.168.121.1";
      networking.firewall.allowedTCPPorts = [ 22 80 443 9000 ];
      fileSystems."/data/dev" = {
        device = "d01:/data/dev";
        fsType = "nfs";
        options = [ "defaults" "noatime" "nolock" "noacl" ];
      };
      services.collectd.enable = false;
      services.nginx = {
        httpConfig = ''
        # phpmyadmin
        server {
          listen 443 ssl spdy;
          server_name
            pma-local.mancloud.eu
          ;
          allow 192.168.121.1;
          allow 192.168.0.0/24;
          allow 192.168.1.0/24;
          allow 127.0.0.1;
          deny all;
          ${nginxSslConfig}
          root ${import ./phpmyadmin-package.nix};
          access_log '/tmp/pma-access.log';
          error_log '/tmp/pma-error.log' debug;
          location ~ "^(.+\.php)($|/)" {
            include ${pkgs.nginx}/conf/mime.types;
            ${builtins.readFile ./src/nginx-php-config.conf}
          }
          location / {
            include ${pkgs.nginx}/conf/mime.types;
            ${builtins.readFile ./src/nginx-rewrite.conf}
          }
        }
        '';
      };
    };
}


*diem-service.nix* contains the configuration for diem (PHP Symfony based
CMF) and is quite large as well (+500 lines).  I removed less relevant parts

{ config, lib, pkgs, nodes, ... }:
let
  cfg = config.services.diem;
  serviceDir = "/var/www";
  systemdService = name: value:
    {
      name = "diem-${name}";
      value = {
        description = "Diem ${name} service";
        wantedBy = [ "multi-user.target" "nginx.target" ];
        after = [ "keys.target" "network.target" "mysql.target" ] ++
lib.mapAttrsToList (n: v:
          "diem-${n}.service"
        ) (lib.filterAttrs (n: v: n < name) cfg.platforms);
        requires = [ "keys.target" "network.target" "mysql.target" ] ++
lib.mapAttrsToList (n: v:
          "diem-${n}.service"
        ) (lib.filterAttrs (n: v: n < name) cfg.platforms);
        environment = {
          inherit (config.environment.variables) SSL_CERT_FILE;
        };
        serviceConfig.ExecStart = "${serviceDir}/${name}/nixSetup.sh";
      };
    };
  serverActivation = value:
    lib.concatStrings (lib.mapAttrsToList(n: v:
      if (lib.isAttrs(v) && lib.hasAttr("platform") v) then
        "${packageActivation n v};"
      else ""
      ) value
    );
  packageActivation = name: value:
    {
      name = "diem-${name}";
      value =
        ''
          # create / symlink project dirs
          mkdir -p ${serviceDir}/${name}
          mkdir -p
${serviceDir}/${name}/{cache/dm,config/dm,data/backup/db,data/backup/uploads,data/restore/db,data/restore/uploads,data/dm/i18n,data/exports,log,web/uploads}

          # letsEncrypt script
          cp ${pkgs.writeText "letsEncrypt.sh" "${letsEncrypt name value}"
} ${serviceDir}/${name}/letsEncrypt.sh
          chmod +x ${serviceDir}/${name}/letsEncrypt.sh

          # nixSetup script
          cp ${pkgs.writeText "nixSetup.sh" "${nixSetup name value}" }
${serviceDir}/${name}/nixSetup.sh
          chmod +x ${serviceDir}/${name}/nixSetup.sh

          # s3Backup script
          cp ${pkgs.writeText "s3Backup.sh" "${s3Backup name}" }
${serviceDir}/${name}/s3Backup.sh
          chmod +x ${serviceDir}/${name}/s3Backup.sh
        '';
    };
  diem = (import ./diem-package.nix);
  ...

in
with lib;
{
  options = {
    services.diem = {
      platforms = mkOption {
        default = {};
        example = {
          test = {
            database = {
              password = "foopass";
            };
            timezone = "Europe/Brussels";
          };
        };
      };
    };
  };

  config = mkIf (cfg.platforms != {}) {
    system.activationScripts = mapAttrs' packageActivation cfg.platforms;
    systemd.services = mapAttrs' systemdService cfg.platforms // timers
cfg.platforms;
  };
}

*diem-package.nix* contains the packaging statements, read from a github
repo

with import <nixpkgs> {};
pkgs.stdenv.mkDerivation rec {
  name = "diem-1.0.0";
  src = pkgs.fetchgit {
    url = "https://github.com/diem-project/diem.git";
    rev = "refs/heads/master";
    sha256 = "11scd9z7h91bd242gvy0grnlx75d25ckx1k0k3qvz74p55f1kww7";
  };

  buildPhase = "true";
  installPhase =
    ''
      mkdir -p $out
      cp -r * $out
    '';
}



*keys-vm01.nix* contains the inclusions of the configuration keys and other
sensitive data for this host

{
  vm01 =
    { config, pkgs, lib, ... }:
    let
      serverKeys = keys:
        lib.genAttrs keys (n:
          {
            text = lib.removeSuffix "\n" (builtins.readFile
(./keys/vm01 + "/${builtins.replaceStrings ["@"] ["-"] n}") );
            group = "keys";
            permissions = "0640";
          }
        )
      ;
    in
    {
      deployment.keys = serverKeys [
        "diem.project.database.password"
        "diem.project.encryption.cipher"
        "diem.project.encryption.key"
        ...
      ];
    };
}


platform-local.nix contains the project definitions per server

with import <nixpkgs/lib>;
{
  vm01 =
    { config, pkgs, ... }:
    {
      services.diem.platforms = {
        project = {
          domain = "local.project";
          path = "/data/dev/projects/project";
        };
      };
      ...
    };
}


Kind regards and thank you again for your willing and friendly attitude!

Erik





On Wed, Jul 6, 2016 at 5:41 AM Teo Klestrup Röijezon <teo at nullable.se>
wrote:

> HI Erik,
>
> That's pretty much entirely wrong. :P ParseTS is just a linter script for
> the game scripting language TorqueScript. ParseTS-Playground was a pastebin
> that would run the submitted code through the linter. For example, see
> https://parsets-playground.nullable.se/snippets/13. The datastore used
> was PostgreSQL.
>
> Anyway, apart from the ParseTS stuff, at least those scripts should be
> pretty much straightforward to copy to any Play application, though for the
> config stuff to work you'll need to add the line 'include "local.conf"' to
> your conf/application.conf.
>
> Any chance you could post your current setup and the errors you get?
>
> // Teo
>
> On 6 July 2016 at 04:31, 4levels <4levels at gmail.com> wrote:
>
>> Hi Teo,
>>
>> I've come quite far in setting up things, but I keep running into
>> building errors.
>> It has everything to do with me removing all references to parsets and
>> postgres and renaming things here and there, trying to merge them with the
>> current deploy setup.
>>
>> Do I understand correctly that parsets is a library to store data, using
>> postgres in the background?  I'd like to start using Event Sourcing with
>> Scala / Akka so I don't need a datastore like parsets, correct?  I'm very
>> unsure about this as I literally started today with learning Scala / Play.
>> I got my toes wet with Java before but that's really it.
>>
>> Something else I found interesting as I'm quite an Nginx fan and have
>> nginx running with proxies already: Nginx has capabilities to deal with
>> Java in different ways, as proxy or tied with eg Clojure for even faster
>> results..
>>
>> The journey continues ;-)
>>
>>
>> Kind regards,
>>
>> Erik
>>
>> On Tue, Jul 5, 2016 at 10:23 PM 4levels <4levels at gmail.com> wrote:
>>
>>> Hi Teo,
>>>
>>> Thank you for your explanation and quick qualitative response!
>>>
>>> I'll be looking at your code asap and report back with my experiences
>>> ;-)
>>>
>>> Kind regards,
>>>
>>> Erik
>>>
>>> On Tue, Jul 5, 2016, 22:08 Teo Klestrup Röijezon <teo at nullable.se>
>>> wrote:
>>>
>>>> Hi,
>>>>
>>>> A JRE should be enough for running it, but you need sbt and a JDK for
>>>> building. I've got a derivation for a Play website at
>>>> https://github.com/BlocklandGlass/ParseTS-Playground/blob/master/parsets-playground.nix,
>>>> with the NixOS/NixOps setup at
>>>> https://github.com/BlocklandGlass/ParseTS-Playground/tree/master/deployment
>>>> .
>>>>
>>>> The gist of it is to run "sbt stage" in the build phase, and to then
>>>> take "target/universal/stage" as your build output. However, you'll also
>>>> need to wrap the launcher script to add your JRE and to add gawk (which the
>>>> launcher script requires). Finally, on any modern system (such as NixOS)
>>>> you'll also want to disable Play's PID file management, since systemd takes
>>>> care of that anyway. I didn't in that script, but you'll probably also want
>>>> to add a testing phase as part of the build.
>>>>
>>>> The big drawback with this approach is that SBT downloads all
>>>> dependencies from the internet on demand, which won't work on a Nix setup
>>>> with proper isolation (ideally, builds should only have network access if
>>>> they deterministically produce a given hash).
>>>>
>>>> I've been toying with the idea of writing a sbt2nix SBT plugin that
>>>> generates Nix definitions to build a local maven mirror for the
>>>> dependencies, but I haven't got around to that (yet).
>>>>
>>>> // Teo
>>>>
>>>> On 5 July 2016 at 21:52, 4levels <4levels at gmail.com> wrote:
>>>>
>>>>> Hi Nix-devs,
>>>>>
>>>>> This is a plain request for assistance / best practices for using
>>>>> Nixos with Java / Scala / Play.  Akka with EventSourcing are also a topic
>>>>> of interest.
>>>>>
>>>>> I'm currently trying to get a Scala Play app up and running on my
>>>>> nixOps deployed machines.  As I'm very unfamiliar with running Java based
>>>>> apps, I'd like to know if someone has experience on the common pitfalls and
>>>>> tips on keeping the servers healthy (I just caused my laptop's 8 cores to
>>>>> go 100% without being able to stop the server started by the activator
>>>>> call).
>>>>>
>>>>> I've seen some related packages in nixpkgs and have many questions
>>>>> like eg. do I need sbt (which seems to provide typesafe - activator) and a
>>>>> jdk on the production servers or are is a jre sufficient? How do I deploy
>>>>> and run a Java app developed locally?
>>>>> And how do I set-up a local nixos vm for Java development?
>>>>>
>>>>> I'm still investigating and learning a lot myself, so nix-related
>>>>> knowledge is my main concern here (as I need to figure out the rest myself
>>>>> anyway ;-)
>>>>>
>>>>> I'll be happy to share my findings and configuration / setup..
>>>>>
>>>>>
>>>>> Kind regards,
>>>>>
>>>>> Erik
>>>>>
>>>>> _______________________________________________
>>>>> nix-dev mailing list
>>>>> nix-dev at lists.science.uu.nl
>>>>> http://lists.science.uu.nl/mailman/listinfo/nix-dev
>>>>>
>>>>>
>>>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.science.uu.nl/pipermail/nix-dev/attachments/20160706/b6cde299/attachment-0001.html>


More information about the nix-dev mailing list