summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEuAndreh <eu@euandre.org>2025-12-04 06:17:50 -0300
committerEuAndreh <eu@euandre.org>2025-12-04 06:25:04 -0300
commit13edba0fa2bd4c3847160809499ce837f52d1264 (patch)
tree73c1df9c4e2d1b36102eddfffb52babb98c5d406
parentetc/transactor.properties.tmpl: Default to "tmp" for data-dir (diff)
downloaddatomic-13edba0fa2bd4c3847160809499ce837f52d1264.tar.gz
datomic-13edba0fa2bd4c3847160809499ce837f52d1264.tar.xz
bin/datomic.in: Add "datomic(1)" commandHEADmain
-rw-r--r--.gitignore1
-rw-r--r--Makefile12
-rwxr-xr-xbin/datomic.in85
-rw-r--r--etc/backup.clj45
4 files changed, 140 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 120223e..c22d59f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
bin/transactor
+bin/datomic
*.db
*.db-shm
*.db-wal
diff --git a/Makefile b/Makefile
index 7e4b5ec..7df81f4 100644
--- a/Makefile
+++ b/Makefile
@@ -39,11 +39,11 @@ include deps.mk
sources = \
- $(sources.sh) \
derived-assets = \
bin/transactor \
+ bin/datomic \
side-assets = \
@@ -82,8 +82,13 @@ install: all
'$(DESTDIR)$(SYSCONFDIR)' \
'$(DESTDIR)$(JAVADIR)' \
- cp bin/transactor '$(DESTDIR)$(BINDIR)'
- cp etc/init.sql etc/logback.xml etc/transactor.properties.tmpl \
+ cp bin/transactor bin/datomic \
+ '$(DESTDIR)$(BINDIR)'
+ cp \
+ etc/backup.clj \
+ etc/init.sql \
+ etc/logback.xml \
+ etc/transactor.properties.tmpl \
'$(DESTDIR)$(SYSCONFDIR)'
cp share/java/datomic/*.jar \
'$(DESTDIR)$(JAVADIR)'
@@ -95,6 +100,7 @@ install: all
uninstall:
rm -rf \
'$(DESTDIR)$(BINDIR)'/transactor \
+ '$(DESTDIR)$(BINDIR)'/datomic \
'$(DESTDIR)$(SYSCONFDIR)' \
'$(DESTDIR)$(JAVADIR)' \
diff --git a/bin/datomic.in b/bin/datomic.in
new file mode 100755
index 0000000..2f60912
--- /dev/null
+++ b/bin/datomic.in
@@ -0,0 +1,85 @@
+#!/bin/sh
+set -euo pipefail
+
+
+usage() {
+ cat <<-'EOF'
+ Usage:
+ datomic backup DBFILE TODIR
+ datomic gc DBFILE
+ EOF
+}
+
+
+ACTION="${1:-}"
+DBFILE="${2:-}"
+TODIR="${3:-}"
+
+if [ -z "$ACTION" ]; then
+ echo 'Missing ACTION.' >&2
+ usage >&2
+ exit 2
+fi
+
+if [ -z "$DBFILE" ]; then
+ echo 'Missing DBFILE.' >&2
+ usage >&2
+ exit 2
+fi
+
+if [ "$ACTION" = 'backup' ] && [ -z "$TODIR" ]; then
+ echo 'Missing TODIR.' >&2
+ usage >&2
+ exit 2
+fi
+
+
+
+lastroot() (
+ cd "$TODIR"/datomic/roots
+ find * | sort -n | tail -n1
+)
+
+backupcmd() {
+ java \
+ -server \
+ -Xms4g \
+ -Xmx4g \
+ --class-path '@SYSCONFDIR@/:@JAVADIR@/*' \
+ clojure.main \
+ -m backup \
+ "$@"
+}
+
+
+case "$ACTION" in
+ (backup)
+ backupcmd backup datomic:sql://app?jdbc:sqlite:"$DBFILE" file:"$TODIR"/datomic
+ backupcmd verify file:"$TODIR"/datomic true "$(lastroot)"
+ sqlite3 "$DBFILE" ".backup '$TODIR/sqlite.db'"
+ {
+ sqlite3 "$DBFILE" .dump > "$TODIR"/dump.sql.next
+ mv "$TODIR"/dump.sql.next "$TODIR"/dump.sql
+ }
+ {
+ sqlite3 "$TODIR"/restored.db < @SYSCONFDIR@/init.sql > /dev/null
+ backupcmd restore file:"$TODIR"/datomic datomic:sql://app?jdbc:sqlite:"$TODIR"/restored.db
+ }
+ ;;
+ (gc)
+ exec java \
+ -server \
+ -Xms4g \
+ -Xmx4g \
+ --class-path '@SYSCONFDIR@/:@JAVADIR@/*' \
+ clojure.main \
+ -m datomic.tools.gc-db \
+ datomic:sql://app?jdbc:sqlite:"$DBFILE" \
+ "$(date --date="$(date +'%Y-%m-01') - 3 months" -Is)"
+ ;;
+ (*)
+ printf 'Bad ACTION: "%s"\n' "$ACTION" >&2
+ usage >&2
+ exit 2
+ ;;
+esac
diff --git a/etc/backup.clj b/etc/backup.clj
new file mode 100644
index 0000000..ae4a380
--- /dev/null
+++ b/etc/backup.clj
@@ -0,0 +1,45 @@
+(ns backup
+ (:require [clojure.edn :as edn]
+ [datomic.require :as req]
+ [datomic.cli :as cli]))
+
+(def commands
+ "Map of command names to descriptions of command arguments."
+ {"backup"
+ {:f 'datomic.backup-cli/backup
+ :named #{{:long-name :from-db-uri :required true :doc "URI for backup source"}
+ {:long-name :to-backup-uri :required true :doc "URI for backup destination"}}
+ :positional [:from-db-uri :to-backup-uri]}
+ "list"
+ {:f 'datomic.backup-cli/list-backups
+ :named #{{:long-name :backup-uri :required true :doc "backup URI"}}
+ :positional [:backup-uri :to-db-uri]}
+ "verify"
+ {:f 'datomic.backup-cli/verify-backup
+ :named #{{:long-name :backup-uri :required true :doc "URI of backup"}
+ {:long-name :read-all :required true :doc "Verify that every segment is readable" :coerce #(boolean (edn/read-string %))}
+ {:long-name :t :required true :doc "Point in time (t) to verify" :coerce #(Long. %)}}
+ :positional [:backup-uri :read-all :t]}
+ "restore"
+ {:f 'datomic.backup-cli/restore
+ :named #{{:long-name :from-backup-uri :required true :doc "URI for restore source"}
+ {:long-name :to-db-uri :required true :doc "URI for restore destination"}
+ {:long-name :t :doc "Point in time (t) to restore, defaults to most recent"
+ :default nil :coerce #(Long. %)}}
+ :positional [:from-backup-uri :to-db-uri :t]}})
+
+(defn -main
+ [& [command & cli-args]]
+ (if-let [{:keys [f named positional vararg]} (get commands command)]
+ (let [args (cli/parse-or-exit! command cli-args named positional vararg)]
+ (try
+ (when-let [result (req/require-and-run f args)]
+ (println result))
+ (catch Throwable t
+ (.printStackTrace t)
+ (cli/fail (.getMessage t))))
+ (when @cli/exit-after-command
+ (System/exit (if @cli/failed 1 0))))
+ (binding [*out* *err*]
+ (println (str "Bad command: \"" command "\"."))
+ (System/exit 2))))