Compare commits
2 Commits
4c8d7df432
...
17013e425e
Author | SHA1 | Date | |
---|---|---|---|
17013e425e | |||
e5934da311 |
@ -4,6 +4,7 @@
|
|||||||
amdgpu-pwm = ./services/hardware/amdgpu-pwm.nix;
|
amdgpu-pwm = ./services/hardware/amdgpu-pwm.nix;
|
||||||
betanin = ./services/web-apps/betanin.nix;
|
betanin = ./services/web-apps/betanin.nix;
|
||||||
dunst = ./services/x11/dunst.nix;
|
dunst = ./services/x11/dunst.nix;
|
||||||
|
koillection = ./services/web-apps/koillection.nix;
|
||||||
porkbun-ddns = ./services/networking/porkbun-ddns.nix;
|
porkbun-ddns = ./services/networking/porkbun-ddns.nix;
|
||||||
radeon-profile-daemon = ./services/hardware/radeon-profile-daemon.nix;
|
radeon-profile-daemon = ./services/hardware/radeon-profile-daemon.nix;
|
||||||
}
|
}
|
||||||
|
318
modules/services/web-apps/koillection.nix
Normal file
318
modules/services/web-apps/koillection.nix
Normal file
@ -0,0 +1,318 @@
|
|||||||
|
{ config, lib, modulesPath, options, pkgs, ... }:
|
||||||
|
let
|
||||||
|
inherit (lib) literalExpression mkEnableOption mkIf mkOption optionalString types;
|
||||||
|
|
||||||
|
opt = options.services.koillection;
|
||||||
|
cfg = config.services.koillection;
|
||||||
|
db = cfg.database;
|
||||||
|
|
||||||
|
koillection = (pkgs.koillection or pkgs.callPackage ../../../pkgs/by-name/ko/koillection/package.nix { }).override {
|
||||||
|
dataDir = cfg.dataDir;
|
||||||
|
};
|
||||||
|
inherit (koillection.passthru) phpPackage;
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.koillection = {
|
||||||
|
enable = mkEnableOption "Koillection, a collection manager";
|
||||||
|
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "koillection";
|
||||||
|
description = lib.mdDoc "User Koillection runs as.";
|
||||||
|
};
|
||||||
|
|
||||||
|
group = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "koillection";
|
||||||
|
description = lib.mdDoc "Group Koillection runs as.";
|
||||||
|
};
|
||||||
|
|
||||||
|
hostName = lib.mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = config.networking.fqdnOrHostName;
|
||||||
|
defaultText = "config.networking.fqdnOrHostName";
|
||||||
|
example = "koillection.example.com";
|
||||||
|
description = lib.mdDoc "The hostname to serve Koillection on.";
|
||||||
|
};
|
||||||
|
|
||||||
|
dataDir = mkOption {
|
||||||
|
description = lib.mdDoc "Koillection data directory";
|
||||||
|
default = "/var/lib/koillection";
|
||||||
|
type = types.path;
|
||||||
|
};
|
||||||
|
|
||||||
|
database = {
|
||||||
|
host = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = if db.createLocally then "/run/postgresql" else null;
|
||||||
|
defaultText = literalExpression ''
|
||||||
|
if config.${opt.database.createLocally}
|
||||||
|
then "/run/postgresql"
|
||||||
|
else null
|
||||||
|
'';
|
||||||
|
example = "192.168.12.85";
|
||||||
|
description = lib.mdDoc "Database host address or unix socket.";
|
||||||
|
};
|
||||||
|
port = mkOption {
|
||||||
|
type = types.port;
|
||||||
|
default = 5432;
|
||||||
|
description = lib.mdDoc "Database host port.";
|
||||||
|
};
|
||||||
|
name = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = "koillection";
|
||||||
|
description = lib.mdDoc "Database name.";
|
||||||
|
};
|
||||||
|
user = mkOption {
|
||||||
|
type = types.str;
|
||||||
|
default = cfg.user;
|
||||||
|
defaultText = literalExpression "user";
|
||||||
|
description = lib.mdDoc "Database username.";
|
||||||
|
};
|
||||||
|
passwordFile = mkOption {
|
||||||
|
type = with types; nullOr path;
|
||||||
|
default = null;
|
||||||
|
example = "/run/keys/koillection-dbpassword";
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
A file containing the password corresponding to
|
||||||
|
{option}`database.user`.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
createLocally = mkOption {
|
||||||
|
type = types.bool;
|
||||||
|
default = false;
|
||||||
|
description = lib.mdDoc "Create the database and database user locally.";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
poolConfig = mkOption {
|
||||||
|
type = with types; attrsOf (oneOf [ str int bool ]);
|
||||||
|
default = {
|
||||||
|
"pm" = "dynamic";
|
||||||
|
"pm.max_children" = 32;
|
||||||
|
"pm.start_servers" = 2;
|
||||||
|
"pm.min_spare_servers" = 2;
|
||||||
|
"pm.max_spare_servers" = 4;
|
||||||
|
"pm.max_requests" = 500;
|
||||||
|
};
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
Options for the bookstack PHP pool. See the documentation on `php-fpm.conf`
|
||||||
|
for details on configuration directives.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
nginx = mkOption {
|
||||||
|
type = types.submodule (
|
||||||
|
lib.recursiveUpdate
|
||||||
|
(import "${modulesPath}/services/web-servers/nginx/vhost-options.nix" { inherit config lib; })
|
||||||
|
{ }
|
||||||
|
);
|
||||||
|
default = { };
|
||||||
|
example = literalExpression ''
|
||||||
|
{
|
||||||
|
serverAliases = [
|
||||||
|
"koillection.''${config.networking.domain}"
|
||||||
|
];
|
||||||
|
# To enable encryption and let let's encrypt take care of certificate
|
||||||
|
forceSSL = true;
|
||||||
|
enableACME = true;
|
||||||
|
}
|
||||||
|
'';
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
With this option, you can customize the nginx virtualHost settings.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkOption {
|
||||||
|
type = with types;
|
||||||
|
attrsOf
|
||||||
|
(nullOr
|
||||||
|
(either
|
||||||
|
(oneOf [ bool int port path str ])
|
||||||
|
(submodule {
|
||||||
|
options = {
|
||||||
|
_secret = mkOption {
|
||||||
|
type = nullOr str;
|
||||||
|
description = lib.mdDoc ''
|
||||||
|
The path to a file containing the value the option should
|
||||||
|
be set to in the final configuration file.
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
})));
|
||||||
|
default = { };
|
||||||
|
example = literalExpression ''
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = mkIf cfg.enable {
|
||||||
|
services.koillection.config = {
|
||||||
|
APP_ENV = "prod";
|
||||||
|
# DB_DRIVER = "pdo_pgsql";
|
||||||
|
DB_USER = db.user;
|
||||||
|
DB_PASSWORD._secret = db.passwordFile;
|
||||||
|
DB_HOST = db.host;
|
||||||
|
DB_PORT = db.port;
|
||||||
|
DB_NAME = db.name;
|
||||||
|
# FIXME
|
||||||
|
# DB_VERSION = lib.versions.major config.services.postgresql.package.version;
|
||||||
|
DB_VERSION = "15";
|
||||||
|
PHP_TZ = config.time.timeZone;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.postgresql = mkIf db.createLocally {
|
||||||
|
enable = true;
|
||||||
|
ensureDatabases = [ db.name ];
|
||||||
|
ensureUsers = [{
|
||||||
|
name = db.user;
|
||||||
|
ensureDBOwnership = true;
|
||||||
|
}];
|
||||||
|
};
|
||||||
|
|
||||||
|
services.phpfpm.pools.koillection = {
|
||||||
|
inherit (cfg) user group;
|
||||||
|
inherit phpPackage;
|
||||||
|
# Copied from docker/php.ini
|
||||||
|
phpOptions = ''
|
||||||
|
max_execution_time = 200
|
||||||
|
|
||||||
|
apc.enabled = 1
|
||||||
|
apc.shm_size = 64M
|
||||||
|
apc.ttl = 7200
|
||||||
|
|
||||||
|
opcache.enable = 1
|
||||||
|
opcache.memory_consumption = 256
|
||||||
|
opcache.max_accelerated_files = 20000
|
||||||
|
opcache.max_wasted_percentage = 10
|
||||||
|
opcache.validate_timestamps = 0
|
||||||
|
opcache.preload = ${koillection}/share/php/koillection/config/preload.php
|
||||||
|
opcache.preload_user = ${cfg.user}
|
||||||
|
expose_php = Off
|
||||||
|
|
||||||
|
session.cookie_httponly = 1
|
||||||
|
realpath_cache_size = 4096K
|
||||||
|
realpath_cache_ttle = 600
|
||||||
|
'';
|
||||||
|
settings = {
|
||||||
|
"listen.mode" = "0660";
|
||||||
|
"listen.owner" = cfg.user;
|
||||||
|
"listen.group" = cfg.group;
|
||||||
|
} // cfg.poolConfig;
|
||||||
|
};
|
||||||
|
|
||||||
|
services.nginx = {
|
||||||
|
enable = lib.mkDefault true;
|
||||||
|
virtualHosts."${cfg.hostName}" = lib.mkMerge [
|
||||||
|
cfg.nginx
|
||||||
|
{
|
||||||
|
root = lib.mkForce "${koillection}/share/php/koillection/public";
|
||||||
|
extraConfig = optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;";
|
||||||
|
locations = {
|
||||||
|
"/" = {
|
||||||
|
index = "index.php";
|
||||||
|
extraConfig = ''try_files $uri $uri/ /index.php?query_string;'';
|
||||||
|
};
|
||||||
|
"~ \.php$" = {
|
||||||
|
extraConfig = ''
|
||||||
|
try_files $uri $uri/ /index.php?$query_string;
|
||||||
|
include ${config.services.nginx.package}/conf/fastcgi_params;
|
||||||
|
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||||
|
fastcgi_param REDIRECT_STATUS 200;
|
||||||
|
fastcgi_pass unix:${config.services.phpfpm.pools."koillection".socket};
|
||||||
|
${optionalString (cfg.nginx.addSSL || cfg.nginx.forceSSL || cfg.nginx.onlySSL || cfg.nginx.enableACME) "fastcgi_param HTTPS on;"}
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
"~ \.(js|css|gif|png|ico|jpg|jpeg)$" = {
|
||||||
|
extraConfig = "expires 365d;";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.services.koillection-setup = {
|
||||||
|
description = "Preparation tasks for koillection";
|
||||||
|
before = [ "phpfpm-koillection.service" ];
|
||||||
|
wantedBy = [ "multi-user.target" ];
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "oneshot";
|
||||||
|
RemainAfterExit = true;
|
||||||
|
User = cfg.user;
|
||||||
|
WorkingDirectory = koillection;
|
||||||
|
RuntimeDirectory = "koillection/cache";
|
||||||
|
RuntimeDirectoryMode = "0700";
|
||||||
|
};
|
||||||
|
path = [ pkgs.replace-secret ];
|
||||||
|
script =
|
||||||
|
let
|
||||||
|
isSecret = v: lib.isAttrs v && v ? _secret && lib.isString v._secret;
|
||||||
|
koillectionEnvVars = lib.generators.toKeyValue {
|
||||||
|
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
|
||||||
|
mkValueString = v: with builtins;
|
||||||
|
if isInt v then toString v
|
||||||
|
else if lib.isString v then v
|
||||||
|
else if true == v then "true"
|
||||||
|
else if false == v then "false"
|
||||||
|
else if isSecret v then hashString "sha256" v._secret
|
||||||
|
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
secretPaths = lib.mapAttrsToList (_: v: v._secret) (lib.filterAttrs (_: isSecret) cfg.config);
|
||||||
|
mkSecretReplacement = file: ''
|
||||||
|
replace-secret ${lib.escapeShellArgs [ ( builtins.hashString "sha256" file ) file "${cfg.dataDir}/.env" ]}
|
||||||
|
'';
|
||||||
|
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
|
||||||
|
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! builtins.elem v [{ } null])) cfg.config;
|
||||||
|
koillectionEnv = pkgs.writeText "koillection.env" (koillectionEnvVars filteredConfig);
|
||||||
|
in
|
||||||
|
''
|
||||||
|
# error handling
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# set permissions
|
||||||
|
umask 077
|
||||||
|
|
||||||
|
# create .env file
|
||||||
|
install -T -m 0600 -o ${cfg.user} ${koillectionEnv} "${cfg.dataDir}/.env"
|
||||||
|
${secretReplacements}
|
||||||
|
|
||||||
|
# prepend `base64:` if it does not exist in APP_KEY
|
||||||
|
|
||||||
|
if ! grep 'APP_KEY=base64:' "${cfg.dataDir}/.env" >/dev/null; then
|
||||||
|
sed -i 's/APP_KEY=/APP_KEY=base64:/' "${cfg.dataDir}/.env"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# migrate db
|
||||||
|
${lib.getExe phpPackage} ${koillection}/share/php/koillection/bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
|
||||||
|
|
||||||
|
# dump translations
|
||||||
|
# TODO: Might need pointing somewhere else.
|
||||||
|
${lib.getExe phpPackage} ${koillection}/share/php/koillection/bin/console app:translations:dump
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.tmpfiles.rules = [
|
||||||
|
"d ${cfg.dataDir} 0710 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.dataDir}/public 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.dataDir}/public/uploads 0750 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.dataDir}/var 0700 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.dataDir}/var/cache 0700 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.dataDir}/var/cache/prod 0700 ${cfg.user} ${cfg.group} - -"
|
||||||
|
"d ${cfg.dataDir}/var/logs 0700 ${cfg.user} ${cfg.group} - -"
|
||||||
|
];
|
||||||
|
|
||||||
|
users = {
|
||||||
|
users = mkIf (cfg.user == "koillection") {
|
||||||
|
koillection = {
|
||||||
|
inherit (cfg) group;
|
||||||
|
isSystemUser = true;
|
||||||
|
};
|
||||||
|
"${config.services.nginx.user}".extraGroups = [ cfg.group ];
|
||||||
|
};
|
||||||
|
groups = mkIf (cfg.group == "koillection") {
|
||||||
|
koillection = { };
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
25
pkgs/by-name/ko/koillection/koillection-dirs.patch
Normal file
25
pkgs/by-name/ko/koillection/koillection-dirs.patch
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
*** a/src/Kernel.php
|
||||||
|
--- b/src/Kernel.php
|
||||||
|
@@ -8,6 +8,21 @@ class Kernel extends BaseKernel
|
||||||
|
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
|
||||||
|
|
||||||
|
class Kernel extends BaseKernel
|
||||||
|
{
|
||||||
|
use MicroKernelTrait;
|
||||||
|
+
|
||||||
|
+ public function getDataDir(): string
|
||||||
|
+ {
|
||||||
|
+ return '@dataDir@';
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public function getCacheDir(): string
|
||||||
|
+ {
|
||||||
|
+ return $this->getDataDir() . '/var/cache/' . $this->getEnvironment();
|
||||||
|
+ }
|
||||||
|
+
|
||||||
|
+ public function getLogDir(): string
|
||||||
|
+ {
|
||||||
|
+ return $this->getDataDir() . '/var/logs';
|
||||||
|
+ }
|
||||||
|
}
|
||||||
|
|
69
pkgs/by-name/ko/koillection/package.nix
Normal file
69
pkgs/by-name/ko/koillection/package.nix
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
{ lib
|
||||||
|
, dataDir ? "/var/lib/koillection"
|
||||||
|
# REVIEW: This supposed to be aliased by the caller, which means it shouldn't
|
||||||
|
# go in by-name, I think.
|
||||||
|
, php83
|
||||||
|
, fetchFromGitHub
|
||||||
|
, mkYarnPackage
|
||||||
|
, fetchYarnDeps
|
||||||
|
}:
|
||||||
|
|
||||||
|
php83.buildComposerProject (finalAttrs: {
|
||||||
|
pname = "koillection";
|
||||||
|
version = "1.5.2";
|
||||||
|
|
||||||
|
src = fetchFromGitHub {
|
||||||
|
owner = "benjaminjonard";
|
||||||
|
repo = "koillection";
|
||||||
|
rev = finalAttrs.version;
|
||||||
|
hash = "sha256-r2rkHhp0F5QfwJuKeu4UdPoluDXxpyhYpie1zUk1h5c=";
|
||||||
|
};
|
||||||
|
|
||||||
|
frontend = mkYarnPackage {
|
||||||
|
inherit (finalAttrs) pname version;
|
||||||
|
|
||||||
|
src = "${finalAttrs.src}/assets";
|
||||||
|
|
||||||
|
offlineCache = fetchYarnDeps {
|
||||||
|
yarnLock = "${finalAttrs.src}/assets/yarn.lock";
|
||||||
|
hash = "";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
patches = [
|
||||||
|
./koillection-dirs.patch
|
||||||
|
];
|
||||||
|
|
||||||
|
postPatch = ''
|
||||||
|
substituteInPlace src/Kernel.php \
|
||||||
|
--replace "@dataDir@" "${dataDir}"
|
||||||
|
'';
|
||||||
|
|
||||||
|
# Lock file uses exact constraints, which Composer doesn't like.
|
||||||
|
composerStrictValidation = false;
|
||||||
|
# Actually installs plugins, i.e., Symfony.
|
||||||
|
composerNoPlugins = false;
|
||||||
|
vendorHash = "sha256-LU9ZN4qUNUpSBGH6AChw3qU4RjgsoPJmLL01FS7UKRQ=";
|
||||||
|
|
||||||
|
postInstall = ''
|
||||||
|
local koillection_out=$out/share/php/koillection
|
||||||
|
|
||||||
|
rm -R $koillection_out/public/uploads
|
||||||
|
ln -s ${dataDir}/.env $koillection_out/.env.local
|
||||||
|
ln -s ${dataDir}/public/uploads $koillection_out/public/uploads
|
||||||
|
|
||||||
|
cp -r ${finalAttrs.frontend} assets/
|
||||||
|
'';
|
||||||
|
|
||||||
|
passthru = {
|
||||||
|
phpPackage = php83;
|
||||||
|
};
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Self-hosted service allowing users to manage any kind of collections";
|
||||||
|
homepage = "https://github.com/benjaminjonard/koillection";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
platforms = lib.platforms.unix;
|
||||||
|
broken = true; # Blocked on NixOS/nixpkgs#254369
|
||||||
|
};
|
||||||
|
})
|
Loading…
Reference in New Issue
Block a user