Merge branch 'master' of github.com:hectic-lab/util.nix

This commit is contained in:
2025-06-30 21:00:04 +00:00
14 changed files with 262 additions and 127 deletions

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake .#postgres-c

View File

@@ -26,16 +26,15 @@
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
{
{ lib lib,
, cargo-pgrx cargo-pgrx,
, pkg-config pkg-config,
, rustPlatform rustPlatform,
, stdenv stdenv,
, Security Security,
, writeShellScriptBin writeShellScriptBin,
}: }:
# The idea behind: Use it mostly like rustPlatform.buildRustPackage and so # The idea behind: Use it mostly like rustPlatform.buildRustPackage and so
# we hand most of the arguments down. # we hand most of the arguments down.
# #
@@ -46,42 +45,38 @@
# If the generated rust bindings aren't needed to use the extension, its a # If the generated rust bindings aren't needed to use the extension, its a
# unnecessary and heavy dependency. If you set this to true, you also # unnecessary and heavy dependency. If you set this to true, you also
# have to add `rustfmt` to `nativeBuildInputs`. # have to add `rustfmt` to `nativeBuildInputs`.
{
{ buildAndTestSubdir ? null buildAndTestSubdir ? null,
, buildType ? "release" buildType ? "release",
, buildFeatures ? [ ] buildFeatures ? [],
, cargoBuildFlags ? [ ] cargoBuildFlags ? [],
, postgresql postgresql,
# cargo-pgrx calls rustfmt on generated bindings, this is not strictly necessary, so we avoid the # cargo-pgrx calls rustfmt on generated bindings, this is not strictly necessary, so we avoid the
# dependency here. Set to false and provide rustfmt in nativeBuildInputs, if you need it, e.g. # dependency here. Set to false and provide rustfmt in nativeBuildInputs, if you need it, e.g.
# if you include the generated code in the output via postInstall. # if you include the generated code in the output via postInstall.
, useFakeRustfmt ? true useFakeRustfmt ? true,
, usePgTestCheckFeature ? true usePgTestCheckFeature ? true,
, ... ...
} @ args: } @ args: let
let
rustfmtInNativeBuildInputs = lib.lists.any (dep: lib.getName dep == "rustfmt") (args.nativeBuildInputs or []); rustfmtInNativeBuildInputs = lib.lists.any (dep: lib.getName dep == "rustfmt") (args.nativeBuildInputs or []);
in in
assert lib.asserts.assertMsg ((args.installPhase or "") == "")
assert lib.asserts.assertMsg ((args.installPhase or "") == "")
"buildPgrxExtensions overwrites the installPhase, so providing one does nothing"; "buildPgrxExtensions overwrites the installPhase, so providing one does nothing";
assert lib.asserts.assertMsg ((args.buildPhase or "") == "") assert lib.asserts.assertMsg ((args.buildPhase or "") == "")
"buildPgrxExtensions overwrites the buildPhase, so providing one does nothing"; "buildPgrxExtensions overwrites the buildPhase, so providing one does nothing";
assert lib.asserts.assertMsg (useFakeRustfmt -> !rustfmtInNativeBuildInputs) assert lib.asserts.assertMsg (useFakeRustfmt -> !rustfmtInNativeBuildInputs)
"The parameter useFakeRustfmt is set to true, but rustfmt is included in nativeBuildInputs. Either set useFakeRustfmt to false or remove rustfmt from nativeBuildInputs."; "The parameter useFakeRustfmt is set to true, but rustfmt is included in nativeBuildInputs. Either set useFakeRustfmt to false or remove rustfmt from nativeBuildInputs.";
assert lib.asserts.assertMsg (!useFakeRustfmt -> rustfmtInNativeBuildInputs) assert lib.asserts.assertMsg (!useFakeRustfmt -> rustfmtInNativeBuildInputs)
"The parameter useFakeRustfmt is set to false, but rustfmt is not included in nativeBuildInputs. Either set useFakeRustfmt to true or add rustfmt from nativeBuildInputs."; "The parameter useFakeRustfmt is set to false, but rustfmt is not included in nativeBuildInputs. Either set useFakeRustfmt to true or add rustfmt from nativeBuildInputs."; let
fakeRustfmt = writeShellScriptBin "rustfmt" ''
let exit 0
fakeRustfmt = writeShellScriptBin "rustfmt" ''
exit 0
''; '';
maybeDebugFlag = lib.optionalString (buildType != "release") "--debug"; maybeDebugFlag = lib.optionalString (buildType != "release") "--debug";
maybeEnterBuildAndTestSubdir = lib.optionalString (buildAndTestSubdir != null) '' maybeEnterBuildAndTestSubdir = lib.optionalString (buildAndTestSubdir != null) ''
export CARGO_TARGET_DIR="$(pwd)/target" export CARGO_TARGET_DIR="$(pwd)/target"
pushd "${buildAndTestSubdir}" pushd "${buildAndTestSubdir}"
''; '';
maybeLeaveBuildAndTestSubdir = lib.optionalString (buildAndTestSubdir != null) "popd"; maybeLeaveBuildAndTestSubdir = lib.optionalString (buildAndTestSubdir != null) "popd";
pgrxPostgresMajor = lib.versions.major postgresql.version; pgrxPostgresMajor = lib.versions.major postgresql.version;
preBuildAndTest = '' preBuildAndTest = ''
@@ -97,65 +92,70 @@ let
pg_ctl stop pg_ctl stop
''; '';
argsForBuildRustPackage = builtins.removeAttrs args [ "postgresql" "useFakeRustfmt" "usePgTestCheckFeature" ]; argsForBuildRustPackage = builtins.removeAttrs args ["postgresql" "useFakeRustfmt" "usePgTestCheckFeature"];
# so we don't accidentally `(rustPlatform.buildRustPackage argsForBuildRustPackage) // { ... }` because # so we don't accidentally `(rustPlatform.buildRustPackage argsForBuildRustPackage) // { ... }` because
# we forgot parentheses # we forgot parentheses
finalArgs = argsForBuildRustPackage // { finalArgs =
buildInputs = (args.buildInputs or [ ]) ++ lib.optionals stdenv.hostPlatform.isDarwin [ Security ]; argsForBuildRustPackage
// {
buildInputs = (args.buildInputs or []) ++ lib.optionals stdenv.hostPlatform.isDarwin [Security];
nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [ nativeBuildInputs =
cargo-pgrx (args.nativeBuildInputs or [])
postgresql ++ [
pkg-config cargo-pgrx
rustPlatform.bindgenHook postgresql
] ++ lib.optionals useFakeRustfmt [ fakeRustfmt ]; pkg-config
rustPlatform.bindgenHook
]
++ lib.optionals useFakeRustfmt [fakeRustfmt];
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild
echo "Executing cargo-pgrx buildPhase" echo "Executing cargo-pgrx buildPhase"
${preBuildAndTest} ${preBuildAndTest}
${maybeEnterBuildAndTestSubdir} ${maybeEnterBuildAndTestSubdir}
PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}" \ PGRX_BUILD_FLAGS="--frozen -j $NIX_BUILD_CORES ${builtins.concatStringsSep " " cargoBuildFlags}" \
${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \ ${lib.optionalString stdenv.hostPlatform.isDarwin ''RUSTFLAGS="''${RUSTFLAGS:+''${RUSTFLAGS} }-Clink-args=-Wl,-undefined,dynamic_lookup"''} \
cargo pgrx package \ cargo pgrx package \
--pg-config ${postgresql.pg_config}/bin/pg_config \ --pg-config ${postgresql.pg_config}/bin/pg_config \
${maybeDebugFlag} \ ${maybeDebugFlag} \
--features "${builtins.concatStringsSep " " buildFeatures}" \ --features "${builtins.concatStringsSep " " buildFeatures}" \
--out-dir "$out" --out-dir "$out"
${maybeLeaveBuildAndTestSubdir} ${maybeLeaveBuildAndTestSubdir}
runHook postBuild runHook postBuild
''; '';
preCheck = preBuildAndTest + args.preCheck or ""; preCheck = preBuildAndTest + args.preCheck or "";
installPhase = '' installPhase = ''
runHook preInstall runHook preInstall
echo "Executing buildPgrxExtension install" echo "Executing buildPgrxExtension install"
${maybeEnterBuildAndTestSubdir} ${maybeEnterBuildAndTestSubdir}
cargo-pgrx pgrx stop all cargo-pgrx pgrx stop all
mv $out/${postgresql}/* $out mv $out/${postgresql}/* $out
rm -rf $out/nix rm -rf $out/nix
${maybeLeaveBuildAndTestSubdir} ${maybeLeaveBuildAndTestSubdir}
runHook postInstall runHook postInstall
''; '';
PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1"; PGRX_PG_SYS_SKIP_BINDING_REWRITE = "1";
CARGO_BUILD_INCREMENTAL = "false"; CARGO_BUILD_INCREMENTAL = "false";
RUST_BACKTRACE = "full"; RUST_BACKTRACE = "full";
checkNoDefaultFeatures = true; checkNoDefaultFeatures = true;
checkFeatures = (args.checkFeatures or [ ]) ++ (lib.optionals usePgTestCheckFeature [ "pg_test" ]) ++ [ "pg${pgrxPostgresMajor}" ]; checkFeatures = (args.checkFeatures or []) ++ (lib.optionals usePgTestCheckFeature ["pg_test"]) ++ ["pg${pgrxPostgresMajor}"];
}; };
in in
rustPlatform.buildRustPackage finalArgs rustPlatform.buildRustPackage finalArgs

View File

@@ -176,7 +176,7 @@
version = "3.4.3"; version = "3.4.3";
src = pkgs.python3Packages.fetchPypi { src = pkgs.python3Packages.fetchPypi {
inherit pname version; inherit pname version;
sha256 = "sha256-gzYP+LyXmA5P8lyWTHvTkj0zPRd6pPf7c2sBnybHy0E="; sha256 = "sha256-gzYP+LyXmA5P8lyWTHvTkj0zPRd6pPf7c2sBnybHy0E=";
}; };
}; };
py3-cryptomus = pkgs.python3Packages.buildPythonPackage rec { py3-cryptomus = pkgs.python3Packages.buildPythonPackage rec {
@@ -184,7 +184,7 @@
version = "1.1"; version = "1.1";
src = pkgs.python3Packages.fetchPypi { src = pkgs.python3Packages.fetchPypi {
inherit pname version; inherit pname version;
sha256 = "sha256-f0BBGfemKxMdz+LMvawWqqRfmF+TrCpMwgtJEYt+fgU="; sha256 = "sha256-f0BBGfemKxMdz+LMvawWqqRfmF+TrCpMwgtJEYt+fgU=";
}; };
}; };
py3-modulegraph = pkgs.python3Packages.buildPythonPackage rec { py3-modulegraph = pkgs.python3Packages.buildPythonPackage rec {
@@ -192,7 +192,7 @@
version = "0.19.6"; version = "0.19.6";
src = pkgs.python3Packages.fetchPypi { src = pkgs.python3Packages.fetchPypi {
inherit pname version; inherit pname version;
sha256 = "sha256-yRTIyVoOEP6IUF1OnCKEtOPbxwlD4wbMZWfjbMVBv0s="; sha256 = "sha256-yRTIyVoOEP6IUF1OnCKEtOPbxwlD4wbMZWfjbMVBv0s=";
}; };
}; };
py3-swifter = pkgs.python3Packages.buildPythonPackage rec { py3-swifter = pkgs.python3Packages.buildPythonPackage rec {
@@ -200,13 +200,13 @@
version = "1.4.0"; version = "1.4.0";
src = pkgs.python3Packages.fetchPypi { src = pkgs.python3Packages.fetchPypi {
inherit pname version; inherit pname version;
sha256 = "sha256-4bt0R2ohs/B6F6oYyX/cuoWZcmvRfacy8J2rzFDia6A="; sha256 = "sha256-4bt0R2ohs/B6F6oYyX/cuoWZcmvRfacy8J2rzFDia6A=";
}; };
}; };
py3-aiogram-newsletter = pkgs.python3Packages.buildPythonPackage rec { py3-aiogram-newsletter = pkgs.python3Packages.buildPythonPackage rec {
pname = "aiogram-newsletter"; pname = "aiogram-newsletter";
version = "0.0.10"; version = "0.0.10";
src = pkgs.fetchFromGitHub { src = pkgs.fetchFromGitHub {
inherit pname version; inherit pname version;
owner = "nessshon"; owner = "nessshon";
@@ -247,13 +247,14 @@
pg-15-ext-plsh = buildPlShExt pkgs "15"; pg-15-ext-plsh = buildPlShExt pkgs "15";
c-hectic = pkgs.callPackage ./package/c/hectic/default.nix {}; c-hectic = pkgs.callPackage ./package/c/hectic/default.nix {};
watch = pkgs.callPackage ./package/c/watch/default.nix {}; watch = pkgs.callPackage ./package/c/watch/default.nix {};
support-bot = pkgs.callPackage ./package/support-bot {};
}; };
devShells.${system} = let devShells.${system} = let
shells = self.devShells.${system}; shells = self.devShells.${system};
in { in {
c = pkgs.mkShell { c = pkgs.mkShell {
buildInputs = (with pkgs; [ inotify-tools gdb gcc ]) ++ (with self.packages.${system}; [ c-hectic nvim-pager watch ]); buildInputs = (with pkgs; [inotify-tools gdb gcc]) ++ (with self.packages.${system}; [c-hectic nvim-pager watch]);
PAGER = "${self.packages.${system}.nvim-pager}/bin/pager"; PAGER = "${self.packages.${system}.nvim-pager}/bin/pager";
}; };
postgres-c = pkgs.mkShell { postgres-c = pkgs.mkShell {
@@ -271,6 +272,7 @@
shellHook = '' shellHook = ''
export PATH=${pkgs.gcc}/bin:$PATH export PATH=${pkgs.gcc}/bin:$PATH
export PAGER="${self.packages.${system}.nvim-pager}/bin/pager" export PAGER="${self.packages.${system}.nvim-pager}/bin/pager"
''; '';
}; };
@@ -352,7 +354,7 @@
char *value = getenv(env_name); char *value = getenv(env_name);
if (value) { if (value) {
char buffer[128]; char buffer[128];
sprintf(buffer, "echo $%s\n", env_name); sprintf(buffer, "echo $%s\n", env_name);
system(buffer); system(buffer);
} else { } else {
@@ -440,7 +442,7 @@
''; '';
}; };
environment.systemPackages = with pkgs; [ environment.systemPackags = with pkgs; [
gdb gdb
hectic.nvim-pager hectic.nvim-pager
(writeScriptBin "check" '' (writeScriptBin "check" ''
@@ -569,7 +571,6 @@
overlays.default = final: prev: ( overlays.default = final: prev: (
let let
hectic-packages = self.packages.${prev.system}; hectic-packages = self.packages.${prev.system};
in { in {
hectic = hectic-packages; hectic = hectic-packages;
postgresql_17 = prev.postgresql_17 // {pkgs = prev.postgresql_17.pkgs // { postgresql_17 = prev.postgresql_17 // {pkgs = prev.postgresql_17.pkgs // {
@@ -594,11 +595,12 @@
hemar = hectic-packages.pg-15-ext-hemar; hemar = hectic-packages.pg-15-ext-hemar;
};}; };};
writers = let writers = let
writeC = writeC = name: argsOrScript:
name: argsOrScript: if lib.isAttrs argsOrScript && !lib.isDerivation argsOrScript
if lib.isAttrs argsOrScript && !lib.isDerivation argsOrScript then then
prev.writers.makeBinWriter ( prev.writers.makeBinWriter (
argsOrScript // { argsOrScript
// {
compileScript = '' compileScript = ''
# Force gcc to treat the input file as C code # Force gcc to treat the input file as C code
${prev.gcc}/bin/gcc -fsyntax-only -xc $contentPath ${prev.gcc}/bin/gcc -fsyntax-only -xc $contentPath
@@ -609,7 +611,8 @@
${prev.gcc}/bin/gcc -xc -o $out $contentPath ${prev.gcc}/bin/gcc -xc -o $out $contentPath
''; '';
} }
) name )
name
else else
prev.writers.makeBinWriter { prev.writers.makeBinWriter {
compileScript = '' compileScript = ''
@@ -621,22 +624,25 @@
fi fi
${prev.gcc}/bin/gcc -xc -o $out $contentPath ${prev.gcc}/bin/gcc -xc -o $out $contentPath
''; '';
} name argsOrScript; }
writeMinC = name
name: includes: body: argsOrScript;
writeC name '' writeMinC = name: includes: body:
${builtins.concatStringsSep "\n" (map (h: "#include " + h) includes)} writeC name ''
${builtins.concatStringsSep "\n" (map (h: "#include " + h) includes)}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
${body} ${body}
} }
''; '';
in prev.writers // { in
writeCBin = name: writeC "/bin/${name}"; prev.writers
writeC = writeC; // {
writeMinCBin = name: includes: body: writeMinC "/bin/${name}" includes body; writeCBin = name: writeC "/bin/${name}";
writeMinC = writeMinC; writeC = writeC;
}; writeMinCBin = name: includes: body: writeMinC "/bin/${name}" includes body;
writeMinC = writeMinC;
};
} }
); );
lib = { lib = {

View File

@@ -1,12 +1,11 @@
{ stdenv, patchelf, gcc, lib, bash, inotify-tools }: { stdenv, patchelf, gcc, lib, bash, inotify-tools }:
stdenv.mkDerivation { stdenv.mkDerivation {
pname = "hectic"; pname = "hectic";
version = "1.0"; version = "1.0";
src = ./.; src = ./.;
doCheck = true; doCheck = true;
nativeBuildInputs = [ gcc inotify-tools ]; nativeBuildInputs = [gcc inotify-tools];
buildPhase = '' buildPhase = ''
ls ls
@@ -86,4 +85,4 @@ EOF
description = "hectic"; description = "hectic";
license = lib.licenses.mit; license = lib.licenses.mit;
}; };
} }

View File

@@ -1,2 +1,8 @@
<<<<<<< HEAD:package/c/hemar/.gitignore
hemar.o hemar.o
hemar.so hemar.so
=======
package/c/postgreact/postgreact.control
package/c/postgreact/postgreact.o
package/c/postgreact/postgreact.so
>>>>>>> 016db3d06ae814e0f0cc8f39cd4e5af729bb39ac:package/c/postgreact/.gitignore

View File

@@ -12,4 +12,5 @@ PG_LDFLAGS += -Wl,-rpath,$(shell $(HECTIC_CONFIG) --libdir)
SHLIB_LINK += $(shell $(HECTIC_CONFIG) --libs) SHLIB_LINK += $(shell $(HECTIC_CONFIG) --libs)
PGXS := $(shell $(PG_CONFIG) --pgxs) PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)
include $(PGXS)

View File

@@ -7,6 +7,8 @@
# --color Pass -fdiagnostics-color=always to compiler. # --color Pass -fdiagnostics-color=always to compiler.
# help, --help Show this help message. # help, --help Show this help message.
set -u
check_dependencies() { check_dependencies() {
for dep in gcc pg_config; do for dep in gcc pg_config; do
if ! command -v "$dep" >/dev/null 2>&1; then if ! command -v "$dep" >/dev/null 2>&1; then
@@ -105,4 +107,4 @@ case "$MODE" in
print_help print_help
exit 1 exit 1
;; ;;
esac esac

View File

@@ -0,0 +1,12 @@
-- complain if script is sourced in psql, rather than via CREATE EXTENSION
\echo Use "CREATE EXTENSION postgreact" to load this file. \quit
-- Define the hello function that uses our C implementation
CREATE FUNCTION hello()
RETURNS
TEXT
STRICT VOLATILE
LANGUAGE C
AS
'MODULE_PATHNAME', 'hello'
;

View File

@@ -0,0 +1,4 @@
comment = '@EXTENSION_COMMENT@'
default_version = '@EXTENSION_VERSION@'
module_pathname = '$libdir/@EXTENSION@'
relocatable = false

View File

@@ -0,0 +1,16 @@
#ifndef POSTGREACT_H
#define POSTGREACT_H
#include "postgres.h"
#ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
#endif
void _PG_init(void);
void _PG_fini(void);
Datum hello(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(hello);
#endif // POSTGREACT_H

View File

@@ -1,12 +1,17 @@
{ stdenv, gcc, lib, bash, c-hectic }: {
stdenv,
gcc,
lib,
bash,
c-hectic,
}:
stdenv.mkDerivation { stdenv.mkDerivation {
pname = "prettify"; pname = "prettify";
version = "1.0"; version = "1.0";
src = ./.; src = ./.;
doCheck = false; doCheck = false;
nativeBuildInputs = [ gcc c-hectic ]; nativeBuildInputs = [gcc c-hectic];
buildPhase = '' buildPhase = ''
ls ls

View File

@@ -1,12 +1,17 @@
{ stdenv, gcc, lib, bash, gdb }: {
stdenv,
gcc,
lib,
bash,
gdb,
}:
stdenv.mkDerivation { stdenv.mkDerivation {
pname = "watch"; pname = "watch";
version = "1.0"; version = "1.0";
src = ./.; src = ./.;
doCheck = false; doCheck = false;
nativeBuildInputs = [ gcc gdb ]; nativeBuildInputs = [gcc gdb];
buildPhase = '' buildPhase = ''
${bash}/bin/sh ./make.sh build ${bash}/bin/sh ./make.sh build
@@ -25,4 +30,4 @@ stdenv.mkDerivation {
description = "watch"; description = "watch";
license = lib.licenses.mit; license = lib.licenses.mit;
}; };
} }

View File

@@ -16,7 +16,7 @@ in
cargoLock.lockFile = ./Cargo.lock; cargoLock.lockFile = ./Cargo.lock;
buildInputs = [ postgresql_15 ]; buildInputs = [postgresql_15];
doCheck = true; doCheck = true;
} }

View File

@@ -0,0 +1,78 @@
{
fetchFromGitHub,
pkgs,
...
}: let
aiogram-newsletter = pkgs.python3Packages.buildPythonPackage {
pname = "example-package";
version = "0.0.10";
src = fetchFromGitHub {
owner = "nessshon";
repo = "aiogram-newsletter";
rev = "bb8a42e4bcff66a9a606fc92ccc27b1d094b20fc";
sha256 = "sha256-atKhccp8Pr8anJUo+M9hnYkYrcgnB9SxrpmsiVusJZs=";
};
propagatedBuildInputs = [ ];
meta = {
description = "";
};
};
in pkgs.python3Packages.buildPythonPackage {
pname = "support-bot";
version = "1.0.0";
src = pkgs.fetchFromGitHub {
owner = "nessshon";
repo = "support-bot";
rev = "9191d9a9ba6bfd81e267b6ca41836db037555976";
sha256 = "sha256-94/cGN0OMytrQB66B2WA44bRaz+qXI627C/oE9iFgNU=";
};
postPatch = ''
cat > setup.py <<'EOF1'
from setuptools import setup
setup(
name="support-bot",
version="1.0.0",
install_requires=[
"aiogram==3.7.0",
"aiogram-newsletter>=0.0.10",
"cachetools==5.3.2",
"environs==10.3.0",
"pydantic==2.5.3",
"redis==5.0.1",
"apscheduler",
],
entry_points={
"console_scripts": [
"support-bot=app.entry_point:main",
],
},
)
EOF1
cat > app/entry_point.py <<'EOF2'
def main():
import asyncio
from .__main__ import main
asyncio.run(main())
EOF2
'';
propagatedBuildInputs = (with pkgs.python3Packages; [
aiogram
apscheduler
cachetools
environs
pydantic
redis
]) ++ [ aiogram-newsletter ];
meta = {
description = "A support bot for GitHub";
homepage = "https://github.com/nessshon/support-bot";
};
}