{ inputs = { # nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; systems.url = "github:nix-systems/default"; flake-parts.url = "github:hercules-ci/flake-parts"; # Lexical is an alternative LSP server. There are several options for Elixir # LSP. See https://gist.github.com/Nezteb/dc63f1d5ad9d88907dd103da2ca000b1 lexical.url = "github:lexical-lsp/lexical"; # Use process-compose to manage background processes during development process-compose-flake.url = "github:Platonic-Systems/process-compose-flake"; }; outputs = { self, nixpkgs, systems, flake-parts, ... } @ inputs: let # Set the Erlang version erlangVersion = "erlang"; # Set the Elixir version elixirVersion = "elixir"; in flake-parts.lib.mkFlake {inherit inputs;} { systems = import systems; imports = [inputs.process-compose-flake.flakeModule]; flake = { nixosModules.default = { config, lib, pkgs, ... }: with lib; let cfg = config.sites.zoeycomputer; working_directory = "/var/lib/zoeycomputer"; in { options.sites.zoeycomputer = { enable = mkEnableOption "Enables the zoey computer"; domain = mkOption rec { type = types.str; default = "zoeys.computer"; example = default; description = "The domain name for the website"; }; ssl = mkOption rec { type = types.bool; default = true; example = default; description = "Whether to enable SSL on the domain or not"; }; phx = { port = mkOption rec { type = types.int; default = 4000; example = default; description = "What port should phoenix run on"; }; package = mkOption rec { type = types.package; default = self.packages.${pkgs.system}.default; example = default; description = "The phoenix package containing the application"; }; secret_key_file = mkOption { type = types.str; example = "/var/lib/skey"; description = "The Secrert key for Phoenix"; }; database = { name = mkOption rec { type = types.str; default = "zoeyscomputer"; example = default; description = "Database name"; }; user = mkOption rec { type = types.str; default = "zoeyscomputer"; example = default; description = "Database user"; }; passwordFile = mkOption { type = types.nullOr types.path; default = null; example = "/run/secrets/db_password"; description = "File containing the database password"; }; host = mkOption rec { type = types.str; default = "localhost"; example = default; description = "Database host"; }; port = mkOption rec { type = types.int; default = 5432; example = default; description = "Database port"; }; }; enableServer = mkOption rec { type = types.bool; default = true; example = default; description = "Enable Phoenix Server, why would you not enable this?"; }; }; }; config = mkIf cfg.enable { services.nginx.virtualHosts.${cfg.domain} = { forceSSL = cfg.ssl; enableACME = cfg.ssl; locations."/".proxyPass = "http://127.0.0.1:${toString cfg.phx.port}"; }; users.users."zoeyscomputer-phx" = { isSystemUser = true; group = "zoeyscomputer-phx"; home = working_directory; # Add this createHome = true; # Add this }; users.groups."zoeyscomputer-phx" = {}; systemd.tmpfiles.rules = [ "d '${working_directory}' 0750 zoeyscomputer-phx zoeyscomputer-phx - -" ]; # Configure PostgreSQL authentication for our user services.postgresql.authentication = mkBefore '' local ${cfg.phx.database.name} ${cfg.phx.database.user} scram-sha-256 ''; # Create a oneshot service to set up the database user with password systemd.services.init-zoeyscomputer-db = { description = "Initialize ZoeysComputer Database User"; wantedBy = ["multi-user.target"]; after = ["postgresql.service"]; before = ["zoeyscomputer.service"]; path = with pkgs; [util-linux]; serviceConfig = { Type = "oneshot"; RemainAfterExit = true; User = "root"; }; script = '' ${optionalString (cfg.phx.database.passwordFile != null) '' # Read password from file PASSWORD=$(cat ${cfg.phx.database.passwordFile}) # Create user and database if they don't exist if ! runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='${cfg.phx.database.user}'" | grep -q 1; then echo "CREATE USER ${cfg.phx.database.user} WITH PASSWORD '$PASSWORD'" | \ runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql else echo "ALTER USER ${cfg.phx.database.user} WITH PASSWORD '$PASSWORD'" | \ runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql fi # Grant privileges to the user echo "GRANT ALL PRIVILEGES ON DATABASE ${cfg.phx.database.name} TO ${cfg.phx.database.user}" | \ runuser -u ${config.services.postgresql.superUser} -- ${config.services.postgresql.package}/bin/psql ''} ''; }; # Ensure database exists services.postgresql.ensureDatabases = [ cfg.phx.database.name ]; systemd.services."zoeyscomputer-phx" = let release_name = "zoeyscomputer"; in { wantedBy = ["multi-user.target"]; after = ["network.target" "postgresql.service" "init-zoeyscomputer-db.service"]; requires = ["network-online.target" "postgresql.service" "init-zoeyscomputer-db.service"]; description = "zoey computer"; environment = { RELEASE_TMP = working_directory; PORT = toString cfg.phx.port; PHX_HOST = cfg.domain; PHX_SERVER = toString cfg.phx.enableServer; }; serviceConfig = { Type = "exec"; User = "zoeyscomputer-phx"; Group = "zoeyscomputer-phx"; # Add this # DynamicUser = true; WorkingDirectory = working_directory; PrivateTmp = true; ExecStart = pkgs.writeShellScript "start-zoeycomputer" '' export SECRET_KEY_BASE=$(cat ${cfg.phx.secret_key_file}) # If a password file is specified, construct DATABASE_URL with password ${ if cfg.phx.database.passwordFile != null then '' export DB_PASSWORD=$(cat ${cfg.phx.database.passwordFile}) export DATABASE_URL="ecto://${cfg.phx.database.user}:$DB_PASSWORD@${cfg.phx.database.host}:${toString cfg.phx.database.port}/${cfg.phx.database.name}" '' else '' export DATABASE_URL="ecto://${cfg.phx.database.user}@${cfg.phx.database.host}/${cfg.phx.database.name}" '' } # Run migrations ${cfg.phx.package}/bin/migrate # Start the application exec ${cfg.phx.package}/bin/${release_name} start ''; ExecStop = '' ${cfg.phx.package}/bin/${release_name} stop ''; ExecReload = '' ${cfg.phx.package}/bin/${release_name} restart ''; Restart = "on-failure"; RestartSec = 5; StartLimitBurst = 3; StartLimitInterval = 10; }; path = [pkgs.bash pkgs.imagemagick pkgs.librsvg]; }; }; }; }; perSystem = { # self', config, system, pkgs, lib, ... }: let npmDeps = (pkgs.callPackage ./assets/default.nix {}).shell.nodeDependencies; heroicons = pkgs.fetchFromGitHub { owner = "tailwindlabs"; repo = "heroicons"; rev = "v2.1.1"; hash = "sha256-y/kY8HPJmzB2e7ErgkUdQijU7oUhfS3fI093Rsvyvqs="; sparseCheckout = ["optimized"]; }; in { # Define a consistent package set for development, testing, and # production. _module.args.pkgs = import nixpkgs { inherit system; overlays = [ ( final: _: let erlang = final.beam.interpreters.${erlangVersion}; beamPackages = final.beam.packages.${erlangVersion}; elixir = beamPackages.${elixirVersion}; in { inherit erlang elixir; # Hex is not used in the devShell. # inherit (beamPackages) hex; } ) ]; }; # # Add hydraJob for x86_64-linux # hydraJobs = lib.optionalAttrs (system == "x86_64-linux") { # default = self.packages.${system}.default; # }; # You can build your Elixir application using mixRelease. packages.default = pkgs.beamPackages.mixRelease { pname = "zoeys-computer"; version = "0.1.0"; src = ./.; removeCookie = false; mixNixDeps = with pkgs; import ./deps.nix { inherit pkgs lib beamPackages; }; buildInputs = with pkgs; [nodejs imagemagick]; nativeBuildInputs = with pkgs; [ imagemagick ]; postBuild = '' echo ${heroicons} ln -sf ${npmDeps}/lib/node_modules assets/node_modules ln -sf ${heroicons} deps/heroicons ls deps/ npm run deploy --prefix ./assets mix do deps.loadpaths --no-deps-check, phx.digest ''; }; # Add dependencies to develop your application using Mix. devShells.default = pkgs.mkShell { buildInputs = with pkgs; ( [ erlang elixir # You are likely to need Node.js if you develop a Phoenix # application. nodejs mix2nix node2nix librsvg imagemagick # Add the language server of your choice. inputs.lexical.packages.${system}.default # I once added Hex via a Nix development shell, but now I install # and upgrade it using Mix. Hex installed using Nix can cause an # issue if you manage Elixir dependencies using Mix. ] # Add a dependency for a file watcher if you develop a Phoenix # application. ++ lib.optional stdenv.isLinux inotify-tools ++ (lib.optionals stdenv.isDarwin ( with darwin.apple_sdk.frameworks; [ CoreFoundation CoreServices ] )) ); }; # You can define background processes in Nix using # process-compose-flake. process-compose.example = { settings = { processes = { ponysay.command = '' while true; do ${lib.getExe pkgs.ponysay} "Enjoy our ponysay demo!" sleep 2 done ''; }; }; }; }; }; }