Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 0 additions & 9 deletions nixos/modules/services/web-apps/plausible.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,6 @@ After that, `plausible` can be deployed like this:
{
services.plausible = {
enable = true;
adminUser = {
# activate is used to skip the email verification of the admin-user that's
# automatically created by plausible. This is only supported if
# postgresql is configured by the module. This is done by default, but
# can be turned off with services.plausible.database.postgres.setup.
activate = true;
email = "admin@localhost";
passwordFile = "/run/secrets/plausible-admin-pwd";
};
server = {
baseUrl = "http://analytics.example.org";
# secretKeybaseFile is a path to the file which contains the secret generated
Expand Down
52 changes: 5 additions & 47 deletions nixos/modules/services/web-apps/plausible.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,6 @@ in {

package = mkPackageOption pkgs "plausible" { };

adminUser = {
name = mkOption {
default = "admin";
type = types.str;
description = ''
Name of the admin user that plausible will created on initial startup.
'';
};

email = mkOption {
type = types.str;
example = "admin@localhost";
description = ''
Email-address of the admin-user.
'';
};

passwordFile = mkOption {
type = types.either types.str types.path;
description = ''
Path to the file which contains the password of the admin user.
'';
};

activate = mkEnableOption "activating the freshly created admin-user";
};

database = {
clickhouse = {
setup = mkEnableOption "creating a clickhouse instance" // { default = true; };
Expand Down Expand Up @@ -164,18 +137,13 @@ in {

imports = [
(mkRemovedOptionModule [ "services" "plausible" "releaseCookiePath" ] "Plausible uses no distributed Erlang features, so this option is no longer necessary and was removed")
(mkRemovedOptionModule [ "services" "plausible" "adminUser" "name" ] "Admin user is now created using first start wizard")
(mkRemovedOptionModule [ "services" "plausible" "adminUser" "email" ] "Admin user is now created using first start wizard")
(mkRemovedOptionModule [ "services" "plausible" "adminUser" "passwordFile" ] "Admin user is now created using first start wizard")
(mkRemovedOptionModule [ "services" "plausible" "adminUser" "activate" ] "Admin user is now created using first start wizard")
];

config = mkIf cfg.enable {
assertions = [
{ assertion = cfg.adminUser.activate -> cfg.database.postgres.setup;
message = ''
Unable to automatically activate the admin-user if no locally managed DB for
postgres (`services.plausible.database.postgres.setup') is enabled!
'';
}
];

services.postgresql = mkIf cfg.database.postgres.setup {
enable = true;
};
Expand Down Expand Up @@ -243,11 +211,7 @@ in {
# Home is needed to connect to the node with iex
HOME = "/var/lib/plausible";

ADMIN_USER_NAME = cfg.adminUser.name;
ADMIN_USER_EMAIL = cfg.adminUser.email;

DATABASE_SOCKET_DIR = cfg.database.postgres.socket;
DATABASE_NAME = cfg.database.postgres.dbname;
DATABASE_URL = "postgresql:///${cfg.database.postgres.dbname}?host=${cfg.database.postgres.socket}";
CLICKHOUSE_DATABASE_URL = cfg.database.clickhouse.url;

BASE_URL = cfg.server.baseUrl;
Expand All @@ -270,7 +234,6 @@ in {
# even though we set `RELEASE_DISTRIBUTION=none` so the cookie should be unused.
# Thus, make a random one, which should then be ignored.
export RELEASE_COOKIE=$(tr -dc A-Za-z0-9 < /dev/urandom | head -c 20)
export ADMIN_USER_PWD="$(< $CREDENTIALS_DIRECTORY/ADMIN_USER_PWD )"
export SECRET_KEY_BASE="$(< $CREDENTIALS_DIRECTORY/SECRET_KEY_BASE )"

${lib.optionalString (cfg.mail.smtp.passwordFile != null)
Expand All @@ -283,10 +246,6 @@ in {

${cfg.package}/migrate.sh
export IP_GEOLOCATION_DB=${pkgs.dbip-country-lite}/share/dbip/dbip-country-lite.mmdb
${cfg.package}/bin/plausible eval "(Plausible.Release.prepare() ; Plausible.Auth.create_user(\"$ADMIN_USER_NAME\", \"$ADMIN_USER_EMAIL\", \"$ADMIN_USER_PWD\"))"
${optionalString cfg.adminUser.activate ''
psql -d plausible <<< "UPDATE users SET email_verified=true where email = '$ADMIN_USER_EMAIL';"
''}

exec plausible start
'';
Expand All @@ -297,7 +256,6 @@ in {
WorkingDirectory = "/var/lib/plausible";
StateDirectory = "plausible";
LoadCredential = [
"ADMIN_USER_PWD:${cfg.adminUser.passwordFile}"
"SECRET_KEY_BASE:${cfg.server.secretKeybaseFile}"
] ++ lib.optionals (cfg.mail.smtp.passwordFile != null) [ "SMTP_USER_PWD:${cfg.mail.smtp.passwordFile}"];
};
Expand Down
27 changes: 3 additions & 24 deletions nixos/tests/plausible.nix
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import ./make-test-python.nix ({ pkgs, lib, ... }: {
import ./make-test-python.nix ({ lib, ... }: {
name = "plausible";
meta = with lib.maintainers; {
maintainers = [ ];
meta = {
maintainers = lib.teams.cyberus.members;
};

nodes.machine = { pkgs, ... }: {
virtualisation.memorySize = 4096;
services.plausible = {
enable = true;
adminUser = {
email = "admin@example.org";
passwordFile = "${pkgs.writeText "pwd" "foobar"}";
activate = true;
};
server = {
baseUrl = "http://localhost:8000";
secretKeybaseFile = "${pkgs.writeText "dont-try-this-at-home" "nannannannannannannannannannannannannannannannannannannan_batman!"}";
Expand All @@ -32,21 +27,5 @@ import ./make-test-python.nix ({ pkgs, lib, ... }: {
machine.succeed("curl -f localhost:8000 >&2")

machine.succeed("curl -f localhost:8000/js/script.js >&2")

csrf_token = machine.succeed(
"curl -c /tmp/cookies localhost:8000/login | grep '_csrf_token' | sed -E 's,.*value=\"(.*)\".*,\\1,g'"
)

machine.succeed(
f"curl -b /tmp/cookies -f -X POST localhost:8000/login -F email=admin@example.org -F password=foobar -F _csrf_token={csrf_token.strip()} -D headers"
)

# By ensuring that the user is redirected to the dashboard after login, we
# also make sure that the automatic verification of the module works.
machine.succeed(
"[[ $(grep 'location: ' headers | cut -d: -f2- | xargs echo) == /sites* ]]"
)

machine.shutdown()
'';
})
127 changes: 99 additions & 28 deletions pkgs/servers/web-apps/plausible/default.nix
Original file line number Diff line number Diff line change
@@ -1,35 +1,51 @@
{ lib
, beamPackages
, buildNpmPackage
, fetchFromGitHub
, nodejs
, nixosTests
, ...
{
lib,
beamPackages,
buildNpmPackage,
rustPlatform,
fetchFromGitHub,
nodejs,
runCommand,
nixosTests,
npm-lockfile-fix,
brotli,
tailwindcss,
esbuild,
...
}:

let
pname = "plausible";
version = "2.0.0";
version = "2.1.4";
mixEnv = "ce";

src = fetchFromGitHub {
owner = "plausible";
repo = "analytics";
rev = "v${version}";
hash = "sha256-yrTwxBguAZbfEKucUL+w49Hr6D7v9/2OjY1h27+w5WI=";
};

# TODO consider using `mix2nix` as soon as it supports git dependencies.
mixFodDeps = beamPackages.fetchMixDeps {
pname = "${pname}-deps";
inherit src version;
hash = "sha256-CAyZLpjmw1JreK3MopqI0XsWhP+fJEMpXlww7CibSaM=";
hash = "sha256-wV2zzRKJM5pQ06pF8vt1ieFqv6s3HvCzNT5Hed29Owk=";
postFetch = ''
${lib.getExe npm-lockfile-fix} $out/assets/package-lock.json
sed -ie '
/defp deps do/ {
n
/\[/ a\
\{:rustler, ">= 0.0.0", optional: true \},
}
' $out/mix.exs
cat >> $out/config/config.exs <<EOF
config :mjml, Mjml.Native,
crate: :mjml_nif,
skip_compilation?: true
EOF
'';
};

assets = buildNpmPackage {
pname = "${pname}-assets";
inherit version;
src = "${src}/assets";
npmDepsHash = "sha256-2t1M6RQhBjZxx36qawVUVC+ob9SvQIq5dy4HgVeY2Eo=";
npmDepsHash = "sha256-Rf1+G9F/CMK09KEh022vHe02FADJtARKX4QEVbmvSqk=";
dontNpmBuild = true;
installPhase = ''
runHook preInstall
Expand All @@ -42,45 +58,100 @@ let
pname = "${pname}-tracker";
inherit version;
src = "${src}/tracker";
npmDepsHash = "sha256-y09jVSwUrxF0nLpLqS1yQweYL+iMF6jVx0sUdQtvrpc=";
npmDepsHash = "sha256-ng0YpBZc0vcg5Bsr1LmgXtzNCtNV6hJIgLt3m3yRdh4=";
dontNpmBuild = true;
installPhase = ''
runHook preInstall
cp -r . "$out"
runHook postInstall
'';
};

mixFodDeps = beamPackages.fetchMixDeps {
inherit
pname
version
src
mixEnv
;
hash = "sha256-N6cYlYwNss2FPYcljANJYbXobmLFauZ64F7Sf/+7Ctg=";
};

mjmlNif = rustPlatform.buildRustPackage {
pname = "mjml-native";
version = "";
src = "${mixFodDeps}/mjml/native/mjml_nif";
cargoHash = "sha256-W4r8W+JGTE6j4gDogL5Yulr0mbaXjDbwDTwhzMbbDcQ=";
doCheck = false;

env = {
RUSTLER_PRECOMPILED_FORCE_BUILD_ALL = "true";
RUSTLER_PRECOMPILED_GLOBAL_CACHE_PATH = "unused-but-required";
};
};

patchedMixFodDeps = runCommand mixFodDeps.name { } ''
mkdir $out
cp -r --no-preserve=mode ${mixFodDeps}/. $out

mkdir -p $out/mjml/priv/native
for lib in ${mjmlNif}/lib/*
do
# normalies suffix to .so, otherswise build would fail on darwin
file=''${lib##*/}
base=''${file%.*}
ln -s "$lib" $out/mjml/priv/native/$base.so
done
'';

in
beamPackages.mixRelease {
inherit pname version src mixFodDeps;
beamPackages.mixRelease rec {
inherit
pname
version
src
mixEnv
;

nativeBuildInputs = [
nodejs
brotli
];

mixFodDeps = patchedMixFodDeps;

passthru = {
tests = { inherit (nixosTests) plausible; };
tests = {
inherit (nixosTests) plausible;
};
updateScript = ./update.sh;
inherit assets tracker;
};

postPatch = ''
substituteInPlace lib/plausible_release.ex --replace 'defp prepare do' 'def prepare do'
'';
env = {
APP_VERSION = version;
RUSTLER_PRECOMPILED_FORCE_BUILD_ALL = "true";
RUSTLER_PRECOMPILED_GLOBAL_CACHE_PATH = "unused-but-required";
};

preBuild = ''
rm -r assets tracker
cp -r ${assets} assets
cp --no-preserve=mode -r ${assets} assets
cp -r ${tracker} tracker

cat >> config/config.exs <<EOF
config :tailwind, path: "${lib.getExe tailwindcss}"
config :esbuild, path: "${lib.getExe esbuild}"
EOF
'';

postBuild = ''
export NODE_OPTIONS=--openssl-legacy-provider # required for webpack compatibility with OpenSSL 3 (https://github.com/webpack/webpack/issues/14532)
npm run deploy --prefix ./assets
npm run deploy --prefix ./tracker

# for external task you need a workaround for the no deps check flag
# https://github.com/phoenixframework/phoenix/issues/2690
mix do deps.loadpaths --no-deps-check, phx.digest
mix do deps.loadpaths --no-deps-check, assets.deploy
mix do deps.loadpaths --no-deps-check, phx.digest priv/static
'';

meta = with lib; {
Expand Down
Loading