├── .envrc ├── .gitignore ├── README.md ├── default.nix ├── flake.lock ├── flake.nix ├── mix.exs ├── mix.lock ├── pkg ├── _pkgs.nix ├── config │ ├── configuration.nix │ └── flake.nix ├── hex.nix ├── mix_compile.nix ├── mix_deps_compile.nix ├── mix_deps_get.nix ├── mix_docs.nix ├── mix_release.nix ├── node_modules.nix ├── rebar3.nix ├── scripts │ └── pkgs_update.exs ├── service.nix └── temporary_postgresql_db.nix └── secrets └── .env /.envrc: -------------------------------------------------------------------------------- 1 | use flake 2 | source secrets/.env 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /_build/ 2 | /deps/ 3 | 4 | /.cache/ 5 | # /secrets/ 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Elixir (Phoenix) Nix Example 2 | 3 | > Example repo to show off how I use Nix as build environment for Elixir / Phoenix projects. 4 | > Important Nix stuff is located within `flake.nix` / `default.nix` & `pkg/` folder. 5 | This repo is not a working project! It just contains parts of the workflow. 6 | 7 | **Note** 8 | 9 | Since starting this example a lot of positive things have happened or are happening within Nixpkgs. 10 | You may be interested in `mixRelease` or friends. 11 | 12 | I for myself stopped to use a full Nix leveraging build setup. I only use parts of this workflow anymore with reusing local `_build` & `deps` as it speeds up things for my requirements enorm. 13 | 14 | --- 15 | 16 | ## Setup 17 | 18 | This repo provides an easy environment setup by the usage of [Nix](https://nixos.org) (w/ flakes enabled). 19 | Nix currently runs on **Linux** and **macOS**. 20 | 21 | - [Getting Nix](https://nixos.org/download.html) 22 | 23 | **Enabling Nix flakes (Nix v2.4)** 24 | ``` 25 | # ~/.config/nix/nix.conf 26 | experimental-features = nix-command flakes 27 | ``` 28 | 29 | By using Nix, beside Nix nothing else needs to be installed manually. 30 | 31 | **Windows** 32 | Recommended also using Nix within a Linux Virtual Machine or WSL 2. 33 | 34 | ### Manual Setup 35 | 36 | For maintaining simplicity, instructions for a manual setup isn't part of this readme. 37 | Anyway, required dependencies: 38 | 39 | - [Elixir](https://elixir-lang.org) (& [Erlang](https://www.erlang.org)) 40 | - [PostgreSQL](https://www.postgresql.org) 41 | - ([Node.js](https://nodejs.org)) 42 | 43 | ## Development 44 | 45 | ### Environment 46 | 47 | ```sh 48 | # Enter shell w/ development environment + temporary PostgreSQL database 49 | nix develop 50 | 51 | # Run commands without shell 52 | nix develop --command 53 | ``` 54 | 55 | ### Basics commands for working with Elixir / Phoenix 56 | 57 | ```sh 58 | # Get Elixir mix deps 59 | mix deps.get 60 | 61 | # Get JS packages 62 | npm install --prefix assets 63 | 64 | # Start Phoenix server 65 | mix phx.server 66 | 67 | # Start Elixir application 68 | mix run --no-halt 69 | 70 | # Enter IEx 71 | iex -S mix 72 | 73 | # Run tests 74 | mix test 75 | ``` 76 | 77 | ### Build 78 | 79 | ```sh 80 | # mix deps 81 | nix build --file default.nix mix_deps --relaxed-sandbox 82 | 83 | # JS packages 84 | nix build --file default.nix node_modules --relaxed-sandbox 85 | 86 | # Compile 87 | nix build --file default.nix mix_build --relaxed-sandbox 88 | 89 | # Compile cacheable `_build` 90 | nix develop --command mix compile 91 | 92 | # Generate docs using `mix docs` 93 | nix build --file default.nix docs --relaxed-sandbox 94 | 95 | # Build production release using `mix release` 96 | nix build --file default.nix release --relaxed-sandbox 97 | ``` 98 | 99 | ### Maintenance 100 | 101 | ```sh 102 | # Update pinned Nix pkgs 103 | elixir pkg/scripts/pkgs_update.exs 104 | # Update flake.lock 105 | nix flake update 106 | 107 | # Check / update / clean Mix deps 108 | mix hex.outdated 109 | mix deps.update --all 110 | mix deps.clean --unlock --unused 111 | 112 | # Check / update / clean NPM packages 113 | npm outdated --prefix assets 114 | npm update --prefix assets 115 | npm prune --prefix assets 116 | 117 | # Flake checks 118 | nix flake check 119 | ``` 120 | 121 | ## [direnv](https://direnv.net/) 122 | 123 | `direnv` let's you automatically load environment variables per directory defined in `.envrc`. 124 | 125 | **Working with direnv** 126 | ```sh 127 | # Install via Nix 128 | nix profile install nixpkgs#direnv 129 | 130 | # Setup hook 131 | # Add the following to your .profile 132 | eval "$(direnv hook bash)" 133 | ``` 134 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import ./pkg/_pkgs.nix, MIX_ENV ? "prod", release_name ? "example" }: 2 | 3 | rec { 4 | erlang = pkgs.beam.interpreters.erlangR24; 5 | # BEAM no X versions are not in cache - needs to build yourself 6 | # erlang = pkgs.beam_nox.interpreters.erlangR24; 7 | elixir = pkgs.beam.packages.erlangR24.elixir_1_12; 8 | # BEAM no X versions are not in cache - needs to build yourself 9 | # elixir = pkgs.beam_nox.packages.erlangR24.elixir_1_12; 10 | nodejs = pkgs.nodejs-16_x; 11 | 12 | LANG = "C.UTF-8"; 13 | MIX_PATH = "${hex}/archives/hex-${hex.version}/hex-${hex.version}/ebin"; 14 | MIX_REBAR3 = "${rebar3}/bin/rebar3"; 15 | 16 | # This is opinionated instead of simple using: 17 | # hex = pkgs.beam.packages.erlang.hex; 18 | hex = pkgs.callPackage ./pkg/hex.nix { 19 | inherit elixir LANG; 20 | }; 21 | 22 | # This is opinionated instead of simple using: 23 | # rebar3 = pkgs.beam.packages.erlang.rebar3; 24 | rebar3 = pkgs.callPackage ./pkg/rebar3.nix { 25 | inherit erlang; 26 | }; 27 | 28 | # Needs `--relaxed-sandbox` if used without setting hash (impure fetch) 29 | mix_deps = pkgs.callPackage ./pkg/mix_deps_get.nix { 30 | inherit elixir MIX_PATH MIX_REBAR3 MIX_ENV LANG; 31 | # hash is also changing with env 32 | # hash = "sha256:${pkgs.lib.fakeSha256}"; 33 | }; 34 | 35 | # Needs `--relaxed-sandbox` if used without setting hash (impure fetch) 36 | mix_deps_build = pkgs.callPackage ./pkg/mix_deps_compile.nix { 37 | inherit elixir MIX_PATH MIX_REBAR3 MIX_ENV LANG; 38 | inherit mix_deps; 39 | # hash is also changing with env 40 | # hash = "sha256:${pkgs.lib.fakeSha256}"; 41 | }; 42 | 43 | # Needs `--relaxed-sandbox` if used without setting hash (impure fetch) 44 | mix_build = pkgs.callPackage ./pkg/mix_compile.nix { 45 | inherit elixir MIX_PATH MIX_REBAR3 MIX_ENV LANG; 46 | inherit mix_deps mix_deps_build; 47 | # hash is also changing with env 48 | # hash = "sha256:${pkgs.lib.fakeSha256}"; 49 | }; 50 | 51 | # Needs `--relaxed-sandbox` if used without setting hash (impure fetch) 52 | node_modules = pkgs.callPackage ./pkg/node_modules.nix { 53 | inherit nodejs; 54 | # hash = "sha256:${pkgs.lib.fakeSha256}"; 55 | }; 56 | 57 | # Needs `--relaxed-sandbox` if used without setting hash & hashed inputs (impure fetch) 58 | # Works probably only for env "dev" → mix.exs 59 | docs = pkgs.callPackage ./pkg/mix_docs.nix { 60 | inherit elixir MIX_HOME MIX_REBAR3 MIX_ENV LANG; 61 | inherit mix_deps mix_build; 62 | # hash is also changing with env 63 | # hash = "sha256:${pkgs.lib.fakeSha256}"; 64 | }; 65 | 66 | # Needs `--relaxed-sandbox` if used without setting hash & hashed inputs (impure fetch) 67 | release = pkgs.callPackage ./pkg/mix_release.nix { 68 | inherit elixir MIX_HOME MIX_REBAR3 MIX_ENV LANG; 69 | inherit mix_deps mix_build release_name; 70 | inherit node_modules; 71 | inherit nodejs; 72 | # hash is also changing with env & release_name 73 | # hash = "sha256:${pkgs.lib.fakeSha256}"; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "nixpkgs": { 4 | "locked": { 5 | "lastModified": 1639505474, 6 | "narHash": "sha256-lnu8bPuJL/ayG4JaZO6wt3y6f1nDeAB380hmFwAMnhQ=", 7 | "owner": "nixos", 8 | "repo": "nixpkgs", 9 | "rev": "fc58c3761e9b9b3aee635bb2894bab97aa1b6ff6", 10 | "type": "github" 11 | }, 12 | "original": { 13 | "owner": "nixos", 14 | "ref": "nixpkgs-unstable", 15 | "repo": "nixpkgs", 16 | "type": "github" 17 | } 18 | }, 19 | "root": { 20 | "inputs": { 21 | "nixpkgs": "nixpkgs" 22 | } 23 | } 24 | }, 25 | "root": "root", 26 | "version": 7 27 | } 28 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Main flake"; 3 | 4 | nixConfig.bash-prompt = "\\e[0;32m[nix-develop@\\h] \\W>\\e[m "; 5 | 6 | inputs = { 7 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 8 | }; 9 | 10 | outputs = { self, nixpkgs }: 11 | let 12 | pkgs = nixpkgs.legacyPackages.x86_64-linux; 13 | LANG = "C.UTF-8"; 14 | root = ./.; 15 | 16 | erlang = pkgs.beam.interpreters.erlangR24; 17 | elixir = pkgs.beam.packages.erlangR24.elixir_1_13; 18 | nodejs = pkgs.nodejs-17_x; 19 | 20 | # This is opinionated instead of simple using: 21 | # pkgs.beam.packages.erlang.hex; 22 | hex = pkgs.callPackage ./pkg/hex.nix { 23 | inherit elixir LANG; 24 | }; 25 | MIX_PATH = "${hex}/archives/hex-${hex.version}/hex-${hex.version}/ebin"; 26 | 27 | # This is opinionated instead of simple using: 28 | # pkgs.beam.packages.erlang.rebar3; 29 | rebar3 = pkgs.callPackage ./pkg/rebar3.nix { 30 | inherit erlang; 31 | }; 32 | MIX_REBAR3 = "${rebar3}/bin/rebar3"; 33 | 34 | 35 | postgresql_setup = import ./pkg/temporary_postgresql_db.nix { }; 36 | in 37 | { 38 | devShell.x86_64-linux = pkgs.mkShell { 39 | inherit LANG MIX_PATH MIX_REBAR3; 40 | # use local HOME to avoid global things 41 | MIX_HOME = ".cache/mix"; 42 | HEX_HOME = ".cache/hex"; 43 | # enable IEx shell history 44 | ERL_AFLAGS = "-kernel shell_history enabled"; 45 | packages = [ 46 | elixir 47 | nodejs 48 | pkgs.inotify-tools 49 | pkgs.postgresql_14 50 | pkgs.nixpkgs-fmt 51 | ]; 52 | shellHook = postgresql_setup; 53 | }; 54 | 55 | checks.x86_64-linux = { 56 | format = pkgs.runCommandLocal "check-formatted" 57 | { 58 | inherit LANG MIX_PATH; 59 | nativeBuildInputs = [ 60 | elixir 61 | pkgs.nixpkgs-fmt 62 | ]; 63 | } '' 64 | cd ${root} 65 | 66 | nixpkgs-fmt *.nix --check 67 | nixpkgs-fmt ./pkg/*.nix --check 68 | touch $out 69 | 70 | # WIP 71 | # mix format --check-formatted 72 | ''; 73 | }; 74 | }; 75 | } 76 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule ElixirNixExample.MixProject do 2 | use Mix.Project 3 | 4 | def project do 5 | [ 6 | app: :elixir_nix_example, 7 | version: "0.1.0", 8 | deps: [], 9 | releases: [ 10 | example: [ 11 | include_executables_for: [:unix] 12 | ] 13 | ] 14 | ] 15 | end 16 | 17 | # Configuration for the OTP application. 18 | # 19 | # Type `mix help compile.app` for more information. 20 | def application do 21 | [ 22 | mod: {ElixirNixExample.Application, []}, 23 | extra_applications: [:logger, :runtime_tools] 24 | ] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"}, 3 | "phoenix": {:hex, :phoenix, "1.6.2", "6cbd5c8ed7a797f25a919a37fafbc2fb1634c9cdb12a4448d7a5d0b26926f005", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bbee475acae0c3abc229b7f189e210ea788e63bd168e585f60c299a4b2f9133"}, 4 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, 5 | "phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"}, 6 | "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"}, 7 | "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, 8 | "telemetry": {:hex, :telemetry, "1.0.0", "0f453a102cdf13d506b7c0ab158324c337c41f1cc7548f0bc0e130bbf0ae9452", [:rebar3], [], "hexpm", "73bc09fa59b4a0284efb4624335583c528e07ec9ae76aca96ea0673850aec57a"}, 9 | } 10 | -------------------------------------------------------------------------------- /pkg/_pkgs.nix: -------------------------------------------------------------------------------- 1 | # generated by pkgs_update.exs (2021-12-15) 2 | import 3 | (fetchTarball { 4 | name = "nixpkgs-unstable_2021-12-15"; 5 | url = "https://github.com/NixOS/nixpkgs/archive/fc58c3761e9b9b3aee635bb2894bab97aa1b6ff6.tar.gz"; 6 | }) 7 | { } 8 | -------------------------------------------------------------------------------- /pkg/config/configuration.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, ... }: 2 | 3 | { 4 | nix = { 5 | package = pkgs.nix_2_4; 6 | extraOptions = '' 7 | experimental-features = nix-command flakes 8 | ''; 9 | }; 10 | 11 | imports = [ 12 | ./hardware-configuration.nix 13 | /home/main/app/elixir-app/source/pkg/service.nix 14 | ]; 15 | 16 | # Use the GRUB 2 boot loader. 17 | boot.loader.grub.enable = true; 18 | boot.loader.grub.version = 2; 19 | boot.loader.grub.device = "/dev/sda"; 20 | 21 | networking.hostName = "elixir-app-host"; 22 | 23 | networking.firewall = { 24 | allowedTCPPorts = [ 80 443 ]; 25 | }; 26 | 27 | networking.useDHCP = false; 28 | networking.interfaces.ens3.useDHCP = true; 29 | 30 | # IPv6 31 | networking.interfaces.ens3.ipv6.addresses = [{ 32 | address = ""; 33 | prefixLength = 64; 34 | }]; 35 | networking.defaultGateway6 = { 36 | address = "fe80::1"; 37 | interface = "ens3"; 38 | }; 39 | 40 | console.keyMap = "de"; 41 | i18n.defaultLocale = "en_US.UTF-8"; 42 | time.timeZone = "Europe/Amsterdam"; 43 | 44 | # System packages 45 | environment.systemPackages = with pkgs; [ 46 | vim 47 | ]; 48 | 49 | # Services 50 | services.caddy = { 51 | enable = true; 52 | email = "me@example.com"; 53 | config = '' 54 | www.example.com { 55 | redir https://example.com{uri} 56 | } 57 | example.com { 58 | encode gzip 59 | log 60 | reverse_proxy localhost:4000 61 | } 62 | ''; 63 | }; 64 | 65 | services.openssh.enable = true; 66 | services.openssh.passwordAuthentication = false; 67 | services.openssh.permitRootLogin = "no"; 68 | 69 | # User accounts. Don't forget to set a password with with passwd 70 | users.users.main = { 71 | isNormalUser = true; 72 | initialPassword = "main"; 73 | extraGroups = [ "wheel" "networkmanager" ]; 74 | openssh.authorizedKeys.keys = [ ]; 75 | }; 76 | 77 | security.doas = { 78 | enable = true; 79 | extraConfig = '' 80 | permit persist keepenv main 81 | permit nopass setenv { NIX_PATH } main cmd nixos-rebuild args switch --impure --relaxed-sandbox 82 | ''; 83 | }; 84 | security.sudo.enable = false; 85 | 86 | # Automatic `nix-collect-garbage -d` 87 | nix.gc.automatic = true; 88 | nix.gc.dates = "weekly"; 89 | nix.gc.options = "--delete-older-than 10d"; 90 | 91 | # QEMU guest agent 92 | # https://docs.hetzner.com/de/cloud/technical-details/faq/#wie-sind-unsere-system-images-aufgebaut 93 | services.qemuGuest.enable = true; 94 | # for password reset via Hetzner Cloud 95 | # systemd.services.qemu-guest-agent.path = [ pkgs.shadow ]; 96 | 97 | # NixOS State Version 98 | system.stateVersion = "21.11"; 99 | } 100 | -------------------------------------------------------------------------------- /pkg/config/flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.11-small"; 3 | 4 | outputs = { self, nixpkgs }: { 5 | nixosConfigurations.elixir-app-host = nixpkgs.lib.nixosSystem { 6 | system = "x86_64-linux"; 7 | modules = [ ./configuration.nix ]; 8 | }; 9 | }; 10 | } 11 | -------------------------------------------------------------------------------- /pkg/hex.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, elixir, LANG }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | pname = "hex"; 5 | version = "1.0.1"; 6 | elixir_version = "1.13.0"; 7 | # How to obtain hash: 8 | # nix-prefetch-url https://repo.hex.pm/installs//hex-.ez 9 | # nix-prefetch-url https://repo.hex.pm/installs/1.13.0/hex-1.0.1.ez 10 | hash = "sha256:1ywryrmbpgf8519413pf8099ggicv3r5pbxan9bi9sbwv2393fw4"; 11 | src = import { 12 | url = "https://repo.hex.pm/installs/${elixir_version}/hex-${version}.ez"; 13 | inherit hash; 14 | }; 15 | inherit LANG; 16 | nativeBuildInputs = [ 17 | elixir 18 | ]; 19 | builder = builtins.toFile "builder.sh" '' 20 | source $stdenv/setup 21 | 22 | mkdir $out 23 | MIX_HOME=$out mix archive.install $src --force 24 | ''; 25 | } 26 | -------------------------------------------------------------------------------- /pkg/mix_compile.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, lib, elixir, MIX_PATH, MIX_REBAR3, MIX_ENV, LANG, mix_deps, mix_deps_build, hash ? null }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | __noChroot = if hash == null then true else false; 5 | name = "mix_build"; 6 | config = ../config; 7 | lib_dir = ../lib; 8 | mix_exs = ../mix.exs; 9 | mix_lock = ../mix.lock; 10 | inherit MIX_PATH MIX_REBAR3 MIX_ENV LANG mix_deps mix_deps_build; 11 | nativeBuildInputs = [ 12 | elixir 13 | ]; 14 | builder = builtins.toFile "builder.sh" '' 15 | source $stdenv/setup 16 | 17 | ln -s $config config 18 | ln -s $lib_dir lib 19 | ln -s $mix_exs mix.exs 20 | ln -s $mix_lock mix.lock 21 | 22 | cp -r $mix_deps/. deps/ 23 | chmod -R 700 deps 24 | 25 | cp -r $mix_deps_build/. _build/ 26 | chmod -R 700 _build 27 | 28 | mix compile 29 | 30 | mkdir $out 31 | cp -r _build/$MIX_ENV/. $out/$MIX_ENV/ 32 | ''; 33 | 34 | outputHashMode = "recursive"; 35 | outputHash = hash; 36 | 37 | impureEnvVars = lib.fetchers.proxyImpureEnvVars; 38 | } 39 | -------------------------------------------------------------------------------- /pkg/mix_deps_compile.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, lib, elixir, MIX_PATH, MIX_REBAR3, MIX_ENV, LANG, mix_deps, hash ? null }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | __noChroot = if hash == null then true else false; 5 | name = "mix_deps_build"; 6 | config = ../config; 7 | mix_exs = ../mix.exs; 8 | mix_lock = ../mix.lock; 9 | inherit MIX_PATH MIX_REBAR3 MIX_ENV LANG mix_deps; 10 | nativeBuildInputs = [ 11 | elixir 12 | ]; 13 | builder = builtins.toFile "builder.sh" '' 14 | source $stdenv/setup 15 | 16 | ln -s $config config 17 | ln -s $mix_exs mix.exs 18 | ln -s $mix_lock mix.lock 19 | 20 | cp -r $mix_deps/. deps/ 21 | chmod -R 700 deps 22 | 23 | mix deps.compile 24 | 25 | mkdir $out 26 | cp -r _build/$MIX_ENV/. $out/$MIX_ENV/ 27 | ''; 28 | 29 | outputHashMode = "recursive"; 30 | outputHash = hash; 31 | 32 | impureEnvVars = lib.fetchers.proxyImpureEnvVars; 33 | } 34 | -------------------------------------------------------------------------------- /pkg/mix_deps_get.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, lib, elixir, MIX_PATH, MIX_REBAR3, MIX_ENV, LANG, hash ? null }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | __noChroot = if hash == null then true else false; 5 | name = "mix_deps"; 6 | config = ../config; 7 | mix_exs = ../mix.exs; 8 | mix_lock = ../mix.lock; 9 | inherit MIX_PATH MIX_REBAR3 MIX_ENV LANG; 10 | nativeBuildInputs = [ 11 | elixir 12 | ]; 13 | builder = builtins.toFile "builder.sh" '' 14 | source $stdenv/setup 15 | 16 | ln -s $config config 17 | ln -s $mix_exs mix.exs 18 | ln -s $mix_lock mix.lock 19 | 20 | export HEX_HOME=$TMPDIR/hex 21 | export MIX_DEPS_PATH=$out 22 | MIX_QUIET=true mix deps.get --only $MIX_ENV --no-archives-check 23 | ''; 24 | 25 | outputHashMode = "recursive"; 26 | outputHash = hash; 27 | 28 | impureEnvVars = lib.fetchers.proxyImpureEnvVars; 29 | } 30 | -------------------------------------------------------------------------------- /pkg/mix_docs.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, lib, elixir, MIX_PATH, MIX_REBAR3, MIX_ENV, LANG, mix_deps, mix_build, hash ? null }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | __noChroot = if hash == null then true else false; 5 | name = "mix_docs"; 6 | lib = ../lib; 7 | mix_exs = ../mix.exs; 8 | mix_lock = ../mix.lock; 9 | inherit MIX_PATH MIX_REBAR3 MIX_ENV LANG mix_deps mix_build; 10 | nativeBuildInputs = [ 11 | elixir 12 | ]; 13 | # In case you have ExDoc :extras, you'll need to add theme here! 14 | builder = builtins.toFile "builder.sh" '' 15 | source $stdenv/setup 16 | 17 | ln -s $lib lib 18 | ln -s $mix_exs mix.exs 19 | ln -s $mix_lock mix.lock 20 | 21 | cp -r $mix_deps/. deps/ 22 | chmod -R 700 deps 23 | 24 | cp -r $mix_build/. _build/ 25 | chmod -R 700 _build 26 | 27 | mkdir $out 28 | mix docs --output $out 29 | ''; 30 | 31 | outputHashMode = "recursive"; 32 | outputHash = hash; 33 | 34 | impureEnvVars = lib.fetchers.proxyImpureEnvVars; 35 | } 36 | -------------------------------------------------------------------------------- /pkg/mix_release.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, lib, elixir, MIX_PATH, MIX_REBAR3, MIX_ENV, LANG, mix_deps, mix_build, release_name, nodejs, node_modules, hash ? null }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | __noChroot = if hash == null then true else false; 5 | name = "mix_release"; 6 | assets = ../assets; 7 | config = ../config; 8 | priv = ../priv; 9 | rel = ../rel; 10 | mix_exs = ../mix.exs; 11 | mix_lock = ../mix.lock; 12 | inherit MIX_ENV MIX_PATH MIX_REBAR3 LANG mix_deps mix_build release_name node_modules; 13 | nativeBuildInputs = [ 14 | elixir 15 | nodejs 16 | ]; 17 | builder = builtins.toFile "builder.sh" '' 18 | source $stdenv/setup 19 | 20 | ln -s $config config 21 | ln -s $rel rel 22 | ln -s $mix_exs mix.exs 23 | ln -s $mix_lock mix.lock 24 | 25 | cp -r $mix_build/. _build/ 26 | chmod -R 700 _build 27 | 28 | cp -r $mix_deps/. deps/ 29 | chmod -R 700 deps 30 | 31 | cp -r $assets/. assets/ 32 | chmod -R 700 assets 33 | 34 | cp -r $node_modules/. assets/node_modules/ 35 | 36 | cp -r $priv/. priv/ 37 | chmod -R 700 priv 38 | 39 | npm run deploy --prefix ./assets 40 | mix phx.digest 41 | 42 | mkdir $out 43 | mix release $release_name --no-compile --path $out --quiet 44 | ''; 45 | 46 | outputHashMode = "recursive"; 47 | outputHash = hash; 48 | 49 | impureEnvVars = lib.fetchers.proxyImpureEnvVars; 50 | } 51 | -------------------------------------------------------------------------------- /pkg/node_modules.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, lib, nodejs, hash ? null }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | __noChroot = if hash == null then true else false; 5 | name = "node_modules"; 6 | package = ../assets/package.json; 7 | lockfile = ../assets/package-lock.json; 8 | nativeBuildInputs = [ 9 | nodejs 10 | ]; 11 | builder = builtins.toFile "builder.sh" '' 12 | source $stdenv/setup 13 | 14 | install $package package.json 15 | install $lockfile package-lock.json 16 | 17 | HOME=. 18 | npm ci --no-progress --no-fund --no-audit --no-update-notifier --loglevel error 19 | 20 | mkdir $out 21 | cp -r node_modules/. $out 22 | ''; 23 | 24 | outputHashMode = "recursive"; 25 | outputHash = hash; 26 | 27 | impureEnvVars = lib.fetchers.proxyImpureEnvVars; 28 | } 29 | -------------------------------------------------------------------------------- /pkg/rebar3.nix: -------------------------------------------------------------------------------- 1 | { stdenvNoCC, erlang }: 2 | 3 | stdenvNoCC.mkDerivation rec { 4 | pname = "rebar3"; 5 | version = "3.17.0"; 6 | # How to obtain hash: 7 | # nix-prefetch-url https://github.com/erlang/rebar3/releases/download//rebar3 8 | hash = "sha256:10f1v62if9g5l8db9fvxm52h3g9rdprgc5dbzrpp6f6vd9g347rr"; 9 | src = import { 10 | url = "https://github.com/erlang/rebar3/releases/download/${version}/rebar3"; 11 | inherit hash; 12 | }; 13 | nativeBuildInputs = [ 14 | erlang 15 | ]; 16 | builder = builtins.toFile "builder.sh" '' 17 | source $stdenv/setup 18 | 19 | mkdir -p $out/bin 20 | cp $src $out/bin/rebar3 21 | chmod +x $out/bin/rebar3 22 | patchShebangs $out/bin/rebar3 23 | ''; 24 | } 25 | -------------------------------------------------------------------------------- /pkg/scripts/pkgs_update.exs: -------------------------------------------------------------------------------- 1 | Mix.install([:castore]) 2 | 3 | defmodule PkgsUpdate do 4 | @moduledoc """ 5 | Updates the local pkg/_pkgs.nix file with pin to the latest Nix packages on Github. 6 | """ 7 | 8 | @nix_pkg_download_url "https://github.com/NixOS/nixpkgs/archive" 9 | @github_channel "nixpkgs-unstable" 10 | 11 | @doc """ 12 | Run the pkg/_pkgs.nix update generation. 13 | """ 14 | def run() do 15 | commit_sha = 16 | github_api_request(github_api_url()) 17 | |> get_latest_commit_sha() 18 | 19 | write_pkgs_file(commit_sha) 20 | 21 | IO.puts(IO.ANSI.format([:green, "Done."])) 22 | end 23 | 24 | defp github_api_url() do 25 | "https://api.github.com/repos/NixOS/nixpkgs/commits?sha=#{@github_channel}" 26 | end 27 | 28 | @spec github_api_request(binary()) :: charlist() 29 | defp github_api_request(github_api_url) do 30 | url = String.to_charlist(github_api_url) 31 | 32 | {:ok, _} = Application.ensure_all_started(:inets) 33 | {:ok, _} = Application.ensure_all_started(:ssl) 34 | 35 | # https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/inets 36 | cacertfile = CAStore.file_path() |> String.to_charlist() 37 | 38 | http_options = [ 39 | ssl: [ 40 | verify: :verify_peer, 41 | cacertfile: cacertfile, 42 | depth: 2, 43 | customize_hostname_check: [ 44 | match_fun: :public_key.pkix_verify_hostname_match_fun(:https) 45 | ] 46 | ] 47 | ] 48 | 49 | user_agent = {'User-Agent', 'httpc'} 50 | options = [body_format: :binary] 51 | 52 | case :httpc.request(:get, {url, [user_agent]}, http_options, options) do 53 | {:ok, {{_, 200, _}, _headers, body}} -> 54 | body 55 | 56 | other -> 57 | raise "couldn't fetch #{url}: #{inspect(other)}" 58 | end 59 | end 60 | 61 | @spec get_latest_commit_sha(binary()) :: binary() 62 | defp get_latest_commit_sha(contents) do 63 | commit_sha_list = 64 | for {start, _end} <- :binary.matches(contents, "\"sha\":") do 65 | # Build the lookup scope from after the key until the end of the binary 66 | scope_start = start + 6 67 | scope_end = byte_size(contents) - scope_start 68 | 69 | # Find the next quote 70 | {start, _} = :binary.match(contents, "\"", scope: {scope_start, scope_end}) 71 | 72 | # Extract SHA which is always 40 bytes 73 | :binary.part(contents, start + 1, 40) 74 | end 75 | 76 | List.first(commit_sha_list) 77 | end 78 | 79 | @spec write_pkgs_file(binary()) :: :ok 80 | defp write_pkgs_file(commit_sha) do 81 | date = Date.utc_today() 82 | 83 | # nix derivation 84 | update = """ 85 | # generated by pkgs_update.exs (#{date}) 86 | import 87 | (fetchTarball { 88 | name = "#{@github_channel}_#{date}"; 89 | url = "#{@nix_pkg_download_url}/#{commit_sha}.tar.gz"; 90 | }) 91 | { } 92 | """ 93 | 94 | # overwrite derivation 95 | File.write!("pkg/_pkgs.nix", update, [:write]) 96 | end 97 | end 98 | 99 | PkgsUpdate.run() 100 | -------------------------------------------------------------------------------- /pkg/service.nix: -------------------------------------------------------------------------------- 1 | { config, pkgs, lib, ... }: 2 | 3 | let 4 | release = (import ../default.nix { }).release; 5 | release_name = "elixir-app"; 6 | working_directory = "/home/main/app/elixir-app"; 7 | in 8 | { 9 | systemd.services.${release_name} = { 10 | wantedBy = [ "multi-user.target" ]; 11 | after = [ "network.target" ]; 12 | description = release_name; 13 | serviceConfig = { 14 | Type = "exec"; 15 | User = "main"; 16 | WorkingDirectory = working_directory; 17 | ExecStart = '' 18 | ${release}/bin/${release_name} start 19 | ''; 20 | ExecStop = '' 21 | ${release}/bin/${release_name} stop 22 | ''; 23 | ExecReload = '' 24 | ${release}/bin/${release_name} restart 25 | ''; 26 | Restart = "on-failure"; 27 | RestartSec = 5; 28 | }; 29 | # needed for disksup do have sh available 30 | path = [ pkgs.bash ]; 31 | }; 32 | 33 | environment.systemPackages = [ release ]; 34 | } 35 | -------------------------------------------------------------------------------- /pkg/temporary_postgresql_db.nix: -------------------------------------------------------------------------------- 1 | { db_name ? "elixir_nix_example_dev" }: 2 | 3 | '' 4 | export PGDATA=$(mktemp --directory) 5 | 6 | # PG_LISTENING_ADDRESS default to localhost 7 | : ''${PG_LISTENING_ADDRESS:=127.0.0.1} 8 | 9 | # DB only listening on socket - no TCP. 10 | # Necessary to run tests concurrently. 11 | if [ "$MIX_ENV" = 'test' ] 12 | then 13 | export PG_LISTENING_ADDRESS="'''" 14 | fi 15 | 16 | initdb --locale=C --encoding=UTF8 --auth-local=peer --auth-host=scram-sha-256 > /dev/null || exit 17 | 18 | # get options for -o from: `postgres --help` 19 | pg_ctl -l $PGDATA/postgresql.log -o "-k $PGDATA -h $PG_LISTENING_ADDRESS" start || exit 20 | 21 | createdb -h $PGDATA ${db_name} 22 | psql -h $PGDATA ${db_name} -c "COMMENT ON DATABASE ${db_name} IS 'Database for Development, Testing & CI'" > /dev/null 23 | 24 | # createuser postgres --createdb 25 | psql -h $PGDATA ${db_name} -c "CREATE USER postgres PASSWORD 'postgres'" > /dev/null 26 | 27 | cleanup_postgres() { 28 | echo 'cleanup postgres...' 29 | 30 | pg_ctl stop --silent 31 | rm -rf $PGDATA 32 | } 33 | 34 | trap cleanup_postgres EXIT 35 | '' 36 | -------------------------------------------------------------------------------- /secrets/.env: -------------------------------------------------------------------------------- 1 | export SECRET='secret' 2 | --------------------------------------------------------------------------------