nix: add initial flake

This commit is contained in:
happysalada 2021-07-03 17:04:49 +09:00
parent 389d55e277
commit e6fa78d9a6
9 changed files with 347 additions and 37 deletions

2
.envrc
View file

@ -1 +1 @@
use_nix
use_flake

1
.gitignore vendored
View file

@ -71,3 +71,4 @@ deploy
assets/package-lock.json
assets/yarn.lock
priv/localisation/*
result

View file

@ -189,6 +189,77 @@ There is a `Makefile` with relevant commands (make sure you set the `MIX_ENV=pro
---
### Option C with nixos
this repo is a flake and includes a nixos module.
Here are the detailed steps to deploy it.
- add it as an input to your system flake.
- add an overlay to make the package available
- add the required configuration in your system
Your flake.nix file would look like the following. Remember to replace myHostName with your actual hostname or however your deployed system is called.
```nix
{
inputs.bonfire.url = "github:happysalada/bonfire-app/main";
outputs = { self, nixpkgs, bonfire }: {
overlay = final: prev: with final;{
# a package named bonfire already exists on nixpkgs so we put it under a different name
elixirBonfire = bonfire.defaultPackage.x86_64-linux;
};
nixosConfigurations.myHostName = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
{
environment.systemPackages = [ agenix.defaultPackage.x86_64-linux ];
nixpkgs.overlays = [ self.overlay ];
}
./myHostName.nix
bonfire.nixosModules.bonfire
];
};
};
}
```
then in myHostName.nix would look like the following
TODO: add the caddy config
```nix
{ config, lib, pkgs, ... }:
{
services.bonfire = {
# you will need to expose bonfire with a reverse proxy, for example caddy
port = 4000;
package = pkgs.elixirBonfire;
dbName = "bonfire";
# the environment should contain a minimum of
# SECRET_KEY_BASE
# SIGNING_SALT
# ENCRYPTION_SALT
# RELEASE_COOKIE
# have a look into nix/module.nix for more details
# the way to deploy secrets is beyond this readme, but I would recommend agenix
environmentFile = "/run/secrets/bonfireEnv";
dbSocketDir = "/var/run/postgresql";
};
# this is uniquely for database backup purposes
# replace myBackupUserName with the user name that will do the backups
# if you want to do backups differently, feel free to remove this part of the config
services.postgresql = {
ensureDatabases = [ "bonfire" ];
ensureUsers = [{
name = "myBackupUserName";
ensurePermissions = { "DATABASE bonfire" = "ALL PRIVILEGES"; };
}];
};
}
```
## Step 3 - Run
By default, the backend listens on port 4000 (TCP), so you can access it on http://localhost:4000/ (if you are on the same machine). In case of an error it will restart automatically.

View file

@ -8,16 +8,15 @@ This is a work in progress guide to getting up and running as a developer. Pleas
Happy hacking!
## Getting set up
There are three main options depending on your needs and preferences.
There are three main options depending on your needs and preferences.
You first need to set set some configuration regardless of which option you choose.
### Configuration
- The first thing to do is choosing what flavour of Bonfire you want to hack on (the default is `classic`), as each flavour has its own config.
- The first thing to do is choosing what flavour of Bonfire you want to hack on (the default is `classic`), as each flavour has its own config.
For example if you want to run the `coordination` flavour:
@ -31,21 +30,21 @@ For example if you want to run the `coordination` flavour:
- `config/dev/secrets.env`
- `config/dev/public.env`
### Option A - the entry way (fully managed via docker-compose, recommended when you're first exploring)
- Dependencies:
- `make`
- Docker
- Docker Compose (recent version)
- Make sure you've edited your .env files (see above) before getting started and proceed to Hello world!
### Option B - the easy way (with docker-managed database & search index, recommended for active development)
- Dependencies:
- `make`
- `make`
- Recent versions of elixir (1.11+) and OTP/erlang (23+)
- Docker
- Docker Compose (recent version)
@ -54,11 +53,11 @@ For example if you want to run the `coordination` flavour:
- Make sure you've edited your .env files (see above) before getting started and proceed to Hello world!
### Option C - the bare metal (if you don't use docker)
- Dependencies:
- `make`
- `make`
- Recent versions of elixir (1.11+) and OTP/erlang (23+)
- Postgres 12+ (or Postgis if using the bonfire_geolocate extension)
- Meili Search (optional)
@ -69,13 +68,13 @@ For example if you want to run the `coordination` flavour:
- Set an environment variable to indicate your choice: `export WITH_DOCKER=no` and proceed to Hello world!
### Option D - the experimental one (dev environment with Nix)
If you use direnv, just cd in the directory and you will have all the dependencies.
If you just have nix, running `nix-shell` will set you up with a shell.
If you use direnv, just cd in the directory and you will have all the dependencies.
If you just have nix, running `nix shell .` (inside the repository) will set you up with a shell.
You will need to create and init the db directory (keeping all your Postgres data inside this directory).
- create the db directory `initdb ./db`
- create the postgres user `createuser postgres -ds`
- create the db `createdb bonfire_dev`
@ -86,7 +85,6 @@ You will need to create and init the db directory (keeping all your Postgres dat
- `iex -S mix phx.server` to start the server
- check out the app on `localhost:4000` in your browser
## Hello world!
- From a fresh checkout of this repository, this command will fetch the app's dependencies and setup the database (the same commands apply for all three options above):
@ -103,7 +101,6 @@ make dev
- See the `make` commands below for more things you may want to do.
## Onboarding
By default, the back-end listens on port 4000 (TCP), so you can access it on http://localhost:4000/
@ -112,41 +109,39 @@ If you haven't set up transactional emails, while in development, you will see a
Note that the first account to be registered is automatically an instance admin.
## Documentation
The code is somewhat documented inline. You can generate HTML docs (using `Exdoc`) by running `mix docs`.
## Additional information
- messctl is a little utility for programmatically updating the .deps files from which the final elixir dependencies list is compiled by the mess script. The only use of it is in the dep-* tasks of the Makefile. It is used by some of the project developers and the build does not rely on it.
- messctl is a little utility for programmatically updating the .deps files from which the final elixir dependencies list is compiled by the mess script. The only use of it is in the dep-\* tasks of the Makefile. It is used by some of the project developers and the build does not rely on it.
- `./forks/` is used to hack on local copies of dependencies. You can clone a dependency from its git repo (like a bonfire extension) and use the local version during development, eg: `make dep.clone.local dep=bonfire_me repo=https://github.com/bonfire-networks/bonfire_me`
- `./forks/` is used to hack on local copies of dependencies. You can clone a dependency from its git repo (like a bonfire extension) and use the local version during development, eg: `make dep.clone.local dep=bonfire_me repo=https://github.com/bonfire-networks/bonfire_me`
- You can migrate the DB when the app is running (useful in a release): `Bonfire.Repo.ReleaseTasks.migrate`
### Usage under Windows (MSYS or CYGWIN)
If you plan on using the `Makefile` (its rather handy), you must have symlinks enabled.
If you plan on using the `Makefile` (its rather handy), you must have symlinks enabled.
You must enable developer mode, and set `core.symlink = true`, [see link.](https://stackoverflow.com/a/59761201)
## Make commands
Run `make` followed by any of these commands when appropriate rather than directly using the equivalent commands like `mix`, `docker`, `docker-compose`, etc. For example, `make setup` will get you started, and `make dev` will run the app.
You can first set an env variable to control which mode these commands will assume you're using. Here are your options:
- `WITH_DOCKER=total` : use docker for everything (default)
- `WITH_DOCKER=partial` : use docker for services like the DB
- `WITH_DOCKER=easy` : use docker for services like the DB & compiled utilities like messctl
- `WITH_DOCKER=no` : please no
- `WITH_DOCKER=total` : use docker for everything (default)
- `WITH_DOCKER=partial` : use docker for services like the DB
- `WITH_DOCKER=easy` : use docker for services like the DB & compiled utilities like messctl
- `WITH_DOCKER=no` : please no
```
make help Makefile commands help **(run this to get more up-to-date commands and help information than available in this document)**
make mix~help Help info for elixir's mix commands
make env.exports Display the vars from dotenv files that you need to load in your environment
make setup First run - prepare environment and dependencies
make dev Run the app in development
make dev.bg Run the app in dev mode, as a background service
@ -154,13 +149,13 @@ make db.reset Reset the DB (caution: this means DATA LOSS)
make db.rollback Rollback previous DB migration (caution: this means DATA LOSS)
make db.rollback.all Rollback ALL DB migrations (caution: this means DATA LOSS)
make update Update the app and all dependencies/extensions/forks, and run migrations
make update.app Update the app and Bonfire extensions in ./deps
make update.deps.bonfire Update to the latest Bonfire extensions in ./deps
make update.deps.bonfire Update to the latest Bonfire extensions in ./deps
make update.deps.all Update evey single dependency (use with caution)
make update.dep~% Update a specify dep (eg. `make update.dep~pointers`)
make update.forks Pull the latest commits from all ./forks
make deps.get Fetch locked version of non-forked deps
make dep.clone.local Clone a git dep and use the local version, eg: `make dep.clone.local dep=bonfire_me repo=https://github.com/bonfire-networks/bonfire_me`
make deps.clone.local.all Clone all bonfire deps / extensions
@ -172,20 +167,20 @@ make dep.hex~% add/enable/disable/delete a hex dep with mes
make dep.git~% add/enable/disable/delete a git dep with messctl command, eg: `make dep.hex.enable dep=pointers repo=https://github.com/bonfire-networks/pointers#main
make dep.local~% add/enable/disable/delete a local dep with messctl command, eg: `make dep.hex.enable dep=pointers path=./libs/pointers
make messctl~% Utility to manage the deps in deps.hex, deps.git, and deps.path (eg. `make messctl~help`)
make contrib.forks Push all changes to the app and extensions in ./forks
make contrib.release Push all changes to the app and extensions in ./forks, increment the app version number, and push a new version/release
make contrib.app.up Update ./deps and push all changes to the app
make contrib.app.release Update ./deps, increment the app version number and push
make git.forks.add Run the git add command on each fork
make git.forks~% Run a git command on each fork (eg. `make git.forks~pull` pulls the latest version of all local deps from its git remote
make test Run tests. You can also run only specific tests, eg: `make test only=forks/bonfire_social/test`
make test.stale Run only stale tests
make test.remote Run tests (ignoring changes in local forks)
make test.watch Run stale tests, and wait for changes to any module's code, and re-run affected tests
make test.db.reset Create or reset the test DB
make rel.build Build the Docker image using previous cache
make rel.tag.latest Add latest tag to last build
make rel.push Add latest tag to last build and push to Docker Hub
@ -193,7 +188,7 @@ make rel.run Run the app in Docker & starts a new `iex` c
make rel.run.bg Run the app in Docker, and keep running in the background
make rel.stop Run the app in Docker, and keep running in the background
make rel.shell Runs a simple shell inside of the container, useful to explore the image
make services Start background docker services (eg. db and search backends). This is automatically done for you if using Docker.
make build Build the docker image
make cmd~% Run a specific command in the container, eg: `make cmd-messclt` or `make cmd~time` or `make cmd~echo args=hello`
@ -223,7 +218,6 @@ mix deps.update distillery
This respects the version bounds in `mix.exs` (`~> 2.0`), so increment that if required.
### `(DBConnection.ConnectionError) tcp recv: closed`
Example:

25
flake.lock Normal file
View file

@ -0,0 +1,25 @@
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1622253036,
"narHash": "sha256-HSVusps0KHjVayUkWvFPiIZe1JKxT+GeDQBzcpw+MFE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "25bca77c48ddb0bacdb46e7398d948a348d06119",
"type": "github"
},
"original": {
"id": "nixpkgs",
"type": "indirect"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}

33
flake.nix Normal file
View file

@ -0,0 +1,33 @@
{
description = "Bonfire!";
outputs = { self, nixpkgs }:
let
# taken from https://github.com/ngi-nix/project-template/blob/master/flake.nix
# System types to support.
supportedSystems = [ "x86_64-linux" "x86_64-darwin" ];
# Helper function to generate an attrset '{ x86_64-linux = f "x86_64-linux"; ... }'.
forAllSystems = f: nixpkgs.lib.genAttrs supportedSystems (system: f system);
# Nixpkgs instantiated for supported system types.
nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; overlays = [ self.overlay ]; });
in
{
overlay = final: prev: {
bonfire = import ./nix/package.nix { pkgs = final; inherit self; };
};
packages = forAllSystems (system:
{
inherit (nixpkgsFor.${system}) bonfire;
});
defaultPackage = forAllSystems (system: self.packages.${system}.bonfire);
nixosModules.bonfire = import ./nix/module.nix;
devShell = forAllSystems
(system:
import ./nix/shell.nix {
pkgs = nixpkgsFor.${system};
}
);
};
}

104
nix/module.nix Normal file
View file

@ -0,0 +1,104 @@
{ pkgs, config, lib, ... }:
with lib;
let
bonfireConfig = config.services.bonfire;
in
{
options.services.bonfire = {
port = mkOption {
type = types.port;
default = 4000;
description = "port to run the instance on";
};
package = mkOption {
type = types.package;
description = "package to run the instance with";
};
hostname = mkOption {
type = types.str;
default = "bonfire.cafe";
example = "bonfire.cafe";
description = ''
hostname for which the service will be run
'';
};
dbName = mkOption {
type = types.str;
default = "bonfire";
description = ''
name of the database you want to connect to
'';
};
dbSocketDir = mkOption {
type = types.nullOr types.str;
default = null;
description = ''
If this is defined, bonfire will connect to postgres
with a unix socket and not TCP/IP
'';
};
environmentFile = mkOption {
type = types.nullOr types.path;
default = null;
description = ''
environment file for secret environment variables
should contain
SECRET_KEY_BASE
SIGNING_SALT
ENCRYPTION_SALT
RELEASE_COOKIE
'';
};
};
config = with bonfireConfig; {
services.postgresql = {
extraPlugins = with pkgs.postgresql_13.pkgs; [ postgis ];
ensureDatabases = [ dbName ];
ensureUsers = [{
# Same name as the unix user is needed
name = "bonfire";
ensurePermissions = { "DATABASE ${dbName}" = "ALL PRIVILEGES"; };
}];
};
systemd.services.bonfire = {
wantedBy = [ "multi-user.target" ];
after = [ "postgresql.service" ];
requires = [ "postgresql.service" ];
description = "Bonfire!";
serviceConfig = {
Type = "exec";
Restart = "on-failure";
RestartSec = 5;
ExecStartPre = "${package}/bin/bonfire eval Bonfire.Repo.ReleaseTasks.migrate";
ExecStart = "${package}/bin/bonfire start";
ExecStop = "${package}/bin/bonfire stop";
DynamicUser = true;
StateDirectory = "bonfire";
EnvironmentFile = environmentFile;
PrivateTmp = true;
ProtectSystem = "full";
NoNewPrivileges = true;
ReadWritePaths = "${if dbSocketDir == null then "" else dbSocketDir} /var/lib/bonfire";
};
environment = {
RELEASE_TMP = "/tmp";
TZDATA_DIR = "/var/lib/bonfire";
LANG = "en_US.UTF-8";
PORT = toString port;
POSTGRES_USER = "bonfire";
POSTGRES_DB = dbName;
POSTGRES_SOCKET_DIR = lib.mkIf (dbSocketDir != null) dbSocketDir;
HOSTNAME = hostname;
WITH_DOCKER = "no";
FLAVOUR = "reflow";
BONFIRE_FLAVOUR = "flavours/reflow";
};
};
};
}

76
nix/package.nix Normal file
View file

@ -0,0 +1,76 @@
{ pkgs, self, flavour ? "reflow" }:
let
beamPackages = with pkgs; beam.packagesWith beam.interpreters.erlang;
in
beamPackages.mixRelease rec {
pname = "bonfire";
version = "1.0.0";
mixEnv = "dev";
src = self;
# TODO make these configurable
BONFIRE_FLAVOUR = "flavours/${flavour}";
FLAVOUR = flavour;
mixFodDeps = beamPackages.fetchMixDeps {
pname = "mix-deps-${pname}";
inherit src mixEnv version;
LANG = "en_US.UTF-8";
inherit BONFIRE_FLAVOUR FLAVOUR;
# override needed here since bonfire dependencies rely on git
installPhase = ''
runHook preInstall
mix deps.get --only ${mixEnv}
cp -r --no-preserve=mode,ownership,timestamps $TEMPDIR/deps $out
runHook postInstall
'';
# TODO add sha256
# since I didn't know exactly what dependencies where being pulled
# I went for the quick hack of not checking for dependency integrity
# This has the downside of triggering a rebuild on every deployment
sha256 = null;
};
frontendAssets = with pkgs; stdenvNoCC.mkDerivation {
pname = "frontend-assets-${pname}";
nativeBuildInputs = [ nodejs cacert git elixir nodePackages.pnpm beamPackages.hex ];
inherit version src;
configurePhase = ''
export HOME=$(mktemp -d)
cp -r ${mixFodDeps} ./deps
chmod +w -R deps
make js.deps.get
'';
buildPhase = ''
mix assets.release
'';
installPhase = ''
cp -r priv/static $out
'';
outputHashAlgo = "sha256";
outputHashMode = "recursive";
# TODO add sha256
# since I didn't know exactly what dependencies where being pulled
# I went for the quick hack of not checking for dependency integrity
# This has the downside of triggering a rebuild on every deployment
outputHash = null;
impureEnvVars = lib.fetchers.proxyImpureEnvVars;
};
nativeBuildInputs = with pkgs; [ rustc cargo gcc ]; # for NIFs
postBuild = ''
mkdir -p priv/static
cp -r ${frontendAssets} priv/static
# digest needs to write files
chmod -R u+w priv/static
mix do deps.loadpaths --no-deps-check, phx.digest
'';
}

View file

@ -1,4 +1,4 @@
{ pkgs ? import <nixpkgs> { } }:
{ pkgs, flavour ? "reflow" }:
with pkgs;
@ -13,14 +13,15 @@ let
rev = "8421d5ee91b120f1fe78fe8b123fc0fdf59609ff";
sha256 = "sha256-MniXkng8v30xzSC+cIZ+K6DWeJLCFDieXZioAQFU4/s=";
};
cargoSha256 = "sha256-z8SdQKME9/6O6ZRkNRI+vYZSf6fxAG4lz0Muv7876fY=";
cargoSha256 = "sha256-K4Wq949DK3STwKo0MgaGNsu3r+qg8OqqXK3O4g4FpR0=";
};
# define packages to install with special handling for OSX
shellBasePackages = [
git
beam.packages.erlang.elixir_1_11
nodejs-15_x
beam.packages.erlang.elixir_1_12
nodejs-16_x
nodePackages.pnpm
postgresql_13
messctl
# for NIFs
@ -51,6 +52,11 @@ let
# elixir
export MIX_ENV=dev
export FORK=./forks
# bonfire
export FLAVOUR=${flavour}
export BONFIRE_FLAVOUR=flavours/${flavour}
export WITH_DOCKER=no
'';
in