Compare commits

..

15 Commits

Author SHA1 Message Date
7aaec7388d feat: nixos module
All checks were successful
build / build (push) Successful in 8s
2026-04-19 10:48:55 -06:00
7e6fb22d03 feat: main entry point
All checks were successful
build / build (push) Successful in 23s
2026-04-18 18:33:36 -06:00
ac1c0eacd9 chore: ignore timestamp 2026-04-18 18:28:00 -06:00
e0ceefa399 fix: server 404 2026-04-18 18:27:45 -06:00
a9d105b639 fix: superscript 2026-04-18 18:21:59 -06:00
1c31f4064a chore: ignore other things too 2026-04-18 17:34:32 -06:00
4e343fde99 feat: render subtitle 2026-04-18 17:27:23 -06:00
d967188e18 fix: bad UUIDs 2026-04-18 17:27:06 -06:00
694136b643 fix: ignore filetags for now 2026-04-18 16:57:37 -06:00
32a45b3a16 feat: node title 2026-04-18 16:57:15 -06:00
5472d93cff fix: 맞는 404 쪽 씀 2026-04-18 16:55:25 -06:00
32dad302f0 feat: navbar 2026-04-18 16:42:57 -06:00
7fbc5c8059 fix: 비는 backlinks 부분을 만들지 않음 2026-04-18 16:24:28 -06:00
55dd22f220 fix: doerg → cfg 2026-04-18 16:14:13 -06:00
dcaac98252 refactor: doerg는 publisher와 결합
All checks were successful
build / build (push) Successful in 5s
2026-04-03 13:31:16 -06:00
15 changed files with 242 additions and 51 deletions

View File

