nixos/anki-sync-server: init
Provide a NixOS module for the [built-in Anki Sync Server](https://docs.ankiweb.net/sync-server.html) included in recent versions of Anki. This supersedes the `ankisyncd` module, but we should keep that for now because `ankisyncd` supports older versions of Anki clients than this module.
This commit is contained in:
parent
3808ba768f
commit
8fe9c18ed3
4 changed files with 217 additions and 0 deletions
|
@ -16,6 +16,8 @@ In addition to numerous new and upgraded packages, this release has the followin
|
|||
|
||||
- [maubot](https://github.com/maubot/maubot), a plugin-based Matrix bot framework. Available as [services.maubot](#opt-services.maubot.enable).
|
||||
|
||||
- [Anki Sync Server](https://docs.ankiweb.net/sync-server.html), the official sync server built into recent versions of Anki. Available as [services.anki-sync-server](#opt-services.anki-sync-server.enable).
|
||||
|
||||
## Backward Incompatibilities {#sec-release-24.05-incompatibilities}
|
||||
|
||||
<!-- To avoid merge conflicts, consider adding your item at an arbitrary place in the list instead. -->
|
||||
|
|
|
@ -635,6 +635,7 @@
|
|||
./services/misc/amazon-ssm-agent.nix
|
||||
./services/misc/ananicy.nix
|
||||
./services/misc/ankisyncd.nix
|
||||
./services/misc/anki-sync-server.nix
|
||||
./services/misc/apache-kafka.nix
|
||||
./services/misc/atuin.nix
|
||||
./services/misc/autofs.nix
|
||||
|
|
68
nixos/modules/services/misc/anki-sync-server.md
Normal file
68
nixos/modules/services/misc/anki-sync-server.md
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Anki Sync Server {#module-services-anki-sync-server}
|
||||
|
||||
[Anki Sync Server](https://docs.ankiweb.net/sync-server.html) is the built-in
|
||||
sync server, present in recent versions of Anki. Advanced users who cannot or
|
||||
do not wish to use AnkiWeb can use this sync server instead of AnkiWeb.
|
||||
|
||||
This module is compatible only with Anki versions >=2.1.66, due to [recent
|
||||
enhancements to the Nix anki
|
||||
package](https://github.com/NixOS/nixpkgs/commit/05727304f8815825565c944d012f20a9a096838a).
|
||||
|
||||
## Basic Usage {#module-services-anki-sync-server-basic-usage}
|
||||
|
||||
By default, the module creates a
|
||||
[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/)
|
||||
unit which runs the sync server with an isolated user using the systemd
|
||||
`DynamicUser` option.
|
||||
|
||||
This can be done by enabling the `anki-sync-server` service:
|
||||
```
|
||||
{ ... }:
|
||||
|
||||
{
|
||||
services.anki-sync-server.enable = true;
|
||||
}
|
||||
```
|
||||
|
||||
It is necessary to set at least one username-password pair under
|
||||
{option}`services.anki-sync-server.users`. For example
|
||||
|
||||
```
|
||||
{
|
||||
services.anki-sync-server.users = [
|
||||
{
|
||||
username = "user";
|
||||
passwordFile = /etc/anki-sync-server/user;
|
||||
}
|
||||
];
|
||||
}
|
||||
```
|
||||
|
||||
Here, `passwordFile` is the path to a file containing just the password in
|
||||
plaintext. Make sure to set permissions to make this file unreadable to any
|
||||
user besides root.
|
||||
|
||||
By default, the server listen address {option}`services.anki-sync-server.host`
|
||||
is set to localhost, listening on port
|
||||
{option}`services.anki-sync-server.port`, and does not open the firewall. This
|
||||
is suitable for purely local testing, or to be used behind a reverse proxy. If
|
||||
you want to expose the sync server directly to other computers (not recommended
|
||||
in most circumstances, because the sync server doesn't use HTTPS), then set the
|
||||
following options:
|
||||
|
||||
```
|
||||
{
|
||||
services.anki-sync-server.host = "0.0.0.0";
|
||||
services.anki-sync-server.openFirewall = true;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Alternatives {#module-services-anki-sync-server-alternatives}
|
||||
|
||||
The [`ankisyncd` NixOS
|
||||
module](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/misc/ankisyncd.nix)
|
||||
provides similar functionality, but using a third-party implementation,
|
||||
[`anki-sync-server-rs`](https://github.com/ankicommunity/anki-sync-server-rs/).
|
||||
According to that project's README, it is "no longer maintained", and not
|
||||
recommended for Anki 2.1.64+.
|
146
nixos/modules/services/misc/anki-sync-server.nix
Normal file
146
nixos/modules/services/misc/anki-sync-server.nix
Normal file
|
@ -0,0 +1,146 @@
|
|||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
utils,
|
||||
...
|
||||
}:
|
||||
with lib; let
|
||||
cfg = config.services.anki-sync-server;
|
||||
name = "anki-sync-server";
|
||||
specEscape = replaceStrings ["%"] ["%%"];
|
||||
usersWithIndexes =
|
||||
lists.imap1 (i: user: {
|
||||
i = i;
|
||||
user = user;
|
||||
})
|
||||
cfg.users;
|
||||
usersWithIndexesFile = filter (x: x.user.passwordFile != null) usersWithIndexes;
|
||||
usersWithIndexesNoFile = filter (x: x.user.passwordFile == null && x.user.password != null) usersWithIndexes;
|
||||
anki-sync-server-run = pkgs.writeShellScriptBin "anki-sync-server-run" ''
|
||||
# When services.anki-sync-server.users.passwordFile is set,
|
||||
# each password file is passed as a systemd credential, which is mounted in
|
||||
# a file system exposed to the service. Here we read the passwords from
|
||||
# the credential files to pass them as environment variables to the Anki
|
||||
# sync server.
|
||||
${
|
||||
concatMapStringsSep
|
||||
"\n"
|
||||
(x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:"''$(cat "''${CREDENTIALS_DIRECTORY}/"${escapeShellArg x.user.username})"'')
|
||||
usersWithIndexesFile
|
||||
}
|
||||
# For users where services.anki-sync-server.users.password isn't set,
|
||||
# export passwords in environment variables in plaintext.
|
||||
${
|
||||
concatMapStringsSep
|
||||
"\n"
|
||||
(x: ''export SYNC_USER${toString x.i}=${escapeShellArg x.user.username}:${escapeShellArg x.user.password}'')
|
||||
usersWithIndexesNoFile
|
||||
}
|
||||
exec ${cfg.package}/bin/anki-sync-server
|
||||
'';
|
||||
in {
|
||||
options.services.anki-sync-server = {
|
||||
enable = mkEnableOption (lib.mdDoc "anki-sync-server");
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.anki-sync-server;
|
||||
defaultText = literalExpression "pkgs.anki-sync-server";
|
||||
description = lib.mdDoc "The package to use for the anki-sync-server command.";
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "::1";
|
||||
description = lib.mdDoc ''
|
||||
IP address anki-sync-server listens to.
|
||||
Note host names are not resolved.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 27701;
|
||||
description = lib.mdDoc "port anki-sync-server listens to";
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = lib.mdDoc "Whether to open the firewall for the specified port.";
|
||||
};
|
||||
|
||||
users = mkOption {
|
||||
type = with types;
|
||||
listOf (submodule {
|
||||
options = {
|
||||
username = mkOption {
|
||||
type = str;
|
||||
description = lib.mdDoc "User name accepted by anki-sync-server.";
|
||||
};
|
||||
password = mkOption {
|
||||
type = nullOr str;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
Password accepted by anki-sync-server for the associated username.
|
||||
**WARNING**: This option is **not secure**. This password will
|
||||
be stored in *plaintext* and will be visible to *all users*.
|
||||
See {option}`services.anki-sync-server.users.passwordFile` for
|
||||
a more secure option.
|
||||
'';
|
||||
};
|
||||
passwordFile = mkOption {
|
||||
type = nullOr path;
|
||||
default = null;
|
||||
description = lib.mdDoc ''
|
||||
File containing the password accepted by anki-sync-server for
|
||||
the associated username. Make sure to make readable only by
|
||||
root.
|
||||
'';
|
||||
};
|
||||
};
|
||||
});
|
||||
description = lib.mdDoc "List of user-password pairs to provide to the sync server.";
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
assertions = [
|
||||
{
|
||||
assertion = (builtins.length usersWithIndexesFile) + (builtins.length usersWithIndexesNoFile) > 0;
|
||||
message = "At least one username-password pair must be set.";
|
||||
}
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = mkIf cfg.openFirewall [cfg.port];
|
||||
|
||||
systemd.services.anki-sync-server = {
|
||||
description = "anki-sync-server: Anki sync server built into Anki";
|
||||
after = ["network.target"];
|
||||
wantedBy = ["multi-user.target"];
|
||||
path = [cfg.package];
|
||||
environment = {
|
||||
SYNC_BASE = "%S/%N";
|
||||
SYNC_HOST = specEscape cfg.address;
|
||||
SYNC_PORT = toString cfg.port;
|
||||
};
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
DynamicUser = true;
|
||||
StateDirectory = name;
|
||||
ExecStart = "${anki-sync-server-run}/bin/anki-sync-server-run";
|
||||
Restart = "always";
|
||||
LoadCredential =
|
||||
map
|
||||
(x: "${specEscape x.user.username}:${specEscape (toString x.user.passwordFile)}")
|
||||
usersWithIndexesFile;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
meta = {
|
||||
maintainers = with maintainers; [telotortium];
|
||||
doc = ./anki-sync-server.md;
|
||||
};
|
||||
}
|
Loading…
Reference in a new issue