nixos/ente: init module and test
This commit is contained in:
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
178
nixos/modules/services/web-apps/ente.md
Normal file
178
nixos/modules/services/web-apps/ente.md
Normal 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";
|
||||
# [...]
|
||||
};
|
||||
}
|
||||
```
|
||||
363
nixos/modules/services/web-apps/ente.nix
Normal file
363
nixos/modules/services/web-apps/ente.nix
Normal 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 ];
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
15
nixos/tests/ente/acme.test.cert.pem
Normal file
15
nixos/tests/ente/acme.test.cert.pem
Normal 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-----
|
||||
6
nixos/tests/ente/acme.test.key.pem
Normal file
6
nixos/tests/ente/acme.test.key.pem
Normal file
@@ -0,0 +1,6 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB631W2iczyfu4h/4f/
|
||||
721JKAsYRAnxLV7oYSUv9rFC+z8CPC7T74Lzmoccr0mR72WhZANiAARy4EFXUVn2
|
||||
zoPcjPf4rTsHC1xKoM6aTlYay4v+jB14hCRPk6Gp1n7S/hMDpXcctv1/JoR5npwb
|
||||
y9fh8zLrAgyLUTfyjVx+JNguNL2ZT8ZU355Oydh1RLyDu7PynR4LRfY=
|
||||
-----END PRIVATE KEY-----
|
||||
13
nixos/tests/ente/ca.cert.pem
Normal file
13
nixos/tests/ente/ca.cert.pem
Normal 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-----
|
||||
6
nixos/tests/ente/ca.key.pem
Normal file
6
nixos/tests/ente/ca.key.pem
Normal file
@@ -0,0 +1,6 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDCIBDkk1pfjwxBpwex2
|
||||
2izySRuBmJ4Za2aRtbnTbPevhHYs0WL8LTPID47dAt0erFihZANiAAST7GqqY2N7
|
||||
XW9SDHXkNOhbLMaIBTtdCpmu4AAEjRzS/KozwcGfWf98GyMJ+t8bFg9f0mCbWrl1
|
||||
TVhIb3eV7k7oadJYvBNljIBnnkKgmw1bnzIE0qbzcRWmz0m5ReFNkGA=
|
||||
-----END PRIVATE KEY-----
|
||||
139
nixos/tests/ente/default.nix
Normal file
139
nixos/tests/ente/default.nix
Normal 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)
|
||||
'';
|
||||
}
|
||||
36
nixos/tests/ente/generate-certs.nix
Normal file
36
nixos/tests/ente/generate-certs.nix
Normal 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
|
||||
'';
|
||||
}
|
||||
14
nixos/tests/ente/snakeoil-certs.nix
Normal file
14
nixos/tests/ente/snakeoil-certs.nix
Normal 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";
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user