nixos/ente: init module and test

This commit is contained in:
oddlama
2025-05-13 20:13:50 +02:00
parent b0731caa94
commit 84d7ec6875
12 changed files with 774 additions and 0 deletions

View File

@@ -90,6 +90,8 @@
- [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).

View File

@@ -1574,6 +1574,7 @@
./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

View File

@@ -0,0 +1,178 @@
# 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";
# [...]
};
}
```

View File

@@ -0,0 +1,363 @@
{
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 ];
}

View File

@@ -482,6 +482,7 @@ in
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 {

View File

@@ -0,0 +1,15 @@
-----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-----

View File

@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB631W2iczyfu4h/4f/
721JKAsYRAnxLV7oYSUv9rFC+z8CPC7T74Lzmoccr0mR72WhZANiAARy4EFXUVn2
zoPcjPf4rTsHC1xKoM6aTlYay4v+jB14hCRPk6Gp1n7S/hMDpXcctv1/JoR5npwb
y9fh8zLrAgyLUTfyjVx+JNguNL2ZT8ZU355Oydh1RLyDu7PynR4LRfY=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,13 @@
-----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-----

View File

@@ -0,0 +1,6 @@
-----BEGIN PRIVATE KEY-----
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCIBDkk1pfjwxBpwex2
2izySRuBmJ4Za2aRtbnTbPevhHYs0WL8LTPID47dAt0erFihZANiAAST7GqqY2N7
XW9SDHXkNOhbLMaIBTtdCpmu4AAEjRzS/KozwcGfWf98GyMJ+t8bFg9f0mCbWrl1
TVhIb3eV7k7oadJYvBNljIBnnkKgmw1bnzIE0qbzcRWmz0m5ReFNkGA=
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,139 @@
{ 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)
'';
}

View File

@@ -0,0 +1,36 @@
# 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
'';
}

View File

@@ -0,0 +1,14 @@
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";
};
}