nixos/ente: init module and test

oddlama 84d7ec68 b0731caa

+2
nixos/doc/manual/release-notes/rl-2511.section.md
···
- [nix-store-veritysetup](https://github.com/nikstur/nix-store-veritysetup-generator), a systemd generator to unlock the Nix Store as a dm-verity protected block device. Available as [boot.initrd.nix-store-veritysetup](options.html#opt-boot.initrd.nix-store-veritysetup.enable).
- [SuiteNumérique Docs](https://github.com/suitenumerique/docs), a collaborative note taking, wiki and documentation web platform and alternative to Notion or Outline. Available as [services.lasuite-docs](#opt-services.lasuite-docs.enable).
- [dwl](https://codeberg.org/dwl/dwl), a compact, hackable compositor for Wayland based on wlroots. Available as [programs.dwl](#opt-programs.dwl.enable).
···
- [nix-store-veritysetup](https://github.com/nikstur/nix-store-veritysetup-generator), a systemd generator to unlock the Nix Store as a dm-verity protected block device. Available as [boot.initrd.nix-store-veritysetup](options.html#opt-boot.initrd.nix-store-veritysetup.enable).
+
- [ente](https://github.com/ente-io/ente), a service that provides a fully open source, end-to-end encrypted platform for photos and videos. Available as [services.ente.api](#opt-services.ente.api.enable) and [services.ente.web](#opt-services.ente.web.enable).
+
- [SuiteNumérique Docs](https://github.com/suitenumerique/docs), a collaborative note taking, wiki and documentation web platform and alternative to Notion or Outline. Available as [services.lasuite-docs](#opt-services.lasuite-docs.enable).
- [dwl](https://codeberg.org/dwl/dwl), a compact, hackable compositor for Wayland based on wlroots. Available as [programs.dwl](#opt-programs.dwl.enable).
+1
nixos/modules/module-list.nix
···
./services/web-apps/echoip.nix
./services/web-apps/eintopf.nix
./services/web-apps/engelsystem.nix
./services/web-apps/ethercalc.nix
./services/web-apps/fediwall.nix
./services/web-apps/fider.nix
···
./services/web-apps/echoip.nix
./services/web-apps/eintopf.nix
./services/web-apps/engelsystem.nix
+
./services/web-apps/ente.nix
./services/web-apps/ethercalc.nix
./services/web-apps/fediwall.nix
./services/web-apps/fider.nix
+178
nixos/modules/services/web-apps/ente.md
···
···
+
# Ente.io {#module-services-ente}
+
+
[Ente](https://ente.io/) is a service that provides a fully open source,
+
end-to-end encrypted platform for photos and videos.
+
+
## Quickstart {#module-services-ente-quickstart}
+
+
To host ente, you need the following things:
+
- S3 storage server (either external or self-hosted like [minio](https://github.com/minio/minio))
+
- Several subdomains pointing to your server:
+
- accounts.example.com
+
- albums.example.com
+
- api.example.com
+
- cast.example.com
+
- photos.example.com
+
- s3.example.com
+
+
The following example shows how to setup ente with a self-hosted S3 storage via minio.
+
You can host the minio s3 storage on the same server as ente, but as this isn't
+
a requirement the example shows the minio and ente setup separately.
+
We assume that the minio server will be reachable at `https://s3.example.com`.
+
+
```nix
+
{
+
services.minio = {
+
enable = true;
+
# ente's config must match this region!
+
region = "us-east-1";
+
# Please use a file, agenix or sops-nix to securely store your root user password!
+
# MINIO_ROOT_USER=your_root_user
+
# MINIO_ROOT_PASSWORD=a_randomly_generated_long_password
+
rootCredentialsFile = "/run/secrets/minio-credentials-full";
+
};
+
+
systemd.services.minio.environment.MINIO_SERVER_URL = "https://s3.example.com";
+
+
# Proxy for minio
+
networking.firewall.allowedTCPPorts = [
+
80
+
443
+
];
+
services.nginx = {
+
recommendedProxySettings = true;
+
virtualHosts."s3.example.com" = {
+
forceSSL = true;
+
useACME = true;
+
locations."/".proxyPass = "http://localhost:9000";
+
# determine max file upload size
+
extraConfig = ''
+
client_max_body_size 16G;
+
proxy_buffering off;
+
proxy_request_buffering off;
+
'';
+
};
+
};
+
}
+
```
+
+
And the configuration for ente:
+
+
```nix
+
{
+
services.ente = {
+
web = {
+
enable = true;
+
domains = {
+
accounts = "accounts.example.com";
+
albums = "albums.example.com";
+
cast = "cast.example.com";
+
photos = "photos.example.com";
+
};
+
};
+
api = {
+
enable = true;
+
nginx.enable = true;
+
# Create a local postgres database and set the necessary config in ente
+
enableLocalDB = true;
+
domain = "api.example.com";
+
# You can hide secrets by setting xyz._secret = file instead of xyz = value.
+
# Make sure to not include any of the secrets used here directly
+
# in your config. They would be publicly readable in the nix store.
+
# Use agenix, sops-nix or an equivalent secret management solution.
+
settings = {
+
s3 = {
+
use_path_style_urls = true;
+
b2-eu-cen = {
+
endpoint = "https://s3.example.com";
+
region = "us-east-1";
+
bucket = "ente";
+
key._secret = pkgs.writeText "minio_user" "minio_user";
+
secret._secret = pkgs.writeText "minio_pw" "minio_pw";
+
};
+
};
+
key = {
+
# generate with: openssl rand -base64 32
+
encryption._secret = pkgs.writeText "encryption" "T0sn+zUVFOApdX4jJL4op6BtqqAfyQLH95fu8ASWfno=";
+
# generate with: openssl rand -base64 64
+
hash._secret = pkgs.writeText "hash" "g/dBZBs1zi9SXQ0EKr4RCt1TGr7ZCKkgrpjyjrQEKovWPu5/ce8dYM6YvMIPL23MMZToVuuG+Z6SGxxTbxg5NQ==";
+
};
+
# generate with: openssl rand -base64 32
+
jwt.secret._secret = pkgs.writeText "jwt" "i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8=";
+
};
+
};
+
};
+
+
networking.firewall.allowedTCPPorts = [
+
80
+
443
+
];
+
services.nginx = {
+
recommendedProxySettings = true; # This is important!
+
virtualHosts."accounts.${domain}".enableACME = true;
+
virtualHosts."albums.${domain}".enableACME = true;
+
virtualHosts."api.${domain}".enableACME = true;
+
virtualHosts."cast.${domain}".enableACME = true;
+
virtualHosts."photos.${domain}".enableACME = true;
+
};
+
}
+
```
+
+
If you have a mail server or smtp relay, you can optionally configure
+
`services.ente.api.settings.smtp` so ente can send you emails (registration code and possibly
+
other events). This is optional.
+
+
After starting the minio server, make sure the bucket exists:
+
+
```
+
mc alias set minio https://s3.example.com root_user root_password --api s3v4
+
mc mb -p minio/ente
+
```
+
+
Now ente should be ready to go under `https://photos.example.com`.
+
+
## Registering users {#module-services-ente-registering-users}
+
+
Now you can open photos.example.com and register your user(s).
+
Beware that the first created account will be considered to be the admin account,
+
which among some other things allows you to use `ente-cli` to increase storage limits for any user.
+
+
If you have configured smtp, you will get a mail with a verification code,
+
otherwise you can find the code in the server logs.
+
+
```
+
journalctl -eu ente
+
[...]
+
ente # [ 157.145165] ente[982]: INFO[0141]email.go:130 sendViaTransmail Skipping sending email to a@a.a: Verification code: 134033
+
```
+
+
After you have registered your users, you can set
+
`settings.internal.disable-registration = true;` to prevent
+
further signups.
+
+
## Increasing storage limit {#module-services-ente-increasing-storage-limit}
+
+
By default, all users will be on the free plan which is the only plan
+
available. While adding new plans is possible in theory, it requires some
+
manual database operations which isn't worthwhile. Instead, use `ente-cli`
+
with your admin user to modify the storage limit.
+
+
## iOS background sync
+
+
On iOS, background sync is achived via a silent notification sent by the server
+
every 30 minutes that allows the phone to sync for about 30 seconds, enough for
+
all but the largest videos to be synced on background (if the app is brought to
+
foreground though, sync will resume as normal). To achive this however, a
+
Firebase account is needed. In the settings option, configure credentials-dir
+
to point towards the directory where the JSON containing the Firebase
+
credentials are stored.
+
+
```nix
+
{
+
# This directory should contain your fcm-service-account.json file
+
services.ente.api.settings = {
+
credentials-dir = "/path/to/credentials";
+
# [...]
+
};
+
}
+
```
+363
nixos/modules/services/web-apps/ente.nix
···
···
+
{
+
config,
+
lib,
+
pkgs,
+
utils,
+
...
+
}:
+
let
+
inherit (lib)
+
getExe
+
mkDefault
+
mkEnableOption
+
mkIf
+
mkMerge
+
mkOption
+
mkPackageOption
+
optional
+
types
+
;
+
+
cfgApi = config.services.ente.api;
+
cfgWeb = config.services.ente.web;
+
+
webPackage =
+
enteApp:
+
cfgWeb.package.override {
+
inherit enteApp;
+
enteMainUrl = "https://${cfgWeb.domains.photos}";
+
extraBuildEnv = {
+
NEXT_PUBLIC_ENTE_ENDPOINT = "https://${cfgWeb.domains.api}";
+
NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT = "https://${cfgWeb.domains.albums}";
+
NEXT_TELEMETRY_DISABLED = "1";
+
};
+
};
+
+
defaultUser = "ente";
+
defaultGroup = "ente";
+
dataDir = "/var/lib/ente";
+
+
yamlFormat = pkgs.formats.yaml { };
+
in
+
{
+
options.services.ente = {
+
web = {
+
enable = mkEnableOption "Ente web frontend (Photos, Albums)";
+
package = mkPackageOption pkgs "ente-web" { };
+
+
domains = {
+
api = mkOption {
+
type = types.str;
+
example = "api.ente.example.com";
+
description = ''
+
The domain under which the api is served. This will NOT serve the api itself,
+
but is a required setting to host the frontends! This will automatically be set
+
for you if you enable both the api server and web frontends.
+
'';
+
};
+
+
accounts = mkOption {
+
type = types.str;
+
example = "accounts.ente.example.com";
+
description = "The domain under which the accounts frontend will be served.";
+
};
+
+
cast = mkOption {
+
type = types.str;
+
example = "cast.ente.example.com";
+
description = "The domain under which the cast frontend will be served.";
+
};
+
+
albums = mkOption {
+
type = types.str;
+
example = "albums.ente.example.com";
+
description = "The domain under which the albums frontend will be served.";
+
};
+
+
photos = mkOption {
+
type = types.str;
+
example = "photos.ente.example.com";
+
description = "The domain under which the photos frontend will be served.";
+
};
+
};
+
};
+
+
api = {
+
enable = mkEnableOption "Museum (API server for ente.io)";
+
package = mkPackageOption pkgs "museum" { };
+
nginx.enable = mkEnableOption "nginx proxy for the API server";
+
+
user = mkOption {
+
type = types.str;
+
default = defaultUser;
+
description = "User under which museum runs. If you set this option you must make sure the user exists.";
+
};
+
+
group = mkOption {
+
type = types.str;
+
default = defaultGroup;
+
description = "Group under which museum runs. If you set this option you must make sure the group exists.";
+
};
+
+
domain = mkOption {
+
type = types.str;
+
example = "api.ente.example.com";
+
description = "The domain under which the api will be served.";
+
};
+
+
enableLocalDB = mkEnableOption "the automatic creation of a local postgres database for museum.";
+
+
settings = mkOption {
+
description = ''
+
Museum yaml configuration. Refer to upstream [local.yaml](https://github.com/ente-io/ente/blob/main/server/configurations/local.yaml) for more information.
+
You can specify secret values in this configuration by setting `somevalue._secret = "/path/to/file"` instead of setting `somevalue` directly.
+
'';
+
default = { };
+
type = types.submodule {
+
freeformType = yamlFormat.type;
+
options = {
+
apps = {
+
public-albums = mkOption {
+
type = types.str;
+
default = "https://albums.ente.io";
+
description = ''
+
If you're running a self hosted instance and wish to serve public links,
+
set this to the URL where your albums web app is running.
+
'';
+
};
+
+
cast = mkOption {
+
type = types.str;
+
default = "https://cast.ente.io";
+
description = ''
+
Set this to the URL where your cast page is running.
+
This is for browser and chromecast casting support.
+
'';
+
};
+
+
accounts = mkOption {
+
type = types.str;
+
default = "https://accounts.ente.io";
+
description = ''
+
Set this to the URL where your accounts page is running.
+
This is primarily for passkey support.
+
'';
+
};
+
};
+
+
db = {
+
host = mkOption {
+
type = types.str;
+
description = "The database host";
+
};
+
+
port = mkOption {
+
type = types.port;
+
default = 5432;
+
description = "The database port";
+
};
+
+
name = mkOption {
+
type = types.str;
+
description = "The database name";
+
};
+
+
user = mkOption {
+
type = types.str;
+
description = "The database user";
+
};
+
};
+
};
+
};
+
};
+
};
+
};
+
+
config = mkMerge [
+
(mkIf cfgApi.enable {
+
services.postgresql = mkIf cfgApi.enableLocalDB {
+
enable = true;
+
ensureUsers = [
+
{
+
name = "ente";
+
ensureDBOwnership = true;
+
}
+
];
+
ensureDatabases = [ "ente" ];
+
};
+
+
services.ente.web.domains.api = mkIf cfgWeb.enable cfgApi.domain;
+
services.ente.api.settings = {
+
# This will cause logs to be written to stdout/err, which then end up in the journal
+
log-file = mkDefault "";
+
db = mkIf cfgApi.enableLocalDB {
+
host = "/run/postgresql";
+
port = 5432;
+
name = "ente";
+
user = "ente";
+
};
+
};
+
+
systemd.services.ente = {
+
description = "Ente.io Museum API Server";
+
after = [ "network.target" ] ++ optional cfgApi.enableLocalDB "postgresql.service";
+
requires = optional cfgApi.enableLocalDB "postgresql.service";
+
wantedBy = [ "multi-user.target" ];
+
+
preStart = ''
+
# Generate config including secret values. YAML is a superset of JSON, so we can use this here.
+
${utils.genJqSecretsReplacementSnippet cfgApi.settings "/run/ente/local.yaml"}
+
+
# Setup paths
+
mkdir -p ${dataDir}/configurations
+
ln -sTf /run/ente/local.yaml ${dataDir}/configurations/local.yaml
+
'';
+
+
serviceConfig = {
+
ExecStart = getExe cfgApi.package;
+
Type = "simple";
+
Restart = "on-failure";
+
+
AmbientCapablities = [ ];
+
CapabilityBoundingSet = [ ];
+
LockPersonality = true;
+
MemoryDenyWriteExecute = true;
+
NoNewPrivileges = true;
+
PrivateMounts = true;
+
PrivateTmp = true;
+
PrivateUsers = false;
+
ProcSubset = "pid";
+
ProtectClock = true;
+
ProtectControlGroups = true;
+
ProtectHome = true;
+
ProtectHostname = true;
+
ProtectKernelLogs = true;
+
ProtectKernelModules = true;
+
ProtectKernelTunables = true;
+
ProtectProc = "invisible";
+
ProtectSystem = "strict";
+
RestrictAddressFamilies = [
+
"AF_INET"
+
"AF_INET6"
+
"AF_NETLINK"
+
"AF_UNIX"
+
];
+
RestrictNamespaces = true;
+
RestrictRealtime = true;
+
RestrictSUIDSGID = true;
+
SystemCallArchitectures = "native";
+
SystemCallFilter = "@system-service";
+
UMask = "077";
+
+
BindReadOnlyPaths = [
+
"${cfgApi.package}/share/museum/migrations:${dataDir}/migrations"
+
"${cfgApi.package}/share/museum/mail-templates:${dataDir}/mail-templates"
+
"${cfgApi.package}/share/museum/web-templates:${dataDir}/web-templates"
+
];
+
+
User = cfgApi.user;
+
Group = cfgApi.group;
+
+
SyslogIdentifier = "ente";
+
StateDirectory = "ente";
+
WorkingDirectory = dataDir;
+
RuntimeDirectory = "ente";
+
};
+
+
# Environment MUST be called local, otherwise we cannot log to stdout
+
environment = {
+
ENVIRONMENT = "local";
+
GIN_MODE = "release";
+
};
+
};
+
+
users = {
+
users = mkIf (cfgApi.user == defaultUser) {
+
${defaultUser} = {
+
description = "ente.io museum service user";
+
inherit (cfgApi) group;
+
isSystemUser = true;
+
home = dataDir;
+
};
+
};
+
groups = mkIf (cfgApi.group == defaultGroup) { ${defaultGroup} = { }; };
+
};
+
+
services.nginx = mkIf cfgApi.nginx.enable {
+
enable = true;
+
upstreams.museum = {
+
servers."localhost:8080" = { };
+
extraConfig = ''
+
zone museum 64k;
+
keepalive 20;
+
'';
+
};
+
+
virtualHosts.${cfgApi.domain} = {
+
forceSSL = mkDefault true;
+
locations."/".proxyPass = "http://museum";
+
extraConfig = ''
+
client_max_body_size 4M;
+
'';
+
};
+
};
+
})
+
(mkIf cfgWeb.enable {
+
services.ente.api.settings = mkIf cfgApi.enable {
+
apps = {
+
accounts = "https://${cfgWeb.domains.accounts}";
+
cast = "https://${cfgWeb.domains.cast}";
+
public-albums = "https://${cfgWeb.domains.albums}";
+
};
+
+
webauthn = {
+
rpid = cfgWeb.domains.accounts;
+
rporigins = [ "https://${cfgWeb.domains.accounts}" ];
+
};
+
};
+
+
services.nginx =
+
let
+
domainFor = app: cfgWeb.domains.${app};
+
in
+
{
+
enable = true;
+
virtualHosts.${domainFor "accounts"} = {
+
forceSSL = mkDefault true;
+
locations."/" = {
+
root = webPackage "accounts";
+
tryFiles = "$uri $uri.html /index.html";
+
extraConfig = ''
+
add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
+
'';
+
};
+
};
+
virtualHosts.${domainFor "cast"} = {
+
forceSSL = mkDefault true;
+
locations."/" = {
+
root = webPackage "cast";
+
tryFiles = "$uri $uri.html /index.html";
+
extraConfig = ''
+
add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
+
'';
+
};
+
};
+
virtualHosts.${domainFor "photos"} = {
+
serverAliases = [
+
(domainFor "albums") # the albums app is shared with the photos frontend
+
];
+
forceSSL = mkDefault true;
+
locations."/" = {
+
root = webPackage "photos";
+
tryFiles = "$uri $uri.html /index.html";
+
extraConfig = ''
+
add_header Access-Control-Allow-Origin 'https://${cfgWeb.domains.api}';
+
'';
+
};
+
};
+
};
+
})
+
];
+
+
meta.maintainers = with lib.maintainers; [ oddlama ];
+
}
+1
nixos/tests/all-tests.nix
···
endlessh-go = runTest ./endlessh-go.nix;
engelsystem = runTest ./engelsystem.nix;
enlightenment = runTest ./enlightenment.nix;
env = runTest ./env.nix;
envfs = runTest ./envfs.nix;
envoy = runTest {
···
endlessh-go = runTest ./endlessh-go.nix;
engelsystem = runTest ./engelsystem.nix;
enlightenment = runTest ./enlightenment.nix;
+
ente = runTest ./ente;
env = runTest ./env.nix;
envfs = runTest ./envfs.nix;
envoy = runTest {
+15
nixos/tests/ente/acme.test.cert.pem
···
···
+
-----BEGIN CERTIFICATE-----
+
MIICRDCCAcqgAwIBAgIIBx6YLUwhT34wCgYIKoZIzj0EAwMwIDEeMBwGA1UEAxMV
+
bWluaWNhIHJvb3QgY2EgNjRhYWY2MB4XDTI1MDUxMzA4NTMyMVoXDTQ1MDUxMzA4
+
NTMyMVowFDESMBAGA1UEAxMJYWNtZS50ZXN0MHYwEAYHKoZIzj0CAQYFK4EEACID
+
YgAEcuBBV1FZ9s6D3Iz3+K07BwtcSqDOmk5WGsuL/owdeIQkT5OhqdZ+0v4TA6V3
+
HLb9fyaEeZ6cG8vX4fMy6wIMi1E38o1cfiTYLjS9mU/GVN+eTsnYdUS8g7uz8p0e
+
C0X2o4HcMIHZMA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYI
+
KwYBBQUHAwIwDAYDVR0TAQH/BAIwADAfBgNVHSMEGDAWgBTNdPze2U/U7/72ULml
+
V/K/73d2xTB5BgNVHREEcjBwgglhY21lLnRlc3SCEmFjY291bnRzLmFjbWUudGVz
+
dIIQYWxidW1zLmFjbWUudGVzdIINYXBpLmFjbWUudGVzdIIOY2FzdC5hY21lLnRl
+
c3SCEHBob3Rvcy5hY21lLnRlc3SCDHMzLmFjbWUudGVzdDAKBggqhkjOPQQDAwNo
+
ADBlAjB9Eao+y/Wzy+mMw4e4P2OidFxDFv8o1jDlCN5mvXBQrlAoSKVwgkpreKsd
+
R/3iaacCMQC7CS3XKJVRbOtI6CjVHs7SV9fwCqJ6EaLcUjeNcigxcSRKGfG1ntl+
+
bt0LubZZd+c=
+
-----END CERTIFICATE-----
+6
nixos/tests/ente/acme.test.key.pem
···
···
+
-----BEGIN PRIVATE KEY-----
+
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB631W2iczyfu4h/4f/
+
721JKAsYRAnxLV7oYSUv9rFC+z8CPC7T74Lzmoccr0mR72WhZANiAARy4EFXUVn2
+
zoPcjPf4rTsHC1xKoM6aTlYay4v+jB14hCRPk6Gp1n7S/hMDpXcctv1/JoR5npwb
+
y9fh8zLrAgyLUTfyjVx+JNguNL2ZT8ZU355Oydh1RLyDu7PynR4LRfY=
+
-----END PRIVATE KEY-----
+13
nixos/tests/ente/ca.cert.pem
···
···
+
-----BEGIN CERTIFICATE-----
+
MIIB/DCCAYKgAwIBAgIIZKr2ScoFkWAwCgYIKoZIzj0EAwMwIDEeMBwGA1UEAxMV
+
bWluaWNhIHJvb3QgY2EgNjRhYWY2MCAXDTI1MDUxMzA4NTMyMVoYDzIxMjUwNTEz
+
MDg1MzIxWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSA2NGFhZjYwdjAQBgcq
+
hkjOPQIBBgUrgQQAIgNiAAST7GqqY2N7XW9SDHXkNOhbLMaIBTtdCpmu4AAEjRzS
+
/KozwcGfWf98GyMJ+t8bFg9f0mCbWrl1TVhIb3eV7k7oadJYvBNljIBnnkKgmw1b
+
nzIE0qbzcRWmz0m5ReFNkGCjgYYwgYMwDgYDVR0PAQH/BAQDAgKEMB0GA1UdJQQW
+
MBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1Ud
+
DgQWBBTNdPze2U/U7/72ULmlV/K/73d2xTAfBgNVHSMEGDAWgBTNdPze2U/U7/72
+
ULmlV/K/73d2xTAKBggqhkjOPQQDAwNoADBlAjBto95DikOxFmQEv/c5dCbz4eYW
+
dsB78N+m2nrMgx10pzOvXNkvrt/D3mUbbnZI1DMCMQDQKQ+qPUF+PdDdSc21v778
+
4Sokp/5SNBUVm7CT0I7OiPTtuLc//r6SK8d9VBQArx0=
+
-----END CERTIFICATE-----
+6
nixos/tests/ente/ca.key.pem
···
···
+
-----BEGIN PRIVATE KEY-----
+
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCIBDkk1pfjwxBpwex2
+
2izySRuBmJ4Za2aRtbnTbPevhHYs0WL8LTPID47dAt0erFihZANiAAST7GqqY2N7
+
XW9SDHXkNOhbLMaIBTtdCpmu4AAEjRzS/KozwcGfWf98GyMJ+t8bFg9f0mCbWrl1
+
TVhIb3eV7k7oadJYvBNljIBnnkKgmw1bnzIE0qbzcRWmz0m5ReFNkGA=
+
-----END PRIVATE KEY-----
+139
nixos/tests/ente/default.nix
···
···
+
{ lib, pkgs, ... }:
+
let
+
accessKey = "BKIKJAA5BMMU2RHO6IBB";
+
secretKey = "V7f1CwQqAcwo80UEIJEjc5gVQUSSx5ohQ9GSrr12";
+
rootCredentialsFile = pkgs.writeText "minio-credentials-full" ''
+
MINIO_ROOT_USER=${accessKey}
+
MINIO_ROOT_PASSWORD=${secretKey}
+
'';
+
+
certs = import ./snakeoil-certs.nix;
+
domain = certs.domain;
+
in
+
{
+
name = "ente";
+
meta.maintainers = [ lib.maintainers.oddlama ];
+
+
nodes.minio =
+
{ ... }:
+
{
+
environment.systemPackages = [ pkgs.minio-client ];
+
services.minio = {
+
enable = true;
+
inherit rootCredentialsFile;
+
};
+
+
networking.firewall.allowedTCPPorts = [
+
9000
+
];
+
+
systemd.services.minio.environment = {
+
MINIO_SERVER_URL = "https://s3.${domain}";
+
};
+
};
+
+
nodes.ente =
+
{
+
config,
+
nodes,
+
lib,
+
...
+
}:
+
{
+
security.pki.certificateFiles = [ certs.ca.cert ];
+
+
networking.extraHosts = ''
+
${config.networking.primaryIPAddress} accounts.${domain} albums.${domain} api.${domain} cast.${domain} photos.${domain} s3.${domain}
+
'';
+
+
networking.firewall.allowedTCPPorts = [
+
80
+
443
+
];
+
+
services.nginx = {
+
recommendedProxySettings = true;
+
virtualHosts =
+
lib.genAttrs
+
[
+
"accounts.${domain}"
+
"albums.${domain}"
+
"api.${domain}"
+
"cast.${domain}"
+
"photos.${domain}"
+
]
+
(_: {
+
sslCertificate = certs.${domain}.cert;
+
sslCertificateKey = certs.${domain}.key;
+
})
+
// {
+
"s3.${domain}" = {
+
forceSSL = true;
+
sslCertificate = certs.${domain}.cert;
+
sslCertificateKey = certs.${domain}.key;
+
locations."/".proxyPass = "http://${nodes.minio.networking.primaryIPAddress}:9000";
+
extraConfig = ''
+
client_max_body_size 32M;
+
proxy_buffering off;
+
proxy_request_buffering off;
+
'';
+
};
+
};
+
};
+
+
services.ente = {
+
web = {
+
enable = true;
+
domains = {
+
accounts = "accounts.${domain}";
+
albums = "albums.${domain}";
+
cast = "cast.${domain}";
+
photos = "photos.${domain}";
+
};
+
};
+
api = {
+
enable = true;
+
nginx.enable = true;
+
enableLocalDB = true;
+
domain = "api.${domain}";
+
settings = {
+
s3 = {
+
use_path_style_urls = true;
+
b2-eu-cen = {
+
endpoint = "https://s3.${domain}";
+
region = "us-east-1";
+
bucket = "ente";
+
key._secret = pkgs.writeText "accesskey" accessKey;
+
secret._secret = pkgs.writeText "secretkey" secretKey;
+
};
+
};
+
key = {
+
encryption._secret = pkgs.writeText "encryption" "T0sn+zUVFOApdX4jJL4op6BtqqAfyQLH95fu8ASWfno=";
+
hash._secret = pkgs.writeText "hash" "g/dBZBs1zi9SXQ0EKr4RCt1TGr7ZCKkgrpjyjrQEKovWPu5/ce8dYM6YvMIPL23MMZToVuuG+Z6SGxxTbxg5NQ==";
+
};
+
jwt.secret._secret = pkgs.writeText "jwt" "i2DecQmfGreG6q1vBj5tCokhlN41gcfS2cjOs9Po-u8=";
+
};
+
};
+
};
+
};
+
+
testScript = ''
+
minio.start()
+
minio.wait_for_unit("minio.service")
+
minio.wait_for_open_port(9000)
+
+
# Create a test bucket on the server
+
minio.succeed("mc alias set minio http://localhost:9000 ${accessKey} ${secretKey} --api s3v4")
+
minio.succeed("mc mb -p minio/ente")
+
+
# Start ente
+
ente.start()
+
ente.wait_for_unit("ente.service")
+
ente.wait_for_unit("nginx.service")
+
+
# Wait until api is up
+
ente.wait_until_succeeds("journalctl --since -2m --unit ente.service --grep 'We have lift-off.'", timeout=30)
+
# Wait until photos app is up
+
ente.wait_until_succeeds("curl -Ls https://photos.${domain}/ | grep -q 'Ente Photos'", timeout=30)
+
'';
+
}
+36
nixos/tests/ente/generate-certs.nix
···
···
+
# Minica can provide a CA key and cert, plus a key
+
# and cert for our fake CA server's Web Front End (WFE).
+
{
+
pkgs ? import <nixpkgs> { },
+
minica ? pkgs.minica,
+
mkDerivation ? pkgs.stdenv.mkDerivation,
+
}:
+
let
+
conf = import ./snakeoil-certs.nix;
+
domain = conf.domain;
+
in
+
mkDerivation {
+
name = "test-certs";
+
buildInputs = [
+
(minica.overrideAttrs (_old: {
+
prePatch = ''
+
sed -i 's_NotAfter: time.Now().AddDate(2, 0, 30),_NotAfter: time.Now().AddDate(20, 0, 0),_' main.go
+
'';
+
}))
+
];
+
dontUnpack = true;
+
+
buildPhase = ''
+
minica \
+
--ca-key ca.key.pem \
+
--ca-cert ca.cert.pem \
+
--domains ${domain},accounts.${domain},albums.${domain},api.${domain},cast.${domain},photos.${domain},s3.${domain}
+
'';
+
+
installPhase = ''
+
mkdir -p $out
+
mv ca.*.pem $out/
+
mv ${domain}/key.pem $out/${domain}.key.pem
+
mv ${domain}/cert.pem $out/${domain}.cert.pem
+
'';
+
}
+14
nixos/tests/ente/snakeoil-certs.nix
···
···
+
let
+
domain = "acme.test";
+
in
+
{
+
inherit domain;
+
ca = {
+
cert = ./ca.cert.pem;
+
key = ./ca.key.pem;
+
};
+
"${domain}" = {
+
cert = ./. + "/${domain}.cert.pem";
+
key = ./. + "/${domain}.key.pem";
+
};
+
}