Skip to content

Instantly share code, notes, and snippets.

@bennyandresen
Created August 13, 2025 10:10
Show Gist options
  • Select an option

  • Save bennyandresen/9a861983afde4b408356257e6e66fc55 to your computer and use it in GitHub Desktop.

Select an option

Save bennyandresen/9a861983afde4b408356257e6e66fc55 to your computer and use it in GitHub Desktop.
services-flake datomic
{ pkgs, lib, config, name, ... }:
let
types = lib.types;
versions = {
datomic = "1.0.7387";
sqlite-jdbc = "3.50.2.0";
};
datomicZip = pkgs.fetchurl {
url =
"https://datomic-pro-downloads.s3.amazonaws.com/${versions.datomic}/datomic-pro-${versions.datomic}.zip";
sha256 = "sha256-ral2iqJD8FsHtI1tH0NRp39LvVJuIhxE+KiCXVQVOPA=";
};
sqliteJdbc = pkgs.fetchurl {
url =
"https://github.com/xerial/sqlite-jdbc/releases/download/${versions.sqlite-jdbc}/sqlite-jdbc-${versions.sqlite-jdbc}.jar";
sha256 = "sha256-mA+EnJRLKPFFW/dLn/Hwc5rrbAGY5CFhWDvH4NVzkWM=";
};
setupScript = pkgs.writeShellApplication {
name = "setup-datomic";
text = ''
mkdir -p ${config.dataDir}
if [ ! -f ${config.dataDir}/sqlite.db ]; then
echo "Creating sqlite database at ${config.dataDir}/sqlite.db"
${pkgs.sqlite}/bin/sqlite3 ${config.dataDir}/sqlite.db "
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA mmap_size = 134217728;
PRAGMA journal_size_limit = 67108864;
PRAGMA cache_size = 2000;
CREATE TABLE datomic_kvs (
id TEXT NOT NULL,
rev INTEGER,
map TEXT,
val BYTEA,
CONSTRAINT pk_id PRIMARY KEY (id)
);
"
fi
'';
};
startScript = pkgs.writeShellApplication {
name = "start-datomic";
runtimeInputs = [ pkgs.jdk23_headless ];
text = ''
echo Starting datomic transactor on port ${builtins.toString config.port}
export DATOMIC_HOME="${datomicFiles}"
export DATOMIC_DB="${config.dbName}"
export DATOMIC_LOG_DIR="/tmp/datomic/${name}/log"
CLASSPATH='${datomicFiles}/resources:${datomicFiles}/datomic-transactor-pro-${versions.datomic}.jar:${datomicFiles}/lib/*:${datomicFiles}/bin'
# run db creation script in background
# ${datomicFiles}/bin/shell ${createDatomicDbScript} &
# can't use bundled shell script because it sets cwd, thus breaking relative paths
java -server -cp "$CLASSPATH" jline.ConsoleRunner clojure.main ${datomicFiles}/bin/shell.clj ${createDatomicDbScript} &
# Run datomic transactor
# can't use bundled shell script because it sets cwd, thus breaking relative paths
exec java -server -cp "$CLASSPATH" \
${
builtins.toString config.javaOpts
} clojure.main --main datomic.launcher ${transactorProperties}
'';
};
transactorProperties = pkgs.writeTextFile {
name = "transactor.properties";
text = ''
port=${builtins.toString config.port}
host=${config.host}
storage-access=remote
protocol=sql
sql-driver-class=org.sqlite.JDBC
sql-url=jdbc:sqlite:${config.dataDir}/sqlite.db
# See https://docs.datomic.com/on-prem/capacity.html
memory-index-threshold=${config.memoryIndexThreshold}
memory-index-max=${config.memoryIndexMax}
object-cache-max=${config.objectCacheMax}
ping-host=${config.health.host}
ping-port=${builtins.toString config.health.port}
'';
};
createDatomicDbScript = pkgs.writeTextFile {
name = "create-db.bsh";
text = ''
db_name = System.getenv("DATOMIC_DB");
if (db_name == null || db_name.isEmpty()) {
db_name = "app";
}
db_uri = "datomic:sql://" + db_name + "?jdbc:sqlite:${config.dataDir}/sqlite.db";
System.out.println("db_uri = " + db_uri);
for (int i = 0; i < 12; i++) {
Thread.sleep(5000);
System.out.println("Testing connection to database '" + db_name + "'...");
try {
if (Peer.createDatabase(db_uri)) {
System.out.println("Created database '" + db_name + "'");
}
System.out.println("Connect using DB URI " + db_uri);
System.exit(0);
} catch (Exception e) {
// System.out.println(e.getMessage());
}
}
System.out.println("WARNING: Could not create or connect to database " + db_name + " after 60s.");
'';
};
# Prepare datomic files
datomicFiles = pkgs.runCommand "datomic-files" { } ''
mkdir -p $out
${pkgs.unzip}/bin/unzip ${datomicZip} -d $out
mv $out/datomic-pro-${versions.datomic}/* $out/
rm -rf $out/datomic-pro-${versions.datomic}
rm -rf $out/presto-server
mkdir -p $out/lib
cp ${sqliteJdbc} $out/lib/sqlite-jdbc-${versions.sqlite-jdbc}.jar
'';
in {
options = {
host = lib.mkOption {
default = "0.0.0.0";
type = types.str;
description = "Host for datomic to listen on";
};
port = lib.mkOption {
type = types.port;
default = 4334;
description = "Port for datomic to listen on";
};
dbName = lib.mkOption {
type = types.str;
default = "app";
description = "Name of the Datomic database";
};
javaOpts = lib.mkOption {
type = with types; either str (listOf str);
default = [ "-Xmx1g" "-Xms1g" "-XX:+UseG1GC" "-XX:MaxGCPauseMillis=50" ];
description = "JVM options for the Datomic transactor";
example = lib.literalExpression ''
[
"-Xmx1g"
"-Xms1g"
"-XX:+UseG1GC"
"-XX:MaxGCPauseMillis=50"
]
'';
};
memoryIndexThreshold = lib.mkOption {
type = types.str;
default = "32m";
description = "Memory index threshold";
};
memoryIndexMax = lib.mkOption {
type = types.str;
default = "256m";
description = "Maximum memory index";
};
objectCacheMax = lib.mkOption {
type = types.str;
default = "128m";
description = "Maximum object cache";
};
health = {
host = lib.mkOption {
default = "localhost";
type = types.str;
description = "Host for datomic health check endpoint to listen on";
};
port = lib.mkOption {
type = types.port;
default = 4336;
description = "Port for datomic health check endpoint to listen on";
};
};
};
config = {
outputs.settings = {
processes."${name}-init" = { command = setupScript; };
processes."${name}" = {
command = startScript;
is_tty = true;
readiness_probe = {
http_get = {
host = config.health.host;
port = config.health.port;
path = "/health";
};
initial_delay_seconds = 2;
period_seconds = 10;
timeout_seconds = 4;
success_threshold = 1;
failure_threshold = 5;
};
depends_on."${name}-init".condition = "process_completed_successfully";
availability = {
restart = "on_failure";
max_restarts = 2;
};
};
};
};
}
{
description = "example flake datomic-pro with juspay/services-flake";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs";
process-compose-flake.url = "github:Platonic-Systems/process-compose-flake";
services-flake.url = "github:juspay/services-flake";
systems.url = "github:nix-systems/default";
};
outputs = inputs@{ flake-parts, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import inputs.systems;
imports = [ inputs.process-compose-flake.flakeModule ];
perSystem = { self', pkgs, lib, config, ... }:
let inherit (inputs.services-flake.lib) multiService;
in {
process-compose."default" = {
imports = [
inputs.services-flake.processComposeModules.default
(multiService ./datomic.nix)
];
cli.options.no-server = true;
services.datomic."example1" = {
enable = true;
dbName = "one";
dataDir = "data/one";
};
services.datomic."example2" = {
enable = true;
dbName = "two";
port = 4337;
health.port = 4339;
dataDir = "data/two";
};
};
};
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment