diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..3550a30 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.gitignore b/.gitignore index 87a3018..afcb08d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ result *.qcow2 +.direnv diff --git a/README.md b/README.md index e587817..4fde771 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,46 @@ +# Erpnext on NixOS + +### Cachix +Using the https://pub-solar.cachix.org binary cache: +``` +cachix use pub-solar +``` +Or manually add the following lines to your `~/.config/nix/nix.conf`: +``` +substituters = https://cache.nixos.org/ https://pub-solar.cachix.org +trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= pub-solar.cachix.org-1:ZicXIxKgdxMtgSJECWR8iihZxHRvu8ObL4n2cuBmtos= +``` + +Pushing to the https://pub-solar.cachix.org binary cache (in this example, we push the +package `run-erpnext` and its dependencies): +``` +nix develop +nix build --json .#run-erpnext \ + | jq -r '.[].outputs | to_entries[].value' \ + | cachix push pub-solar +``` + +### NixOS VM +``` +nix build '.#nixosConfigurations.test-vm.config.system.build.vm' +./result/bin/run-nixos-vm + +# In the VM, use root & empty password to login +# Watch erpnext startup: +# journalctl -fu erpnext.service +# Open http://localhost:8081 in your browser +# User: Administrator +# Password: admin +``` + +### Docker ``` docker run -d --name erpnext-redis-socketio -p 12311:6379 redis:latest docker run -d --name erpnext-redis-queue -p 6379:6379 redis:latest docker run -d --name erpnext-db -p 3306:3306 -e MARIADB_ROOT_PASSWORD=password -e MARIADB_DATABASE=erpnext -e MARIADB_USER=erpnext -e MARIADB_PASSWORD=erpnext mariadb:latest --collation-server=utf8mb4_unicode_ci -nix build .#runErpNext -./result/bin/runErpNext +nix build .#run-erpnext +./result/bin/run-erpnext # new terminal nix shell nixpkgs#nginx @@ -13,3 +49,12 @@ nginx -c /tmp/erpnext/nginx-erpnext.conf -g "daemon off;" # User: Administrator # Password: admin ``` + +### Links: +- https://erpnext.com +- https://docs.erpnext.com/docs/v14/user/manual/en/setting-up +- https://discuss.frappe.io/t/installing-the-docker-image-on-a-local-machine-without-letsencrypt-so-we-can-access-it-with-http-localhost/87585/7 +- https://github.com/frappe/frappe_docker/blob/main/images/production/Containerfile +- https://github.com/frappe/bench +- https://github.com/frappe/erpnext +- https://github.com/frappe/frappe diff --git a/flake.lock b/flake.lock index cfc6cdb..980e0ea 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,28 @@ { "nodes": { + "devshell": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "systems": [ + "systems" + ] + }, + "locked": { + "lastModified": 1685972731, + "narHash": "sha256-VpwVUthxs3AFgvWxGTHu+KVDnS/zT3xkCtmjX2PjNQs=", + "owner": "numtide", + "repo": "devshell", + "rev": "6b2554d28d46bfa6e24b941e999a145760dad0e1", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, "nixpkgs": { "locked": { "lastModified": 1685655444, @@ -16,26 +39,10 @@ "type": "github" } }, - "pip2nix": { - "flake": false, - "locked": { - "lastModified": 1681921436, - "narHash": "sha256-jrUOMhpOFxrgCXNgMWz475nKZdMiSQb0+nbaIqQORSM=", - "owner": "nix-community", - "repo": "pip2nix", - "rev": "2ad8bd4c841116a1b9ab7853a53ed2d38e0ab93f", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "pip2nix", - "type": "github" - } - }, "root": { "inputs": { + "devshell": "devshell", "nixpkgs": "nixpkgs", - "pip2nix": "pip2nix", "systems": "systems" } }, diff --git a/flake.nix b/flake.nix index 1feac9d..c582151 100644 --- a/flake.nix +++ b/flake.nix @@ -2,42 +2,71 @@ description = "Dev Setup"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - inputs.systems.url = "github:nix-systems/default"; - inputs.pip2nix = { - url = "github:nix-community/pip2nix"; - flake = false; - }; - outputs = {nixpkgs, systems, pip2nix, ...}: + inputs.systems.url = "github:nix-systems/default"; + + inputs.devshell.url = "github:numtide/devshell"; + inputs.devshell.inputs.nixpkgs.follows = "nixpkgs"; + inputs.devshell.inputs.systems.follows = "systems"; + + outputs = {self, nixpkgs, systems, devshell }: let eachSystem = nixpkgs.lib.genAttrs (import systems); - pkgs = import nixpkgs { - system = "x86_64-linux"; - overlays = [ - (import ./python-overlay.nix) - (import ./overlay.nix) - ]; - }; + # Nixpkgs instantiated for system types in nix-systems + nixpkgsFor = eachSystem (system: + import nixpkgs { + inherit system; + overlays = [ + self.overlays.default + self.overlays.pythonOverlay + devshell.overlays.default + ]; + } + ); in { - packages = eachSystem (system: { - devEnv = pkgs.buildEnv { - name = "erpnext-nix-dev-env"; - paths = [ - pkgs.dasel + overlays = { + default = (import ./overlay.nix); + pythonOverlay = (import ./python-overlay.nix); + }; + devShells = eachSystem (system: + let + pkgs = nixpkgsFor.${system}; + in + { + default = pkgs.devshell.mkShell { + # Add additional packages you'd like to be available in your devshell + # PATH here + devshell.packages = with pkgs; [ ]; + commands = [ + { + help = pkgs.cachix.meta.description; + name = pkgs.cachix.pname; + package = pkgs.cachix; + } + ]; + bash.extra = '' + ''; }; - inherit pkgs; - run-erpnext = pkgs.run-erpnext; - pip2nix = import "${pip2nix}/default.nix" { inherit pkgs; pythonPackages = "python310Packages"; }; - erpnext = pkgs.python3-erpnext.pkgs.erpnext; - bench = pkgs.python3-erpnext.pkgs.bench; - pythonPkgs = pkgs.python3-erpnext.pkgs; }); - nixosConfigurations = { + packages = eachSystem (system: + let + pkgs = nixpkgsFor.${system}; + in + { + run-erpnext = pkgs.run-erpnext; + erpnext = pkgs.python3.pkgs.erpnext; + bench = pkgs.python3.pkgs.bench; + }); + nixosConfigurations = + let + system = "x86_64-linux"; + pkgs = nixpkgsFor.${system}; + in + { test-vm = nixpkgs.lib.nixosSystem { - inherit pkgs; - system = "x86_64-linux"; + inherit system pkgs; modules = [./test-vm/configuration.nix]; }; }; diff --git a/overlay.nix b/overlay.nix index 175cfcf..773554a 100644 --- a/overlay.nix +++ b/overlay.nix @@ -1,7 +1,7 @@ -self: super: { - run-erpnext = self.callPackage ./scripts/run-erpnext.nix {}; - frappe-erpnext-assets = self.callPackage ./node/frappe-erpnext-assets.nix {}; - erpnext-app = self.callPackage ./node/erpnext-app.nix {}; - frappe-app = self.callPackage ./node/frappe-app.nix {}; - erpnext-nginx-conf = self.callPackage ./nginx-erpnext-conf.nix {}; +final: prev: { + run-erpnext = final.callPackage ./scripts/run-erpnext.nix {}; + frappe-erpnext-assets = final.callPackage ./node/frappe-erpnext-assets.nix {}; + erpnext-app = final.callPackage ./node/erpnext-app.nix {}; + frappe-app = final.callPackage ./node/frappe-app.nix {}; + erpnext-nginx-conf = final.callPackage ./nginx-erpnext-conf.nix {}; } diff --git a/python-overlay.nix b/python-overlay.nix index fa9905c..41610e5 100644 --- a/python-overlay.nix +++ b/python-overlay.nix @@ -1,111 +1,50 @@ -self: super: { - python3-erpnext = super.python3.override { - packageOverrides = pyself: pysuper: { - bench = pyself.callPackage ./python/bench.nix {}; - erpnext = pyself.callPackage ./python/erpnext.nix {}; - frappe = pyself.callPackage ./python/frappe.nix {}; +final: prev: { + python3 = prev.python3.override { + packageOverrides = pyFinal: pyPrev: { + bench = pyFinal.callPackage ./python/bench.nix {}; + erpnext = pyFinal.callPackage ./python/erpnext.nix {}; + frappe = pyFinal.callPackage ./python/frappe.nix {}; - email-reply-parser = pyself.callPackage ./python/email-reply-parser.nix {}; - git-url-parse = pyself.callPackage ./python/git-url-parse.nix {}; - gocardless-pro = pyself.callPackage ./python/gocardless-pro.nix {}; - honcho = pyself.callPackage ./python/honcho.nix {}; - jsonobject = pyself.callPackage ./python/jsonobject.nix {}; - maxminddb-geolite2 = pyself.callPackage ./python/maxminddb-geolite2.nix {}; - posthog = pyself.callPackage ./python/posthog.nix {}; - psycopg2-binary = pyself.callPackage ./python/psycopg2-binary.nix {}; - pypdf2 = pyself.callPackage ./python/pypdf2.nix {}; - pypika = pyself.callPackage ./python/pypika.nix {}; - python-youtube = pyself.callPackage ./python/python-youtube.nix {}; - rauth = pyself.callPackage ./python/rauth.nix {}; - redisearch = pyself.callPackage ./python/redisearch.nix {}; - rejson = pyself.callPackage ./python/rejson.nix {}; - taxjar = pyself.callPackage ./python/taxjar.nix {}; - traceback-with-variables = pyself.callPackage ./python/traceback-with-variables.nix {}; + email-reply-parser = pyFinal.callPackage ./python/email-reply-parser.nix {}; + git-url-parse = pyFinal.callPackage ./python/git-url-parse.nix {}; + gocardless-pro = pyFinal.callPackage ./python/gocardless-pro.nix {}; + honcho = pyFinal.callPackage ./python/honcho.nix {}; + jsonobject = pyFinal.callPackage ./python/jsonobject.nix {}; + maxminddb-geolite2 = pyFinal.callPackage ./python/maxminddb-geolite2.nix {}; + posthog = pyFinal.callPackage ./python/posthog.nix {}; + psycopg2-binary = pyFinal.callPackage ./python/psycopg2-binary.nix {}; + pypdf2 = pyFinal.callPackage ./python/pypdf2.nix {}; + pypika = pyFinal.callPackage ./python/pypika.nix {}; + python-youtube = pyFinal.callPackage ./python/python-youtube.nix {}; + rauth = pyFinal.callPackage ./python/rauth.nix {}; + redisearch = pyFinal.callPackage ./python/redisearch.nix {}; + rejson = pyFinal.callPackage ./python/rejson.nix {}; + taxjar = pyFinal.callPackage ./python/taxjar.nix {}; + traceback-with-variables = pyFinal.callPackage ./python/traceback-with-variables.nix {}; - #barcodenumber = pysuper.barcodenumber.overridePythonAttrs (oldAttrs: (rec { - # version = "0.5.0"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-VZfHLwSF9aDoy5L1x4O2mu8/f2ijYKgyjCrQ1KKY5Ho="; - # }; - #})); - bleach = pysuper.bleach.overridePythonAttrs (oldAttrs: (rec { + bleach = pyPrev.bleach.overridePythonAttrs (oldAttrs: (rec { version = "3.3.1"; - src = pysuper.fetchPypi { + src = pyPrev.fetchPypi { inherit version; inherit (oldAttrs) pname; sha256 = "sha256-MGSDpal5VHQWCtV/zj3dG1BVHpge7Y4VpYLTTO8oqvo="; }; })); - #phonenumbers = pysuper.phonenumbers.overridePythonAttrs (oldAttrs: (rec { - # version = "8.12.40"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-APKVWkVrRY+barDSQykEnD5zWMRN/Bl5/kkIztQPHrg="; - # }; - #})); - plaid-python = pysuper.plaid-python.overridePythonAttrs (oldAttrs: (rec { + plaid-python = pyPrev.plaid-python.overridePythonAttrs (oldAttrs: (rec { version = "7.2.1"; - src = pysuper.fetchPypi { + src = pyPrev.fetchPypi { inherit version; inherit (oldAttrs) pname; sha256 = "sha256-ryrTJug3fIyG2XGE9gwL5BzXH1B1IB39szMcyF1N5RM="; }; - propagatedBuildInputs = [ pysuper.requests ]; - checkInputs = [ pysuper.pytest ]; + propagatedBuildInputs = [ pyPrev.requests ]; + checkInputs = [ pyPrev.pytest ]; # Integration tests require API keys and internet access - checkPhase = "py.test -rxs ./tests/unit"; + checkPhase = "pyPrev.test -rxs ./tests/unit"; })); - #pycountry = pysuper.pycountry.overridePythonAttrs (oldAttrs: (rec { - # version = "20.7.3"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-gQhKU9NFQ0TAKS3uvCD80KFIjBNtSQAxLL1GXPVSy0I="; - # }; - #})); - #pymysql = pysuper.pymysql.overridePythonAttrs (oldAttrs: (rec { - # version = "1.0.3"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-PdqUPvNpQGinXWnQcXVdvsrO4a35ofxbIGgw0rZ9Jeg="; - # }; - # format = "pyproject"; - # buildInputs = [pysuper.setuptools]; - #})); - #pypng = pysuper.pypng.overridePythonAttrs (oldAttrs: (rec { - # version = "0.20220715.0"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-c5xDO6lvB4MV3lTA25da7lN8vD4dCuTtmqsMoeQn4sE="; - # }; - #})); - #pytz = pysuper.pytz.overridePythonAttrs (oldAttrs: (rec { - # version = "2022.1"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-HnYOL+aoFjvAs9mhnE+ENCr6Cir/6/qoSwG5eKAuyqc="; - # }; - #})); - #redis = pysuper.redis.overridePythonAttrs (oldAttrs: (rec { - # version = "3.5.3"; - # src = pysuper.fetchPypi { - # inherit version; - # inherit (oldAttrs) pname; - # sha256 = "sha256-Dn4M/KhmDeqLfVzYxPbF4p4R8xFYwLCukaOX8A5aBaI="; - # }; - # pythonImportsCheck = []; - # # tests require a running redis - # doCheck = false; - #})); - tweepy = pysuper.tweepy.overridePythonAttrs (oldAttrs: (rec { + tweepy = pyPrev.tweepy.overridePythonAttrs (oldAttrs: (rec { version = "3.10.0"; - src = pysuper.fetchPypi { + src = pyPrev.fetchPypi { inherit version; inherit (oldAttrs) pname; sha256 = "sha256-duaVS4BspHDdqHf1fbh5L/8GoL66DtQ+/DgFdx458Go="; @@ -113,14 +52,6 @@ self: super: { doCheck = false; pythonImportsCheck = []; })); - #unidecode = pysuper.unidecode.overridePythonAttrs (oldAttrs: (rec { - # version = "1.2.0"; - # src = pysuper.fetchPypi { - # inherit version; - # pname = "Unidecode"; - # sha256 = "sha256-jXOpfTh6lWkiNE9rdCQ8LGdxWUZZd4dEstvarY9rcn0="; - # }; - #})); }; }; } diff --git a/python/erpnext.nix b/python/erpnext.nix index 14fb430..0a66505 100644 --- a/python/erpnext.nix +++ b/python/erpnext.nix @@ -53,10 +53,4 @@ buildPythonPackage rec { python-stdnum frappe ]; - #nativeCheckInputs = [ - # bench - #]; - #checkPhase = '' - # bench --site test_site run-tests --app erpnext - #''; } diff --git a/python/frappe.nix b/python/frappe.nix index 0ec195d..12636bd 100644 --- a/python/frappe.nix +++ b/python/frappe.nix @@ -208,17 +208,4 @@ buildPythonPackage rec { google-auth posthog ]; - - #nativeCheckInputs = [ - # bench - # redis - #]; - #checkPhase = '' - # tmp=$(mktemp -d) - # cd $tmp - # bench -v init frappe-bench --skip-assets - # cd frappe-bench - # mkdir -p sites/test_site - # bench --site test_site run-tests --app frappe - #''; } diff --git a/test-vm/configuration.nix b/test-vm/configuration.nix index 7027cdf..4b787f6 100644 --- a/test-vm/configuration.nix +++ b/test-vm/configuration.nix @@ -128,11 +128,11 @@ systemd.services.erpnext = let - penv = pkgs.python3-erpnext.buildEnv.override { + penv = pkgs.python3.buildEnv.override { extraLibs = [ - pkgs.python3-erpnext.pkgs.frappe - pkgs.python3-erpnext.pkgs.erpnext - pkgs.python3-erpnext.pkgs.bench + pkgs.python3.pkgs.frappe + pkgs.python3.pkgs.erpnext + pkgs.python3.pkgs.bench ]; }; appsFile = pkgs.writeText "erpnext-apps.txt" '' @@ -166,7 +166,7 @@ packages = [ pkgs.mariadb-client pkgs.nodejs penv ]; }; script = '' - export PYTHON_PATH=${penv}/${pkgs.python3-erpnext.sitePackages} + export PYTHON_PATH=${penv}/${pkgs.python3.sitePackages} export PATH="${pkgs.mariadb-client}/bin:${pkgs.nodejs}/bin:${penv}/bin:$PATH" # Upstream initializes the DB with this command