| ... | ... | @@ -0,0 +1,195 @@ |
| 1 | import './log.css'; |
| 2 | export default ({ content }) => <main>{content}</main> |
| 3 | export const meta = { title: 'clover\'s log' }; |
| 4 | |
| 5 | [go back to the home page](/) |
| 6 | |
| 7 | [git]: https://git.paperclover.net/ |
| 8 | [home infra]: https://git.paperclover.net/clo/home-infra |
| 9 | [name paint bot]: https://git.paperclover.net/clo/discord-name-painter |
| 10 | [todo tracker]: https://git.paperclover.net/clo/todo-tracker |
| 11 | [ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector |
| 12 | [progress.ts]: https://jsr.io/@clo/lib/doc/progress.ts |
| 13 | |
| 14 | # clover's log |
| 15 | |
| 16 | ## 2026-01-12 |
| 17 | |
| 18 | tags: [album] |
| 19 | |
| 20 | i started more music work. i've gotten better at lyric writing, phrasing this |
| 21 | new song as a sort of "adventure". felt for one of the first times that i was |
| 22 | doing worldbuilding in a song. the imagery is that good. |
| 23 | |
| 24 | ## 2026-01-11 |
| 25 | |
| 26 | tags: [progress.ts], [todo tracker] |
| 27 | |
| 28 | i finished streaming io on the @clo/lib/[progress.ts]. very proud of it. my git |
| 29 | commits describe the tech better than me reiterating. |
| 30 | |
| 31 | > ## feat(lib/progress): implement streaming wire protocol |
| 32 | > resolves `#47` |
| 33 | > |
| 34 | > `encodeByteStream` converts these events into a `ReadableStream`. by |
| 35 | > batching events together, the stream contents remain small, that way the |
| 36 | > code that constructs progress nodes do not have to worry about calling |
| 37 | > many setters at once, it gets debounced be the serializer. stream |
| 38 | > backpressure causes larger time-gaps to be batched (smaller). this |
| 39 | > enables servers to respond with rich progress. |
| 40 | > |
| 41 | > ``` |
| 42 | > const root = new progress.Root(); |
| 43 | > doActionWithProgress(root).then(root.end, root.error); |
| 44 | > // streaming clients indicate a header |
| 45 | > if (req.headers.get("Accept")?.includes(progress.contentType)) |
| 46 | > return new Response(progress.encodeByteStream(root), { |
| 47 | > headers: { 'Content-Type': progress.contentType }, |
| 48 | > }); |
| 49 | > // to support non-streaming clients |
| 50 | > return Response.json(await root.asPromise()); |
| 51 | > ``` |
| 52 | > |
| 53 | > and `decodeByteStream` on the client: |
| 54 | > |
| 55 | > ``` |
| 56 | > const output = document.getElementById("output"); |
| 57 | > const res = await fetch(...); |
| 58 | > if (!res.ok) throw ...; |
| 59 | > const root = new progress.Root(); |
| 60 | > root.on("change", (active) => { |
| 61 | > output.innerText = ansi.strip(progress.formatAnsi( |
| 62 | > performance.now(), |
| 63 | > active, |
| 64 | > )); |
| 65 | > }); |
| 66 | > const result = await progress.decodeByteStream(res.body, root); |
| 67 | > output.innerText = JSON.stringify(result); |
| 68 | > ``` |
| 69 | > |
| 70 | > there is currently no document bindings, but i plan to. additionally, a |
| 71 | > React hook is very trivial to implement for this -- but that is |
| 72 | > unplanned for this repository. for transports that require JSON or |
| 73 | > UTF-8, there is `encodeEventStream` which returns a `ReadableStream` of |
| 74 | > JSON objects which can be compressed at the developer's discretion. |
| 75 | |
| 76 | > ## feat(lib/progress): headless rendering + time estimation |
| 77 | > node signaling is done by providing a `progress.Root` to every node, |
| 78 | > dispatching events to it when the node changes. the root is connected to |
| 79 | > an observer to construct a UI out of it. there are two apis planned: |
| 80 | > |
| 81 | > - `attachToScreen` binds a root to a TTY screen (via the log.Widget API). |
| 82 | > the primary use of this is to implement the top level `progress.start`. |
| 83 | > |
| 84 | > - a serialization system that allows transmitting a `Root` over a wire. |
| 85 | > this commit was going to include this but it is an unexpectedly large |
| 86 | > component. |
| 87 | > |
| 88 | > - potentially a browser binding like `attachToDocument`. this will not |
| 89 | > be added in this patch. |
| 90 | > |
| 91 | > additionally, resolves #33 by implementing `estimatedTime` |
| 92 | |
| 93 | i also did a large part of the work to create a "code todo tracking" tool. i |
| 94 | would say it's about half done, since the second half is simply fixing all of |
| 95 | the little bugs there are. most of this code is currently ai-generated, but |
| 96 | with me manually coming in to write interfaces and the modular program |
| 97 | architecture. then i synthesize the code and the tests. this was basically just |
| 98 | going on ambiently while [progress.ts] was in progress. |
| 99 | |
| 100 | ## 2026-01-09 |
| 101 | |
| 102 | tags: [home infra] |
| 103 | |
| 104 | finished SSO sub-project. im happy with the setup i used to protect internal |
| 105 | services, such as pgadmin and qbittorrent. it's a caddy snippet that i can |
| 106 | re-use very easily. |
| 107 | |
| 108 | ``` |
| 109 | (reverse_proxy_auth) { |
| 110 | handle /snow.oauth2/* { |
| 111 | 		reverse_proxy "http://forward-auth" { |
| 112 | 			header_up X-Real-IP {remote_host} |
| 113 | 			header_up X-Forwarded-Uri {uri} |
| 114 | 		} |
| 115 | 	} |
| 116 | handle { |
| 117 | forward_auth "http://forward-auth" { |
| 118 | uri /snow.oauth2/auth |
| 119 | header_up X-Real-IP {remote_host} |
| 120 | @error status 401 |
| 121 | handle_response @error { |
| 122 | redir * /snow.oauth2/sign_in?rd={scheme}://{host}{uri} |
| 123 | } |
| 124 | @valid_group header X-Auth-Request-Groups *role:{args[1]}* |
| 125 | handle_response @valid_group { |
| 126 | method {method} |
| 127 | rewrite {uri} |
| 128 | reverse_proxy {args[0]} { |
| 129 | header_up Cookie ([^;]*?)\s*_oauth2_proxy_\d=[^;]*(;?.*) "$1$2" |
| 130 | {block} |
| 131 | } |
| 132 | } |
| 133 | handle_response { |
| 134 | rewrite /403.html |
| 135 | file_server { |
| 136 | status 403 |
| 137 | root /etc/caddy |
| 138 | } |
| 139 | } |
| 140 | } |
| 141 | } |
| 142 | } |
| 143 | |
| 144 | # usage |
| 145 | pg.{$HOME_DOMAIN} { |
| 146 | import reverse_proxy_auth "http://pgadmin" admin |
| 147 | } |
| 148 | qbt.{$HOME_DOMAIN} { |
| 149 | import reverse_proxy_auth "http://qbittorrent" media-manage |
| 150 | } |
| 151 | ``` |
| 152 | |
| 153 | |
| 154 | ## 2026-01-04 |
| 155 | |
| 156 | tags: [home infra] |
| 157 | |
| 158 | working on SSO for my internal services. for context, i have about 12 |
| 159 | self-hosted services running, half of which i allow my friends to access. |
| 160 | currently, this is done through manually creating an account on such service |
| 161 | (jellyfin, forgejo), but many are done through a caddy rule. in the interest of |
| 162 | making my password manager less confused (ip vs domain, subdomain etc), i'm |
| 163 | slowly reducing this setup to a single sign in page. |
| 164 | |
| 165 | to do this, i am using https://keycloak.org, which supports openid connect |
| 166 | (how i will configure forgejo and jellyfin), as well as a separate service to |
| 167 | provide forward auth proxying (how i protect services like copyparty, |
| 168 | syncthing, pgadmin, and many more). i tried authelia beforehand, but i really |
| 169 | do not recommend them due to how hard it is to configure, passkeys being |
| 170 | annoying to setup, and limited themes. i also dont recommend authentik, but i |
| 171 | couldnt figure out how to even start using it after i installed it. |
| 172 | |
| 173 | keycloak is a bit stupid on config. as all the config lies in the postgres |
| 174 | database, i can't use a config file to setup the primary realm. so instead, i |
| 175 | have this huge python script to use the API to upsert the configuration in. |
| 176 | this works pretty well, and means that for locally running the infrastructure |
| 177 | for testing, i can get the config to be the same (useful if you brick |
| 178 | keycloak, which is pretty easy to do). |
| 179 | |
| 180 | ## 2026-01-02 |
| 181 | |
| 182 | tags: [home infra], [git], [name paint bot] show |
| 183 | |
| 184 | i deleted all my github repositories except four: my "readme", a bug |
| 185 | reproduction repo, the mirror for [ts lie detector], and a shared private repo |
| 186 | with someone that is load bearing. in this process, i've moved all the projects |
| 187 | to my [forgejo instance][git]. |
| 188 | |
| 189 | with this, [name paint bot], one of my few remaining projects that is still |
| 190 | active, moves to that forgejo instance using their github migrator. some of my |
| 191 | private projects, like my pet scripting language, were migrated as well. it |
| 192 | feels more alive on my site because of the theming and per-repo icons. |
| 193 | |
| 194 | after a year of forgejo, i am really happy with how it treats me. |
| 195 | |