{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Quick tips and learnings",
  "home_page_url": "https://pype.dev/til/",
  "feed_url": "https://pype.dev/til/feed.json",
  "description": "My thoughts and streams of consciousness organized into barely coherent posts about things",
  "authors": [
    {
      "name": "Nic Payne"
    }
  ],
  "items": [
    {
      "id": "https://pype.dev/agents-can-miss-obvious-things-too/",
      "url": "https://pype.dev/agents-can-miss-obvious-things-too/",
      "title": "Agents can miss obvious things too",
      "content_html": "\u003cp\u003eI am working with MiniMax M.2 to vibe up some webapp ideas for \u003ca href=\"/nexus/\" class=\"wikilink\" data-title=\"Nexus\" data-description=\"Nexus is a task tracker and second-brain for collaborating with AI agents on software development. It keeps project boards, task details, subtasks, daily...\" data-date=\"2026-02-05\" data-preview=\"Nexus is a task tracker and second-brain for collaborating with AI agents on software development. It keeps project boards, task details, subtasks, daily...\"\u003eNexus\u003c/a\u003e and I\njust spent many minutes not having the app render correctly because the agent\nmounted the wrong directory into the \u003ca href=\"/docker/\" class=\"glossary-term\" title=\"docker - container runtime\" data-hover-title=\"docker\"\u003edocker\u003c/a\u003e compose stack. I noticed it about 5\nseconds after opening the file, but wasted nearly a half hour thinking it was a\ndocker issue in distrobox\u0026hellip;\u003c/p\u003e\n\u003cdiv class=\"admonition danger\"\u003e\n\u003cp class=\"admonition-title\"\u003eDanger\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/clankers/\" class=\"glossary-term\" title=\"An appropriately derogatory reference to AI agents\" data-hover-title=\"Clankers\"\u003eClankers\u003c/a\u003e are still just clankers man\u003c/p\u003e\n\u003c/div\u003e\n",
      "content_text": "\nI am working with MiniMax M.2 to vibe up some webapp ideas for \u003ca href=\"/nexus/\" class=\"wikilink\" data-title=\"Nexus\" data-description=\"Nexus is a task tracker and second-brain for collaborating with AI agents on software development. It keeps project boards, task details, subtasks, daily...\" data-date=\"2026-02-05\"\u003eNexus\u003c/a\u003e and I\njust spent many minutes not having the app render correctly because the agent\nmounted the wrong directory into the docker compose stack. I noticed it about 5\nseconds after opening the file, but wasted nearly a half hour thinking it was a\ndocker issue in distrobox...\n\n!!! danger \"\"\n\n    Clankers are still just clankers man\n",
      "summary": "I am working with MiniMax M.2 to vibe up some webapp ideas for Nexus and I just spent many minutes not having the app render correctly because the agent...",
      "date_published": "2026-04-03T14:39:51Z",
      "date_modified": "2026-04-03T14:39:51Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "agents",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/prettier-docker-ps-command/",
      "url": "https://pype.dev/prettier-docker-ps-command/",
      "title": "Prettier Docker ‘ps’ Command",
      "content_html": "\u003cp\u003eThe \u003ccode\u003edocker ps\u003c/code\u003e command is very useful, but I hate reading the output. Turns\nout, you can make it prettier:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003edocker ps\u003c/code\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u0026ndash;format \u0026ldquo;table\u0026rdquo; is implied with the command.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cpre\u003e\u003ccode\u003e\n❯ docker ps --format \u0026quot;table\u0026quot; | grep can\n9b716a7d1ab0   postgres:17                                               \u0026quot;docker-entrypoint.s…\u0026quot;   13 hours ago    Up 13 hours (healthy)            0.0.0.0:5432-\u0026gt;5432/tcp, [::]:5432-\u0026gt;5432/tcp                                                cannalyzer-db-1\nf7708ea0c112   adminer                                                   \u0026quot;entrypoint.sh docke…\u0026quot;   13 hours ago    Up 13 hours                      0.0.0.0:8081-\u0026gt;8080/tcp, [::]:8081-\u0026gt;8080/tcp                                                cannalyzer-adminer-1\n93cd67719ed4   frontend:latest                                           \u0026quot;/docker-entrypoint.…\u0026quot;   13 hours ago    Up 13 hours                      0.0.0.0:5173-\u0026gt;80/tcp, [::]:5173-\u0026gt;80/tcp                                                    cannalyzer-frontend-1\nf517625fca98   traefik:3.0                                               \u0026quot;/entrypoint.sh --pr…\u0026quot;   13 hours ago    Up 13 hours                      0.0.0.0:80-\u0026gt;80/tcp, [::]:80-\u0026gt;80/tcp, 0.0.0.0:8090-\u0026gt;8080/tcp, [::]:8090-\u0026gt;8080/tcp           cannalyzer-proxy-1\nf4daa036216e   schickling/mailcatcher                                    \u0026quot;sh -c 'mailcatcher …\u0026quot;   13 hours ago    Up 13 hours                      0.0.0.0:1025-\u0026gt;1025/tcp, [::]:1025-\u0026gt;1025/tcp, 0.0.0.0:1080-\u0026gt;1080/tcp, [::]:1080-\u0026gt;1080/tcp   cannalyzer-mailcatcher-1\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eBut you can pass a template string to the \u003ccode\u003e--format\u003c/code\u003e option, like so:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003edocker ps --format \u0026quot;table {{.Names}}\u0026quot;\u003c/code\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\n✗ docker ps --format \u0026quot;table {{.Names}}\u0026quot; | grep can\ncannalyzer-db-1\ncannalyzer-adminer-1\ncannalyzer-frontend-1\ncannalyzer-proxy-1\ncannalyzer-mailcatcher-1\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003edocker ps --format \u0026quot;table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\u0026quot;\u003c/code\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\n❯ docker ps --format \u0026quot;table {{.Names}}\\t{{.Ports}}\u0026quot; | grep can\ncannalyzer-db-1             0.0.0.0:5432-\u0026gt;5432/tcp, [::]:5432-\u0026gt;5432/tcp\ncannalyzer-adminer-1        0.0.0.0:8081-\u0026gt;8080/tcp, [::]:8081-\u0026gt;8080/tcp\ncannalyzer-frontend-1       0.0.0.0:5173-\u0026gt;80/tcp, [::]:5173-\u0026gt;80/tcp\ncannalyzer-proxy-1          0.0.0.0:80-\u0026gt;80/tcp, [::]:80-\u0026gt;80/tcp, 0.0.0.0:8090-\u0026gt;8080/tcp, [::]:8090-\u0026gt;8080/tcp\ncannalyzer-mailcatcher-1    0.0.0.0:1025-\u0026gt;1025/tcp, [::]:1025-\u0026gt;1025/tcp, 0.0.0.0:1080-\u0026gt;1080/tcp, [::]:1080-\u0026gt;1080/tcp\n\u003c/code\u003e\u003c/pre\u003e\n\u003ch2 id=\"picture\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePicture\u003c/span\u003e \u003ca href=\"#picture\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI noticed my template isn\u0026rsquo;t folding the codeblocks in a way that actually makes this post look like I\u0026rsquo;m lying!\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251229122717_67e76a67.png\" alt=\"20251229122717_67e76a67.png\"\u003e\n\u003c/figure\u003e\n",
      "content_text": "\nThe `docker ps` command is very useful, but I hate reading the output. Turns\nout, you can make it prettier:\n\n`docker ps`\n\n\u003e --format \"table\" is implied with the command.\n\n```\n\n❯ docker ps --format \"table\" | grep can\n9b716a7d1ab0   postgres:17                                               \"docker-entrypoint.s…\"   13 hours ago    Up 13 hours (healthy)            0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp                                                cannalyzer-db-1\nf7708ea0c112   adminer                                                   \"entrypoint.sh docke…\"   13 hours ago    Up 13 hours                      0.0.0.0:8081-\u003e8080/tcp, [::]:8081-\u003e8080/tcp                                                cannalyzer-adminer-1\n93cd67719ed4   frontend:latest                                           \"/docker-entrypoint.…\"   13 hours ago    Up 13 hours                      0.0.0.0:5173-\u003e80/tcp, [::]:5173-\u003e80/tcp                                                    cannalyzer-frontend-1\nf517625fca98   traefik:3.0                                               \"/entrypoint.sh --pr…\"   13 hours ago    Up 13 hours                      0.0.0.0:80-\u003e80/tcp, [::]:80-\u003e80/tcp, 0.0.0.0:8090-\u003e8080/tcp, [::]:8090-\u003e8080/tcp           cannalyzer-proxy-1\nf4daa036216e   schickling/mailcatcher                                    \"sh -c 'mailcatcher …\"   13 hours ago    Up 13 hours                      0.0.0.0:1025-\u003e1025/tcp, [::]:1025-\u003e1025/tcp, 0.0.0.0:1080-\u003e1080/tcp, [::]:1080-\u003e1080/tcp   cannalyzer-mailcatcher-1\n```\n\nBut you can pass a template string to the `--format` option, like so:\n\n`docker ps --format \"table {{.Names}}\"`\n\n```\n\n✗ docker ps --format \"table {{.Names}}\" | grep can\ncannalyzer-db-1\ncannalyzer-adminer-1\ncannalyzer-frontend-1\ncannalyzer-proxy-1\ncannalyzer-mailcatcher-1\n```\n\n`docker ps --format \"table {{.Names}}\\t{{.Status}}\\t{{.Ports}}\"`\n\n```\n\n❯ docker ps --format \"table {{.Names}}\\t{{.Ports}}\" | grep can\ncannalyzer-db-1             0.0.0.0:5432-\u003e5432/tcp, [::]:5432-\u003e5432/tcp\ncannalyzer-adminer-1        0.0.0.0:8081-\u003e8080/tcp, [::]:8081-\u003e8080/tcp\ncannalyzer-frontend-1       0.0.0.0:5173-\u003e80/tcp, [::]:5173-\u003e80/tcp\ncannalyzer-proxy-1          0.0.0.0:80-\u003e80/tcp, [::]:80-\u003e80/tcp, 0.0.0.0:8090-\u003e8080/tcp, [::]:8090-\u003e8080/tcp\ncannalyzer-mailcatcher-1    0.0.0.0:1025-\u003e1025/tcp, [::]:1025-\u003e1025/tcp, 0.0.0.0:1080-\u003e1080/tcp, [::]:1080-\u003e1080/tcp\n```\n\n## Picture\n\nI noticed my template isn't folding the codeblocks in a way that actually makes this post look like I'm lying!\n\n![20251229122717_67e76a67.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251229122717_67e76a67.png)\n",
      "summary": "The command is very useful, but I hate reading the output. Turns out, you can make it prettier:",
      "date_published": "2025-12-29T05:33:05Z",
      "date_modified": "2025-12-29T05:33:05Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "docker",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/columns-env-var-for-nicer-screenshots/",
      "url": "https://pype.dev/columns-env-var-for-nicer-screenshots/",
      "title": "COLUMNS Env Var For Nicer Screenshots",
      "content_html": "\u003cp\u003eI was working on \u003ca href=\"/uv-run-pep-723-is-a-match-made-in-heaven/\" class=\"wikilink\" data-title=\"UV Run + PEP 723 Is A Match Made In Heaven\" data-description=\"This post will be short but hopefully be a dense few paragraphs about how using along with PEP 723 is life-changing.\" data-date=\"2025-12-08\" data-preview=\"This post will be short but hopefully be a dense few paragraphs about how using along with PEP 723 is life-changing.\"\u003eUV Run + PEP 723 Is A Match Made In Heaven\u003c/a\u003e and I wanted to grab a screenshot of a TUI, but my terminal is full screen and the TUI was then super stretched\u0026hellip; Nice for working and using it, but not nice for a screenshot going into a blog post\u0026hellip; see the difference \u003ccode\u003eexport COLUMNS=120\u003c/code\u003e does here!\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eCOLUMNS=120 mytui.py\n\u003c/code\u003e\u003c/pre\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251208111709_aa06bd03.png\" alt=\"20251208111709_aa06bd03.png\"\u003e\n\u003c/figure\u003e\n\u003cpre\u003e\u003ccode\u003emytui.py\n\u003c/code\u003e\u003c/pre\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251208111820_a209638f.png\" alt=\"20251208111820_a209638f.png\"\u003e\n\u003c/figure\u003e\n",
      "content_text": "\nI was working on \u003ca href=\"/uv-run-pep-723-is-a-match-made-in-heaven/\" class=\"wikilink\" data-title=\"UV Run + PEP 723 Is A Match Made In Heaven\" data-description=\"This post will be short but hopefully be a dense few paragraphs about how using along with PEP 723 is life-changing.\" data-date=\"2025-12-08\"\u003eUV Run + PEP 723 Is A Match Made In Heaven\u003c/a\u003e and I wanted to grab a screenshot of a TUI, but my terminal is full screen and the TUI was then super stretched... Nice for working and using it, but not nice for a screenshot going into a blog post... see the difference `export COLUMNS=120` does here!\n\n```\nCOLUMNS=120 mytui.py\n```\n\n![20251208111709_aa06bd03.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251208111709_aa06bd03.png)\n\n```\nmytui.py\n```\n\n![20251208111820_a209638f.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251208111820_a209638f.png)\n",
      "summary": "I was working on uv-run-pep-723-is-a-match-made-in-heaven and I wanted to grab a screenshot of a TUI, but my terminal is full screen and the TUI was then...",
      "date_published": "2025-12-08T05:16:55Z",
      "date_modified": "2025-12-08T05:16:55Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "terminal",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/check-swap-in-htop/",
      "url": "https://pype.dev/check-swap-in-htop/",
      "title": "Check SWAP in htop",
      "content_html": "\u003cp\u003eI struggle with maxxing swap on all my machines, I am sure it\u0026rsquo;s a neovim config\nthat I\u0026rsquo;ve propagated everywhere and I\u0026rsquo;m too lazy to figure it out. Instead I\njust swapoff and swapon \u003ca href=\"/kind/\" class=\"glossary-term\" title=\"kind - kubernetes in docker\" data-hover-title=\"kind\"\u003ekind\u003c/a\u003e of often\u0026hellip; but this morning I had to know how to\nsee what\u0026rsquo;s using all that swap\u0026hellip;.\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251115125424_a1ddbd7c.png\" alt=\"20251115125424_a1ddbd7c.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eThe way to see SWAP usage by process in htop is:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eopen htop\u003c/li\u003e\n\u003cli\u003eSlam \u003ccode\u003eF2\u003c/code\u003e (\u003ccode\u003ef\u003c/code\u003e if you\u0026rsquo;re just in old school \u003ccode\u003etop\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003esee the screenshot, find M_SWAP\u003c/li\u003e\n\u003cli\u003emove M_SWAP to \u003ccode\u003eActive Columns\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251115125603_5d4af687.png\" alt=\"20251115125603_5d4af687.png\"\u003e\n\u003cfigcaption\u003e\u003cp\u003eCredit to \u003ca href=\"https://superuser.com/questions/206424/whats-using-my-swap-ubuntu\"\u003ethis superuser post\u003c/a\u003e\u003c/p\u003e\u003c/figcaption\u003e\u003c/figure\u003e\n",
      "content_text": "\nI struggle with maxxing swap on all my machines, I am sure it's a neovim config\nthat I've propagated everywhere and I'm too lazy to figure it out. Instead I\njust swapoff and swapon kind of often... but this morning I had to know how to\nsee what's using all that swap....\n\n![20251115125424_a1ddbd7c.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251115125424_a1ddbd7c.png)\n\nThe way to see SWAP usage by process in htop is:\n\n1. open htop\n2. Slam `F2` (`f` if you're just in old school `top`)\n3. see the screenshot, find M_SWAP\n4. move M_SWAP to `Active Columns`\n\n![20251115125603_5d4af687.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251115125603_5d4af687.png)\n\n\u003e Credit to [this superuser post](https://superuser.com/questions/206424/whats-using-my-swap-ubuntu)\n",
      "summary": "I struggle with maxxing swap on all my machines, I am sure it's a neovim config that I've propagated everywhere and I'm too lazy to figure it out. Instead I...",
      "date_published": "2025-11-15T06:52:14Z",
      "date_modified": "2025-11-15T06:52:14Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "dev",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/use-jellyfin-tags-for-content-moderation/",
      "url": "https://pype.dev/use-jellyfin-tags-for-content-moderation/",
      "title": "Use Jellyfin Tags For Content Moderation",
      "content_html": "\u003cp\u003eJellyfin brings in ratings and allows you to set custom ratings for shows,\nwhich is nice for things like \u0026ldquo;block anything worse than TV-14 for my kid\u0026rsquo;s\naccount\u0026rdquo;. But what if my firend\u0026rsquo;s kids can watch shows I don\u0026rsquo;t want my kids\nwatching? Turns out tags exist and are great!\u003c/p\u003e\n\u003ch2 id=\"tag-the-content\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTag the content\u003c/span\u003e \u003ca href=\"#tag-the-content\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFirst is to actually tag the content - in the UI it\u0026rsquo;s simple.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eLogin with admin\u003c/li\u003e\n\u003cli\u003eManage Metadata\u003c/li\u003e\n\u003cli\u003eClick the content you want to tag and find the \u003ccode\u003eTags\u003c/code\u003e section\u003c/li\u003e\n\u003cli\u003eAdd your tag\u0026hellip; for example \u003ccode\u003eblocked:girls\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"filter-on-the-account\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFilter on the account\u003c/span\u003e \u003ca href=\"#filter-on-the-account\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eGo to \u003ccode\u003eDashboard\u003c/code\u003e -\u0026gt; \u003ccode\u003eUsers\u003c/code\u003e -\u0026gt; \u003ccode\u003eLibrary Access\u003c/code\u003e -\u0026gt; \u003ccode\u003eParental Control\u003c/code\u003e -\u0026gt; \u003ccode\u003eBlock items with tags\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\nJellyfin brings in ratings and allows you to set custom ratings for shows,\nwhich is nice for things like \"block anything worse than TV-14 for my kid's\naccount\". But what if my firend's kids can watch shows I don't want my kids\nwatching? Turns out tags exist and are great!\n\n## Tag the content\n\nFirst is to actually tag the content - in the UI it's simple.\n\n1. Login with admin\n2. Manage Metadata\n3. Click the content you want to tag and find the `Tags` section\n4. Add your tag... for example `blocked:girls`\n\n## Filter on the account\n\n1. Go to `Dashboard` -\u003e `Users` -\u003e `Library Access` -\u003e `Parental Control` -\u003e `Block items with tags`\n",
      "summary": "Jellyfin brings in ratings and allows you to set custom ratings for shows, which is nice for things like \"block anything worse than TV-14 for my kid's...",
      "date_published": "2025-10-08T10:21:01Z",
      "date_modified": "2025-10-08T10:21:01Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "jellyfin",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/sending-sms-with-smtp/",
      "url": "https://pype.dev/sending-sms-with-smtp/",
      "title": "Sending SMS with SMTP",
      "content_html": "\u003cp\u003eI\u0026rsquo;m cooking something up over here that will include SMS message notifications\nas a feature and I\u0026rsquo;ve been doing some reading on different providers\u0026hellip;\u003c/p\u003e\n\u003cp\u003eMany specialize in bulk SMS messaging, like for campaigns and promotions. So\nthis pricing model doesn\u0026rsquo;t work for me.\u003c/p\u003e\n\u003cp\u003eThe providers that do quick single SMS messages are often $0.005 to $.04 cents\nper message which isn\u0026rsquo;t insanity but I\u0026rsquo;m not exactly backed by anyone\u0026hellip;\u003c/p\u003e\n\u003cp\u003eWell today I learned that you can actually send SMS messages via SMTP! I\nwouldn\u0026rsquo;t bank on total reliability here but for testing and initial roll out of\nwhat I want I think this will be perfect.\u003c/p\u003e\n\u003cp\u003eAnd because I\u0026rsquo;m just that \u003ca href=\"/kind/\" class=\"glossary-term\" title=\"kind - kubernetes in docker\" data-hover-title=\"kind\"\u003ekind\u003c/a\u003e of guy, here\u0026rsquo;s some code you can \u003ccode\u003euv run smtp2sms.py\u003c/code\u003e and get yourself a handy little text. I\u0026rsquo;m going to build a simpler\nmodule but you can get a \u003ccode\u003egmail\u003c/code\u003e and \u003ccode\u003egmail app password\u003c/code\u003e within about 10\nminutes. \u003ca href=\"https://support.google.com/mail/answer/185833?hl=en\"\u003esee here\u003c/a\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# /// script\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# requires-python = \u0026#34;\u0026gt;=3.10\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# dependencies = [\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# ]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# ///\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e,\u003c/span\u003e \u003cspan class=\"nn\"\u003esmtplib\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eemail.mime.text\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eMIMEText\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eSMTP_APP_USER\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SMTP_APP_USER\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# me@gmail.com\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eSMTP_APP_PASSWORD\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SMTP_APP_PASSWORD\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eSMTP_SERVER\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;SMTP_SERVER\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# smtp.gmail.ch\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# NOTE: I haven\u0026#39;t verified all the carrier domains - this was provided by ChatGPT\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecarriers\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;att\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;txt.att.net\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mms.att.net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;tmobile\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;tmomail.net\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;tmomail.net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;verizon\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e# \u0026#34;sms\u0026#34;: \u0026#34;vtext.com\u0026#34;, # not reliable\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;vzwpix.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;vzwpix.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;sprint\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;messaging.sprintpcs.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;pm.sprint.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;googlefi\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;msg.fi.google.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;msg.fi.google.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;uscellular\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;email.uscc.net\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mms.uscc.net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;boost\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;sms.myboostmobile.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;myboostmobile.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;cricket\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;sms.cricketwireless.net\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mms.cricketwireless.net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;metropcs\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mymetropcs.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;mymetropcs.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;virgin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;sms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;vmobl.com\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;mms\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;vmobl.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003econtacts\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"s2\"\u003e\u0026#34;me\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;carrier\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;verizon\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;number\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;1234567890\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eMIMEText\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Hello from a python script? What?!\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;From\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eSMTP_APP_USER\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eSEND_TO\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;me\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Complex for my future use cases - deal with it\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;To\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003econtacts\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eSEND_TO\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;number\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e@\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ecarriers\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003econtacts\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eSEND_TO\u003c/span\u003e\u003cspan class=\"p\"\u003e][\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;carrier\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]][\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;sms\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"n\"\u003esmtplib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSMTP\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSMTP_SERVER\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e587\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003eemail_handler\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eemail_handler\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estarttls\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eemail_handler\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elogin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSMTP_APP_USER\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eSMTP_APP_PASSWORD\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eemail_handler\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esendmail\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSMTP_APP_USER\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;To\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]],\u003c/span\u003e \u003cspan class=\"n\"\u003emsg\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eas_string\u003c/span\u003e\u003cspan class=\"p\"\u003e())\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI'm cooking something up over here that will include SMS message notifications\nas a feature and I've been doing some reading on different providers...\n\nMany specialize in bulk SMS messaging, like for campaigns and promotions. So\nthis pricing model doesn't work for me.\n\nThe providers that do quick single SMS messages are often $0.005 to $.04 cents\nper message which isn't insanity but I'm not exactly backed by anyone...\n\nWell today I learned that you can actually send SMS messages via SMTP! I\nwouldn't bank on total reliability here but for testing and initial roll out of\nwhat I want I think this will be perfect.\n\nAnd because I'm just that kind of guy, here's some code you can `uv run\nsmtp2sms.py` and get yourself a handy little text. I'm going to build a simpler\nmodule but you can get a `gmail` and `gmail app password` within about 10\nminutes. [see here](https://support.google.com/mail/answer/185833?hl=en)\n\n```python\n# /// script\n# requires-python = \"\u003e=3.10\"\n# dependencies = [\n# ]\n# ///\nimport os, smtplib\nfrom email.mime.text import MIMEText\n\nSMTP_APP_USER=os.environ.get(\"SMTP_APP_USER\") # me@gmail.com\nSMTP_APP_PASSWORD=os.environ.get(\"SMTP_APP_PASSWORD\")\nSMTP_SERVER=os.environ.get(\"SMTP_SERVER\")  # smtp.gmail.ch\n\n# NOTE: I haven't verified all the carrier domains - this was provided by ChatGPT\n\ncarriers = {\n    \"att\": {\n        \"sms\": \"txt.att.net\",\n        \"mms\": \"mms.att.net\"\n    },\n    \"tmobile\": {\n        \"sms\": \"tmomail.net\",\n        \"mms\": \"tmomail.net\"\n    },\n    \"verizon\": {\n        # \"sms\": \"vtext.com\", # not reliable\n        \"sms\": \"vzwpix.com\",\n        \"mms\": \"vzwpix.com\"\n    },\n    \"sprint\": {\n        \"sms\": \"messaging.sprintpcs.com\",\n        \"mms\": \"pm.sprint.com\"\n    },\n    \"googlefi\": {\n        \"sms\": \"msg.fi.google.com\",\n        \"mms\": \"msg.fi.google.com\"\n    },\n    \"uscellular\": {\n        \"sms\": \"email.uscc.net\",\n        \"mms\": \"mms.uscc.net\"\n    },\n    \"boost\": {\n        \"sms\": \"sms.myboostmobile.com\",\n        \"mms\": \"myboostmobile.com\"\n    },\n    \"cricket\": {\n        \"sms\": \"sms.cricketwireless.net\",\n        \"mms\": \"mms.cricketwireless.net\"\n    },\n    \"metropcs\": {\n        \"sms\": \"mymetropcs.com\",\n        \"mms\": \"mymetropcs.com\"\n    },\n    \"virgin\": {\n        \"sms\": \"vmobl.com\",\n        \"mms\": \"vmobl.com\"\n    }\n}\ncontacts = {\n    \"me\": {\"carrier\": \"verizon\", \"number\": \"1234567890\"},\n}\nmsg = MIMEText(\"Hello from a python script? What?!\")\nmsg[\"From\"] = SMTP_APP_USER\n\nSEND_TO = \"me\"\n\n# Complex for my future use cases - deal with it\nmsg[\"To\"] = f\"{contacts[SEND_TO]['number']}@{carriers[contacts[SEND_TO]['carrier']]['sms']}\"\n\nwith smtplib.SMTP(SMTP_SERVER, 587) as email_handler:\n    email_handler.starttls()\n    email_handler.login(SMTP_APP_USER, SMTP_APP_PASSWORD)\n    email_handler.sendmail(SMTP_APP_USER, [msg[\"To\"]], msg.as_string())\n\n```\n",
      "summary": "I'm cooking something up over here that will include SMS message notifications as a feature and I've been doing some reading on different providers...",
      "date_published": "2025-10-04T09:38:58Z",
      "date_modified": "2025-10-04T09:38:58Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/docker-compose-build-from-git-repo/",
      "url": "https://pype.dev/docker-compose-build-from-git-repo/",
      "title": "Docker Compose Build From Git Repo",
      "content_html": "\u003cp\u003eYou can reference a git repo in compose\u0026rsquo;s \u0026ldquo;build\u0026rdquo; argument for a service as of \u003ca href=\"https://github.com/docker/compose/pull/2430\"\u003ethis PR\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nYou can reference a git repo in compose's \"build\" argument for a service as of [this PR](https://github.com/docker/compose/pull/2430)\n",
      "summary": "You can reference a git repo in compose's \"build\" argument for a service as of this PR",
      "date_published": "2025-09-12T08:45:20Z",
      "date_modified": "2025-09-12T08:45:20Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "git",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/zsh-alias-for-uv-scripts/",
      "url": "https://pype.dev/zsh-alias-for-uv-scripts/",
      "title": "Zsh alias for uv scripts",
      "content_html": "\u003cp\u003eIt\u0026rsquo;s apparently advantageous for \u003ca href=\"/uv/\" class=\"glossary-term\" title=\"uv by Astral.sh is one of the dopest python toolchain tools to ever exist\" data-hover-title=\"UV\"\u003euv\u003c/a\u003e to have a specific shebang set that \u003ccode\u003euv init\u003c/code\u003e doesn\u0026rsquo;t add, no problem, we can have a zsh alias for it\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ealias\u003c/span\u003e add-shebang\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;f(){ \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e  if [[ ! -s $1 ]]; then \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e    echo \u0026#34;#!/usr/bin/env -S uv run --script\u0026#34; \u0026gt; \u0026#34;$1\u0026#34;; \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e  elif head -n1 \u0026#34;$1\u0026#34; | grep -q \u0026#34;^#!\u0026#34;; then \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e    echo \u0026#34;Shebang already present in $1\u0026#34;; \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e  else \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e    sed -i \u0026#34;1i #!/usr/bin/env -S uv run --script\u0026#34; \u0026#34;$1\u0026#34;; \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e  fi\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e}; f\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThen if you have a python script from \u003ccode\u003euv init --script\u003c/code\u003e you can \u003ccode\u003eadd-shebang myscript.py\u003c/code\u003e to add the shebang.\u003c/p\u003e\n",
      "content_text": "\nIt's apparently advantageous for uv to have a specific shebang set that `uv init` doesn't add, no problem, we can have a zsh alias for it\n\n\n```bash\nalias add-shebang='f(){ \n  if [[ ! -s $1 ]]; then \n    echo \"#!/usr/bin/env -S uv run --script\" \u003e \"$1\"; \n  elif head -n1 \"$1\" | grep -q \"^#!\"; then \n    echo \"Shebang already present in $1\"; \n  else \n    sed -i \"1i #!/usr/bin/env -S uv run --script\" \"$1\"; \n  fi\n}; f'\n```\n\nThen if you have a python script from `uv init --script` you can `add-shebang myscript.py` to add the shebang.\n\n",
      "summary": "It's apparently advantageous for uv to have a specific shebang set that doesn't add, no problem, we can have a zsh alias for it",
      "date_published": "2025-09-08T06:33:13Z",
      "date_modified": "2025-09-08T06:33:13Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/pair-acegamer-wireless-ps4-controller-with-pc/",
      "url": "https://pype.dev/pair-acegamer-wireless-ps4-controller-with-pc/",
      "title": "Pair AceGamer Wireless PS4 Controller With PC",
      "content_html": "\u003cp\u003eI bought a $20 PS4 knock-off controller recently as my PS4 controllers are 12\nyears old (to me!) and I bought them used before that\u0026hellip; the instructions for a\n$20 controller are obviously bad but a godsend redittor discovered the hidden\nsecret to pair this device with my computer! Instructions (for ps4) say to plug\nit in, hit the home button, and you\u0026rsquo;re paired. Now, that didn\u0026rsquo;t work on PC\nobviously, but it ALSO didn\u0026rsquo;t work on my PS4\u0026hellip; Unfortunte! It works wired just\nfine, but turns out the magic to send the thing into traditional Bluetooth\nPairing Mode is to hold the home and share buttons down - wait for a rapid few\nblinks - then you\u0026rsquo;ll see the controller in your bluetooth pairing menu!\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250814115137_34c132cd.png\" alt=\"20250814115137_34c132cd.png\"\u003e\n\u003c/figure\u003e\n",
      "content_text": "\nI bought a $20 PS4 knock-off controller recently as my PS4 controllers are 12\nyears old (to me!) and I bought them used before that... the instructions for a\n$20 controller are obviously bad but a godsend redittor discovered the hidden\nsecret to pair this device with my computer! Instructions (for ps4) say to plug\nit in, hit the home button, and you're paired. Now, that didn't work on PC\nobviously, but it ALSO didn't work on my PS4... Unfortunte! It works wired just\nfine, but turns out the magic to send the thing into traditional Bluetooth\nPairing Mode is to hold the home and share buttons down - wait for a rapid few\nblinks - then you'll see the controller in your bluetooth pairing menu!\n\n![20250814115137_34c132cd.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250814115137_34c132cd.png)\n",
      "summary": "I bought a $20 PS4 knock-off controller recently as my PS4 controllers are 12 years old (to me!) and I bought them used before that... the instructions for a...",
      "date_published": "2025-08-14T06:51:12Z",
      "date_modified": "2025-08-14T06:51:12Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "gaming",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/migrate-github-issues-to-kanboard/",
      "url": "https://pype.dev/migrate-github-issues-to-kanboard/",
      "title": "Migrate Github Issues to Kanboard",
      "content_html": "\u003cp\u003eI am working on shifting everything, or as much as I reasonably can, related to\ndev for myself to on-prem, including git and CI/CD. So for Quadtask I had a\nbunch of Github issues that I wanted to migrate to my kanboard instance, gpt-5\ndid a bang-up job on this script\u003c/p\u003e\n\u003cp\u003eIt gets each issue, creates a tag for each label, creates the kanboard ticket, then closes the github issue\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#!/bin/bash\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# GitHub to Kanboard Issue Migration Script\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Uses GitHub CLI (gh) and curl to migrate issues\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Exit on error\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eset\u003c/span\u003e -e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Check required environment variables\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003efor\u003c/span\u003e var in KANBOARD_URL KANBOARD_TOKEN GH_TOKEN QUADTASK_KANBOARD_PROJECT_ID\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e -z \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"p\"\u003e!var\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Error: \u003c/span\u003e\u003cspan class=\"nv\"\u003e$var\u003c/span\u003e\u003cspan class=\"s2\"\u003e is not set\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eexit\u003c/span\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# GitHub repository (default: pypeaday/quadtask)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eGITHUB_REPO\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eGITHUB_REPO\u003c/span\u003e\u003cspan class=\"k\"\u003e:-\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;pypeaday/quadtask\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Kanboard API endpoint\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eKANBOARD_API\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$KANBOARD_URL\u003c/span\u003e\u003cspan class=\"s2\"\u003e/jsonrpc.php\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Get all open issues from GitHub\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Fetching open issues from \u003c/span\u003e\u003cspan class=\"nv\"\u003e$GITHUB_REPO\u003c/span\u003e\u003cspan class=\"s2\"\u003e...\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eISSUES_JSON\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003egh issue list --repo \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$GITHUB_REPO\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e --state open --limit \u003cspan class=\"m\"\u003e1000\u003c/span\u003e --json number,title,body,labels\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Count issues\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eISSUE_COUNT\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$ISSUES_JSON\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq \u003cspan class=\"s1\"\u003e\u0026#39;. | length\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Found \u003c/span\u003e\u003cspan class=\"nv\"\u003e$ISSUE_COUNT\u003c/span\u003e\u003cspan class=\"s2\"\u003e open issues\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Process each issue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$ISSUES_JSON\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -c \u003cspan class=\"s1\"\u003e\u0026#39;.[]\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e \u003cspan class=\"k\"\u003ewhile\u003c/span\u003e \u003cspan class=\"nb\"\u003eread\u003c/span\u003e -r issue\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Extract issue details\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eNUMBER\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$issue\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.number\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eTITLE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$issue\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.title\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eBODY\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$issue\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.body // \u0026#34;\u0026#34;\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Extract label names as a JSON array\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eLABELS_JSON\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$issue\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -c \u003cspan class=\"s1\"\u003e\u0026#39;[.labels[]?.name] // []\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Prepare Kanboard task data\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eREQUEST_DATA\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003ejq -n \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        --arg method \u003cspan class=\"s2\"\u003e\u0026#34;createTask\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        --argjson id \u003cspan class=\"m\"\u003e1\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        --arg jsonrpc \u003cspan class=\"s2\"\u003e\u0026#34;2.0\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        --arg title \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$TITLE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        --arg description \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$BODY\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        --arg project_id \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$QUADTASK_KANBOARD_PROJECT_ID\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s1\"\u003e\u0026#39;{\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e            \u0026#34;jsonrpc\u0026#34;: $jsonrpc,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e            \u0026#34;method\u0026#34;: $method,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e            \u0026#34;id\u0026#34;: $id,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e            \u0026#34;params\u0026#34;: {\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                \u0026#34;title\u0026#34;: $title,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                \u0026#34;description\u0026#34;: $description,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                \u0026#34;project_id\u0026#34;: $project_id\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e            }\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e        }\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Create task in Kanboard\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Creating task: \u003c/span\u003e\u003cspan class=\"nv\"\u003e$TITLE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nv\"\u003eRESPONSE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003ecurl -s -X POST \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        -H \u003cspan class=\"s2\"\u003e\u0026#34;Content-Type: application/json\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        -u \u003cspan class=\"s2\"\u003e\u0026#34;jsonrpc:\u003c/span\u003e\u003cspan class=\"nv\"\u003e$KANBOARD_TOKEN\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        -d \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$REQUEST_DATA\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$KANBOARD_API\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Check for errors\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$RESPONSE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -e \u003cspan class=\"s1\"\u003e\u0026#39;.error\u0026#39;\u003c/span\u003e \u0026gt; /dev/null\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Error creating task: \u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$RESPONSE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.error.message\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nv\"\u003eTASK_ID\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$RESPONSE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.result\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Created task ID: \u003c/span\u003e\u003cspan class=\"nv\"\u003e$TASK_ID\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e# If there are labels, set them as Kanboard tags on the task\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$LABELS_JSON\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq \u003cspan class=\"s1\"\u003e\u0026#39;length\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e -gt \u003cspan class=\"m\"\u003e0\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Setting tags on task \u003c/span\u003e\u003cspan class=\"nv\"\u003e$TASK_ID\u003c/span\u003e\u003cspan class=\"s2\"\u003e: \u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$LABELS_JSON\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;join(\u0026#34;, \u0026#34;)\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nv\"\u003eTAGS_REQUEST\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003ejq -n \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                --arg jsonrpc \u003cspan class=\"s2\"\u003e\u0026#34;2.0\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                --arg method \u003cspan class=\"s2\"\u003e\u0026#34;setTaskTags\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                --argjson id \u003cspan class=\"m\"\u003e2\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                --arg project_id \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$QUADTASK_KANBOARD_PROJECT_ID\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                --arg task_id \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$TASK_ID\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                --argjson tags \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$LABELS_JSON\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s1\"\u003e\u0026#39;{\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                    jsonrpc: $jsonrpc,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                    method: $method,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                    id: $id,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                    params: {\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                        project_id: $project_id,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                        task_id: $task_id,\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                        tags: $tags\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                    }\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s1\"\u003e                }\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nv\"\u003eTAGS_RESPONSE\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003ecurl -s -X POST \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                -H \u003cspan class=\"s2\"\u003e\u0026#34;Content-Type: application/json\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                -u \u003cspan class=\"s2\"\u003e\u0026#34;jsonrpc:\u003c/span\u003e\u003cspan class=\"nv\"\u003e$KANBOARD_TOKEN\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                -d \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$TAGS_REQUEST\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$KANBOARD_API\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$TAGS_RESPONSE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -e \u003cspan class=\"s1\"\u003e\u0026#39;.error\u0026#39;\u003c/span\u003e \u0026gt; /dev/null\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Warning: Failed to set tags: \u003c/span\u003e\u003cspan class=\"k\"\u003e$(\u003c/span\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$TAGS_RESPONSE\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.error.message\u0026#39;\u003c/span\u003e\u003cspan class=\"k\"\u003e)\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Tags set successfully\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"c1\"\u003e# Close the corresponding GitHub issue to avoid duplicates\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nv\"\u003eTASK_URL\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eKANBOARD_URL\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e/?controller=TaskViewController\u0026amp;action=show\u0026amp;task_id=\u003c/span\u003e\u003cspan class=\"si\"\u003e${\u003c/span\u003e\u003cspan class=\"nv\"\u003eTASK_ID\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Closing GitHub issue #\u003c/span\u003e\u003cspan class=\"nv\"\u003e$NUMBER\u003c/span\u003e\u003cspan class=\"s2\"\u003e with comment linking to Kanboard task...\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eif\u003c/span\u003e gh issue close \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$NUMBER\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e --repo \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$GITHUB_REPO\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e --comment \u003cspan class=\"s2\"\u003e\u0026#34;Migrated to Kanboard task \u003c/span\u003e\u003cspan class=\"nv\"\u003e$TASK_ID\u003c/span\u003e\u003cspan class=\"s2\"\u003e: \u003c/span\u003e\u003cspan class=\"nv\"\u003e$TASK_URL\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e \u0026gt;/dev/null\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ethen\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Closed GitHub issue #\u003c/span\u003e\u003cspan class=\"nv\"\u003e$NUMBER\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eelse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Warning: Failed to close GitHub issue #\u003c/span\u003e\u003cspan class=\"nv\"\u003e$NUMBER\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;----------------------------------------\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Migration completed\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI am working on shifting everything, or as much as I reasonably can, related to\ndev for myself to on-prem, including git and CI/CD. So for Quadtask I had a\nbunch of Github issues that I wanted to migrate to my kanboard instance, gpt-5\ndid a bang-up job on this script\n\nIt gets each issue, creates a tag for each label, creates the kanboard ticket, then closes the github issue\n\n```bash\n\n#!/bin/bash\n\n# GitHub to Kanboard Issue Migration Script\n# Uses GitHub CLI (gh) and curl to migrate issues\n\n# Exit on error\nset -e\n\n# Check required environment variables\nfor var in KANBOARD_URL KANBOARD_TOKEN GH_TOKEN QUADTASK_KANBOARD_PROJECT_ID; do\n    if [ -z \"${!var}\" ]; then\n        echo \"Error: $var is not set\"\n        exit 1\n    fi\ndone\n\n# GitHub repository (default: pypeaday/quadtask)\nGITHUB_REPO=${GITHUB_REPO:-\"pypeaday/quadtask\"}\n\n# Kanboard API endpoint\nKANBOARD_API=\"$KANBOARD_URL/jsonrpc.php\"\n\n# Get all open issues from GitHub\necho \"Fetching open issues from $GITHUB_REPO...\"\nISSUES_JSON=$(gh issue list --repo \"$GITHUB_REPO\" --state open --limit 1000 --json number,title,body,labels)\n\n# Count issues\nISSUE_COUNT=$(echo \"$ISSUES_JSON\" | jq '. | length')\necho \"Found $ISSUE_COUNT open issues\"\n\n# Process each issue\necho \"$ISSUES_JSON\" | jq -c '.[]' | while read -r issue; do\n    # Extract issue details\n    NUMBER=$(echo \"$issue\" | jq -r '.number')\n    TITLE=$(echo \"$issue\" | jq -r '.title')\n    BODY=$(echo \"$issue\" | jq -r '.body // \"\"')\n    # Extract label names as a JSON array\n    LABELS_JSON=$(echo \"$issue\" | jq -c '[.labels[]?.name] // []')\n    \n    # Prepare Kanboard task data\n    REQUEST_DATA=$(jq -n \\\n        --arg method \"createTask\" \\\n        --argjson id 1 \\\n        --arg jsonrpc \"2.0\" \\\n        --arg title \"$TITLE\" \\\n        --arg description \"$BODY\" \\\n        --arg project_id \"$QUADTASK_KANBOARD_PROJECT_ID\" \\\n        '{\n            \"jsonrpc\": $jsonrpc,\n            \"method\": $method,\n            \"id\": $id,\n            \"params\": {\n                \"title\": $title,\n                \"description\": $description,\n                \"project_id\": $project_id\n            }\n        }')\n    \n    # Create task in Kanboard\n    echo \"Creating task: $TITLE\"\n    RESPONSE=$(curl -s -X POST \\\n        -H \"Content-Type: application/json\" \\\n        -u \"jsonrpc:$KANBOARD_TOKEN\" \\\n        -d \"$REQUEST_DATA\" \\\n        \"$KANBOARD_API\")\n    \n    # Check for errors\n    if echo \"$RESPONSE\" | jq -e '.error' \u003e /dev/null; then\n        echo \"Error creating task: $(echo \"$RESPONSE\" | jq -r '.error.message')\"\n    else\n        TASK_ID=$(echo \"$RESPONSE\" | jq -r '.result')\n        echo \"Created task ID: $TASK_ID\"\n\n        # If there are labels, set them as Kanboard tags on the task\n        if [ \"$(echo \"$LABELS_JSON\" | jq 'length')\" -gt 0 ]; then\n            echo \"Setting tags on task $TASK_ID: $(echo \"$LABELS_JSON\" | jq -r 'join(\", \")')\"\n            TAGS_REQUEST=$(jq -n \\\n                --arg jsonrpc \"2.0\" \\\n                --arg method \"setTaskTags\" \\\n                --argjson id 2 \\\n                --arg project_id \"$QUADTASK_KANBOARD_PROJECT_ID\" \\\n                --arg task_id \"$TASK_ID\" \\\n                --argjson tags \"$LABELS_JSON\" \\\n                '{\n                    jsonrpc: $jsonrpc,\n                    method: $method,\n                    id: $id,\n                    params: {\n                        project_id: $project_id,\n                        task_id: $task_id,\n                        tags: $tags\n                    }\n                }')\n\n            TAGS_RESPONSE=$(curl -s -X POST \\\n                -H \"Content-Type: application/json\" \\\n                -u \"jsonrpc:$KANBOARD_TOKEN\" \\\n                -d \"$TAGS_REQUEST\" \\\n                \"$KANBOARD_API\")\n\n            if echo \"$TAGS_RESPONSE\" | jq -e '.error' \u003e /dev/null; then\n                echo \"Warning: Failed to set tags: $(echo \"$TAGS_RESPONSE\" | jq -r '.error.message')\"\n            else\n                echo \"Tags set successfully\"\n            fi\n        fi\n\n        # Close the corresponding GitHub issue to avoid duplicates\n        TASK_URL=\"${KANBOARD_URL}/?controller=TaskViewController\u0026action=show\u0026task_id=${TASK_ID}\"\n        echo \"Closing GitHub issue #$NUMBER with comment linking to Kanboard task...\"\n        if gh issue close \"$NUMBER\" --repo \"$GITHUB_REPO\" --comment \"Migrated to Kanboard task $TASK_ID: $TASK_URL\" \u003e/dev/null; then\n            echo \"Closed GitHub issue #$NUMBER\"\n        else\n            echo \"Warning: Failed to close GitHub issue #$NUMBER\"\n        fi\n    fi\n    \n    echo \"----------------------------------------\"\ndone\n\necho \"Migration completed\"\n```\n",
      "summary": "I am working on shifting everything, or as much as I reasonably can, related to dev for myself to on-prem, including git and CI/CD. So for Quadtask I had a...",
      "date_published": "2025-08-07T14:43:27Z",
      "date_modified": "2025-08-07T14:43:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "kanboard",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/add-yourself-to-www-data-to-view-your-nextcloud-data-on-the-filesystem/",
      "url": "https://pype.dev/add-yourself-to-www-data-to-view-your-nextcloud-data-on-the-filesystem/",
      "title": "Add yourself to www-data to view your Nextcloud data on the filesystem",
      "content_html": "\u003cp\u003eI am working on using \u003ca href=\"/gotify-cli-for-notifying-me-of-nextcloud-uploads/\" class=\"wikilink\" data-title=\"gotify cli for notifying me of nextcloud uploads\" data-description=\"I was looking for a way to get notified from nextcloud when files were uploaded to a certain directory. This is because the upload is very spotty due to the...\" data-date=\"2025-07-16\" data-preview=\"I was looking for a way to get notified from nextcloud when files were uploaded to a certain directory. This is because the upload is very spotty due to the...\"\u003egotify cli for notifying me of nextcloud uploads\u003c/a\u003e and\nI\u0026rsquo;ll start by running the gotify cli as my user so I\u0026rsquo;ll need to be able to see\nthe directory in the filesystem that Nextcloud stores the data in.\u003c/p\u003e\n\u003cp\u003eThat folder on disk is owned by \u003ccode\u003ewww-data\u003c/code\u003e (see \u003ca href=\"/nextcloud-permissions-with-zfs-and-ansible-nas/\" class=\"wikilink\" data-title=\"Nextcloud permissions with ZFS and Ansible-NAS\" data-description=\"As the nextcloud docs say... if you want to write to an external volume that location has to be writeable by the user/group on the host system... so if that...\" data-date=\"2022-05-19\" data-preview=\"As the nextcloud docs say... if you want to write to an external volume that location has to be writeable by the user/group on the host system... so if that...\"\u003eNextcloud permissions with ZFS and Ansible-NAS\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eTurns out it\u0026rsquo;s my computer and I can do what I want so I just add myself to the\n\u003ccode\u003ewww-data\u003c/code\u003e group instead of running the \u003ccode\u003egotify cli\u003c/code\u003e as \u003ccode\u003eroot\u003c/code\u003e\u003c/p\u003e\n\u003ch1 id=\"bonus\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBonus\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eYou can remove users from groups with \u003ccode\u003egpasswd -d username groupname\u003c/code\u003e\u003c/p\u003e\n",
      "content_text": "\nI am working on using \u003ca href=\"/gotify-cli-for-notifying-me-of-nextcloud-uploads/\" class=\"wikilink\" data-title=\"gotify cli for notifying me of nextcloud uploads\" data-description=\"I was looking for a way to get notified from nextcloud when files were uploaded to a certain directory. This is because the upload is very spotty due to the...\" data-date=\"2025-07-16\"\u003egotify cli for notifying me of nextcloud uploads\u003c/a\u003e and\nI'll start by running the gotify cli as my user so I'll need to be able to see\nthe directory in the filesystem that Nextcloud stores the data in.\n\nThat folder on disk is owned by `www-data` (see \u003ca href=\"/nextcloud-permissions-with-zfs-and-ansible-nas/\" class=\"wikilink\" data-title=\"Nextcloud permissions with ZFS and Ansible-NAS\" data-description=\"As the nextcloud docs say... if you want to write to an external volume that location has to be writeable by the user/group on the host system... so if that...\" data-date=\"2022-05-19\"\u003eNextcloud permissions with ZFS and Ansible-NAS\u003c/a\u003e\n\nTurns out it's my computer and I can do what I want so I just add myself to the\n`www-data` group instead of running the `gotify cli` as `root`\n\n# Bonus\n\nYou can remove users from groups with `gpasswd -d username groupname`\n",
      "summary": "I am working on using gotify-cli-for-notifying-me-of-nextcloud-uploads and I'll start by running the gotify cli as my user so I'll need to be able to see the...",
      "date_published": "2025-07-17T07:38:09Z",
      "date_modified": "2025-07-17T07:38:09Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "nextcloud",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/lua-type-hinting-undefined-global/",
      "url": "https://pype.dev/lua-type-hinting-undefined-global/",
      "title": "Lua type hinting - undefined global",
      "content_html": "\u003cp\u003eI have tons of linting errors in my nvim config, partly because I use Lazyvim\nplus a set of poorly written customizations that have traveled with me for a\nwhile. I try to re-work things and right now I\u0026rsquo;ve been revisiting my developer\nsetup. Well the LSP rrors as I\u0026rsquo;ve gone through my neovim config are ticking me\noff - and this was the biggest one \u0026ldquo;Undefined global: vim\u0026rdquo;. I saw \u003ccode\u003evim.fn\u003c/code\u003e\neverywhere but the LSP hated it\u0026hellip; turns out \u003ccode\u003evim\u003c/code\u003e is a given global and you\ncan help your lsp with this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e---@diagnostic disable: undefined-global\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- vim is a global in Neovim Lua scripts\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eFor example:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e---@diagnostic disable: undefined-global\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e-- vim is a global in Neovim Lua scripts\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kr\"\u003ereturn\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"s2\"\u003e\u0026#34;pypeaday\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003edir\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003evim.fn\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estdpath\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;config\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e..\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/lua/pypeaday\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003econfig\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003edaily\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequire\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;pypeaday.daily\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evim.api\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003envim_create_user_command\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;DailyNote\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edaily.check_and_open_daily_note\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evim.api\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003envim_create_user_command\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Daily\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edaily.check_and_open_daily_note\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evim.api\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003envim_create_user_command\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;BackLinks\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edaily.find_backlinks\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evim.keymap\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;leader\u0026gt;dn\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edaily.check_and_open_daily_note\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003edesc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Open daily note\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evim.keymap\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;leader\u0026gt;df\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edaily.find_daily_files\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003edesc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Find daily files\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003evim.keymap\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eset\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;n\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;leader\u0026gt;dl\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edaily.find_backlinks\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003edesc\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Find backlinks\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e})\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"kr\"\u003eend\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI have tons of linting errors in my nvim config, partly because I use Lazyvim\nplus a set of poorly written customizations that have traveled with me for a\nwhile. I try to re-work things and right now I've been revisiting my developer\nsetup. Well the LSP rrors as I've gone through my neovim config are ticking me\noff - and this was the biggest one \"Undefined global: vim\". I saw `vim.fn`\neverywhere but the LSP hated it... turns out `vim` is a given global and you\ncan help your lsp with this:\n\n```lua\n---@diagnostic disable: undefined-global\n-- vim is a global in Neovim Lua scripts\n```\n\nFor example:\n\n```lua\n\n---@diagnostic disable: undefined-global\n-- vim is a global in Neovim Lua scripts\nreturn {\n  \"pypeaday\",\n  dir = vim.fn.stdpath(\"config\") .. \"/lua/pypeaday\",\n  config = function()\n    local daily = require(\"pypeaday.daily\")\n    vim.api.nvim_create_user_command(\"DailyNote\", daily.check_and_open_daily_note, {})\n    vim.api.nvim_create_user_command(\"Daily\", daily.check_and_open_daily_note, {})\n    vim.api.nvim_create_user_command(\"BackLinks\", daily.find_backlinks, {})\n    vim.keymap.set(\"n\", \"\u003cleader\u003edn\", daily.check_and_open_daily_note, { desc = \"Open daily note\" })\n    vim.keymap.set(\"n\", \"\u003cleader\u003edf\", daily.find_daily_files, { desc = \"Find daily files\" })\n    vim.keymap.set(\"n\", \"\u003cleader\u003edl\", daily.find_backlinks, { desc = \"Find backlinks\" })\n  end,\n}\n```\n",
      "summary": "I have tons of linting errors in my nvim config, partly because I use Lazyvim plus a set of poorly written customizations that have traveled with me for a...",
      "date_published": "2025-07-11T06:08:39Z",
      "date_modified": "2025-07-11T06:08:39Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "lua",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/nostr-hex-key/",
      "url": "https://pype.dev/nostr-hex-key/",
      "title": "Nostr hex key",
      "content_html": "\u003cp\u003eyou can hexify your \u003ca href=\"/nostr/\" class=\"wikilink\" data-title=\"Nostr\" data-description=\"???+ note \u0026#34;How to start...\u0026#34;\" data-date=\"2025-07-02\" data-preview=\"???+ note \u0026#34;How to start...\u0026#34;\"\u003eNostr\u003c/a\u003e key when you need to sometimes which I needed to for \u003ca href=\"/tags/postiz/\" class=\"wikilink\" data-title=\"Posts tagged: postiz\" data-description=\"All posts with the tag \u0026#34;postiz\u0026#34;\" data-preview=\"All posts with the tag \u0026#34;postiz\u0026#34;\"\u003ePosts tagged: postiz\u003c/a\u003e (link with a thought I\u0026rsquo;m sure I have on postiz)\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# /// script\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# requires-python = \u0026#34;\u0026gt;=3.8\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# dependencies = [\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#     \u0026#34;bech32\u0026#34;,\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# ]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# ///\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eos\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003ebech32\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Set your nsec... key to NOSTR_PRIVATE_KEY or bake it in...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ensec_key\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eos\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eenviron\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;NOSTR_PRIVATE_KEY\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003edecode_nsec\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ensec_key\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ehrp\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebech32\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebech32_decode\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ensec_key\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ehrp\u003c/span\u003e \u003cspan class=\"o\"\u003e!=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;nsec\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"k\"\u003eraise\u003c/span\u003e \u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Not a valid nsec key\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edecoded\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ebech32\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econvertbits\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e5\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"nb\"\u003ebytes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edecoded\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehex\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Example usage\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eraw_hex\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edecode_nsec\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ensec_key\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Raw hex private key: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003eraw_hex\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nyou can hexify your \u003ca href=\"/nostr/\" class=\"wikilink\" data-title=\"Nostr\" data-description=\"???+ note \u0026#34;How to start...\u0026#34;\" data-date=\"2025-07-02\"\u003eNostr\u003c/a\u003e key when you need to sometimes which I needed to for \u003ca href=\"/tags/postiz/\" class=\"wikilink\" data-title=\"Posts tagged: postiz\" data-description=\"All posts with the tag \u0026#34;postiz\u0026#34;\"\u003ePosts tagged: postiz\u003c/a\u003e (link with a thought I'm sure I have on postiz)\n\n```python\n\n# /// script\n# requires-python = \"\u003e=3.8\"\n# dependencies = [\n#     \"bech32\",\n# ]\n# ///\n#\nimport os\n\nimport bech32\n\n# Set your nsec... key to NOSTR_PRIVATE_KEY or bake it in...\nnsec_key = os.environ.get(\"NOSTR_PRIVATE_KEY\")\n\n\ndef decode_nsec(nsec_key):\n    hrp, data = bech32.bech32_decode(nsec_key)\n    if hrp != \"nsec\":\n        raise ValueError(\"Not a valid nsec key\")\n    decoded = bech32.convertbits(data, 5, 8, False)\n    return bytes(decoded).hex()\n\n\n# Example usage\nraw_hex = decode_nsec(nsec_key)\nprint(f\"Raw hex private key: {raw_hex}\")\n```\n",
      "summary": "you can hexify your nostr key when you need to sometimes which I needed to for postiz (link with a thought I'm sure I have on postiz)",
      "date_published": "2025-07-02T08:43:32Z",
      "date_modified": "2025-07-02T08:43:32Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "nostr",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-688/",
      "url": "https://pype.dev/thoughts-688/",
      "title": "💭 slash pages",
      "content_html": "\u003ca href=\"https://slashpages.net/\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://slashpages.net/\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - slash pages\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\u003cp\u003eHere\u0026rsquo;s my thought on \u003ca href=\"https://slashpages.net/\"\u003eslash pages\u003c/a\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWaylon\u0026rsquo;s mentioned slash pages before, but as I work on building my blog again and writing more regularly thanks to \u0026ldquo;thoughts\u0026rdquo; etc. I\u0026rsquo;d like to start putting together some meaningful slash pages for myself\u0026hellip; I\u0026rsquo;m not sure what to put out there yet but this thought can serve as a todo for me\u003c/p\u003e\n\u003chr\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eNote\u003c/p\u003e\n\u003cp\u003eThis is one of [[ my-thoughts ]]. I picked this up from \u003ca href=\"https://waylonwalker.com\"\u003eWaylon Walker\u003c/a\u003e(\u003ca href=\"https://thoughts.waylonwalker.com\"\u003ehttps://thoughts.waylonwalker.com\u003c/a\u003e). It\u0026rsquo;s a short note that I make about someone else\u0026rsquo;s content online.  Learn more about the process \u003ca href=\"/thoughts/\" class=\"wikilink\" data-title=\"Thoughts\" data-description=\"My thoughts are found at my thoughts feed\" data-date=\"2025-06-09\" data-preview=\"My thoughts are found at my thoughts feed\"\u003eThoughts\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n",
      "content_text": "\n\u003ca href=\"https://slashpages.net/\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://slashpages.net/\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - slash pages\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://slashpages.net/\"\u003eslash pages\u003c/a\u003e\n\n---\n\nWaylon's mentioned slash pages before, but as I work on building my blog again and writing more regularly thanks to \"thoughts\" etc. I'd like to start putting together some meaningful slash pages for myself... I'm not sure what to put out there yet but this thought can serve as a todo for me\n\n---\n\n!!! note\n    This is one of [[ my-thoughts ]]. I picked this up from [Waylon Walker](https://waylonwalker.com)(https://thoughts.waylonwalker.com). It's a short note that I make about someone else's content online.  Learn more about the process \u003ca href=\"/thoughts/\" class=\"wikilink\" data-title=\"Thoughts\" data-description=\"My thoughts are found at my thoughts feed\" data-date=\"2025-06-09\"\u003eThoughts\u003c/a\u003e\n",
      "summary": "Waylon's mentioned slash pages before, but as I work on building my blog again and writing more regularly thanks to \"tho",
      "date_published": "2025-06-12T11:41:43Z",
      "date_modified": "2025-06-12T11:41:43Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "blog",
        "slash-pages",
        "til",
        "todo",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/double-check-your-cidr-blocks/",
      "url": "https://pype.dev/double-check-your-cidr-blocks/",
      "title": "Double check your CIDR blocks!",
      "content_html": "\u003ch1 id=\"tl-dr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003etl;dr\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eCopy paste - don\u0026rsquo;t rely on typing correctly\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"the-issue\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Issue\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI use tailscale at home and generally love it. One thing I use it for is\nwhitelisting - so I have some apps publically available and my homelab ones\nhave DNS with SSL certs by LE but I whitelist to only my private IPs so that I\ncan use https at home basically. However I have had a few issues\nwith a couple apps, and I \u003ca href=\"/kind/\" class=\"glossary-term\" title=\"kind - kubernetes in docker\" data-hover-title=\"kind\"\u003ekind\u003c/a\u003e of chaulked it up to \u0026ldquo;network protocol this\u0026rdquo;\n\u0026ldquo;need to figure out that header that\u0026rdquo; but ultimately I was being lazy and the\nissues weren\u0026rsquo;t catastrophic\u0026hellip; it was things like:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ecouldn\u0026rsquo;t get to my NVR by DNS, so I\u0026rsquo;d have to use tailscale_ip:port\u003c/li\u003e\n\u003cli\u003ecouldn\u0026rsquo;t login with my user in jellyfin cause I lock me down to LAN only\nsince I dont\u0026rsquo; use a password\u003c/li\u003e\n\u003cli\u003egotify wouldn\u0026rsquo;t connect using DNS\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThese things aren\u0026rsquo;t huge but they are annoying and today I decided to figure it out.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"typo\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTypo!\u003c/span\u003e \u003ca href=\"#typo\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;m not an expert yet but I think the main problem was the CIDR block I was\nusing in my traefik config.\u003c/p\u003e\n\u003cp\u003eThis\u0026rsquo;ll shock some of you but \u003ccode\u003e10.64.0.0/10\u003c/code\u003e and \u003ccode\u003e100.64.0.0/10\u003c/code\u003e are not the\nsame blocks!\u003c/p\u003e\n\u003cdiv class=\"admonition warning\"\u003e\n\u003cp class=\"admonition-title\"\u003eCheck your CIDRs\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s always DNS, and even when it\u0026rsquo;s not, it\u0026rsquo;s still probably networking\u003c/p\u003e\n\u003c/div\u003e\n",
      "content_text": "\n# tl;dr\n\nCopy paste - don't rely on typing correctly...\n\n# The Issue\n\nI use tailscale at home and generally love it. One thing I use it for is\nwhitelisting - so I have some apps publically available and my homelab ones\nhave DNS with SSL certs by LE but I whitelist to only my private IPs so that I\ncan use https at home basically. However I have had a few issues\nwith a couple apps, and I kind of chaulked it up to \"network protocol this\"\n\"need to figure out that header that\" but ultimately I was being lazy and the\nissues weren't catastrophic... it was things like:\n\n1. couldn't get to my NVR by DNS, so I'd have to use tailscale_ip:port\n2. couldn't login with my user in jellyfin cause I lock me down to LAN only\n   since I dont' use a password\n3. gotify wouldn't connect using DNS\n\n\u003e These things aren't huge but they are annoying and today I decided to figure it out.\n\n## Typo!\n\nI'm not an expert yet but I think the main problem was the CIDR block I was\nusing in my traefik config.\n\nThis'll shock some of you but `10.64.0.0/10` and `100.64.0.0/10` are not the\nsame blocks!\n\n!!! warning \"Check your CIDRs\"\n    It's always DNS, and even when it's not, it's still probably networking\n",
      "summary": "Copy paste - don't rely on typing correctly...",
      "date_published": "2025-06-07T07:27:31Z",
      "date_modified": "2025-06-07T07:27:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "tailscale",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/backups-are-dope/",
      "url": "https://pype.dev/backups-are-dope/",
      "title": "Backups are dope",
      "content_html": "\u003cp\u003eI accidently chown\u0026rsquo;d -R an app directory and it totally screwed up the database\nfolder. Luckily I zfs replicate my \u003ca href=\"/docker/\" class=\"glossary-term\" title=\"docker - container runtime\" data-hover-title=\"docker\"\u003edocker\u003c/a\u003e volumes to another drive even on the\nsame host, so a quick [rsync] from /harbor/encrypted/docker/manyfold to\n/tank/encrypted/docker/manyfold got me back up and running since I hadn\u0026rsquo;t\nreplicated the messed up permission set yet\u003c/p\u003e\n\u003cp\u003eQ: How to link to my rsync like a pro post?\u003c/p\u003e\n\u003cp\u003eA: Wikilinks \u003ccode\u003e\u0026lt;a href=\u0026quot;/rsync-like-a-pro/\u0026quot; class=\u0026quot;wikilink\u0026quot; data-title=\u0026quot;rsync like a pro\u0026quot; data-description=\u0026quot;I am moving a hefty amount of data to a new ZFS pool due to some corruption and I want to avoid using for this just to make sure I don\u0026amp;#39;t propagate any...\u0026quot; data-date=\u0026quot;2024-12-11\u0026quot;\u0026gt;rsync like a pro\u0026lt;/a\u0026gt;\u003c/code\u003e \u003ca href=\"/rsync-like-a-pro/\" class=\"wikilink\" data-title=\"rsync like a pro\" data-description=\"I am moving a hefty amount of data to a new ZFS pool due to some corruption and I want to avoid using for this just to make sure I don\u0026#39;t propagate any...\" data-date=\"2024-12-11\" data-preview=\"I am moving a hefty amount of data to a new ZFS pool due to some corruption and I want to avoid using for this just to make sure I don\u0026#39;t propagate any...\"\u003ersync like a pro\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI accidently chown'd -R an app directory and it totally screwed up the database\nfolder. Luckily I zfs replicate my docker volumes to another drive even on the\nsame host, so a quick [rsync] from /harbor/encrypted/docker/manyfold to\n/tank/encrypted/docker/manyfold got me back up and running since I hadn't\nreplicated the messed up permission set yet\n\nQ: How to link to my rsync like a pro post?\n\nA: Wikilinks `\u003ca href=\"/rsync-like-a-pro/\" class=\"wikilink\" data-title=\"rsync like a pro\" data-description=\"I am moving a hefty amount of data to a new ZFS pool due to some corruption and I want to avoid using for this just to make sure I don\u0026#39;t propagate any...\" data-date=\"2024-12-11\"\u003ersync like a pro\u003c/a\u003e` \u003ca href=\"/rsync-like-a-pro/\" class=\"wikilink\" data-title=\"rsync like a pro\" data-description=\"I am moving a hefty amount of data to a new ZFS pool due to some corruption and I want to avoid using for this just to make sure I don\u0026#39;t propagate any...\" data-date=\"2024-12-11\"\u003ersync like a pro\u003c/a\u003e\n",
      "summary": "I accidently chown'd -R an app directory and it totally screwed up the database folder. Luckily I zfs replicate my docker volumes to another drive even on...",
      "date_published": "2025-05-27T19:42:27Z",
      "date_modified": "2025-05-27T19:42:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech",
        "homelab",
        "zfs",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/aurora-u-blue-zram-config/",
      "url": "https://pype.dev/aurora-u-blue-zram-config/",
      "title": "Aurora U-Blue ZRAM Config",
      "content_html": "\u003cp\u003eI keep running out of space with my swap getting maxxed out\u0026hellip; I don\u0026rsquo;t know why but U-Blue uses Zram already and apparently I can easily override the defaults:\u003c/p\u003e\n\u003cp\u003ein \u003ccode\u003eusr/lib/systemd/zram-generator.conf\u003c/code\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode class=\"language-conf\"\u003e\n# This config file enables a /dev/zram0 device with the default settings:\n# — size — same as available RAM or 8GB, whichever is less\n# — compression — most likely lzo-rle\n#\n# To disable, uninstall zram-generator-defaults or create empty\n# /etc/systemd/zram-generator.conf file.\n[zram0]\nzram-size = min(ram, 8192)\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAnd so I just made my own file in \u003ccode\u003e/etc/systemd/zram-generator.conf\u003c/code\u003e:\u003c/p\u003e\n\u003cpre\u003e\u003ccode class=\"language-conf\"\u003e\n[zram0]\nzram-size = min(ram, 16384)\n\n\u003c/code\u003e\u003c/pre\u003e\n",
      "content_text": "\nI keep running out of space with my swap getting maxxed out... I don't know why but U-Blue uses Zram already and apparently I can easily override the defaults:\n\nin `usr/lib/systemd/zram-generator.conf`\n\n```conf\n\n# This config file enables a /dev/zram0 device with the default settings:\n# — size — same as available RAM or 8GB, whichever is less\n# — compression — most likely lzo-rle\n#\n# To disable, uninstall zram-generator-defaults or create empty\n# /etc/systemd/zram-generator.conf file.\n[zram0]\nzram-size = min(ram, 8192)\n\n```\n\nAnd so I just made my own file in `/etc/systemd/zram-generator.conf`:\n\n```conf\n\n[zram0]\nzram-size = min(ram, 16384)\n\n```\n",
      "summary": "I keep running out of space with my swap getting maxxed out... I don't know why but U-Blue uses Zram already and apparently I can easily override the defaults:",
      "date_published": "2025-05-27T13:51:27Z",
      "date_modified": "2025-05-27T13:51:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "infrastructure",
        "infrastructure",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/pandas-dataframe-info-always-answers-and-is-sometimes-right/",
      "url": "https://pype.dev/pandas-dataframe-info-always-answers-and-is-sometimes-right/",
      "title": "Pandas.DataFrame info always answers and is sometimes right",
      "content_html": "\u003ch1 id=\"til\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTIL\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eToday I learned that the \u003ccode\u003e.info()\u003c/code\u003e of a \u003ccode\u003epandas.DataFrame\u003c/code\u003e will always\ngive you an answer, but it is wildly difficult to know how accurate it is, because it depends\non the underlying data types.\nIf everything is a NumPy Dtype, then the result you get is the amount of memory NumPy is using\nunder the hood, which is very accurate.\nBut as soon as you have something like a Python string in the data frame, now pandas will\ntell you how much memory the NumPy data types are using, and it will give you some quick\nestimate of how much memory space the Python string types are using because numpy is just storing\na pointer to that data in memory, but this is far less\naccurate because it doesn\u0026rsquo;t actually know what the strings are.\u003c/p\u003e\n\u003ch2 id=\"memory-usage-deep-or-true\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMemory Usage Deep or True\u003c/span\u003e \u003ca href=\"#memory-usage-deep-or-true\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTo get an honest answer about a DataFrame\u0026rsquo;s memory usage, you need to pass \u003ccode\u003ememory_usage='deep'\u003c/code\u003e to the \u003ccode\u003e.info()\u003c/code\u003e method.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# This is the default\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eIn \u003cspan class=\"o\"\u003e[\u003c/span\u003e50\u003cspan class=\"o\"\u003e]\u003c/span\u003e: df.info\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003ememory_usage\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eTrue\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;class \u003cspan class=\"s1\"\u003e\u0026#39;pandas.core.frame.DataFrame\u0026#39;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRangeIndex: \u003cspan class=\"m\"\u003e10\u003c/span\u003e entries, \u003cspan class=\"m\"\u003e0\u003c/span\u003e to \u003cspan class=\"m\"\u003e9\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eData columns \u003cspan class=\"o\"\u003e(\u003c/span\u003etotal \u003cspan class=\"m\"\u003e3\u003c/span\u003e columns\u003cspan class=\"o\"\u003e)\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"c1\"\u003e#   Column  Non-Null Count  Dtype \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e---  ------  --------------  ----- \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e   s1      \u003cspan class=\"m\"\u003e10\u003c/span\u003e non-null     int64 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e   s2      \u003cspan class=\"m\"\u003e10\u003c/span\u003e non-null     int64 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e2\u003c/span\u003e   s3      \u003cspan class=\"m\"\u003e10\u003c/span\u003e non-null     object\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edtypes: int64\u003cspan class=\"o\"\u003e(\u003c/span\u003e2\u003cspan class=\"o\"\u003e)\u003c/span\u003e, object\u003cspan class=\"o\"\u003e(\u003c/span\u003e1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ememory usage: 372.0+ bytes\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNotice that the memory usage has a \u003ccode\u003e+\u003c/code\u003e in it? You can force a deeper analysis by passing \u003ccode\u003ememory_usage='deep'\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eIn \u003cspan class=\"o\"\u003e[\u003c/span\u003e51\u003cspan class=\"o\"\u003e]\u003c/span\u003e: df.info\u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"nv\"\u003ememory_usage\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;deep\u0026#39;\u003c/span\u003e\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;class \u003cspan class=\"s1\"\u003e\u0026#39;pandas.core.frame.DataFrame\u0026#39;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRangeIndex: \u003cspan class=\"m\"\u003e10\u003c/span\u003e entries, \u003cspan class=\"m\"\u003e0\u003c/span\u003e to \u003cspan class=\"m\"\u003e9\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eData columns \u003cspan class=\"o\"\u003e(\u003c/span\u003etotal \u003cspan class=\"m\"\u003e3\u003c/span\u003e columns\u003cspan class=\"o\"\u003e)\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"c1\"\u003e#   Column  Non-Null Count  Dtype \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e---  ------  --------------  ----- \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e0\u003c/span\u003e   s1      \u003cspan class=\"m\"\u003e10\u003c/span\u003e non-null     int64 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e   s2      \u003cspan class=\"m\"\u003e10\u003c/span\u003e non-null     int64 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e2\u003c/span\u003e   s3      \u003cspan class=\"m\"\u003e10\u003c/span\u003e non-null     object\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edtypes: int64\u003cspan class=\"o\"\u003e(\u003c/span\u003e2\u003cspan class=\"o\"\u003e)\u003c/span\u003e, object\u003cspan class=\"o\"\u003e(\u003c/span\u003e1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ememory usage: 792.0 bytes\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\n# TIL\n\nToday I learned that the `.info()` of a `pandas.DataFrame` will always\ngive you an answer, but it is wildly difficult to know how accurate it is, because it depends\non the underlying data types.\nIf everything is a NumPy Dtype, then the result you get is the amount of memory NumPy is using\nunder the hood, which is very accurate.\nBut as soon as you have something like a Python string in the data frame, now pandas will\ntell you how much memory the NumPy data types are using, and it will give you some quick\nestimate of how much memory space the Python string types are using because numpy is just storing \na pointer to that data in memory, but this is far less\naccurate because it doesn't actually know what the strings are.\n\n## Memory Usage Deep or True\n\nTo get an honest answer about a DataFrame's memory usage, you need to pass `memory_usage='deep'` to the `.info()` method. \n\n```bash\n# This is the default\nIn [50]: df.info(memory_usage=True)\n\u003cclass 'pandas.core.frame.DataFrame'\u003e\nRangeIndex: 10 entries, 0 to 9\nData columns (total 3 columns):\n #   Column  Non-Null Count  Dtype \n---  ------  --------------  ----- \n 0   s1      10 non-null     int64 \n 1   s2      10 non-null     int64 \n 2   s3      10 non-null     object\ndtypes: int64(2), object(1)\nmemory usage: 372.0+ bytes\n```\n\nNotice that the memory usage has a `+` in it? You can force a deeper analysis by passing `memory_usage='deep'`\n\n```bash\nIn [51]: df.info(memory_usage='deep')\n\u003cclass 'pandas.core.frame.DataFrame'\u003e\nRangeIndex: 10 entries, 0 to 9\nData columns (total 3 columns):\n #   Column  Non-Null Count  Dtype \n---  ------  --------------  ----- \n 0   s1      10 non-null     int64 \n 1   s2      10 non-null     int64 \n 2   s3      10 non-null     object\ndtypes: int64(2), object(1)\nmemory usage: 792.0 bytes\n```\n\n\n",
      "summary": "Today I learned that the of a will always give you an answer, but it is wildly difficult to know how accurate it is, because it depends on the underlying...",
      "date_published": "2025-05-26T09:08:08Z",
      "date_modified": "2025-05-26T09:08:08Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/searxng/",
      "url": "https://pype.dev/searxng/",
      "title": "SearXNG",
      "content_html": "\u003cp\u003eI heard about \u003ca href=\"https://searxng.org\"\u003eSearXNG\u003c/a\u003e on a couple podcasts and saw it trending on GitHub\nseveral times before I finally decided to stand it up.\nI used it transparently when trying out \u003ca href=\"https://khoj.dev/\"\u003ekhoj\u003c/a\u003e, a self-hosted AI LLM agent\nplayground \u003ca href=\"/kind/\" class=\"glossary-term\" title=\"kind - kubernetes in docker\" data-hover-title=\"kind\"\u003ekind\u003c/a\u003e of a thing, but I\u0026rsquo;ve also been messing around with \u003ca href=\"https://openwebui.com\"\u003eOpen-WebUI\u003c/a\u003e to\nhave a self-hosted ChatGPT-like experience.\nI don\u0026rsquo;t know if SearchXNG used to be harder to set up, but it was pretty simple with a \u003ca href=\"/docker/\" class=\"glossary-term\" title=\"docker - container runtime\" data-hover-title=\"docker\"\u003eDocker\u003c/a\u003e Compose up\nand a couple configuration options given my personal homelab setup. Using it feels\nvery nice.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003eservices\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nt\"\u003eredis\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003econtainer_name\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eredis\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003edocker.io/valkey/valkey:8-alpine\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003ecommand\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003evalkey-server --save 30 1 --loglevel warning\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003erestart\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eunless-stopped\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003evolumes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"l\"\u003e./data/redis:/data\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003elogging\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"nt\"\u003edriver\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;json-file\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"nt\"\u003eoptions\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nt\"\u003emax-size\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1m\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nt\"\u003emax-file\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"nt\"\u003esearxng\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003econtainer_name\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003esearxng\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003edocker.io/searxng/searxng:latest\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003erestart\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eunless-stopped\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eports\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"s2\"\u003e\u0026#34;8080:8080\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003evolumes\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"l\"\u003e./data/searxng:/etc/searxng:rw\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eenv_file\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003e.env\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003eenvironment\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"c\"\u003e# - SEARXNG_BASE_URL=https://${SEARXNG_HOSTNAME:-localhost}/\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"l\"\u003eUWSGI_WORKERS=${SEARXNG_UWSGI_WORKERS:-4}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e- \u003cspan class=\"l\"\u003eUWSGI_THREADS=${SEARXNG_UWSGI_THREADS:-4}\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e    \u003c/span\u003e\u003cspan class=\"nt\"\u003elogging\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"nt\"\u003edriver\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;json-file\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e      \u003c/span\u003e\u003cspan class=\"nt\"\u003eoptions\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nt\"\u003emax-size\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1m\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e        \u003c/span\u003e\u003cspan class=\"nt\"\u003emax-file\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eI love the self-hosted aspect. I love not seeing any ads on my search\nresults. I did a few searches where I know what results to expect and\nit did okay.\nIt is good right now at filtering out garbage in my results.\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://dropper.wayl.one/api/file/4ff5953c-bc8d-4978-8509-b43582460a0b.png\" alt=\"SearXNG\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eSo I look forward to tweaking it and using it as a search\nbackend with open web UI. Next on my list is having enough resources to run\nOllama and a stable diffusion generator at the same time and have image\ngeneration working through open web UI.\u003c/p\u003e\n",
      "content_text": "\n\nI heard about [SearXNG](https://searxng.org) on a couple podcasts and saw it trending on GitHub\nseveral times before I finally decided to stand it up. \nI used it transparently when trying out [khoj](https://khoj.dev/), a self-hosted AI LLM agent\nplayground kind of a thing, but I've also been messing around with [Open-WebUI](https://openwebui.com) to\nhave a self-hosted ChatGPT-like experience. \nI don't know if SearchXNG used to be harder to set up, but it was pretty simple with a Docker Compose up\nand a couple configuration options given my personal homelab setup. Using it feels\nvery nice. \n\n```yaml\nservices:\n  redis:\n    container_name: redis\n    image: docker.io/valkey/valkey:8-alpine\n    command: valkey-server --save 30 1 --loglevel warning\n    restart: unless-stopped\n    volumes:\n      - ./data/redis:/data\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1m\"\n        max-file: \"1\"\n\n  searxng:\n    container_name: searxng\n    image: docker.io/searxng/searxng:latest\n    restart: unless-stopped\n    ports:\n      - \"8080:8080\"\n    volumes:\n      - ./data/searxng:/etc/searxng:rw\n    env_file: .env\n    environment:\n      # - SEARXNG_BASE_URL=https://${SEARXNG_HOSTNAME:-localhost}/\n      - UWSGI_WORKERS=${SEARXNG_UWSGI_WORKERS:-4}\n      - UWSGI_THREADS=${SEARXNG_UWSGI_THREADS:-4}\n    logging:\n      driver: \"json-file\"\n      options:\n        max-size: \"1m\"\n        max-file: \"1\"\n```\n\nI love the self-hosted aspect. I love not seeing any ads on my search\nresults. I did a few searches where I know what results to expect and\nit did okay. \nIt is good right now at filtering out garbage in my results. \n\n![SearXNG](https://dropper.wayl.one/api/file/4ff5953c-bc8d-4978-8509-b43582460a0b.png)\n\nSo I look forward to tweaking it and using it as a search\nbackend with open web UI. Next on my list is having enough resources to run\nOllama and a stable diffusion generator at the same time and have image\ngeneration working through open web UI.\n\n",
      "summary": "I heard about SearXNG on a couple podcasts and saw it trending on GitHub several times before I finally decided to stand it up. I used it transparently when...",
      "date_published": "2025-05-25T08:33:10Z",
      "date_modified": "2025-05-25T08:33:10Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/grafana-gotify-for-push-notifications/",
      "url": "https://pype.dev/grafana-gotify-for-push-notifications/",
      "title": "Grafana + Gotify for push notifications",
      "content_html": "\u003cp\u003eIt\u0026rsquo;s easy to configure push notifications through Gotify from Grafana by setting up a generic \u003ccode\u003eWebhook\u003c/code\u003e Alerting Contact Point\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThe parameters are:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eHTTP Method: POST\u003c/li\u003e\n\u003cli\u003eURL: http://\u003cgotify url\u003e/message\u003c/li\u003e\n\u003cli\u003eAuthentication is done with an app token generated from Gotify, and you put it in \u003ccode\u003eAuthorization Header: Credentials\u003c/code\u003e in the Grafana configuration\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\u003ca href=\"https://dropper.wayl.one/api/file/610b4efb-b4c4-4930-ac63-499d501ba3c3.png\"\u003eSee image\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nIt's easy to configure push notifications through Gotify from Grafana by setting up a generic `Webhook` Alerting Contact Point...\n\nThe parameters are:\n\n* HTTP Method: POST\n* URL: http://\u003cgotify url\u003e/message\n* Authentication is done with an app token generated from Gotify, and you put it in `Authorization Header: Credentials` in the Grafana configuration \n\n[See image](https://dropper.wayl.one/api/file/610b4efb-b4c4-4930-ac63-499d501ba3c3.png)\n",
      "summary": "It's easy to configure push notifications through Gotify from Grafana by setting up a generic Alerting Contact Point...",
      "date_published": "2025-04-07T10:12:51Z",
      "date_modified": "2025-04-07T10:12:51Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "infrastructure",
        "homelab",
        "tech",
        "til"
      ]
    }
  ]
}