{ pkgs, lib, config, modulesPath, ... }: { imports = [ "${modulesPath}/profiles/minimal.nix" "${modulesPath}/profiles/qemu-guest.nix" "${modulesPath}/virtualisation/qemu-vm.nix" ]; config = { services.qemuGuest.enable = true; system.stateVersion = "23.05"; fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; autoResize = true; }; boot = { growPartition = true; loader.timeout = 5; }; virtualisation = { diskSize = 8000; # MB memorySize = 2048; # MB # We don't want to use tmpfs, otherwise the nix store's size will be bounded # by a fraction of available RAM. writableStoreUseTmpfs = false; forwardPorts = [{ guest.port = 22; host.port = 2222; } { guest.port = 9090; host.port = 9090; } { guest.port = 8081; host.port = 8081; }]; }; # So that we can ssh into the VM, see e.g. # http://blog.patapon.info/nixos-local-vm/#accessing-the-vm-with-ssh services.openssh.enable = true; services.openssh.settings.PermitRootLogin = "yes"; # Give root an empty password to ssh in. users.extraUsers.root.password = ""; users.users.root.openssh.authorizedKeys.keys = [ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMNeQYLFauAbzDyIbKC86NUh9yZfiyBm/BtIdkcpZnSU" ]; users.mutableUsers = false; networking.firewall.enable = false; environment.systemPackages = with pkgs; [ git htop neovim ]; services.mysql = { enable = true; package = pkgs.mariadb; ensureUsers = [{ name = "root"; ensurePermissions = { "*.*" = "ALL PRIVILEGES"; }; }]; ensureDatabases = [ "root" ]; }; services.redis.servers = { # Queue, naming it "" makes it use default values. "".enable = true; socketio = { enable = true; port = 12311; }; }; users.users.erpnext = { description = "User to run erpnext"; group = "erpnext"; isSystemUser = true; home = "/var/lib/erpnext"; createHome = true; }; systemd.services.setup-mysql = { enable = true; before = [ "erpnext.service" ]; after = [ "mysql.service" ]; wantedBy = [ "erpnext.service" ]; partOf = [ "erpnext.service" ]; script = '' ${pkgs.mariadb-client}/bin/mysql -e "SET PASSWORD FOR 'root'@'localhost' = PASSWORD('password')"; ''; serviceConfig = { RemainAfterExit = true; Type = "oneshot"; }; }; systemd.services.ensure-bench-dir = { enable = true; before = [ "erpnext.service" ]; wantedBy = [ "erpnext.service" ]; partOf = [ "erpnext.service" ]; script = '' for subdir in apps sites config/pids logs; do mkdir -p /var/lib/erpnext/bench/$subdir done ''; serviceConfig = { RemainAfterExit = true; Type = "oneshot"; User = "erpnext"; }; }; services.nginx = { enable = true; appendHttpConfig = builtins.readFile "${pkgs.erpnext-nginx-conf}"; }; systemd.services.erpnext = let penv = pkgs.python3-erpnext.buildEnv.override { extraLibs = [ pkgs.python3-erpnext.pkgs.frappe pkgs.python3-erpnext.pkgs.erpnext pkgs.python3-erpnext.pkgs.bench ]; }; appsFile = pkgs.writeText "erpnext-apps.txt" '' frappe erpnext ''; # In a module, this could be provided by a use as a file as it could # contain secrets and we don't want this in the nix-store. But here it # is OK. commonSiteConfig = pkgs.writeText "erpnext-common_site_config.json" '' { "db_host": "localhost", "db_port": 3306, "db_name": "erpnext" , "db_password": "erpnext" , "redis_cache": "redis://localhost:6379?db=0", "redis_queue": "redis://localhost:6379?db=1", "redis_socketio": "redis://localhost:6379?db=2", "socketio_port": 3000 } ''; in { enable = true; wantedBy = [ "multi-user.target" ]; after = [ "mysql.service" "redis.service" "redis-socketio.service" ]; description = "ERPNext"; confinement = { enable = true; packages = [ pkgs.mariadb-client pkgs.nodejs penv ]; }; script = '' export PYTHON_PATH=${penv}/${pkgs.python3-erpnext.sitePackages} export PATH="${pkgs.mariadb-client}/bin:${pkgs.nodejs}/bin:${penv}/bin:$PATH" # Upstream initializes the DB with this command # TODO: Make this idempotent cd /var/lib/erpnext/bench/sites bench new-site localhost --mariadb-root-password password --admin-password admin || true bench --site localhost install-app erpnext # TODO: Run these as systemd units node /var/lib/erpnext/bench/apps/frappe/socketio.js & gunicorn --chdir="/var/lib/erpnext/bench/sites" --bind=0.0.0.0:9090 --threads=4 --workers=2 --worker-class=gthread --worker-tmp-dir=/dev/shm --timeout=120 --preload frappe.app:application ''; serviceConfig = { User = "erpnext"; NoNewPrivileges = true; Type = "simple"; BindReadOnlyPaths = [ "/etc/hosts:/etc/hosts" "${pkgs.frappe-app}:${pkgs.frappe-app}" "${pkgs.frappe-app}/share/apps/frappe:/var/lib/erpnext/bench/apps/frappe" "${pkgs.erpnext-app}:${pkgs.erpnext-app}" "${pkgs.erpnext-app}/share/apps/erpnext:/var/lib/erpnext/bench/apps/erpnext" "${pkgs.frappe-erpnext-assets}/share/sites/assets:/var/lib/erpnext/bench/sites/assets" "${appsFile}:/var/lib/erpnext/bench/sites/apps.txt" "${commonSiteConfig}:/var/lib/erpnext/bench/sites/common_site_config.json" "${penv}:/var/lib/erpnext/bench/env" ]; BindPaths = [ "/var/lib/erpnext:/var/lib/erpnext" ]; }; }; }; }