~aleteoryx/webjrnl

a71a7b3f51c5b99819e2bf3b630f3290e4a3f7b1 — Aleteoryx 22 days ago 48b0876
-proxied, readme
3 files changed, 161 insertions(+), 5 deletions(-)

A COPYING
A README.md
M jrnl.go
A COPYING => COPYING +13 -0
@@ 0,0 1,13 @@
Copyright (C) 2026 by Aleteoryx <alyx@aleteoryx.me>

Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
SOFTWARE.

A README.md => README.md +133 -0
@@ 0,0 1,133 @@
# webjrnl

minimal HTML4 journalling program, written in golang

features:

- filesystem database
- javascript-free
- you can probably use this on anything with a TCP stack
- versioning system

I wrote this so I could edit my journal (a text file) from multiple
computers. it is basically a system for versioning and editing sections
of a specially-formatted text file


## installing

```
$ go build -ldflags '-w -s'
$ mkdir db
$ echo '[{"id": "marmalade", "passhash": "..."}]' >db/users.json
$ ./jrnl
usage: ./jrnl [-proxied] addr [mtpt]
$ ./jrnl :8080 db
initialized!
```

this is untested in go versions < 1.24.5. if you successfuly compile it
in an earlier version, let me know and I will update go.mod. it has
been tested on linux, but should have no problems running on other
unixes, plan 9, or even windows.

webjrnl is a single executable, jrnl. it takes 2 arguments: the address
on which to listen, and the database location. if the second argument
is omitted, it is assumed to be `.`

if you plan to run webjrnl behind a reverse proxy, pass it the -proxied
flag. this will tell it to read the X-Forwarded-For header when writing
logs.

the database has the following layout:

```
db/
	users.json - the list of users
	{userid}/{year}/{month}/{day}/ - the entry for a given date
		latest - contains the ID of the latest edit, in base 10
		{id} - the full text of edit id
```

users.json contains an array, each element of which corresponds to a
jrnl. it must contain id and passhash. the former is the jrnl username,
the latter is the SHA-1 hash of the user's password. it may also
contain a timezone field, the value of which should be an IANA time
zone name, like "UTC" or "America/New_York". this timezone is used when
displaying dates in the UI.

users.json is read once, at startup. if you modify it, you will need to
restart the service.

no caching of the database is performed. for a performant experience,
make sure your kernel is configured to cache the database directory in
RAM. 

webjrnl is written in such a way that multiple instances can be pointed
to the same database, and access it concurrently without risk of
corruption.


## usage

jrnl text is mostly free-form ASCII text, delimited by lines of the
form `> YYYY-MM-DD`. in any text box accepting jrnl text, you may
insert such a line anywhere to start a new entry. webjrnl will sort it
into reverse chronological order, once it is submitted.

webjrnl does not delete anything. when an entry is edited, the new text
is saved as the latest revision. it is not possible to delete an entry
or revision, without hand-editing the database.

a typical jrnl excerpt looks something like:

```
> 2026-04-26

= 1725, cafe =

got webjrnl mostly together, just need to write the docs.

...


> 2026-04-25

= 1020, desk =

I'm SO EXCITED for today!!!

...
```

the homepage, /dash, has links to a list of entries, an import tool, a
backup tool, and each year's page. if there is no entry for today or
yesterday, buttons to create them are also presented.

the import tool accepts jrnl text, and populates the database with it.
the backup tool exports the entire database as jrnl text, including all
revisions. it can be accessed programatically at /save?download

year pages are populated with the jrnl text for a given year, and have
a menu to navigate to an entry's page. this is intended as the primary
editing frontend.

entry pages allow you to change an entry's text, and contain a list of
previous revisions. this is not jrnl text, and does not parse date
headings. if you include one, it may lead to unexpected results the
next time the entry is parsed as part of jrnl text


## security

some. in particular, POST requests from a different Referer or Origin
are blocked, as are all javascript requests from different sites,
assuming the browser supports the `Sec-Fetch-*` set of headers

HTTP basic authentication is very much so insecure over an unencrypted
connection.

the contents of the database are stored in plaintext.




M jrnl.go => jrnl.go +15 -5
@@ 13,7 13,7 @@ func fatal(err error) {
}

func usage() {
	fmt.Fprintln(os.Stderr, "usage: jrnl addr [mtpt]")
	fmt.Fprintln(os.Stderr, "usage: jrnl [-proxied] addr [mtpt]")
	os.Exit(1)
}



@@ 54,13 54,23 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}

func main() {
	if 2 > len(os.Args) || len(os.Args) > 3 {
	if 2 > len(os.Args) {
		usage()
	} 
	}

	os.Args = os.Args[1:]
	if os.Args[0] == "-proxied" {
		proxied = true
		os.Args = os.Args[1:]
	}

	if 1 > len(os.Args) || len(os.Args) > 2 {
		usage()
	}

	addr := os.Args[1]
	addr := os.Args[0]
	if len(os.Args) == 3 {
		Mtpt = os.Args[2]
		Mtpt = os.Args[1]
	} else {
		Mtpt = "."
	}