<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Alyx.pink</title><description>No description</description><link>https://alyx.pink/</link><language>en</language><item><title>I benchmarked 21 local LLMs on real MCP tool calling. Here&apos;s what actually works.</title><link>https://alyx.pink/posts/2026-02-26-local-llm-mcp-benchmark/</link><guid isPermaLink="true">https://alyx.pink/posts/2026-02-26-local-llm-mcp-benchmark/</guid><description>Real MCP tool calls against a real API, scored with semantic validation. A 4B model beat models 9x its size, single-shot benchmarks are lying to you, and reasoning beats tool training.</description><pubDate>Thu, 26 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I spent the past few weeks building a tool-calling benchmark for local LLMs and running 21 models through it. Not synthetic evals, not vibes. Real MCP tool calls against a real API (&lt;a href=&quot;https://workunit.app&quot;&gt;Workunit&lt;/a&gt;, which I built) with real database state, scored with semantic validation.&lt;/p&gt;
&lt;p&gt;Three things surprised me. I&apos;ll start with those, then get into the data.&lt;/p&gt;
&lt;h2&gt;The three findings that matter&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. A 4B model beat models 9x its size.&lt;/strong&gt; &lt;code&gt;qwen3-4b-thinking-2507&lt;/code&gt;, at 4B parameters and 2.3 GB on disk, scored 89.3% in agentic mode. It outperformed 11 of the 20 other models, including models up to 36B. A 3B model (&lt;code&gt;ministral-3-3b&lt;/code&gt;, 2.8 GB) scored 85.1% and beat four models in the 20-36B range. If you&apos;ve been assuming you need a big model for reliable tool calling, these results say otherwise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Single-shot benchmarks are lying to you.&lt;/strong&gt; Every model scored higher in agentic mode (iterative with real API feedback) than in single-shot mode (one response, no correction). The average gap was +18.3 percentage points, but on multi-step reasoning tasks it was +37.3pp. The single-shot pass rate on reasoning tasks was 2.0%. Agentic: 49.7%. If you&apos;re picking models based on single-shot evals, you&apos;re probably making the wrong choice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Reasoning beats tool training.&lt;/strong&gt; &lt;code&gt;phi-4-reasoning-plus&lt;/code&gt; (15B) was not fine-tuned for tool calling at all. It scored 91.4% in agentic mode, beating most tool-trained models. But here&apos;s the catch: it scored 35.1% in single-shot. It couldn&apos;t even format a basic explicit tool call without feedback (36.4% on L0). Give it the agentic loop, and it figures out the format, self-corrects, and performs at the top of the pack. Tool training gives consistency; reasoning gives ceiling.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;What I tested&lt;/h2&gt;
&lt;p&gt;21 models, 3B to 80B parameters, mostly Q4_K_M quantization. All running locally on a single RTX 4080 SUPER (16GB VRAM) with LM Studio. No cloud APIs.&lt;/p&gt;
&lt;p&gt;The target was the &lt;a href=&quot;https://workunit.app&quot;&gt;Workunit&lt;/a&gt; MCP server, which exposes 19 tools for project management: creating projects, tracking work items, managing tasks, handling assets, searching. Tool calls had real consequences: creating a project returned a real UUID that subsequent tasks had to reference.&lt;/p&gt;
&lt;p&gt;28 tasks across three difficulty levels:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;L0 (Explicit, 11 tasks)&lt;/strong&gt;: &quot;Call &lt;code&gt;create_project&lt;/code&gt; with name=X, status=Y.&quot; The model just needs to format the call.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L1 (Natural language, 10 tasks)&lt;/strong&gt;: &quot;I need to track work on fixing our login page...&quot; The model figures out which tool and what parameters.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L2 (Multi-step reasoning, 7 tasks)&lt;/strong&gt;: &quot;Set up everything for a dark mode feature.&quot; The model plans a tool sequence, chains entity IDs across calls, and generates semantically appropriate content.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two evaluation modes: &lt;strong&gt;single-shot&lt;/strong&gt; (one prompt, one response, no feedback) and &lt;strong&gt;agentic&lt;/strong&gt; (model gets real API responses back, iterates until pass or 300s timeout, 25-turn cap).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The full rankings&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/e03ce8d3-d1c9-4779-a753-9c14f9e435e3.png&quot; alt=&quot;Score heatmap across all models and levels&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Params&lt;/th&gt;
&lt;th&gt;Disk&lt;/th&gt;
&lt;th&gt;Tool-trained&lt;/th&gt;
&lt;th&gt;L0&lt;/th&gt;
&lt;th&gt;L1&lt;/th&gt;
&lt;th&gt;L2&lt;/th&gt;
&lt;th&gt;Overall&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;glm-4.7-flash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30B&lt;/td&gt;
&lt;td&gt;16.9 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;97.0&lt;/td&gt;
&lt;td&gt;89.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3-coder-next&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;80B&lt;/td&gt;
&lt;td&gt;45.2 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;85.7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;95.2&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;devstral-small-2-2512&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;24B&lt;/td&gt;
&lt;td&gt;12.4 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;82.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ministral-3-14b-reasoning&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;14B&lt;/td&gt;
&lt;td&gt;8.5 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;82.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3.5-35b-a3b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;35B&lt;/td&gt;
&lt;td&gt;20.6 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;82.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;94.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;magistral-small-2509&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;24B&lt;/td&gt;
&lt;td&gt;14.2 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;98.5&lt;/td&gt;
&lt;td&gt;77.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;92.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3-coder-30b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30B&lt;/td&gt;
&lt;td&gt;17.4 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;75.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;phi-4-reasoning-plus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;15B&lt;/td&gt;
&lt;td&gt;8.4 GB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;96.5&lt;/td&gt;
&lt;td&gt;77.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91.4&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gpt-oss-20b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20B&lt;/td&gt;
&lt;td&gt;11.3 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;92.0&lt;/td&gt;
&lt;td&gt;81.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen3-4b-thinking-2507&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;td&gt;2.3 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;67.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lfm2-24b-a2b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;24B (MoE)&lt;/td&gt;
&lt;td&gt;13.4 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;92.0&lt;/td&gt;
&lt;td&gt;75.4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;89.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rnj-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8.3B&lt;/td&gt;
&lt;td&gt;4.8 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;64.8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;88.3&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;granite-4-h-tiny&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;3.9 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;98.6&lt;/td&gt;
&lt;td&gt;91.5&lt;/td&gt;
&lt;td&gt;69.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;86.7&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nemotron-3-nano&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;30B&lt;/td&gt;
&lt;td&gt;22.8 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;98.5&lt;/td&gt;
&lt;td&gt;59.3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;gemma-3-12b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;12B&lt;/td&gt;
&lt;td&gt;7.6 GB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;91.0&lt;/td&gt;
&lt;td&gt;66.7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ernie-4.5-21b-a3b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;21B&lt;/td&gt;
&lt;td&gt;12.6 GB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;57.6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ministral-3-3b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;2.8 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;100.0&lt;/td&gt;
&lt;td&gt;92.0&lt;/td&gt;
&lt;td&gt;63.2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;85.1&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;&lt;code&gt;glm-4.6v-flash&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.4B&lt;/td&gt;
&lt;td&gt;7.4 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;90.9&lt;/td&gt;
&lt;td&gt;83.5&lt;/td&gt;
&lt;td&gt;67.1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;&lt;code&gt;seed-oss-36b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;36B&lt;/td&gt;
&lt;td&gt;20.3 GB&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;86.8&lt;/td&gt;
&lt;td&gt;71.3&lt;/td&gt;
&lt;td&gt;41.7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;66.6&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;&lt;code&gt;qwen2.5-coder-32b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;32B&lt;/td&gt;
&lt;td&gt;18.5 GB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;72.7&lt;/td&gt;
&lt;td&gt;40.0&lt;/td&gt;
&lt;td&gt;17.9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;43.5&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;&lt;code&gt;deepseek-r1-0528-qwen3-8b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;4.7 GB&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;97.3&lt;/td&gt;
&lt;td&gt;22.0&lt;/td&gt;
&lt;td&gt;0.0&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;39.8&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Per-level and overall scores for all 21 models in agentic mode.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The winner is &lt;strong&gt;&lt;code&gt;glm-4.7-flash&lt;/code&gt;&lt;/strong&gt; (30B, 95.4%), with &lt;code&gt;qwen3-coder-next&lt;/code&gt; (80B, 95.2%) close behind. 17 of 21 models exceeded 85% overall. For basic and natural-language tool calling, the problem is mostly solved. L2 multi-step reasoning is where models separate: scores range from 0% to 89.3%, and only two models broke 85%.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;The single-shot vs agentic gap&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/0e105337-83c1-4670-85ad-a0a5d0816479.png&quot; alt=&quot;Single-shot vs agentic overall scores&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The per-level breakdown tells the story:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/cf8fdfa1-08c1-4a2d-b48d-32e99aece48f.png&quot; alt=&quot;Level breakdown in agentic mode&quot; /&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Level&lt;/th&gt;
&lt;th&gt;Single-shot (mean)&lt;/th&gt;
&lt;th&gt;Agentic (mean)&lt;/th&gt;
&lt;th&gt;Lift&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L0 (Explicit)&lt;/td&gt;
&lt;td&gt;92.4%&lt;/td&gt;
&lt;td&gt;97.4%&lt;/td&gt;
&lt;td&gt;+5.0pp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1 (Natural language)&lt;/td&gt;
&lt;td&gt;76.2%&lt;/td&gt;
&lt;td&gt;88.8%&lt;/td&gt;
&lt;td&gt;+12.6pp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2 (Reasoning)&lt;/td&gt;
&lt;td&gt;28.6%&lt;/td&gt;
&lt;td&gt;65.9%&lt;/td&gt;
&lt;td&gt;+37.3pp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;At L0, the lift is small because most models can format a tool call correctly on the first try. At L1, the lift comes from models correcting tool selection or parameter mapping after seeing the error. At L2, the lift is qualitatively different: multi-step tool chains require iterating with real responses. You can&apos;t predict the UUID of a project you haven&apos;t created yet.&lt;/p&gt;
&lt;p&gt;The two biggest lifts came from models with strong reasoning but poor zero-shot formatting:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;phi-4-reasoning-plus&lt;/code&gt;&lt;/strong&gt;: +56.3pp (35.1% → 91.4%). Could not format basic tool calls without feedback. Once the agentic loop let it observe and correct, it performed at the top.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;qwen3-4b-thinking-2507&lt;/code&gt;&lt;/strong&gt;: +49.6pp (39.7% → 89.3%). Same pattern. Strong reasoning, needs the loop.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The smallest lift: &lt;strong&gt;&lt;code&gt;deepseek-r1-0528-qwen3-8b&lt;/code&gt;&lt;/strong&gt; at +0.6pp. The agentic loop can&apos;t help when the problem isn&apos;t formatting but a fundamental inability to map natural language to tools or plan multi-step chains.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/5dc8282c-ca23-41d0-a6b6-4bef97aabed2.png&quot; alt=&quot;Agentic lift per model&quot; /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Tool training: helpful, not decisive&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/554d336b-d76f-46d9-b4c7-5d1ddcacd0a4.png&quot; alt=&quot;Tool-trained vs control group comparison&quot; /&gt;&lt;/p&gt;
&lt;p&gt;I included 5 control-group models that had no tool-specific fine-tuning. The numbers:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;N&lt;/th&gt;
&lt;th&gt;Agentic (mean)&lt;/th&gt;
&lt;th&gt;Std Dev&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tool-trained&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;88.7%&lt;/td&gt;
&lt;td&gt;7.2pp&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;69.3%&lt;/td&gt;
&lt;td&gt;25.4pp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The 19.4pp gap is real. But look at the standard deviations. Tool training raises the floor and tightens the spread. Without it, you&apos;re rolling the dice: &lt;code&gt;phi-4-reasoning-plus&lt;/code&gt; hits 91.4% (top 8), while &lt;code&gt;deepseek-r1&lt;/code&gt; hits 39.8% (bottom 2) and &lt;code&gt;qwen2.5-coder&lt;/code&gt; hits 43.5%.&lt;/p&gt;
&lt;p&gt;The phi-4 result is worth sitting with. A 15B model with no tool training, performing at the level of the best tool-trained models, purely through in-context learning within the agentic loop. It suggests that tool training is most valuable for single-shot reliability (tool-trained single-shot average: 70.2%, control: 51.6%), with diminishing returns when models get real feedback.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Practical picks by VRAM budget&lt;/h2&gt;
&lt;p&gt;Disk size is a reasonable floor for VRAM requirements. Here&apos;s what I&apos;d pick at each tier:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Under 4 GB&lt;/strong&gt;: &lt;code&gt;qwen3-4b-thinking-2507&lt;/code&gt; (4B, 2.3 GB, 89.3%) if your agent framework supports agentic loops. &lt;code&gt;ministral-3-3b&lt;/code&gt; (3B, 2.8 GB, 85.1%) if you need single-shot reliability (76.0% SS vs 39.7% for the 4B).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8-12 GB&lt;/strong&gt;: &lt;code&gt;ministral-3-14b-reasoning&lt;/code&gt; (14B, 8.5 GB, 94.0%). Tied for 3rd place. 100% on both L0 and L1. This is the sweet spot for most setups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;12-16 GB&lt;/strong&gt;: &lt;code&gt;devstral-small-2-2512&lt;/code&gt; (24B, 12.4 GB, 94.0%) edges out &lt;code&gt;magistral-small-2509&lt;/code&gt; on both L1 and L2, and is 1.8 GB smaller.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;16+ GB&lt;/strong&gt;: &lt;code&gt;glm-4.7-flash&lt;/code&gt; (30B, 16.9 GB, 95.4%). The overall winner.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;48+ GB&lt;/strong&gt;: &lt;code&gt;qwen3-coder-next&lt;/code&gt; (80B, 45.2 GB, 95.2%). Only 0.2pp behind the winner, 100% on L0 and L1.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Things that bit me&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Jinja templates can silently break everything.&lt;/strong&gt; &lt;code&gt;seed-oss-36b&lt;/code&gt; initially scored 0% on all agentic tasks. Not because the model was bad, but because LM Studio&apos;s Jinja engine didn&apos;t support Python&apos;s &lt;code&gt;in&lt;/code&gt; operator for tuple/array membership testing. Three constructs in the chat template were incompatible. After rewriting them, it scored 66.6%. If a model scores surprisingly badly, check the inference stack before blaming the model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Code completion models don&apos;t transfer.&lt;/strong&gt; &lt;code&gt;qwen2.5-coder-32b&lt;/code&gt; is a 32B model that scored 43.5%. It emits FIM tokens (&lt;code&gt;&amp;lt;|fim_suffix|&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;|fim_middle|&amp;gt;&lt;/code&gt;) and is designed for code completion, not chat-based tool calling. Code pretraining does not transfer to structured tool calling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;One model had a cliff, not a slope.&lt;/strong&gt; &lt;code&gt;deepseek-r1-0528-qwen3-8b&lt;/code&gt;: 97.3% on L0, 22% on L1, 0% on L2. It can follow explicit formatting instructions, but it falls off completely the moment it needs to interpret natural language or reason about tool sequences.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;Single hardware config (RTX 4080 SUPER 16GB, 64GB RAM). Mostly Q4_K_M quantization. One MCP domain (Workunit&apos;s project management tools). Temperature 0.0. Single run per model. 8192 token context for all models. The exact numbers should be taken with the appropriate grain of salt, but the relative rankings and structural findings (agentic &amp;gt; single-shot, small models viable, reasoning &amp;gt; raw size) should hold.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;All data is open&lt;/h2&gt;
&lt;p&gt;Full source code, all 28 task definitions, runner scripts, and every result JSON with complete audit trails are in the repo. Each result file contains the exact prompt sent, every tool call with arguments, every API response received, and scoring details. You can open any result, pick a task, and see exactly what happened.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href=&quot;https://github.com/3615-computer/workunit-benchmarks&quot;&gt;github.com/3615-computer/workunit-benchmarks&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/3615-computer/workunit-benchmarks/blob/main/local-llm-mcp-calling/reports/research_paper.md&quot;&gt;full research paper&lt;/a&gt; has formal methodology, all tables, validation details, and appendices.&lt;/p&gt;
</content:encoded></item><item><title>Workunit: The missing context layer for working with AI</title><link>https://alyx.pink/posts/2025-10-24-workunit-missing-context-layer-for-ai/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-10-24-workunit-missing-context-layer-for-ai/</guid><description>Ever lose your train of thought when an LLM hits its context limit? Or struggled to share your work between Claude, GPT, and Gemini? I built Workunit to solve this.</description><pubDate>Fri, 24 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hey everyone,&lt;/p&gt;
&lt;p&gt;I&apos;ve been building with AI for a while now, and I kept running into the same frustrating problem: no matter how powerful these models get—Claude, GPT, Gemini—they all eventually lose track of what you&apos;re doing. You know the drill: you&apos;re deep into building a feature, the context window fills up, the conversation compresses, and suddenly the LLM forgets the plan it carefully crafted an hour ago.&lt;/p&gt;
&lt;p&gt;But here&apos;s what really bugged me: you can&apos;t easily move work between different models. Say you want Claude&apos;s planning brilliance but GPT&apos;s design chops? Good luck manually copying context between chats. Want two models working on the same codebase? You&apos;re basically maintaining two separate conversations that have no idea what the other is doing.&lt;/p&gt;
&lt;p&gt;So I built &lt;a href=&quot;https://workunit.app&quot;&gt;Workunit&lt;/a&gt; to fix this.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/2d01d86b-32f5-4eb4-934b-68ae0607f626.png&quot; alt=&quot;Screenshot of Workunit homepage&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;The Problem: Context Fragmentation&lt;/h2&gt;
&lt;p&gt;Here&apos;s what I was dealing with:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Context windows are still too small.&lt;/strong&gt; Even the best models compress your conversation after a while. That detailed architecture plan you made at the start? Reduced to a summary. Those important decisions about why you chose approach A over B? Gone. The model starts improvising based on incomplete memory.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No context sharing between models.&lt;/strong&gt; Each AI lives in its own bubble. You can&apos;t start planning with Claude, move to Gemini for implementation, then ask GPT to review the code—at least not without manually copying a ton of context every single time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Teams can&apos;t collaborate with AI.&lt;/strong&gt; When you&apos;re working with others, everyone&apos;s having separate conversations with their preferred models. There&apos;s no shared understanding, no single source of truth. Your coworker using GPT has no idea what you just asked Claude to do.&lt;/p&gt;
&lt;h2&gt;What is Workunit?&lt;/h2&gt;
&lt;p&gt;Think of Workunit as a context and memory layer that sits between you and any AI model. It&apos;s also a project management tool, but the real magic is how it lets you share context across different models seamlessly.&lt;/p&gt;
&lt;p&gt;Here&apos;s how it works: instead of just chatting with an AI, you create a &lt;strong&gt;Workunit&lt;/strong&gt;. Each workunit contains:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The problem you&apos;re solving&lt;/strong&gt; - clearly defined&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Success criteria&lt;/strong&gt; - what &quot;done&quot; looks like&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI context&lt;/strong&gt; - a persistent knowledge base where models can dump what they&apos;ve learned&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tasks&lt;/strong&gt; - specific work items with their own focused context&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once you&apos;ve created a workunit, any AI with access to the MCP (Model Context Protocol) integration can read and update it. That means Claude, GPT, Gemini, or whatever new model comes out next month—they all see the same information.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/00c12661-4b0b-4a74-b9ca-660bbcaf7b5f.png&quot; alt=&quot;Screenshot of Workunit dashboard showing a list of workunits with their status, tasks count, and recent activity&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Assets: Building your context graph&lt;/h2&gt;
&lt;p&gt;Here&apos;s where Workunit gets really interesting. Beyond just tracking tasks, you can define &lt;strong&gt;Assets&lt;/strong&gt;—the building blocks of your project&apos;s context. Think of them as the things your AI needs to understand before it starts working.&lt;/p&gt;
&lt;p&gt;There are four types of assets:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Knowledge&lt;/strong&gt; - Your documentation, coding standards, API docs, architecture decisions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;People&lt;/strong&gt; - Team members, stakeholders, even your AI assistants&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Product&lt;/strong&gt; - The actual things you&apos;re building (apps, features, platforms)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System&lt;/strong&gt; - Technical infrastructure (databases, APIs, deployment pipelines)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you link assets to a workunit, you&apos;re creating what I call a &quot;context graph.&quot; Now when an AI looks at your workunit, it automatically knows which systems are involved, what documentation to reference, who&apos;s working on what, and which products are affected.&lt;/p&gt;
&lt;p&gt;And here&apos;s the cool part: LLMs can manage these assets themselves through the MCP tools. They can create new assets as they discover them, search for existing ones, and update them when information changes. So if you&apos;re working with Claude and mention &quot;we&apos;re using PostgreSQL with this specific schema,&quot; Claude can create a System asset for it. Later, when GPT needs that context, it can search for and find that PostgreSQL asset without you having to explain it again.&lt;/p&gt;
&lt;p&gt;No more &quot;hey Claude, remember we&apos;re using PostgreSQL with that specific schema I told you about 50 messages ago?&quot; The context is right there, persistent and accessible.&lt;/p&gt;
&lt;p&gt;The best part? Start small. I usually begin with 5-10 core assets—my main database, API documentation, coding guidelines, team members. As projects grow, the asset library grows with them. It&apos;s documentation that actually stays useful instead of going stale in some wiki nobody reads.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/af3e385f-f9e0-4cac-b69c-f281e8ad287d.png&quot; alt=&quot;Screenshot showing the assets page with different asset types&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;How I actually use it&lt;/h2&gt;
&lt;p&gt;Let me show you a real example. Say I&apos;m building a new authentication system:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I start by planning with Claude: &quot;Let&apos;s add a &apos;Won&apos;t Do&apos; task status&quot;&lt;/li&gt;
&lt;li&gt;Claude uses the Workunit MCP to create the structure, defines the problem, sets success criteria, breaks it into tasks&lt;/li&gt;
&lt;li&gt;I get a URL like &lt;code&gt;https://workunit.app/workunits/abc123&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Later, I can tell GPT: &quot;Take a look at this workunit &lt;code&gt;https://workunit.app/workunits/abc123&lt;/code&gt; and focus on the database schema task&quot; - with the task URL&lt;/li&gt;
&lt;li&gt;GPT sees all the context, implements that specific piece, and updates the task status&lt;/li&gt;
&lt;li&gt;My teammate can comment on tasks, see progress, and even jump in with their own preferred AI&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/cadb23fc-47a2-4eda-aec6-6f5e2fc06daa.png&quot; alt=&quot;Screenshot of a workunit detail page showing the problem statement, success criteria, AI context section, and list of tasks with their statuses (todo, in progress, done)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The killer feature? I can tell any model to &quot;update task status as you work&quot; and I get a real-time view of what&apos;s in progress, what&apos;s done, and what&apos;s blocked. No more wondering if the AI actually completed something or just said it did.&lt;/p&gt;
&lt;h2&gt;Setting up the MCP integration&lt;/h2&gt;
&lt;p&gt;Getting your AI tools connected to Workunit is straightforward. The Model Context Protocol (MCP) is what makes all this magic happen—it&apos;s the bridge that lets AI assistants access Workunit directly from your development environment.&lt;/p&gt;
&lt;p&gt;The beauty of using MCP is that Workunit works with &lt;strong&gt;any LLM that supports the Model Context Protocol&lt;/strong&gt;. Claude, GPT, Gemini, or whatever comes next—if it supports MCP, it can connect to Workunit. That means you&apos;re future-proof as the AI landscape evolves.&lt;/p&gt;
&lt;p&gt;Here are setup examples for the most commonly documented tools:&lt;/p&gt;
&lt;h3&gt;For Claude Code&lt;/h3&gt;
&lt;p&gt;Just run this in your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;claude mcp add --transport http workunit https://workunit.app/mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then open Claude Code and ask about available Workunit tools to verify it&apos;s working.&lt;/p&gt;
&lt;h3&gt;For Gemini CLI&lt;/h3&gt;
&lt;p&gt;Add the integration with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gemini mcp add --transport http workunit https://workunit.app/mcp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once connected, your AI assistant can:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create and modify workunits&lt;/li&gt;
&lt;li&gt;Generate and assign tasks&lt;/li&gt;
&lt;li&gt;Link assets to your work&lt;/li&gt;
&lt;li&gt;Access and update context across conversations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The setup process is similar for any MCP-compatible tool—just point it to &lt;code&gt;https://workunit.app/mcp&lt;/code&gt; and you&apos;re good to go. It takes maybe two minutes, and then you&apos;ve got persistent context across all your AI interactions. Worth it.&lt;/p&gt;
&lt;p&gt;For more details and troubleshooting, check out the &lt;a href=&quot;https://workunit.app/guides/mcp-integration&quot;&gt;MCP integration guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Beyond solo work&lt;/h2&gt;
&lt;p&gt;Workunit isn&apos;t just for working with multiple AI models—though that&apos;s incredibly powerful. It&apos;s also built for teams:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Comments on tasks&lt;/strong&gt; - your team can discuss specific work items&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check-ins&lt;/strong&gt; - gather status updates from everyone in your organization&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Shared understanding&lt;/strong&gt; - humans and AI working from the same source of truth&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It&apos;s designed for small teams and solo builders who want AI assistance without enterprise complexity. You know, those of us who don&apos;t need fancy Gantt charts but do need to actually ship things.&lt;/p&gt;
&lt;h2&gt;Why this matters&lt;/h2&gt;
&lt;p&gt;The AI landscape is moving fast. New models launch every month, each with different strengths. Workunit means you&apos;re not locked into one model&apos;s ecosystem—you can use the best tool for each job.&lt;/p&gt;
&lt;p&gt;More importantly, it solves the memory problem. Context doesn&apos;t disappear when a conversation gets long. The AI can always reference back to what you&apos;ve built together, decisions you&apos;ve made, and patterns you&apos;ve established.&lt;/p&gt;
&lt;p&gt;And when your team grows? The context layer grows with you. Everyone works from the same foundation instead of scattered conversations across different tools.&lt;/p&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;Workunit is live in beta right now, completely free while I refine things. I&apos;m looking for feedback from builders who are tired of fighting context windows and want to use multiple AI models effectively.&lt;/p&gt;
&lt;p&gt;If you&apos;ve ever lost a great plan halfway through a conversation, or wished you could seamlessly move work between different models, &lt;a href=&quot;https://workunit.app&quot;&gt;give Workunit a shot&lt;/a&gt;. I&apos;d love to hear what you think.&lt;/p&gt;
</content:encoded></item><item><title>Found my perfect image hosting solution: Slink</title><link>https://alyx.pink/posts/2025-09-17-discovered-slink-image-hosting/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-09-17-discovered-slink-image-hosting/</guid><description>Finally discovered a self-hosted image sharing platform with great UX that actually strips EXIF data for privacy.</description><pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hey everyone,&lt;/p&gt;
&lt;p&gt;I&apos;ve been searching for the perfect image hosting solution for my blog, and I finally found it! Meet &lt;a href=&quot;https://github.com/andrii-kryvoviaz/slink&quot;&gt;Slink&lt;/a&gt;, a self-hosted image sharing platform that actually gets it right.&lt;/p&gt;
&lt;p&gt;Here&apos;s what had me hooked: it strips EXIF data from images automatically. You know, all that metadata that can reveal your location, camera model, and other personal info? Gone. This is something I really needed, especially since &lt;a href=&quot;https://github.com/mtlynch/picoshare&quot;&gt;PicoShare&lt;/a&gt; (which I use for other needs, keeping this lack of feature in mind) keeps files exactly as uploaded.EXIF data and all.&lt;/p&gt;
&lt;p&gt;But it&apos;s not just about privacy. The UI/UX is great. Upload an image, get a clean link, done. No fuss, no bloated interface, just a smooth experience that makes adding images to my static blog actually enjoyable. It supports everything from PNG and JPG to modern formats like AVIF and HEIC.&lt;/p&gt;
&lt;p&gt;I&apos;ve already uploaded a bunch of images for my previous posts to Slink (you&apos;re looking at one right now!). Having complete control over my media while keeping things private and simple? That&apos;s exactly what I was after.&lt;/p&gt;
&lt;p&gt;If you&apos;re tired of wrestling with image hosting or worried about metadata privacy, give &lt;a href=&quot;https://github.com/andrii-kryvoviaz/slink&quot;&gt;Slink&lt;/a&gt; a shot. It&apos;s been a game-changer for my workflow.&lt;/p&gt;
</content:encoded></item><item><title>Update everything on macOS with one Fish command</title><link>https://alyx.pink/posts/2025-09-17-fish-shell-update-everything-mac/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-09-17-fish-shell-update-everything-mac/</guid><description>A handy Fish shell function to update Homebrew, casks, and Mac App Store apps all at once.</description><pubDate>Wed, 17 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Picture from &lt;a href=&quot;https://unsplash.com/fr/@wasdrew?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash&quot;&gt;Andras Vas&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/fr/photos/macbook-pro-turned-on-Bd7gNnWJBkU?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Got tired of running multiple apps and commands to keep my Mac updated? Here&apos;s a Fish shell function that handles everything in one go:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function upall --description &quot;Update all the things&quot;
    brew update-reset &amp;amp;&amp;amp; \
    brew update &amp;amp;&amp;amp; \
    brew upgrade --formulae &amp;amp;&amp;amp; \
    brew cu --all --cleanup --no-brew-update --interactive --include-mas &amp;amp;&amp;amp; \
    brew cleanup --prune=all
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Just run &lt;code&gt;upall&lt;/code&gt; and you&apos;re set! Here&apos;s what each part does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew update-reset&lt;/code&gt;&lt;/strong&gt; - Resets Homebrew to fix any potential issues&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew update&lt;/code&gt;&lt;/strong&gt; - Fetches the latest package definitions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew upgrade --formulae&lt;/code&gt;&lt;/strong&gt; - Updates all your CLI tools and libraries&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew cu --all --cleanup --no-brew-update --interactive --include-mas&lt;/code&gt;&lt;/strong&gt; - Updates cask applications and Mac App Store apps (requires &lt;a href=&quot;https://github.com/buo/homebrew-cask-upgrade&quot;&gt;brew-cask-upgrade&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;brew cleanup --prune=all&lt;/code&gt;&lt;/strong&gt; - Removes old versions and cleans up the cache&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The &lt;code&gt;--interactive&lt;/code&gt; flag is particularly nice—it lets you choose which cask apps to update, perfect for when you want to skip that one app that always breaks after updates.&lt;/p&gt;
&lt;p&gt;To add this to your Fish shell, paste the function into a function file, mine is &lt;code&gt;.config/fish/functions/upall.fish&lt;/code&gt;. No more remembering multiple commands or running them one by one!&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: You&apos;ll need to install &lt;code&gt;brew-cask-upgrade&lt;/code&gt; first, see &lt;a href=&quot;https://github.com/buo/homebrew-cask-upgrade&quot;&gt;github.com/buo/homebrew-cask-upgrade&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
</content:encoded></item><item><title>A new chapter: 3615.computer</title><link>https://alyx.pink/posts/2025-09-12-a-new-chapter/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-09-12-a-new-chapter/</guid><description>Announcing 3615.computer, my new umbrella company for Workunit and Aeronef.</description><pubDate>Fri, 12 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Hey everyone,&lt;/p&gt;
&lt;p&gt;I&apos;ve launched &lt;a href=&quot;https://3615.computer&quot;&gt;3615.computer&lt;/a&gt;, my new umbrella for software projects. The name&apos;s a nod to those funny little &lt;a href=&quot;https://en.wikipedia.org/wiki/Minitel&quot;&gt;Minitel&lt;/a&gt; &quot;computers&quot; we had at home in France—our own internet before the internet, where you&apos;d dial &quot;3615&quot; for services over the landline.&lt;/p&gt;
&lt;p&gt;::github{org=&quot;3615-computer&quot;}&lt;/p&gt;
&lt;p&gt;Right now, I&apos;m focusing on two main projects that solve very different problems I care about.&lt;/p&gt;
&lt;h1&gt;Workunit: AI for Focused Teams&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://workunit.app/&quot;&gt;&lt;strong&gt;Workunit&lt;/strong&gt;&lt;/a&gt; tackles a problem I&apos;ve experienced firsthand—context fragmentation. You know the drill: scattered conversations across Slack, decisions buried in email threads, and that one crucial detail lost somewhere in your note-taking app.&lt;/p&gt;
&lt;p&gt;Workunit consolidates your team&apos;s scattered tools into one AI-powered workspace. Think of it as having a thoughtful team member who remembers every decision, understands your project&apos;s context, and helps you stay focused instead of constantly context-switching.&lt;/p&gt;
&lt;p&gt;It&apos;s built for small teams and solo entrepreneurs who want AI assistance without the complexity.&lt;/p&gt;
&lt;h1&gt;Aeronef: Guild Wars 2 Companion&lt;/h1&gt;
&lt;p&gt;&lt;a href=&quot;https://aeronef.app/&quot;&gt;&lt;strong&gt;Aeronef&lt;/strong&gt;&lt;/a&gt; started as a personal itch I needed to scratch. As a Guild Wars 2 player, I was tired of juggling multiple wikis, calculators, and spreadsheets to track my progress and manage my in-game economy.&lt;/p&gt;
&lt;p&gt;So I built a comprehensive companion app that handles account value tracking, crafting calculations, daily quest management, and real-time trading post analytics—all in one place. It&apos;s built by a gamer, for gamers, with no affiliation to ArenaNet.&lt;/p&gt;
&lt;p&gt;If you&apos;re managing multiple characters or trying to optimize your gold-making strategies, Aeronef might save you some headaches.&lt;/p&gt;
</content:encoded></item><item><title>Cloudflare&apos;s Bold Documentation: Just Let the AI Do It</title><link>https://alyx.pink/posts/2025-09-12-cloudflare-ai-migration-docs/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-09-12-cloudflare-ai-migration-docs/</guid><description>Cloudflare&apos;s migration docs casually suggest letting an LLM handle your entire project migration. And you know what? It actually works.</description><pubDate>Fri, 12 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Picture from &lt;a href=&quot;https://unsplash.com/fr/@sharadmbhat?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash&quot;&gt;Sharad Bhat&lt;/a&gt; on &lt;a href=&quot;https://unsplash.com/fr/photos/un-grand-nuage-orange-dans-un-ciel-sombre-_Z0JGI6FGlA?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I stumbled across something fascinating in Cloudflare&apos;s documentation recently—something that made me do a double-take and then immediately try it out.&lt;/p&gt;
&lt;p&gt;Hidden in their migration guides is this wonderfully matter-of-fact suggestion: &quot;or if you want to just do the migration automatically by your LLM, feed it this file and let it happen.&quot;&lt;/p&gt;
&lt;p&gt;Not buried in a footnote. Not hedged with disclaimers. Just a casual &quot;hey, your AI can probably handle this entire migration for you.&quot;&lt;/p&gt;
&lt;h2&gt;The Documentation Revolution&lt;/h2&gt;
&lt;p&gt;This is remarkable for a few reasons. First, it&apos;s Cloudflare—a major infrastructure company—officially endorsing AI automation for non-trivial technical tasks. Second, they&apos;re not just mentioning it as a possibility; they&apos;re providing structured documentation specifically designed to be consumed by LLMs.&lt;/p&gt;
&lt;p&gt;Most documentation is written for humans, with context and explanations that help us understand the &quot;why&quot; behind each step. But when you&apos;re feeding instructions to an AI, you want something different: precise, structured information that can be parsed and executed systematically.&lt;/p&gt;
&lt;p&gt;Cloudflare seems to have cracked this code. Their migration files are clean, comprehensive, and apparently LLM-friendly enough that they can confidently tell developers to &quot;just let it happen.&quot;&lt;/p&gt;
&lt;h2&gt;The One-Shot Migration&lt;/h2&gt;
&lt;p&gt;I tested this approach, and it worked exactly as advertised. Fed the migration guide to an AI assistant, pointed it at my project, and watched it methodically work through the entire migration process.&lt;/p&gt;
&lt;p&gt;No back-and-forth debugging sessions. No missed steps. No &quot;oh wait, I forgot to update that config file&quot; moments. Just a systematic, thorough migration that actually worked on the first try.&lt;/p&gt;
&lt;p&gt;This isn&apos;t some toy example either—we&apos;re talking about migrating real projects with real complexity, dependencies, and edge cases.&lt;/p&gt;
&lt;h2&gt;The AI-Optimized Structure&lt;/h2&gt;
&lt;p&gt;What makes this documentation so effective becomes clear when you look at how it&apos;s structured. Here&apos;s a section that perfectly demonstrates the AI-friendly approach:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;### 3. Determine Project Type

**First, check for Pages Functions:**

- Look for a `functions/` directory with .js/.ts files
- If found, you **must** add `wrangler pages functions build` to your build process (see step 6)

**Then, run your build command and check the output directory:**

- **If _worker.js exists**: You have a Workers script project
  - Add `&quot;main&quot;: &quot;./path/to/_worker.js&quot;`
  - Add binding to assets: `&quot;assets&quot;: {&quot;directory&quot;: &quot;path&quot;, &quot;binding&quot;: &quot;ASSETS&quot;}`
- **If no _worker.js**: You have an assets-only project
  - Just use `&quot;assets&quot;: {&quot;directory&quot;: &quot;path&quot;}` without main field

### 6. Pages Functions Migration (if applicable)

**ONLY if you have a `functions/` directory with .js/.ts files:**

- **Always add** the Pages Functions build command to your build process
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the clear conditional logic, the explicit use of &quot;&lt;strong&gt;must&lt;/strong&gt;&quot; and &quot;&lt;strong&gt;ONLY if&lt;/strong&gt;&quot;, and the decision trees. This isn&apos;t documentation—it&apos;s a structured algorithm that an AI can follow step-by-step without ambiguity.&lt;/p&gt;
&lt;h2&gt;What This Means for Documentation&lt;/h2&gt;
&lt;p&gt;Cloudflare&apos;s approach hints at something bigger: the future of technical documentation might be dual-purpose. Written for humans to understand, but structured for machines to execute.&lt;/p&gt;
&lt;p&gt;This could fundamentally change how we approach complex technical processes. Instead of hoping developers follow 47 steps correctly, why not provide instructions that an AI can execute flawlessly?&lt;/p&gt;
&lt;p&gt;The implications go beyond migrations. Think about deployment guides, security audits, refactoring tasks, or any process that involves systematic changes across a codebase.&lt;/p&gt;
&lt;h2&gt;The Trust Factor&lt;/h2&gt;
&lt;p&gt;What strikes me most is the confidence behind that casual suggestion. Cloudflare didn&apos;t arrive at &quot;just let the AI do it&quot; overnight. They&apos;ve clearly tested this extensively and found it reliable enough to recommend to their users.&lt;/p&gt;
&lt;p&gt;That level of trust in AI automation for production systems feels like a significant shift. We&apos;re moving from &quot;AI might help with some tasks&quot; to &quot;AI can handle entire categories of work better than humans.&quot;&lt;/p&gt;
&lt;h2&gt;Looking Forward&lt;/h2&gt;
&lt;p&gt;This feels like one of those moments where the future quietly announces itself. Not with fanfare or keynotes, but with a single line in technical documentation that changes how we think about automation.&lt;/p&gt;
&lt;p&gt;Other companies will likely follow suit. The pattern is too useful to ignore: provide structured, AI-consumable documentation alongside human-readable guides.&lt;/p&gt;
&lt;p&gt;The question isn&apos;t whether this approach will spread—it&apos;s how quickly, and what other technical processes will benefit from this dual-purpose documentation strategy.&lt;/p&gt;
&lt;p&gt;Cloudflare just showed us what confident AI integration looks like: practical, tested, and casually revolutionary.&lt;/p&gt;
</content:encoded></item><item><title>How to use 1Password to authenticate to your favorite IRC server</title><link>https://alyx.pink/posts/2025-04-23-use-1password-with-halloy-irc-client/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-04-23-use-1password-with-halloy-irc-client/</guid><description>Learn how to use 1Password to securely authenticate to your favorite IRC server using the Halloy IRC client.</description><pubDate>Wed, 23 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;How to use 1Password to authenticate to your favorite IRC server&lt;/h2&gt;
&lt;p&gt;1Password is a password manager that can help you securely store and manage your passwords, including those for IRC servers. In this guide, we&apos;ll show you how to use 1Password to authenticate to your favorite IRC server using the Halloy IRC client.&lt;/p&gt;
&lt;h2&gt;Prerequisites&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;You need to have &lt;a href=&quot;https://1password.com/&quot;&gt;1Password&lt;/a&gt; installed and set up on your device. Any other password manager that supports the same features can be used, check your password manager&apos;s documentation for more information.
&lt;ul&gt;
&lt;li&gt;1Password CLI (&lt;code&gt;op&lt;/code&gt;) must be installed and configured. You can find the installation instructions &lt;a href=&quot;https://developer.1password.com/docs/cli/get-started/&quot;&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;You need to have the &lt;a href=&quot;https://halloy.chat/&quot;&gt;Halloy IRC client&lt;/a&gt; installed on your device. Other IRC clients may also work, but this guide was tested with Halloy.&lt;/li&gt;
&lt;li&gt;You need to have an account on the IRC server you want to connect to. I&apos;ll be usng &lt;a href=&quot;https://libera.chat/&quot;&gt;libera.chat&lt;/a&gt; as an example.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Step 1: Create a new login in 1Password&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open 1Password and click on the &lt;strong&gt;&quot;+ New Item&quot;&lt;/strong&gt; button.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select &quot;Login&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the following information:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Name&lt;/strong&gt;: The name of the IRC server (e.g., &quot;libera.chat&quot;).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Username&lt;/strong&gt;: Your IRC username.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Password&lt;/strong&gt;: Your IRC password.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Website&lt;/strong&gt;: The URL of the IRC server (e.g., &quot;irc://libera.chat&quot;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select a vault and click &quot;Save&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Step 2: Configure Halloy to get password from 1Password on startup&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open the Halloy IRC client configuration file. This file is usually located at :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Windows&lt;/strong&gt;: &lt;code&gt;%AppData%\halloy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mac&lt;/strong&gt;: &lt;code&gt;~/Library/Application Support/halloy&lt;/code&gt; or &lt;code&gt;$HOME/.config/halloy&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Linux&lt;/strong&gt;: &lt;code&gt;$XDG_CONFIG_HOME/halloy&lt;/code&gt; or &lt;code&gt;$HOME/.config/halloy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In 1Password, copy the UUID of the login you created in Step 1. You can find the UUID by clicking on the login &amp;gt; three-dots menu &amp;gt; &quot;Copy item UUID&quot;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following lines to the configuration file:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[servers.&amp;lt;server&amp;gt;]
server = &quot;&amp;lt;server&amp;gt;&quot;
nickname = &quot;&amp;lt;nickname&amp;gt;&quot;
channels = []
sasl.plain.username = &quot;&amp;lt;username&amp;gt;&quot;
sasl.plain.password_command = &quot;op read op://&amp;lt;vault&amp;gt;/&amp;lt;uuid&amp;gt;/&amp;lt;field-name&amp;gt;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Save the configuration file.&lt;/li&gt;
&lt;li&gt;Restart the Halloy IRC client.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Step 3: Connect to the IRC server&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open the Halloy IRC client.&lt;/li&gt;
&lt;li&gt;You should see a prompt asking to unlock your 1Password vault, asked by Halloy&apos;s app.&lt;/li&gt;
&lt;li&gt;Enter your 1Password master password to unlock the vault.&lt;/li&gt;
&lt;li&gt;Once unlocked, Halloy will automatically retrieve your IRC password from 1Password and connect to the IRC server using the credentials you provided.&lt;/li&gt;
&lt;li&gt;You should now be connected to the IRC server and can start chatting!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Using 1Password to authenticate to your favorite IRC server with the Halloy IRC client is a secure and convenient way to manage your passwords. By following the steps outlined in this guide, you can easily connect to your IRC server without having to remember or type in your password every time.&lt;/p&gt;
&lt;p&gt;This method also helps to keep your passwords secure (not hardcoded) and reduces the risk of password leaks or breaches.&lt;/p&gt;
</content:encoded></item><item><title>Running a local PDS in dev mode and create a custom record</title><link>https://alyx.pink/posts/2025-03-29-local-pds-custom-record/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-03-29-local-pds-custom-record/</guid><description>Messing around with ATProto and Personal Data Server (PDS)</description><pubDate>Sat, 29 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;I wanted to play around with ATProto and Personal Data Server (PDS). My goal was just to see how to write a record to the PDS, using a custom type. Here are some quick notes just in case it can help anyone, and so it does not get &quot;lost&quot;.&lt;/p&gt;
&lt;p&gt;I haven&apos;t made an extended search for everything: I&apos;m not sure what &lt;code&gt;PDS_DEV_MODE&lt;/code&gt; does entirely. Maybe I could have used something else than &lt;code&gt;PDS_INVITE_REQUIRED=0&lt;/code&gt; to not require an invitation code on signup. It&apos;s your turn to scratch around and find out!&lt;/p&gt;
&lt;h2&gt;Launch a local PDS in dev mode&lt;/h2&gt;
&lt;p&gt;This will launch a container running the PDS, the blobs (files, uploads) are stored on disk but not on a volume, so it&apos;s ephemeral. The secrets are generated automatically. Invitations are disabled. Dev mode is enabled. The container is named &lt;code&gt;pds-dev&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker run --rm \
  -e PDS_DATADIR=/pds \
  -e PDS_BLOBSTORE_DISK_LOCATION=/pds/blocks \
  -e PDS_BLOBSTORE_DISK_TMP_LOCATION=/pds/tmp \
  -e PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=$(openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32) \
  -e PDS_JWT_SECRET=$(openssl rand --hex 16) \
  -e PDS_DEV_MODE=true \
  -e PDS_ADMIN_PASSWORD=$(openssl rand --hex 16) \
  -e LOG_ENABLED=true \
  -e LOG_LEVEL=debug \
  -e PDS_INVITE_REQUIRED=0 \
  -p 3000:3000 \
  --name pds-dev \
  ghcr.io/bluesky-social/pds:0.4
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Create a custom record&lt;/h2&gt;
&lt;p&gt;In my case, &lt;code&gt;example.alyx.customRecord&lt;/code&gt; is obviously not registered. Click here to learn more about the &lt;a href=&quot;https://atproto.com/guides/glossary#nsid-namespaced-id&quot;&gt;NSID&lt;/a&gt; and &lt;a href=&quot;https://atproto.com/guides/glossary#lexicon&quot;&gt;Lexicon&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Create a new account&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ export pds_account_password=$(openssl rand --hex 16)

$ curl -X POST localhost:3000/xrpc/com.atproto.server.createAccount \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{&quot;handle&quot;: &quot;alyx.example&quot;, &quot;email&quot;: &quot;alyx@alyx.example&quot;, &quot;password&quot;: &quot;&apos;$pds_account_password&apos;&quot;}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Get the JWT access token&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ curl -s -X POST localhost:3000/xrpc/com.atproto.server.createSession \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{&quot;identifier&quot;: &quot;alyx.example&quot;, &quot;password&quot;: &quot;&apos;$pds_account_password&apos;&quot;}&apos; | jq -r &apos;.accessJwt&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ export pds_access_session=$(curl -s -X POST localhost:3000/xrpc/com.atproto.server.createSession \
  -H &quot;Content-Type: application/json&quot; \
  -d &apos;{&quot;identifier&quot;: &quot;alyx.example&quot;, &quot;password&quot;: &quot;&apos;$pds_account_password&apos;&quot;}&apos; | jq -r &apos;.accessJwt&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Create your custom record&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;$ curl -X POST localhost:3000/xrpc/com.atproto.repo.createRecord \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer xxx&quot; \
  -d &apos;{&quot;repo&quot;: &quot;alyx.example&quot;, &quot;collection&quot;: &quot;example.alyx.customRecord&quot;, &quot;record&quot;: {&quot;custom&quot;: &quot;Hello, world!&quot;}}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ curl -X POST localhost:3000/xrpc/com.atproto.repo.createRecord \
  -H &quot;Content-Type: application/json&quot; \
  -H &quot;Authorization: Bearer &quot;$pds_access_session&quot;&quot; \
  -d &apos;{&quot;repo&quot;: &quot;alyx.example&quot;, &quot;collection&quot;: &quot;example.alyx.customRecord&quot;, &quot;record&quot;: {&quot;custom&quot;: &quot;Hello, world!&quot;}}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Inspect your PDS using a web app&lt;/h2&gt;
&lt;p&gt;You can also use &lt;a href=&quot;https://pdsls.dev/&quot;&gt;pdsls.dev&lt;/a&gt; to inspect any records stored on your PDS. Yes, everything stored on your PDS is public, keep this in mind.&lt;/p&gt;
</content:encoded></item><item><title>Hello, World!</title><link>https://alyx.pink/posts/2025-02-23-hello-world/</link><guid isPermaLink="true">https://alyx.pink/posts/2025-02-23-hello-world/</guid><description>New blog engine, new hello world post!</description><pubDate>Sun, 23 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;strong&gt;Hello, World!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Hi there! I&apos;m thrilled to launch my very own (new) blogging platform. After spending time on micro-blogging platforms, and a while self-hosting Ghost, I&apos;ve decided to move away from social networks and create a space where I can express myself more freely and openly. Ghost was a great platform to write long articles, but it felt inappropriate for short updates or heavy customization.&lt;/p&gt;
&lt;p&gt;I started developing my own theme, but when I saw how good &lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;Fuwari&lt;/a&gt; was, I couldn&apos;t resist using it.&lt;/p&gt;
&lt;p&gt;Here’s what you can expect from my blog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Short and Sweet Posts&lt;/strong&gt;: Think microblogging but less social media focused. I’ll be sharing quick thoughts, snippets of code, or interesting links I come across throughout my day.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Thoughtful Quotes&lt;/strong&gt;: If I stumble upon an intriguing article or paper, I might feature a compelling quote to spark some reflection.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Programming Projects&lt;/strong&gt;: Updates on side projects I’m tinkering with. I’ll highlight challenges and solutions, giving a glimpse behind the scenes of my coding adventures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I want to show a bit of who I am, and sharing my thoughts and learnings, in case it could help anyone out there. I hope I&apos;ll feel at home here, like I used to on microbbloging platforms.&lt;/p&gt;
</content:encoded></item><item><title>Comment se passer de ChatGPT Plus à 20 $ par mois, au profit de LobeChat ?</title><link>https://alyx.pink/posts/2024-03-22-se-passer-de-chatgpt-au-profit-de-lobechat/</link><guid isPermaLink="true">https://alyx.pink/posts/2024-03-22-se-passer-de-chatgpt-au-profit-de-lobechat/</guid><description>Découvrez comment remplacer votre abonnement ChatGPT Plus par LobeChat, une interface open-source qui vous permet de payer uniquement ce que vous utilisez via l&apos;API OpenAI.</description><pubDate>Fri, 22 Mar 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;J&apos;ai récemment arrêté mon abonnement ChatGPT Plus et j&apos;ai pu le remplacer par une simple souscription à leur API, me permettant de payer juste ce que j&apos;utilise. Beaucoup moins cher, et proposant – selon moi – plus d&apos;options que leur interface assez sommaire, sans pour autant ne plus pouvoir profiter de toutes les fonctionnalités que OpenAI propose.&lt;/p&gt;
&lt;h2&gt;Qu&apos;est-ce que LobeChat ?&lt;/h2&gt;
&lt;p&gt;LobeChat est un projet open source, disponible sur &lt;a href=&quot;https://github.com/lobehub/lobe-chat&quot;&gt;https://github.com/lobehub/lobe-chat&lt;/a&gt;, qui se décrit ainsi :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An open-source, modern-design ChatGPT/LLMs UI/Framework. Supports speech-synthesis, multi-modal, and extensible (function call) plugin system. One-click &lt;strong&gt;FREE&lt;/strong&gt; deployment of your private ChatGPT/Gemini/Ollama chat application.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/2bf83a52-d0ab-424a-b020-adf636e4e769.webp&quot; alt=&quot;Interface LobeChat&quot; /&gt;&lt;/p&gt;
&lt;p&gt;C&apos;est un front-end qui peut se brancher à diverses API, que ce soit ChatGPT ou d&apos;autres, comme votre propre instance locale via &lt;a href=&quot;https://github.com/jmorganca/ollama&quot;&gt;Ollama&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;L&apos;interface ressemble beaucoup à celle de ChatGPT, ce qui rend l&apos;adoption très facile.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/94f12df8-54c3-43be-8951-40410752e187.png&quot; alt=&quot;Fonctionnalités de LobeChat&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Comment lancer LobeChat ?&lt;/h2&gt;
&lt;p&gt;Personnellement, j&apos;ai utilisé Unraid pour lancer le container Docker de LobeChat avec la configuration suivante :&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ docker run -d -p 3210:3210 \
  -e OPENAI_API_KEY=sk-xxxx \
  -e ACCESS_CODE=lobe66 \
  --name lobe-chat \
  lobehub/lobe-chat
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cette configuration :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expose le container sur le port 3210&lt;/li&gt;
&lt;li&gt;Fournit votre clé API OpenAI&lt;/li&gt;
&lt;li&gt;Définit un mot de passe d&apos;accès&lt;/li&gt;
&lt;li&gt;Utilise l&apos;image Docker officielle&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Comment l&apos;utiliser ?&lt;/h2&gt;
&lt;p&gt;Pour utiliser LobeChat, vous devez :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configurer votre clé API OpenAI dans les variables d&apos;environnement&lt;/li&gt;
&lt;li&gt;Accéder à l&apos;interface web via votre navigateur&lt;/li&gt;
&lt;li&gt;Saisir le code d&apos;accès que vous avez défini&lt;/li&gt;
&lt;li&gt;Commencer à discuter avec l&apos;IA&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/436b2fcb-4593-4358-9d67-caf0557c572b.png&quot; alt=&quot;Interface utilisateur 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/dea53237-30bc-41ba-ae9e-312c9664489e.png&quot; alt=&quot;Interface utilisateur 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;L&apos;interface est intuitive et offre de nombreuses options de personnalisation.&lt;/p&gt;
&lt;h2&gt;Analyse des coûts&lt;/h2&gt;
&lt;p&gt;Avant LobeChat, je payais 20 $ par mois pour ChatGPT Plus. Avec l&apos;API OpenAI via LobeChat :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;En février : j&apos;ai dépensé 9 $ d&apos;utilisation API&lt;/li&gt;
&lt;li&gt;En mars (jusqu&apos;au 16) : 6,61 $ seulement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je recommande de configurer des limites de crédit pour éviter les mauvaises surprises sur votre facture.&lt;/p&gt;
&lt;h2&gt;Fonctionnalités supplémentaires de LobeChat&lt;/h2&gt;
&lt;h3&gt;LobeHub et marketplace d&apos;agents&lt;/h3&gt;
&lt;p&gt;LobeChat propose un &quot;LobeHub&quot; avec des agents de conversation prédéfinis pour différents cas d&apos;usage.&lt;/p&gt;
&lt;h3&gt;Plugin Store&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/60fe8ef2-95d4-4565-a3cf-07c6e003356c.webp&quot; alt=&quot;Plugin Store&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Un magasin de plugins permet d&apos;étendre les fonctionnalités avec diverses intégrations.&lt;/p&gt;
&lt;h3&gt;Application Web Progressive (PWA)&lt;/h3&gt;
&lt;p&gt;LobeChat peut être installé comme une PWA sur vos appareils, offrant une expérience native sur mobile et desktop.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/e5702144-6d51-41d3-95b2-3ffac314818a.webp&quot; alt=&quot;Interface mobile responsive&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Support multi-providers&lt;/h3&gt;
&lt;p&gt;En plus d&apos;OpenAI, LobeChat supporte :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS Bedrock&lt;/li&gt;
&lt;li&gt;Google AI&lt;/li&gt;
&lt;li&gt;Azure OpenAI&lt;/li&gt;
&lt;li&gt;Et bien d&apos;autres fournisseurs d&apos;IA&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Pour moi, c&apos;est un succès. J&apos;économise de l&apos;argent tout en ayant plus de flexibilité et d&apos;options. Le seul inconvénient est que les conversations ne sont stockées que dans le navigateur, ce qui peut être gênant si vous changez d&apos;appareil.&lt;/p&gt;
&lt;p&gt;Cette solution est particulièrement adaptée pour les utilisateurs avec une utilisation occasionnelle de l&apos;IA. Les utilisateurs plus intensifs pourraient encore préférer l&apos;abonnement ChatGPT Plus pour sa simplicité.&lt;/p&gt;
&lt;p&gt;LobeChat représente une excellente alternative open-source qui vous donne le contrôle sur vos coûts et vos données.&lt;/p&gt;
</content:encoded></item><item><title>Comment fonctionne mon NAS UNRAID à domicile ?</title><link>https://alyx.pink/posts/2023-12-17-comment-fonctionne-mon-nas-unraid-a-domicile/</link><guid isPermaLink="true">https://alyx.pink/posts/2023-12-17-comment-fonctionne-mon-nas-unraid-a-domicile/</guid><description>J&apos;ai depuis longtemps un NAS à la maison. Retour d&apos;expérience sur Unraid, un OS pour serveur qui me permet de gérer facilement stockage de masse et hébergement de services.</description><pubDate>Sun, 17 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;J&apos;ai depuis longtemps un NAS à la maison. Je ne sais plus vers quel âge, probablement vers 15 ans. J&apos;avais récupéré des pièces d&apos;une décheterie pour assembler mon premier serveur. Autant vous dire que les performances n&apos;étaient pas au rendez-vous.&lt;/p&gt;
&lt;p&gt;Un vieux petit boîtier tout jauni, dans lequel j&apos;avais coupé un trou au Dreml pour lui coller un ventilateur tant il avait chaud dans mon placard. Ma mère ne voulait pas le voir en dehors, il était trop moche. Je m&apos;amusais bien avec, un petit serveur LAMP et je ne sais plus trop quoi d&apos;autres dessus. Ça m&apos;a fait les bases de l&apos; admininistration système et de serveur web.&lt;/p&gt;
&lt;p&gt;J&apos;ai pu m&apos;acheter une vraie config de NAS quelques années plus tard, très light en performance, mais me permettant de faire tourner Plex correctement et d&apos;avoir du stockage de dispo. Je faisais tourner Ubuntu Server dessus. Ça demandait beaucoup d&apos;interventions manuelles pour le mettre à jour, et le faire évoluer (installer des nouvelles applications par exemple). J&apos;ai écrit quelques scripts pour me simplifier la tâche mais c&apos;était assez relou. J&apos;ai vite su que c&apos;était pas pour moi d&apos;avoir un truc comme ça à gérer à la maison, ou même dans mon métier. Je voulais déjà tout automatiser.&lt;/p&gt;
&lt;p&gt;J&apos;ai déménagé à Paris vers mes 20 ans, et j&apos;ai emmené mon PC gaming ainsi que mon serveur. J&apos;ai un peu repris la main dessus, collectant les films de vacances sur mon NAS pour les regarder entre amis. Ça demandait toujours autant de maintenance cependant.&lt;/p&gt;
&lt;p&gt;J&apos;avais envie d&apos;un NAS autonome dont j&apos;aurais juste à m&apos;occuper des films de vacances et des logiciels, rien d&apos;autres. Un truc bête genre un Synology... mais c&apos;est hors de prix, et pas le choix du software qui tourne dessus.&lt;/p&gt;
&lt;p&gt;Et j&apos;ai fini par trouvé la solution. J&apos;ai un NAS très autonome, j&apos;ai besoin de faire que très peu de maintenance, l&apos;installation était d&apos;une facilitée déconcertante, et le grand choix d&apos;applications disponibles en one-click install est un vrai plaisir. Je vais bien sûr vous parler de &lt;a href=&quot;https://unraid.net/&quot;&gt;Unraid&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;Mais d&apos;abord, c&apos;est quoi un NAS exactement ?&lt;/h1&gt;
&lt;p&gt;Un NAS, aussi appelé &quot;Network Attached Storage&quot; et un système de stockage connecté en réseau qui permet de centraliser les données et de les mettre à disposition des utilisateurs.&lt;/p&gt;
&lt;p&gt;Ce NAS fait un peu plus que ça, puisque&apos;il héberger également des services, c&apos;est devenu un abus de langage je pense de dire NAS pour en fait ce qui est un simple serveur de stockage et plus encore. Peut-être que le terme de serveur à domicile serait plus approprié ?&lt;/p&gt;
&lt;h1&gt;Mon utilisation&lt;/h1&gt;
&lt;p&gt;On peut répartir l&apos;utilisation de mon NAS en deux catégories, qui se complètement l&apos;une et l&apos;autre. La partie donc je m&apos;occupe très peu, et dont je suis heureuse de laisser Unraid laisser ça, et l&apos;autre partie qui m&apos;amuse: lancer et gérer des services.&lt;/p&gt;
&lt;h2&gt;Stockage de masse&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/a99c4a16-af6c-4b39-935e-4e62178b53c2.png&quot; alt=&quot;5 disques, 80TB au total, dont 48TB utilisables (deux disques de parités)&quot; /&gt;&lt;/p&gt;
&lt;p&gt;J&apos;ai une grappe totale de &lt;code&gt;5x16TB&lt;/code&gt; de données, soit &lt;code&gt;80TB&lt;/code&gt;. Cependant, deux des disques sont utilisé pour la parité. Ce sont des disques servant à sauvegarder des informations qui permettent de récupérer les données en cas de panne d&apos;un autre disque. Si l&apos;un de mes disques venaient à mourir, alors Unraid peut recalculer les données manquantes.&lt;/p&gt;
&lt;p&gt;J&apos;ai fait le choix d&apos;avoir 2 disques de parité: avec autant de données disponible (&lt;code&gt;48TB&lt;/code&gt;), je veux surtout pas avoir un soucis lors de la perte d&apos;un disque par exemple. Elles me sont précieuses, ça vaut le coup d&apos;investir pour les protéger. Il faut noter que le re-calcul de la parité peut prendre des heures et des heures, et est très intensive sur l&apos;utilisation des disques. Pas question de perdre un second disque pendant la restauration.&lt;/p&gt;
&lt;p&gt;Mais tout de même, &lt;code&gt;48TB&lt;/code&gt; ça me laisse une belle marge d&apos;utilisation, et il est tout à fait possible de continuer à l&apos;étendre comme bon me semble. Unraid permet d&apos;ajouter des disques à une grappe existante d&apos;une manière extrêmement simple. Seul problème: vous ne pouvez pas dépasser la taille du plus petit disque de parité. Dans mon cas, je ne pourrais jamais mettre un disque plus gros que 16TB.&lt;/p&gt;
&lt;p&gt;Pour faire simple et vous donner un exemple, pour &lt;a href=&quot;https://docs.unraid.net/unraid-os/manual/storage-management/#adding-disks&quot;&gt;installer un nouveau dissque&lt;/a&gt;, il vous suffit d&apos;éteindre le NAS, brancher vos nouveaux disques, rallumer le NAS, assigner les nouveaux disques à la grappe de votre choix (vous pouvez en avoir plusieurs), lui dire de le formater, et il s&apos;occupe de faire le reste pour vous. Une fois que tout est prêt, le stockage disponible est automatiquement mis à disposition.&lt;/p&gt;
&lt;h2&gt;Hébergement de services&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/075ce524-5b06-4595-be4c-571db21746e4.png&quot; alt=&quot;Le menu principal de l&apos;interface d&apos;admin de Unraid&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Unraid tire sa puissance de sa facilité à installer des applications, en utilisant la technologie des containers Docker, ainsi qu&apos;un &quot;marketplace&quot; rendant encore plus simple le lancement des apps proposés.&lt;/p&gt;
&lt;p&gt;Le &quot;marketplace,&quot; c&apos;est ce qui peut s&apos;apparenter à des templates, souvent pré-rempli pour vous faciliter la tâche, avec des exemples. Par exemple, pour un serveur Redis, vous aurez ça:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/96229e51-1144-4fcd-94fc-4efacda99fdb.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;La plupart des options peuvent être laissés par défaut et votre container se lancera automatiquement. Vous avez un accès rapide aux logs pour voir ce qu&apos;il se passe au lancement. Si le container expose une Web UI, et que le template est bien fait, alors un bouton &quot;Web UI&quot; apparaitra en cliquant sur le logo du container déployé.&lt;/p&gt;
&lt;p&gt;Unraid intègre un système qui détecte si une nouvelle version du container est disponible, et en un seul clic, il le mettra à jour. Attention aux notes de mises à jour cependant ! Il arrive parfois que vous deviez effectuer une action manuelle avant/après la mise à jour, ne vous faites pas avoir.&lt;/p&gt;
&lt;p&gt;Vous vous retrouvez alors avec une interface d&apos;admistration de vos applications déployées, en cours ou stoppées:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/2e3ac7d4-faf0-4669-af07-0c44e1a99a08.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Services utilisés&lt;/h2&gt;
&lt;p&gt;J&apos;utilise de nombreux services accumulés au fil du temps. Prenez le temps de les prendre en main et comprendre ce qu&apos;ils peuvent faire pour vous. Amusez vous aussi, si le service vous plait pas, supprimez le tout simplement, il n&apos;y a rien d&apos;instrusif dans l&apos;OS à supprimer qui pourrait être laisser derrière, sauf peut-être les appdata, mais c&apos;est juste un dossier à un seul endroit donc pas de panique pour les nettoyage.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://tailscale.com/&quot;&gt;&lt;strong&gt;Tailscale&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Je commence par celui-ci car il est intégré nativement – via un plugin disponible – à Unraid et vous permet de vous connecter à votre serveur depuis n&apos;importe où, comme si vous étiez chez vous.&lt;/p&gt;
&lt;p&gt;Pour rappel, Tailscale est un service de réseau privé virtuel (VPN) qui permet de connecter de manière sécurisée et facile des appareils et des services à travers internet. Il utilise la technologie WireGuard pour établir des connexions rapides et cryptées, offrant ainsi un accès sécurisé aux ressources de votre réseau, dans le monde entier. Tailscale se distingue par sa configuration simple, ne nécessitant pas de matériel spécialisé ni de configuration réseau complexe. Il offre également tout un tas de features très utiles, comme la possibilité d&apos;envoyer des fichiers entre les appareils du réseau, ou encore une meilleure gestion de DNS pour tous vos appareils.&lt;/p&gt;
&lt;p&gt;Ça vous ouvre des possibilités incroyable: un point de montage sur Files.app de votre iPhone vers vos Partages Samba, l&apos;utilisation de Adguard-Home en dehors de chez vous, sans jamais avoir besoin d&apos;exposer vos services sur internet. Peu importe où vous êtes, sur n&apos;importe quel réseau, vous êtes comme chez vous.&lt;/p&gt;
&lt;p&gt;Vous pouvez même vous servir de votre serveur comme un nœud de sortie VPN: dans ce cas, toute votre connexion sera tunnelée vers votre serveur et sortira de chez vous. Pas mal si vous voulez regarder un contenu géo-bloqué de votre pays pendant un voyage par exemple.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://cdn.sanity.io/images/w77i7m8x/production/6d16dd6fac328a0575f3bf9d460cf6cb37f9044e-1360x725.svg?w=3840&amp;amp;q=75&amp;amp;fit=clip&amp;amp;auto=format&quot; alt=&quot;masthead static&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/AdguardTeam/AdGuardHome&quot;&gt;&lt;strong&gt;AdGuard Home&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un bloqueur de publicités et de tracking pour tout votre réseau, qui protège tous les appareils connectés. Fonctionne uniquement via les DNS cependant, donc impossible de supprimer les pré-roll de pubs YouTue ou Twitch.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/cloudflare/cloudflared&quot;&gt;&lt;strong&gt;Cloudflared Tunnel&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Crée un tunnel sécurisé entre votre serveur Unraid et les services Cloudflare pour un accès distant sécurisé. Ça vous permet d&apos;exposer vos services publiquement, comme &lt;a href=&quot;https://blog-ghost.alyx.pink/&quot;&gt;blog.alyxpractice.com&lt;/a&gt; par exemple ou bien &lt;a href=&quot;https://files.alyxpractice.com/&quot;&gt;files.alyxpractice.com&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://hub.docker.com/r/tiredofit/db-backup/&quot;&gt;&lt;strong&gt;db-backup&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un outil pour automatiser les sauvegardes de vos bases de données.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://ghost.org/&quot;&gt;&lt;strong&gt;Ghost&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Une plateforme de blogging élégante et moderne, idéale pour ceux qui cherchent une alternative à WordPress. C&apos;est sur celle-ci que vous me lisez !&lt;/p&gt;
&lt;p&gt;C&apos;est beaucoup centré sur la génération de contenu payant, mais vous pouvez très facilement désactiver tout ça et rendre tout gratuit et accessible. Je n&apos;ai pas encore trop fouillé si il existait beaucoup de plugins, thèmes, etc... mais son interface de base me permet de me focus sur mon contenu et c&apos;est très bien comme ça.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://homarr.dev/&quot;&gt;&lt;strong&gt;Homarr&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un tableau de bord personnalisable pour centraliser et accéder facilement à vos applications et services web. Plus simple que de passer par Unraid &amp;gt; Docker &amp;gt; Web UI systématiquement.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://www.home-assistant.io/&quot;&gt;&lt;strong&gt;Home Assistant&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Une solution de domotique pour contrôler et automatiser votre maison intelligente. Apporte de nombreuses solutions pour combler le manque des plateformes classiques type Siri, Alexa, Google Assistant, etc...&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/Womabre/unraid-docker-templates&quot;&gt;&lt;strong&gt;icloudpd&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un service pour télécharger automatiquement vos photos iCloud sur votre serveur Unraid. Attention le token doit être renouveler régulièrement, mais je me sens plus safe d&apos;avoir toutes mes photos en backup chez moi.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://mariadb.org/&quot;&gt;&lt;strong&gt;MariaDB&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un système de gestion de base de données, compatible avec MySQL, pour stocker et gérer vos données.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/mtlynch/picoshare&quot;&gt;&lt;strong&gt;Picoshare&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un outil simple pour partager des fichiers et des images via votre serveur. Disponible via &lt;a href=&quot;https://files.alyxpractice.com/&quot;&gt;files.alyxpractice.com&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/adb17c13-709d-4f67-a52d-6f787f14d04c.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://www.plex.tv/&quot;&gt;&lt;strong&gt;Plex Media Server&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un serveur de médias pour organiser et diffuser votre collection de vidéos, musiques et photos, pour vous et vos amis. Parfait pour partager vos films de vacances ! Disponible sur toutes les plateformes.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/74a5277e-79f0-477d-8558-7a1474e15373.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/Prowlarr/Prowlarr&quot;&gt;&lt;strong&gt;Prowlarr&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un gestionnaire pour indexer des sites de torrents et Usenet, facilitant la recherche de médias. Il vous permet de centraliser la configuration de vos clients de torrents et indexers, et synchroniser les paramètres avec la suite logiciels &lt;code&gt;*arr&lt;/code&gt; comme &lt;code&gt;radarr&lt;/code&gt; ou &lt;code&gt;sonarr&lt;/code&gt;...&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://www.qbittorrent.org/&quot;&gt;&lt;strong&gt;qBittorrent&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un client torrent léger et puissant pour télécharger et partager des fichiers.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/Radarr/Radarr&quot;&gt;&lt;strong&gt;Radarr&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un gestionnaire de films qui automatise le processus de recherche, téléchargement et organisation de votre collection de films.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/PhasecoreX/docker-red-discordbot&quot;&gt;&lt;strong&gt;Red-DiscordBot&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un bot Discord personnalisable avec un système de &quot;&lt;a href=&quot;https://index.discord.red/&quot;&gt;cogs&lt;/a&gt;&quot; ou plugins, créés par la communauté, pour améliorer l&apos;interaction sur vos serveurs Discord. Il est utilisé sur le discord de 3615.computer.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://sonarr.tv/&quot;&gt;&lt;strong&gt;Sonarr&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un gestionnaire de séries TV qui automatise le téléchargement et l&apos;organisation de vos émissions télévisées.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/slskd/slskd&quot;&gt;&lt;strong&gt;Soulseek&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Une application de partage de fichiers axée sur la musique, idéale pour découvrir et télécharger de la musique rare ou spécifique. Fonctionne en pair à pair, voir friends to peers, puisque chacun a un pseudo et vous pouvez discuter avec la personne. C&apos;est toujours sympa de se faire remercier d&apos;avoir partager des fichiers avec quelqu&apos;un !&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/72a9a8f9-8ab3-4494-9a6e-0094b02925fc.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://tautulli.com/&quot;&gt;&lt;strong&gt;Tautulli&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un outil de surveillance et de suivi pour Plex Media Server, offrant des statistiques détaillées sur l&apos;utilisation. Pratique pour savoir si votre serveur fait plaisir à vos invités. Il me permet aussi d&apos;envoyer des notifications sur Discord et bien d&apos;autres pour savoir quand un nouvel épisode ou film de vacances est disponible.&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/louislam/uptime-kuma&quot;&gt;&lt;strong&gt;UptimeKuma&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un moniteur de disponibilité pour garder un œil sur l&apos;état et la performance de vos sites web et services. Il vous permet de vous envoyer une alerte où vous le souhaitez.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/a19f8800-09e7-4c44-9bd7-13a4eeeca63d.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;&lt;a href=&quot;https://github.com/Jeeaaasus/youtube-dl&quot;&gt;&lt;strong&gt;YouTube-DL&lt;/strong&gt;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Un outil pour télécharger des vidéos de YouTube et d&apos;autres sites de partage de vidéos, directement sur votre serveur. Ça me permet d&apos;avoir les vidéos de mes chaînes favorite en local pour les regarder via Plex par exemple.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/bf5aefbc-5681-459b-aea7-476df4972cda.png&quot; alt=&quot;L&apos;interface est un peu austère cependant&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Mes services favoris&lt;/h1&gt;
&lt;h2&gt;Unraid, le coeur du serveur&lt;/h2&gt;
&lt;p&gt;Unraid, c&apos;est un peu le couteau suisse des systèmes d&apos;exploitation pour serveurs. Basé sur Linux, il est très souple et &quot;facile d&apos;entretien&quot;. Vous pouvez mixer des disques durs de différentes tailles pour créer un espace de stockage sur mesure.&lt;/p&gt;
&lt;p&gt;Vous pouvez créer un pool de stockage de masse, une partie cache pour accélérer l&apos;upload des fichiers vers celui-ci, faire une grappe de SSD pour vos applications et machines virtuelles, etc... Il s&apos;adapte à vos besoins.&lt;/p&gt;
&lt;p&gt;Son gros plus ? La redondance des données, grâce à un système de parité qui protège contre la perte de données. Il est très intuitif et convivial avec une interface web facile à gérer. Vous n&apos;aurez quasiment pas besoin de la ligne de commande, pour pas dire du tout.&lt;/p&gt;
&lt;p&gt;Vous pouvez faire plus encore: par exemple, lancer une machine virtuelle Windows, installer une carte graphique de gaming dans le NAS, et grâce à une configuration de mise, vous pourriez très bien en faire votre PC de jeu en plus de votre NAS. L&apos;exemple idéal me semble être pour les invités qui viennent chez moi: on est toujours frustré de pas pouvoir se faire une game de Overwatch ensemble. Peut-être que je pourrais régler ce soucis...&lt;/p&gt;
&lt;p&gt;À noter qu&apos;il est payant et n&apos;est pas open-source. Mais son prix est tellement faible par rapport au gain de temps assuré, croyez moi c&apos;est un bon investissement. Vous pouvez l&apos;essayer 30 jours avant de vous faire un avis &lt;a href=&quot;https://unraid.net/pricing&quot;&gt;unraid.net/pricing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/a41b198a-b1f7-4bdd-912e-37f0753d4c0d.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Fun fact: l&apos;OS est stocké sur clé USB (puis chargé en RAM ?) donc prévoyez d&apos;en condamner une, ou en acheter une pour l&apos;occasion.&lt;/p&gt;
&lt;p&gt;Personnellement, j&apos;ai essayé TrueNAS (et peut-être FreeNAS aussi, je me souviens plus ?): non merci. Si vous rêvez d&apos;avoir constamment les mains dans le cambouis, et d&apos;un OS libre et open-source, peut-être vous y trouverez votre bonheur.&lt;/p&gt;
&lt;h2&gt;Hardware&lt;/h2&gt;
&lt;p&gt;C&apos;est une vielle machine – mon très ancien PC de jeu – qui fait désormais tourner mon NAS. Il est prévu de faire un gros rafraichissement dans quelques mois:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MSI Z77A-G45 (MS-7752) , Version 1.0
American Megatrends Inc., Version V2.5
BIOS dated: Tue 19 Jun 2012 12:00:00 AM CEST

Intel® Core™ i3-2120 CPU @ 3.30GHz

Memory: 8 GiB DDR3

5x16TB HDD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Honnêtement, il est assez stable. N&apos;espérer du transcode Plex, ou pouvoir installer un container rapidement, mais il tient la route.&lt;/p&gt;
&lt;p&gt;Il est parfois dans les choux, et me fait parfois très peur quand je dois le force reboot cependant, ça reste très rare.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;Sans lui ma vie quotidienne serait bien différente. Il me permet d&apos;explorer énormément de choses, facilement, de lancer des services à droite à gauche, et enfin avoir un serveur de stockage redondant pour y sauvegarder tout ce dont j&apos;ai besoin depuis mes différents appareils (attention à ne jamais confondre redondance et sauvegarde/backup !).&lt;/p&gt;
&lt;p&gt;J&apos;ai tous mes media en un seul endroit, et plusieurs petits services qui sont chez moi, qui ne dépendent presque de personne &lt;em&gt;(tousse tousse cloudflare tunnel tousse tousse).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Un rafraichissement de la configuration est à venir, je n&apos;ai pas encore d&apos;idée précise en tête mais je veux pas non plus casser la banque. Je pense tabler sur un budget de 500-600€ pour carte-mère, CPU, mémoire vive, alimentation, disque NVMe pour ce qui nécessite vraiment de la grande vitesse (genre des base de données) et double disque SSD (dont un de parité) pour ce qui est entre le stockage et le besoin de performance (applications Docker et machines virtuelle).&lt;/p&gt;
&lt;p&gt;Une nouvelle alimentation au meilleur rendement ne serait pas de trop également, ainsi que quelques ventilateurs plus silencieux. Je pense partir sur des composants &quot;grand public&quot; plutôt que des gammes orientés serveurs professionnels, pour des raisons de coût.&lt;/p&gt;
&lt;p&gt;Je ne pense pas me tourner vers un serveur de type professionnel, mais qui sait, si je trouve une affaire sur eBay par exemple...&lt;/p&gt;
&lt;h1&gt;Screenshots divers&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/803f01c2-d975-46a0-9f4e-c2197b1d08b6.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/5c870fb4-d6bb-4372-8f6b-3909cbb09ad9.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/a5ec92e4-cf39-4d73-8083-64589b8b4653.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/a67b3307-d63a-4d5a-a2e4-b791cfcfdfc3.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/942f7a9a-6684-4aa8-83f3-ebc229bb70de.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/b3ef0622-0c7b-4ebb-a41c-742945b60675.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;Parlez-moi de votre expérience !&lt;/h1&gt;
&lt;p&gt;Pour plus d&apos;information sur Unraid, rendez-vous sur &lt;a href=&quot;https://unraid.net/&quot;&gt;unraid.net&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Vous avez déjà un NAS, souhaitez le mettre à niveau ou simplement partager votre expérience et les services les plus cools que vous faites tourner ? Parlez-en moi sur Mastodon (&lt;a href=&quot;https://3615.computer/@alyx&quot;&gt;3615.computer/@alyx&lt;/a&gt;), je me ferais un plaisir d&apos;échanger avec nous !&lt;/p&gt;
</content:encoded></item><item><title>Comment fonctionne mon bot qui poste des paysages Minecraft aléatoires ?</title><link>https://alyx.pink/posts/2023-12-14-comment-fonctionne-mon-bot-craftviews/</link><guid isPermaLink="true">https://alyx.pink/posts/2023-12-14-comment-fonctionne-mon-bot-craftviews/</guid><description>Explication du fonctionnement de mon bot CraftViews qui génère et partage automatiquement des captures d&apos;écran de paysages Minecraft aléatoires toutes les 4 heures.</description><pubDate>Thu, 14 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Qu&apos;est-ce que c&apos;est ?&lt;/h2&gt;
&lt;p&gt;J&apos;ai créé le bot &lt;a href=&quot;https://3615.computer/@CraftViews&quot;&gt;@CraftViews&lt;/a&gt; qui envoie toutes les 4h une &quot;photo&quot; d&apos;un paysage Minecraft entièrement aléatoire sur le réseau social &lt;a href=&quot;https://joinmastodon.org/&quot;&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le script, écrit en &lt;a href=&quot;https://go.dev/&quot;&gt;Go&lt;/a&gt;, va lancer Minecraft, créer un nouveau monde, se téléporter à un endroit aléatoire à la surface, prendre une capture d&apos;écran et la poster sur Mastodon.&lt;/p&gt;
&lt;h2&gt;Exemples&lt;/h2&gt;
&lt;p&gt;Voici quelques exemples de captures générées par le bot :&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/0261ac89-9421-4b97-9e78-f180e5b0ed51.png&quot; alt=&quot;Paysage Minecraft 1&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/9b9aa25a-d029-4449-8bd7-fbc7ca98835b.png&quot; alt=&quot;Paysage Minecraft 2&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/da19e5c3-8158-4c3a-95b7-ce82d378ca84.png&quot; alt=&quot;Paysage Minecraft 3&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Idée&lt;/h2&gt;
&lt;p&gt;J&apos;ai toujours trouvé les paysages de Minecraft absolument fantastique. Généré aléatoirement et procéduralement, ils sont toujours uniques et peuvent être d&apos;une incroyable beauté.&lt;/p&gt;
&lt;p&gt;Ajoutant à cela des shaders, permettant de rendre le jeu encore plus beau, on obtient des vues magnifiques. Ajoutez à ça quelques éléments tel que de l&apos;eau, une source de lave brillante de mille feux, un lever ou coucher de soleil et on a le plus beau des paysages.&lt;/p&gt;
&lt;p&gt;Pour nous vieux joueurs (je joue depuis la version Alpha du jeu en décembre 2010), il y a aussi un côté nostalgique à voir ces paysages. À la façon de Edward Hopper, souvent vides de vie, ils ont quelque chose d&apos;un peu étrange, comme si c&apos;était abandonné de toute vie.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/9ec08179-29c4-4846-bb29-d0d719fdf9b3.png&quot; alt=&quot;Paysage Minecraft 4&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Dans les grandes lignes&lt;/h2&gt;
&lt;p&gt;Ce projet de bot se compose de deux fichiers Go principaux :&lt;/p&gt;
&lt;h3&gt;main.go&lt;/h3&gt;
&lt;p&gt;Ce fichier contient la logique principale pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Prendre les captures d&apos;écran de Minecraft&lt;/li&gt;
&lt;li&gt;Les publier sur Mastodon avec l&apos;API appropriée&lt;/li&gt;
&lt;li&gt;Gérer les erreurs et la configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;minecraft.go&lt;/h3&gt;
&lt;p&gt;Ce fichier contient toutes les fonctions spécifiques à Minecraft :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lancement du jeu&lt;/li&gt;
&lt;li&gt;Création d&apos;un nouveau monde&lt;/li&gt;
&lt;li&gt;Téléportation à des coordonnées aléatoires&lt;/li&gt;
&lt;li&gt;Prise de capture d&apos;écran&lt;/li&gt;
&lt;li&gt;Fermeture du jeu&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/ac91fa2d-95e5-401d-93bb-6a115a33c14a.png&quot; alt=&quot;Paysage Minecraft 5&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Comment tout ça fonctionne&lt;/h2&gt;
&lt;p&gt;Le processus est relativement simple mais nécessite plusieurs étapes coordonnées :&lt;/p&gt;
&lt;h3&gt;1. Lancement de Minecraft&lt;/h3&gt;
&lt;p&gt;Le script utilise la bibliothèque &lt;code&gt;robotgo&lt;/code&gt; pour automatiser les interactions avec l&apos;interface utilisateur et lancer Minecraft.&lt;/p&gt;
&lt;h3&gt;2. Création d&apos;un nouveau monde&lt;/h3&gt;
&lt;p&gt;Une fois Minecraft ouvert, le bot navigue dans les menus pour créer un nouveau monde avec des paramètres aléatoires.&lt;/p&gt;
&lt;h3&gt;3. Téléportation aléatoire&lt;/h3&gt;
&lt;p&gt;Le bot utilise la commande &lt;code&gt;/spreadplayers&lt;/code&gt; de Minecraft pour se téléporter à un endroit aléatoire à la surface du monde généré. Cette commande est particulièrement utile car elle garantit que le joueur apparaît toujours sur un bloc solide.&lt;/p&gt;
&lt;h3&gt;4. Configuration de l&apos;environnement&lt;/h3&gt;
&lt;p&gt;Le bot configure ensuite :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le mode de jeu (créatif ou spectateur pour une meilleure vue)&lt;/li&gt;
&lt;li&gt;L&apos;heure de la journée (aléatoire)&lt;/li&gt;
&lt;li&gt;Les conditions météorologiques&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;5. Prise de capture d&apos;écran&lt;/h3&gt;
&lt;p&gt;Une fois positionné, le bot prend une capture d&apos;écran haute résolution du paysage.&lt;/p&gt;
&lt;h3&gt;6. Publication sur Mastodon&lt;/h3&gt;
&lt;p&gt;Finalement, la capture d&apos;écran est postée sur Mastodon avec une description générée automatiquement.&lt;/p&gt;
&lt;h2&gt;Qu&apos;est-ce qu&apos;on peut améliorer&lt;/h2&gt;
&lt;h3&gt;Automatisation&lt;/h3&gt;
&lt;p&gt;Actuellement, le script doit être lancé manuellement. Une amélioration évidente serait de l&apos;automatiser via :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un serveur dans le cloud avec une tâche cron&lt;/li&gt;
&lt;li&gt;Un service Windows/Linux qui tourne en arrière-plan&lt;/li&gt;
&lt;li&gt;Une instance Docker déployée sur un VPS&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Stockage en masse des captures&lt;/h3&gt;
&lt;p&gt;Au lieu de poster immédiatement, le bot pourrait :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Générer plusieurs captures d&apos;écran à la fois&lt;/li&gt;
&lt;li&gt;Les stocker localement ou dans le cloud&lt;/li&gt;
&lt;li&gt;Les poster selon un planning défini&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Shaders et resource packs aléatoires&lt;/h3&gt;
&lt;p&gt;Pour encore plus de variété, le bot pourrait :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Choisir aléatoirement parmi différents shaders&lt;/li&gt;
&lt;li&gt;Utiliser différents resource packs&lt;/li&gt;
&lt;li&gt;Varier les générateurs de terrain&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Partager les informations du monde&lt;/h3&gt;
&lt;p&gt;Une fonctionnalité intéressante serait de partager :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le seed du monde généré&lt;/li&gt;
&lt;li&gt;Les coordonnées exactes de la capture&lt;/li&gt;
&lt;li&gt;Les paramètres utilisés (heure, météo, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cela permettrait aux joueurs de revisiter ces lieux s&apos;ils le souhaitent.&lt;/p&gt;
&lt;h3&gt;Génération de texte alternatif&lt;/h3&gt;
&lt;p&gt;Pour l&apos;accessibilité, le bot pourrait générer automatiquement des descriptions textuelles des images pour les personnes malvoyantes.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Ce petit projet m&apos;a permis d&apos;explorer l&apos;automatisation d&apos;un jeu vidéo tout en créant du contenu intéressant pour les réseaux sociaux. C&apos;est pas grand chose mais c&apos;est du travail honnête !&lt;/p&gt;
&lt;p&gt;Le code source est disponible sur &lt;a href=&quot;https://github.com/VictorBersy/minecraft-screenshot-bot&quot;&gt;GitHub&lt;/a&gt; pour ceux qui souhaiteraient l&apos;adapter ou l&apos;améliorer.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;Article original publié le 14 décembre 2023&lt;/em&gt;&lt;/p&gt;
</content:encoded></item><item><title>Create a Mastodon Instance with Fly.io</title><link>https://alyx.pink/posts/2023-07-05-create-mastodon-instance-with-flyio/</link><guid isPermaLink="true">https://alyx.pink/posts/2023-07-05-create-mastodon-instance-with-flyio/</guid><description>A comprehensive guide to creating a personal Mastodon instance using Fly.io, Cloudflare, and SendGrid.</description><pubDate>Wed, 05 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;em&gt;Photo by &lt;a href=&quot;https://unsplash.com/@framemily?utm_source=ghost&amp;amp;utm_medium=referral&amp;amp;utm_campaign=api-credit&quot;&gt;frame harirak&lt;/a&gt; / &lt;a href=&quot;https://unsplash.com/?utm_source=ghost&amp;amp;utm_medium=referral&amp;amp;utm_campaign=api-credit&quot;&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I wanted to create my own Mastodon instance for a few days and I went ahead during the night.&lt;/p&gt;
&lt;p&gt;It went pretty smoothly, outside of some unrelated issues I had:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Fly.io CLI version I was using had some weird issues where it was losing the connection to their machines&lt;/li&gt;
&lt;li&gt;The name I used caused troubles with the DB because it was starting with digits (&lt;code&gt;3615-computer&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other than that, it took me about an hour in total from buying the domain to having a Mastodon instance up and running.&lt;/p&gt;
&lt;p&gt;Here are the services I&apos;m using to run my instance:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web hosting&lt;/strong&gt;: fly.io (~7$/month)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;DNS&lt;/strong&gt;: Cloudflare (Free)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Media storage&lt;/strong&gt;: Cloudflare R2 (~2$/month)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mailing&lt;/strong&gt;: SendGrid (Free, 100 emails/day)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide will consider that you want to use the same stack.&lt;/p&gt;
&lt;p&gt;Don&apos;t worry about messing something up: you should not have any issues deleting and recreating multiple times every resource we are using. The web hosting machines, the DNS records, the R2 bucket, etc… Take your time, and when you feel like everything is working, start using the instance and see how it goes.&lt;/p&gt;
&lt;h2&gt;Requirements&lt;/h2&gt;
&lt;p&gt;You will need to install:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/get-docker/&quot;&gt;docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fly.io/docs/hands-on/install-flyctl/&quot;&gt;flyctl&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Setup media storage with Cloudflare R2&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Go to Cloudflare dashboard (https://dash.cloudflare.com/)&lt;/li&gt;
&lt;li&gt;Click on &quot;Create bucket&quot;&lt;/li&gt;
&lt;li&gt;Enter a name for your bucket. It must be unique and you won&apos;t be able to rename it later&lt;/li&gt;
&lt;li&gt;Once created, go to &quot;Settings&quot; and copy your &quot;S3 API&quot; URL, we will use it later&lt;/li&gt;
&lt;li&gt;Create your API keys:
&lt;ul&gt;
&lt;li&gt;In the sidebar, &quot;R2&quot; &amp;gt; &quot;Overview&quot; &amp;gt; &quot;Manage R2 API Tokens&quot; &amp;gt; &quot;Create API token&quot;&lt;/li&gt;
&lt;li&gt;Name your token however your want&lt;/li&gt;
&lt;li&gt;Select &quot;Permissions&quot; &amp;gt; &quot;Edit&quot;&lt;/li&gt;
&lt;li&gt;Don&apos;t set any TTL&lt;/li&gt;
&lt;li&gt;Click &quot;Create API token&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy your &quot;Access Key ID&quot; and &quot;Secret Access Key&quot; for later&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You are done with R2.&lt;/p&gt;
&lt;h2&gt;Setup emails with SendGrid&lt;/h2&gt;
&lt;h3&gt;Authenticate your domain&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to your &lt;a href=&quot;https://app.sendgrid.com&quot;&gt;SendGrid dashboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Go to &quot;Settings&quot; &amp;gt; &lt;a href=&quot;https://app.sendgrid.com/settings/sender_auth&quot;&gt;&quot;Send Authentication&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Authenticate your domain&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Create an API key&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;To create your API key, go to &quot;Settings&quot; &amp;gt; &lt;a href=&quot;https://app.sendgrid.com/settings/api_keys&quot;&gt;&quot;API Keys&quot;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&quot;Create API key&quot;&lt;/li&gt;
&lt;li&gt;Set a name, and select &quot;Restricted Access&quot;&lt;/li&gt;
&lt;li&gt;Select &quot;Mail Send&quot; &amp;gt; &quot;Mail Send&quot; and enable this setting&lt;/li&gt;
&lt;li&gt;Click on &quot;Create and View&quot;&lt;/li&gt;
&lt;li&gt;Copy your secret key, you can&apos;t see it ever again&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Disable tracking&lt;/h3&gt;
&lt;p&gt;Don&apos;t forget to disable tracking by going to &lt;a href=&quot;https://app.sendgrid.com/settings/tracking&quot;&gt;app.sendgrid.com/settings/tracking&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Set the env vars and secrets on Fly.io&lt;/h3&gt;
&lt;p&gt;Use your secret key:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;fly secrets set SMTP_LOGIN=apikey SMTP_PASSWORD=your_secret_key
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In your &lt;code&gt;fly.toml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## Sending email via SMTP also requires secrets
## named SMTP_LOGIN and SMTP_PASSWORD
SMTP_SERVER = &quot;smtp.sendgrid.net&quot;
SMTP_PORT = &quot;587&quot;
SMTP_ENABLE_STARTTLS = &quot;always&quot;
SMTP_FROM_ADDRESS = &quot;noreply@example.com&quot; # Set your from email
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Deploy Mastodon using Fly.io&lt;/h2&gt;
&lt;p&gt;To deploy Mastodon with Fly.io, I&apos;ve used the work of &lt;a href=&quot;https://github.com/tmm1&quot;&gt;@tmm1&lt;/a&gt;: &lt;a href=&quot;https://github.com/tmm1/flyapp-mastodon&quot;&gt;flyapp-mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It gives you a lot of sane defaults, a guide to explain what to do, when, and why, and also a guide to scale your instance further if needed.&lt;/p&gt;
&lt;p&gt;Edit your &lt;code&gt;fly.toml&lt;/code&gt; following the guide on the repository.&lt;/p&gt;
&lt;p&gt;Some notes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I suggest creating a fly.io organization inside your account and setting the &lt;code&gt;--org&lt;/code&gt; parameter for the commands that require it (&lt;code&gt;apps create&lt;/code&gt; and &lt;code&gt;pg create&lt;/code&gt;). It makes it easier to manage your billing and projects. Otherwise, everything will get mixed up in your default &lt;code&gt;personal&lt;/code&gt; space.&lt;/li&gt;
&lt;li&gt;Don&apos;t forget to update the location of your services (&lt;code&gt;sjc&lt;/code&gt; in this example) to the one you want to use (&lt;a href=&quot;https://fly.io/docs/reference/regions/&quot;&gt;complete list&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;You won&apos;t need to attach an IPv4 as Cloudflare will proxy everything for you, exposing your instance with IPv4 and IPv6.&lt;/li&gt;
&lt;li&gt;Some commands are in the wrong order like the &lt;code&gt;fly scale memory 1024&lt;/code&gt; can&apos;t be run just after &lt;code&gt;fly apps create xxx&lt;/code&gt;. No worries, here&apos;s an order that I think is better and easier to follow:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# Create your organization
flyctl orgs create my-org

# Create the machine for the mastodon app
fly apps create --org my-org mastodon-example

# Set required secrets
export SECRET_KEY_BASE=$(docker run --rm -it tootsuite/mastodon:latest bin/rake secret)
export OTP_SECRET=$(docker run --rm -it tootsuite/mastodon:latest bin/rake secret)
fly secrets set OTP_SECRET=$OTP_SECRET SECRET_KEY_BASE=$SECRET_KEY_BASE
docker run --rm -e OTP_SECRET=$OTP_SECRET -e SECRET_KEY_BASE=$SECRET_KEY_BASE -it tootsuite/mastodon:latest bin/rake mastodon:webpush:generate_vapid_key | sed &apos;s/\r//&apos; | fly secrets import
fly secrets set AWS_ACCESS_KEY_ID=xxx AWS_SECRET_ACCESS_KEY=yyy
fly secrets set SMTP_LOGIN=&amp;lt;public token&amp;gt; SMTP_PASSWORD=&amp;lt;secret token&amp;gt;

# Create the redis machine
fly apps create --org my-org mastodon-example-redis
bin/fly-redis volumes create --region sjc --size 1 mastodon_redis
bin/fly-redis deploy

# Create the PostgreSQL machine
fly pg create --org my-org --region sjc --name mastodon-example-db
fly pg attach mastodon-example-db
fly deploy -c fly.setup.toml # run `rails db:schema:load`, may take 2-3 minutes

# Deploy the application
fly deploy

# Scale the app machine to 1GB of RAM
fly scale memory 1024

# Set yourself as the owner of your own instance, after signing up
fly ssh console -C &apos;tootctl accounts modify &amp;lt;username&amp;gt; --confirm --role Owner&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Setup DNS with Cloudflare&lt;/h2&gt;
&lt;h3&gt;Add your domain to Cloudflare&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to Cloudflare dashboard (https://dash.cloudflare.com/)&lt;/li&gt;
&lt;li&gt;Click &quot;Add a site&quot; and enter your domain name (example.com)&lt;/li&gt;
&lt;li&gt;Select the Free plan and click on &quot;Continue&quot;&lt;/li&gt;
&lt;li&gt;They are going to scan your DNS records, if you have any, to copy them&lt;/li&gt;
&lt;li&gt;Update your domain&apos;s nameservers as described&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If everything is ok, the check will pass.&lt;/p&gt;
&lt;h3&gt;Follow their quick start guide&lt;/h3&gt;
&lt;p&gt;During the quick start guide:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Improve security
&lt;ul&gt;
&lt;li&gt;Enable &quot;Automatic HTTPS Rewrites&quot;&lt;/li&gt;
&lt;li&gt;Enable &quot;Always Use HTTPS&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Optimize performance
&lt;ul&gt;
&lt;li&gt;Enable &quot;Brotli&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Summary: check all our options are &quot;ON&quot; and click &quot;Finish&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Setup your DNS records for Mastodon&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In Cloudflare&apos;s sidebar, go to &quot;DNS &amp;gt; Records&quot;&lt;/li&gt;
&lt;li&gt;Click on &quot;Add record&quot;&lt;/li&gt;
&lt;li&gt;You need to add:
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;AAAA&lt;/code&gt; records for your instance
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;fly ips list&lt;/code&gt; will show it&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;CNAME&lt;/code&gt; for your fly.io certificate validation
&lt;ul&gt;
&lt;li&gt;In the fly.io dashboard, go to your mastodon app instance &amp;gt; Certificates &amp;gt; View &amp;gt; Domain ownership verification&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In Cloudflare&apos;s sidebar, go to &quot;SSL/TLS &amp;gt; Overview&quot;&lt;/li&gt;
&lt;li&gt;Set the encryption mode to &quot;Full&quot;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Setup your DNS records to redirect any inbound emails to your personal email&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;In Cloudflare&apos;s sidebar, go to &quot;Email &amp;gt; Email Routing&quot;&lt;/li&gt;
&lt;li&gt;Follow their guide to set any DNS records required (they do everything for you)&lt;/li&gt;
&lt;li&gt;Once done, in the &quot;Routes&quot; tab, you should have something similar to:
&lt;ul&gt;
&lt;li&gt;Custom addresses: noreply@example.com | Send to an email | your personal email | Active&lt;/li&gt;
&lt;li&gt;Catch-all address: enabled | Send to an email | your personal email as destination&lt;/li&gt;
&lt;li&gt;Destination addresses: your personal email | Verified&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;SendGrid takes care of sending emails from Mastodon, and Cloudflare takes care of redirecting any inbound emails to your personal address.&lt;/p&gt;
&lt;h3&gt;Setup your DNS records for Cloudflare R2&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to Cloudflare dashboard (https://dash.cloudflare.com/)&lt;/li&gt;
&lt;li&gt;In the sidebar, go to &quot;R2&quot; &amp;gt; &quot;Overview&quot;&lt;/li&gt;
&lt;li&gt;Click on your bucket name &amp;gt; &quot;Settings&quot;&lt;/li&gt;
&lt;li&gt;In the &quot;Public access&quot; section, click on &quot;Connect Domain&quot; and enter the domain or subdomain name you want to use (example: &lt;code&gt;mastodon-files.example.com&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Review the settings and click on &quot;Connect domain&quot; and you are done&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Setup Cloudflare R2 as your media storage&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;In your &lt;code&gt;fly.toml&lt;/code&gt; file, add:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[env]
S3_ENABLED = true
S3_BUCKET = &quot;3615-computer-mastodon&quot; # Name of your R2 bucket
S3_ALIAS_HOST = &quot;mastodon-files.3615.computer&quot; # Subdomain of your bucket public access
S3_ENDPOINT = &quot;https://xxx.r2.cloudflarestorage.com/&quot; # Endpoint for your bucket (click on your bucket to see it)
S3_PERMISSION = &quot;private&quot;
S3_PROTOCOL = &quot;https&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Comment the &quot;mount&quot; section of your &lt;code&gt;fly.toml&lt;/code&gt; file:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;## Comment out this section if you use cloud storage
# [mounts]
#   source = &quot;mastodon_uploads&quot;
#   destination = &quot;/opt/mastodon/public/system&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Generate your API keys:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to Cloudflare &amp;gt; R2 &amp;gt; Manage R2 API Tokens&lt;/li&gt;
&lt;li&gt;Permissions: Edit&lt;/li&gt;
&lt;li&gt;No TTL (Forever)&lt;/li&gt;
&lt;li&gt;Copy your access and secret keys for the next step&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Set your secrets:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;fly secrets set AWS_ACCESS_KEY_ID=xxx
fly secrets set AWS_SECRET_ACCESS_KEY=xxx
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Improve Docker build time by using BuildKit and SemaphoreCI</title><link>https://alyx.pink/posts/2020-09-13-improve-docker-build-time-by-using-buildkit-and-semaphoreci/</link><guid isPermaLink="true">https://alyx.pink/posts/2020-09-13-improve-docker-build-time-by-using-buildkit-and-semaphoreci/</guid><description>Learn how to significantly improve Docker build times using BuildKit and SemaphoreCI&apos;s private registry, reducing build time from 3:30 to 2:00 minutes with proper caching.</description><pubDate>Sun, 13 Sep 2020 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Context&lt;/h2&gt;
&lt;p&gt;At my current job, we use Docker on all our environments: from local to production, and for our CI pipelines.&lt;/p&gt;
&lt;p&gt;The first CI step is to build a Docker image that will be used during the whole pipeline and eventually deployed if tests pass.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/1ced0f00-38ba-4f17-8684-33dcdca8d1b2.png&quot; alt=&quot;Our CI pipeline&quot; /&gt;&lt;/p&gt;
&lt;p&gt;The faster the build, the faster the tests and others things developers care about will start, so it&apos;s crucial to not &quot;waste&quot; minutes on builds.&lt;/p&gt;
&lt;p&gt;We mostly have web projects, and they all have some kind of dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PHP packages through &lt;code&gt;composer&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;JS packages through &lt;code&gt;npm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;PHP extensions&lt;/li&gt;
&lt;li&gt;etc...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All of these dependencies can require a lot of time to be installed. And so, in order to keep the build time as short as possible, caching their installation could make our build much shorter.&lt;/p&gt;
&lt;p&gt;While most CI providers have tools to cache these dependencies in one way or another, ideally, you&apos;d like to cache Docker layers to make things easier. You won&apos;t have to set up different tools for different package providers, and you can even cache other things like &lt;code&gt;apt-get&lt;/code&gt;, &lt;code&gt;apk&lt;/code&gt;, etc... that you might run during your build.&lt;/p&gt;
&lt;p&gt;Here&apos;s a typical multi-stage Dockerfile that we use for our PHP applications:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM composer:2 as composer-build
...
FROM node:10-alpine as npm-build
...
FROM php:7.4-apache as php-extensions-build
...
# Final build, &quot;app&quot;, our base for others
FROM php:7.4-apache as app
...
# Various stages for release/dev/etc...
FROM app as release
...
# Back dev stage, install backend dev tools here
FROM app as dev-back
...
# Front dev stage, install frontend dev tools here
FROM npm-build as dev-front
...
# Test stage, used during CI/CD and local testing
FROM app as test
...
# Default docker build target will be &apos;app&apos;
FROM app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use multi-stage Docker build so we can pick exactly what we want for release images, or for our dev environments, keeping our images as light as we can.&lt;/p&gt;
&lt;h2&gt;Issues we were facing&lt;/h2&gt;
&lt;h3&gt;Caching all docker stages manually is hard&lt;/h3&gt;
&lt;p&gt;The easiest solution, at first sight, would be to build each stage separately and tag them:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker build -t my-project:composer-build --target=composer-build .
docker build -t my-project:npm-build --target=npm-build .
docker build -t my-project:php-extensions-build --target=php-extensions-build .
# etc...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And send everything to a container registry such as AWS ECR:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker push aws_account_id.dkr.ecr.region.amazonaws.com/my-project:composer-build
docker push aws_account_id.dkr.ecr.region.amazonaws.com/my-project:npm-build
docker push aws_account_id.dkr.ecr.region.amazonaws.com/my-project:php-extensions-build
# etc...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you&apos;d have to pull each of these stages before starting to build your image.&lt;/p&gt;
&lt;p&gt;But you may have noticed it&apos;s now a lot of things to manually setup, and maintain if you add a new stage at some point.&lt;/p&gt;
&lt;h3&gt;Running builds in parallel is hard&lt;/h3&gt;
&lt;p&gt;For example, &lt;code&gt;composer-build&lt;/code&gt; and &lt;code&gt;npm-build&lt;/code&gt; do not depend on each other, so you could build them in parallel.&lt;/p&gt;
&lt;p&gt;But you would have to write your own parser, or just come up with some bash scripts to do that, and maintain it over time too. That would be tedious! Especially when you know this dependency graph is already described in your Dockerfile.&lt;/p&gt;
&lt;p&gt;Maybe I&apos;m missing something, but as far as I know, without BuildKit, you can&apos;t easily build stages in parallel.&lt;/p&gt;
&lt;h3&gt;AWS ECR or any other remote registry is too slow to cache our stages&lt;/h3&gt;
&lt;p&gt;Another issue that might depend on your CI provider: pushing Docker images to the remote registry (AWS ECR, GCP Container Registry, etc...) is taking a long time.&lt;/p&gt;
&lt;p&gt;Pulling/pushing each stage to a remote registry is going to take a significant amount of your build time step. Plus, it would clutter your registry with intermediate stages that you don&apos;t care about long-term wise.&lt;/p&gt;
&lt;h2&gt;Solution: Using BuildKit&lt;/h2&gt;
&lt;p&gt;To solve our issues related to caching and parallel builds, we are now using BuildKit.&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href=&quot;https://github.com/moby/buildkit&quot;&gt;BuildKit project&lt;/a&gt;, we can easily solve all our issues with a single command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;buildctl build ... \
  --output type=image,name=localhost:5000/myrepo:image-test,push=true \
  --export-cache type=registry,ref=localhost:5000/myrepo:buildcache,push=true \
  --import-cache type=registry,ref=localhost:5000/myrepo:buildcache \
  --opt target=test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we are asking to target the &lt;code&gt;test&lt;/code&gt; stage, we are also sure to build only what we want without having to maintain any kind of manual script.&lt;/p&gt;
&lt;p&gt;What we are saying to BuildKit is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The final image created must be pushed to &lt;code&gt;localhost:5000/myrepo:image-test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Export/Import cache to/from &lt;code&gt;localhost:5000/myrepo:buildcache&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;We want to build the test image, so we pass &lt;code&gt;--opt target=test&lt;/code&gt; as a parameter.
&lt;ul&gt;
&lt;li&gt;This way all our test tools will be there for next CI steps.&lt;/li&gt;
&lt;li&gt;To create the release image, just change it to &lt;code&gt;--opt target=release&lt;/code&gt; and name the new image as you&apos;d like.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Solution: Use your CI provider&apos;s private Docker registry&lt;/h2&gt;
&lt;p&gt;I&apos;m not sure if other CI providers have something similar, but SemaphoreCI had a good idea to deploy their own Docker registry, where it&apos;s much faster to push your cache and images.&lt;/p&gt;
&lt;p&gt;The push time is excellent, much better than on AWS ECR.&lt;/p&gt;
&lt;p&gt;Thanks to this, we can use their private Docker registry to push/pull our intermediate stages and cache layers.&lt;/p&gt;
&lt;h2&gt;Before / after + screenshots&lt;/h2&gt;
&lt;p&gt;On a basic Laravel application, here are the results.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before BuildKit:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/ad08620d-eb87-4fcb-9c92-30584a0e4d38.png&quot; alt=&quot;Before using BuildKit&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After BuildKit:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/63cce72d-e6ae-45fb-980d-5000090eb0a3.png&quot; alt=&quot;After using BuildKit&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Before using BuildKit, the pipeline was taking around ~3min30s. It&apos;s now taking ~2min. Though, it depends on what cache layers need to be rebuilt. If none of them are changing, like if you are running again the same build and it&apos;s already cached, it takes ~14s.&lt;/p&gt;
&lt;h2&gt;What improvements I&apos;d like to make?&lt;/h2&gt;
&lt;h3&gt;Tracing our CI pipelines&lt;/h3&gt;
&lt;p&gt;I&apos;m a fan of observability and I want to apply this methodology on our CI/CD pipelines too. BuildKit comes with a way to export traces to Jaeger, and it can be visualized like so:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/f85b05ef-d9ec-4bfc-88e1-002e1bc678e7.jpg&quot; alt=&quot;BuildKit traces exported and visualized with Jaeger&quot; /&gt;&lt;/p&gt;
&lt;p&gt;By doing so, we could track over time the build time easily for each step. We could see the effects on what steps. We can also avoid wasting time optimizing a step if there is another that takes 90% of the build time.&lt;/p&gt;
&lt;h3&gt;Move out from CI the push/tag release steps&lt;/h3&gt;
&lt;p&gt;You may have noticed that I am building, pushing, and tagging the release candidate image during the CI pipeline. I did not realize while upgrading our CI/CD pipelines that it does not make sense to have these things there.&lt;/p&gt;
&lt;p&gt;Ideally, I&apos;d like to have something like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CI:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Build test image&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;li&gt;Build release candidate image&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CD:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Push release candidate image to AWS ECR&lt;/li&gt;
&lt;li&gt;Tag release candidate as &quot;release&quot; on AWS ECR&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy to X&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Deploy to Kubernetes development&lt;/li&gt;
&lt;li&gt;Deploy to Kubernetes staging&lt;/li&gt;
&lt;li&gt;Deploy to Kubernetes production&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By doing so, I could make the CI pipeline even shorter and our developers would receive automated feedback faster.&lt;/p&gt;
&lt;p&gt;That would give something like:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://slink.alyx.pink/image/95694e84-c977-487a-a5b3-adb12f7c66eb.png&quot; alt=&quot;Improved CI/CD pipeline&quot; /&gt;&lt;/p&gt;
</content:encoded></item></channel></rss>