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 new file mode 100644 index 0000000..25e9f70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.direnv/ +/.pre-commit-config.yaml +/result* diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c27951 --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# haqq.nix + +This repository aims to provide Nix and NixOS entrypoints for the Haqq +ecosystem. + +> [!WARNING] +> We only support `x86_64-linux` target. Support for anything over than that is +> not planned at this point in time. Contributions are welcome. + +## Quick Start + +> [!NOTE] +> A user must be somewhat proficient with Nix and NixOS. If not, please consider +> following our [official installation +> instructions](https://docs.haqq.network/network/run-node/). + +Add `haqq.nix` to your `flake.nix` and import desired module. For example: + +``` nix +{ + description = "My Haqq node"; + inputs = { + haqq.url = "github:haqq-network/haqq.nix"; + nixpkgs.follows = "haqq"; + }; + outputs = inputs: { + nixosConfigurations.myHaqqNode = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ + inputs.haqq.nixosModules.haqqd + ({ pkgs, ... }: { + services.haqqd = { + enable = true; + settings = { + app = { + pruning = "custom"; + pruning-interval = 10; + pruning-keep-recent = 30000; + min-retain-blocks = 30000; + api = { + enable = true; + address = "tcp://0.0.0.0:1317"; + }; + }; + config = { + moniker = "my-haqq-node"; + p2p.laddr = "tcp://0.0.0.0:26656"; + rpc.laddr = "tcp://0.0.0.0:26657"; + } + }; + extraPreStartup = '' + if [ ! -f "$DAEMON_HOME/.bootstrapped" ]; then + index="https://pub-70119b7efa294225aa1b869b2a15c7f4.r2.dev/index.json" + snapshot="$(curl -s "$index" | jq -r .pruned[0].link)" + wget -qO- "$snapshot" | \ + lz4 -d - | \ + tar -C "$DAEMON_HOME" -x -f - + fi + ''; + }; + systemd.services.haqqd.path = with pkgs; [ curl jq wget lz4 gnutar ]; + }) + ]; + }; + }; +} +``` + +This will enable and launch a `haqqd.service` systemd service, which will +download a latest pruned snapshot on the first start. You can look up available +options in the source code for the module. + +We plan on adding more documentation and guides in the future. + +## License + +[Apache 2.0](./LICENSE) diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..a1ed186 --- /dev/null +++ b/flake.lock @@ -0,0 +1,58 @@ +{ + "nodes": { + "flake-parts": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib" + }, + "locked": { + "lastModified": 1733312601, + "narHash": "sha256-4pDvzqnegAfRkPwO3wmwBhVi/Sye1mzps0zHWYnP88c=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "205b12d8b7cd4802fbcb8e8ef6a0f1408781a4f9", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1735304618, + "narHash": "sha256-Fs+lNYmpj/1U8SmLJtUmtqr1MeQhKcLDoF9m4E3+7Ig=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "3f02dc2866b6a8f2f5ecde6a7632bb3aa82b26a3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "release-24.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs-lib": { + "locked": { + "lastModified": 1733096140, + "narHash": "sha256-1qRH7uAUsyQI7R1Uwl4T+XvdNv778H0Nb5njNrqvylY=", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + }, + "original": { + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/5487e69da40cbd611ab2cadee0b4637225f7cfae.tar.gz" + } + }, + "root": { + "inputs": { + "flake-parts": "flake-parts", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..7e2ec2a --- /dev/null +++ b/flake.nix @@ -0,0 +1,17 @@ +{ + description = "haqq.nix"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/release-24.11"; + + flake-parts.url = "github:hercules-ci/flake-parts"; + }; + + outputs = + inputs: + inputs.flake-parts.lib.mkFlake { inherit inputs; } { + systems = [ "x86_64-linux" ]; + + imports = [ ./nix ]; + }; +} diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..7b73ab5 --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,9 @@ +_: { + imports = [ + ./modules + ./overlays.nix + ./packages + ./partitions + ./tests + ]; +} diff --git a/nix/modules/default.nix b/nix/modules/default.nix new file mode 100644 index 0000000..eb9a5c3 --- /dev/null +++ b/nix/modules/default.nix @@ -0,0 +1,11 @@ +{ config, inputs, ... }: +{ + flake.nixosModules = { + default = config.flake.nixosModules.haqqd; + + haqqd = { + nixpkgs.overlays = [ inputs.self.overlays.default ]; + imports = [ ./haqqd ]; + }; + }; +} diff --git a/nix/modules/haqqd/default.nix b/nix/modules/haqqd/default.nix new file mode 100644 index 0000000..2a184c9 --- /dev/null +++ b/nix/modules/haqqd/default.nix @@ -0,0 +1,369 @@ +{ + config, + lib, + options, + pkgs, + ... +}: +let + cfg = config.services.haqqd; + + toml = pkgs.formats.toml { }; +in +{ + options.services.haqqd = { + enable = lib.mkEnableOption "shariah-compliant Web3 platform daemon"; + + packages = { + upgrades = lib.mkOption { + type = with lib.types; listOf package; + default = lib.mapAttrsToList (_: package: package) pkgs.haqqPackages; + description = '' + A list of packages to be used by Cosmovisor for upgrades. By default + includes all available releases. + ''; + }; + + genesis = lib.mkOption { + type = lib.types.package; + default = lib.head cfg.packages.upgrades; + description = '' + A package that will be used for the initial start of the node. Managed + by Cosmovisor. + ''; + }; + + config = lib.mkOption { + type = lib.types.package; + default = lib.last cfg.packages.upgrades; + description = '' + A package that will be used for node configuration. You probably will + never have to change this. + ''; + }; + }; + + enableMutableSettings = lib.mkEnableOption '' + copying configuration files from the store instead of linking them. This + can be enabled if there's a need to dynamically alter these files. For + example to calculate block and hash to initialise the daemon from state + sync. + ''; + + settings = { + app = lib.mkOption { + inherit (toml) type; + default = { }; + description = '' + User-defined configuration for $DAEMON_HOME/config/app.toml. This will + override any default values from that file. + ''; + example = { + pruning = "custom"; + pruning-interval = 10; + pruning-keep-recent = 30000; + min-retain-blocks = 30000; + api = { + enable = true; + address = "tcp://0.0.0.0:1317"; + }; + json-rpc = { + enable = true; + address = "0.0.0.0:8545"; + ws-address = "0.0.0.0:8546"; + }; + }; + }; + + config = lib.mkOption { + inherit (toml) type; + default = { }; + description = '' + User-defined configuration for $DAEMON_HOME/config/config.toml. This + will override any default values from that file. + ''; + example = { + p2p = { + laddr = "tcp://0.0.0.0:26656"; + seeds = ""; + persistent_peers = ""; + pex = false; + }; + rpc.laddr = "tcp://0.0.0.0:26657"; + statesync = { + enable = true; + rpc_servers = "https://rpc.tm.haqq.network:443,https://rpc.haqq.sh:443"; + }; + instrumentation = { + prometheus = true; + prometheus_listen_addr = "127.0.0.1:26660"; + }; + }; + }; + + client = lib.mkOption { + inherit (toml) type; + default = { }; + description = '' + User-defined configuration for $DAEMON_HOME/config/client.toml. This + will override any default values from that file. + ''; + example = { + chain-id = "haqq_11235-1"; + node = "tcp://127.0.0.1:26657"; + }; + }; + }; + + finalSettings = + let + generateTOML = + name: + toml.generate "${name}.toml" ( + lib.recursiveUpdate (lib.importTOML "${cfg.packages.config}/share/haqqd/config/${name}.toml") + cfg.settings.${name} + ); + in + { + app = lib.mkOption { + inherit (options.services.haqqd.settings.app) type; + default = generateTOML "app"; + readOnly = true; + description = '' + Final derivation for $DAEMON_HOME/config/app.toml. This fill will be + used by the service. + ''; + }; + + config = lib.mkOption { + inherit (options.services.haqqd.settings.config) type; + default = generateTOML "config"; + readOnly = true; + description = '' + Final derivation for $DAEMON_HOME/config/config.toml. This fill will + be used by the service. + ''; + }; + + client = lib.mkOption { + inherit (options.services.haqqd.settings.client) type; + default = generateTOML "client"; + readOnly = true; + description = '' + Final derivation for $DAEMON_HOME/config/client.toml. This fill will + be used by the service. + ''; + }; + }; + + extraPreStartup = lib.mkOption { + type = lib.types.lines; + default = ""; + description = '' + Extra commands to be executed during early stages of startup. Don't + forget to make configuration files mutable before trying to change them. + ''; + example = lib.literalExpression '' + if [ ! -f "$DAEMON_HOME/.bootstrapped" ]; then + snapshot="$(curl -s "https://pub-70119b7efa294225aa1b869b2a15c7f4.r2.dev/index.json" | jq -r .pruned[0].link)" + wget -qO- "$snapshot" | \ + lz4 -d - | \ + tar -C "$DAEMON_HOME" -x -f - + fi + ''; + }; + + extraPostStartup = lib.mkOption { + type = lib.types.lines; + default = ""; + description = '' + Extra commands to be executed during late stages of startup. Don't + forget to make configuration files mutable before trying to change them. + ''; + example = lib.literalExpression '' + if [ ! -f "$DAEMON_HOME/.bootstrapped" ]; then + height="$(curl -s "https://rpc.tm.haqq.network/block" | jq -r '.result.block.header.height')" + trust_height="$((height - 3000))" + trust_hash="$(curl -s "https://rpc.tm.haqq.network/block?height=$trust_height" | jq -r '.result.block_id.hash')" + + jq -nR \ + --arg trust_height "$trust_height" \ + --arg trust_hash "$trust_hash" \ + '{trust_height: $trust_height, trust_hash: $trust_hash}' \ + >"$DAEMON_HOME/state-sync.json" + else + trust_height="$(jq -r .trust_height <"$DAEMON_HOME/state-sync.json")" + trust_hash="$(jq -r .trust_hash <"$DAEMON_HOME/state-sync.json")" + fi + + dasel put -f "$DAEMON_HOME/config/config.toml" \ + -t int -v "$trust_height" statesync.trust_height + dasel put -f "$DAEMON_HOME/config/config.toml" \ + -t string -v "$trust_hash" statesync.trust_hash + ''; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "haqqd"; + description = '' + User that will run the service. + ''; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "haqqd"; + description = '' + Group that will run the service. + ''; + }; + + stateDirectory = lib.mkOption { + type = with lib.types; nullOr str; + default = "/var/lib/haqqd"; + description = '' + A directory to hold service state. For cosmovisor and haqqd to work it + will be treated as a $HOME directory. If set to null, the systemd + service will run as a DynamicUser. + ''; + }; + }; + + config = lib.mkIf cfg.enable { + users = { + users = lib.mkIf (cfg.user == "haqqd") { + ${cfg.user} = { + isSystemUser = true; + home = if cfg.stateDirectory != null then cfg.stateDirectory else "/var/empty"; + createHome = cfg.stateDirectory != null; + inherit (cfg) group; + }; + }; + + groups = lib.mkIf (cfg.group == "haqqd") { ${cfg.group} = { }; }; + }; + + environment.systemPackages = [ + (pkgs.writeScriptBin "haqqctl" '' + exec systemd-run \ + --quiet \ + --pipe \ + --pty \ + --wait \ + --collect \ + --service-type=exec \ + --property=User=${cfg.user} \ + -- \ + ${lib.getExe cfg.packages.config} "$@" + '') + ]; + + systemd.services.haqqd = { + path = [ cfg.packages.config ]; + + preStart = '' + set -euxo pipefail + + mkdir -p "$DAEMON_HOME" + + ${cfg.extraPreStartup} + + if [ ! -f "$DAEMON_HOME/.bootstrapped" ]; then + haqqd init ${ + if lib.hasAttr "moniker" cfg.settings.config then cfg.settings.config.moniker else "haqqd" + } --chain-id "${ + if lib.hasAttr "chain-id" cfg.settings.client then cfg.settings.client.chain-id else "haqq_11235-1" + }" + fi + + ${ + if cfg.enableMutableSettings then + '' + cp -vf ${cfg.finalSettings.app} "$DAEMON_HOME/config/app.toml" + cp -vf ${cfg.finalSettings.config} "$DAEMON_HOME/config/config.toml" + cp -vf ${cfg.finalSettings.client} "$DAEMON_HOME/config/client.toml" + '' + else + '' + ln -vfs ${cfg.finalSettings.app} "$DAEMON_HOME/config/app.toml" + ln -vfs ${cfg.finalSettings.config} "$DAEMON_HOME/config/config.toml" + ln -vfs ${cfg.finalSettings.client} "$DAEMON_HOME/config/client.toml" + '' + } + + mkdir -p "$DAEMON_HOME/cosmovisor/genesis/bin" + ln -sf "${cfg.packages.genesis}/bin/haqqd" "$DAEMON_HOME/cosmovisor/genesis/bin/haqqd" + ${lib.concatMapStrings ( + package: + let + base = "$DAEMON_HOME/cosmovisor/upgrades/v${package.version}/bin"; + in + '' + mkdir -p "${base}" + ln -sf "${package}/bin/haqqd" "${base}/haqqd" + '' + ) cfg.packages.upgrades} + + ${cfg.extraPostStartup} + + touch "$DAEMON_HOME/.bootstrapped" + ''; + + script = '' + exec ${lib.getExe pkgs.cosmovisor} run start + ''; + + environment = rec { + HOME = if cfg.stateDirectory != null then cfg.stateDirectory else "/var/lib/haqqd"; + DAEMON_NAME = "haqqd"; + DAEMON_HOME = "${HOME}/.haqqd"; + DAEMON_ALLOW_DOWNLOAD_BINARIES = "false"; + DAEMON_RESTART_AFTER_UPGRADE = "true"; + COSMOVISOR_COLOR_LOGS = "false"; + UNSAFE_SKIP_BACKUP = "true"; + }; + + serviceConfig = { + User = cfg.user; + Group = cfg.group; + + DynamicUser = cfg.stateDirectory == null; + StateDirectory = if cfg.stateDirectory != null then cfg.stateDirectory else "haqqd"; + + Restart = "always"; + RestartSec = 5; + + AmbientCapabilities = [ "" ]; + CapabilityBoundingSet = [ "" ]; + LockPersonality = true; + MemoryDenyWriteExecute = false; # This is required for the application to work. + NoNewPrivileges = true; + PrivateDevices = true; + PrivateTmp = true; + PrivateUsers = true; + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = true; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "invisible"; + ProtectSystem = "full"; + RemoveIPC = true; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + ]; + RestrictRealtime = true; + RestrictSUIDSGID = true; + SystemCallArchitectures = "native"; + }; + + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network-online.target" ]; + }; + }; +} diff --git a/nix/overlays.nix b/nix/overlays.nix new file mode 100644 index 0000000..39b554f --- /dev/null +++ b/nix/overlays.nix @@ -0,0 +1,10 @@ +{ inputs, ... }: +{ + imports = [ inputs.flake-parts.flakeModules.easyOverlay ]; + + perSystem = + { config, ... }: + { + overlayAttrs = config.legacyPackages; + }; +} diff --git a/nix/packages/cosmovisor.nix b/nix/packages/cosmovisor.nix new file mode 100644 index 0000000..58b9a49 --- /dev/null +++ b/nix/packages/cosmovisor.nix @@ -0,0 +1,45 @@ +{ + buildGoModule, + fetchFromGitHub, + installShellFiles, +}: +buildGoModule rec { + pname = "cosmovisor"; + version = "1.7.0"; + + src = fetchFromGitHub { + owner = "cosmos"; + repo = "cosmos-sdk"; + rev = "refs/tags/cosmovisor/v${version}"; + hash = "sha256-3+yQTka62jiZ0asgzrj+43EE4E2NODQAr5vfoyYcOuc="; + }; + sourceRoot = "${src.name}/tools/cosmovisor"; + + vendorHash = "sha256-lyJgsVSX41RPzTVRCwnWYt2MxxrjvvbkOlXZ9kK/Xek="; + + nativeBuildInputs = [ installShellFiles ]; + + subPackages = [ "cmd/cosmovisor" ]; + + ldflags = [ + "-w" + "-s" + ]; + + postInstall = '' + installShellCompletion --cmd cosmovisor \ + --bash <($out/bin/cosmovisor completion bash) \ + --fish <($out/bin/cosmovisor completion fish) \ + --zsh <($out/bin/cosmovisor completion zsh) + ''; + + meta = { + description = '' + Cosmovisor is a process manager for Cosmos SDK application binaries that + automates application binary switch at chain upgrades + ''; + homepage = "https://docs.cosmos.network/main/build/tooling/cosmovisor"; + changelog = "https://github.com/cosmos/cosmos-sdk/blob/tools/cosmovisor/v${version}/tools/cosmovisor/CHANGELOG.md"; + mainProgram = "cosmovisor"; + }; +} diff --git a/nix/packages/default.nix b/nix/packages/default.nix new file mode 100644 index 0000000..58cb20b --- /dev/null +++ b/nix/packages/default.nix @@ -0,0 +1,10 @@ +_: { + perSystem = + { pkgs, ... }: + { + legacyPackages = { + cosmovisor = pkgs.callPackage ./cosmovisor.nix { }; + haqqPackages = pkgs.callPackages ./haqq { }; + }; + }; +} diff --git a/nix/packages/haqq/default.nix b/nix/packages/haqq/default.nix new file mode 100644 index 0000000..5a0b1a2 --- /dev/null +++ b/nix/packages/haqq/default.nix @@ -0,0 +1,19 @@ +{ callPackage, lib, ... }: +let + versions = builtins.fromJSON (builtins.readFile ./versions.json); + + latestVersion = lib.last (builtins.sort lib.versionOlder (builtins.attrNames versions)); + + fullName = version: "haqq_${builtins.replaceStrings [ "." ] [ "_" ] version}"; + + packages = lib.mapAttrs' ( + version: attr: + lib.nameValuePair (fullName version) ( + callPackage ./derivation.nix { + inherit version; + inherit (attr) url hash; + } + ) + ) versions; +in +packages // { haqq = builtins.getAttr (fullName latestVersion) packages; } diff --git a/nix/packages/haqq/derivation.nix b/nix/packages/haqq/derivation.nix new file mode 100644 index 0000000..b0e2b78 --- /dev/null +++ b/nix/packages/haqq/derivation.nix @@ -0,0 +1,58 @@ +{ + fetchurl, + hash ? null, + lib, + stdenv, + url ? null, + version ? null, +}: +stdenv.mkDerivation { + pname = "haqq"; + inherit version; + + src = fetchurl { inherit url hash; }; + sourceRoot = "."; + + dontPatch = true; + dontConfigure = true; + dontBuild = true; + dontFixup = true; + + installPhase = '' + runHook preInstall + + patchelf \ + --set-interpreter "$(cat $NIX_CC/nix-support/dynamic-linker)" \ + --set-rpath "${lib.makeLibraryPath [ stdenv.cc.libc ]}" \ + bin/haqqd + install -Dm755 -t $out/bin bin/haqqd + + $out/bin/haqqd init default --home . --chain-id haqq_11235-1 + install -Dm644 -t $out/share/haqqd/config config/app.toml + install -Dm644 -t $out/share/haqqd/config config/client.toml + install -Dm644 -t $out/share/haqqd/config config/config.toml + + runHook postInstall + ''; + + passthru.updateScript = ./update.sh; + + meta = { + description = "Shariah-compliant Web3 platform"; + longDescription = '' + Haqq is a scalable, high-throughput Proof-of-Stake blockchain that is + fully compatible and interoperable with Ethereum. It's built using the + Cosmos SDK which runs on top of CometBFT consensus engine. Ethereum + compatibility allows developers to build applications on Haqq using the + existing Ethereum codebase and toolset, without rewriting smart contracts + that already work on Ethereum or other Ethereum-compatible networks. + Ethereum compatibility is done using modules built by Tharsis for their + Evmos network. + ''; + homepage = "https://haqq.network"; + license = lib.licenses.asl20; + platforms = [ "x86_64-linux" ]; + sourceProvenance = [ lib.sourceTypes.binaryNativeCode ]; + mainProgram = "haqqd"; + }; +} diff --git a/nix/packages/haqq/update.sh b/nix/packages/haqq/update.sh new file mode 100755 index 0000000..0c664bc --- /dev/null +++ b/nix/packages/haqq/update.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env nix-shell +#!nix-shell -i bash -p coreutils curl jq nix + +set -euo pipefail + +get_version() { + jq -r '.tag_name' <<<"$1" | + sed 's/^v//' +} + +get_url() { + jq -r ' + .assets[]? | select( + .browser_download_url | + test(".*haqq_.*_(?i)linux_(amd64|x86_64).*") + ) | .browser_download_url + ' <<<"$1" +} + +get_hash() { + local type="sha256" + nix-hash \ + --to-sri \ + --type "$type" \ + "$(nix-prefetch-url --type "$type" "$1")" +} + +api="https://api.github.com/repos/haqq-network/haqq/releases" +result="$(curl --fail -s ${GITHUB_TOKEN:+-u ":$GITHUB_TOKEN"} "$api")" + +declare -a versions + +while read -r obj; do + version="$(get_version "$obj")" + url="$(get_url "$obj")" + hash="$(get_hash "$url")" + + versions+=( + "$( + jq -nc \ + --arg version "$version" \ + --arg url "$url" \ + --arg hash "$hash" \ + '{ $version: { url: $url, hash: $hash } }' + )" + ) +done < <(jq -c '.[]' <<<"$result") + +dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" +jq --slurp 'add' <<<"${versions[*]}" >"$dir/versions.json" diff --git a/nix/packages/haqq/versions.json b/nix/packages/haqq/versions.json new file mode 100644 index 0000000..f0276c7 --- /dev/null +++ b/nix/packages/haqq/versions.json @@ -0,0 +1,122 @@ +{ + "1.8.2": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.8.2/haqq_1.8.2_linux_amd64.tar.gz", + "hash": "sha256-bGGaQVebLgS7/uUWg3yAakqkAItSTWuZArrF70BN+bo=" + }, + "1.8.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.8.1/haqq_1.8.1_linux_amd64.tar.gz", + "hash": "sha256-0H69u03/cPiYSQnjrcKKbFTYBy6/RSF6ilbKWu+7Mws=" + }, + "1.8.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.8.0/haqq_1.8.0_linux_amd64.tar.gz", + "hash": "sha256-6E0FzAix0EzoNr4Km+D2bskgucu3KC23maKOqtzrbkA=" + }, + "1.7.8": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.8/haqq_1.7.8_linux_amd64.tar.gz", + "hash": "sha256-xDw218t5OeIwi6sl5Q1G0ZYNO4d+GHx6Fx1kyvFAk0I=" + }, + "1.7.7": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.7/haqq_1.7.7_linux_amd64.tar.gz", + "hash": "sha256-A/hF/bGX8+s0ocoXQeDG7/wtGUahSRBNIpIvaHU/ewk=" + }, + "1.7.6": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.6/haqq_1.7.6_linux_amd64.tar.gz", + "hash": "sha256-z8IzTEbgzhVVOhIeVXM5ilokZwlnKKPDGKeR+iMBzEE=" + }, + "1.7.5": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.5/haqq_1.7.5_linux_amd64.tar.gz", + "hash": "sha256-uooV5vcVHKepiQhk9cYvGXg/UdlLCLF48XxFXkyJZKc=" + }, + "1.7.4": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.4/haqq_1.7.4_linux_amd64.tar.gz", + "hash": "sha256-wJbK9rOQqQSdD/jMb3TyMjytCF5DqoaCVJg9RdDIqOE=" + }, + "1.7.3": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.3/haqq_1.7.3_Linux_x86_64.tar.gz", + "hash": "sha256-itOV44cO5mFbtiTkeQicTXhxSRQvuaz/Lc/tBkICWRc=" + }, + "1.7.2": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.2/haqq_1.7.2_Linux_x86_64.tar.gz", + "hash": "sha256-I72BP9FdEGiOzrGk9z0bVH8czLO4lQqE7aDu2kp6MNY=" + }, + "1.7.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.1/haqq_1.7.1_Linux_x86_64.tar.gz", + "hash": "sha256-zntfani6ggsF5rPp0XQVERblONyYnxiph7/udn+MEEU=" + }, + "1.7.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.7.0/haqq_1.7.0_Linux_x86_64.tar.gz", + "hash": "sha256-rDSSHbh4JVBYOFCuGxaldICMKI6cuPuzO+6Jr8ijlkk=" + }, + "1.6.4": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.6.4/haqq_1.6.4_Linux_x86_64.tar.gz", + "hash": "sha256-/guHSnL6M50qgAD8lo932nJb9UZOygjxwVxNEXMpk3U=" + }, + "1.6.3": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.6.3/haqq_1.6.3_Linux_x86_64.tar.gz", + "hash": "sha256-XtYmBoZnQ4z1L8ooic6+Bjob5EALqsUGOg4ayeFPu3g=" + }, + "1.6.2": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.6.2/haqq_1.6.2_Linux_x86_64.tar.gz", + "hash": "sha256-GwiaqyO2YQS+4uaVptNdchJm8VezzGEdiIygwKwSyDM=" + }, + "1.6.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.6.1/haqq_1.6.1_Linux_x86_64.tar.gz", + "hash": "sha256-Exjbvn30u+xsH399WuMTJNKP3sihZTvyARgqAbG6Sl0=" + }, + "1.6.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.6.0/haqq_1.6.0_Linux_x86_64.tar.gz", + "hash": "sha256-P/02YCTEj6ul9vJ/dLmx5yCaRh9xHSYZnXSHek9ctnA=" + }, + "1.5.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.5.0/haqq_1.5.0_Linux_x86_64.tar.gz", + "hash": "sha256-uYp2NUIOvoskV2+5I6wlbdHNTb/jStHFoMJsOmtYLdA=" + }, + "1.4.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.4.1/haqq_1.4.1_Linux_x86_64.tar.gz", + "hash": "sha256-R6UA86gtxLyajn3afLAjqpP6pecMo9CbmPNTiCI8Cbo=" + }, + "1.4.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.4.0/haqq_1.4.0_Linux_x86_64.tar.gz", + "hash": "sha256-6gsnsYVBJzMee47rAygUmX3OIj3+KjQ2fEy6AJ7vYBA=" + }, + "1.3.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.3.1/haqq_1.3.1_Linux_x86_64.tar.gz", + "hash": "sha256-tNebjrWCD33XtbtrsCE3lVskayEuUKzS+NZSeTnY1Z8=" + }, + "1.3.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.3.0/haqq_1.3.0_Linux_x86_64.tar.gz", + "hash": "sha256-0QnYW7y2093TtwVA0SvPkyYh9+b9wI1KfOT+In8BpmA=" + }, + "1.1.9": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.1.9/haqq_1.1.9_Linux_x86_64.tar.gz", + "hash": "sha256-bT2jLGQwbF0Wx29M88hcSyb2gMib9Ah7se+Brxu0JnM=" + }, + "1.2.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.2.1/haqq_1.2.1_Linux_x86_64.tar.gz", + "hash": "sha256-wOfdTqN5Eij8FT+GOZgFM4kkvLlPqHjq6xsiW3yQYOM=" + }, + "1.2.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.2.0/haqq_1.2.0_Linux_x86_64.tar.gz", + "hash": "sha256-VzSO105TmzeyHt97aOx7XZn0X1VTm9H5KllxdHCZVGs=" + }, + "1.1.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.1.0/haqq_1.1.0_Linux_x86_64.tar.gz", + "hash": "sha256-o39kKYdPdK3OdbtS4mYtREviPGmYPBMq8Nd3b7BFHaA=" + }, + "1.0.3": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.0.3/haqq_1.0.3_Linux_x86_64.tar.gz", + "hash": "sha256-W36Nsq9M8kyxYGN4aVFt0ZzcTQwHu6f1TSlpYEBU2Ok=" + }, + "1.0.2": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.0.2/haqq_1.0.2_Linux_x86_64.tar.gz", + "hash": "sha256-FHpLIv4MlaSlwX1Rt3p1xtHyKaJQjMOS0MB7qERPb4Q=" + }, + "1.0.1": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.0.1/haqq_1.0.1_Linux_x86_64.tar.gz", + "hash": "sha256-eo7hM90PeS8bLs5DP7tZyQXUE8u5tDs7DWr73P5IM9I=" + }, + "1.0.0": { + "url": "https://github.com/haqq-network/haqq/releases/download/v1.0.0/haqq_1.0.0_Linux_x86_64.tar.gz", + "hash": "sha256-gDDpjZtNJ6OQGkjCkPSCi7vF3pLkqjpp16sEgq6m/cM=" + } +} diff --git a/nix/partitions/default.nix b/nix/partitions/default.nix new file mode 100644 index 0000000..315e864 --- /dev/null +++ b/nix/partitions/default.nix @@ -0,0 +1,14 @@ +{ inputs, ... }: +{ + imports = [ inputs.flake-parts.flakeModules.partitions ]; + + partitionedAttrs = { + checks = "dev"; + devShells = "dev"; + }; + + partitions.dev = { + extraInputsFlake = ./dev; + module.imports = [ ./dev/flake-module.nix ]; + }; +} diff --git a/nix/partitions/dev/flake-module.nix b/nix/partitions/dev/flake-module.nix new file mode 100644 index 0000000..4625f11 --- /dev/null +++ b/nix/partitions/dev/flake-module.nix @@ -0,0 +1,23 @@ +{ inputs, ... }: +{ + systems = [ "x86_64-linux" ]; + + imports = [ inputs.git-hooks-nix.flakeModule ]; + + perSystem = + { config, pkgs, ... }: + { + pre-commit.settings.hooks = { + convco.enable = true; + deadnix.enable = true; + nixfmt-rfc-style.enable = true; + statix.enable = true; + typos = { + enable = true; + pass_filenames = false; + }; + }; + + devShells.default = pkgs.mkShell { shellHook = config.pre-commit.installationScript; }; + }; +} diff --git a/nix/partitions/dev/flake.lock b/nix/partitions/dev/flake.lock new file mode 100644 index 0000000..9755839 --- /dev/null +++ b/nix/partitions/dev/flake.lock @@ -0,0 +1,85 @@ +{ + "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1696426674, + "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks-nix": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [], + "nixpkgs-stable": "nixpkgs-stable" + }, + "locked": { + "lastModified": 1734797603, + "narHash": "sha256-ulZN7ps8nBV31SE+dwkDvKIzvN6hroRY8sYOT0w+E28=", + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "f0f0dc4920a903c3e08f5bdb9246bb572fcae498", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks-nix", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs-stable": { + "locked": { + "lastModified": 1730741070, + "narHash": "sha256-edm8WG19kWozJ/GqyYx2VjW99EdhjKwbY3ZwdlPAAlo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d063c1dd113c91ab27959ba540c0d9753409edf3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "git-hooks-nix": "git-hooks-nix" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/nix/partitions/dev/flake.nix b/nix/partitions/dev/flake.nix new file mode 100644 index 0000000..c3784aa --- /dev/null +++ b/nix/partitions/dev/flake.nix @@ -0,0 +1,10 @@ +{ + inputs = { + git-hooks-nix = { + url = "github:cachix/git-hooks.nix"; + inputs.nixpkgs.follows = ""; + }; + }; + + outputs = _: { }; +} diff --git a/nix/tests/basic.nix b/nix/tests/basic.nix new file mode 100644 index 0000000..af92a0a --- /dev/null +++ b/nix/tests/basic.nix @@ -0,0 +1,120 @@ +{ + imports, + jq, + moreutils, + nixosTest, +}: +let + moniker = "haqqd-basic"; + chain-id = "haqq_11111-1"; +in +nixosTest { + name = "haqqd-basic"; + + nodes.server = _: { + inherit imports; + + services.haqqd = { + enable = true; + + enableMutableSettings = true; + settings = { + app = { + api = { + enable = true; + address = "tcp://0.0.0.0:1317"; + }; + grpc = { + enable = true; + address = "0.0.0.0:9090"; + }; + grpc-web = { + enable = true; + address = "0.0.0.0:9091"; + }; + json-rpc = { + enable = true; + address = "0.0.0.0:8545"; + ws-address = "0.0.0.0:8546"; + }; + }; + + config = { + inherit moniker; + p2p = { + laddr = "tcp://0.0.0.0:26656"; + seeds = ""; + persistent_peers = ""; + pex = false; + }; + rpc.laddr = "tcp://0.0.0.0:26657"; + }; + + client = { + inherit chain-id; + node = "tcp://127.0.0.1:26657"; + }; + }; + + extraPreStartup = '' + haqqd config keyring-backend test + haqqd config chain-id ${chain-id} + haqqd keys add test --keyring-backend test + ''; + + extraPostStartup = '' + g() { + local genesis="$DAEMON_HOME/config/genesis.json" + jq "$1" "$genesis" | sponge "$genesis" + } + g '.chain_id = "${chain-id}"' + g '.app_state["coinomics"]["max_supply"]["amount"] = "100000000000000000000000000000"' + g '.app_state["coinomics"]["max_supply"]["denom"] = "aISLM"' + g '.app_state["coinomics"]["params"]["enable_coinomics"] = true' + g '.app_state["coinomics"]["params"]["mint_denom"] = "aISLM"' + g '.app_state["crisis"]["constant_fee"]["amount"] = "50000000000000000000000"' + g '.app_state["crisis"]["constant_fee"]["denom"] = "aISLM"' + g '.app_state["evm"]["params"]["evm_denom"] = "aISLM"' + g '.app_state["gov"]["params"]["min_deposit"][0]["amount"] = "5000000000000000000000"' + g '.app_state["gov"]["params"]["min_deposit"][0]["denom"] = "aISLM"' + g '.app_state["staking"]["params"]["bond_denom"] = "aISLM"' + + addr="$(haqqd keys show test -a --keyring-backend test)" + haqqd add-genesis-account "$addr" 20000000000000000000000000000aISLM + + haqqd gentx test 1000000000000000000000aISLM --keyring-backend test --chain-id ${chain-id} + haqqd collect-gentxs + + haqqd validate-genesis + ''; + }; + + systemd.services.haqqd.path = [ + jq + moreutils + ]; + }; + + testScript = '' + import json + + with subtest("Ensure bootstrap is successful"): + server.wait_for_file("/var/lib/haqqd/.haqqd/.bootstrapped") + + with subtest("Ensure the systemd service is operational"): + server.wait_for_unit("haqqd.service") + + with subtest("Ensure all ports are open and connectable"): + server.wait_for_open_port(26656) + server.wait_for_open_port(26657) + server.wait_for_open_port(1317) + server.wait_for_open_port(9090) + server.wait_for_open_port(9091) + server.wait_for_open_port(8545) + server.wait_for_open_port(8546) + + with subtest("Ensure haqqctl is working as expected"): + moniker = json.loads(server.succeed("haqqctl status"))["NodeInfo"]["moniker"] + assert moniker == "${moniker}" + ''; +} diff --git a/nix/tests/default.nix b/nix/tests/default.nix new file mode 100644 index 0000000..874c988 --- /dev/null +++ b/nix/tests/default.nix @@ -0,0 +1,12 @@ +{ inputs, ... }: +{ + perSystem = + { pkgs, ... }: + { + legacyPackages.nixosTests = { + haqqd-basic = pkgs.callPackage ./basic.nix { + imports = [ inputs.self.nixosModules.haqqd ]; + }; + }; + }; +}