@@ -33,8 +33,8 @@ let
];
doerg-config = writeText "doerg-extra-config.edn" ''
#:net.deertopia.doerg
{:ibm-plex-web "${ibm-plex-web}"
#:net.deertopia.doerg.config
{:ibm-plex-web "${ibm-plex-web}/share/ibm-plex-web"
:latex "${lib.getExe' our-tex "xelatex"}"
:dvisvgm "${lib.getExe' our-tex "dvisvgm"}"
:doerg-temml-worker "${lib.getExe doerg-temml-worker}"
@@ -43,15 +43,21 @@ let
in mkCljBin' {
name = "net.deertopia/doerg";
version = "0.1.0";
projectSrc = lib.cleanSource ./.;
projectSrc = lib.cleanSourceWith {
filter = path: type:
lib.sources.cleanSourceFilter path type
|| lib.hasSuffix ".nix" path;
src = ./.;
};
lockfile = ./deps-lock.json;
main-ns = "net.deertopia.doerg.main";
nativeBuildInputs = [
plex
makeWrapper
];
buildInputs = [
propagatedBuildInputs = [
doerg-parser
ibm-plex-web
doerg-temml-worker
plex
our-tex
@@ -73,5 +79,6 @@ in mkCljBin' {
XDG_STATE_HOME=$(mktemp -d "state-home-XXXXXX")
clojure -M:test
'';
passthru = { inherit plex our-tex test-emacs; };
passthru = { inherit plex our-tex test-emacs doerg-config; };
meta.mainProgram = "doerg";
}

17
dev/user.clj Normal file
View File

@@ -0,0 +1,17 @@
(ns user
(:require [net.deertopia.doerg.server :as server]
[net.deertopia.doerg.config :as cfg]
[net.deertopia.doerg.cached-file :as cached-file]
[babashka.fs :as fs]))
(cfg/load-config! :profile :dev)
(when (not= :running (server/status))
(server/start!))
(defn invalidate-html-cache! []
(fs/delete-tree (server/html-dir))
nil)
(defn toggle-html-cache! []
(alter-var-root #'cached-file/*use-cache?* not))

View File

@@ -14,12 +14,15 @@
"x86_64-linux"
];
overlays = [
inputs.sydpkgs.overlays.default
clj-nix.overlays.default
];
each-system = f: nixpkgs.lib.genAttrs supportedSystems (system: f rec {
pkgs = import nixpkgs {
inherit system;
overlays = [
inputs.sydpkgs.overlays.default
clj-nix.overlays.default
overlays = overlays ++ [
self.overlays.default
];
};
@@ -36,13 +39,17 @@
});
overlays.default = final: prev:
let graal = x: final.mkGraalBin { cljDrv = x; };
let
# is this really the correct way to satisfy our
# dependencies? lmfao
final' = final.appendOverlays overlays;
graal = x: final'.mkGraalBin { cljDrv = x; };
in {
ibm-plex-web = final.callPackage ./ibm-plex-web.nix {};
doerg = final.callPackage ./. {};
doerg-parser = final.callPackage ./doerg-parser {};
doerg-temml-worker = final.callPackage ./doerg-temml-worker {};
our-tex = final.callPackage ./our-tex.nix {};
ibm-plex-web = final'.callPackage ./ibm-plex-web.nix {};
doerg = final'.callPackage ./. {};
doerg-parser = final'.callPackage ./doerg-parser {};
doerg-temml-worker = final'.callPackage ./doerg-temml-worker {};
our-tex = final'.callPackage ./our-tex.nix {};
};
checks = each-system ({ pkgs, system, ... }: {
@@ -52,6 +59,8 @@
(pkgs.lib.attrValues self.packages.${system});
});
nixosModules.default = import ./module.nix { inherit self; };
devShells = each-system ({ pkgs, system, ... }: {
default = pkgs.mkShell {
inputsFrom = [

107
module.nix Normal file
View File

@@ -0,0 +1,107 @@
{ self, ... }:
{ config, lib, pkgs, ... }:
let
cfg = config.services.doerg;
doerg-config = pkgs.writeText "doerg-config.edn" ''
#:net.deertopia.doerg.config
{:org-roam-db-path "${cfg.databasePath}"
:state-directory "${cfg.stateDir}"
:port ${builtins.toString cfg.port}}
'';
inherit (lib) types;
org-roam-db-sync = pkgs.writeText "org-roam-db-sync.el" ''
#!/usr/bin/env -S emacs -Q -x
(require 'org-roam)
(setq org-roam-directory (expand-file-name (car command-line-args-left)))
(setq org-roam-db-location (expand-file-name (cadr command-line-args-left)))
(org-roam-db-sync)
'';
in {
options.services.doerg = {
enable = lib.mkEnableOption "Doerg";
org-roam-db-sync.enable = lib.mkEnableOption "Org-roam db sync";
port = lib.mkOption {
default = 21984;
type = lib.types.port;
description = ''
The port on which Doerg will listen.
'';
};
stateDir = lib.mkOption {
type = types.path;
default = "/var/lib/private/doerg";
description = "Daemon's state directory.";
};
orgDir = lib.mkOption {
type = types.path;
description = "Org roam directory.";
};
package = lib.mkPackageOption pkgs "doerg" {};
databasePath = lib.mkOption {
type = types.path;
description = "Org roam database path";
default = cfg.orgDir + "org-roam.db";
};
openFirewall = lib.mkOption {
type = types.bool;
description = "Open doerg ports?";
default = false;
};
};
config = lib.mkIf cfg.enable {
nixpkgs.overlays = [ self.overlays.default ];
systemd.services.org-roam-db-sync = lib.mkIf cfg.org-roam-db-sync.enable {
script = lib.escapeShellArgs [
(lib.getExe cfg.package.test-emacs)
"-Q" "-x" org-roam-db-sync cfg.orgDir cfg.databasePath
];
serviceConfig = {
Type = "oneshot";
ReadOnlyBindPaths = [
cfg.orgDir
];
};
};
systemd.timers.org-roam-db-sync = lib.mkIf cfg.org-roam-db-sync.enable {
unitConfig.StopWhenUnneeded = true;
timerConfig = {
OnActiveSec = "1h";
RandomizedDelaySec = "30m";
Persistent = true;
};
};
networking.firewall.allowedTCPPorts = lib.mkIf cfg.openFirewall [
cfg.port
];
systemd.services.doerg = {
after = [ "network-online.target" ];
wants = [ "network-online.target" "org-roam-db-sync.timer" ];
wantedBy = [ "multi-user.target" ];
environment.DOERG_CONFIG = doerg-config;
serviceConfig = {
# WorkingDirectory = cfg.stateDir;
StateDirectory = "doerg";
ExecStart = lib.getExe cfg.package;
DynamicUser = true;
ProtectSystem = "strict";
PrivateTmp = true;
BindReadOnlyPaths = [
cfg.orgDir
# cfg.databasePath
"/nix"
];
};
};
};
}

View File

@@ -1,6 +1,6 @@
#:net.deertopia.doerg.config
{:ibm-plex-web #or [#xdg-data-dir "ibm-plex-web"
#env IBM_PLEX_WEB]
{:ibm-plex-web #or [#env IBM_PLEX_WEB
#xdg-data-dir "ibm-plex-web"]
:latex "xelatex"
:dvisvgm "dvisvgm"
:doerg-temml-worker

View File

@@ -18,8 +18,10 @@
calling `compute` with no arguments only if stale? is logical true."
[& {:keys [file stale? compute]}]
(when (or (not *use-cache?*) stale?)
(let [r (compute)]
(let [r (compute)
dir (fs/parent file)]
(assert (string? r))
(fs/create-dirs (fs/parent file))
(when-not (fs/exists? dir)
(fs/create-dirs dir))
(spit file r)))
file)

View File

@@ -46,7 +46,8 @@
"Aero tag to search for a directory on $XDG_DATA_DIRS."
(some #(let [x (fs/path % value)]
(and (fs/exists? x) x))
(fs/split-paths (System/getenv "XDG_DATA_DIRS"))))
(some-> (System/getenv "XDG_DATA_DIRS")
fs/split-paths)))
(defmethod aero/reader 'file
[{:keys [source]} tag value]
@@ -66,9 +67,6 @@
(throw (ex-info "Failed to conform config"
(s/explain-data ::config r))))))
(defn load-config! [var spec files & {:as opts}]
(alter-var-root var (constantly (read-config files opts))))
(def sources
[;; Default config.
(io/resource "net/deertopia/doerg/default-config.edn")
@@ -80,3 +78,10 @@
(def default (read-config sources))
(def ^:dynamic *cfg* default)
(defn load-config!
([& {:as opts :keys [files]
:or {files sources}}]
(let [passthru-opts (-> opts (dissoc :files))]
(alter-var-root
#'*cfg* (constantly (read-config files passthru-opts))))))

View File

@@ -4,7 +4,6 @@
[babashka.process :as p]
[cheshire.core :as json]
[clojure.core.match :refer [match]]
[net.deertopia.doerg :as-alias doerg]
[clojure.java.io :as io]
[clojure.set :as set]
[clojure.string :as str]
@@ -35,7 +34,7 @@
:or {in *in*}}]
(let [r (-> (p/process
{:in in :out :string}
(-> cfg/*cfg* ::doerg/doerg-parser str))
(-> cfg/*cfg* ::cfg/doerg-parser str))
(common/deref-with-timeout *uniorg-timeout-duration*))]
(when (zero? (:exit r))
(-> r :out (json/parse-string (comp keyword camel->kebab))))))

View File

@@ -1,5 +1,8 @@
(ns net.deertopia.doerg.main
(:require [net.deertopia.doerg.server :as server]
[net.deertopia.doerg.config :as cfg])
(:gen-class))
(defn -main []
(println "hello from doerg"))
(defn -main [& _]
(binding [cfg/*cfg* (cfg/read-config cfg/sources)]
(server/start!)))

View File

@@ -63,16 +63,19 @@
(defn org-document
"Recursively render an Org-mode document to Hiccup."
[doc & {:as opts :keys [postamble]}]
[doc & {:as opts :keys [postamble header title]}]
(binding [*opts* opts]
(tex-temml/binding-worker
(let [rendered (-> doc gather-footnotes render-tex-snippets
org-element-recursive)]
[:html
[:head
[:title "org document"]
(when title
[:title title])
doerg-html/head]
[:body {:lang default-language}
(when header
[:header header])
[:article
rendered
(when postamble
@@ -273,11 +276,14 @@
(defmethod org-element "bold" [{:keys [children]}]
[:b children])
(defmethod org-element "strike-through" [{:keys [children]}]
[:s children])
(defmethod org-element "subscript" [{:keys [children]}]
[:sub children])
(defmethod org-element "superscript" [{:keys [children]}]
[:super children])
[:sup children])
(defmethod org-element "italic" [{:keys [children]}]
[:em children])
@@ -382,9 +388,18 @@
(defmethod org-keyword "TITLE" [{:keys [value]}]
[:h1 value])
(defmethod org-keyword "SUBTITLE" [{:keys [value]}]
[:p.subtitle value])
(defmethod org-keyword "LATEX_COMPILER" [_] nil)
(defmethod org-keyword "LATEX_HEADER" [_] nil)
;; TODO: A bunch of things I'd rather hide for now, but should be
;; implemented eventually.
(defmethod org-keyword "FILETAGS" [_] nil)
(defmethod org-element "planning" [_] nil)
(defmethod org-element "timestamp" [_] nil)
;; Not sure how to deal with this one yet.
(defmethod org-keyword "AUTHOR" [_] nil)

View File

@@ -35,7 +35,7 @@
(fs/create-dirs dest)
(fs/create-dirs resource-dir)
(force-create-sym-link (fs/file resource-dir "ibm-plex-web")
(-> cfg/*cfg* ::doerg/ibm-plex-web))
(-> cfg/*cfg* ::cfg/ibm-plex-web))
(doseq [x #{"Temml-Plex.css" "tuftesque.css" "deerstar.css"}]
(force-create-sym-link
(fs/file resource-dir x)

View File

@@ -133,11 +133,15 @@
elisp/read-alist)))
(defn public? [node]
(-> node properties (get "DEERTOPIAVISIBILITY") (= "public")))
(some-> (properties node) (get "DEERTOPIAVISIBILITY") (= "public")))
(defn get-public-node [node]
(when-let [n (get-node node)]
(when (public? n) n)))
(defn graph-visible? [node]
(#{"public" "graphonly"}
(-> node properties (get "DEERTOPIAVISIBILITY"))))
(some-> (properties node) (get "DEERTOPIAVISIBILITY"))))
(defn backlinks
"Returns a collection of nodes linking to `node`."
@@ -149,7 +153,8 @@
where links.dest = ?"
(elisp/print (str (:id node)))])
:let [id' (elisp/read-string id)]
:when (-> id' parse-uuid get-node public?)]
:when (some-> (-> id' parse-uuid get-node)
public?)]
(make-node id' {:title (elisp/read-string title)})))

View File

@@ -23,9 +23,7 @@
[net.deertopia.doerg.cached-file :as cached-file]
[babashka.fs :as fs]
[aero.core :as aero]
[clojure.string :as str]
[net.deertopia.doerg :as-alias doerg]
[net.deertopia.doerg.config :as doerg-config]))
[clojure.string :as str]))
;;; Routes
@@ -51,8 +49,13 @@
(defn html-dir []
(-> cfg/*cfg* ::cfg/state-directory (fs/file "html")))
(defn not-found [req]
(response/not-found "not found"))
(declare node-by-slug)
;; This could infinitely loop if the 404 page can't be found. lmfao.
(defn not-found [_req]
(-> (node-by-slug {:path-params {:slug not-found-slug}})
(assoc :status 404))
#_(response/not-found "not found"))
(defn org-file->html-file [org-file]
(fs/file (html-dir)
@@ -73,17 +76,34 @@
#_[:a {:href (str "/n/" (slug/from-uuid path))}
(or (seq children) raw-link)]])
(def navbar
"Hiccup element for Deertopia.net's navbar."
[:nav.navbar
[:ol.navbar-list
[:li
[:a.home-link {:href "/"}
"🦌 deertopia.net"]]
#_[:li
[:a.home-link {:href "/graph"}
"graph"]]
#_
[:li
[:a.home-link {:onclick "alert('unimplemented }:(')"}
"search"]]]])
(defn backlinks-postamble [node]
[:section#backlinks
[:h2 "Backlinks"]
[:ul
(for [n (->> (roam/backlinks node)
(sort-by (comp str/lower-case roam/title)))]
[:li (slug-link (roam/slug n)
(roam/title n))])]])
(let [backlinks (roam/backlinks node)]
(when-not (empty? backlinks)
[:section#backlinks
[:h2 "Backlinks"]
[:ul
(for [n (->> backlinks
(sort-by (comp str/lower-case roam/title)))]
[:li (slug-link (roam/slug n)
(roam/title n))])]])))
(defn node-by-slug [{{:keys [slug]} :path-params :as req}]
(if-some [node (some-> slug slug/from-string roam/get-node)]
(if-some [node (some-> slug slug/from-string roam/get-public-node)]
(let [org-file (roam/org-file node)
html-file (org-file->html-file org-file)]
(cached-file/cached-file
@@ -91,7 +111,9 @@
:stale? (cached-file/newer-than? org-file html-file)
:compute #(doerg-render/to-html
org-file
:postamble (backlinks-postamble node)))
:postamble (backlinks-postamble node)
:header navbar
:title (roam/title node)))
(-> (str html-file)
response/file-response
(response/content-type "text/html")))
@@ -118,7 +140,7 @@
(if-some [[_ resource] (re-matches #"^/resource/ibm-plex-web/(.*)" uri)]
(-> resource
(response/file-response
{:root (-> doerg-config/*cfg* ::doerg/ibm-plex-web str)}))
{:root (-> cfg/*cfg* ::cfg/ibm-plex-web str)}))
(-> uri
(str/replace-first #"^/resource/" "")
(response/resource-response

View File

@@ -83,14 +83,14 @@
acc))))
(defn- invoke-latex [& {:keys [file output-dir]}]
(let [latex (-> cfg/*cfg* ::doerg/latex)]
(let [latex (-> cfg/*cfg* ::cfg/latex)]
(invoke
{:dir output-dir}
latex "-no-pdf" "-interaction" "nonstopmode"
"-output-directory" output-dir file)))
(defn- invoke-dvisvgm [& {:keys [file output-dir]}]
(let [dvisvgm (-> cfg/*cfg* ::doerg/dvisvgm)]
(let [dvisvgm (-> cfg/*cfg* ::cfg/dvisvgm)]
(invoke
{:dir output-dir}
dvisvgm "--page=1-" "--optimize" "--clipjoin"

View File

@@ -25,7 +25,7 @@
fs/file))
(defn worker []
(let [doerg-temml-worker (-> cfg/*cfg* ::doerg/doerg-temml-worker)]
(let [doerg-temml-worker (-> cfg/*cfg* ::cfg/doerg-temml-worker)]
(when (or (not (fs/exists? prelude-file))
(zero? (fs/size prelude-file)))
(-> "net/deertopia/doerg/prelude.tex"