package mathesar and run as service

This commit is contained in:
Noah Masur 2025-05-05 20:36:27 +00:00
parent e803e6a02a
commit b3a7b280b5
6 changed files with 429 additions and 1 deletions

View File

@ -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}";

295
pkgs/mathesar/package.nix Normal file
View File

@ -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 <<EOF > $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 <<EOF > $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 <<EOF > $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
'';
}

View File

@ -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-----

View File

@ -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-----

View File

@ -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 ];
};
}

View File

@ -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;