commit af6e41db7a340305c03645d9a8f9eb1401487c38
parent 947a1c1373cd834513356dbb32b20c3d425af45c
Author: Daniel Barlow <dan@telent.net>
Date: Mon, 6 Oct 2025 22:24:33 +0100
logshippers: add victorialogsend script
it's only very lightly tested but it seems to work. the _stream
may be quite wrong, or perhaps that's a local admin decision anyway
Diffstat:
2 files changed, 96 insertions(+), 14 deletions(-)
diff --git a/pkgs/logshippers/default.nix b/pkgs/logshippers/default.nix
@@ -11,6 +11,14 @@
}:
let
name = "logshippers";
+ luafy = name : source :
+ writeFennel name {
+ packages = [ anoia lualinux fennel ];
+ macros = [ anoia.dev ];
+ mainFunction = "run";
+ } source;
+ incz = luafy name ./incz.fnl;
+ victorialogsend = luafy name ./victorialogsend.fnl;
in
stdenv.mkDerivation {
inherit name;
@@ -21,22 +29,13 @@ stdenv.mkDerivation {
buildPhase = ''
fennelrepl --test ./incz.fnl
- cp -p ${
- writeFennel name {
- packages = [
- anoia
- lualinux
- fennel
- ];
- macros = [
- anoia.dev
- ];
- mainFunction = "run";
- } ./incz.fnl
- } ${name}
+ fennelrepl --test ./victorialogsend.fnl
+ cp -p ${incz} incz
+ cp -p ${victorialogsend} victorialogsend
'';
installPhase = ''
- install -D ${name} $out/bin/${name}
+ install -D incz $out/bin/incz
+ install -D victorialogsend $out/bin/victorialogsend
'';
}
diff --git a/pkgs/logshippers/victorialogsend.fnl b/pkgs/logshippers/victorialogsend.fnl
@@ -0,0 +1,83 @@
+(local { : base64 : assoc } (require :anoia))
+(local tai64 (require :anoia.tai64))
+(local ll (require :lualinux))
+(import-macros { : expect= : define-tests } :anoia.assert)
+
+(local crlf "\r\n")
+
+(fn chunk [str]
+ (let [len (# str)]
+ (string.format "%x%s%s%s" len crlf str crlf)))
+
+(fn parse-url [str]
+ ;; this is a very poor parser as it won't recognise
+ ;; credentials in the authority and it lumps query-string/fragment
+ ;; into the path
+ (let [(scheme host path)
+ (string.match str "(.-)://(.-)(/.+)")]
+ { : scheme : host : path }))
+
+(define-tests
+ (expect= (parse-url "https://www.example.com/stairway/to/heaven")
+ { :scheme "https"
+ :host "www.example.com"
+ :path "/stairway/to/heaven"
+ }))
+
+(fn parse-args [args]
+ (case args
+ ["--basic-auth" auth & rest]
+ (assoc (parse-args rest) :auth auth)
+
+ [url] { :url (parse-url url) }
+ _ (error "invalid args")))
+
+
+(fn http-header [host path auth]
+ (let [b64 (base64 :url)
+ authstr
+ (if auth
+ (string.format "Authorization: basic %s\n" (b64:encode auth))
+ "")]
+ (string.format
+ "POST %s HTTP/1.1\r
+Host: %s\
+%sTransfer-Encoding: chunked\r
+\r
+"
+ path host authstr)))
+
+(fn format-timestamp-rfc3339 [timestamp prec]
+ (let [(sec nano) (-> timestamp tai64.from-timestamp tai64.to-utc)
+ subsec (string.sub (string.format "%09d" nano) 1 prec)]
+ (.. (os.date "!%FT%T" sec)
+ "." subsec
+ "Z")))
+
+(define-tests
+ (expect= (format-timestamp-rfc3339 "@4000000068e2f0d3257dc09b" 9)
+ "2025-10-05T22:26:54.628998299Z"))
+
+
+
+(fn process-line [line]
+ (let [(timestamp hostname service msg) (string.match line "(@%x+) (%g+) (%g+) (.+)$")]
+ (->
+ (string.format
+ "{%q:%q,%q:%q,%q:%q,%q:%q}\n"
+ :_time (format-timestamp-rfc3339 timestamp)
+ :service service
+ :_msg msg
+ :host hostname)
+ chunk)))
+
+(fn run []
+ (let [{ : auth : url } (parse-args arg)]
+ (ll.write 1 (http-header url.host url.path auth))
+ (while (case (io.stdin:read "l")
+ line (ll.write 1 (process-line line))))
+ (ll.write 1 (chunk "")))
+ (io.stderr:write (ll.read 0)))
+
+
+{ : run }