| ... | @@ -12,9 +12,212 @@ export const meta = { title: 'clover\'s log' }; | ... | @@ -12,9 +12,212 @@ export const meta = { title: 'clover\'s log' }; |
| 12 | [this site]: https://paperclover.net/ | 12 | [this site]: https://paperclover.net/ |
| 13 | [todo tracker]: https://git.paperclover.net/clo/todo-tracker | 13 | [todo tracker]: https://git.paperclover.net/clo/todo-tracker |
| 14 | [ts lie detector]: https://git.paperclover.net/clo/ts-lie-detector | 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 | # clover's log | 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 | ## 2026-01-14 | 221 | ## 2026-01-14 |
| 19 | | 222 | |
| 20 | tags: [next.js blog post], [this site] | 223 | tags: [next.js blog post], [this site] |