2021-04-16 18:00:27 +02:00
|
|
|
{ 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 useMysql [
|
|
|
|
pdo_mysql
|
|
|
|
])
|
|
|
|
);
|
|
|
|
|
|
|
|
nginxPackage = config.services.nginx.package;
|
|
|
|
|
|
|
|
user = cfg.user;
|
|
|
|
group = cfg.group;
|
|
|
|
|
|
|
|
db = cfg.database;
|
|
|
|
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 {
|
2021-04-23 00:34:45 +02:00
|
|
|
type = types.enum [ "mysql" "sqlite" ];
|
|
|
|
default = "mysql";
|
2021-04-16 18:00:27 +02:00
|
|
|
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";
|
2021-04-23 00:34:45 +02:00
|
|
|
description = "Name of the MySQL database.";
|
2021-04-16 18:00:27 +02:00
|
|
|
};
|
|
|
|
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 {
|
|
|
|
warnings =
|
|
|
|
optional (!useMysql) ''
|
|
|
|
Please note: Using another database than MySQL isn't officially supported.
|
|
|
|
'';
|
|
|
|
|
|
|
|
environment.systemPackages = [ artisan ];
|
|
|
|
|
|
|
|
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" ];
|
2021-04-23 00:34:45 +02:00
|
|
|
after = optional useMysql "mysql.service";
|
2021-04-16 18:00:27 +02:00
|
|
|
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 ];
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|