diff --git a/flake.nix b/flake.nix index 40f02dc..50a9457 100644 --- a/flake.nix +++ b/flake.nix @@ -127,6 +127,7 @@ influxdb = "influxdb.${baseName}"; irc = "irc.${baseName}"; mail = "noahmasur.com"; + mathesar = "mathesar.${baseName}"; metrics = "metrics.${baseName}"; minecraft = "minecraft.${baseName}"; n8n = "n8n.${baseName}"; diff --git a/pkgs/mathesar/package.nix b/pkgs/mathesar/package.nix new file mode 100644 index 0000000..32c1351 --- /dev/null +++ b/pkgs/mathesar/package.nix @@ -0,0 +1,295 @@ +{ + runtimeShell, + python313, + python313Packages, + fetchFromGitHub, + fetchPypi, + fetchurl, + gettext, + unzip, + ... +}: +let + + django-modern-rpc = python313Packages.buildPythonPackage rec { + pname = "django_modern_rpc"; + version = "1.1.0"; + src = fetchPypi { + inherit pname version; + hash = "sha256-+LBIfkBxe9lvfZIqPI2lFSshTZBL1NpmCWBAgToyJns="; + }; + doCheck = false; + pyproject = true; + build-system = [ + python313Packages.setuptools + python313Packages.wheel + python313Packages.poetry-core + ]; + }; + django-property-filter = python313Packages.buildPythonPackage rec { + pname = "django_property_filter"; + version = "1.3.0"; + src = fetchPypi { + inherit pname version; + hash = "sha256-dpsF4hm0S4lQ6tIRJ0bXgPjWTr1fq1NSCZP0M6L4Efk="; + }; + doCheck = false; + pyproject = true; + build-system = [ + python313Packages.setuptools + python313Packages.wheel + python313Packages.django + python313Packages.django-filter + ]; + }; + django-fernet-encrypted-fields = python313Packages.buildPythonPackage rec { + pname = "django-fernet-encrypted-fields"; + version = "0.3.0"; + src = fetchPypi { + inherit pname version; + hash = "sha256-OAMb2vFySm6IXuE3zGaivX3DcmxDjhiep+RHmewLqbM="; + }; + doCheck = false; + pyproject = true; + build-system = [ + python313Packages.setuptools + python313Packages.wheel + ]; + propagatedBuildInputs = with python313Packages; [ + django + cryptography + ]; + }; + drf-access-policy = python313Packages.buildPythonPackage rec { + pname = "drf-access-policy"; + version = "1.5.0"; + src = fetchPypi { + inherit pname version; + hash = "sha256-EsahQYIgjUBUSi/W8GXbc7pvYLPRJ6kpJg6A3RkrjL8="; + }; + doCheck = false; + pyproject = true; + build-system = [ + python313Packages.setuptools + python313Packages.wheel + ]; + propagatedBuildInputs = with python313Packages; [ + pyparsing + djangorestframework + ]; + }; + + pythonPkg = python313.override { + self = python313; + packageOverrides = pyfinal: pyprev: { + inherit + django-modern-rpc + django-property-filter + django-fernet-encrypted-fields + drf-access-policy + # psycopg-binary + ; + }; + }; + + python = pythonPkg.withPackages ( + ps: with ps; [ + gunicorn + django + clevercsv + django + dj-database-url + django-filter + django-modern-rpc + django-property-filter + djangorestframework + django-fernet-encrypted-fields + drf-access-policy + frozendict + gunicorn + psycopg + # psycopg-binary + psycopg2-binary + requests + sqlalchemy + whitenoise + ] + ); + + staticAssets = fetchurl { + url = "https://github.com/mathesar-foundation/mathesar/releases/download/0.2.2/static_files.zip"; + sha256 = "sha256-1X2zFpCSwilUxhqHlCw/tg8C5zVcVL6CxDa9yh0ylGA="; + }; + +in +python313Packages.buildPythonApplication rec { + pname = "mathesar"; + version = "0.2.2"; + src = fetchFromGitHub { + owner = "mathesar-foundation"; + repo = "mathesar"; + rev = version; + sha256 = "sha256-LHxFJpPV0GJfokSPzfZQO44bBg/+QjXsk04Ry9uhUAs="; + }; + format = "other"; + nativeBuildInputs = [ unzip ]; + propagatedBuildInputs = [ + python.pkgs.gunicorn + python.pkgs.django + ]; + buildInputs = [ + gettext + ]; + dependencies = [ + pythonPkg.pkgs.clevercsv + pythonPkg.pkgs.django + pythonPkg.pkgs.dj-database-url + pythonPkg.pkgs.django-filter + pythonPkg.pkgs.django-modern-rpc + pythonPkg.pkgs.django-property-filter + pythonPkg.pkgs.djangorestframework + pythonPkg.pkgs.django-fernet-encrypted-fields + pythonPkg.pkgs.drf-access-policy + pythonPkg.pkgs.frozendict + pythonPkg.pkgs.gunicorn + pythonPkg.pkgs.psycopg + pythonPkg.pkgs.psycopg2-binary + pythonPkg.pkgs.requests + pythonPkg.pkgs.sqlalchemy + pythonPkg.pkgs.whitenoise + ]; + + # Manually unzip the extra zip file into a temporary directory + postUnpack = '' + mkdir -p $TMPDIR/unzipped + unzip ${staticAssets} -d $TMPDIR/unzipped + ''; + + # Override the default build phase to prevent it from looking for setup.py + # Add any non-Python build commands here if needed (e.g., building frontend assets) + buildPhase = '' + runHook preBuild + + echo "Skipping standard Python build phase; application files copied in installPhase." + # If you had frontend assets to build, you'd run the command here, e.g.: + # npm install + # npm run build + + runHook postBuild + ''; + + # This copies the application code into the Nix store output + installPhase = '' + runHook preInstall + + # Destination: python's site-packages directory within $out + # This makes 'import mathesar', 'import db', etc. work more easily. + INSTALL_PATH="$out/lib/${python.libPrefix}/site-packages/${pname}" + mkdir -p "$INSTALL_PATH" + + echo "Copying application code to $INSTALL_PATH" + + # Copy all essential source directories needed at runtime + # Adjust this list based on mathesar's actual structure and runtime needs! + cp -r mathesar "$INSTALL_PATH/" + cp -r db "$INSTALL_PATH/" + cp -r config "$INSTALL_PATH/" + cp -r translations "$INSTALL_PATH/" + cp -r mathesar_ui "$INSTALL_PATH/" # If needed + + # Copy the management script + cp manage.py "$INSTALL_PATH/" + + # Copy assets from unzipped directory + mkdir -p "$INSTALL_PATH/mathesar/static/mathesar" + cp -r $TMPDIR/unzipped/static_files/* "$INSTALL_PATH/mathesar/static/mathesar" + + # Create wrapper scripts in $out/bin for easy execution + + mkdir -p $out/bin + + # Wrapper for manage.py + # It ensures the app code is in PYTHONPATH and runs manage.py + echo "Creating manage.py wrapper..." + cat < $out/bin/mathesar-manage + #!${python.interpreter} + import os + import sys + + # Add the installation path to the Python path + sys.path.insert(0, "$INSTALL_PATH") + + # Set DJANGO_SETTINGS_MODULE environment variable if required by mathesar + # You might need to adjust 'config.settings.production' to the actual settings file used + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings.production') + + # Change directory to where manage.py is, if necessary for relative paths + # os.chdir("$INSTALL_PATH") + + print(f"Running manage.py from: $INSTALL_PATH/manage.py") + print(f"Python path includes: $INSTALL_PATH") + print(f"Executing with args: {sys.argv[1:]}") + + # Find manage.py and execute it + manage_py_path = os.path.join("$INSTALL_PATH", "manage.py") + if not os.path.exists(manage_py_path): + print(f"Error: manage.py not found at {manage_py_path}", file=sys.stderr) + sys.exit(1) + + # Prepare arguments for execute_from_command_line + # The first argument should be the script name itself + argv = [manage_py_path] + sys.argv[1:] + + try: + from django.core.management import execute_from_command_line + execute_from_command_line(argv) + except Exception as e: + print(f"Error executing manage.py: {e}", file=sys.stderr) + # Optionally re-raise or exit with error + import traceback + traceback.print_exc() + sys.exit(1) + + EOF + chmod +x $out/bin/mathesar-manage + + # Wrapper for install + echo "Creating install wrapper..." + cat < $out/bin/mathesar-install + #!${runtimeShell} + # Add the app to the Python Path + export PYTHONPATH="$INSTALL_PATH:\${"PYTHONPATH:-"}" + + # Set Django settings module if needed + export DJANGO_SETTINGS_MODULE='config.settings.production' + + # Change to the app directory + cd "$INSTALL_PATH" + ${python}/bin/python -m mathesar.install + EOF + chmod +x $out/bin/mathesar-install + + # Wrapper for gunicorn (example) + # Assumes mathesar uses a standard wsgi entry point, e.g., config/wsgi.py + # Adjust 'config.wsgi:application' if necessary + echo "Creating gunicorn wrapper..." + cat < $out/bin/mathesar-gunicorn + #!${runtimeShell} + # Add the app to the Python Path + export PYTHONPATH="$INSTALL_PATH:\${"PYTHONPATH:-"}" + + # Set Django settings module if needed + export DJANGO_SETTINGS_MODULE='config.settings.production' + + # Change to the app directory if gunicorn needs it + # cd "$INSTALL_PATH" + + # Execute gunicorn, passing along any arguments + # Ensure the gunicorn package is in propagatedBuildInputs + exec ${python}/bin/gunicorn config.wsgi:application "\$@" + EOF + chmod +x $out/bin/mathesar-gunicorn + + runHook postInstall + ''; +} diff --git a/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar-postgres.age b/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar-postgres.age new file mode 100644 index 0000000..3a5985e --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar-postgres.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyA2SUsv +d1R3Slo3UDhDUWNiTDcybU9mTzhycDNKVCtteHhwRXU4M0x5UVF3CkUvZHdISzE4 +NU9qdy9sUGxTeTJxY2huWS9zVnRzd2o0UFpxc0FjT2lDTGMKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIG1SM3gxRzJ5RllOTXhHZC9sc1Z6U1VCc3ptTWZRYzNJQ1g4ZjRI +d3U3RVUKekNYc0VpSUw4TVJsSGNYNElxUUcwS25oczVUWnNlUTd2SXlvYlEvcE01 +ZwotPiBzc2gtZWQyNTUxOSBuanZYNUEgc0JUZlkyY0dKWjhzVlMwTi8zS1VrVGt1 +UGJzeURtbEZ3ZWpncWZNK2x5SQpJdVNSV2NLdHBEVThtWVBhUm1GSEEvV2s0bGZB +WFJTK0xIa01ucHBxcm1jCi0+IHNzaC1lZDI1NTE5IENxSU9VQSBrRitXVzdvRmgz +VW5HMHpBRStuTnRDZmlCdVRra09JNE85Wkl4bkJJWkFzCkZUeTRWK0VmL21VRlpW +SWg5dnJ4UGw4aXZteUxyaXZUaGpxQkdQL0hXK0kKLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IE4xSlZYbmFWZlhCQ3RibFN5Z3gzMDN5cXNDQzA4UnViUk9IclBTdE4valkK +MGx3Nkhac3NEZGh2SExUMU94NExiRlMvRndobUlJdmhBWU1CM0diL0V3ZwotLS0g +SDhvSzRFUFdGN0ZLUm9hWWV2T2tHMVdvTHlWOEZWTkZjLzd0VE12SU9kNAp2wHsq +uKxlrH9xXj17Zd3FAjRlSMjjVVOYZOVyiPMWY7MD7V5XVY0hCPnKpzX4RyYeTfqf +3W2LaWecMcRtTdRLOyCqVC+1DXtCAL/DqxIWcu3Q+zDQog== +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar.age b/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar.age new file mode 100644 index 0000000..815cdfe --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar.age @@ -0,0 +1,17 @@ +-----BEGIN AGE ENCRYPTED FILE----- +YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHNzaC1lZDI1NTE5IE1nSGFPdyBJZjI5 +Y0pMd01IdVJIa2RtU3BrajNGblczeUdkWUgxdFliQmJGK2RDclhVCjVlbmRpV1Bj +K1hMTm1HZ0pnYm05YmZ0Tm9UZjVoQ3FMM2dhVkRtV1FBZWsKLT4gc3NoLWVkMjU1 +MTkgWXlTVU1RIGxzbXA2NVRXbGxsY05hU0ZGWlU2MTNGVGozN2dycnBaNUZ4Ykxx +UjB0d3MKcUdRampFeTdGek9FaHVrV3Y1ay8rMGh6M2NRZjVpUHdWYVJYZENmOGty +RQotPiBzc2gtZWQyNTUxOSBuanZYNUEgamdMKzRnRldKYW9sYzJGWmR2TjRYVlRZ +SXhKT3BrNC9XdHhpUVFQSXVROApoM3BUajhmR01VeDR1MHJhMnJFUkxCOW9DckZF +TkhMd1BYODMrVm5PSGFJCi0+IHNzaC1lZDI1NTE5IENxSU9VQSAzbVJsT2pBTENF +eGUrOEdHakZzb3ExMGwyMW91TEZORmpxdUJJMUJlZEJFClV2UlFlNVBxSmlaMnNs +MTlFNzVOSjVqMVp5a1dwUVJqR3ZPRkdnY0w5dXMKLT4gc3NoLWVkMjU1MTkgejFP +Y1p3IDcrZzhhWjh4UFVSM1loTTV1UXp2NDF1cGlLUWZ2bTN0NHJiOFhESFdJQWcK +UFZzT2hmSTFlR0VNenVobktDN2xaZElwTWFZVklscFAvQmQyZjJiTU4wawotLS0g +UW8vRlpmcGV1SmR1blZRK3c0eVpGeUlZMEc5eGlRcVpnbXI5UkNUelJEYwo8Na+w +XzVV1/LPzA3kl0yDvF2b0nn1TmR903ralFbjmT2Rv/HNDVyVklIz1Jycaje8W8uV +vZGGicSNIIZbGLEYT9fMUzY1KPoU6LUx0mgGUK2PZssHmG9mbW/Jx3R6 +-----END AGE ENCRYPTED FILE----- diff --git a/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar.nix b/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar.nix new file mode 100644 index 0000000..a85399a --- /dev/null +++ b/platforms/nixos/modules/nmasur/presets/services/mathesar/mathesar.nix @@ -0,0 +1,97 @@ +{ + config, + pkgs, + lib, + ... +}: + +let + inherit (config.nmasur.settings) hostnames; + cfg = config.nmasur.presets.services.mathesar; +in + +{ + + options.nmasur.presets.services.mathesar = { + enable = lib.mkEnableOption "Postgres web UI"; + port = lib.mkOption { + type = lib.types.port; + description = "Port to use for the localhost"; + default = 8099; + }; + }; + + config = lib.mkIf cfg.enable { + + systemd.services.mathesar = { + description = "Postgres web UI"; + after = [ + "network.target" + "postgresql.target" + ]; + requires = [ + "mathesar-secret.service" + "mathesar-postgres-secret.service" + ]; + wantedBy = [ "multi-user.target" ]; + environment = { + POSTGRES_HOST = "127.0.0.1"; + POSTGRES_DB = "mathesar_django"; + POSTGRES_USER = "mathesar"; + # POSTGRES_PASSWORD = "none"; + POSTGRES_PORT = "5432"; + ALLOWED_HOSTS = "*"; + SKIP_STATIC_COLLECTION = "true"; + DEBUG = "true"; + }; + serviceConfig = { + Type = "simple"; + DynamicUser = true; + StateDirectory = "mathesar"; + + EnvironmentFile = [ + config.secrets.mathesar.dest + config.secrets.mathesar-postgres.dest + ]; + }; + preStart = "exec ${pkgs.nmasur.mathesar}/bin/mathesar-install"; + script = + let + args = [ "--bind=127.0.0.1:${builtins.toString cfg.port}" ]; + in + '' + exec ${pkgs.nmasur.mathesar}/bin/mathesar-gunicorn ${toString args} + ''; + }; + + secrets.mathesar = { + source = ./mathesar.age; + dest = "${config.secretsDirectory}/mathesar"; + owner = builtins.toString config.users.users.postgres.uid; + group = builtins.toString config.users.users.postgres.uid; + }; + secrets.mathesar-postgres = { + source = ./mathesar-postgres.age; + dest = "${config.secretsDirectory}/mathesar-postgres"; + owner = builtins.toString config.users.users.postgres.uid; + group = builtins.toString config.users.users.postgres.uid; + }; + + # Allow web traffic to Caddy + nmasur.presets.services.caddy.routes = [ + { + match = [ { host = [ hostnames.mathesar ]; } ]; + handle = [ + { + handler = "reverse_proxy"; + upstreams = [ { dial = "localhost:${builtins.toString cfg.port}"; } ]; + } + ]; + } + ]; + + # Configure Cloudflare DNS to point to this machine + services.cloudflare-dyndns.domains = [ hostnames.mathesar ]; + + }; +} diff --git a/platforms/nixos/modules/nmasur/profiles/communications.nix b/platforms/nixos/modules/nmasur/profiles/communications.nix index 9d0b968..6d8a934 100644 --- a/platforms/nixos/modules/nmasur/profiles/communications.nix +++ b/platforms/nixos/modules/nmasur/profiles/communications.nix @@ -28,11 +28,12 @@ in grafana.enable = lib.mkDefault true; influxdb2.enable = lib.mkDefault true; litestream.enable = lib.mkDefault true; - pgweb.enable = lib.mkDefault true; + mathesar.enable = lib.mkDefault true; minecraft-server.enable = lib.mkDefault true; n8n.enable = lib.mkDefault true; nix-autoupgrade.enable = lib.mkDefault false; # On by default for communications ntfy-sh.enable = lib.mkDefault true; + pgweb.enable = lib.mkDefault true; postgresql.enable = lib.mkDefault true; thelounge.enable = lib.mkDefault true; uptime-kuma.enable = lib.mkDefault true;