-
-
Notifications
You must be signed in to change notification settings - Fork 15.1k
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
nixos/mysql: replace initialDatabases, ensureDatabases, and ensureUsers options with activationScripts option #107342
Conversation
…nsureUsers with services.mysql.activationScripts
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.
I still have very mixed feelings about this, since this crams everything into one single unit running in a specific execution environment and also makes ordering way less flexible than with separate systemd service units.
However since I'm not using either the ensure*
options nor this mechanism, I'm not having a particularly strong opinion on this.
description = "The content of the script."; | ||
}; | ||
}; | ||
in either str (submodule { options = scriptOptions; }); |
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.
Why not use coercedTo
here?
Eg. like this:
in either str (submodule { options = scriptOptions; }); | |
in coercedTo str (text: { inherit text; }) (submodule { options = scriptOptions; }); |
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.
Implementation taken directly from system.activationScript
, so I didn't think too hard about it. Thanks for the recommendation.
set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set; | ||
withHeadlines = addAttributeName set'; |
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.
With the above change, this would be just:
set' = mapAttrs (n: v: if isString v then noDepEntry v else v) set; | |
withHeadlines = addAttributeName set'; | |
withHeadlines = addAttributeName set; |
wantedBy = [ "multi-user.target" ]; | ||
|
||
serviceConfig = mkMerge [ | ||
commonServiceConfig |
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.
This would mean that it would run in the same environment as the MySQL service (eg. same user/group, restricted system, etc...), so if you want to populate the database based on state data in another application with another user, you need to either override these options (which will then affect all activation scripts) or use a separate systemd service anyway.
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.
If the script runs as any user othere than mysql
database permissions would not exist to create databases, etc... You always want an activationScript
to run as mysql
, never as another user.
Type = "oneshot"; | ||
RemainAfterExit = true; | ||
SyslogIdentifier = "mysql-activation-scripts"; | ||
ExecStart = pkgs.writeScript "mysql-activation-scripts.sh" cfg.activationScripts.script; |
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.
What about something like this directly in the service definition:
{
description = "Run MySQL-specific NixOS activation";
after = [ "mysql.service" ];
wantedBy = [ "multi-user.target" ];
inherit (cfg.activationScripts) script;
serviceConfig = ...;
}
You then could also remove the #! ${pkgs.runtimeShell}
above.
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.
I believe this was added because script
doesn't allow trap
error handling, or something along those lines.
_localstatus=0 | ||
${v.text} | ||
if (( _localstatus > 0 )); then | ||
printf "MySQL activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus" |
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.
printf "MySQL activation script snippet '%s' failed (%s)\n" "${a}" "$_localstatus" | |
printf "MySQL activation script snippet '%s' failed (%s)\n" ${lib.escapeShellArg a} "$_localstatus" |
The same as |
This is good (and necessary!) work, thank you. There is a lot of duplication to set up the the auth socket - can't we handle this as part of the activation script's environment instead so we only need to do it once? |
I'm curious how the deprecated options lead to a not-reproducible system? (I'm still new to nix principles, was about to use them for a postgres for matrix-synapse) |
I'm curious how the deprecated options lead to a not-reproducible system?
Because the corresponding sql statements are only executed on first run.
|
@peterhoeg you're absolutely correct about the @paulreimer additional to the Does this help answer the question? |
Closing due to lack on consensus from the community. Unfortunate. |
Do you have a record of this investigation? Are we tracking these issues? In this conversation, I've found
The community does not seem to be excited about the suggested solution, and neither am I. Nonetheless, some issues do exist, and I believe we can address them, either by reporting state problems, or by solving the problems in new and better ways. |
Tracked in #206467. |
That's true. However, we also notice that many database operation libraries in different programming languages provide similar ensure* operations, and they have not removed these operations just because of this difficulty.
This is the same issue as above, and it won't be a problem as long as we don't consider extending the In fact, we already have a PR: #339977
Very good idea! But in fact, I think it's just an implementation issue, and it has nothing to do with how the options are designed. We can definitely do this on the existing options.
Ensuring state consistency declaratively is indeed challenging, but this does not mean there are no simplified ways to handle it. In fact, by looking at various database libraries across different languages, we can find many approaches rather than a binary choice. For example, we can assert that the current state of the library (if it already exists) is consistent with the declared options. If not, we can throw an error and refuse to start, prompting the user to manually adjust the relevant options. This sacrifices some availability during upgrades to enhance data consistency. Which I think will be a good balance. |
Not sure which tools you're talking about specifically, but let's not forget that we follow a declarative paradigm and changing options with nothing happening afterwards will be pretty unintuitive. Like, it was already a surprise to people when the very first nextcloud module had configuration parameters that couldn't be changed after first install and this module has way less users than the postgresql module.
I'm afraid I don't understand what you want to say here. Yes, I do think it's a non-trivial task. Yes, it caused pain for the maintainers during the past two releases.
I think it's way too harsh to break the entire DB server if a single database is having problems.
What's the improvement if the semantics of the options is the exact same, the code is just executed at a different time? The core issue we have is that users and databases are not configuration, but data in case of database software. So our current tooling is limited here, unfortunately. There are ideas laid out #206467. My preference is to have useful tooling for state-management in NixOS in general. Not some ad-hoc solution hacked down in the prestart hook that has the potential of confusing users and being a pain to maintainers. While I have rough plans for it, I don't know if I get to that soonish. If anybody is faster, feel free! |
Motivation for this change
I have been
brainwashedconvinced by @grahamc that theensure*
options aren't a good fit for NixOS. I'm seeking to remove these options from both themysql
andpostgresql
modules, but still leave other module authors who wish to provision databases with a reasonable way to do so. My plan forpostgresql
andmysql
was as follows:initialDatabases
,ensureDatabases
andensureUsers
optionsactivationScripts
option which module authors (and NixOS users) can use to write idempotent sql statements, via shell code, which will be executed as themysql
/postgresql
super admin user to do things like provision local accounts, databases, etc... (ie. replace theensureDatabases
andensureUsers
options with a new option that provides the same functionality, but with even more flexibility)initialDatabases
,ensureDatabases
andensureUsers
in NixOS with the newactivationScripts
optionactivationScripts
option, very explicitly explaining how it leads to systems which aren't reproducible and why that is badactivationScripts
option from thepostgresql
/mysql
systemd
service so a full restart ofpostgresql
/mysql
is not required every time someone altersactivationScripts
- which is a significant advantage over simply tacking more snippets onto the.postStart
of the databasesystemd
unitsYou might ask why should we get rid of the
ensure*
options?Aside from promoting systems which aren't reproducible, the
ensure*
options have a few other major problems withpostgresql
, mainly dealing with encoding and extensions. Currently all attempts to extend theensureDatabase
options have led to scenarios where a usersconfiguration.nix
could very easily not match the reality of what the database actually looks like. After investigating what it would take to make theensure*
options truly reproducible and actually enforce what they describe I came to the conclusion this is simply a bad idea because any database changes we might make can lead to data corruption and require a backup before making them - something we don't want to do every time the user does a rebuild.While some will argue that users can simply add sql statements to the
.postStart
of themysql
/postgresql
systemd
units I do see this as somewhat hacky, providing no documentation, and causesmysql
/postgresql
restarts with no easy and user-facing-api friendly way correct that.NOTE: this is to replace #84146 and #94048. The
postgresql
variant of this PR will follow shortly after, if this is merged.NOTE: @aszlig - I didn't provision a separate
systemd
unit for each snippet because I wanted to mimicsystem.activationScripts
- I found that to be a reasonable interface with a good set of trade-offs.Things done
sandbox
innix.conf
on non-NixOS linux)nix-shell -p nixpkgs-review --run "nixpkgs-review wip"
./result/bin/
)nix path-info -S
before and after)