-
-
Notifications
You must be signed in to change notification settings - Fork 13.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
plausible: init at 1.3.0 #124055
plausible: init at 1.3.0 #124055
Changes from all commits
7cd86c7
34041e4
50bc40e
68c8a43
85c7a4a
6e12c74
4d32493
885512d
204d1b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,273 @@ | ||
{ lib, pkgs, config, ... }: | ||
|
||
with lib; | ||
|
||
let | ||
cfg = config.services.plausible; | ||
|
||
# FIXME consider using LoadCredential as soon as it actually works. | ||
envSecrets = '' | ||
export ADMIN_USER_PWD="$(<${cfg.adminUser.passwordFile})" | ||
export SECRET_KEY_BASE="$(<${cfg.server.secretKeybaseFile})" | ||
${optionalString (cfg.mail.smtp.passwordFile != null) '' | ||
export SMTP_USER_PWD="$(<${cfg.mail.smtp.passwordFile})" | ||
''} | ||
''; | ||
in { | ||
options.services.plausible = { | ||
enable = mkEnableOption "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; }; | ||
url = mkOption { | ||
default = "http://localhost:8123/default"; | ||
type = types.str; | ||
description = '' | ||
The URL to be used to connect to <package>clickhouse</package>. | ||
''; | ||
}; | ||
}; | ||
postgres = { | ||
setup = mkEnableOption "creating a postgresql instance" // { default = true; }; | ||
dbname = mkOption { | ||
default = "plausible"; | ||
type = types.str; | ||
description = '' | ||
Name of the database to use. | ||
''; | ||
}; | ||
socket = mkOption { | ||
default = "/run/postgresql"; | ||
type = types.str; | ||
description = '' | ||
Path to the UNIX domain-socket to communicate with <package>postgres</package>. | ||
''; | ||
}; | ||
}; | ||
}; | ||
|
||
server = { | ||
disableRegistration = mkOption { | ||
Ma27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
default = true; | ||
type = types.bool; | ||
description = '' | ||
Whether to prohibit creating an account in plausible's UI. | ||
''; | ||
}; | ||
secretKeybaseFile = mkOption { | ||
type = types.either types.path types.str; | ||
description = '' | ||
Path to the secret used by the <literal>phoenix</literal>-framework. Instructions | ||
how to generate one are documented in the | ||
<link xlink:href="https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Secret.html#content"> | ||
framework docs</link>. | ||
''; | ||
}; | ||
port = mkOption { | ||
default = 8000; | ||
type = types.port; | ||
description = '' | ||
Port where the service should be available. | ||
''; | ||
}; | ||
baseUrl = mkOption { | ||
type = types.str; | ||
description = '' | ||
Public URL where plausible is available. | ||
''; | ||
}; | ||
}; | ||
|
||
mail = { | ||
email = mkOption { | ||
default = "hello@plausible.local"; | ||
type = types.str; | ||
description = '' | ||
The email id to use for as <emphasis>from</emphasis> address of all communications | ||
from Plausible. | ||
''; | ||
}; | ||
smtp = { | ||
hostAddr = mkOption { | ||
default = "localhost"; | ||
type = types.str; | ||
description = '' | ||
The host address of your smtp server. | ||
''; | ||
}; | ||
hostPort = mkOption { | ||
default = 25; | ||
type = types.port; | ||
description = '' | ||
The port of your smtp server. | ||
''; | ||
}; | ||
user = mkOption { | ||
default = null; | ||
type = types.nullOr types.str; | ||
description = '' | ||
The username/email in case SMTP auth is enabled. | ||
''; | ||
}; | ||
passwordFile = mkOption { | ||
default = null; | ||
type = with types; nullOr (either str path); | ||
description = '' | ||
The path to the file with the password in case SMTP auth is enabled. | ||
''; | ||
}; | ||
enableSSL = mkEnableOption "SSL when connecting to the SMTP server"; | ||
retries = mkOption { | ||
type = types.ints.unsigned; | ||
default = 2; | ||
description = '' | ||
Number of retries to make until mailer gives up. | ||
''; | ||
}; | ||
}; | ||
}; | ||
}; | ||
|
||
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; | ||
}; | ||
|
||
services.clickhouse = mkIf cfg.database.clickhouse.setup { | ||
enable = true; | ||
happysalada marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}; | ||
|
||
systemd.services = mkMerge [ | ||
{ | ||
plausible = { | ||
inherit (pkgs.plausible.meta) description; | ||
documentation = [ "https://plausible.io/docs/self-hosting" ]; | ||
wantedBy = [ "multi-user.target" ]; | ||
after = optional cfg.database.postgres.setup "plausible-postgres.service"; | ||
Ma27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
requires = optional cfg.database.clickhouse.setup "clickhouse.service" | ||
++ optionals cfg.database.postgres.setup [ | ||
"postgresql.service" | ||
"plausible-postgres.service" | ||
]; | ||
|
||
environment = { | ||
# NixOS specific option to avoid that it's trying to write into its store-path. | ||
# See also https://github.com/lau/tzdata#data-directory-and-releases | ||
TZDATA_DIR = "/var/lib/plausible/elixir_tzdata"; | ||
|
||
# Configuration options from | ||
# https://plausible.io/docs/self-hosting-configuration | ||
PORT = toString cfg.server.port; | ||
DISABLE_REGISTRATION = boolToString cfg.server.disableRegistration; | ||
|
||
RELEASE_TMP = "/var/lib/plausible/tmp"; | ||
|
||
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; | ||
CLICKHOUSE_DATABASE_URL = cfg.database.clickhouse.url; | ||
|
||
BASE_URL = cfg.server.baseUrl; | ||
|
||
MAILER_EMAIL = cfg.mail.email; | ||
SMTP_HOST_ADDR = cfg.mail.smtp.hostAddr; | ||
SMTP_HOST_PORT = toString cfg.mail.smtp.hostPort; | ||
SMTP_RETRIES = toString cfg.mail.smtp.retries; | ||
SMTP_HOST_SSL_ENABLED = boolToString cfg.mail.smtp.enableSSL; | ||
|
||
SELFHOST = "true"; | ||
} // (optionalAttrs (cfg.mail.smtp.user != null) { | ||
SMTP_USER_NAME = cfg.mail.smtp.user; | ||
}); | ||
|
||
path = [ pkgs.plausible ] | ||
++ optional cfg.database.postgres.setup config.services.postgresql.package; | ||
|
||
serviceConfig = { | ||
DynamicUser = true; | ||
Ma27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
PrivateTmp = true; | ||
WorkingDirectory = "/var/lib/plausible"; | ||
StateDirectory = "plausible"; | ||
ExecStartPre = "@${pkgs.writeShellScript "plausible-setup" '' | ||
${envSecrets} | ||
${pkgs.plausible}/createdb.sh | ||
${pkgs.plausible}/migrate.sh | ||
${optionalString cfg.adminUser.activate '' | ||
if ! ${pkgs.plausible}/init-admin.sh | grep 'already exists'; then | ||
psql -d plausible <<< "UPDATE users SET email_verified=true;" | ||
fi | ||
''} | ||
''} plausible-setup"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the argument to this shell script for? It calls e.g.
But the shell script defined directly above does not use this argument. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To quote
A similar approach for shorter names is done in e.g. the module of Hydra or the firewall :) Does that answer your question? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually wanted to ask you why you used @ in this case? (rather without) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Ma27 Ah yes, thank you! Is the intent to make it look better in |
||
ExecStart = "@${pkgs.writeShellScript "plausible" '' | ||
Ma27 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
${envSecrets} | ||
plausible start | ||
''} plausible"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question as https://github.com/NixOS/nixpkgs/pull/124055/files#r668400359 |
||
}; | ||
}; | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would there be a need to add a service for a remote shell ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea! I guess it's nicer though to have a wrapper-script for that which can be executed from the shell. An example for what I mean would be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks good! I'm not sure I understand the way secrets are managed, but otherwise it looks good! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm even if the environment is correctly set up, |
||
(mkIf cfg.database.postgres.setup { | ||
# `plausible' requires the `citext'-extension. | ||
plausible-postgres = { | ||
after = [ "postgresql.service" ]; | ||
bindsTo = [ "postgresql.service" ]; | ||
requiredBy = [ "plausible.service" ]; | ||
partOf = [ "plausible.service" ]; | ||
serviceConfig.Type = "oneshot"; | ||
unitConfig.ConditionPathExists = "!/var/lib/plausible/.db-setup"; | ||
script = '' | ||
mkdir -p /var/lib/plausible/ | ||
PSQL() { | ||
/run/wrappers/bin/sudo -Hu postgres ${config.services.postgresql.package}/bin/psql --port=5432 "$@" | ||
} | ||
PSQL -tAc "CREATE ROLE plausible WITH LOGIN;" | ||
PSQL -tAc "CREATE DATABASE plausible WITH OWNER plausible;" | ||
PSQL -d plausible -tAc "CREATE EXTENSION IF NOT EXISTS citext;" | ||
touch /var/lib/plausible/.db-setup | ||
''; | ||
}; | ||
}) | ||
]; | ||
}; | ||
|
||
meta.maintainers = with maintainers; [ ma27 ]; | ||
meta.doc = ./plausible.xml; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<chapter xmlns="http://docbook.org/ns/docbook" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please convert nixos docs to markdown. |
||
xmlns:xlink="http://www.w3.org/1999/xlink" | ||
xmlns:xi="http://www.w3.org/2001/XInclude" | ||
version="5.0" | ||
xml:id="module-services-plausible"> | ||
<title>Plausible</title> | ||
<para> | ||
<link xlink:href="https://plausible.io/">Plausible</link> is a privacy-friendly alternative to | ||
Google analytics. | ||
</para> | ||
<section xml:id="module-services-plausible-basic-usage"> | ||
<title>Basic Usage</title> | ||
<para> | ||
At first, a secret key is needed to be generated. This can be done with e.g. | ||
<screen><prompt>$ </prompt>openssl rand -base64 64</screen> | ||
</para> | ||
<para> | ||
After that, <package>plausible</package> can be deployed like this: | ||
<programlisting>{ | ||
services.plausible = { | ||
<link linkend="opt-services.plausible.enable">enable</link> = true; | ||
adminUser = { | ||
<link linkend="opt-services.plausible.adminUser.activate">activate</link> = true; <co xml:id='ex-plausible-cfg-activate' /> | ||
<link linkend="opt-services.plausible.adminUser.email">email</link> = "admin@localhost"; | ||
<link linkend="opt-services.plausible.adminUser.passwordFile">passwordFile</link> = "/run/secrets/plausible-admin-pwd"; | ||
}; | ||
server = { | ||
<link linkend="opt-services.plausible.server.baseUrl">baseUrl</link> = "http://analytics.example.org"; | ||
<link linkend="opt-services.plausible.server.secretKeybaseFile">secretKeybaseFile</link> = "/run/secrets/plausible-secret-key-base"; <co xml:id='ex-plausible-cfg-secretbase' /> | ||
}; | ||
}; | ||
}</programlisting> | ||
<calloutlist> | ||
<callout arearefs='ex-plausible-cfg-activate'> | ||
<para> | ||
<varname>activate</varname> is used to skip the email verification of the admin-user that's | ||
automatically created by <package>plausible</package>. This is only supported if | ||
<package>postgresql</package> is configured by the module. This is done by default, but | ||
can be turned off with <xref linkend="opt-services.plausible.database.postgres.setup" />. | ||
</para> | ||
</callout> | ||
<callout arearefs='ex-plausible-cfg-secretbase'> | ||
<para> | ||
<varname>secretKeybaseFile</varname> is a path to the file which contains the secret generated | ||
with <package>openssl</package> as described above. | ||
</para> | ||
</callout> | ||
</calloutlist> | ||
</para> | ||
</section> | ||
</chapter> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import ./make-test-python.nix ({ pkgs, lib, ... }: { | ||
name = "plausible"; | ||
meta = with lib.maintainers; { | ||
maintainers = [ ma27 ]; | ||
}; | ||
|
||
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!"}"; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ❤️ |
||
}; | ||
}; | ||
}; | ||
|
||
testScript = '' | ||
start_all() | ||
machine.wait_for_unit("plausible.service") | ||
machine.wait_for_open_port(8000) | ||
|
||
machine.succeed("curl -f localhost:8000 >&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() | ||
''; | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As pretty much 100% of shell scripts, this is probably wrong:
The shell this runs in doesn't have
set -e
nor any other reasonable way of error propagation, so when the configured file isn't readable by theDynamicUser
, this code just prints e.g.and then happily goes on with its day, with
ADMIN_USER_PWD
andSECRET_KEY_BASE
set to the empty string.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if it had
set -e
, it would still be wrong, in the same way as the scripts of Elixir itself here:elixir-lang/elixir#11114
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixes in PR #130062