| ... | ... | @@ -12,9 +12,212 @@ export const meta = { title: 'clover\'s log' }; |
| 12 | 12 | [this site]: https://paperclover.net/ |
| 13 | 13 | [todo tracker]: https://git.paperclover.net/clo/todo-tracker |
| 14 | 14 | [ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector |
| 15 | [evil inc]: https://evil.inc/ |
| 16 | [history of japan reanimated]: /history-of-japan-reanimated |
| 17 | [react mutation]: /history-of-japan-reanimated |
| 15 | 18 | |
| 16 | 19 | # clover's log |
| 17 | 20 | |
| 21 | these are like mini blog posts, but more in generally just what the heck i'm up to. |
| 22 | |
| 23 | ## 2026-02-06 |
| 24 | |
| 25 | spent yesterday and today cathing up with this page and preparing to get |
| 26 | writing heavy on the blog. i also fixed a bug on the video player where it |
| 27 | wouldnt work on latest chrome on non-av1 accelerated hardware. |
| 28 | |
| 29 | there were two bugs: |
| 30 | |
| 31 | - chrome without av1 and version >=142 would try and play the hls stream |
| 32 | natively. they made `video.canPlayType("application/x-mpegURL")` return |
| 33 | "maybe", which is total propaganda because chrome does not actually support |
| 34 | this type of stream. they support some insane subset that isn't real world |
| 35 | use cases. |
| 36 | - hls polyfill was never being used because of a bundling mistake, so those |
| 37 | users would get the unoptimized original file. |
| 38 | |
| 39 | since i was testing windows 7, decided to also fix some IE9 bugs. the page |
| 40 | works besides the video player and the fonts, which is all things i believe i |
| 41 | could easily fix, so i opened new issues for those tasks. |
| 42 | |
| 43 | ## 2026-02-04 |
| 44 | |
| 45 | tags: [this site] |
| 46 | |
| 47 | i did a ton of random stuff to [this site], including the RSS feed and random |
| 48 | bug fixes on the domain. trying to reduce the issue count on my forgejo. |
| 49 | |
| 50 | ## 2026-02-03 |
| 51 | |
| 52 | tags: [history of japan reanimated] |
| 53 | |
| 54 | immediately after the youtube premiere, i worked on my webpage of it. the live |
| 55 | credits was a really fun touch that really completes things. i'm very happy |
| 56 | with the final result. |
| 57 | |
| 58 | i also had to do a ton of work fixing the video player to properly show |
| 59 | thumbnails on the video player. the biggest one was adding even more hell to |
| 60 | the dash-av1 player. a issue that `dash.js` has is providing the first frame of |
| 61 | media before the user presses "play." this doesn't happen with the hls or |
| 62 | native players. so my workaround... delay attachment of the view. |
| 63 | |
| 64 | ``` |
| 65 | player.initialize(null, dashFile, false); |
| 66 | player.updateSettings({ |
| 67 | streaming: { cacheInitSegments: true }, |
| 68 | }); |
| 69 | player.preload(); |
| 70 | |
| 71 | video.addEventListener("play", function play() { |
| 72 | video.removeEventListener("play", play); |
| 73 | |
| 74 | enableSafariAirplay(); // <-- another important hack |
| 75 | player.attachView(video); |
| 76 | onCloverVideoInit?.(id, video); |
| 77 | }); |
| 78 | video.controls = true; |
| 79 | ``` |
| 80 | |
| 81 | but on chrome you get cooked because a video without a source is not playable. |
| 82 | so a second hack used to attach a fake source. |
| 83 | |
| 84 | ``` |
| 85 | const duration = new Promise<number>((resolve) => { |
| 86 | player.on(dashjs.MediaPlayer.events.MANIFEST_LOADED, (e) => { |
| 87 | const duration = e.data.mediaPresentationDuration; |
| 88 | resolve(duration); // seconds |
| 89 | }); |
| 90 | }); |
| 91 | mediaSource = new MediaSource(); |
| 92 | mediaSource.addEventListener("sourceopen", () => { |
| 93 | duration.then((duration) => { |
| 94 | mediaSource = mediaSource!; // typescript assertion |
| 95 | mediaSource.duration = duration; |
| 96 | const sb = mediaSource.addSourceBuffer('video/webm; codecs="vp8"'); |
| 97 | const oneFrame = Uint8Array.from( |
| 98 | atob("GkXfo59ChoEBQveBAULygQRC84EIQoKEd2VibUKHgQJChYECGFOAZwEAAAAAAAITEU2bdLpNu4tTq4QVSalmU6yBoU27i1OrhBZUrmtTrIHWTbuMU6uEElTDZ1OsggEjTbuMU6uEHFO7a1OsggH97AEAAAAAAABZ" + "A".repeat(119) + "VSalmsCrXsYMPQkBNgIxMYXZmNjIuMy4xMDBXQYxMYXZmNjIuMy4xMDBEiYhAXgAAAAAAABZUrmvIrgEAAAAAAAA/14EBc8WIDV7YG1KxA/CcgQAitZyDdW5kiIEAhoVWX1ZQOIOBASPjg4QCYloA4JCwgQG6gQGagQJVsIRVuYEBElTDZ/tzc59jwIBnyJlFo4dFTkNPREVSRIeMTGF2ZjYyLjMuMTAwc3PWY8CLY8WIDV7YG1KxA/BnyKFFo4dFTkNPREVSRIeUTGF2YzYyLjExLjEwMCBsaWJ2cHhnyKFFo4hEVVJBVElPTkSHkzAwOjAwOjAwLjEyMDAwMDAwMAAfQ7Z11eeBAKOigQAAgBACAJ0BKgEAAQALxwiFhYiFhIg/ggAMDWAA/ua1AKOVgQAoALEBAC8R/AAYABhYL/QAJAAAo5WBAFAAsQEALxH8ABgAGFgv9AAkAAAcU7trkbuPs4EAt4r3gQHxggGj8IED"), |
| 99 | (x) => x.charCodeAt(0), |
| 100 | ); |
| 101 | sb.appendBuffer(oneFrame); |
| 102 | }); |
| 103 | }); |
| 104 | objectUrl = URL.createObjectURL(mediaSource); |
| 105 | |
| 106 | mainSource = document.createElement("source"); |
| 107 | mainSource.src = objectUrl; |
| 108 | video.appendChild(mainSource); |
| 109 | ``` |
| 110 | |
| 111 | the payload within contains a single webm vp8 frame, one pixel by one pixel. |
| 112 | this is enough to get chrome to shut up. now the experience is nearly perfect, |
| 113 | with browser support all the way to the ancient firefox version on my old |
| 114 | laptop, to modern Apple Silicon Macs. |
| 115 | |
| 116 | ## 2026-01-31 |
| 117 | |
| 118 | tags: [history of japan reanimated] |
| 119 | |
| 120 | main section of animation for the project was already done, but i wanted to |
| 121 | make some needed adjustments to my scene. so i had been doing those for a few |
| 122 | days, as well as animating an extra 25 seconds for the outro section. it was |
| 123 | very fun doing that process, since i actually grabbed an old macbook i had in |
| 124 | storage to take the textedit screenshot. more details on this entire process |
| 125 | will be in the project page. |
| 126 | |
| 127 | ## 2026-01-30 |
| 128 | |
| 129 | tags: [react mutation] |
| 130 | |
| 131 | at work, one of my coworkers was complaining about my helper functions for |
| 132 | TanStack Query's mutation system. the conclusion was that the mutation system |
| 133 | we were building on was flawed and not enjoyable to use. so i spent a few days |
| 134 | making an alternative library. it's on the JSR: [@clo/react-mutation](https://jsr.io/@clo/react-mutation). |
| 135 | |
| 136 | this project was really fun because of how much the API surface changed as i |
| 137 | started staging the library into our actual code. the experience has shaped how |
| 138 | i want to go about my upcoming blog post for the better. |
| 139 | |
| 140 | ## 2026-01-25 |
| 141 | |
| 142 | tags: [ts lie detector], [todo tracker] |
| 143 | |
| 144 | added a trivial binary to the lie detector, `tsld-node`, which is like `ts-node` or `tsx` but it runs with the lie detector. |
| 145 | |
| 146 | for todo tracker, it's been vibe coded to a point where the sitegen repo gets |
| 147 | through all commits, but there are still some issues with missing TODOs. i haven't really had time to prioritize this, even with an ai agent writing most of it, since even then i have to review the progress of it, so it's just not worth my time until other projects pull through. |
| 148 | |
| 149 | ## 2026-01-24 |
| 150 | |
| 151 | tags: [git], [home infra] |
| 152 | |
| 153 | i moved forgejo off of sqlite and onto postgres. the only motiviation behind this was to easily backdate the repositories i had imported: [name paint bot] and a scripting language compiler i wrote. i was more comfortable doing this on a real database server than just editing the file. |
| 154 | |
| 155 | the migration took a bit for me to figure out, but last year someone named Sven [did the same thing](https://sven-seeberg.de/wp/?p=1213), and found this `pgloader` command with the critical `data only` clause. |
| 156 | |
| 157 | ``` |
| 158 | echo "LOAD DATABASE |
| 159 | FROM sqlite:///root/forgejo.db |
| 160 | INTO postgresql://forgejo:$POSTGRES_PASSWORD_FORGEJO@postgres/forgejo |
| 161 | WITH data only, reset sequences, prefetch rows = 10000 |
| 162 | SET work_mem TO '16MB', maintenance_work_mem TO '512MB';" > ./pgloader-command |
| 163 | |
| 164 | pgloader ./pgloader-command |
| 165 | ``` |
| 166 | |
| 167 | ## 2026-01-18 |
| 168 | |
| 169 | tags: [git], [home infra] |
| 170 | |
| 171 | i finally setup system integrated ssh push, but with a twist. |
| 172 | |
| 173 | the first problem is having two ssh servers on the same machine, one for the |
| 174 | host, and another for Forgejo. this means that one of them had to live on |
| 175 | another port, so i chose to move the host. but it kind of just sucks to use |
| 176 | this. the proper solution is to use a custom config to direct the `git` user to |
| 177 | the right place. |
| 178 | |
| 179 | what made this harder is i actually had two git instances; the second one is |
| 180 | for a temporary infrastructure i'm running for [evil inc] until the |
| 181 | organization gets dedicated hardware (more about this in a future post). so |
| 182 | even if i routed `git` specially, it still wouldnt know which git server to go |
| 183 | to. |
| 184 | |
| 185 | **the solution**: write a custom "router" script that generates an |
| 186 | `authorized_keys` file based on which git instances actually have a key, then |
| 187 | the line within the `authorized_keys` forces a special wrapper which intercepts |
| 188 | the targetted git repository, routing it to the git instance with it. |
| 189 | |
| 190 | the sshd config looks like |
| 191 | |
| 192 | ``` |
| 193 | Match User git |
| 194 | AuthorizedKeysCommand /bin/python /mnt/storage1/apps/home-infra/config/forgejo/ssh/keys.py %u %t %k |
| 195 | AuthorizedKeysCommandUser git |
| 196 | ``` |
| 197 | |
| 198 | it is convenient because `AuthorizedKeysCommand` is run on every connection. it produces a file with zero or one valid keys: |
| 199 | |
| 200 | ``` |
| 201 | command="/mnt/storage1/apps/home-infra/config/forgejo/ssh/route.sh --clover 1 --evil 2",no-port-forwarding,...,restrict ssh-ed25519 AAAAC3NzaC... |
| 202 | ``` |
| 203 | |
| 204 | and then the route script does this shit: |
| 205 | |
| 206 | ``` |
| 207 | if [ -d "/mnt/storage1/apps/forgejo/git/repositories/${repo}" ]; then |
| 208 | [ -z "$clover_id" ] && echo "ssh key is not configured for repository on git.paperclover.net" >&2 && exit 1 |
| 209 | exec sudo docker exec -i -u git forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$clover_id" |
| 210 | elif [ -d "/mnt/storage1/apps/evil-infra/forgejo/git/repositories/${repo}" ]; then |
| 211 | [ -z "$evil_id" ] && echo "ssh key is not configured for repository on git.evil.inc" >&2 && exit 1 |
| 212 | exec sudo docker exec -i -u git evil-forgejo /usr/bin/env SSH_ORIGINAL_COMMAND="$SSH_ORIGINAL_COMMAND" /usr/local/bin/forgejo --config=/custom/conf/app.ini serv key-"$evil_id" |
| 213 | else |
| 214 | echo "repo not found" >&2 |
| 215 | exit 1 |
| 216 | fi |
| 217 | ``` |
| 218 | |
| 219 | it works beautifully. [see the whole patch for more info](https://git.paperclover.net/clo/home-infra/commit/e402d6ef71dfc310caeeb4d3579bddd9d0710594) |
| 220 | |
| 18 | 221 | ## 2026-01-14 |
| 19 | 222 | |
| 20 | 223 | tags: [next.js blog post], [this site] |