@@ 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.
@@ 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.
+
+
+
@@ 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 = "."
}