1. 71
    1. 14

      for example, say you want to print the visiting users IP address - how would you do this on a statically generated website?

      Personally? CGI. These are mostly exceptions. Most or your web site is still static. For dynamic elements, I would probably just use pp and create a static binary that would handle dynamic content.

      another example - i generate all of my RSS feeds on my homepage when my website starts up. this ensures that i get a fresh copy of my friends’ latest posts.

      Simple cron to update your index.html, again exception from presenting mostly static files.

      i decided that i would host a website on a machine that i controlled, and program in plain old HTML, and edit everything by hand using vim.

      ~ * ~ and so i gave birth to the worst workflow ever used ~ * ~

      the first draft of https://j3s.sh was built like this - it is where a lot of my ideas for the layout came from. the website still looks largely the same - writing HTML by hand felt a lot like doing intensive labor by hand - like dishwashing with a hand pump instead of a sink. it forced me into an insane level of minimalism in order to avoid the pain of having to use the workflow. i embedded styling directly into the HTML, made cross-site functions as minimal as possible, and shied away from much styling.

      in a lot of ways, this experience was exactly what i had hoped for. it was simple, reliable, and maintainable - the HTML i wrote could be reused without modification, and i felt pretty confident that my site would be around for awhile. i had no dependencies. but i grew very tired of the workflow.

      i wanted to handle “templating” of common functionality more efficiently. i didn’t want to use a pre-existing framework. i wanted to have fun. and i wanted to leave myself a lot of room for adjustment in case i changed my mind about this-or-that.

      Sounds like my way to building mkws. Started with plain HTML but the problem was duplication. Wasn’t happy with any templating system so I settled on sh via pp. It’s great at manipulating plain text and presenting it in as much of a human friendly way as possible, so just render that plain text inside HTML.

      mkws does the minimum required to build a static web site (generate HTML pages and make sure there’s little duplicated code), has a very powerful templating system, minimal set of dependencies (2, 3 if you add Markdown support and only because I preferred keeping stuff separated and not cramming every features in a single binary when there are already good existing web servers or Markdown processors or file change monitors like entr and everybody has their preferences) but you could just download the binary package and start building your web site skipping other downloads. Other dependencies are just a package manager install away but you can accomplish a lot with the standard UNIX tools. Also, it’s very customizable and very fun to use.

      none of the above felt correct to me. more importantly, none of the above excited me.

      For me personally, nothing feels “more correct” than serving plain HTML files.

      I personally believe it’s a bad idea to have HTTP serving code in my web site, also routing. For, me, a web site is just a set of HTML files.

      1. 4

        Simple cron to update your index.html, again exception from presenting mostly static files.

        I did use this at one point (i had a script called update-j3s), but i found programmatic XML manipulation to be very tedious (and my ability to make shell do what i wanted was… bad). i like my current solution far more, but can understand the drive behind doing it this way too. (i tried! :D)

        i love the fact that it’s innately tied to the website (the routing, the server, the html, and the rss reader) - maybe some people like things to be more single-purpose-unixy - but to me, it was less fun.

        Most or your web site is still static

        true! but any part can quickly become dynamic :3 i like maintaining that “freedom”

        edit: also i am a HUGE fan of mkws! so cool to see your comment on my little post. :D

        1. 3

          I did use this at one point (i had a script called update-j3s), but i found programmatic XML manipulation to be very tedious (and my ability to make shell do what i wanted was… bad). i like my current solution far more, but can understand the drive behind doing it this way too. (i tried! :D)

          Matter of preference in the end, works for you.

          i love the fact that it’s innately tied to the website (the routing, the server, the html, and the rss reader) - maybe some people like things to be more single-purpose-unixy - but to me, it was less fun.

          Fun is different for everybody, I agree. 😁

        2. 2

          edit: also i am a HUGE fan of mkws! so cool to see your comment on my little post. :D

          Thanks for enjoying my work! mkws is little as well, as code and as a project also. 😁

    2. 8

      This was a fun post.

      1. 4

        Also, I originally thought this would be a post about https://redbean.dev/

    3. 8

      Spreading the Fossil love. One binary and one file for all your needs: website, source control, wiki, tickets, forum, chat. Deploying changes is just a commit, if you are using the auto-sync mode.

      The downside is that its UI may be too spartan for some people’s tastes and they may need some time getting used to the system’s quirks. The documentation could be better with regards to web customisation too as it focuses on the source control bits way too much IMO.

      1. 6

        i ADORE fossil! (and sqlite for that matter!)

      2. 1

        This is exactly what I do for websites these days, just run fossil-scm as my website. It basically does everything I care about, most content is just a wiki page.

        1. 1

          AS your website? Can you share a link? I’m curious to see that.

          1. 3

            An example: https://ziefi.com/index

            These days, I generally buy a domain for each thing I care about and then turn on Fossil and dump content into it.

            Another example(without a link) I had a friend need lots of help with their house, so I turned up a domain(of their last name) with Fossil on it, and then I documented all the stuff I did, added files to version control of the diff. manuals for the various things I came across, etc. It was super handy, and now they have a documented list complete with manuals for everything I did on their property that will be there until I get tired of paying the domain bill(but they can always run it off the USB I gave them with a checked out version.

            1. 2

              this is awesome, i have never seen fossil used like this. :3

            2. 2

              Amazing! Thanks for the example site! I stole some ideas from your wiki markup!

              The lovely thing about these Fossil sites is that you can fossil clone them and tinker on your own. I’m not sure if that was actually your intent and you missed taking away a permission or something haha.

              1. 3

                It’s all public data(from my perspective), of course you can fossil clone! Feel free to send fixes to my particular site if you are bored!

            3. 1

              Ha! Amazing. That’s pretty much what I expected it to look like, honestly,

              It probably wouldn’t be that hard to write a few queries and turn fossil into a true static site generator, strip out the stuff you don’t need for the site like the forum, wiki, chat, etc. 🤔

              1. 2

                Why bother? I mean the whole point from my perspective, I can get more than I need complete and ready to go in 5m or less, with basically no maintenance work, ever.

    4. 6
          #!/bin/sh -eu
          # deploy my website
          cd /home/j3s/code/j3s.sh
          if git fetch origin; then
            go build
            mv j3s.sh /usr/local/bin/j3s.sh
            service j3s.sh restart

      It’s kind of disingenuous to say your website is one binary, when you’re running a script with 6 obvious dependencies every minute, to work around the fact that it’s not fully functioning by itself (eg, it needs to be rebuilt and redeployed).

      1. 3

        i can see how you could see it that way. my intent was to highlight the binary as a fun unit to play with, not to be exactly technically correct.

        to work around the fact that it’s not fully functioning by itself

        fun fact, it doesn’t need to be rebuilt and redeployed if you are only changing the content of your HTML templates - if you update them, it’ll be reflected immediately. it’s a part of my workflow i haven’t really tackled yet but i could definitely differentiate “binary updates” from “content updates” if i ever cared to do that (probably not).

        1. 3

          fun fact, it doesn’t need to be rebuilt and redeployed if you are only changing the content of your HTML templates

          Very useful. Would be rad if those files could be processed, something… I don’t know, a hypertext preprocessor, so they themselves could be the source of dynamic content!

    5. 5

      My first website was artisanally handcrafted this way on vim as a set of files on our departments server in my user directory. I do not miss it except through the distorted lens of nostalgia.

      I too like to understand everything about what I use. I gave up when I saw a photo of an Intel chip layout. I now resign myself to being an illiterate wielder of powerful magic.

    6. 4

      My website is about 30 lines of shell; I guess I need to brag now: http://len.falken.directory/misc/writing-for-the-internet-across-a-human-lifetime.txt

      1. 2

        looks like http://len.falken.ink is a bad link in yout text

        1. 1

          Yeah, I refused to pay $40 to renew the .ink domain so I let it expire and bought a super cheap .directory domain.

          1. 1

            Considering your old domain is now scammy as hell you probably did the right thing… but you should update old links.

            1. 1

              Good point; will do :)

      2. 1

        Hard wrapped text looks weird on a screen that’s narrower than the chosen width.

      3. 1

        Your site is a little gem!

        Not only plain text, but some posts are valid Prolog programs!

        I must admit find it is a little too radical to not have any clickable links, so I find difficult to reproduce your style in my own page; on the other hand, your site is easily read in all the platforms and a browsers I use, including lynx(1)!

        Very well done!

        1. 1

          Double click -> Right click -> open in new tab. Browsers could be smarter and highlight links in text files.

          1. 2

            Certainly it is not a problem on the desktop.

            But that workflow is more complicated on lynx(1), where I have to trigger tmux(1), navigate to the part of the page it has the link, copy it, then paste it on the command line to open a page inside lynx(1).

            Given you are using plain text files, I imagined you had TUI browsers in mind. It does not seem the case. (In fact, I just noticed your landing page is a RSS feed, which is nicely rendered in the desktop, but not much on mobile, lynx(1) or w3m(1).)

            Anyhow… I liked it quite a lot! 🙂

    7. 4

      Since this is go you can also trivially embed your templates etc in the binary :)

      1. 1

        good call! i definitely need to do this!

    8. 3

      I’m still totally bought into the static site generation idea, but mine is handwritten by me personally.

      My opinion is that static site generators are so easy to write that it’s no more work to make one from scratch than to learn an off the shelf one - if you’re already a programmer.

    9. 3

      i love this article, it’s genuinely unclear to me if it

      is ironic.

      1. 1

        as the author, i can confirm that it’s not ironic!

        1. 1

          So, off topic, but why don’t you capitalize ‘I’? Also is the broken line wrapping deliberate? It… Sends a vibe…

          1. 4

            OP writes only in lower case - why would you expect him or her to capitalize that one letter? :)

          2. 3

            the line wrap breakage is not intentional & i’ll try and make it better :3

            it’s like me asking you why you used so many ellipses in your sentence - idk, stylistic choice. i like lowercase. :)

            1. 1

              csee cummings :)

    10. 3

      This should honestly probably be more common than it is, now that I think of it.

    11. 3

      i spend a lot of time choosing my things, and i refuse to depend on anyone else

      I hope I never have to work with this person.

      1. 3

        you might like me more than you think. :3 decoupling software from arcane needless processes is something that a lot of companies and people love. and it’s work that i find very fulfilling.

        i do not mean that i would never ask a question - i love pairing and cooperation. what i do mean is that if a process of piece of software depends on anyone other than me/my team, i tend to examine those dependencies with a lot of scrutiny. half of the time, they’re not needed.

        for example, at $dayjob, i recently updated an internal tool that relied on manually created user accounts. i realized that this could be done dynamically instead. stuff like that.

        but in my personal life, i’m a lot more extreme because i can afford to be :)

    12. 2

      maybe it’s just the operator in me thinking, but i’ve thought for a while you can do pretty powerful, even enterprise grade webapps, with just a true single go binary (compile the templates into the binary) and an sqlite database alongside… you could even use some caddy libraries to do the SSL and perhaps auto let’s encrypt for SSL, just open ports 80 and 443 for your redirects, challenges, and stuff like that, an auth provider if needed… and that’s the operational overhead…

      database backups can happen via the web (either via ui or api calls), and if your use case requires more, litestream is there to stream in real time…

      if object storage is needed, S3 apis are pretty commoditized now too, i wonder what percentage of web apps in use globally could be switched to this model… i imagine most

      i wish i had the skills to create that, as i have some ideas for some fairly straightforward CRUD apps w/ < 1000 users that it seems to me i should have no problem running on a basic VPS, more users should be more than possible as long as the code is efficient

      i’d like to devote some more time to that ideal but i feel like that’s a great stack… it seems like the toolkit is all there to do it…

      maybe we can coin the new stack name… instead of the LAMP stack for linux, apache, mysql and php, we can call it the xSG stack…. xGS rolls off the tongue better,

      x (any OS due to Go’s cross compilation abilities)
      G, golang
      S, sqlite

      S standing for sqlite’s a bit of a stretch (microsoft sql server?)

      maybe “xGSqlite” isn’t too long, but that’s like 5 syllables vs. LAMP’s 1….

      maybe 2 binaries… one frontend routing traffic, and 1 with your webapp, this way your frontend binary could care for upgrades and could do seamless “blue/green” type deployments with your continuously integrated/delivered webapp, but part of me says a little downtime for upgrades and db migrations is not the end of the world, not for your vast majority

    13. 2
      if git fetch origin; then...

      Are you sure this works? Git fetch just puts the new commits from the origin into the .git folder, it doesn’t update the working copy of the files. To me it seems like you would be building the previous version every time.

      Also if you like single binaries, check out MirageOS. You can compile your server along with its entire operating system into a single binary (with almost all Unix legacy cruft ruthlessly cut out), then run it on bare KVM.

      1. 1

        ah, good catch - that was some psuedocode that i wrote on the fly. i’ve updated the post with the code that i actually use.

        mirageos looks very interesting! i’ve been toying with oasis linux recently - you might find it interesting: https://github.com/oasislinux/oasis

    14. 2

      Hey, I really enjoyed reading your article and discovering your website, along with your other projects. We have very similar taste it seems. I’m going to email you! :D

      1. 1

        looking forward to it :3

    15. 2

      how do you handle TLS?

      edit: ah, thanks to @jjasghar finding the source, I can infer that TLS is handled by a proxy(?), so it’s not really “my website is one binary”, but close enough I guess.

      1. 3

        yep - it’s handled by tlstunnel1 currently - i would like to absorb this into the binary as well. but yes, good point - the reverse proxy is not a part of the binary yet! i would like it to be at some point, potentially.

        1. 2

          huh TIL about tlstunnel… that looks really interesting

        2. 2

          Ha! I was planning to build one of those myself as I wasn’t happy with stunnel. Thanks! I thought I read stunnel in the source code.

        3. 1

          Can you please post your /etc/tlstunnel/config?

          1. 1

            sure! the documentation was a little tough to swallow, so here’s an excerpt that includes a few different use cases:

            i use a companion tool called “kimchi” for some services (by the same author), but you may choose not to depending on how you want your setup to work.

            # standard reverse proxy use-case
            frontend j3s.sh:443 {
                backend tcp+proxy://localhost:80
            # optionally, you could add a kimchi config like this to add some proxy headers (this does add another dep):
            site j3s.sh {
                    reverse_proxy http://localhost:4666
            # redirecting
            # tlstunnel handles certs & TLS
            frontend desertrosetattoo.com:443 {
                backend tcp+proxy://localhost:80
            # kimchi handles redirection
            site desertrosetattoo.com {
                    redirect https://www.facebook.com/desertrosetattoo/
            # direct to backend
            frontend existentialcrisis.sh:443 {
                backend localhost:4357

            my biggest hangup was kimchi requiring tcp+proxy as opposed to just proxy for the cases where i wound up using it.

    16. 2

      here’s the actual code: https://git.j3s.sh/j3s.sh/tree

      It’s clever, not gonna lie.

    17. 2

      I’ve done a single binary website a few times before too, heck my dpldocs.info website is one main binary plus one html archive per subdomain, but I don’t often actually go all the way to one binary anymore because the deployment of small changes gets more complicated - it means actually rebuilding the server. (btw the article’s “compilation speed is fast (less than 10s most of the time)” makes me laugh, since I consider a 3s full rebuild on my computer (a budget machine i got in 2015) to be my upper limit of tolerance… a 10s build would be painfully slow to me)

      I don’t want to wait for the 2 1/2 second server rebuild and restart to see my change, so what I typically do is have the server binary load html templates from a runtime directory on demand. I actually have an open source example (though I haven’t pushed to it for a while, I so rarely git commit things lol, still good enough for this illustration): https://github.com/adamdruppe/ffr-bingo

      The server serves its dynamic functions as well as files out of the assets folder (which are just plain css/js/images) and the templates folder, which are HTML fragments the server pieces together. It opens the template requested and the skeleton.html file and merges them; the server does skeleton.querySelector("main").replaceWith(template.querySelector("main") (basically). So then the shared stuff is shared and the rest just injected at runtime so I can edit those easily and hit refresh to instantly see the changes - no recompile lag (which is about 2.5s again but that’s 2.45s longer than I want to wait).

      I’m still not completely happy with it, but I do find it overall works pretty well. (and btw, yes, I wrote all the code myself, from the webserver code to the script interpreter to the server-side dom and template implementation, that code is all in here https://github.com/adamdruppe/arsd/ if you’re interested, cgi.d is the web server, dom is the dom (lol), webtemplate.d does the piecing together of the template directory, etc)

      edit: I should mention actually I prefer doing the traditional cgi instead of the web server for a lot of things too since then the server doesn’t even need to be restarted! but meh, that bingo site needs long-lived connections, so traditional cgi model didn’t fit that as well.

    18. 2

      Nice work :)

      My website is written in assembly o/

      1. 2

        Did you port your wiki/interlinked system thing to Uxn then?

        1. 2

          Yeah, the wiki assembles to a 1200 bytes rom, and generates about 500 pages in 400ms on a 12 years old laptop.


          It can be assembled with the self-hosted assembler


          1. 1

            Very nice.

      2. 2

        omg HI DEVINE! you’re such a huge inspiration of mine. it’s amazing to see you comment on my post :D

        i love hundred rabbits to death. it’s what has inspired me to take my life into my own hands. i’m sure you’re aware, but rekka made my website’s pufferfish logo :3

        so cool to see you. i hope you’re doing well!

        1. 3

          Rekka showed me your website and I loved it instantly, I was so happy to see your little pufferfish show up on HN/Lobsters yesterday :) Looking forward to see how it evolves.

          We have a this little webring, if you’re ever keen to join, lemme know!

          1. 1

            yes i am very keen! feel free to contact me via email! (or this thread is fine too but i check lobsters pretty randomly :D)