parent
b5e7f4a3a9
commit
f8c0d8a036
@ -0,0 +1,517 @@ |
||||
{ config, lib, pkgs, modulesPath, ... }: |
||||
|
||||
let |
||||
inherit (lib) mkDefault mkEnableOption mkForce mkIf mkMerge mkOption types |
||||
boolToString callPackageWith optional optionals optionalString recursiveUpdate; |
||||
|
||||
cfg = config.services.snipe-it; |
||||
|
||||
phpPackage = cfg.phpPackage.withExtensions ({ enabled, all }: |
||||
enabled ++ (with all; [ |
||||
json |
||||
openssl |
||||
pdo |
||||
mbstring |
||||
tokenizer |
||||
curl |
||||
ldap |
||||
zip |
||||
fileinfo |
||||
bcmath |
||||
gd |
||||
] ++ optionals usePgsql [ |
||||
pdo_pgsql |
||||
] ++ optionals useMysql [ |
||||
pdo_mysql |
||||
]) |
||||
); |
||||
|
||||
nginxPackage = config.services.nginx.package; |
||||
|
||||
user = cfg.user; |
||||
group = cfg.group; |
||||
|
||||
db = cfg.database; |
||||
usePgsql = db.type == "pgsql"; |
||||
useMysql = db.type == "mysql"; |
||||
useSqlite = db.type == "sqlite"; |
||||
|
||||
mail = cfg.mail; |
||||
|
||||
useSSL = with cfg.nginx; (addSSL || forceSSL || onlySSL || enableACME); |
||||
|
||||
snipe-it = cfg.package.override { inherit (cfg) cacheDir dataDir; }; |
||||
|
||||
artisan = pkgs.writeShellScriptBin "snipe-it" '' |
||||
cd ${snipe-it} |
||||
sudo=exec |
||||
if [[ "$USER" != ${user} ]]; then |
||||
sudo='exec /run/wrappers/bin/sudo -u ${user}' |
||||
fi |
||||
$sudo ${phpPackage}/bin/php artisan $* |
||||
''; |
||||
|
||||
in { |
||||
|
||||
options.services.snipe-it = { |
||||
enable = mkEnableOption "Snipe-IT free open source IT asset management"; |
||||
|
||||
package = mkOption { |
||||
type = types.package; |
||||
default = callPackageWith pkgs ../../pkgs/snipe-it { }; |
||||
description = "Snipe-IT derivation to use."; |
||||
}; |
||||
|
||||
phpPackage = mkOption { |
||||
type = types.package; |
||||
default = pkgs.php74; |
||||
description = "PHP package to use."; |
||||
}; |
||||
|
||||
user = mkOption { |
||||
type = types.str; |
||||
default = "snipe-it"; |
||||
description = "User Snipe-IT runs as."; |
||||
}; |
||||
|
||||
group = mkOption { |
||||
type = types.str; |
||||
default = "snipe-it"; |
||||
description = "Group Snipe-IT runs as."; |
||||
}; |
||||
|
||||
hostName = mkOption { |
||||
type = types.str; |
||||
example = "assets.example.com"; |
||||
description = "FQDN for the Snipe-IT instance."; |
||||
}; |
||||
|
||||
maxResults = mkOption { |
||||
type = types.int; |
||||
default = 500; |
||||
description = '' |
||||
The result limit. This value determines the maximum number of results to return, |
||||
even if a higher limit is passed in an API request. This is done to prevent |
||||
timeouts when custom scripts are requesting large numbers of assets at a time. |
||||
''; |
||||
}; |
||||
|
||||
# Basic app settings |
||||
appKeyFile = mkOption { |
||||
type = types.path; |
||||
example = "/run/keys/snipe-it-appkey"; |
||||
description = '' |
||||
A file containing the app key. Used for encryption where needed. |
||||
Can be generated with <code>head -c32 /dev/urandom | base64</code>. |
||||
''; |
||||
}; |
||||
|
||||
dataDir = mkOption { |
||||
type = types.path; |
||||
default = "/var/lib/snipe-it"; |
||||
description = "Snipe-IT's data directory."; |
||||
}; |
||||
|
||||
cacheDir = mkOption { |
||||
type = types.path; |
||||
default = "/var/cache/snipe-it"; |
||||
description = "Snipe-IT's cache directory"; |
||||
}; |
||||
|
||||
database = { |
||||
type = mkOption { |
||||
type = types.enum [ "pgsql" "mysql" "sqlite" ]; |
||||
default = "pgsql"; |
||||
description = "Database engine to use."; |
||||
}; |
||||
host = mkOption { |
||||
type = types.str; |
||||
default = "localhost"; |
||||
description = "Database host address."; |
||||
}; |
||||
port = mkOption { |
||||
type = types.port; |
||||
default = if useMysql then 3306 else 5432; |
||||
description = '' |
||||
Database host port. This currently only has |
||||
an effect when using MySQL. |
||||
''; |
||||
}; |
||||
name = mkOption { |
||||
type = types.str; |
||||
default = "snipe-it"; |
||||
description = "Name of the PostgreSQL or MySQL database."; |
||||
}; |
||||
username = mkOption { |
||||
type = types.str; |
||||
default = user; |
||||
description = "Username to use to connect to database."; |
||||
}; |
||||
passwordFile = mkOption { |
||||
type = types.nullOr types.path; |
||||
example = "/run/keys/snipe-it-dbpass"; |
||||
description = '' |
||||
File containing the password corresponding to |
||||
<option>database.username</option>. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
mail = { |
||||
host = mkOption { |
||||
type = types.str; |
||||
default = "localhost"; |
||||
description = "Mail server host address."; |
||||
}; |
||||
port = mkOption { |
||||
type = types.port; |
||||
default = 25; |
||||
description = "Mail server host port."; |
||||
}; |
||||
encryption = mkOption { |
||||
type = types.nullOr (types.enum [ "ssl" "tls" ]); |
||||
default = null; |
||||
example = "tls"; |
||||
description = '' |
||||
Type of transport encryption to use. |
||||
''; |
||||
}; |
||||
username = mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
description = "User to use to connect to mail server."; |
||||
}; |
||||
passwordFile = mkOption { |
||||
type = types.path; |
||||
example = "/run/keys/snipe-it-mailpass"; |
||||
description = '' |
||||
File containing the password corresponding to |
||||
<option>mail.username</option>. |
||||
''; |
||||
}; |
||||
fromAddress = mkOption { |
||||
type = types.str; |
||||
description = ''Global "From" address.''; |
||||
}; |
||||
fromName = mkOption { |
||||
type = types.str; |
||||
default = "Snipe-IT"; |
||||
description = ''Global "From" name.''; |
||||
}; |
||||
replytoAddress = mkOption { |
||||
type = types.nullOr types.str; |
||||
default = null; |
||||
description = '' |
||||
Global "Reply-To" address. If null (the default), |
||||
"Reply-To" won't be set. |
||||
''; |
||||
}; |
||||
replytoName = mkOption { |
||||
type = types.str; |
||||
default = "Snipe-IT"; |
||||
description = '' |
||||
Global "Reply-To" name. <option>mail.replytoAddress</option> |
||||
also has to be set for this to have any effect. |
||||
''; |
||||
}; |
||||
autoEmbed = { |
||||
enable = mkEnableOption "Embed images into mails instead of hyperlinking them."; |
||||
|
||||
method = mkOption { |
||||
type = types.enum [ "attachment" "base64" ]; |
||||
default = "attachment"; |
||||
description = "Embedding method to use."; |
||||
}; |
||||
}; |
||||
}; |
||||
|
||||
memcached = { |
||||
enable = mkEnableOption "memcached as caching backend"; |
||||
host = mkOption { |
||||
type = types.str; |
||||
default = "localhost"; |
||||
description = "Memcached host address."; |
||||
}; |
||||
|
||||
port = mkOption { |
||||
type = types.port; |
||||
default = 11211; |
||||
description = "Memcached host port."; |
||||
}; |
||||
}; |
||||
|
||||
session = { |
||||
lifeTime = mkOption { |
||||
type = types.int; |
||||
default = 12000; |
||||
description = "Session lifetime in minutes."; |
||||
}; |
||||
expireOnClose = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = "Expire sessions when closing browser window."; |
||||
}; |
||||
encrypt = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = "Encrypt stored session data."; |
||||
}; |
||||
}; |
||||
|
||||
maxUploadSize = mkOption { |
||||
type = types.str; |
||||
default = "16M"; |
||||
example = "1G"; |
||||
description = "The maximum size for uploads (e.g. images)."; |
||||
}; |
||||
|
||||
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 = '' |
||||
Options for the Snipe-IT PHP pool. See the documentation on |
||||
<literal>php-fpm.conf</literal> for details on configuration directives. |
||||
''; |
||||
}; |
||||
|
||||
nginx = mkOption { |
||||
type = types.submodule ( |
||||
recursiveUpdate |
||||
(import |
||||
(modulesPath + "/services/web-servers/nginx/vhost-options.nix") |
||||
{ inherit config lib; }) |
||||
{} |
||||
); |
||||
default = {}; |
||||
example = { |
||||
serverAliases = [ |
||||
"snipe-it.\${config.networking.domain}" |
||||
]; |
||||
# To enable encryption and let letsencrypt take care of certificate |
||||
forceSSL = true; |
||||
enableACME = true; |
||||
}; |
||||
description = '' |
||||
With this option, you can customize the nginz virtualHost settings. |
||||
''; |
||||
}; |
||||
|
||||
extraConfig = mkOption { |
||||
type = types.nullOr types.lines; |
||||
default = null; |
||||
example = '' |
||||
LOGIN_MAX_ATTEMPTS=3 |
||||
LOGIN_LOCKOUT_DURATION=300 |
||||
''; |
||||
}; |
||||
|
||||
}; |
||||
|
||||
config = mkIf cfg.enable { |
||||
assertions = [ |
||||
{ assertion = usePgsql -> db.port == 5432; |
||||
message = "PostgreSQL is currently only supported with default port 5432."; |
||||
} |
||||
]; |
||||
|
||||
warnings = |
||||
optional (!useMysql) '' |
||||
Please note: Using another database than MySQL isn't officially supported. |
||||
''; |
||||
|
||||
environment.systemPackages = [ artisan ]; |
||||
|
||||
services.postgresql = mkIf (usePgsql && db.host == "localhost") { |
||||
enable = mkDefault true; |
||||
ensureDatabases = [ db.name ]; |
||||
ensureUsers = [{ |
||||
name = db.username; |
||||
ensurePermissions = { "DATABASE \"${db.name}\"" = "ALL PRIVILEGES"; }; |
||||
}]; |
||||
}; |
||||
|
||||
services.mysql = mkIf (useMysql && db.host == "localhost") { |
||||
enable = mkDefault true; |
||||
package = mkDefault pkgs.mariadb; |
||||
ensureDatabases = [ db.name ]; |
||||
ensureUsers = [{ |
||||
name = db.username; |
||||
ensurePermissions = { "${db.name}.*" = "ALL PRIVILEGES"; }; |
||||
}]; |
||||
}; |
||||
|
||||
services.phpfpm.pools.snipe-it = { |
||||
inherit user group; |
||||
phpOptions = '' |
||||
log_errors = on |
||||
post_max_size = ${cfg.maxUploadSize} |
||||
upload_max_filesize = ${cfg.maxUploadSize} |
||||
''; |
||||
settings = { |
||||
"listen.mode" = "0660"; |
||||
"listen.owner" = user; |
||||
"listen.group" = group; |
||||
} // cfg.poolConfig; |
||||
}; |
||||
|
||||
services.nginx = { |
||||
enable = mkDefault true; |
||||
virtualHosts."${cfg.hostName}" = mkMerge [ cfg.nginx { |
||||
root = mkForce "${snipe-it}/public"; |
||||
extraConfig = '' |
||||
index index.php index.html index.htm; |
||||
${optionalString useSSL "fastcgi_param HTTPS on;"} |
||||
''; |
||||
locations = { |
||||
"/" = { |
||||
extraConfig = ''try_files $uri $uri/ /index.php$is_args$args;''; |
||||
}; |
||||
"~ \.php$" = { |
||||
extraConfig = '' |
||||
try_files $uri $uri/ =404; |
||||
include ${nginxPackage}/conf/fastcgi_params; |
||||
fastcgi_index index.php; |
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; |
||||
fastcgi_pass unix:${config.services.phpfpm.pools."snipe-it".socket}; |
||||
${optionalString useSSL "fastcgi_param HTTPS on;"} |
||||
''; |
||||
}; |
||||
}; |
||||
}]; |
||||
}; |
||||
|
||||
systemd.tmpfiles.rules = [ |
||||
"d ${cfg.cacheDir} 0700 ${user} ${group} - -" |
||||
"d ${cfg.cacheDir}/bootstrap 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir} 0710 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/accessories 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/assets 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/avatars 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/barcodes 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/categories 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/companies 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/components 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/consumables 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/departments 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/locations 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/manufacturers 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/models 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/uploads/suppliers 0750 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/app 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/app/backups 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/app/backups/env-backups 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/debugbar 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/framework 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/framework/cache 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/framework/sessions 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/framework/views 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/logs 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/assetmodels 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/assets 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/audits 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/imports 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/licenses 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/signatures 0700 ${user} ${group} - -" |
||||
"d ${cfg.dataDir}/storage/private_uploads/users 0700 ${user} ${group} - -" |
||||
] ++ optionals useSqlite [ |
||||
"f ${cfg.dataDir}/database.sqlite 0600 ${user} ${group} -" |
||||
]; |
||||
|
||||
|
||||
systemd.services.snipe-it-setup = { |
||||
description = "Preparation tasks for Snipe-IT"; |
||||
before = [ "phpfpm-snipe-it.service" ]; |
||||
after = optional useMysql "mysql.service" |
||||
++ optional usePgsql "postgresql.service"; |
||||
wantedBy = [ "multi-user.target" ]; |
||||
serviceConfig = { |
||||
Type = "oneshot"; |
||||
User = user; |
||||
WorkingDirectory = "${snipe-it}"; |
||||
}; |
||||
script = '' |
||||
# create .env file |
||||
cat > ${cfg.dataDir}/.env << EOF |
||||
APP_KEY="base64:$(head -n1 ${cfg.appKeyFile})" |
||||
APP_URL="http${optionalString useSSL "s"}://${cfg.hostName}" |
||||
APP_LOG=syslog |
||||
MAX_RESULTS=${toString cfg.maxResults} |
||||
|
||||
'' + optionalString useSSL '' |
||||
ENABLE_HSTS=true |
||||
SECURE_COOKIES=true |
||||
|
||||
'' + '' |
||||
SESSION_DRIVER=file |
||||
SESSION_LIFETIME=${toString cfg.session.lifeTime} |
||||
EXPIRE_ON_CLOSE=${boolToString cfg.session.expireOnClose} |
||||
ENCRYPT=${boolToString cfg.session.encrypt} |
||||
|
||||
DB_CONNECTION=${db.type} |
||||
'' + optionalString (db.type != "sqlite") '' |
||||
DB_HOST=${db.host} |
||||
DB_PORT=${toString db.port} |
||||
DB_PASSWORD="$(head -n1 ${db.passwordFile})" |
||||
DB_USERNAME=${db.username} |
||||
DB_DATABASE=${db.name} |
||||
'' + '' |
||||
|
||||
MAIL_DRIVER=smtp |
||||
MAIL_HOST=${mail.host} |
||||
MAIL_PORT=${toString mail.port} |
||||
${optionalString (mail.encryption != null) "MAIL_ENCRYPTION=${mail.encryption}"} |
||||
MAIL_FROM_ADDR=${mail.fromAddress} |
||||
MAIL_FROM_NAME=${mail.fromName} |
||||
MAIL_AUTO_EMBED=${boolToString mail.autoEmbed.enable} |
||||
MAIL_AUTO_EMBED_METHOD=${mail.autoEmbed.method} |
||||
'' + optionalString (mail.username != null) '' |
||||
MAIL_USERNAME=${mail.username} |
||||
MAIL_PASSWORD="$(head -n1 ${mail.passwordFile})" |
||||
'' + optionalString (mail.replytoAddress != null) '' |
||||
MAIL_REPLYTO_ADDR=${mail.replytoAddress} |
||||
MAIL_REPLYTO_NAME=${mail.replytoName} |
||||
'' + optionalString cfg.memcached.enable '' |
||||
|
||||
CACHE_DRIVER=memcached |
||||
MEMCACHED_HOST=${cfg.memcached.host} |
||||
MEMCACHED_PORT=${toString cfg.memcached.port} |
||||
'' + '' |
||||
|
||||
${optionalString (cfg.extraConfig != null) cfg.extraConfig} |
||||
EOF |
||||
chmod 600 ${cfg.dataDir}/.env |
||||
|
||||
# re-evaluate configuration |
||||
${phpPackage}/bin/php artisan config:clear |
||||
${phpPackage}/bin/php artisan config:cache |
||||
|
||||
# migrate db |
||||
${phpPackage}/bin/php artisan migrate --force |
||||
|
||||
# create caches |
||||
${phpPackage}/bin/php artisan event:cache |
||||
${phpPackage}/bin/php artisan view:cache |
||||
''; |
||||
}; |
||||
|
||||
users = { |
||||
users."${user}" = { |
||||
isSystemUser = true; |
||||
home = cfg.dataDir; |
||||
group = group; |
||||
}; |
||||
groups."${group}" = {}; |
||||
users."${config.services.nginx.user}".extraGroups = [ group ]; |
||||
}; |
||||
}; |
||||
} |
Loading…
Reference in new issue