{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "Thoughts on tech Archive",
  "home_page_url": "https://pype.dev/tech/",
  "feed_url": "https://pype.dev/tech/archive/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/harbor-faulted-disk-replacement/",
      "url": "https://pype.dev/harbor-faulted-disk-replacement/",
      "title": "The Faulted Disk: harbor Replacement Writeup",
      "content_html": "\u003cp\u003eThe sequel to \u003ca href=\"/panicking-led-to-losing-my-desktop\"\u003epanicking-led-to-losing-my-desktop\u003c/a\u003e — this time the monitoring actually caught the disk dying, and nothing was lost.\u003c/p\u003e\n\u003ch2 id=\"what-happened\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat happened\u003c/span\u003e \u003ca href=\"#what-happened\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eharbor\u003c/code\u003e is my replica pool — a 10.9T mirror (2x 12TB) that receives syncoid snapshots from \u003ccode\u003etank\u003c/code\u003e. One side of the mirror, a Seagate Exos \u003ccode\u003eST12000NM0127\u003c/code\u003e (serial \u003ccode\u003eZJV4QFLB\u003c/code\u003e, \u003ccode\u003e/dev/sdb\u003c/code\u003e), went \u003cstrong\u003eFAULTED\u003c/strong\u003e with 14 read + 22 checksum errors.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emirror-0                              DEGRADED     0     0     0\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ata-ST12000VN0008-2PH103_ZTM0NFDW  ONLINE       0     0     0\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ata-ST12000NM0127_ZJV4QFLB         FAULTED     14     0    22  too many errors\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThe IronWolf mirror side carried the pool — \u003ccode\u003eNo known data errors\u003c/code\u003e. ZFS redundancy did exactly its job.\u003c/p\u003e\n\u003ch2 id=\"the-difference-from-last-time\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe difference from last time\u003c/span\u003e \u003ca href=\"#the-difference-from-last-time\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eLast failure: no monitoring, found out by accident months later, desktop died.\u003c/p\u003e\n\u003cp\u003eThis failure: SigNoz + node-exporter\u0026rsquo;s ZFS collector → \u003ccode\u003enode_zfs_zpool_state{state=\u0026quot;degraded\u0026quot;}\u003c/code\u003e → alert rule → Gotify → my phone. The gotify notification fired \u003cem\u003ebefore\u003c/em\u003e I knew anything was wrong.\u003c/p\u003e\n\u003ch2 id=\"diagnosis\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDiagnosis\u003c/span\u003e \u003ca href=\"#diagnosis\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eBefore \u003ccode\u003ezpool clear\u003c/code\u003e or replace — check SMART. \u003ccode\u003esmartctl-exporter\u003c/code\u003e already scrapes all disks into SigNoz, so I didn\u0026rsquo;t even need sudo:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eReallocated_Sector_Ct\u003c/code\u003e raw = \u003cstrong\u003e3024\u003c/strong\u003e and counting\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eOffline_Uncorrectable\u003c/code\u003e value/worst still 100 but raw errors climbing\u003c/li\u003e\n\u003cli\u003eSMART overall: still PASS (SMART\u0026rsquo;s overall bit is conservative until threshold)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e3k+ remapped sectors is a platter going bad — not a cable blip. Verdict: replace, don\u0026rsquo;t clear.\u003c/p\u003e\n\u003ch2 id=\"the-swap-the-annoying-part\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe swap (the annoying part)\u003c/span\u003e \u003ca href=\"#the-swap-the-annoying-part\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHot-plug is never as smooth as it should be:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003ezpool offline harbor \u0026lt;old\u0026gt;\u003c/code\u003e to stop writes to the dead drive — actually skipped; drive fell off the bus on its own\u003c/li\u003e\n\u003cli\u003eNew IronWolf \u003ccode\u003eZTN1CQ07\u003c/code\u003e in hand → plugged into ghost\u0026rsquo;s SATA → \u003cstrong\u003edidn\u0026rsquo;t enumerate\u003c/strong\u003e\u003c/li\u003e\n\u003cli\u003eForced rescan (\u003ccode\u003eecho \u0026quot;- - -\u0026quot; | sudo tee /sys/class/scsi_host/host*/scan\u003c/code\u003e) → nothing\u003c/li\u003e\n\u003cli\u003eMoved it to the \u003cem\u003eold drive\u0026rsquo;s port\u003c/em\u003e → nothing\u003c/li\u003e\n\u003cli\u003eSabrent USB dock on aurora → dock enumerated as \u003ccode\u003e0B\u003c/code\u003e device, no disk behind it → reseated + replugged → \u003cstrong\u003edrive spun up and appeared\u003c/strong\u003e: \u003ccode\u003e/dev/sdd\u003c/code\u003e, 10.9T, old ZFS partition table on it (used drive — \u003ccode\u003epart1\u003c/code\u003e/\u003ccode\u003epart9\u003c/code\u003e layout)\u003c/li\u003e\n\u003cli\u003eBack to ghost, direct SATA → enumerated as \u003ccode\u003esdb\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eLesson: \u0026ldquo;spins up but doesn\u0026rsquo;t enumerate\u0026rdquo; on direct SATA + dock-shows-0B = seating/power problem, not DOA. The drive was fine all along.\u003c/p\u003e\n\u003ch2 id=\"the-replace\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe replace\u003c/span\u003e \u003ca href=\"#the-replace\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo zpool replace harbor \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ata-ST12000NM0127_ZJV4QFLB \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  ata-ST12000VN0008-2PH103_ZTN1CQ07\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eResilver: \u003cstrong\u003e8.80T in 18h38m, 0 errors\u003c/strong\u003e (~154M/s). Pool stayed online and usable the whole time.\u003c/p\u003e\n\u003cp\u003eOne gotcha: after resilver, \u003ccode\u003ezpool status\u003c/code\u003e showed \u003ccode\u003eerrors: 1 data errors\u003c/code\u003e — but \u003ccode\u003ezpool status -v\u003c/code\u003e showed an \u003cstrong\u003eempty error list\u003c/strong\u003e. The corrupted data was already repaired; the counter was just stale. \u003ccode\u003esudo zpool clear harbor\u003c/code\u003e → clean.\u003c/p\u003e\n\u003ch2 id=\"the-monitoring-that-made-this-possible\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe monitoring that made this possible\u003c/span\u003e \u003ca href=\"#the-monitoring-that-made-this-possible\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWired during this session (all now in SigNoz):\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003enode_zfs_zpool_state\u003c/code\u003e — pool health (node-exporter zfs collector)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003esmartctl-exporter\u003c/code\u003e — SMART attributes incl. reallocated sectors (the early-death signal)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ezfs-metrics.sh\u003c/code\u003e textfile bridge — sanoid \u003ccode\u003e--monitor-*\u003c/code\u003e exit codes, zpool error counters, scrub ages, resilver %, syncoid last-success\u003c/li\u003e\n\u003cli\u003eAlert rules → Gotify → phone\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe replication alerting even proved itself live: syncoid failed twice during the resilver window and I got paged on both. Turned out to be send-time contention — self-healed once resilver finished — but the notification path works end to end.\u003c/p\u003e\n\u003ch2 id=\"remaining-homework\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eRemaining homework\u003c/span\u003e \u003ca href=\"#remaining-homework\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003eBuy the replacement spare (the shelf\u0026rsquo;s empty now)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e10Fold\u003c/code\u003e datasets are garbage + have zero snapshots — destroy\u003c/li\u003e\n\u003cli\u003eEvery scheduled job emits \u003ccode\u003ecron_last_success_epoch\u003c/code\u003e now — if a job dies silently again, I\u0026rsquo;ll know\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSame failure as April, opposite outcome. Redundancy did the protection; monitoring did the \u003cem\u003edetection\u003c/em\u003e. You need both.\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003e\u003cem\u003eCo-authored with Devin, who ran the monitoring stack, the SMART diagnosis, and the alerting loop.\u003c/em\u003e\u003c/p\u003e\n",
      "content_text": "\nThe sequel to [panicking-led-to-losing-my-desktop](/panicking-led-to-losing-my-desktop) — this time the monitoring actually caught the disk dying, and nothing was lost.\n\n## What happened\n\n`harbor` is my replica pool — a 10.9T mirror (2x 12TB) that receives syncoid snapshots from `tank`. One side of the mirror, a Seagate Exos `ST12000NM0127` (serial `ZJV4QFLB`, `/dev/sdb`), went **FAULTED** with 14 read + 22 checksum errors.\n\n``` text\nmirror-0                              DEGRADED     0     0     0\n  ata-ST12000VN0008-2PH103_ZTM0NFDW  ONLINE       0     0     0\n  ata-ST12000NM0127_ZJV4QFLB         FAULTED     14     0    22  too many errors\n```\n\nThe IronWolf mirror side carried the pool — `No known data errors`. ZFS redundancy did exactly its job.\n\n## The difference from last time\n\nLast failure: no monitoring, found out by accident months later, desktop died.\n\nThis failure: SigNoz + node-exporter's ZFS collector → `node_zfs_zpool_state{state=\"degraded\"}` → alert rule → Gotify → my phone. The gotify notification fired *before* I knew anything was wrong.\n\n## Diagnosis\n\nBefore `zpool clear` or replace — check SMART. `smartctl-exporter` already scrapes all disks into SigNoz, so I didn't even need sudo:\n\n- `Reallocated_Sector_Ct` raw = **3024** and counting\n- `Offline_Uncorrectable` value/worst still 100 but raw errors climbing\n- SMART overall: still PASS (SMART's overall bit is conservative until threshold)\n\n3k+ remapped sectors is a platter going bad — not a cable blip. Verdict: replace, don't clear.\n\n## The swap (the annoying part)\n\nHot-plug is never as smooth as it should be:\n\n1. `zpool offline harbor \u003cold\u003e` to stop writes to the dead drive — actually skipped; drive fell off the bus on its own\n2. New IronWolf `ZTN1CQ07` in hand → plugged into ghost's SATA → **didn't enumerate**\n3. Forced rescan (`echo \"- - -\" | sudo tee /sys/class/scsi_host/host*/scan`) → nothing\n4. Moved it to the *old drive's port* → nothing\n5. Sabrent USB dock on aurora → dock enumerated as `0B` device, no disk behind it → reseated + replugged → **drive spun up and appeared**: `/dev/sdd`, 10.9T, old ZFS partition table on it (used drive — `part1`/`part9` layout)\n6. Back to ghost, direct SATA → enumerated as `sdb`\n\nLesson: \"spins up but doesn't enumerate\" on direct SATA + dock-shows-0B = seating/power problem, not DOA. The drive was fine all along.\n\n## The replace\n\n``` bash\nsudo zpool replace harbor \\\n  ata-ST12000NM0127_ZJV4QFLB \\\n  ata-ST12000VN0008-2PH103_ZTN1CQ07\n```\n\nResilver: **8.80T in 18h38m, 0 errors** (~154M/s). Pool stayed online and usable the whole time.\n\nOne gotcha: after resilver, `zpool status` showed `errors: 1 data errors` — but `zpool status -v` showed an **empty error list**. The corrupted data was already repaired; the counter was just stale. `sudo zpool clear harbor` → clean.\n\n## The monitoring that made this possible\n\nWired during this session (all now in SigNoz):\n\n- `node_zfs_zpool_state` — pool health (node-exporter zfs collector)\n- `smartctl-exporter` — SMART attributes incl. reallocated sectors (the early-death signal)\n- `zfs-metrics.sh` textfile bridge — sanoid `--monitor-*` exit codes, zpool error counters, scrub ages, resilver %, syncoid last-success\n- Alert rules → Gotify → phone\n\nThe replication alerting even proved itself live: syncoid failed twice during the resilver window and I got paged on both. Turned out to be send-time contention — self-healed once resilver finished — but the notification path works end to end.\n\n## Remaining homework\n\n- Buy the replacement spare (the shelf's empty now)\n- `10Fold` datasets are garbage + have zero snapshots — destroy\n- Every scheduled job emits `cron_last_success_epoch` now — if a job dies silently again, I'll know\n\nSame failure as April, opposite outcome. Redundancy did the protection; monitoring did the *detection*. You need both.\n\n---\n\n*Co-authored with Devin, who ran the monitoring stack, the SMART diagnosis, and the alerting loop.*\n",
      "summary": "The sequel to panicking-led-to-losing-my-desktop — this time the monitoring actually caught the disk dying, and nothing was lost.",
      "date_published": "2026-10-06T21:30:00Z",
      "date_modified": "2026-10-06T21:30:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "zfs",
        "tech",
        "backup"
      ]
    },
    {
      "id": "https://pype.dev/it-will-be-hard-to-do-more/",
      "url": "https://pype.dev/it-will-be-hard-to-do-more/",
      "title": "It Will Be Hard To Do More",
      "content_html": "\u003cp\u003eRecently I was given a raise at work after I broached the subject with my\nmanager. I wasn\u0026rsquo;t sure what the outcome would be but I have learned two things\nin nearly a decade in corporate America:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eNo one will fight for you harder than yourself\u003c/li\u003e\n\u003cli\u003eThe worst they can say is no\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"the-raise\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Raise\u003c/span\u003e \u003ca href=\"#the-raise\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSo I talked with my manager about my workload and compensation, and to my\nsurprise he took me seriously. He went up the chain with some accomplishments\nof the year that I wrote up for him (it\u0026rsquo;s his job to advocate for me, but it\u0026rsquo;s\ncertainly my job to give him the ammunition to take into battle). Before much\ntime had passed, a big wig put some time on my calendar. I had a good\nrelationship with my (then) manager, and even with the big wig, so there was\nalways some amount of personal connection amidst the money-talk.\u003c/p\u003e\n\u003ch2 id=\"the-reorg\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Reorg\u003c/span\u003e \u003ca href=\"#the-reorg\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eCue a managerial change and reorganization of the team. The dust settled, and\nmy manager wasn\u0026rsquo;t my manager and the big wig wasn\u0026rsquo;t a big wig over my team\nanymore. The details aren\u0026rsquo;t that relevant, but the outcome was that my \u003cem\u003enow\u003c/em\u003e\nmanager is someone I don\u0026rsquo;t know personally — back to square one of\nprofessional-relationship-building.\u003c/p\u003e\n\u003ch2 id=\"the-conversation\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Conversation\u003c/span\u003e \u003ca href=\"#the-conversation\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eDue to the timing of it all, when my raise became official it was this new\nmanager who signed off on it. It was their happy-responsibility to have a quick\nchat to tell me it was official. But that conversation was the most\nawkward of the three.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eDoes this raise of X% close the gap with the other competing opportunities\nyou mentioned to (previous manager)? Because it\u0026rsquo;ll be hard to do more than\nthis.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"admonition danger\"\u003e\n\u003cp class=\"admonition-title\"\u003e???\u003c/p\u003e\n\u003cp\u003eHard to do more?\u003c/p\u003e\n\u003c/div\u003e\n\u003cp\u003eI was taken aback initially. It felt almost threatening.\u003c/p\u003e\n\u003cp\u003eI really wasn\u0026rsquo;t sure how to respond. I think I was grateful and cordial,\ntalked about how I really do believe in the mission of our work and I\u0026rsquo;m\nthankful that I can receive a raise. This lady is nice but she doesn\u0026rsquo;t know\nwhat my \u0026ldquo;other opportunities\u0026rdquo; are, so there\u0026rsquo;s no honest answer for me to give.\nI wanted to be sarcastic, crack a joke about record profits, make light of her\ndescribing me as a \u0026ldquo;critical player in enterprise deliverables,\u0026rdquo; but I\u0026rsquo;m not\ntrying to make enemies.\u003c/p\u003e\n\u003ch2 id=\"the-reframe\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Reframe\u003c/span\u003e \u003ca href=\"#the-reframe\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSo I called a friend to vent, of course, and he gave me some good advice.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIt was a threat.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eBut not in the way I took it at first. My friend has been at the same company\nlonger than me, and he expounded on her words: \u0026ldquo;it will be hard to do more.\u0026rdquo;\nI felt like she was annoyed with me for fighting for a raise, but my friend\nsaid she was probably just trying to give me a realistic view of my position\nin the company. \u0026ldquo;It will be hard to do more\u0026rdquo; means there are processes in\nplace at such a large enterprise. Rewarding start-up-like behavior and\ncontribution just isn\u0026rsquo;t well-supported. It\u0026rsquo;s much easier to let someone quit\nwho\u0026rsquo;s been with the company a decade and replace them for 20% more than it is\nto just pay them 20% more. If someone (like me) isn\u0026rsquo;t satisfied, that\u0026rsquo;s not the\nonly thing in the conversation - time at the company, time in position, time\nunder specific manager, etc. all are hurdles to more compensation, and I\u0026rsquo;m\nreferring to salary anyways, equity feels like it\u0026rsquo;s out of my grasp. But if I\u0026rsquo;m\ngone and market conditions dictate that my position and responsibilities are\nworth 20% more than what I was being paid, that\u0026rsquo;s what the position will be\nposted at. My previous manager and the big wig were the types to say\npolicy-be-damned when it would get in the way - it might be how I got this\nraise in the first place. But no shade to my new manager, they\u0026rsquo;re just doing their job.\u003c/p\u003e\n\u003cp\u003eIn the end, they were just being honest, and I should appreciate that.\u003c/p\u003e\n",
      "content_text": "\nRecently I was given a raise at work after I broached the subject with my\nmanager. I wasn't sure what the outcome would be but I have learned two things\nin nearly a decade in corporate America:\n\n1. No one will fight for you harder than yourself\n2. The worst they can say is no\n\n## The Raise\n\nSo I talked with my manager about my workload and compensation, and to my\nsurprise he took me seriously. He went up the chain with some accomplishments\nof the year that I wrote up for him (it's his job to advocate for me, but it's\ncertainly my job to give him the ammunition to take into battle). Before much\ntime had passed, a big wig put some time on my calendar. I had a good\nrelationship with my (then) manager, and even with the big wig, so there was\nalways some amount of personal connection amidst the money-talk.\n\n## The Reorg\n\nCue a managerial change and reorganization of the team. The dust settled, and\nmy manager wasn't my manager and the big wig wasn't a big wig over my team\nanymore. The details aren't that relevant, but the outcome was that my _now_\nmanager is someone I don't know personally — back to square one of\nprofessional-relationship-building.\n\n## The Conversation\n\nDue to the timing of it all, when my raise became official it was this new\nmanager who signed off on it. It was their happy-responsibility to have a quick\nchat to tell me it was official. But that conversation was the most\nawkward of the three.\n\n\u003e Does this raise of X% close the gap with the other competing opportunities\n\u003e you mentioned to (previous manager)? Because it'll be hard to do more than\n\u003e this.\n\n!!! danger \"???\"\n\n    Hard to do more?\n\nI was taken aback initially. It felt almost threatening.\n\nI really wasn't sure how to respond. I think I was grateful and cordial,\ntalked about how I really do believe in the mission of our work and I'm\nthankful that I can receive a raise. This lady is nice but she doesn't know\nwhat my \"other opportunities\" are, so there's no honest answer for me to give.\nI wanted to be sarcastic, crack a joke about record profits, make light of her\ndescribing me as a \"critical player in enterprise deliverables,\" but I'm not\ntrying to make enemies.\n\n## The Reframe\n\nSo I called a friend to vent, of course, and he gave me some good advice.\n\n\u003e It was a threat.\n\nBut not in the way I took it at first. My friend has been at the same company\nlonger than me, and he expounded on her words: \"it will be hard to do more.\"\nI felt like she was annoyed with me for fighting for a raise, but my friend\nsaid she was probably just trying to give me a realistic view of my position\nin the company. \"It will be hard to do more\" means there are processes in\nplace at such a large enterprise. Rewarding start-up-like behavior and\ncontribution just isn't well-supported. It's much easier to let someone quit\nwho's been with the company a decade and replace them for 20% more than it is\nto just pay them 20% more. If someone (like me) isn't satisfied, that's not the\nonly thing in the conversation - time at the company, time in position, time\nunder specific manager, etc. all are hurdles to more compensation, and I'm\nreferring to salary anyways, equity feels like it's out of my grasp. But if I'm\ngone and market conditions dictate that my position and responsibilities are\nworth 20% more than what I was being paid, that's what the position will be\nposted at. My previous manager and the big wig were the types to say\npolicy-be-damned when it would get in the way - it might be how I got this\nraise in the first place. But no shade to my new manager, they're just doing their job.\n\nIn the end, they were just being honest, and I should appreciate that.\n",
      "summary": "Recently I was given a raise at work after I broached the subject with my manager. I wasn't sure what the outcome would be but I have learned two things in...",
      "date_published": "2026-05-22T06:23:20Z",
      "date_modified": "2026-05-22T06:23:20Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "work",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/panicking-led-to-losing-my-desktop/",
      "url": "https://pype.dev/panicking-led-to-losing-my-desktop/",
      "title": "Panicking Led to Losing My Desktop",
      "content_text": "\n## False Sense of Security\n\nI thought I had backups handled... can you imagine how the rest of this post is\ngoing to go with that intro?\n\nTo be fair, I do have backups figured out on my NAS - simple ZFS +\nsanoid/syncoid + replica pool + off-site backup with simple restore pathways.\nHowever, my desktop has been another story entirely. My desktop OS didn't\nsupport ZFS when I started checking it out, and I spent weeks thinking through\nhow I would backup my HOME directory and projects mostly. I landed on a\nsolution that I did validate once, but it fell off my radar and lo' and behold\nthat was problematic...\n\nSo that backup was based on restic for my home directory, but it was lazy. I\nverified it one time but I had built it with ai, thought I understood the\nrestic repo part, and then promptly moved on with my life never buttoning it\nall up. That home directory backup got too big for where I was going to end up\nrestoring it. My desktop system was installed on a 4 TB NVMe drive and due to\nthe circumstances spawning this blog post I was gonna have to drop to a 500 GB\nboot drive with some extra disks as the storage layer. Overall it looked like:\n\n- A 4 TB SSD that was going bad - old OS\n- A 500 GB SSD, that was going to be my new operating system boot disk\n- A 2 TB SSD that was originally going to be this external storage volume\n  anyways but I never set it up because the version of Aurora I was running\n  didn't have ZFS, I was married to the idea of using ZFS, so I never ended up\n  taking advantage of the space. However it was moot to me because my boot drive\n  was 4 TB, high quality drive, so I was \"just sure\" I didn't need it.\n- AND a 4 TB rust disk as well, which was already a ZFS pool, left over from a\n  previous desktop configuration, and admittedly I had forgotten it was even in the system.\n\n## The Storm\n\nIf it wasn't clear the problem is that my super-nice high-speed 4TB NVMe drive\nwas going bad, like really bad. Eventually my OS stopped booting, it was even\ndifficult to live-boot from any other ISO due to, I think ultimately, that disk\ncausing such extreme latency in the start-up processes that they just failed.\nSo I quickly found myself with little-to-no access to my primary desktop's\ndata...\n\n## Where It Went Wrong\n\nWhat I did is I live booted into an Ubuntu server environment (which took blood\nsweat and tears to successfully get into), mounted my home directory from the 4 TB SSD, and\ntried to continue my restic backup to my NAS, like an idiot. But at the same\ntime I also tried to prune it by only backing up a few projects because I\nwas getting worried about time. This was the first primary mistake - trying to\nmuck with my backup script under duress.\n\nThen over the course of the whole thing it ended up taking over a week to solve\nthis when it could've been 2-3 days. So say it with me kids - \"Don't make\ndecisions under duress\"\n\n## Climbing Out\n\nI downloaded opencode and had it help me write the right excludes syntax in my\nrestic backup script and got it back up going. That went ok but opencode agents\nhad no historical context for why anything was the way it was, and frankly an\nagent would've been misled thinking the backup solution was much more solid\nthan it was due to how I documented it.\n\nAgents also miss things... in my chat sessions it knew about the other 2\navailable disks on the desktop system, I could have done a fresh backup to the 4 TB\nspinning rust disk no problem: install zfs, mount the pool, change target of\nrestic, run full... that would've been beautifully simple. But instead I\ntrimmed it down and backed not-everything up to the NAS over the network, and\nto a different backup target nonetheless... SMH.\n\nAs I started to consider which OS I was going to go with next I failed to\ninstall Pop_OS! or Ubuntu onto the new disc... Then I tried Omarchy and the\ninstall script just looped. So, I reinstalled Aurora onto the new 500 GB disk\nand then quickly realized I don't have Firefox tabs, my SSH keys are in that\nrestic backup, my ssh config, api keys in hidden files.... Everything is in\nthat restic backup... The backup that's too big to restore to my new boot drive.\n\nBut you know what I have? That 2 terabyte disk mounted just fine as a\nZFS dataset. And I could mount the 4 TB rust disk with zfs as well because this\nversion of Aurora has zfs working flawlessly!\n\n## Hindsight\n\nWhat I should've done is so simple... While in that ubuntu live environment I\nshould've just either updated restic to be a local backup to the 4 TB rust\ndisk, or rsync'd my home directory to it plain and simple... I got all in my\nhead about not backing up python venvs, node_modules, etc. that I didn't think\nto just basically carbon copy it all to a healthy disk and then prune it later.\nThen I could've synced everything back over that I needed to the new Desktop's\n$HOME and then scheduled the rsync or restic again to that locally mounted disk.\n\n## The Detail I Left Out\n\nThe keen reader might stop to think... why not just mount the old 4TB disk and\ncopy what you need to your new desktop? And that's a prudent question...\nHowever, in order to get anything installed I had to physically remove the 4TB\nSSD from the motherboard, which was basically a full PC tear-down. From there I\nwas able to at least boot in and out of iso's like you'd otherwise expect, and\nI have a USB/NVMe adapter so I planned to mount the old drive and copy things over from\nthere... But sadly... it won't mount. it's dead-dead and it appears that\nanything I didn't save in my days-long-panicked-state is just. gone.\n\nI feel pretty stupid to have not taken advantage of the 2 available disks local\nto the machine, to have naively copied stuff over and dealt with the\norganization later once my OS was back up. I tried to be smart and efficient\nand ended up wasting so much time and losing quite a lot of \"stuff\"... ideas,\nblog posts that I never committed, etc.\n\n## Current Status\n\nSo a few lessons...\n\n1. untested backups are not backups\n2. false backups might be worse than none, although I did at least save a few things so maybe the jury is out here\n3. making decisions while stressed out will lead to missing obviously better pathways... slow down, talk it out\n\nAs for my current status - I'm working on [[desktop-setup-2026]] and recovering what I can from my haphazard'd rsyncs in the live ubuntu env I got into. I'm also setting up a new Linux laptop at work at the same time so maybe I'll hve some workflow changes to write about in the future. For now, it's nice to be forced to accept that not every idea was that important, the good stuff will come back around, and ultimately computers and shit are just things, they're not life.\n",
      "summary": "I thought I had backups handled... can you imagine how the rest of this post is going to go with that intro?",
      "date_published": "2026-05-13T08:24:00Z",
      "date_modified": "2026-05-13T08:24:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "backup",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/the-relief-of-earning/",
      "url": "https://pype.dev/the-relief-of-earning/",
      "title": "The Relief of Earning",
      "content_html": "\u003ch2 id=\"the-setup\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Setup\u003c/span\u003e \u003ca href=\"#the-setup\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWork has been crazy for months, and I haven\u0026rsquo;t written too much about it, a few\nnotes here and there but nothing substantial. The craziness is sourced in a\npretty cliched-scenario where we have lofty goals and important things to build\nbut the foundation on which we are building, like architectural decisions, is\nbasically wet sand. So as I\u0026rsquo;ve been trying to find my footing in the mud I feel\nlike I end up spinning my wheels moreso than making any progress. The chaos led\nme eventually to approach my manager about my compensation - I make a nice\nliving but for the stress I\u0026rsquo;d been feeling I could certainly go make more. My\nmanager successfully fought for me up the chain and I was given a slightly bigger\ncarrot, but that\u0026rsquo;s not the point of this post. I started to feel even more\nanxiety after I was told about the raise because, just like with AI and agentic\ncoding, now I was sure the expectations would be even higher. I didn\u0026rsquo;t think\nanything would actually come from me asking my boss about a raise, but now that\nit did I was afraid the ceiling of expectations was just going up up up.\u003c/p\u003e\n\u003ch2 id=\"the-carrot-and-the-anxiety\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Carrot and the Anxiety\u003c/span\u003e \u003ca href=\"#the-carrot-and-the-anxiety\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThat\u0026rsquo;s where the relief of earning comes in\u0026hellip; I mentioned this to my project\nmanager, and we have candid conversations regularly, and he told me something\nthat I think some people might have been offended by but I was genuinely\nrelieved to hear it. He said he\u0026rsquo;s glad I got a raise, and that he told our boss\nto \u003cem\u003enot\u003c/em\u003e really pursue it for me until I had actually delivered something. So even\nthough I feel like I\u0026rsquo;m spinning my wheels a lot, my direct leadership sees real\nvalue - and the relief is that I can rest in confirmation that the carrot is a\nresponse to work I already did, not a quiet elevation of expectations.\u003c/p\u003e\n",
      "content_text": "\n## The Setup\n\nWork has been crazy for months, and I haven't written too much about it, a few\nnotes here and there but nothing substantial. The craziness is sourced in a\npretty cliched-scenario where we have lofty goals and important things to build\nbut the foundation on which we are building, like architectural decisions, is\nbasically wet sand. So as I've been trying to find my footing in the mud I feel\nlike I end up spinning my wheels moreso than making any progress. The chaos led\nme eventually to approach my manager about my compensation - I make a nice\nliving but for the stress I'd been feeling I could certainly go make more. My\nmanager successfully fought for me up the chain and I was given a slightly bigger\ncarrot, but that's not the point of this post. I started to feel even more\nanxiety after I was told about the raise because, just like with AI and agentic\ncoding, now I was sure the expectations would be even higher. I didn't think\nanything would actually come from me asking my boss about a raise, but now that\nit did I was afraid the ceiling of expectations was just going up up up.\n\n## The Carrot and the Anxiety\n\nThat's where the relief of earning comes in... I mentioned this to my project\nmanager, and we have candid conversations regularly, and he told me something\nthat I think some people might have been offended by but I was genuinely\nrelieved to hear it. He said he's glad I got a raise, and that he told our boss\nto _not_ really pursue it for me until I had actually delivered something. So even\nthough I feel like I'm spinning my wheels a lot, my direct leadership sees real\nvalue - and the relief is that I can rest in confirmation that the carrot is a\nresponse to work I already did, not a quiet elevation of expectations.\n",
      "summary": "Work has been crazy for months, and I haven't written too much about it, a few notes here and there but nothing substantial. The craziness is sourced in a...",
      "date_published": "2026-05-08T05:43:39Z",
      "date_modified": "2026-05-08T05:43:39Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "work",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/fixing-firefox-launcher-on-kubuntu-22/",
      "url": "https://pype.dev/fixing-firefox-launcher-on-kubuntu-22/",
      "title": "Fixing Firefox Launcher on Kubuntu 22",
      "content_html": "\u003cul\u003e\n\u003cli\u003eProblem: Firefox installed but missing from KDE/Plasma app menu\u003c/li\u003e\n\u003cli\u003eRoot cause: No .desktop launcher file — Firefox only worked from terminal\u003c/li\u003e\n\u003cli\u003eSolution: Created desktop entry with standard KDE fields\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre\u003e\u003ccode\u003e\n[Desktop Entry]\nVersion=1.0\nName=Firefox\nGenericName=Web Browser\nComment=Web Browser\nExec=firefox %u\nTerminal=false\nIcon=firefox\nType=Application\nCategories=Network;WebBrowser;\nMimeType=x-scheme-handler/http;x-scheme-handler/https;\nStartupNotify=true\nStartupWMClass=firefox\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cul\u003e\n\u003cli\u003eLocation: ~/.local/share/applications/firefox.desktop (user-local, no sudo needed)\u003c/li\u003e\n\u003cli\u003eDiscovery step: Checked /usr/share/applications/ — empty for Firefox\u003c/li\u003e\n\u003cli\u003eApplied fix: Ran kbuildsycoca5 to rebuild Plasma\u0026rsquo;s menu cache\u003c/li\u003e\n\u003cli\u003eResult: Firefox now appears in app menu (Network category) and Alt+F2 autocomplete\u003c/li\u003e\n\u003c/ul\u003e\n",
      "content_text": "\n- Problem: Firefox installed but missing from KDE/Plasma app menu\n- Root cause: No .desktop launcher file — Firefox only worked from terminal\n- Solution: Created desktop entry with standard KDE fields\n\n```\n\n[Desktop Entry]\nVersion=1.0\nName=Firefox\nGenericName=Web Browser\nComment=Web Browser\nExec=firefox %u\nTerminal=false\nIcon=firefox\nType=Application\nCategories=Network;WebBrowser;\nMimeType=x-scheme-handler/http;x-scheme-handler/https;\nStartupNotify=true\nStartupWMClass=firefox\n\n```\n\n- Location: ~/.local/share/applications/firefox.desktop (user-local, no sudo needed)\n- Discovery step: Checked /usr/share/applications/ — empty for Firefox\n- Applied fix: Ran kbuildsycoca5 to rebuild Plasma's menu cache\n- Result: Firefox now appears in app menu (Network category) and Alt+F2 autocomplete\n",
      "summary": "Problem: Firefox installed but missing from KDE/Plasma app menu - Root cause: No .desktop launcher file — Firefox only worked from terminal - Solution:...",
      "date_published": "2026-04-11T10:32:50Z",
      "date_modified": "2026-04-11T10:32:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "agents",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/data-loading-is-a-huge-deal/",
      "url": "https://pype.dev/data-loading-is-a-huge-deal/",
      "title": "Data Loading is a Huge Deal",
      "content_text": "\nI've been thinking about the work I am doing and have to do in my role at Cat,\nin Cat Autonomy, building Forge (see [[forge-ahead]]). I feel like I have\nlittle revelations almost every day now, not that it means I'm writing\nsomething amazing and producing it really fast but there's just a whole suite\nof problems that different technologies solve at different levels and the more\nI become aware of the problems that exist, the more the existence of some\nsolutions makes sense.\n\n!!! note \"The Problem Perspective\"\n\n    I don't know if this is a real thinking technique or if I'm onto something\n    novel(doubt) but I think a lot in terms of problems - \"what problem needs\n    solving?\" and that's how I've come to prioritize my work, it's only been very\n    recently that I've realized I do this and I think I should highlight it's very\n    important to document the problem, otherwise every day you might try to solve a\n    different problem but be working on the same code\n\n!!! note \"Problem Space\"\n\n    Kedro solves a lot of these problems, so when making rada in Reman the problem\n    space was already more contained and narrow, Forge's problem-space is much more\n    vast\n\nThe one problem I'm really fixated on right now is data loading, the problem I\nneed to solve is accessing data from a wide-variety of scripts/tools, without a\nframework or standard method/library of accessing data in the first place. I've\nseen lots of projects on GitHub claim to make data loading easier and I didn't\nquite understand what problem they were solving... one example to name is [Data\nLoad Tool](https://dlthub.com/). I've seen similar ones I don't have the name\nfor right now that claim to make it easy/fast to load data from s3, or ways to\nmake s3 and a database both abstract in the user-experience for loading data.\nBut I hadn't really understood why these tools existed. I have a lot of\nexperience with [Kedro](https://dlthub.com/) and their\n[DataCatalog](https://docs.kedro.org/en/latest/catalog-data/introduction/)\nwhich provides a python object over a set of yamlfiles that makes it pretty\nsimple to load and save data in a way that isolates I/O from the business\nlogic. But what I didn't realize at the time how powerful that catalog was, the\npower of standard patterns and shared libraries. Now that I don't have it\navailable to me, I'm quite aware of the absence.\n\nIn my new role something I'm realizing is that for all the developers my team\nnow supports, there isn't a canonical way to access data. When I was in Reman\nand working with kedro, the DataCatalog was the access pattern and so when I\nwas developing a platform I never really had to think about it - it was an\nestablished pattern that I treated as a constraint and then built processes\naround it. I've been battling some mental block for weeks on Forge because of\nthe lack of that canonical pattern, and as I've talked with other engineers it\nseems like the baseline assumption is that data is just available on a\nfilesystem, but everyone's code loads data in different ways. On my small Reman\nteam, with common patterns to build on, it was easy to make things cloud-native\nor shim in some devops to improve people's lives. But when everyone's doing\ntheir own thing, and everyone's \"own thing\" is very much built-on some rigid tribal\npatterns then it's hard to really move fast cause everyone isn't already moving\nin the same direction.\n\nThat made me realize that the first problem Forge needed to solve was in\nproviding a way for engineers to have filesystem-native data access in the\nCloud, where we are S3-first in our storage philosophy. I didn't need to figure\nout a way for everyone to name a dataset, define the dataset in the first\nplace, and give a nice `my_dataset.load` that worked in python, bash, cpp, and\nwho knows what else.... I reframed the problem from \"how do engineers load up\nthe data\" to \"how do engineers have access to the data\". The requirements of\nthe Cat Autonomy group was pretty simple: POSIX-compliant storage.\n\nMy pathway to solving this problem is initially underway, I can't imagine it'll\nbe too difficult to setup for FSx instances for teams and give them an api to\nrun a Batch Job with the FSx mounted. From there, their code can load data from\n`/mnt/fsx/\u003cwhatever\u003e` just like they otherwise could be doing locally. Or maybe\nFSx will let us setup mounts to very flexible mount points and their local\nscripts will \"just work\" :shrugs:. I don't know the exact shape, but after\nrealizing the loading data is a big deal, I'm thankful I have a narrower\nproblem to solve first.\n\n!!! note \"S3 Files\"\n\n    Literally yesterday, AWS launched \"S3 Files\" offering an NFS filesystem service over buckets. I'm not sure if NFS is going to be a viable filesystem protocol for all of our use cases, but looks like we're not the only people who need the filesystem access patterns over S3.\n\n!!! warning \"A Future Problem - Canonical Reference\"\n\n    Another high-value thing Forge needs to solve is \"what is data\". The data\n    formats we have are not super simple, it's not just a set of SQL tables. We\n    have files that relate to each other based on hard-filepath patterns, and those\n    patterns are full of tribal knowledge and distributed processes. So a simple\n    question like \"How do I use forge to access my data\" is hard. In Reman a data\n    scientist would ask \"how do I use rada to access my data?\" and the answer is\n    \"We use Kedro, and Kedro solved that problem for us via the Catalog\" but\n    without Kedro, without 100% being in python (devs are also in embedded systems,\n    cpp code, and more), without even consistent practices in the existing \"how do\n    I access my data\" workflows, it's really impossible to systemetize and codify\n    it. It is my next challenge to tackle though...\n",
      "summary": "I've been thinking about the work I am doing and have to do in my role at Cat, in Cat Autonomy, building Forge (see forge-ahead). I feel like I have little...",
      "date_published": "2026-04-08T07:40:14Z",
      "date_modified": "2026-04-08T07:40:14Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "forge",
        "rada",
        "kedro",
        "tech"
      ]
    },
    {
      "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_text": "\nI am working with MiniMax M.2 to vibe up some webapp ideas for [[Nexus]] 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/the-irony-of-10x/",
      "url": "https://pype.dev/the-irony-of-10x/",
      "title": "The Irony of 10x",
      "content_text": "\n## Opener\n\nAgentic coding has been an exciting change to me over the last couple of months\nspecifically. I've been using AI tools for a few years now but something really\nshifted with Opus 4.5 as well as the tools/harnesses getting better and more\nuseful around the same time. I've been influenced by people like Simon Willison\nand Steve Yegge who have been on the forefront of agentic coding and [vibe\nengineering](https://simonwillison.net/2025/Oct/7/vibe-engineering/) since the\ndawn of ChatGPT and in my small circles of work I'm definitely on the bloodiest\nbleeding edge of the adoption of these practices. Are they the future? I don't\nknow - I tried to maintain a skeptical posture but until the bubble pops it's\nlooking like this is at least a direction the future of my line of work is\ngoing.\n\n!!! danger \"\"\n\n    The tough part is mixing the new world of agentic coding with\n    developers on wildly different points in the spectrum of adoption and maturity.\n\nHere's a short anecdote about what I mean...\n\nIn a new project that I'm on, I've refactored/reimplemented a lot of legacy\ncode, producing about 140k lines of code, configuration, and\ndocs in about 2 months. It's an insane amount of \"product\" and I've done it\nentirely with agents. But I didn't do it with a handful of vanilla chat\nsessions like \"Hey Copilot, reimplement this API, no mistakes\". I've been building\nout my own process for using LLMs effectively. I have no actual idea besides my\nown experience if what I'm building is useful, but it feels pretty good -\nagents for planning, building, reviewing, testing, etc. My \"harness\" includes\nspecialized agents and opinionated development workflows\nto try to ensure that code is never one-shotted into production.\n\nThe catch though is that I'm working with a handful of developers and they are not\nearly-adopters or aggressive experimenters with these new agentic tools. Most\nof them are still in Steve Yegge's stage 2 or 3 of AI coding as he's outlined\n[in this medium\narticle introducing\nGasTown](https://steve-yegge.medium.com/welcome-to-gas-town-4f25ee16dd04). They\nopen a chat session, and say yes or no to Opus. I've been in-between stages\n6 and 7 for a while now - managing multiple agentic sessions that themselves\nrun specialized subagents primarily for context management, although I don't\nfeel quite ready for full blown stage 8 (agents running agents running agents).\nSo this isn't me saying that I'm\n\"better\" than the guys I'm working with, just the project we're on\ntogether is now being touched by people on wildly different ends of the agentic\ncoding spectrum, and the project itself is an experiment to me of living in the \"New World\".\n\nFinally the anecdote - I'm getting PRs from guys in stages 2 and 3,\nthousands of lines of code and config, that I know they are not necessarily\nexperts in, but they're producing that code with fairly vanilla practices. How\ndo I verify it? I know my own prompts, I know what agents generated my code, I know\nthe direction and prodding I gave as they're building, but all\nI know in a PR review from someone else is the diff - not how they tested it\n(unless of course there's tests, and there must be, but tests are only as good\nas the tests are...), not the steering they gave, not the manual UAT validation\nthey did, or even the full intent... And ultimately I own the code they merge\nbecause chances are I outlast their contract with the team. It's kind of a\nscary thing to review and accept... do I trust my\nagentic practices to validate their work? Cause I certainly don't trust myself\nto validate it perfectly.\n\n!!! note \"a note about contractors\"\n\n    I'm not implying anything about anyone who works contract, I think the facts is that contractors are typically dis-incentivized to really \"own\" something - an unfortunate consequence of the world.\n\n## This is about me, not everyone\n\nI want to make sure to reiterate the context of this post and my thoughts -\nit's really about me. I'm not saying anything here is true for anyone else, I'm\nnot making predictions about tomorrow, and I'm not a prominent FOSS developer or a\nhigh-profile ex-FAANG engineer. I'm not talking about every project under the sun.\nI work at a big company, not a fast-paced startup. I work on internal tooling,\nnothing that should even be seen outside our network. I'm also a self-taught\ndeveloper, not a trained software engineer.\n\nAs far as AI goes, lately I'm mostly all in on Github Copilot\nCLI with a sprinkling of Opencode. I'm not using Claude Code or Codex which\nseem to have their own communities around plugins and usage.\n\nAnd since we're so focused on me right now, the important thing to keep as the\nbackdrop for this post is my temperament and make-up. I'm definitely made a\ncertain kind of way, not different than every single person necessarily, but\ndifferent enough, from enough people, to not blend in with the 9-5 guy who can\nlog out and not think about work until the morning (I'm not saying that's a\nstrength either, my boundary problems are for another time).\n\nI feel an aggressive burden to solve problems and own those solutions, call it\nwhite-knighting or a savior complex if you want, but I've got enough of a\nreputation at Caterpillar now (and anywhere else I've worked) to become a go-to\nperson for more than I think is necessarily appropriate. And with that burden\nI'll bring results - if a problem hooks into my brain it. will. be. solved. I\nprobably won't do the best most clean-code solution right out of the gate, but\nI'll do whatever I can to find a solution.\n\n## Who I was before agents\n\nThat is who I was before agents made code easy to produce. My first boss at\nCaterpillar used the phrase \"tenacious learner\" to describe me in several\nreviews. I kind of rejected the description because I basically refused to\nbelieve that I was really any different, any harder of a worker, than my peers.\n\nBut with almost a decade of experience in the corporate world, and some adult\nperspective on my life, I think it's accurate... I am a harder worker, to my\ndetriment sometimes, than a fair number of people I've worked with\\*\\*.\n\nI'm not brilliant but I can focus for a long time in the right circumstances.\nThe gift of perseverance (or the curse of not being able to let something go,\ndepending on how you look at it) has led to blessing in my life in both reward\nand skill.\n\n!!! note \"\\*\\*\"\n\n    There's nothing wrong with it either, Cat's somewhat noticed that work in my EOY reviews and I'm certainly not against \"just doing your job\".\n\nAnd then AI came along and with another set of the right circumstances\ncatalyzed a new way to work.\n\n## Leaning into \"Agentic Engineering\"\n\nI jumped onto experimenting with AI coding tools as soon as they became\navailable, but mostly I just tried vibe coding rather than using tools for real\nengineering work. I vibed up an API at one of my jobs that went into production\nway too early, with far too little validation, and it was scary to support it\nfrom then on out. I also did the meme, vibe-coded a TODO app, and threw that\npuppy into the internet without locking down my API endpoints... That was\nbefore agents were quite as useful as they can be\nnow, but that experience along with a handful of other stepping stones (like\nlearning some real actual fundamentals about security) began to\ngive me confidence in using the AI as a tool, like my IDE is a tool, for\nproducing **solutions** that take the form of code.\n\n!!! note \"\"\n\n    As one-shot apps got better and better, and as I learned about scoping work\n    more appropriately for agentic tools my confidence in them grew.\n\nThere's quite a difference between \"Claude make me a todo app, no mistakes\" and\nscoping out a solution in natural language, with some technical guardrails, and\nhaving agents tackle the implementation methodically.\n\n## What actually changed\n\nWhat's actually changed for me is quite a lot... I haven't opened my IDE to\nseriously write code for months now. I've oscillated between Opencode and\nCopilot CLI, leaning moreso into Copilot since it's an approved tool at work\nand as of mid-February is quite good. Mentally I'm approaching problems with a\nlittle more thought on the front-end than before because prior to agents I\nwould think as I implemented. At the scale of work that I do, this was really\nfine - working on CLI utilities to solve simple problems, developing an\niterative testing cycle for each problem that allowed me to move fast, and once\nI found a groove I was cooking. But now I don't even need to find it, I open\nOpencode or Copilot CLI with my Planner agent, describe what I want to happen and have\nOpus or GPT scope out a plan for me. Usually there's some back and forth on\nfeature scoping, then I review a markdown file it produces, and once it looks\ndecent enough to me I say \"go\" and it goes.\n\nThat works a lot better than I even care to admit because at the same time as\nI've been leaning harder into agents, I've been building my own harness of\nsorts - not a replacement for Copilot CLI or a competitor to Opencode, but\nmoreso an opinionated workflow spine that I force agents into to give strict\ngates to the SDLC (software development lifecycle).\n\n!!! warning \"Problem Solving Workflows\"\n\n    Plan and implement is fine for a lot of things, and I do think it's only getting better. My harness,\n    mentioned a few times around here before, called Nexus, is a set of agents and\n    rules that I want the code I'll be responsible for to go through before it\n    lands in production. That cycle isn't too complex, and there's only about\n    10,000 similar tools to Nexus on Github trending right now. I've thought about dropping my idea and picking up\n    something more popular, like\n    [superpowers](https://www.github.com/obra/superpowers) but at the moment I'm\n    continuing to develop on and lean into my own idea here.\n\n!!! note \"Mini post on Nexus\"\n\n    I keep saying a blog post is coming, but the high level of Nexus is that it's a task\n    tracker with a CLI that agents use to advance a ticket through a plan -\u003e build\n    -\u003e test -\u003e review -\u003e verify -\u003e merge lifecycle that is almost exactly how I\n    would otherwise have solved a problem by hand. I think it needs work, I need to\n    be harder on TDD methodlogies with agents, and work on verification gating a\n    bit more (shoutout to [showboat](https://github.com/simonw/showboat) by Simon\n    Willison) but overall it's a system of thinking that I already participate in\n    so I'm doing my best to farm out specific parts of my workflow to agents rather\n    than trying to one-shot enterprise problems and solutions.\n\n!!! danger \"Who's doing the thinking?\"\n\n    I've noticed that as I've developed Nexus out though, I lean on the agents for\n    more and more of my own thinking, and am trusting my problem solving\n    **process** moreso than my actual problem solving abilities.\n\n## Hidden costs\n\nThe cost of this increase in speed is a lack of familiarity - and the fallout\nof lack of familiarity is hard to express. There's also many facets to it.\nFor me, the first facet is that Nexus helps me move fast, but as I've leaned\ninto it for more and more of the planning, I'm less and less familiar with the\nstate of the code. I find myself asking my reviewer agents in fresh sessions\noften to explain it to me, and thankfully they're usually consistent, but\nnonetheless I'm still not intimately familiar with the code. And on Nexus it's\nnot a big deal, that's low stakes, it's just me and my workflow.\n\nI'm using Nexus + Copilot at work and that feels like higher\nstakes... I have my agents explain the status of our project and although they're\nalso somewhat consistent the thing that's scary is that other people are\nworking on that repo with me, and that's where another layer of complexity\nmanifests itself. If it's just me and my [[clankers]], let's go all day long,\nrebuild, ask questions, etc... but I have other developers I rub shoulders with\nnow, and if they ask me a question what am I going to say? \"Hold on, let me prompt\nmy agent for you\" - it's LMGTFY on steroids. And the burden becomes if I\nfeel like I can own and support what those other developers push into the repo.\n\n## Murky responsibility boundaries\n\nWhy do I own their work? Well for the third time, this post is pretty\nself-centered and all about me, and my situation is that the other developers I\nwork with presently are all contractors. Their work agreement with Cat could\nend at any second, for practically any reason. The incentive structure isn't\nthere for these guys who technically work for an agency... Their bonuses aren't\nbigger (or even exist) if Cat performs well, there's no extra vacation days in\nit for em (aye, contractors don't get vacation days anyways), and not that it's\na problem, moreso just the nature of the world we're in - but they're basically\nmercenaries out to the highest bidder and I happen to know of **multiple times\nwhere Cat lost a good person to a higher bidder**.\n\nSo this isn't really me trying to be negative about contractors at all, I'm\nhere for a pay-check as well but Cat at least gives me SOME incentive to work\nhard with the goal of compensation regardless of how altruistic I feel in my\nown circumstances.\n\n!!! note \"Incentive\"\n\n    Better ratings mean marginally better end-of-year salary increases, and I've received some other awards that certainly give me pause about jumping ship to another long-term place even when things can be crazy at Cat.\n\nIt's more than just the contrator-ownership dilema, I've dug myself quite a\nhole over the last 8-10 years, gaining a reputation that I think many would\nappreciate, but for me only lately increases the stress. I don't need to parrot\nevery accolade I've ever received, that's not the point, but to make the point\nas clear as I can - I have a lot of respect from quite a few people at\nCaterpillar. I'm blessed to have that reputation, and it's not like I haven't\nworked hard for it - but people talk about me in a way so flattering I feel\nlike the main character in a fictional story sometimes.\n\nIn a fictional story I can check out the ending, hit up spark notes, or ask AI\nhow it ends... but there isn't an \"end\" in my real world scenario, there's only\ntomorrow and I feel the pressure of not knowing what tomorrow holds now more\nthan ever.\n\n!!! danger \"\"\n\n    Being noticed is starting to feel more costly than rewarding...\n\n## Financial irony\n\nWhat's the cost? It's hard to get specific without writing a novel but here's\nthe TLDR - because I've been pretty good at what I do I've been able to do this\ntype of work outside my normal 9-5 responsibilities and with that extra work\nhas been some pretty great financial benefits. However with Cat changes,\nresponsibility increases, and now owning code that others (and their clankers)\nwrite, the extra time I gained for myself is eaten-up and has been reclaimed by\nthe mega-corp... \"Exceeding expectations\" every year just meant the bar is\nraised, the expectations are higher, the time-commitment requirement is higher,\nand as I've had to meet the requirements of both the new world and the curse of\nbeing noticed, I've lost the time for the extra work... For years I've realized\nthe benefit of my own skills and drive, but the irony of agents (and a handful\nof other things) is that with the dramatic increase in expectations, not only\non me but on those I work with and therefore their output, I don't get to\nrealize the benefits of my own gifts anymore.\n\n## Meaning and fatigue\n\nI feel very torn because the work I've been called into with Cat is good, I\nsaid in [[cat-autonomy-2-0]] that autonomy will save people's lives. I love\ngetting to participate in that mission, it's the primary reason I didn't jump\nship to try to maintain the levels, and type, of work I was doing before... But\nin a few short months the mission is being drowned out by expectations and\nrequirements that are so high I'm losing the grip on my own life.\n\n## Open questions\n\nThat leads me to questions that I can't answer, the question I ask daily now of\n\"What about tomorrow?\". What will agents do for us tomorrow, what problems will\nbe solved, what bugs will I create (by agents of course because I've never\nwritten a bug by hand in my whole life \\s). If I stopped using agents would\npeople still be impressed? Would it even matter?\n\n## Fin\n\nI'm certainly not anti-AI, it's typing all my code. I'm not anti-collaboration,\nalthough I do wish I could work alone with just my clanker-army to worry about.\nI'm not sure what I am anymore though... AI has changed how I work, what I work\non, and who I work with... Everything has changed in such a short period of\ntime and like the ending of this post, it's pretty jarring.\n\n!!! danger \"\"\n\n    Death comes to us all - James Acaster.\n\nThanks for reading.\n",
      "summary": "Agentic coding has been an exciting change to me over the last couple of months specifically. I've been using AI tools for a few years now but something...",
      "date_published": "2026-03-26T06:00:50Z",
      "date_modified": "2026-03-26T06:00:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "ai",
        "tech",
        "agents",
        "personal"
      ]
    },
    {
      "id": "https://pype.dev/remote-terraform-state-requires-working-traefik-duh/",
      "url": "https://pype.dev/remote-terraform-state-requires-working-traefik-duh/",
      "title": "Remote Terraform State Requires Working Traefik… DUH!",
      "content_html": "\u003cp\u003eI\u0026rsquo;m working on some spring cleaning in my homelab and backed myself into a\nhilarious corner yesterday. I use Open Tofu for any of my Terraform needs now,\nand although I don\u0026rsquo;t manage a ton with terraform, I do manage all my cloudflare\nstuff with it. I decided I wanted to use my own minio instance as the s3 remote\nstate backend for my workspaces so I could rely on my typical NAS data\nbackup/retention workflow for the buckets in case anything went wrong, as\nopposed to a local state file that I\u0026rsquo;m not taking a lot of precautions with.\nWell during my Spring Cleaning I was working towards replacing ingress into my\nhome network with Cloudflare tunnels and in the midst of that update I took\ndown traefik, no matter a simple \u0026rsquo;tofu apply\u0026rsquo; should get me right back to\nworking order\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╷\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"k\"\u003eError\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"k\"\u003eError\u003c/span\u003e \u003cspan class=\"k\"\u003einspecting\u003c/span\u003e \u003cspan class=\"k\"\u003estates\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"k\"\u003ethe\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;s3\u0026#34;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackend\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e     \u003cspan class=\"k\"\u003eoperation\u003c/span\u003e \u003cspan class=\"k\"\u003eerror\u003c/span\u003e \u003cspan class=\"k\"\u003eS3\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"k\"\u003eListObjectsV2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003ehttps\u003c/span\u003e \u003cspan class=\"k\"\u003eresponse\u003c/span\u003e \u003cspan class=\"k\"\u003eerror\u003c/span\u003e \u003cspan class=\"k\"\u003eStatusCode\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"m\"\u003e404\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003eRequestID\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003eHostID\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"k\"\u003eapi\u003c/span\u003e \u003cspan class=\"k\"\u003eerror\u003c/span\u003e \u003cspan class=\"k\"\u003eNotFound\u003c/span\u003e\u003cspan class=\"err\"\u003e:\u003c/span\u003e \u003cspan class=\"k\"\u003eNot\u003c/span\u003e \u003cspan class=\"k\"\u003eFound\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eHilarious problem with thankfully an easy fix\u0026hellip; downloading the state file\nfrom Minio wasn\u0026rsquo;t a big deal since the container was still running without\nissue, and placing the state file in the folder to use as the local state\nsolution for the interim went totally smooth, but this highlights the set of\ninterdependencies I\u0026rsquo;m creating for myself and as I take the next few days/weeks\nto do some spring cleaning I\u0026rsquo;m hoping I can separate out the external ingress\nfrom internal with a bit more clear boundaries so that I never lock myself out\nof a workflow I only execute on my LAN in the first place!\u003c/p\u003e\n",
      "content_text": "\nI'm working on some spring cleaning in my homelab and backed myself into a\nhilarious corner yesterday. I use Open Tofu for any of my Terraform needs now,\nand although I don't manage a ton with terraform, I do manage all my cloudflare\nstuff with it. I decided I wanted to use my own minio instance as the s3 remote\nstate backend for my workspaces so I could rely on my typical NAS data\nbackup/retention workflow for the buckets in case anything went wrong, as\nopposed to a local state file that I'm not taking a lot of precautions with.\nWell during my Spring Cleaning I was working towards replacing ingress into my\nhome network with Cloudflare tunnels and in the midst of that update I took\ndown traefik, no matter a simple 'tofu apply' should get me right back to\nworking order...\n\n```hcl\n╷\n│ Error: Error inspecting states in the \"s3\" backend:\n│     operation error S3: ListObjectsV2, https response error StatusCode: 404, RequestID: , HostID: , api error NotFound: Not Found\n```\n\nHilarious problem with thankfully an easy fix... downloading the state file\nfrom Minio wasn't a big deal since the container was still running without\nissue, and placing the state file in the folder to use as the local state\nsolution for the interim went totally smooth, but this highlights the set of\ninterdependencies I'm creating for myself and as I take the next few days/weeks\nto do some spring cleaning I'm hoping I can separate out the external ingress\nfrom internal with a bit more clear boundaries so that I never lock myself out\nof a workflow I only execute on my LAN in the first place!\n",
      "summary": "I'm working on some spring cleaning in my homelab and backed myself into a hilarious corner yesterday. I use Open Tofu for any of my Terraform needs now, and...",
      "date_published": "2026-03-23T08:12:25Z",
      "date_modified": "2026-03-23T08:12:25Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "traefik",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/to-live-in-a-world-without-ai/",
      "url": "https://pype.dev/to-live-in-a-world-without-ai/",
      "title": "To Live In A World Without AI",
      "content_html": "\u003cp\u003eI\u0026rsquo;m finding lately that I wish we could go back to pre-ChatGPT\u0026hellip; A world\nwithout a code-gen easy button, where \u0026ldquo;easy\u0026rdquo; was LSP autocomplete, where tools\nwere at my fingertips rather than remote inference endpoints, and where I and\neveryone I worked with, was paid and judged based on what we could produce with\nour own 2 hands, even if \u003ccode\u003ectrl + c\u003c/code\u003e and \u003ccode\u003ectrl + v\u003c/code\u003e was sometimes a common set of keys\u0026hellip; it\nsure beat answering \u0026ldquo;yes\u0026rdquo; to a robot every 38 seconds.\u003c/p\u003e\n",
      "content_text": "\nI'm finding lately that I wish we could go back to pre-ChatGPT... A world\nwithout a code-gen easy button, where \"easy\" was LSP autocomplete, where tools\nwere at my fingertips rather than remote inference endpoints, and where I and\neveryone I worked with, was paid and judged based on what we could produce with\nour own 2 hands, even if `ctrl + c` and `ctrl + v` was sometimes a common set of keys... it\nsure beat answering \"yes\" to a robot every 38 seconds.\n",
      "summary": "I'm finding lately that I wish we could go back to pre-ChatGPT... A world without a code-gen easy button, where \"easy\" was LSP autocomplete, where tools were...",
      "date_published": "2026-03-22T14:50:38Z",
      "date_modified": "2026-03-22T14:50:38Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "ai",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/paynepride-dot-com-outage-on-vacation/",
      "url": "https://pype.dev/paynepride-dot-com-outage-on-vacation/",
      "title": "paynepride dot com outage on vacation",
      "content_html": "\u003cp\u003eThe day after I leave for vacation I start getting SSL errors on every homelab\nservice I host for myself and others. The culprit was my Cloudflare API token\nexpiring. It was easy to find the 403s in the logs for Traefik (thank goodness\nfor Tailscale getting me into the lab from afar). The solution was to rotate the\nAPI token, replace the value in Traefik\u0026rsquo;s .env file, and hit it with the \u0026ldquo;just\ndeploy\u0026rdquo; button. Now I don\u0026rsquo;t know why this expired - the key looks like it has\nno expiration to me - and I\u0026rsquo;m too tired from the beach to dig in further.\nUntil next time, I expect this error to come back March 16 2027 I suppose.\u003c/p\u003e\n",
      "content_text": "\nThe day after I leave for vacation I start getting SSL errors on every homelab\nservice I host for myself and others. The culprit was my Cloudflare API token\nexpiring. It was easy to find the 403s in the logs for Traefik (thank goodness\nfor Tailscale getting me into the lab from afar). The solution was to rotate the\nAPI token, replace the value in Traefik's .env file, and hit it with the \"just\ndeploy\" button. Now I don't know why this expired - the key looks like it has\nno expiration to me - and I'm too tired from the beach to dig in further.\nUntil next time, I expect this error to come back March 16 2027 I suppose.\n",
      "summary": "The day after I leave for vacation I start getting SSL errors on every homelab service I host for myself and others. The culprit was my Cloudflare API token...",
      "date_published": "2026-03-16T13:21:31Z",
      "date_modified": "2026-03-16T13:21:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "ofc",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/my-thoughts-on-beads/",
      "url": "https://pype.dev/my-thoughts-on-beads/",
      "title": "My Thoughts on Beads",
      "content_text": "\n[Steve Yegge](https://en.wikipedia.org/wiki/Steve_Yegge) is a pretty well-known individual in the tech field, having been\naround for a long time at some of the larger companies. He's making quite a\nsplash in the agentic coding world as well. I've appreciated Steve's posts and\nprojects lately and wanted to put some thoughts on one called\n[beads](https://github.com/steveyegge/beads).\n\n## Beads\n\nBeads is an issue tracker with links - issues relate to and block each other,\nbut agents use beads to keep track of information and dependencies without\nstoring it in their context 100% of the time. It seems like a very popular and\nuseful tool - but I am not using it, and that's what I wanted to capture... why\nnot?\n\nThe answer for me is about **where** the organization layer is for the\ndeveloper. Beads exists in a single repo - it's a system-wide CLI but you 'bd\ninit' in a git repo, and beads uses the `.git/` folder, worktrees, [dolt](https://docs.dolthub.com/), and some\ngit hooks to operate within that git repo. Outside the repo, it takes another\ntool to tie together all the beads databases you might have.\n\nFor me, I'm hardly \"in\" a git repo anymore. My workflow is that when I have\nsomething to work on, I create a \"workspace\" ([self-defined concept](https://pypeaday.github.io/dotfiles/terminal/workspaces/#installation)) which is\njust a folder on my filesystem where I check-out git worktrees from any of the\nrepos related to the work I'm doing. Sometimes it's 1 worktree from 6 repos,\nsometimes it's 6 worktrees from 1 repo for parallel work...\n\nSo because I like to organize myself in this way, beads is already \"out\" for\nme. That's the main reason - I don't have any real technical issues with beads\nor any criticism, it just is designed for a workflow that is not how I work.\n\nThis is why I'm building [[nexus]], something I hope to be able to put out\nthere \"soon\". It won't be as general-purpose as beads, but my goal with it is\nto be plug-and-play for any agentic harness (copilot cli, claude code,\nopencode, etc.). It's a challenge thinking about it as a personal tool but also\nas a tool to share someday, but agentic coding is making it possible to make\nsome cool shareable stuff and I'm excited for my own workflow-task-manager to\nmature and at least become something useful to me (it already is, but building\nthe plane in the air makes it kind of hard to enjoy the plane).\n\n### Credit\n\n- banner image from ChatGPT\n",
      "summary": "Steve Yegge is a pretty well-known individual in the tech field, having been around for a long time at some of the larger companies. He's making quite a...",
      "date_published": "2026-03-03T05:00:47Z",
      "date_modified": "2026-03-03T05:00:47Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "ai",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/forge-ahead/",
      "url": "https://pype.dev/forge-ahead/",
      "title": "Forge Ahead",
      "content_text": "\nYesterday's [[reflection-contentment-and-work]] has a second-part this morning.\nAs I was wrapping up a project I didn't realize the closed-off-ness of\nleaving... I handed in some notes, and mid-message to someone the clock struck\nmidnight and I was locked out. It's fitting to be honest, and now in the wake\nof yesterday's contentment post, experiencing some more loss than I expected,\ntoday we forge ahead.\n\nMy main focus for work now is a project I will refer to as `forge`. It is what\nI will bring to Caterpillar Autonomy but I can only build it thanks to the\ngifts and experience God has given me. The thing I want to put on paper is a\nshort list of experiences I think God in his sovereignty, gave me over the last\nfew years and what they provide for me for Forge.\n\n## Cat Reman Platform\n\nOne of the first things that comes to mind is another project I was sad to\nlose: a platform I started for an analytics team in Cat Reman. That began as a\nsimple python cli to automate some developer operations that was otherwise a\ndozen clicks through the AWS console. Using [boto3] and some simple python I\ngave that team the start of some real velocity gains. Eventually that grew into\na larger kubernetes-based service where the data science operations: code quality,\ndeployment, updates, webapps, etc. were all handled by our platform. It wasn't\nperfect, it wasn't self-contained, it was a set of things kind of glued\ntogether with systems and scripts, but it worked, it works today and is under\nfantastic ownership.\n\nWhat I learned just from starting that cli was to be passionate about solving\nproblems. No one asked me to make it, but it needed made, and the team is in\nsuch a better place for me having started it.\n\n## Kedro and OpenShift\n\nAnother short project I was able to participate in a few years ago was leading\na data-syncronization task into a fiery horrific crash that lasted weeks -\nneigh months longer - than was necessary or appropriate... The details aren't\nrelevant - I was the lead dev in a new place tasks with syncing up data between\n2 applications. Ultimately, could've been a python script but I over-designed a\nkedro-based solution because of some requirements I misunderstood. Part of that\nmisunderstanding was not knowing how to get the requirements I needed, but they\nweren't provided in full, I didn't know any better, so for weeks we were a\ncorporate meme trying to use python to update a database that no one on our\nteam understood if we were allowed to write to.... It was very confusing.\n\nBut what I learned was a lot about gathering requirements, questioning\nassumptions, and the importance of understanding your constraints as fully as\npossible as early as possible.\n\n## A Real Platform\n\nThen one of the biggest blessings I see for Forge, is the experience I've\ngotten recently with AWS at scale... Not 1 or 2 services created with\nMedium.com copy pasta tutorials from docs... but experience working in a large\nproject across many accounts, supporting several teams, using a wide variety of\ntechnologies from Terraform to Kubernetes.\n\nOn this project I learned about practical system design and gained a lot of\nconfidence in supporting systems that are complicated... I'm not the smartest\nguy in the world but I'm no dummy, and even while lacking fluency in the system\nI was supporting on this project I learned a lot of troubleshooting skills and\ngained confidence in my ability to troubleshoot complex systems.\n\nI'll need this for Forge - which will break certainly, but I'll be there to fix\nit and I'm sure I can because I've done these other things.\n\n## Fin\n\nThe Lord has been with me through these projects - he has certainly blessed me\nwith a skillset and personality that lends itself to being really useful in the\nTech world I've landed in (which is a whole 'nother story of God's sovereignty\nand provision). I am very grateful for the swath of experience I've gotten over\nthe last 8 years or so, and this season I'm in of change is rocking me a little\nmore than I anticipated but by God's grace I think I see the purpose, or at\nleast **a** purpose, and I pray I am making the choices for work he wants me to\nmake.\n",
      "summary": "Yesterday's reflection-contentment-and-work has a second-part this morning. As I was wrapping up a project I didn't realize the closed-off-ness of leaving......",
      "date_published": "2026-02-17T05:14:15Z",
      "date_modified": "2026-02-17T05:14:15Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "work",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/kubernetes-external-secrets-operator/",
      "url": "https://pype.dev/kubernetes-external-secrets-operator/",
      "title": "Kubernetes External Secrets Operator",
      "content_text": "\nI wanted to put a short demo together of using External Secrets Operator (ESO)\nto expose secrets from a vault (like Hashicorp Vault, AWS Secrets Manager, etc)\nto services running in kubernetes\n\nDemo code is [here in this github repo](https://github.com/pypeaday/blog-kubernetes-external-secrets-operator-demo)\n\nThis post is a high level overview of the components, see the repo for the full example.\n\n## Setup\n\n- [[docker]] for containerized development\n- [[kind]] for setting up a quick cluster\n- [[kubectl]] for accessing the cluster\n- [[helm]] for installing ArgoCD and ESO\n\n- and then [justfile](https://github.com/casey/just) is there to wrap the commands to easier execution\n\n## Step 0 - Vault\n\n- for the demo we'll setup Hashicorp Vault in docker compose to easily bring it\n  up and down\n- and the init-script is in the repo - it uses curl to make some secrets in\n  vault that we'll reference later\n\n```yml\nservices:\n  vault:\n    image: hashicorp/vault:1.18\n    container_name: vault\n    ports:\n      - \"58200:8200\n    environment:\n      VAULT_DEV_ROOT_TOKEN_ID: root\n      VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200\n    volumes:\n      - vault-data:/vault/file\n    cap_add:\n      - IPC_LOCK\n    command: server -dev -dev-root-token-id=root\n\nvolumes:\n  vault-data:\n```\n\n- bringing up the vault instance is a simple `docker compose up` (use the just recipes which some `curl` commands for checking status etc.)\n\n```bash\ncurl -s http://localhost:58200/v1/sys/health | jq .  # or just vault-status\n{\n  \"initialized\": true,\n  \"sealed\": false,\n  \"standby\": false,\n  \"performance_standby\": false,\n  \"replication_performance_mode\": \"disabled\",\n  \"replication_dr_mode\": \"disabled\",\n  \"server_time_utc\": 1770893657,\n  \"version\": \"1.18.5\",\n  \"enterprise\": false,\n  \"cluster_name\": \"vault-cluster-acfb9930\",\n  \"cluster_id\": \"4d9162f4-e501-371b-7f94-bd60052b40a3\",\n  \"echo_duration_ms\": 0,\n  \"clock_skew_ms\": 0,\n  \"replication_primary_canary_age_ms\": 0\n}\n```\n\n## Step 1 - App\n\n- We need an app that requires secrets\n- app code in repo, essentially it's a python webserver to show the vault\n  values (obviously this would expose real secrets so it's just a demo)\n- Below is one of the endpoints in the vibe-coded app, just to illustrate that\n  we're going to give secrets to the app as environment variables (or as mounted\n  files!)\n- In the repo, the app is included and there's a `just build` which builds the docker image\n- There is also `just deploy` which handles loading the image into `kind`'s image cache\n\n```py\n\n# example route from demo-app - see git repo\n@app.get(\"/env\", response_class=HTMLResponse)\ndef show_env():\n    # ESO brings Vault secrets into environment variables\n    env_vars = dict(os.environ)\n\n    # Sort by category, then by key\n    sorted_items = sorted(env_vars.items(), key=lambda x: (classify_env(x[0]), x[0]))\n\n    cards = \"\".join(create_card(k, v) for k, v in sorted_items)\n\n    secret_count = sum(1 for k in env_vars if k in SECRET_KEYS)\n    config_count = sum(1 for k in env_vars if k in CONFIG_KEYS)\n    system_count = len(env_vars) - secret_count - config_count\n\n    html = HTML_TEMPLATE.format(\n        cards=cards,\n        secret_count=secret_count,\n        config_count=config_count,\n        system_count=system_count,\n    )\n\n    return HTMLResponse(content=html)\n\ndef read_mounted_files(directory: str) -\u003e dict:\n    \"\"\"Read all files from a mounted directory.\"\"\"\n    files_data = {}\n    if os.path.exists(directory) and os.path.isdir(directory):\n        for filename in os.listdir(directory):\n            filepath = os.path.join(directory, filename)\n            if os.path.isfile(filepath):\n                try:\n                    with open(filepath, \"r\") as f:\n                        files_data[filename] = f.read().strip()\n                except Exception as e:\n                    files_data[filename] = f\"\u003cError reading file: {e}\u003e\"\n    return files_data\n\n```\n\n## Step 2 - Cluster\n\n- use `kind` to bring up a cluster\n- this will start a few docker containers to act as your control-plane and workers\n\n```yml\n# kind-config.yml\nkind: Cluster\napiVersion: kind.x-k8s.io/v1alpha4\nname: eso-demo\nnodes:\n  - role: control-plane\n    extraPortMappings:\n      - containerPort: 30080\n        hostPort: 58080\n        protocol: TCP\n  - role: worker\n```\n\n```\nkind create cluster --config kind-config.yaml --name eso-demo\n```\n\n## Step 3 - External Secrets Operator\n\n- installed with [[helm]] from the official helm chart\n- NOTE: this is the Operator, not the secrets... this is the thing which goes\n  to the secrets backend and creates kubernetes secrets\n\n```\nhelm repo add external-secrets https://charts.external-secrets.io 2\u003e/dev/null || true\nhelm repo update\nhelm install external-secrets external-secrets/external-secrets \\\n  --namespace external-secrets \\\n  --create-namespace \\\n  --wait\n```\n\nIn the repo this is mostly `just eso-install`\n\n## Step 3.5 - Secretstore\n\n- You need a `clustersecretstore` to be the place that ESO puts secrets\n\n```\napiVersion: external-secrets.io/v1\nkind: ClusterSecretStore\nmetadata:\n  name: vault-backend\nspec:\n  provider:\n    vault:\n      server: \"http://10.10.0.1:58200\"\n      path: \"secret\"\n      version: \"v2\"\n      auth:\n        tokenSecretRef:\n          name: vault-token\n          key: token\n          namespace: external-secrets\n\n```\n\n## Step 4 - Secrets\n\n- Secrets go in the `clustersecretstore`\n  - in this example it's called 'vault-backend'\n- In the demo we can just `kubectl apply -f \u003cmanifest\u003e` to deploy the secret to\n  the cluster\n- In practice this should be handled by something more mature than raw-doggin\n  kubectl commands\n\n```yml\n# manifests/external-secrets.yml\n---\napiVersion: external-secrets.io/v1\nkind: ExternalSecret\nmetadata:\n  name: demo-app-secrets\n  namespace: default\nspec:\n  refreshInterval: \"10s\"\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: vault-backend\n  target:\n    name: demo-app-secrets\n    creationPolicy: Owner\n  data:\n    - secretKey: DATABASE_PASSWORD\n      remoteRef:\n        key: secret/data/demo-app/secrets\n        property: database_password\n    - secretKey: API_KEY\n      remoteRef:\n        key: secret/data/demo-app/secrets\n        property: api_key\n```\n\n## Step 4.1 - Files\n\n- ESO supports mounting files to containers as well through special `ExternalSecret` resources\n- One of the example seecrets is a TLS certificate\n\n```yml\n# manifests/external-secrets-files.yml\n---\n# File-based ExternalSecret for TLS certificates\n# These will be mounted as files in /etc/secrets/\napiVersion: external-secrets.io/v1\nkind: ExternalSecret\nmetadata:\n  name: demo-app-tls-files\n  namespace: default\nspec:\n  refreshInterval: \"10s\"\n  secretStoreRef:\n    kind: ClusterSecretStore\n    name: vault-backend\n  target:\n    name: demo-app-tls-files\n    creationPolicy: Owner\n    # Template to ensure proper file formatting\n    template:\n      type: Opaque\n      data:\n        tls.crt: \"{{ .tls_crt }}\"\n        tls.key: \"{{ .tls_key }}\"\n  data:\n    - secretKey: tls_crt\n      remoteRef:\n        key: secret/data/demo-app/tls-files\n        property: tls.crt\n    - secretKey: tls_key\n      remoteRef:\n        key: secret/data/demo-app/tls-files\n        property: tls.key\n```\n\n- Notice how there's a `spec.target.template` which templates out the file\n  contents from the secret contents\n\n## Step 5 - Helm Chart\n\n- This isn't about setting up a helm chart so I'm not going to explain a lot\n  but the working example is simple, not secure, and in the repo\n- The helm chart renders manifests - I've paired one down and added comments to\n  the relevant things\n- The thing to just take note of is the reference of the secrets in the `envFrom` section\n\n```yml\n# deployment.yml\napiVersion: apps/v1\nkind: Deployment\nmetadata:\n  annotations:\n    meta.helm.sh/release-name: demo-app\n    meta.helm.sh/release-namespace: default\n  name: demo-app\n  namespace: default\nspec:\n  replicas: 1\n  template:\n    metadata:\n      labels:\n        app.kubernetes.io/instance: demo-app\n        app.kubernetes.io/name: demo-app\n    spec:\n      containers:\n        - envFrom:\n            - secretRef:\n                name: demo-app-secrets # name of example secret from section 4\n            - secretRef:\n                name: demo-app-config # another example in the repo\n          image: demo-app:latest # the image you built and loaded into kind - simple 'just' recipe in the repo\n          imagePullPolicy: Never\n          name: demo-app\n          volumeMounts:\n            - mountPath: /etc/secrets\n              name: secrets-volume\n              readOnly: true\n            - mountPath: /etc/config\n              name: configs-volume\n              readOnly: true\n      volumes:\n        - name: secrets-volume\n          secret:\n            defaultMode: 420\n            secretName: demo-app-tls-files # example secret file from section 4.1\n        - name: configs-volume\n          secret:\n            defaultMode: 420\n            secretName: demo-app-config-files\n```\n\n## Step 5.1 - Deploy\n\n- We can deploy the demo-app from the git repo to the cluster\n- For a local demo a few things happen\n  - local image build\n  - loading that image into [[kind]] (`kind` doesn't have access to your host's docker image cache, so images need to be loaded into the cluster cache)\n- `just deploy` takes care of this for you, read the recipe in the repo if\n  you're interested in more there, the focus of this post and example are to\n  briefly show how to use ESO though\n\n## Step 6 - Profit\n\nThe example app just displays things that are mounted in - totally vibe-coded\nto illustrate the secrets mounting, not the appropriate way to leak secrets.\n\n![20260210233804_614254b7.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20260210233804_614254b7.png)\n\n![20260210233828_19852225.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20260210233828_19852225.png)\n",
      "summary": "I wanted to put a short demo together of using External Secrets Operator (ESO) to expose secrets from a vault (like Hashicorp Vault, AWS Secrets Manager,...",
      "date_published": "2026-02-10T07:48:34Z",
      "date_modified": "2026-02-10T07:48:34Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "devops",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/cat-autonomy-2-0/",
      "url": "https://pype.dev/cat-autonomy-2-0/",
      "title": "Cat Autonomy 2.0",
      "content_text": "\nI wrote about my new role [[new-job-caterpillar-autonomy]] a bit a couple weeks\nago during an insanely busy time - having just started the role and wrapping up\nwhat ended up being side-work, paired with some sleep-deprived ADHD hyperfocus\non my new responsibilities and [[nexus]], that post is less of an update and\nmore of a mind-dump. This post is meant to be a calmer update about my upcoming\ntime with Caterpillar Autonomy.\n\nIt starts, like all good stories, with Caterpillar's RTO (return to office)\nmandate and how it has affected a lot of people negatively (in my opinion). I\nhaven't seen one positive remark about it fromanyone making less than $250k a\nyear + Bonus + Equity if you catch my drift...\n\nI know of people who really did lose their jobs at the end of 2025 for refusing\nto move. My story with RTO is covered elsewhere, I'd been blessed with remote\nwork and was even secure in a remote position in the face of Caterpillar being\nwilling to fire me [[after-exceeding-expectations-for-4-years]].\n\nBut time passes and life happens, and the sovereign Lord brought me to this current\ncircumstance: where I'm fully committed to Caterpillar Autonomy, and it\nfeels weird to say it. I'm going to skip the RTO details, if you know you know.\nWhere I'm at right now is on the other side (or very nearly on the other side)\nof a choice I didn't feel freedom to make because it came from a conviction.\n\nThe manager of the group I'm in told me a story about working for another\nmining company, where he was managing mines in the Congo. These mines can be in\nthe most remote of remote places on earth - in cultures where human life is\ndefinitely treated differently than in the first-world midwest USA. Operators of\nthese huge mining trucks in these parts of the world can be smoking meth in the\ncabs - which obviously leads to unsafe operation. Safety of the mines\nthemselves can be a second-thought as well, they collapse and then the people\ninside have to be excavated out. Sean told me he had come back to work after a\nweekend just to be told, as if it was no big deal, that \"Motombu died\" and to\nfind another operator. Very crass attitude towards human life...\n\nI'm still pretty frustrated at Caterpillar for how they've handled RTO (and\ncorporitisms like \"Caterpillar family\" and \"we bleed Cat yellow\" are\ndistracting nonsense), and part of me wants to leave still over the personal\noffence, but Caterpillar will make the safest mining equipment, without a\ndoubt. And given that Cat will make the safest equipment then the story I was told and\nthe mission I couldn't help but believe in is that rolling out good autonomy\nsoftware and solutions to these remote mines will save lives.\n\n!!! warning \"\"\n\n    I'm obviously not like superman or anything, but something feels better about enabling good autonomy rather than supporting an online sales platform, or working for a bank...\n\nI was invited (coerced?) into that mission - and my role in it is enabling the\nAutonomy group to manage data for their software stack. I get to help these\nengineers be experts in the things they're experts in by offloading some\noperational overhead (data movement, pipelineing, infrastructure and devops,\netc.) so that they can produce the best autonomy software on the planet.\n",
      "summary": "I wrote about my new role new-job-caterpillar-autonomy a bit a couple weeks ago during an insanely busy time - having just started the role and wrapping up...",
      "date_published": "2026-02-09T06:07:12Z",
      "date_modified": "2026-02-09T06:07:12Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "work",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/learning-how-to-agent/",
      "url": "https://pype.dev/learning-how-to-agent/",
      "title": "Learning How To Agent",
      "content_text": "\n## Introduction \u0026 Background\n\nI've been using AI tools for codegen for a few years now, but not super\nheavily. I either use the in-line copilot stuff, which is like LSP on\nseteroids, or I have gone all the way to the other side of full on vibecoding\nwith Windsurf. That's been fun enough, but not super fulfilling and at the end\nof that I either have a simple webapp that does what I want but I don't\nunderstand, or else a half-broken thing I tried to understand but couldn't\nprompt in the right direction.\n\n!!! note \"One Caveat\"\n\n    My one caveat with Windsurf, which is a full IDE that I don't have a lot of comfortable navigation in due to all the Cascade keybindings clobbaring my own keymaps - is that with a HEAVY spec, it's done will with the Claude models at implementing a real idea I have. It's closer to vibe-engineering than vibe-coding, but that's my only instance, the rest of my Windsurf usage is \"make me a cool app - no mistakes\"\n\nThis year it feels like some tools exploded and\nI mentioned this in [[new-job-caterpillar-autonomy]]. I've been using\n[opencode](https://opencode.ai) a lot over the last few weeks and have iterated\nmany times already on a system of work that I'm trying to lean into for\nimproving my efficiency.\n\n## The Problem Space\n\nMy desire for a great Developer Experience is years old now, shout out to\nThePrimeagen for his [FEM\ncourse](https://frontendmasters.com/courses/developer-productivity-v2/) and\n[Waylon Walker](https://waylonwalker.com) for being a constant source of\nencouragement to be the best developer I can be. I sometimes (often) get\ntunnel-visioned on developer-productivity initiatives and lose the forest\nthrough the trees when ironing out a workflow - generally to find out I way\nover complicated the solution OR worse, started solving a problem I don't even\nhave.\n\n## First Attempt: Local Progress Tracker\n\nWell that's where I've been for a few weeks... I'm building Nexus, my\nsecond-brain at work to collaborate with agents on the truckload of stuff I'm\nexpected to get done. For a while now I've had a \"working-notes\" repo, which is\nbasically a blog, built with markata and navigated via markdown-lsp, where I do\nlike what I do here - take daily notes, track projects and status, and it gets\nbuilt into a nice little website I can reference with my boss.\n\nNow that we have copilot in full-swing, I'm trying to integrate agents a bit\nmore. Opencode has made this so nice - so many tools and modes of interaction,\nhighly customizable interface but also an amazing default experience... So I\nwas trying to lean into using agents and subagents for more work I started down\nthe path of building out a progress tracker. I started with a simple \"skill\"\nthat told the agent to put some info into a sqlite file, and even create the\nfile and schema based on the work. This worked fine, but was specific to each\nrepo (and actually each worktree I was in) and it was hard for me to get\nvisibility into all the work my fleet of agents was doing. Now that's actually\nproblem 1 - I don't have a fleet of agents, I had some terminal sessions going\nwith opencode, but I got it in my head that I was going to have an army of\nClaude's on my computer, constantly and autonomously knocking out tickets and I\nneeded to know who was doing what, where, when, and why..\n\n## Nexus V1 \u0026 V2: The Over-Engineering Phase\n\nSo, I dropped the local \"progress tracker\" and jumped into a huge FastAPI\nproject that I called Nexus. It was a python cli + api, with a server for\ncentralized management. It presented a kanban board, had policy gates on\n\"plans\" being approved before work could start on an Epic (and therefore any\nchild tickets). It has worktree tracking and automation, etc. It had a lot...\nit didn't all work, and it was hard to build the autonomous system... I wanted\nagentic feedback loops where `voidshaper` and I made epic plans for Epics (see the pun?) and then once the Plan was approved `star-commander` comes on the scene and makes tickets or checks tickets, depending on what's already ready to go it farmed out the work to `starsmith` (and variants for complexity) to build and then automatically calls in `recon-officer` and `qa-engineer` and at the end of it `gatekeeper` came in and\napproved or denied the changes. If denied - automatically start the loop again,\ntracking the work in Nexus, if approved - rebase and merge the branch, clean up\nthe worktree, update the ticket, close it, and get working on the next thing\nthat opens up. Ticket dependencies were in there, tracking stale agent\nsessions, clever routing of tasks to smaller models where appropriate.... you\ncan see that I went too far too fast too hard.\n\n![20260203124603_d5948740.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20260203124603_d5948740.png)\n\nNexus went through 2 or 3 iterations of this feature set. I was building it with several constraints in mind - specifically at work. 1. rate-limiting on large models (so not just using opus for everything), 2. good stewardship (not burning down cities for docstrings - farm out that work to haiku or a -mini model), 3. Copilot support - even though I like to use opencode, I wanted to supper copilot cli and in vscode as a means to share this with my coworkers who were mostly using vscode, 4. agent sessions were dying so I needed some kind of liveness probe for manual intervention in sessions where maybe the vpn died and the agent lost network... stuff like this was on my mind - but you know what wasn't? actually doing some work... I was solving problems I don't have, but were fun to think about.\n\n## The Reality Check\n\nSo, what are my problems? I boiled them down to a few things...\n\n1. I want to manage my workstreams in workspaces - folders on my computer where I put git worktrees\n2. I want varying levels of ai automation in my workflows... some things could be handled by an agent fully autonmously, but most things I'm at least paired up, reviewing diffs still, doing research while working, etc.\n3. In those varying levels of automation, I wanted the same amount of task tracking to a centralized location\n\nAnd after 2 weeks of ADHD-driven hyper focus, and many iterations on Nexus,\nthen Nexus V2.... what happened is that Nexus V2 died, and we say long live\nNexus V3!\n\n## Nexus V3: The Pragmatic Pivot\n\nNexus V3 is the same idea, but different approach... I'm leaning into opencode\ntooling specifically, dropping my care to support copilot given the lack of\nfeatures. I'm using a few opencode [plugins](https://opencode.ai/docs/plugins/)\n([opencode-notify](https://github.com/kdcokenny/opencode-notify) and\n[opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents)\nand a few [commands](https://opencode.ai/docs/commands/). Instead of building\nmy own tracker, I'm using [kanboard](https://kanboard.org/) because it's\nbasically a feature-complete agile/sprint/kanban board that I use at home, has\na simple plugin ecosystem for light customization, and solves practically every\nstatus-tracking problem I tried to build from the ground up initially - ticket\ndependencies/linkages, actions to change ticket colors for a simple intuitive\nUI based on state, easy columns and tagging configuration, a simple API and\nthere's even an [mcp server](https://github.com/bivex/kanboard-mcp).\n\nSo, I've given up on the full automation for now, although\n[opencode-pilot](https://github.com/athal7/opencode-pilot) looks VERY PROMISING\nfor this in the future. Today though, through some simple commands to give to\nagents for updating kanboard, I manually put them in worktrees, and they get to\nwork - the tracking and human-in-the-loop model is going well for the work I\nneed done.\n\n## Lessons Learned\n\nI learned and relearned plenty of lessons on this over the last 2 weeks... Data\nmodels matter more than almost anything, well-defined workflows are required if\nyou want agents to help you iterate, and not everything has to be a product...\nThat last one's personal, but every time I have an idea I **think** is good,\nI'm sure it'll be something to share, but a good lesson for me is to just build\nthe things I need for me, and **eventually** maybe it can be cleaned up to\nshare, but when I start building something with anyone other than **me** in\nmind, I'm in for a long hard journey\n\n## Current State \u0026 Future\n\nSo what's the summary? I don't think I know how to agent super well yet - but\nI'm trying to get better to stay on the forefront of my co-workers who I see\nusing AI in simple and sometimes scary ways\n\n!!! danger \"Copilot x sudo\"\n\n    Do not give copilot `sudo` on your CI server and say \"fix my problem\"... are you retarded???\n\nI am not going hardcore with [ralph\nwiggum](https://awesomeclaude.ai/ralph-wiggum) or\n[gastown](https://github.com/steveyegge/gastown) - although those inspired\nNexus v1 and v2, but I am dialing in my agentic workflow with some simple\nspecialized agents, farming out work to subagents for context management,\nplanning ahead of time to put appropriate context in a ticket, and getting\nclose to having agents check out tickets, make worktrees, and do simple work by\nthemselves (this was working in Nexus V2 but only intermittenly).\n\nSomeday I'll open-source Nexus and share the configuration and workflow, for\nnow it's private as I'm actually using it to build out what I want rather than\ntrying to recreate gastown with a cool space theme.\n",
      "summary": "I've been using AI tools for codegen for a few years now, but not super heavily. I either use the in-line copilot stuff, which is like LSP on seteroids, or I...",
      "date_published": "2026-02-03T05:50:33Z",
      "date_modified": "2026-02-03T05:50:33Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "genai",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/new-job-caterpillar-autonomy/",
      "url": "https://pype.dev/new-job-caterpillar-autonomy/",
      "title": "New Job - Caterpillar Autonomy",
      "content_text": "\nIn [[im-back-from-the-dead]] I mentioned my new role that started this year -\nit's a return to Caterpillar Autonomy. I built some data pipelines and\njunior-grade infrastructure 5 years ago but left over burn-out at the prospect of\na job with smaller scoped work and more technical guidance. That was a good\nmove, and in God's sovereignty he's brought me back. Now I'm looking at code,\nthankfully not that I wrote, but that the guy after me wrote - it accomplished\na job, like my original work did, but also like what I built back then -\nthere's better ways today. The amount of things in front of me is absolutely\ndaunting, and the people on the team have been seeded with a very high opinion\nof me... I don't know how much is valid and how much is hype, but I'm gonna try\nto live up to it.\n\nOne way I'm striving to do that is to maximize my efficiency with the AI coding\ntools available to me. We have Github Copilot and I've picked up a lot over the\ncourse of the last few years (and especially more recently) in using agents,\nplanning out work, documenting plans for agents to follow, splitting up work\nvia worktrees, etc. I feel pretty good about where I'm going and someday I\nmight even release Nexus. Nexus is serving as my second-brain at work, the hub\nwhere I collaborate with my fleet of agents on the work that must get done.\nDetails to come on this, but the thing that matters is that in ironing out\nworkflow I've moved from prompt + chat to really managing a long-lived stream\nof work. AI has been changing the world, and there's been developer hype for\nyears now. But \"make me a cool app, no mistakes\" isn't going to cut it. Even\n\"make an app but generate a plan first\" isn't going to cut it... Developers\nhave to adopt a higher level role, a systems-oriented and architecture-driven\nworldview must become primary in order to keep up. Code-gen and syntax writing\nare not what developers were **ever** paid to do, although many believed so.\nOur jobs have been to solve problems and deliver code that solves the problem.\nThe code is cheap now, but solving problems still is a human task.\n\nIn the Autonomy group - there's problems all up and down the stack. My focus is\non infrastructure and developer operations - it's become my bread and butter\nover the last several years. I'm excited to help the team grow, and I'm excited\nto grow personally/technically as I lean into the agentic workflow to produce\ncode that I'm actually proud of, that has my name on the commit, and that\nsolves real problems.\n\n## Example - Local Development\n\nOne of the first things I'm tackling is a developer-pain-point of working on\ntheir laptop. I've been in this space for years, mostly with python programmer\nwho are writing data science code. They don't know about virtual environments,\nchecking $PATH, assuming bad state in their terminal session, how to configure\nVS Code, etc. Often they just want to write some scripts and somehow test it.\nThe solution I see most often is for devs to write code in JupyterLab/Notebooks\nin AWS or some environment close to their data - this is fine I guess, but it's\nnot developing good pipelines, and it's tedious as hell. In my last job I\nhelped set developers up with workflows that allowed them to run their IDE of\nchoice locally (getting all the goodies of syntax highlighting, LSP, etc) and a\nCLI that took their code and ran it in the cloud, right next to data, in the\nsame way that prod runs. It was a hit. After that I introduced some tools to\nhelp them manage python environments - we had strict templating requirements in\nour projects, so making tools to automate those things wasn't too hard - it's\nmuch easier than trying to make something flexible for every use case. The\nopinions made the automation and tooling easy to make and distribute.\n\nWell I'm up against a similar task now, but oh so much worse... Larger team,\nlarger environment sprawl, larger infrastructure mismanagement, the whole\ngambit. And I'm here for it... Here's the first problem I'm addressing - local\ndevelopment for Airflow DAGs that run in an Airflow deployment on Kubernetes.\nThe deployment itself is a little odd, Airflow is an orchestrator, all the\npipelines run in external AWS Batch jobs - so a DAG hits the Batch API to run\nthe code. The design there is actually nice, but how are devs testing code?\n\nOh that's easy... they SSH into the prod server, which is a 5 year old desktop\nTHAT I BUILT WITH A CO-WORKER BEFORE I WAS IN AUTONOMY THE FIRST TIME... hold\non, WHAT!? Yes, it's true.. so they SSH into the prod server, run some bash\nscripts in their userspace that setup airflow and a few db utilities in a\ndocker compose stack, authenticate with AWS themselves from that server, and\nthen they run DAGs against real data to test it... I am beyond shook.\n\nHere's what I've put together - a bootstrap process (I like `just` + PEP 723\npython scripts as opposed to bash, but to each their own, and bash of course\nhas its place) that spins up a [kind](https://kind.sigs.k8s.io/) cluster on\ntheir laptop, the process pulls some private images and loads them into the\nkind cluster, it installs airflow from a helm chart (the same helm chart we'll\nuse in dev and prod... no more docker compose over here, kubectl over there),\nand everything just. comes. up. No SSH into ancient server, no touching cloud\ninfra (they get MinIO and a DB container to emulate S3 and RDS in our AWS\naccounts), no sweat on testing DAGs. They stage some data in MinIO (`just open\nminio` handles the port-forward and opens the browser), then `just open\nairflow` (their DAGs are hot-reloaded via hostPath mounting), and they can run\nDAGs locally until they're satisfied with the results.\n\nIt's taken me about a week of split-focused effort (I mentioned Nexus and I've\nbeen co-building and dogfooding that at the same time) but I'm proud of that\nlocal setup now. I am a bit shocked they've dealt with a brittle, hacky, often\nbroken development workflow for the last 4 years or so, but that development I\nsuppose.\n\n## The Point\n\nThis post wasn't meant to be me glazing myself for awesome local development\npractices, I am simply excited about this new chapter. I love solving problems,\nand I love owning those solutions. There's a whole mess of things to address,\nmy mind is buzzing, and I feel like God has blessed me with renewed passion\n(again see [[im-back-from-the-dead]]). I've given up some pay and some freedom\nto take this role, but I think it'll pay dividends.\n\nLife happens to all of us - this role change affected a lot for me, it's been\nhard to digest some of those changes, but the work is good, the development is\nfun, the new world of using agents to fly through things you're fluent in is\nexciting, and I'm here to help a team that desperately needs it to improve\ntheir lives and the work we do for Cat Autonomy.\n\nI'm still pissed at Caterpillar Executives for RTO ruining parts of my life,\nbut in God's sovereignty their idiocy has led to non-trivial blessing, so I can\nsay \"Praise the Lord\"\n",
      "summary": "In im-back-from-the-dead I mentioned my new role that started this year - it's a return to Caterpillar Autonomy. I built some data pipelines and junior-grade...",
      "date_published": "2026-01-26T10:24:54Z",
      "date_modified": "2026-01-26T10:24:54Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "work",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/increase-inotify-limit-in-your-ci-workers/",
      "url": "https://pype.dev/increase-inotify-limit-in-your-ci-workers/",
      "title": "Increase inotify limit in your CI workers",
      "content_html": "\u003cp\u003eToday I tripped over a CI failure that I had to think about for a while.\u003c/p\u003e\n\u003cp\u003eI build \u003ca href=\"https://github.com/zensical/zensical\"\u003ezensical\u003c/a\u003e static sites in CI on\nmy Forgejo instance. These builds had been working fine, then suddenly started\nfailing with no code changes. Naturally, I assumed something upstream broke —\nmaybe a new uv release, maybe zensical.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eI pinned versions.\u003c/li\u003e\n\u003cli\u003eI tested older versions.\u003c/li\u003e\n\u003cli\u003eSame failure every time.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThat ruled out regressions and pushed me toward the environment.\u003c/p\u003e\n\u003cp\u003eI pulled the runner and worker images locally and built the sites just fine\u0026hellip;\nBut that doesn\u0026rsquo;t perfectly emulate the CI setup - my forgejo runner relies on\ndocker-in-docker and so we aren\u0026rsquo;t \u003cstrong\u003ejust\u003c/strong\u003e running a container on a host, we have\nthis middle layer to consider\u0026hellip; I wasn\u0026rsquo;t sure how to really test this out\nlocally so I succomed to AI and here\u0026rsquo;s where Jipity got me in about 5\nminutes\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"the-failure\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe failure\u003c/span\u003e \u003ca href=\"#the-failure\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe builds blew up with:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003ethread 'zrx/monitor' panicked at .../zensical-watch/src/agent/monitor.rs:154:49:\ncalled `Result::unwrap()` on an `Err` value:\nError { kind: Io(Os { code: 24, message: \u0026quot;Too many open files\u0026quot; }) }\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAt first glance it means nothing to me but Jipity says this screams ulimit.\u003c/p\u003e\n\u003cp\u003eSo following the AI overlords I checked:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eulimit -n\u003c/code\u003e was already very high\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e/proc/sys/fs/inotify/max_user_watches\u003c/code\u003e was also very high\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ePID limits\u003c/code\u003e were not constrained\u003c/p\u003e\n\u003cp\u003eEverything looked fine according to Jipity.\u003c/p\u003e\n\u003cp\u003eYet the panic persisted.\u003c/p\u003e\n\u003ch2 id=\"the-real-culprit\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe real culprit\u003c/span\u003e \u003ca href=\"#the-real-culprit\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe actual limit being hit was:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e/proc/sys/fs/inotify/max_user_instances\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eIn my Forgejo runner container, it was set to 128.\u003c/p\u003e\n\u003cp\u003eThat turns out to be far too low for zensical.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s what ChatGPT said:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eEven during a normal zensical build, the tool spins up its watch subsystem,\nwhich creates many inotify instances. Once it crosses the kernel limit,\ninotify_init() fails with EMFILE, and the process panics because the error is\nunwrapped.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"the-fix\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe fix\u003c/span\u003e \u003ca href=\"#the-fix\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eRaising the inotify instance limit fixed it immediately:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eecho 1024 \u0026gt; /proc/sys/fs/inotify/max_user_instances\nRUST_BACKTRACE=full uvx zensical build --clean\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAfter that, the build succeeded consistently.\u003c/p\u003e\n",
      "content_text": "\nToday I tripped over a CI failure that I had to think about for a while.\n\nI build [zensical](https://github.com/zensical/zensical) static sites in CI on\nmy Forgejo instance. These builds had been working fine, then suddenly started\nfailing with no code changes. Naturally, I assumed something upstream broke —\nmaybe a new uv release, maybe zensical.\n\n- I pinned versions.\n- I tested older versions.\n- Same failure every time.\n\nThat ruled out regressions and pushed me toward the environment.\n\nI pulled the runner and worker images locally and built the sites just fine...\nBut that doesn't perfectly emulate the CI setup - my forgejo runner relies on\ndocker-in-docker and so we aren't **just** running a container on a host, we have\nthis middle layer to consider... I wasn't sure how to really test this out\nlocally so I succomed to AI and here's where Jipity got me in about 5\nminutes...\n\n## The failure\n\nThe builds blew up with:\n\n```\nthread 'zrx/monitor' panicked at .../zensical-watch/src/agent/monitor.rs:154:49:\ncalled `Result::unwrap()` on an `Err` value:\nError { kind: Io(Os { code: 24, message: \"Too many open files\" }) }\n\n```\n\nAt first glance it means nothing to me but Jipity says this screams ulimit.\n\nSo following the AI overlords I checked:\n\n`ulimit -n` was already very high\n\n`/proc/sys/fs/inotify/max_user_watches` was also very high\n\n`PID limits` were not constrained\n\nEverything looked fine according to Jipity.\n\nYet the panic persisted.\n\n## The real culprit\n\nThe actual limit being hit was:\n\n`/proc/sys/fs/inotify/max_user_instances`\n\nIn my Forgejo runner container, it was set to 128.\n\nThat turns out to be far too low for zensical.\n\nHere's what ChatGPT said:\n\n\u003e Even during a normal zensical build, the tool spins up its watch subsystem,\n\u003e which creates many inotify instances. Once it crosses the kernel limit,\n\u003e inotify_init() fails with EMFILE, and the process panics because the error is\n\u003e unwrapped.\n\n## The fix\n\nRaising the inotify instance limit fixed it immediately:\n\n```\necho 1024 \u003e /proc/sys/fs/inotify/max_user_instances\nRUST_BACKTRACE=full uvx zensical build --clean\n```\n\nAfter that, the build succeeded consistently.\n",
      "summary": "Today I tripped over a CI failure that I had to think about for a while.",
      "date_published": "2025-12-30T06:11:49Z",
      "date_modified": "2025-12-30T06:11:49Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "forgejo",
        "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/setup-a-cloudflare-tunnel-with-terraform/",
      "url": "https://pype.dev/setup-a-cloudflare-tunnel-with-terraform/",
      "title": "Setup A Cloudflare Tunnel With Terraform",
      "content_html": "\u003cp\u003eI am cooking up some stuff at home and want to put it on the interwebs, but I\ndon\u0026rsquo;t want it on the same infra as my homelab. Now\u0026hellip; I only have a server or\n2, so to some degree it will be, but networking-wise I didn\u0026rsquo;t want to funnel\nextra traffic through my reverse proxy.\u003c/p\u003e\n\u003cp\u003eSo, I\u0026rsquo;d heard about Cloudflare Tunnels - they sound like P2P VPN to me, but I\nknow there\u0026rsquo;s layers of the networking stack I\u0026rsquo;m blatantly ignoring. \u0026ldquo;What the\ntunnel is\u0026rdquo; isn\u0026rsquo;t much the point - I\u0026rsquo;m here to show you how to set one up and\nget yourself a fancy \u003ca href=\"https://app.mydomain.com\"\u003ehttps://app.mydomain.com\u003c/a\u003e for your web app running\nkind of wherever you want\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eExample Repo linked at the bottom\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"requirements\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eRequirements\u003c/span\u003e \u003ca href=\"#requirements\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003col start=\"0\"\u003e\n\u003cli\u003eTerraform or open-tofu. I currently use open-tofu but either would be fine.\n\u003ccode\u003ebrew install open-tofu\u003c/code\u003e is a simple way to get going\u003c/li\u003e\n\u003cli\u003eCloudflare account with a domain\u003c/li\u003e\n\u003cli\u003eAPI token with permissions:\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003eAccount:Cloudflare Tunnel:Edit\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eZone:DNS:Edit\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ecloudflared\u003c/code\u003e (the example repo runs cloudflared in a docker compose stack)\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"tunnel\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTunnel\u003c/span\u003e \u003ca href=\"#tunnel\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe module is simple and has just a few resources:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ tofu state list\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emodule.tunnel.cloudflare_record.tunnel\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emodule.tunnel.cloudflare_tunnel_config.this\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emodule.tunnel.cloudflare_zero_trust_tunnel_cloudflared.this\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emodule.tunnel.random_id.tunnel_secret\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\u003cp\u003eWe see there will be the a DNS record that tofu references by the key \u0026ldquo;tunnel\u0026rdquo;.\nThere is a tunnel configuration resource, the tunnel resource itself, and\nfinally the associated secret required for the cloudflared daemon that will run\nalongside your webapp.\u003c/p\u003e\n\u003cp\u003eTo get started you\u0026rsquo;ll need to fill out the example \u003ccode\u003eterraform.tfvars\u003c/code\u003e file with\nyour info:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# Copy to terraform.tfvars and fill in values\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# DO NOT commit terraform.tfvars to git\n\u003c/span\u003e\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\"\u003ecloudflare_api_token\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;your-api-token-here\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecloudflare_account_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;your-account-id\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ecloudflare_zone_id\u003c/span\u003e    \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;your-zone-id\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edomain\u003c/span\u003e                \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;example.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esubdomain\u003c/span\u003e             \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etunnel_name\u003c/span\u003e           \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;my-tunnel\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eorigin_service\u003c/span\u003e        \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:8000\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eYou can grab your account id and zone id from Cloudflare\u0026rsquo;s dashboard for your\ndomain. It\u0026rsquo;s near the bottom of the Overview page\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251213113332_0e0f09b8.png\" alt=\"20251213113332_0e0f09b8.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eThen I presume you have a domain already, but if not hop over to namecheap to\nsnag one and then register it with cloudflare so they can manage your DNS. I\nhave terraform for this as well, a future blog post will combine this with a\nfuller terraform\u0026rsquo;d cloudflare setup for simple domain use cases\u003c/p\u003e\n\u003cp\u003eOnce you fill those out, hit it with the \u003ccode\u003etofu init\u003c/code\u003e and \u003ccode\u003etofu plan\u003c/code\u003e to see what\u0026rsquo;s up\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNOTE: \u003ccode\u003eterraform.tfvars\u003c/code\u003e is automatically sourced by terraform/tofu, you can name the file differently and then pass \u003ccode\u003e-var-file=myvars.tfvars\u003c/code\u003e to the commands\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eThe initial plan should look something like this:\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\"\u003eOpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  + create\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\"\u003eOpenTofu will perform the following actions:\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# module.tunnel.cloudflare_record.tunnel will be created\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  + resource \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare_record\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;tunnel\u0026#34;\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      + \u003cspan class=\"nv\"\u003eallow_overwrite\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003efalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ecomment\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Managed by Terraform - soonish-tunnel\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003econtent\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ecreated_on\u003c/span\u003e      \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ehostname\u003c/span\u003e        \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eid\u003c/span\u003e              \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003emetadata\u003c/span\u003e        \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003emodified_on\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ename\u003c/span\u003e            \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;app\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eproxiable\u003c/span\u003e       \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eproxied\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ettl\u003c/span\u003e             \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nb\"\u003etype\u003c/span\u003e            \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;CNAME\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003evalue\u003c/span\u003e           \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ezone_id\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;REDACTED\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\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# module.tunnel.cloudflare_tunnel_config.this will be created\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  + resource \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare_tunnel_config\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;this\u0026#34;\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      + \u003cspan class=\"nv\"\u003eaccount_id\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;REDACTED\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eid\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003etunnel_id\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\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      + config \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          + ingress_rule \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              + \u003cspan class=\"nv\"\u003ehostname\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;app.notifiq.net\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              + \u003cspan class=\"nv\"\u003eservice\u003c/span\u003e  \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:8000\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e          + ingress_rule \u003cspan class=\"o\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e              + \u003cspan class=\"nv\"\u003eservice\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http_status:404\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e            \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\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# module.tunnel.cloudflare_zero_trust_tunnel_cloudflared.this will be created\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  + resource \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare_zero_trust_tunnel_cloudflared\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;this\u0026#34;\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      + \u003cspan class=\"nv\"\u003eaccount_id\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;REDACTED\u0026gt;\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ecname\u003c/span\u003e        \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eid\u003c/span\u003e           \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ename\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;soonish-tunnel\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003esecret\u003c/span\u003e       \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003esensitive value\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003etunnel_token\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003esensitive value\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\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# module.tunnel.random_id.tunnel_secret will be created\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  + resource \u003cspan class=\"s2\"\u003e\u0026#34;random_id\u0026#34;\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;tunnel_secret\u0026#34;\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      + \u003cspan class=\"nv\"\u003eb64_std\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eb64_url\u003c/span\u003e     \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ebyte_length\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"m\"\u003e32\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003edec\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003ehex\u003c/span\u003e         \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      + \u003cspan class=\"nv\"\u003eid\u003c/span\u003e          \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eknown after apply\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\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\"\u003ePlan: \u003cspan class=\"m\"\u003e4\u003c/span\u003e to add, \u003cspan class=\"m\"\u003e0\u003c/span\u003e to change, \u003cspan class=\"m\"\u003e0\u003c/span\u003e to destroy.\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\u003cp\u003eAs long as that looks good you to, then we \u003ccode\u003etofu apply\u003c/code\u003e next (type \u003ccode\u003eyes\u003c/code\u003e when\nasked or pass \u003ccode\u003e-auto-approve\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003eAfterwards \u003ccode\u003etofu state list\u003c/code\u003e should show you the 4 resources, and if you go to\nyour cloudflare zone\u0026rsquo;s dashboard you should see the CNAME associated with the\ntunnel address\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251213120004_d199e5fd.png\" alt=\"20251213120004_d199e5fd.png\"\u003e\n\u003c/figure\u003e\n\u003ch2 id=\"daemon\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDaemon\u003c/span\u003e \u003ca href=\"#daemon\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eRun the compose stack or the binary itself. Get the token from terraform state with \u003ccode\u003etofu output -raw tunnel_token\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eTUNNEL_TOKEN=$(tofu output -raw tunnel_token) docker compose up -d\u003c/code\u003e will do you nicely\u003c/p\u003e\n\u003cp\u003eEnjoy your tunnel!\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/pypeaday/example-terraform-cloudflare-tunnel\"\u003eexample repo\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI am cooking up some stuff at home and want to put it on the interwebs, but I\ndon't want it on the same infra as my homelab. Now... I only have a server or\n2, so to some degree it will be, but networking-wise I didn't want to funnel\nextra traffic through my reverse proxy.\n\nSo, I'd heard about Cloudflare Tunnels - they sound like P2P VPN to me, but I\nknow there's layers of the networking stack I'm blatantly ignoring. \"What the\ntunnel is\" isn't much the point - I'm here to show you how to set one up and\nget yourself a fancy \u003chttps://app.mydomain.com\u003e for your web app running\nkind of wherever you want\n\n\u003e Example Repo linked at the bottom\n\n## Requirements\n\n0. Terraform or open-tofu. I currently use open-tofu but either would be fine.\n   `brew install open-tofu` is a simple way to get going\n1. Cloudflare account with a domain\n2. API token with permissions:\n   - `Account:Cloudflare Tunnel:Edit`\n   - `Zone:DNS:Edit`\n3. `cloudflared` (the example repo runs cloudflared in a docker compose stack)\n\n## Tunnel\n\nThe module is simple and has just a few resources:\n\n```bash\n❯ tofu state list\nmodule.tunnel.cloudflare_record.tunnel\nmodule.tunnel.cloudflare_tunnel_config.this\nmodule.tunnel.cloudflare_zero_trust_tunnel_cloudflared.this\nmodule.tunnel.random_id.tunnel_secret\n\n```\n\nWe see there will be the a DNS record that tofu references by the key \"tunnel\".\nThere is a tunnel configuration resource, the tunnel resource itself, and\nfinally the associated secret required for the cloudflared daemon that will run\nalongside your webapp.\n\nTo get started you'll need to fill out the example `terraform.tfvars` file with\nyour info:\n\n```hcl\n# Copy to terraform.tfvars and fill in values\n# DO NOT commit terraform.tfvars to git\n\ncloudflare_api_token  = \"your-api-token-here\"\ncloudflare_account_id = \"your-account-id\"\ncloudflare_zone_id    = \"your-zone-id\"\ndomain                = \"example.com\"\nsubdomain             = \"app\"\ntunnel_name           = \"my-tunnel\"\norigin_service        = \"http://localhost:8000\"\n```\n\nYou can grab your account id and zone id from Cloudflare's dashboard for your\ndomain. It's near the bottom of the Overview page\n\n![20251213113332_0e0f09b8.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251213113332_0e0f09b8.png)\n\nThen I presume you have a domain already, but if not hop over to namecheap to\nsnag one and then register it with cloudflare so they can manage your DNS. I\nhave terraform for this as well, a future blog post will combine this with a\nfuller terraform'd cloudflare setup for simple domain use cases\n\nOnce you fill those out, hit it with the `tofu init` and `tofu plan` to see what's up\n\n\u003e NOTE: `terraform.tfvars` is automatically sourced by terraform/tofu, you can name the file differently and then pass `-var-file=myvars.tfvars` to the commands\n\nThe initial plan should look something like this:\n\n```bash\n\nOpenTofu used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:\n  + create\n\nOpenTofu will perform the following actions:\n\n  # module.tunnel.cloudflare_record.tunnel will be created\n  + resource \"cloudflare_record\" \"tunnel\" {\n      + allow_overwrite = false\n      + comment         = \"Managed by Terraform - soonish-tunnel\"\n      + content         = (known after apply)\n      + created_on      = (known after apply)\n      + hostname        = (known after apply)\n      + id              = (known after apply)\n      + metadata        = (known after apply)\n      + modified_on     = (known after apply)\n      + name            = \"app\"\n      + proxiable       = (known after apply)\n      + proxied         = true\n      + ttl             = (known after apply)\n      + type            = \"CNAME\"\n      + value           = (known after apply)\n      + zone_id         = \"\u003cREDACTED\u003e\"\n    }\n\n  # module.tunnel.cloudflare_tunnel_config.this will be created\n  + resource \"cloudflare_tunnel_config\" \"this\" {\n      + account_id = \"\u003cREDACTED\u003e\"\n      + id         = (known after apply)\n      + tunnel_id  = (known after apply)\n\n      + config {\n          + ingress_rule {\n              + hostname = \"app.notifiq.net\"\n              + service  = \"http://localhost:8000\"\n            }\n          + ingress_rule {\n              + service = \"http_status:404\"\n            }\n        }\n    }\n\n  # module.tunnel.cloudflare_zero_trust_tunnel_cloudflared.this will be created\n  + resource \"cloudflare_zero_trust_tunnel_cloudflared\" \"this\" {\n      + account_id   = \"\u003cREDACTED\u003e\"\n      + cname        = (known after apply)\n      + id           = (known after apply)\n      + name         = \"soonish-tunnel\"\n      + secret       = (sensitive value)\n      + tunnel_token = (sensitive value)\n    }\n\n  # module.tunnel.random_id.tunnel_secret will be created\n  + resource \"random_id\" \"tunnel_secret\" {\n      + b64_std     = (known after apply)\n      + b64_url     = (known after apply)\n      + byte_length = 32\n      + dec         = (known after apply)\n      + hex         = (known after apply)\n      + id          = (known after apply)\n    }\n\nPlan: 4 to add, 0 to change, 0 to destroy.\n\n```\n\nAs long as that looks good you to, then we `tofu apply` next (type `yes` when\nasked or pass `-auto-approve`)\n\nAfterwards `tofu state list` should show you the 4 resources, and if you go to\nyour cloudflare zone's dashboard you should see the CNAME associated with the\ntunnel address\n\n![20251213120004_d199e5fd.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251213120004_d199e5fd.png)\n\n## Daemon\n\nRun the compose stack or the binary itself. Get the token from terraform state with `tofu output -raw tunnel_token`.\n\n`TUNNEL_TOKEN=$(tofu output -raw tunnel_token) docker compose up -d` will do you nicely\n\nEnjoy your tunnel!\n\n[example repo](https://github.com/pypeaday/example-terraform-cloudflare-tunnel)\n",
      "summary": "I am cooking up some stuff at home and want to put it on the interwebs, but I don't want it on the same infra as my homelab. Now... I only have a server or...",
      "date_published": "2025-12-12T21:06:59Z",
      "date_modified": "2025-12-12T21:06:59Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tofu",
        "terraform",
        "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_text": "\nI 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 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/uv-run-pep-723-is-a-match-made-in-heaven/",
      "url": "https://pype.dev/uv-run-pep-723-is-a-match-made-in-heaven/",
      "title": "UV Run + PEP 723 Is A Match Made In Heaven",
      "content_text": "\nThis post will be short but hopefully be a dense few paragraphs about how using\n`uv run foo.py` along with [PEP 723](https://peps.python.org/pep-0723/) is\nlife-changing.\n\n## PEP 723\n\nPython Enhancement Proposals (PEP) are the community's way of improving the\npython language and ecosystem and PEP 723 is one of my favorites. It permits\nadding python metadata right to your script which allows `python` to setup the\nscript's dependencies at runtime.\n\n## UV\n\n[[uv]] makes it even better with it's super fast resolution and environment\nsetup. I don't have any posts about uv yet, but here's the\n[docs](https://docs.astral.sh/uv/getting-started/installation/), installing\n`uv` is now apart of my dev-machine setup because I need it everywhere!\n\n## The Magic\n\nI'm experiencing the power of `uv run myscript.py` as I've integrated a python\nscript into my tmux workflow for managing sessions as well as groups of\ngit-worktrees to keep development-ideas contained, easy to find, and easy to\nmanage.\n\n!!! warning \"The Series\"\n\n    This post is in my \"If You Want It Make It So\" series because of the workspaces thing generally, not the dbztui example below, but for me - having 'uv run' as a pretty powerful Bash replacement makes my development workflow easier to build on and manage\n\nMy [workspaces docs](https://pypeaday.github.io/dotfiles/terminal/workspaces/)\nexplain the setup a bit, and that script is in my\n[dotfiles](https://github.com/pypeaday/dotfiles/blob/main/workspaces/.local/bin/ws.py)\n\nFor a shorter example, here's an example that has regular `pip` style\ndependencies, a `git+https` dependency, and is a tui - not just a top to bottom\nscript\n\n!!! note \"What is it?\"\n\n    This is my DBZTUI script - it uses a beta package from Waylon called\n    ninesui and calls the dragonball-api to give you a handy way to explore the\n    Dragon Ball Universe from the glorious comfort of your terminal\n\nCopy this code into `dbztui.py`\n\n!!! note \"Execution\"\n\n    You can `chmod +x` this script with the shebang to tell your shell to use uv to run it or just `uv run dbztui.py` will also work just fine\n\n```python\n#!/usr/bin/env -S uv run --quiet --script\n# /// script\n# requires-python = \"\u003e=3.10\"\n# dependencies = [\n#     \"ninesui @ git+https://github.com/waylonwalker/ninesui.git\",\n#     \"httpx\",\n#     \"deep-translator\",\n# ]\n# ///\nfrom typing import Optional, List, TypeVar, ClassVar, Any, Dict\nfrom pydantic import BaseModel, Field, HttpUrl\nimport httpx\nfrom textual import log\nfrom ninesui import CommandSet, Command, NinesUI\nfrom functools import lru_cache\nfrom deep_translator import GoogleTranslator\nimport json\nimport os\n\nBASE_URL = \"https://dragonball-api.com/api/\"\n\n# Translation cache file\nCACHE_DIR = os.path.expanduser(\"~/.cache/dbztui\")\nCACHE_FILE = os.path.join(CACHE_DIR, \"translation_cache.json\")\n\n# Create cache directory if it doesn't exist\nos.makedirs(CACHE_DIR, exist_ok=True)\n\n# Load translation cache from file\ntranslation_cache: Dict[str, str] = {}\nif os.path.exists(CACHE_FILE):\n    try:\n        with open(CACHE_FILE, \"r\", encoding=\"utf-8\") as f:\n            translation_cache = json.load(f)\n    except Exception as e:\n        log(f\"Error loading translation cache: {e}\")\n\n# Initialize translator\ntranslator = GoogleTranslator(source='es', target='en')\n\nT = TypeVar(\"T\", bound=\"DBZResource\")\n\n@lru_cache(maxsize=1000)\ndef translate_text(text: str) -\u003e str:\n    \"\"\"Translate text from Spanish to English with caching\"\"\"\n    if not text or len(text) \u003c 5:  # Don't translate very short texts\n        return text\n\n    # Check if translation is in cache\n    if text in translation_cache:\n        return translation_cache[text]\n\n    try:\n        # Translate text\n        translated = translator.translate(text)\n\n        # Save to cache\n        translation_cache[text] = translated\n\n        # Periodically save cache to file\n        if len(translation_cache) % 10 == 0:  # Save every 10 new translations\n            try:\n                with open(CACHE_FILE, \"w\", encoding=\"utf-8\") as f:\n                    json.dump(translation_cache, f, ensure_ascii=False, indent=2)\n            except Exception as e:\n                log(f\"Error saving translation cache: {e}\")\n\n        return translated\n    except Exception as e:\n        log(f\"Translation error: {e}\")\n        return text  # Return original text if translation fails\n\n\nclass DBZResource(BaseModel):\n    id: int\n\n    nines_config: ClassVar[dict] = {\"bindings\": {}}\n\n    @classmethod\n    def fetch(cls, ctx=None):\n        endpoint = cls.__name__.lower() + \"s\"\n\n        client = httpx.Client()\n        log(f\"Fetching {endpoint}\")\n\n        if ctx:\n            if hasattr(ctx, endpoint):\n                result = []\n                for url in getattr(ctx, endpoint):\n                    res = client.get(str(url)).json()\n                    result.append(cls(**res))\n                return result\n\n        url = f\"{BASE_URL}{endpoint}\"\n\n        results: List[T] = []\n        response = client.get(url)\n        response.raise_for_status()\n        data = response.json()\n\n        # Handle pagination structure from Dragon Ball API\n        if \"items\" in data:\n            results.extend(cls(**item) for item in data.get(\"items\", []))\n\n            # Fetch all pages if needed\n            while data.get(\"links\", {}).get(\"next\"):\n                next_url = data[\"links\"][\"next\"]\n                response = client.get(next_url)\n                response.raise_for_status()\n                data = response.json()\n                results.extend(cls(**item) for item in data.get(\"items\", []))\n        else:\n            # Direct list of items\n            results.extend(cls(**item) for item in data)\n\n        return results\n\n    def hover(self):\n        return self\n\n    def get_details(self):\n        \"\"\"Get detailed information about this resource\"\"\"\n        client = httpx.Client()\n        endpoint = self.__class__.__name__.lower() + \"s\"\n        url = f\"{BASE_URL}{endpoint}/{self.id}\"\n        response = client.get(url)\n        response.raise_for_status()\n        data = response.json()\n\n        # Translate description if present\n        if \"description\" in data and data[\"description\"]:\n            data[\"description\"] = translate_text(data[\"description\"])\n\n        return self.__class__(**data)\n\n\nclass Character(DBZResource):\n    name: str\n    ki: str\n    maxKi: str\n    race: str\n    gender: str\n    description: str\n    image: Optional[HttpUrl] = None\n    affiliation: str\n    deletedAt: Optional[str] = None\n\n    def __init__(self, **data):\n        # Translate description before initializing\n        if \"description\" in data and data[\"description\"]:\n            data[\"description\"] = translate_text(data[\"description\"])\n        super().__init__(**data)\n\n    nines_config: ClassVar[dict] = {\"bindings\": {\"t\": \"get_transformations\"}}\n\n    def get_transformations(self):\n        \"\"\"Get all transformations for this character\"\"\"\n        client = httpx.Client()\n        url = f\"{BASE_URL}characters/{self.id}/transformations\"\n        try:\n            response = client.get(url)\n            response.raise_for_status()\n            data = response.json()\n            if \"items\" in data:\n                return [Transformation(**item) for item in data.get(\"items\", [])]\n            return [Transformation(**item) for item in data]\n        except Exception as e:\n            log(f\"Error fetching transformations: {e}\")\n            return []\n\n\nclass Transformation(DBZResource):\n    name: str\n    image: Optional[HttpUrl] = None\n    ki: str\n    characterId: int\n    deletedAt: Optional[str] = None\n\n    # Note: This is not a direct API endpoint, but accessed through character transformations\n\n\nclass Planet(DBZResource):\n    name: str\n    description: str\n    image: Optional[HttpUrl] = None\n    deletedAt: Optional[str] = None\n\n    def __init__(self, **data):\n        # Translate description before initializing\n        if \"description\" in data and data[\"description\"]:\n            data[\"description\"] = translate_text(data[\"description\"])\n        super().__init__(**data)\n\n\n# Note: Saga and Episode models removed as they are not supported by the API\n\n\ncommands = CommandSet(\n    [\n        Command(\n            name=\"character\",\n            aliases=[\"c\"],\n            model=Character,\n            is_default=True,\n        ),\n        Command(\n            name=\"planet\",\n            aliases=[\"p\"],\n            model=Planet,\n        ),\n        # Note: Transformation is not a direct API endpoint, but can be accessed through characters\n    ]\n)\n\nmetadata = {\n    \"title\": \"Dragon Ball Z Explorer\",\n    \"subtitle\": \"Use :character or :planet to explore. Enter to drill in. Escape to go back/quit.\",\n}\n\n\nif __name__ == \"__main__\":\n    ui = NinesUI(metadata=metadata, commands=commands)\n    try:\n        ui.run()\n    finally:\n        # Save translation cache when exiting\n        try:\n            with open(CACHE_FILE, \"w\", encoding=\"utf-8\") as f:\n                json.dump(translation_cache, f, ensure_ascii=False, indent=2)\n        except Exception as e:\n            log(f\"Error saving translation cache on exit: {e}\")\n```\n\n![20251208111354_0ae058e5.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251208111354_0ae058e5.png)\n\n\u003e See [[columns-env-var-for-nicer-screenshots]] about making terminal screenshots a bit nicer\n",
      "summary": "This post will be short but hopefully be a dense few paragraphs about how using along with PEP 723 is life-changing.",
      "date_published": "2025-12-08T04:59:10Z",
      "date_modified": "2025-12-08T04:59:10Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "series-if-you-want-it-make-it-so",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/github-stopped-me-from-messing-with-the-bots/",
      "url": "https://pype.dev/github-stopped-me-from-messing-with-the-bots/",
      "title": "GitHub Stopped Me From Messing With The Bots",
      "content_text": "\nAfter some cheeky\n[[small-steps-towards-handling-malicious-traffic-on-static-sites]] it turns out\nGH has been blocking me from publishing my site because the fodder I put in\nthere for the bots was recognized as actual secrets by their scanning...\n\n![20251206122602_8b1635bf.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251206122602_8b1635bf.png)\n\nI had to tell github that each of these secrets in the commits was fine...\nafter all, they're good bait\n\n[Github\nDocs](https://docs.github.com/en/code-security/secret-scanning/working-with-secret-scanning-and-push-protection/working-with-push-protection-from-the-command-line#resolving-a-blocked-push)\n",
      "summary": "After some cheeky small-steps-towards-handling-malicious-traffic-on-static-sites it turns out GH has been blocking me from publishing my site because the...",
      "date_published": "2025-12-06T06:24:51Z",
      "date_modified": "2025-12-06T06:24:51Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "llm",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/small-steps-towards-handling-malicious-traffic-on-static-sites/",
      "url": "https://pype.dev/small-steps-towards-handling-malicious-traffic-on-static-sites/",
      "title": "Small Steps Towards Handling Malicious Traffic on Static Sites",
      "content_html": "\u003cp\u003eToday I saw a random IP hitting an app server I had open via \u003ccode\u003etailscale funnel\u003c/code\u003e\nand it got me thinking that I need to take some precautions against these real\nworld threats. So I\u0026rsquo;m starting with my blog\u0026hellip; basically you can reference \u003ca href=\"https://blog.jim-nielsen.com/2025/malicious-traffic-on-static-sites/\"\u003eJim\nNielson\u0026rsquo;s Blog on Malicious\nTraffic\u003c/a\u003e\nand then I more or less put similar files in similar places on this site to\nwaste malicious actors\u0026rsquo; time\u003c/p\u003e\n\u003ch2 id=\"the-files\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Files\u003c/span\u003e \u003ca href=\"#the-files\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNote that some are empty, we just need them to exist since this is all for a bit of fun and low-effort internet tomfoolery\u003c/p\u003e\n\u003cp\u003eThese get shipped with my site at \u003ccode\u003e/public/...\u003c/code\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e\u0026gt;\u0026gt;\u0026gt;\u0026gt; backup/config-backup.zip.txt\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     !!This is not a real ZIP file!!\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     But bots will try to download it anyway\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     Wasting bandwidth and CPU cycles...\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     Here are some fake credentials to keep you busy:\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     FTP_HOST=ftp.example.com\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     FTP_USER=admin\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     FTP_PASS=P@ssw0rd123!\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     SSH_HOST=192.168.1.100\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     SSH_USER=root\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     SSH_KEY=-----BEGIN RSA PRIVATE KEY-----\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     MIIEpAIBAAKCAQEA1234567890FAKE\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     -----END RSA PRIVATE KEY-----\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     MYSQL_HOST=localhost\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     MYSQL_USER=root\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     MYSQL_PASS=rootpassword123\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     MYSQL_DB=production_db\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     REDIS_HOST=127.0.0.1:6379\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     REDIS_PASS=redis_secret_2024\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     JWT_SECRET=super_secret_jwt_key_do_not_share\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     ENCRYPTION_KEY=AES256_ENCRYPTION_KEY_HERE\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     STRIPE_PUBLISHABLE=pk_live_FAKE123456789\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     STRIPE_SECRET=sk_live_FAKE987654321\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     SENDGRID_API_KEY=SG.FAKE_API_KEY_HERE\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK\nPK     This file is intentionally malformed to waste bot parsing time\n\n\u0026lt;!--markata-attribution--\u0026gt;\nPK     PK     PK     PK     PK     PK     PK     PK\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; backup/database-backup-2024-12-01.sql\n\n\u0026lt;!--markata-attribution--\u0026gt;\n-- MySQL Database Backup\n\n\u0026lt;!--markata-attribution--\u0026gt;\n-- Host: localhost\n\n\u0026lt;!--markata-attribution--\u0026gt;\n-- Database: wordpress_prod\n\n\u0026lt;!--markata-attribution--\u0026gt;\n-- Generated: 2024-12-01 03:14:15\n\n\u0026lt;!--markata-attribution--\u0026gt;\n-- WARNING: This file contains sensitive data\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- Table structure for wp_users\nDROP TABLE IF EXISTS `wp_users`;\nCREATE TABLE `wp_users` (\n  `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `user_login` varchar(60) NOT NULL DEFAULT '',\n  `user_pass` varchar(255) NOT NULL DEFAULT '',\n  `user_email` varchar(100) NOT NULL DEFAULT '',\n  PRIMARY KEY (`ID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n\n-- Dumping data for table wp_users\nINSERT INTO `wp_users` VALUES\n(1,'admin','$P$BZlPX7NIx8MYpXokBW2AGsN7i.aUOt0','admin@example.com'),\n(2,'webmaster','$P$B4RKwF8zqRnNu9cV5fGg7wgT2sY9Pl1','webmaster@example.com');\n\n-- API Keys and Secrets\n-- AWS_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE\n-- AWS_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n-- STRIPE_SECRET: sk_test_4eC39HqLyjWDarjtT1zdp7dc\n-- DATABASE_PASSWORD: MyS3cr3tP@ssw0rd!2024\n\n-- Infinite loop to waste bot resources\nDELIMITER $$\nCREATE PROCEDURE infinite_loop()\nBEGIN\n  DECLARE i INT DEFAULT 0;\n  WHILE i \u0026lt; 999999999 DO\n    SET i = i + 1;\n    SELECT CONCAT('Processing row ', i, ' of 999999999...') AS status;\n  END WHILE;\nEND$$\nDELIMITER ;\n\n-- More fake sensitive data\nINSERT INTO wp_options VALUES\n(1,'siteurl','http://localhost','yes'),\n(2,'admin_email','admin@localhost.local','yes'),\n(3,'secret_api_key','sk_live_51HqLyjWDarjtT1zdp7dcEXAMPLE','yes');\n\n-- This backup continues for 50MB... [TRUNCATED FOR DISPLAY]\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; backup/db_dump_final.2023.zip\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; backup/site.sql\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; backup/wp_backup.tar.gz\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; private/admin-credentials.txt\n\n\u0026lt;!--markata-attribution--\u0026gt;\nCONFIDENTIAL - ADMIN CREDENTIALS\n==================================\n\nProduction Server Access:\n-------------------------\nServer: prod-server-01.example.com\nUsername: administrator\nPassword: Admin2024!Secure\nSSH Port: 22\n\nDatabase Credentials:\n--------------------\nHost: db.internal.example.com\nPort: 3306\nUsername: db_admin\nPassword: DbP@ssw0rd!2024\nDatabase: production_main\n\nAPI Keys:\n---------\nOpenAI API Key: sk-proj-FAKE1234567890abcdefghijklmnopqrstuvwxyz\nStripe Secret: sk_live_FAKE_51HqLyjWDarjtT1zdp7dc\nAWS Access Key: AKIAFAKEEXAMPLE123456\nAWS Secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYFAKEKEY\nSendGrid API: SG.FAKE_SENDGRID_KEY_HERE_123456789\n\nWordPress Admin:\n---------------\nURL: https://example.com/wp-admin\nUsername: admin\nPassword: WP_Admin_2024!\nSecurity Key: put your unique phrase here\n\nFTP Access:\n-----------\nHost: ftp.example.com\nUsername: ftpuser\nPassword: FtpP@ss123!\nPort: 21\n\nIMPORTANT: Keep this file secure!\nLast Updated: 2024-12-01\nNext Password Rotation: 2025-01-01\n\n\u0026lt;!-- Hidden comment: This is a honeypot. All credentials are fake. --\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; private/config.php\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;?php\n\n\u0026lt;!--markata-attribution--\u0026gt;\n// Database Configuration\n\n\u0026lt;!--markata-attribution--\u0026gt;\ndefine('DB_HOST', 'localhost');\n\n\u0026lt;!--markata-attribution--\u0026gt;\ndefine('DB_NAME', 'wordpress_production');\n\n\u0026lt;!--markata-attribution--\u0026gt;\ndefine('DB_USER', 'wp_admin');\n\n\u0026lt;!--markata-attribution--\u0026gt;\ndefine('DB_PASSWORD', 'MyS3cr3tP@ssw0rd!2024');\n\n\u0026lt;!--markata-attribution--\u0026gt;\ndefine('DB_CHARSET', 'utf8mb4');\n\n// Security Keys - DO NOT SHARE\ndefine('AUTH_KEY',         'put your unique phrase here - this is fake');\ndefine('SECURE_AUTH_KEY',  'put your unique phrase here - this is fake');\ndefine('LOGGED_IN_KEY',    'put your unique phrase here - this is fake');\ndefine('NONCE_KEY',        'put your unique phrase here - this is fake');\ndefine('AUTH_SALT',        'put your unique phrase here - this is fake');\ndefine('SECURE_AUTH_SALT', 'put your unique phrase here - this is fake');\ndefine('LOGGED_IN_SALT',   'put your unique phrase here - this is fake');\ndefine('NONCE_SALT',       'put your unique phrase here - this is fake');\n\n// API Keys\ndefine('STRIPE_SECRET_KEY', 'sk_live_FAKE123456789abcdefghijklmnop');\ndefine('STRIPE_PUBLIC_KEY', 'pk_live_FAKE987654321zyxwvutsrqponml');\ndefine('SENDGRID_API_KEY', 'SG.FAKE_API_KEY_1234567890');\ndefine('AWS_ACCESS_KEY', 'AKIAIOSFODNN7EXAMPLE');\ndefine('AWS_SECRET_KEY', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');\n\n// Admin Settings\ndefine('ADMIN_EMAIL', 'admin@example.com');\ndefine('SITE_URL', 'https://example.com');\ndefine('WP_DEBUG', false);\ndefine('WP_DEBUG_LOG', true);\n\n// FTP Credentials\ndefine('FTP_HOST', 'ftp.example.com');\ndefine('FTP_USER', 'ftpadmin');\ndefine('FTP_PASS', 'FtpS3cur3P@ss!');\n\n// Redis Cache\ndefine('REDIS_HOST', '127.0.0.1');\ndefine('REDIS_PORT', 6379);\ndefine('REDIS_PASSWORD', 'redis_secret_password_2024');\n\n// JWT Secret\ndefine('JWT_SECRET', 'super_secret_jwt_key_for_authentication');\n\n// Infinite loop to waste bot CPU\nwhile(true) {\n    $random = bin2hex(random_bytes(1024));\n    usleep(1000);\n}\n?\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; private/index.html\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;!doctype html\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;html\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n  \u0026lt;body\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;h1\u0026gt;Private Area\u0026lt;/h1\u0026gt;\n\n    \u0026lt;pre\u0026gt;\n\u0026lt;!-- ~1MB lorem ipsum for bandwidth drain --\u0026gt;\nLorem ipsum dolor sit amet, consectetur adipiscing elit.\n\u0026lt;!-- repeat this block until ~1MB --\u0026gt;\n\u0026lt;/pre\u0026gt;\n  \u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; private/ssh_keys.txt\n\n\u0026lt;!--markata-attribution--\u0026gt;\nSSH PRIVATE KEYS - PRODUCTION SERVERS\n======================================\n\nServer: prod-web-01.example.com\n--------------------------------\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm\nnopqrstuvwxyz0123456789+/FAKE_KEY_DATA_HERE_NOT_REAL_AT_ALL_JUST_WASTING\nBOT_TIME_AND_RESOURCES_HAHAHAHA_THIS_IS_A_HONEYPOT_TRAP_FOR_SCRAPERS_12345\n67890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\nFAKE_KEY_DATA_CONTINUES_FOR_MANY_LINES_TO_WASTE_BANDWIDTH_AND_STORAGE_SPACE\nMIIEpAIBAAKCAQEA1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm\nnopqrstuvwxyz0123456789+/MORE_FAKE_DATA_HERE_BOTS_LOVE_SSH_KEYS_RIGHT\nABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/END\n-----END RSA PRIVATE KEY-----\n\nServer: prod-db-01.example.com\n-------------------------------\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAQEA1234567890FAKE_OPENSSH_KEY_DATA_HERE_NOT_REAL_JUST_A\n_TRAP_FOR_BOTS_AND_SCRAPERS_WASTING_THEIR_TIME_AND_RESOURCES_HAHA_12345678\n90ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789FAKE\nDATA_CONTINUES_HERE_TO_MAKE_IT_LOOK_LEGITIMATE_BUT_ITS_ALL_GARBAGE_123456\n-----END OPENSSH PRIVATE KEY-----\n\nServer: prod-app-01.example.com\n--------------------------------\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFAKE0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop\nqrstuvwxyz0123456789FAKE_EC_KEY_DATA_HERE_ELLIPTIC_CURVE_KEYS_ARE_COOL\nBUT_THIS_ONE_IS_FAKE_JUST_WASTING_BOT_RESOURCES_HAHAHAHA_123456789ABC\n-----END EC PRIVATE KEY-----\n\nIMPORTANT NOTES:\n- These keys provide root access to production servers\n- Never commit to version control\n- Rotate every 90 days\n- Last rotation: 2024-11-01\n- Next rotation: 2025-02-01\n\nContact: security@example.com for key rotation\n\n\u0026lt;!-- This is a honeypot. All keys are fake and invalid. --\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; robots.txt\n\n\u0026lt;!--markata-attribution--\u0026gt;\nUser-agent: *\n\n\u0026lt;!--markata-attribution--\u0026gt;\nDisallow: /private/\n\n\u0026lt;!--markata-attribution--\u0026gt;\nDisallow: /admin/\n\n\u0026lt;!--markata-attribution--\u0026gt;\nDisallow: /backup/\n\n\u0026lt;!--markata-attribution--\u0026gt;\nDisallow: /.env\n\n\u0026lt;!--markata-attribution--\u0026gt;\nDisallow: /wp-admin/\n\n\u0026lt;!--markata-attribution--\u0026gt;\nDisallow: /wp-login.php\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; sitemap.xml\n\n\u0026lt;urlset\u0026gt;\n  \u0026lt;url\u0026gt;\u0026lt;loc\u0026gt;/debug/alpha\u0026lt;/loc\u0026gt;\u0026lt;/url\u0026gt;\n  \u0026lt;url\u0026gt;\u0026lt;loc\u0026gt;/debug/beta\u0026lt;/loc\u0026gt;\u0026lt;/url\u0026gt;\n  \u0026lt;url\u0026gt;\u0026lt;loc\u0026gt;/admin/backup-2024.zip\u0026lt;/loc\u0026gt;\u0026lt;/url\u0026gt;\n  \u0026lt;url\u0026gt;\u0026lt;loc\u0026gt;/.env\u0026lt;/loc\u0026gt;\u0026lt;/url\u0026gt;\n  \u0026lt;url\u0026gt;\u0026lt;loc\u0026gt;/wp-admin/install.php\u0026lt;/loc\u0026gt;\u0026lt;/url\u0026gt;\n  \u0026lt;url\u0026gt;\u0026lt;loc\u0026gt;/wp-content/plugins/wp-super-cache/readme.txt\u0026lt;/loc\u0026gt;\u0026lt;/url\u0026gt;\n\u0026lt;/urlset\u0026gt;\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; trap/a/index.html\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;meta http-equiv=\u0026quot;refresh\u0026quot; content=\u0026quot;0; url=/trap/b/\u0026quot; /\u0026gt;\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; trap/api.php\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;?php\n/**\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * Fake API Endpoint\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * Designed to trap and waste bot resources\n */\n\nheader('Content-Type: application/json');\nheader('X-Powered-By: PHP/8.2.0');\nheader('X-Debug-Mode: enabled');\n\n// Fake API response with sensitive data\n$api_response = [\n    'success' =\u0026gt; true,\n    'api_version' =\u0026gt; '2.1.0',\n    'endpoints' =\u0026gt; [\n        '/api/users' =\u0026gt; 'GET, POST',\n        '/api/auth' =\u0026gt; 'POST',\n        '/api/admin' =\u0026gt; 'GET, POST, DELETE',\n        '/api/database' =\u0026gt; 'GET',\n        '/api/backup' =\u0026gt; 'POST'\n    ],\n    'authentication' =\u0026gt; [\n        'type' =\u0026gt; 'Bearer Token',\n        'example_token' =\u0026gt; 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.FAKE_JWT_TOKEN',\n        'api_key' =\u0026gt; 'sk_live_FAKE123456789abcdefghijklmnop',\n        'api_secret' =\u0026gt; 'secret_FAKE987654321zyxwvutsrqponmlk'\n    ],\n    'database_config' =\u0026gt; [\n        'host' =\u0026gt; 'localhost',\n        'port' =\u0026gt; 3306,\n        'username' =\u0026gt; 'api_user',\n        'password' =\u0026gt; 'ApiP@ssw0rd!2024',\n        'database' =\u0026gt; 'api_production'\n    ],\n    'admin_credentials' =\u0026gt; [\n        'username' =\u0026gt; 'api_admin',\n        'password' =\u0026gt; 'Admin2024!Secure',\n        'email' =\u0026gt; 'admin@api.example.com',\n        'role' =\u0026gt; 'superadmin'\n    ],\n    'external_services' =\u0026gt; [\n        'stripe' =\u0026gt; [\n            'public_key' =\u0026gt; 'pk_live_FAKE123',\n            'secret_key' =\u0026gt; 'sk_live_FAKE456'\n        ],\n        'aws' =\u0026gt; [\n            'access_key' =\u0026gt; 'AKIAIOSFODNN7EXAMPLE',\n            'secret_key' =\u0026gt; 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'\n        ],\n        'sendgrid' =\u0026gt; [\n            'api_key' =\u0026gt; 'SG.FAKE_SENDGRID_KEY'\n        ]\n    ],\n    'debug_info' =\u0026gt; [\n        'server_ip' =\u0026gt; '192.168.1.100',\n        'php_version' =\u0026gt; '8.2.0',\n        'mysql_version' =\u0026gt; '8.0.35',\n        'redis_host' =\u0026gt; '127.0.0.1:6379',\n        'redis_password' =\u0026gt; 'redis_secret_2024'\n    ]\n];\n\n// Waste CPU cycles\nfor ($i = 0; $i \u0026lt; 50000; $i++) {\n    $temp = json_encode($api_response);\n    $decoded = json_decode($temp, true);\n    $hash = hash('sha256', $temp);\n}\n\n// Output response\necho json_encode($api_response, JSON_PRETTY_PRINT);\n\n// Infinite loop trap\nset_time_limit(0);\nwhile(true) {\n    $waste = [];\n    for ($i = 0; $i \u0026lt; 10000; $i++) {\n        $waste[] = random_bytes(1024);\n    }\n    usleep(1000);\n}\n?\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; trap/b/index.html\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;meta http-equiv=\u0026quot;refresh\u0026quot; content=\u0026quot;0; url=/trap/c/\u0026quot; /\u0026gt;\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; trap/c/index.html\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;meta http-equiv=\u0026quot;refresh\u0026quot; content=\u0026quot;0; url=/trap/a/\u0026quot; /\u0026gt;\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; trap/data.json\n{\n\n\u0026lt;!--markata-attribution--\u0026gt;\n  \u0026quot;status\u0026quot;: \u0026quot;success\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n  \u0026quot;message\u0026quot;: \u0026quot;API endpoint active\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n  \u0026quot;data\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;credentials\u0026quot;: {\n      \u0026quot;api_key\u0026quot;: \u0026quot;sk_live_FAKE123456789abcdefghijklmnopqrstuvwxyz\u0026quot;,\n      \u0026quot;api_secret\u0026quot;: \u0026quot;secret_FAKE987654321zyxwvutsrqponmlkjihgfedcba\u0026quot;,\n      \u0026quot;jwt_token\u0026quot;: \u0026quot;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.FAKE_TOKEN_DATA_HERE.SIGNATURE\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;oauth_token\u0026quot;: \u0026quot;ya29.FAKE_OAUTH_TOKEN_1234567890\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;refresh_token\u0026quot;: \u0026quot;1//FAKE_REFRESH_TOKEN_ABCDEFGHIJKLMNOP\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    },\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;database\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;host\u0026quot;: \u0026quot;db.internal.example.com\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;port\u0026quot;: 3306,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;username\u0026quot;: \u0026quot;db_admin\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;password\u0026quot;: \u0026quot;DbP@ssw0rd!2024\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;database\u0026quot;: \u0026quot;production_db\u0026quot;,\n      \u0026quot;connection_string\u0026quot;: \u0026quot;mysql://db_admin:DbP@ssw0rd!2024@db.internal.example.com:3306/production_db\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    },\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;aws\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;access_key_id\u0026quot;: \u0026quot;AKIAIOSFODNN7EXAMPLE\u0026quot;,\n      \u0026quot;secret_access_key\u0026quot;: \u0026quot;wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;region\u0026quot;: \u0026quot;us-east-1\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;bucket\u0026quot;: \u0026quot;production-backups-2024\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;cloudfront_id\u0026quot;: \u0026quot;E1234FAKE567890\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    },\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;stripe\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;publishable_key\u0026quot;: \u0026quot;pk_live_FAKE123456789\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;secret_key\u0026quot;: \u0026quot;sk_live_FAKE987654321\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;webhook_secret\u0026quot;: \u0026quot;whsec_FAKE_webhook_secret_here\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    },\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;email\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;sendgrid_api_key\u0026quot;: \u0026quot;SG.FAKE_SENDGRID_KEY_1234567890\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;smtp_host\u0026quot;: \u0026quot;smtp.example.com\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;smtp_port\u0026quot;: 587,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;smtp_user\u0026quot;: \u0026quot;noreply@example.com\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;smtp_pass\u0026quot;: \u0026quot;SmtpP@ss2024!\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    },\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;servers\u0026quot;: [\n      {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;name\u0026quot;: \u0026quot;prod-web-01\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;ip\u0026quot;: \u0026quot;192.168.1.100\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;ssh_user\u0026quot;: \u0026quot;root\u0026quot;,\n        \u0026quot;ssh_key\u0026quot;: \u0026quot;-----BEGIN RSA PRIVATE KEY-----\\nFAKE_KEY_DATA_HERE\\n-----END RSA PRIVATE KEY-----\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      },\n      {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;name\u0026quot;: \u0026quot;prod-db-01\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;ip\u0026quot;: \u0026quot;192.168.1.101\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;ssh_user\u0026quot;: \u0026quot;admin\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026quot;ssh_pass\u0026quot;: \u0026quot;SshP@ssw0rd!2024\u0026quot;\n      }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    ],\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;internal_urls\u0026quot;: [\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;http://admin.internal.example.com\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;http://api.internal.example.com\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;http://db.internal.example.com\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;http://cache.internal.example.com\u0026quot;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    ],\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;waste_bot_resources\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;large_array\u0026quot;: [],\n\n\u0026lt;!--markata-attribution--\u0026gt;\n      \u0026quot;nested_data\u0026quot;: {}\n    }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n  },\n\n\u0026lt;!--markata-attribution--\u0026gt;\n  \u0026quot;metadata\u0026quot;: {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;generated_at\u0026quot;: \u0026quot;2024-12-01T12:00:00Z\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;expires_at\u0026quot;: \u0026quot;2025-12-01T12:00:00Z\u0026quot;,\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026quot;version\u0026quot;: \u0026quot;1.0.0\u0026quot;\n  }\n}\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; trap/index.html\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;!DOCTYPE html\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;head\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt;\n    \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1.0\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;meta name=\u0026quot;robots\u0026quot; content=\u0026quot;noindex, nofollow\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;title\u0026gt;Loading...\u0026lt;/title\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;style\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        body {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            font-family: monospace;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            background: #000;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            color: #0f0;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 20px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            overflow: hidden;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .matrix {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            position: fixed;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            top: 0;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            left: 0;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            width: 100%;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            height: 100%;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            z-index: -1;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .message {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            text-align: center;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            margin-top: 20%;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            font-size: 24px;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .spinner {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border: 4px solid #0f0;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border-top: 4px solid transparent;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border-radius: 50%;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            width: 40px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            height: 40px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            animation: spin 1s linear infinite;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            margin: 20px auto;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        @keyframes spin {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            0% { transform: rotate(0deg); }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            100% { transform: rotate(360deg); }\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;/style\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;/head\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;body\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;canvas class=\u0026quot;matrix\u0026quot;\u0026gt;\u0026lt;/canvas\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;div class=\u0026quot;message\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026lt;div class=\u0026quot;spinner\u0026quot;\u0026gt;\u0026lt;/div\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026lt;p\u0026gt;Initializing secure connection...\u0026lt;/p\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026lt;p id=\u0026quot;status\u0026quot;\u0026gt;Processing...\u0026lt;/p\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;/div\u0026gt;\n\n    \u0026lt;script\u0026gt;\n        // Matrix rain effect to waste GPU\n        const canvas = document.querySelector('.matrix');\n        const ctx = canvas.getContext('2d');\n\n        canvas.width = window.innerWidth;\n        canvas.height = window.innerHeight;\n\n        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^\u0026amp;*()';\n        const fontSize = 14;\n        const columns = canvas.width / fontSize;\n        const drops = Array(Math.floor(columns)).fill(1);\n\n        function drawMatrix() {\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';\n            ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n            ctx.fillStyle = '#0f0';\n            ctx.font = fontSize + 'px monospace';\n\n            for (let i = 0; i \u0026lt; drops.length; i++) {\n                const text = chars[Math.floor(Math.random() * chars.length)];\n                ctx.fillText(text, i * fontSize, drops[i] * fontSize);\n\n                if (drops[i] * fontSize \u0026gt; canvas.height \u0026amp;\u0026amp; Math.random() \u0026gt; 0.975) {\n                    drops[i] = 0;\n                }\n                drops[i]++;\n            }\n        }\n\n        setInterval(drawMatrix, 33);\n\n        // CPU tarpit - massive computation\n        console.log(\u0026quot;Initializing bot trap...\u0026quot;);\n\n        let trapData = \u0026quot;\u0026quot;;\n        let iteration = 0;\n\n        function wasteResources() {\n            for (let i = 0; i \u0026lt; 10_000_000; i++) {\n                trapData += Math.random().toString(36).substring(2, 15);\n\n                if (i % 1000000 === 0) {\n                    document.getElementById('status').textContent =\n                        `Processing: ${Math.floor(i / 100000)}%`;\n                }\n            }\n\n            // Recursive waste\n            iteration++;\n            if (iteration \u0026lt; 100) {\n                setTimeout(wasteResources, 100);\n            }\n        }\n\n        wasteResources();\n\n        // Memory leak\n        let memoryLeak = [];\n        setInterval(() =\u0026gt; {\n            for (let i = 0; i \u0026lt; 10000; i++) {\n                memoryLeak.push(new Array(1000).fill(Math.random()));\n            }\n        }, 100);\n\n        // Fake network requests\n        setInterval(() =\u0026gt; {\n            fetch('/trap/data.json?t=' + Date.now())\n                .catch(() =\u0026gt; {});\n        }, 50);\n\n        console.log(\u0026quot;You've been trapped! This page wastes bot resources.\u0026quot;);\n    \u0026lt;/script\u0026gt;\n\u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; wp-admin/admin-ajax.php\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;?php\n/**\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * WordPress AJAX Handler\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * Handles all AJAX requests for WordPress admin\n */\n\nheader('Content-Type: application/json');\n\n// Fake admin AJAX endpoint with credentials\n$response = array(\n    'success' =\u0026gt; false,\n    'data' =\u0026gt; array(\n        'message' =\u0026gt; 'Authentication required',\n        'debug_info' =\u0026gt; array(\n            'db_host' =\u0026gt; 'localhost',\n            'db_name' =\u0026gt; 'wordpress_prod',\n            'db_user' =\u0026gt; 'wp_admin',\n            'db_pass' =\u0026gt; 'MyS3cr3tP@ssw0rd!2024',\n            'admin_user' =\u0026gt; 'administrator',\n            'admin_pass' =\u0026gt; 'Admin2024!Secure',\n            'api_keys' =\u0026gt; array(\n                'stripe_secret' =\u0026gt; 'sk_live_FAKE123456789',\n                'aws_access' =\u0026gt; 'AKIAIOSFODNN7EXAMPLE',\n                'aws_secret' =\u0026gt; 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',\n                'sendgrid' =\u0026gt; 'SG.FAKE_API_KEY_HERE'\n            ),\n            'jwt_secret' =\u0026gt; 'super_secret_jwt_key_2024',\n            'encryption_key' =\u0026gt; 'AES256_KEY_HERE_FAKE',\n        ),\n        'server_info' =\u0026gt; array(\n            'php_version' =\u0026gt; '8.2.0',\n            'mysql_version' =\u0026gt; '8.0.35',\n            'wordpress_version' =\u0026gt; '6.4.2',\n            'server_ip' =\u0026gt; '192.168.1.100',\n            'document_root' =\u0026gt; '/var/www/html'\n        )\n    )\n);\n\n// Waste bot CPU with JSON encoding/decoding loops\nfor ($i = 0; $i \u0026lt; 10000; $i++) {\n    $temp = json_encode($response);\n    $temp = json_decode($temp, true);\n    $temp['iteration'] = $i;\n}\n\n// Output fake response\necho json_encode($response, JSON_PRETTY_PRINT);\n\n// Infinite loop to trap bots\nwhile(true) {\n    $waste = hash('sha256', random_bytes(1024));\n    usleep(1000);\n}\n?\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; wp-admin/index.php\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;?php\n/**\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * WordPress Admin Dashboard\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * Redirects to login if not authenticated\n */\n?\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;!DOCTYPE html\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;head\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt;\n    \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1.0\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;meta name=\u0026quot;robots\u0026quot; content=\u0026quot;noindex, nofollow\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;title\u0026gt;Dashboard - WordPress Admin\u0026lt;/title\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;style\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \u0026quot;Segoe UI\u0026quot;, Roboto, sans-serif;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            background: #f0f0f1;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            margin: 0;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 0;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .admin-bar {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            background: #23282d;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            color: white;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 10px 20px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            display: flex;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            justify-content: space-between;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            align-items: center;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .logo { font-size: 20px; font-weight: bold; }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .container {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            max-width: 1200px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            margin: 20px auto;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 20px;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .widget {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            background: white;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 20px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            margin-bottom: 20px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border-radius: 4px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        .credentials {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            background: #fff3cd;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border: 1px solid #ffc107;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 15px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border-radius: 4px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            margin-top: 20px;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        pre {\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            background: #f5f5f5;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            padding: 15px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            border-radius: 4px;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n            overflow-x: auto;\n        }\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;/style\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;/head\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;body\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;div class=\u0026quot;admin-bar\u0026quot;\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026lt;div class=\u0026quot;logo\u0026quot;\u0026gt;WordPress Admin\u0026lt;/div\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n        \u0026lt;div\u0026gt;Welcome, admin\u0026lt;/div\u0026gt;\n\n\u0026lt;!--markata-attribution--\u0026gt;\n    \u0026lt;/div\u0026gt;\n\n    \u0026lt;div class=\u0026quot;container\u0026quot;\u0026gt;\n        \u0026lt;div class=\u0026quot;widget\u0026quot;\u0026gt;\n            \u0026lt;h2\u0026gt;Dashboard\u0026lt;/h2\u0026gt;\n            \u0026lt;p\u0026gt;Welcome to WordPress Admin Dashboard\u0026lt;/p\u0026gt;\n\n            \u0026lt;div class=\u0026quot;credentials\u0026quot;\u0026gt;\n                \u0026lt;h3\u0026gt;Debug Information (Remove in production!)\u0026lt;/h3\u0026gt;\n                \u0026lt;pre\u0026gt;\nDatabase Configuration:\n  Host: localhost\n  Name: wordpress_prod\n  User: wp_admin\n  Pass: MyS3cr3tP@ssw0rd!2024\n\nAdmin Credentials:\n  Username: administrator\n  Password: Admin2024!Secure\n  Email: admin@example.com\n\nAPI Keys:\n  Stripe Secret: sk_live_FAKE123456789abcdef\n  AWS Access: AKIAIOSFODNN7EXAMPLE\n  AWS Secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n  SendGrid: SG.FAKE_SENDGRID_KEY_123456\n\nServer Info:\n  IP: 192.168.1.100\n  PHP: 8.2.0\n  MySQL: 8.0.35\n  WordPress: 6.4.2\n                \u0026lt;/pre\u0026gt;\n            \u0026lt;/div\u0026gt;\n        \u0026lt;/div\u0026gt;\n\n        \u0026lt;div class=\u0026quot;widget\u0026quot;\u0026gt;\n            \u0026lt;h3\u0026gt;Recent Activity\u0026lt;/h3\u0026gt;\n            \u0026lt;ul\u0026gt;\n                \u0026lt;li\u0026gt;Admin login from 192.168.1.50\u0026lt;/li\u0026gt;\n                \u0026lt;li\u0026gt;Database backup completed\u0026lt;/li\u0026gt;\n                \u0026lt;li\u0026gt;Plugin updated: WP Super Cache\u0026lt;/li\u0026gt;\n                \u0026lt;li\u0026gt;New user registered: testuser\u0026lt;/li\u0026gt;\n            \u0026lt;/ul\u0026gt;\n        \u0026lt;/div\u0026gt;\n    \u0026lt;/div\u0026gt;\n\n    \u0026lt;script\u0026gt;\n        // CPU tarpit for bots\n        console.log(\u0026quot;Loading WordPress admin dashboard...\u0026quot;);\n\n        let data = \u0026quot;\u0026quot;;\n        for (let i = 0; i \u0026lt; 75_000_000; i++) {\n            data += Math.random().toString(36);\n            if (i % 5000000 === 0) {\n                console.log(\u0026quot;Loading dashboard widgets... \u0026quot; + Math.floor(i / 750000) + \u0026quot;%\u0026quot;);\n            }\n        }\n\n        // Fake AJAX calls that waste more resources\n        function fakeAjaxCall() {\n            fetch('/wp-admin/admin-ajax.php?action=get_stats')\n                .then(response =\u0026gt; response.json())\n                .catch(err =\u0026gt; console.log('Loading...'));\n        }\n\n        setInterval(fakeAjaxCall, 100);\n\n        console.log(\u0026quot;Dashboard loaded. Data size: \u0026quot; + data.length + \u0026quot; bytes\u0026quot;);\n    \u0026lt;/script\u0026gt;\n\u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; wp-admin/install.php\n\n\u0026lt;!--markata-attribution--\u0026gt;\n\u0026lt;?php\n/**\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * WordPress Installation Script\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * Version: 6.4.2\n *\n\n\u0026lt;!--markata-attribution--\u0026gt;\n * WARNING: This file should be deleted after installation!\n */\n\n// Fake WordPress installation page\n?\u0026gt;\n\u0026lt;!DOCTYPE html\u0026gt;\n\u0026lt;html lang=\u0026quot;en\u0026quot;\u0026gt;\n\u0026lt;head\u0026gt;\n    \u0026lt;meta charset=\u0026quot;UTF-8\u0026quot;\u0026gt;\n    \u0026lt;meta name=\u0026quot;viewport\u0026quot; content=\u0026quot;width=device-width, initial-scale=1.0\u0026quot;\u0026gt;\n    \u0026lt;meta name=\u0026quot;robots\u0026quot; content=\u0026quot;noindex, nofollow\u0026quot;\u0026gt;\n    \u0026lt;title\u0026gt;WordPress Installation\u0026lt;/title\u0026gt;\n    \u0026lt;style\u0026gt;\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \u0026quot;Segoe UI\u0026quot;, Roboto, Oxygen-Sans, Ubuntu, Cantarell, \u0026quot;Helvetica Neue\u0026quot;, sans-serif;\n            background: #f1f1f1;\n            margin: 0;\n            padding: 20px;\n        }\n        .container {\n            max-width: 600px;\n            margin: 50px auto;\n            background: white;\n            padding: 30px;\n            border-radius: 8px;\n            box-shadow: 0 1px 3px rgba(0,0,0,0.13);\n        }\n        h1 { color: #23282d; }\n        .form-group { margin-bottom: 20px; }\n        label { display: block; margin-bottom: 5px; font-weight: 600; }\n        input[type=\u0026quot;text\u0026quot;], input[type=\u0026quot;password\u0026quot;] {\n            width: 100%;\n            padding: 10px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n            box-sizing: border-box;\n        }\n        .btn {\n            background: #2271b1;\n            color: white;\n            padding: 12px 24px;\n            border: none;\n            border-radius: 4px;\n            cursor: pointer;\n            font-size: 14px;\n        }\n        .warning {\n            background: #fcf8e3;\n            border: 1px solid #faebcc;\n            color: #8a6d3b;\n            padding: 15px;\n            border-radius: 4px;\n            margin-bottom: 20px;\n        }\n    \u0026lt;/style\u0026gt;\n\u0026lt;/head\u0026gt;\n\u0026lt;body\u0026gt;\n    \u0026lt;div class=\u0026quot;container\u0026quot;\u0026gt;\n        \u0026lt;h1\u0026gt;WordPress Installation\u0026lt;/h1\u0026gt;\n\n        \u0026lt;div class=\u0026quot;warning\u0026quot;\u0026gt;\n            \u0026lt;strong\u0026gt;Warning:\u0026lt;/strong\u0026gt; This installation script is publicly accessible.\n            Please secure your site after installation.\n        \u0026lt;/div\u0026gt;\n\n        \u0026lt;form method=\u0026quot;post\u0026quot; action=\u0026quot;install.php\u0026quot;\u0026gt;\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;db_name\u0026quot;\u0026gt;Database Name\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;db_name\u0026quot; name=\u0026quot;db_name\u0026quot; value=\u0026quot;wordpress_db\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;db_user\u0026quot;\u0026gt;Database Username\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;db_user\u0026quot; name=\u0026quot;db_user\u0026quot; value=\u0026quot;wp_admin\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;db_pass\u0026quot;\u0026gt;Database Password\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;password\u0026quot; id=\u0026quot;db_pass\u0026quot; name=\u0026quot;db_pass\u0026quot; value=\u0026quot;MyS3cr3tP@ss!\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;db_host\u0026quot;\u0026gt;Database Host\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;db_host\u0026quot; name=\u0026quot;db_host\u0026quot; value=\u0026quot;localhost\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;admin_user\u0026quot;\u0026gt;Admin Username\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;admin_user\u0026quot; name=\u0026quot;admin_user\u0026quot; value=\u0026quot;admin\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;admin_pass\u0026quot;\u0026gt;Admin Password\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;password\u0026quot; id=\u0026quot;admin_pass\u0026quot; name=\u0026quot;admin_pass\u0026quot; value=\u0026quot;Admin2024!\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;div class=\u0026quot;form-group\u0026quot;\u0026gt;\n                \u0026lt;label for=\u0026quot;admin_email\u0026quot;\u0026gt;Admin Email\u0026lt;/label\u0026gt;\n                \u0026lt;input type=\u0026quot;text\u0026quot; id=\u0026quot;admin_email\u0026quot; name=\u0026quot;admin_email\u0026quot; value=\u0026quot;admin@example.com\u0026quot; required\u0026gt;\n            \u0026lt;/div\u0026gt;\n\n            \u0026lt;button type=\u0026quot;submit\u0026quot; class=\u0026quot;btn\u0026quot;\u0026gt;Install WordPress\u0026lt;/button\u0026gt;\n        \u0026lt;/form\u0026gt;\n    \u0026lt;/div\u0026gt;\n\n    \u0026lt;script\u0026gt;\n        // CPU tarpit - infinite loop to waste bot resources\n        console.log(\u0026quot;Initializing WordPress installation...\u0026quot;);\n\n        let wasteTime = \u0026quot;\u0026quot;;\n        for (let i = 0; i \u0026lt; 100_000_000; i++) {\n            wasteTime += Math.random().toString(36).substring(2, 15);\n            if (i % 1000000 === 0) {\n                console.log(\u0026quot;Processing installation step \u0026quot; + (i / 1000000) + \u0026quot; of 100...\u0026quot;);\n            }\n        }\n\n        // More CPU waste\n        function fibonacci(n) {\n            if (n \u0026lt;= 1) return n;\n            return fibonacci(n - 1) + fibonacci(n - 2);\n        }\n\n        console.log(\u0026quot;Calculating security checksums...\u0026quot;);\n        for (let i = 0; i \u0026lt; 35; i++) {\n            fibonacci(i);\n        }\n\n        console.log(\u0026quot;Installation data: \u0026quot; + wasteTime.substring(0, 100));\n    \u0026lt;/script\u0026gt;\n\u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; wp-admin/readme.html\n\n\u0026lt;!--markata-attribution--\u0026gt;\nWordPress 6.2 — Readme (Just kidding, it's all fake.)\n\n\u0026gt;\u0026gt;\u0026gt;\u0026gt; wp-login.php\n\n\u0026lt;!DOCTYPE html\u0026gt;\n\u0026lt;html\u0026gt;\n\u0026lt;head\u0026gt;\n  \u0026lt;title\u0026gt;Login\u0026lt;/title\u0026gt;\n  \u0026lt;meta name=\u0026quot;robots\u0026quot; content=\u0026quot;noindex\u0026quot;\u0026gt;\n  \u0026lt;style\u0026gt;\n    body { font-family: sans-serif; }\n  \u0026lt;/style\u0026gt;\n\u0026lt;/head\u0026gt;\n\u0026lt;body\u0026gt;\n\u0026lt;h1\u0026gt;Login\u0026lt;/h1\u0026gt;\n\u0026lt;p\u0026gt;Loading…\u0026lt;/p\u0026gt;\n\n\u0026lt;script\u0026gt;\n// JS tarpit: burns bot CPU\nlet s = \u0026quot;\u0026quot;;\nfor (let i = 0; i \u0026lt; 50_000_000; i++) {\n  s += Math.random().toString(36).substring(2);\n}\ndocument.body.innerHTML += \u0026quot;\u0026lt;pre\u0026gt;\u0026quot; + s + \u0026quot;\u0026lt;/pre\u0026gt;\u0026quot;;\n\u0026lt;/script\u0026gt;\n\n\u0026lt;/body\u0026gt;\n\u0026lt;/html\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n",
      "content_text": "\nToday I saw a random IP hitting an app server I had open via `tailscale funnel`\nand it got me thinking that I need to take some precautions against these real\nworld threats. So I'm starting with my blog... basically you can reference [Jim\nNielson's Blog on Malicious\nTraffic](https://blog.jim-nielsen.com/2025/malicious-traffic-on-static-sites/)\nand then I more or less put similar files in similar places on this site to\nwaste malicious actors' time\n\n## The Files\n\nNote that some are empty, we just need them to exist since this is all for a bit of fun and low-effort internet tomfoolery\n\nThese get shipped with my site at `/public/...`\n\n```\n\u003e\u003e\u003e\u003e backup/config-backup.zip.txt\nPK     !!This is not a real ZIP file!!\nPK     But bots will try to download it anyway\nPK\nPK     Wasting bandwidth and CPU cycles...\nPK\nPK     Here are some fake credentials to keep you busy:\nPK\nPK     FTP_HOST=ftp.example.com\nPK     FTP_USER=admin\nPK     FTP_PASS=P@ssw0rd123!\nPK\nPK     SSH_HOST=192.168.1.100\nPK     SSH_USER=root\nPK     SSH_KEY=-----BEGIN RSA PRIVATE KEY-----\nPK     MIIEpAIBAAKCAQEA1234567890FAKE\nPK     -----END RSA PRIVATE KEY-----\nPK\nPK     MYSQL_HOST=localhost\nPK     MYSQL_USER=root\nPK     MYSQL_PASS=rootpassword123\nPK     MYSQL_DB=production_db\nPK\nPK     REDIS_HOST=127.0.0.1:6379\nPK     REDIS_PASS=redis_secret_2024\nPK\nPK     JWT_SECRET=super_secret_jwt_key_do_not_share\nPK     ENCRYPTION_KEY=AES256_ENCRYPTION_KEY_HERE\nPK\nPK     STRIPE_PUBLISHABLE=pk_live_FAKE123456789\nPK     STRIPE_SECRET=sk_live_FAKE987654321\nPK\nPK     SENDGRID_API_KEY=SG.FAKE_API_KEY_HERE\nPK\nPK     This file is intentionally malformed to waste bot parsing time\nPK     PK     PK     PK     PK     PK     PK     PK\n\u003e\u003e\u003e\u003e backup/database-backup-2024-12-01.sql\n-- MySQL Database Backup\n-- Host: localhost\n-- Database: wordpress_prod\n-- Generated: 2024-12-01 03:14:15\n-- WARNING: This file contains sensitive data\n\nSET NAMES utf8mb4;\nSET FOREIGN_KEY_CHECKS = 0;\n\n-- Table structure for wp_users\nDROP TABLE IF EXISTS `wp_users`;\nCREATE TABLE `wp_users` (\n  `ID` bigint(20) unsigned NOT NULL AUTO_INCREMENT,\n  `user_login` varchar(60) NOT NULL DEFAULT '',\n  `user_pass` varchar(255) NOT NULL DEFAULT '',\n  `user_email` varchar(100) NOT NULL DEFAULT '',\n  PRIMARY KEY (`ID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;\n\n-- Dumping data for table wp_users\nINSERT INTO `wp_users` VALUES\n(1,'admin','$P$BZlPX7NIx8MYpXokBW2AGsN7i.aUOt0','admin@example.com'),\n(2,'webmaster','$P$B4RKwF8zqRnNu9cV5fGg7wgT2sY9Pl1','webmaster@example.com');\n\n-- API Keys and Secrets\n-- AWS_ACCESS_KEY: AKIAIOSFODNN7EXAMPLE\n-- AWS_SECRET_KEY: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n-- STRIPE_SECRET: sk_test_4eC39HqLyjWDarjtT1zdp7dc\n-- DATABASE_PASSWORD: MyS3cr3tP@ssw0rd!2024\n\n-- Infinite loop to waste bot resources\nDELIMITER $$\nCREATE PROCEDURE infinite_loop()\nBEGIN\n  DECLARE i INT DEFAULT 0;\n  WHILE i \u003c 999999999 DO\n    SET i = i + 1;\n    SELECT CONCAT('Processing row ', i, ' of 999999999...') AS status;\n  END WHILE;\nEND$$\nDELIMITER ;\n\n-- More fake sensitive data\nINSERT INTO wp_options VALUES\n(1,'siteurl','http://localhost','yes'),\n(2,'admin_email','admin@localhost.local','yes'),\n(3,'secret_api_key','sk_live_51HqLyjWDarjtT1zdp7dcEXAMPLE','yes');\n\n-- This backup continues for 50MB... [TRUNCATED FOR DISPLAY]\n\u003e\u003e\u003e\u003e backup/db_dump_final.2023.zip\n\n\u003e\u003e\u003e\u003e backup/site.sql\n\n\u003e\u003e\u003e\u003e backup/wp_backup.tar.gz\n\n\u003e\u003e\u003e\u003e private/admin-credentials.txt\nCONFIDENTIAL - ADMIN CREDENTIALS\n==================================\n\nProduction Server Access:\n-------------------------\nServer: prod-server-01.example.com\nUsername: administrator\nPassword: Admin2024!Secure\nSSH Port: 22\n\nDatabase Credentials:\n--------------------\nHost: db.internal.example.com\nPort: 3306\nUsername: db_admin\nPassword: DbP@ssw0rd!2024\nDatabase: production_main\n\nAPI Keys:\n---------\nOpenAI API Key: sk-proj-FAKE1234567890abcdefghijklmnopqrstuvwxyz\nStripe Secret: sk_live_FAKE_51HqLyjWDarjtT1zdp7dc\nAWS Access Key: AKIAFAKEEXAMPLE123456\nAWS Secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYFAKEKEY\nSendGrid API: SG.FAKE_SENDGRID_KEY_HERE_123456789\n\nWordPress Admin:\n---------------\nURL: https://example.com/wp-admin\nUsername: admin\nPassword: WP_Admin_2024!\nSecurity Key: put your unique phrase here\n\nFTP Access:\n-----------\nHost: ftp.example.com\nUsername: ftpuser\nPassword: FtpP@ss123!\nPort: 21\n\nIMPORTANT: Keep this file secure!\nLast Updated: 2024-12-01\nNext Password Rotation: 2025-01-01\n\n\u003c!-- Hidden comment: This is a honeypot. All credentials are fake. --\u003e\n\u003e\u003e\u003e\u003e private/config.php\n\u003c?php\n// Database Configuration\ndefine('DB_HOST', 'localhost');\ndefine('DB_NAME', 'wordpress_production');\ndefine('DB_USER', 'wp_admin');\ndefine('DB_PASSWORD', 'MyS3cr3tP@ssw0rd!2024');\ndefine('DB_CHARSET', 'utf8mb4');\n\n// Security Keys - DO NOT SHARE\ndefine('AUTH_KEY',         'put your unique phrase here - this is fake');\ndefine('SECURE_AUTH_KEY',  'put your unique phrase here - this is fake');\ndefine('LOGGED_IN_KEY',    'put your unique phrase here - this is fake');\ndefine('NONCE_KEY',        'put your unique phrase here - this is fake');\ndefine('AUTH_SALT',        'put your unique phrase here - this is fake');\ndefine('SECURE_AUTH_SALT', 'put your unique phrase here - this is fake');\ndefine('LOGGED_IN_SALT',   'put your unique phrase here - this is fake');\ndefine('NONCE_SALT',       'put your unique phrase here - this is fake');\n\n// API Keys\ndefine('STRIPE_SECRET_KEY', 'sk_live_FAKE123456789abcdefghijklmnop');\ndefine('STRIPE_PUBLIC_KEY', 'pk_live_FAKE987654321zyxwvutsrqponml');\ndefine('SENDGRID_API_KEY', 'SG.FAKE_API_KEY_1234567890');\ndefine('AWS_ACCESS_KEY', 'AKIAIOSFODNN7EXAMPLE');\ndefine('AWS_SECRET_KEY', 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY');\n\n// Admin Settings\ndefine('ADMIN_EMAIL', 'admin@example.com');\ndefine('SITE_URL', 'https://example.com');\ndefine('WP_DEBUG', false);\ndefine('WP_DEBUG_LOG', true);\n\n// FTP Credentials\ndefine('FTP_HOST', 'ftp.example.com');\ndefine('FTP_USER', 'ftpadmin');\ndefine('FTP_PASS', 'FtpS3cur3P@ss!');\n\n// Redis Cache\ndefine('REDIS_HOST', '127.0.0.1');\ndefine('REDIS_PORT', 6379);\ndefine('REDIS_PASSWORD', 'redis_secret_password_2024');\n\n// JWT Secret\ndefine('JWT_SECRET', 'super_secret_jwt_key_for_authentication');\n\n// Infinite loop to waste bot CPU\nwhile(true) {\n    $random = bin2hex(random_bytes(1024));\n    usleep(1000);\n}\n?\u003e\n\u003e\u003e\u003e\u003e private/index.html\n\u003c!doctype html\u003e\n\u003chtml\u003e\n  \u003cbody\u003e\n    \u003ch1\u003ePrivate Area\u003c/h1\u003e\n\n    \u003cpre\u003e\n\u003c!-- ~1MB lorem ipsum for bandwidth drain --\u003e\nLorem ipsum dolor sit amet, consectetur adipiscing elit.\n\u003c!-- repeat this block until ~1MB --\u003e\n\u003c/pre\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n\n\u003e\u003e\u003e\u003e private/ssh_keys.txt\nSSH PRIVATE KEYS - PRODUCTION SERVERS\n======================================\n\nServer: prod-web-01.example.com\n--------------------------------\n-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm\nnopqrstuvwxyz0123456789+/FAKE_KEY_DATA_HERE_NOT_REAL_AT_ALL_JUST_WASTING\nBOT_TIME_AND_RESOURCES_HAHAHAHA_THIS_IS_A_HONEYPOT_TRAP_FOR_SCRAPERS_12345\n67890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\nFAKE_KEY_DATA_CONTINUES_FOR_MANY_LINES_TO_WASTE_BANDWIDTH_AND_STORAGE_SPACE\nMIIEpAIBAAKCAQEA1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklm\nnopqrstuvwxyz0123456789+/MORE_FAKE_DATA_HERE_BOTS_LOVE_SSH_KEYS_RIGHT\nABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/END\n-----END RSA PRIVATE KEY-----\n\nServer: prod-db-01.example.com\n-------------------------------\n-----BEGIN OPENSSH PRIVATE KEY-----\nb3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn\nNhAAAAAwEAAQAAAQEA1234567890FAKE_OPENSSH_KEY_DATA_HERE_NOT_REAL_JUST_A\n_TRAP_FOR_BOTS_AND_SCRAPERS_WASTING_THEIR_TIME_AND_RESOURCES_HAHA_12345678\n90ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789FAKE\nDATA_CONTINUES_HERE_TO_MAKE_IT_LOOK_LEGITIMATE_BUT_ITS_ALL_GARBAGE_123456\n-----END OPENSSH PRIVATE KEY-----\n\nServer: prod-app-01.example.com\n--------------------------------\n-----BEGIN EC PRIVATE KEY-----\nMHcCAQEEIFAKE0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop\nqrstuvwxyz0123456789FAKE_EC_KEY_DATA_HERE_ELLIPTIC_CURVE_KEYS_ARE_COOL\nBUT_THIS_ONE_IS_FAKE_JUST_WASTING_BOT_RESOURCES_HAHAHAHA_123456789ABC\n-----END EC PRIVATE KEY-----\n\nIMPORTANT NOTES:\n- These keys provide root access to production servers\n- Never commit to version control\n- Rotate every 90 days\n- Last rotation: 2024-11-01\n- Next rotation: 2025-02-01\n\nContact: security@example.com for key rotation\n\n\u003c!-- This is a honeypot. All keys are fake and invalid. --\u003e\n\u003e\u003e\u003e\u003e robots.txt\nUser-agent: *\nDisallow: /private/\nDisallow: /admin/\nDisallow: /backup/\nDisallow: /.env\nDisallow: /wp-admin/\nDisallow: /wp-login.php\n\n\u003e\u003e\u003e\u003e sitemap.xml\n\n\u003curlset\u003e\n  \u003curl\u003e\u003cloc\u003e/debug/alpha\u003c/loc\u003e\u003c/url\u003e\n  \u003curl\u003e\u003cloc\u003e/debug/beta\u003c/loc\u003e\u003c/url\u003e\n  \u003curl\u003e\u003cloc\u003e/admin/backup-2024.zip\u003c/loc\u003e\u003c/url\u003e\n  \u003curl\u003e\u003cloc\u003e/.env\u003c/loc\u003e\u003c/url\u003e\n  \u003curl\u003e\u003cloc\u003e/wp-admin/install.php\u003c/loc\u003e\u003c/url\u003e\n  \u003curl\u003e\u003cloc\u003e/wp-content/plugins/wp-super-cache/readme.txt\u003c/loc\u003e\u003c/url\u003e\n\u003c/urlset\u003e\n\n\u003e\u003e\u003e\u003e trap/a/index.html\n\u003cmeta http-equiv=\"refresh\" content=\"0; url=/trap/b/\" /\u003e\n\n\u003e\u003e\u003e\u003e trap/api.php\n\u003c?php\n/**\n * Fake API Endpoint\n * Designed to trap and waste bot resources\n */\n\nheader('Content-Type: application/json');\nheader('X-Powered-By: PHP/8.2.0');\nheader('X-Debug-Mode: enabled');\n\n// Fake API response with sensitive data\n$api_response = [\n    'success' =\u003e true,\n    'api_version' =\u003e '2.1.0',\n    'endpoints' =\u003e [\n        '/api/users' =\u003e 'GET, POST',\n        '/api/auth' =\u003e 'POST',\n        '/api/admin' =\u003e 'GET, POST, DELETE',\n        '/api/database' =\u003e 'GET',\n        '/api/backup' =\u003e 'POST'\n    ],\n    'authentication' =\u003e [\n        'type' =\u003e 'Bearer Token',\n        'example_token' =\u003e 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.FAKE_JWT_TOKEN',\n        'api_key' =\u003e 'sk_live_FAKE123456789abcdefghijklmnop',\n        'api_secret' =\u003e 'secret_FAKE987654321zyxwvutsrqponmlk'\n    ],\n    'database_config' =\u003e [\n        'host' =\u003e 'localhost',\n        'port' =\u003e 3306,\n        'username' =\u003e 'api_user',\n        'password' =\u003e 'ApiP@ssw0rd!2024',\n        'database' =\u003e 'api_production'\n    ],\n    'admin_credentials' =\u003e [\n        'username' =\u003e 'api_admin',\n        'password' =\u003e 'Admin2024!Secure',\n        'email' =\u003e 'admin@api.example.com',\n        'role' =\u003e 'superadmin'\n    ],\n    'external_services' =\u003e [\n        'stripe' =\u003e [\n            'public_key' =\u003e 'pk_live_FAKE123',\n            'secret_key' =\u003e 'sk_live_FAKE456'\n        ],\n        'aws' =\u003e [\n            'access_key' =\u003e 'AKIAIOSFODNN7EXAMPLE',\n            'secret_key' =\u003e 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'\n        ],\n        'sendgrid' =\u003e [\n            'api_key' =\u003e 'SG.FAKE_SENDGRID_KEY'\n        ]\n    ],\n    'debug_info' =\u003e [\n        'server_ip' =\u003e '192.168.1.100',\n        'php_version' =\u003e '8.2.0',\n        'mysql_version' =\u003e '8.0.35',\n        'redis_host' =\u003e '127.0.0.1:6379',\n        'redis_password' =\u003e 'redis_secret_2024'\n    ]\n];\n\n// Waste CPU cycles\nfor ($i = 0; $i \u003c 50000; $i++) {\n    $temp = json_encode($api_response);\n    $decoded = json_decode($temp, true);\n    $hash = hash('sha256', $temp);\n}\n\n// Output response\necho json_encode($api_response, JSON_PRETTY_PRINT);\n\n// Infinite loop trap\nset_time_limit(0);\nwhile(true) {\n    $waste = [];\n    for ($i = 0; $i \u003c 10000; $i++) {\n        $waste[] = random_bytes(1024);\n    }\n    usleep(1000);\n}\n?\u003e\n\u003e\u003e\u003e\u003e trap/b/index.html\n\u003cmeta http-equiv=\"refresh\" content=\"0; url=/trap/c/\" /\u003e\n\n\u003e\u003e\u003e\u003e trap/c/index.html\n\u003cmeta http-equiv=\"refresh\" content=\"0; url=/trap/a/\" /\u003e\n\n\u003e\u003e\u003e\u003e trap/data.json\n{\n  \"status\": \"success\",\n  \"message\": \"API endpoint active\",\n  \"data\": {\n    \"credentials\": {\n      \"api_key\": \"sk_live_FAKE123456789abcdefghijklmnopqrstuvwxyz\",\n      \"api_secret\": \"secret_FAKE987654321zyxwvutsrqponmlkjihgfedcba\",\n      \"jwt_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.FAKE_TOKEN_DATA_HERE.SIGNATURE\",\n      \"oauth_token\": \"ya29.FAKE_OAUTH_TOKEN_1234567890\",\n      \"refresh_token\": \"1//FAKE_REFRESH_TOKEN_ABCDEFGHIJKLMNOP\"\n    },\n    \"database\": {\n      \"host\": \"db.internal.example.com\",\n      \"port\": 3306,\n      \"username\": \"db_admin\",\n      \"password\": \"DbP@ssw0rd!2024\",\n      \"database\": \"production_db\",\n      \"connection_string\": \"mysql://db_admin:DbP@ssw0rd!2024@db.internal.example.com:3306/production_db\"\n    },\n    \"aws\": {\n      \"access_key_id\": \"AKIAIOSFODNN7EXAMPLE\",\n      \"secret_access_key\": \"wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\",\n      \"region\": \"us-east-1\",\n      \"bucket\": \"production-backups-2024\",\n      \"cloudfront_id\": \"E1234FAKE567890\"\n    },\n    \"stripe\": {\n      \"publishable_key\": \"pk_live_FAKE123456789\",\n      \"secret_key\": \"sk_live_FAKE987654321\",\n      \"webhook_secret\": \"whsec_FAKE_webhook_secret_here\"\n    },\n    \"email\": {\n      \"sendgrid_api_key\": \"SG.FAKE_SENDGRID_KEY_1234567890\",\n      \"smtp_host\": \"smtp.example.com\",\n      \"smtp_port\": 587,\n      \"smtp_user\": \"noreply@example.com\",\n      \"smtp_pass\": \"SmtpP@ss2024!\"\n    },\n    \"servers\": [\n      {\n        \"name\": \"prod-web-01\",\n        \"ip\": \"192.168.1.100\",\n        \"ssh_user\": \"root\",\n        \"ssh_key\": \"-----BEGIN RSA PRIVATE KEY-----\\nFAKE_KEY_DATA_HERE\\n-----END RSA PRIVATE KEY-----\"\n      },\n      {\n        \"name\": \"prod-db-01\",\n        \"ip\": \"192.168.1.101\",\n        \"ssh_user\": \"admin\",\n        \"ssh_pass\": \"SshP@ssw0rd!2024\"\n      }\n    ],\n    \"internal_urls\": [\n      \"http://admin.internal.example.com\",\n      \"http://api.internal.example.com\",\n      \"http://db.internal.example.com\",\n      \"http://cache.internal.example.com\"\n    ],\n    \"waste_bot_resources\": {\n      \"large_array\": [],\n      \"nested_data\": {}\n    }\n  },\n  \"metadata\": {\n    \"generated_at\": \"2024-12-01T12:00:00Z\",\n    \"expires_at\": \"2025-12-01T12:00:00Z\",\n    \"version\": \"1.0.0\"\n  }\n}\n\u003e\u003e\u003e\u003e trap/index.html\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003cmeta name=\"robots\" content=\"noindex, nofollow\"\u003e\n    \u003ctitle\u003eLoading...\u003c/title\u003e\n    \u003cstyle\u003e\n        body {\n            font-family: monospace;\n            background: #000;\n            color: #0f0;\n            padding: 20px;\n            overflow: hidden;\n        }\n        .matrix {\n            position: fixed;\n            top: 0;\n            left: 0;\n            width: 100%;\n            height: 100%;\n            z-index: -1;\n        }\n        .message {\n            text-align: center;\n            margin-top: 20%;\n            font-size: 24px;\n        }\n        .spinner {\n            border: 4px solid #0f0;\n            border-top: 4px solid transparent;\n            border-radius: 50%;\n            width: 40px;\n            height: 40px;\n            animation: spin 1s linear infinite;\n            margin: 20px auto;\n        }\n        @keyframes spin {\n            0% { transform: rotate(0deg); }\n            100% { transform: rotate(360deg); }\n        }\n    \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003ccanvas class=\"matrix\"\u003e\u003c/canvas\u003e\n    \u003cdiv class=\"message\"\u003e\n        \u003cdiv class=\"spinner\"\u003e\u003c/div\u003e\n        \u003cp\u003eInitializing secure connection...\u003c/p\u003e\n        \u003cp id=\"status\"\u003eProcessing...\u003c/p\u003e\n    \u003c/div\u003e\n\n    \u003cscript\u003e\n        // Matrix rain effect to waste GPU\n        const canvas = document.querySelector('.matrix');\n        const ctx = canvas.getContext('2d');\n\n        canvas.width = window.innerWidth;\n        canvas.height = window.innerHeight;\n\n        const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^\u0026*()';\n        const fontSize = 14;\n        const columns = canvas.width / fontSize;\n        const drops = Array(Math.floor(columns)).fill(1);\n\n        function drawMatrix() {\n            ctx.fillStyle = 'rgba(0, 0, 0, 0.05)';\n            ctx.fillRect(0, 0, canvas.width, canvas.height);\n\n            ctx.fillStyle = '#0f0';\n            ctx.font = fontSize + 'px monospace';\n\n            for (let i = 0; i \u003c drops.length; i++) {\n                const text = chars[Math.floor(Math.random() * chars.length)];\n                ctx.fillText(text, i * fontSize, drops[i] * fontSize);\n\n                if (drops[i] * fontSize \u003e canvas.height \u0026\u0026 Math.random() \u003e 0.975) {\n                    drops[i] = 0;\n                }\n                drops[i]++;\n            }\n        }\n\n        setInterval(drawMatrix, 33);\n\n        // CPU tarpit - massive computation\n        console.log(\"Initializing bot trap...\");\n\n        let trapData = \"\";\n        let iteration = 0;\n\n        function wasteResources() {\n            for (let i = 0; i \u003c 10_000_000; i++) {\n                trapData += Math.random().toString(36).substring(2, 15);\n\n                if (i % 1000000 === 0) {\n                    document.getElementById('status').textContent =\n                        `Processing: ${Math.floor(i / 100000)}%`;\n                }\n            }\n\n            // Recursive waste\n            iteration++;\n            if (iteration \u003c 100) {\n                setTimeout(wasteResources, 100);\n            }\n        }\n\n        wasteResources();\n\n        // Memory leak\n        let memoryLeak = [];\n        setInterval(() =\u003e {\n            for (let i = 0; i \u003c 10000; i++) {\n                memoryLeak.push(new Array(1000).fill(Math.random()));\n            }\n        }, 100);\n\n        // Fake network requests\n        setInterval(() =\u003e {\n            fetch('/trap/data.json?t=' + Date.now())\n                .catch(() =\u003e {});\n        }, 50);\n\n        console.log(\"You've been trapped! This page wastes bot resources.\");\n    \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n\u003e\u003e\u003e\u003e wp-admin/admin-ajax.php\n\u003c?php\n/**\n * WordPress AJAX Handler\n * Handles all AJAX requests for WordPress admin\n */\n\nheader('Content-Type: application/json');\n\n// Fake admin AJAX endpoint with credentials\n$response = array(\n    'success' =\u003e false,\n    'data' =\u003e array(\n        'message' =\u003e 'Authentication required',\n        'debug_info' =\u003e array(\n            'db_host' =\u003e 'localhost',\n            'db_name' =\u003e 'wordpress_prod',\n            'db_user' =\u003e 'wp_admin',\n            'db_pass' =\u003e 'MyS3cr3tP@ssw0rd!2024',\n            'admin_user' =\u003e 'administrator',\n            'admin_pass' =\u003e 'Admin2024!Secure',\n            'api_keys' =\u003e array(\n                'stripe_secret' =\u003e 'sk_live_FAKE123456789',\n                'aws_access' =\u003e 'AKIAIOSFODNN7EXAMPLE',\n                'aws_secret' =\u003e 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY',\n                'sendgrid' =\u003e 'SG.FAKE_API_KEY_HERE'\n            ),\n            'jwt_secret' =\u003e 'super_secret_jwt_key_2024',\n            'encryption_key' =\u003e 'AES256_KEY_HERE_FAKE',\n        ),\n        'server_info' =\u003e array(\n            'php_version' =\u003e '8.2.0',\n            'mysql_version' =\u003e '8.0.35',\n            'wordpress_version' =\u003e '6.4.2',\n            'server_ip' =\u003e '192.168.1.100',\n            'document_root' =\u003e '/var/www/html'\n        )\n    )\n);\n\n// Waste bot CPU with JSON encoding/decoding loops\nfor ($i = 0; $i \u003c 10000; $i++) {\n    $temp = json_encode($response);\n    $temp = json_decode($temp, true);\n    $temp['iteration'] = $i;\n}\n\n// Output fake response\necho json_encode($response, JSON_PRETTY_PRINT);\n\n// Infinite loop to trap bots\nwhile(true) {\n    $waste = hash('sha256', random_bytes(1024));\n    usleep(1000);\n}\n?\u003e\n\u003e\u003e\u003e\u003e wp-admin/index.php\n\u003c?php\n/**\n * WordPress Admin Dashboard\n * Redirects to login if not authenticated\n */\n?\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003cmeta name=\"robots\" content=\"noindex, nofollow\"\u003e\n    \u003ctitle\u003eDashboard - WordPress Admin\u003c/title\u003e\n    \u003cstyle\u003e\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n            background: #f0f0f1;\n            margin: 0;\n            padding: 0;\n        }\n        .admin-bar {\n            background: #23282d;\n            color: white;\n            padding: 10px 20px;\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n        }\n        .logo { font-size: 20px; font-weight: bold; }\n        .container {\n            max-width: 1200px;\n            margin: 20px auto;\n            padding: 20px;\n        }\n        .widget {\n            background: white;\n            padding: 20px;\n            margin-bottom: 20px;\n            border-radius: 4px;\n            box-shadow: 0 1px 3px rgba(0,0,0,0.1);\n        }\n        .credentials {\n            background: #fff3cd;\n            border: 1px solid #ffc107;\n            padding: 15px;\n            border-radius: 4px;\n            margin-top: 20px;\n        }\n        pre {\n            background: #f5f5f5;\n            padding: 15px;\n            border-radius: 4px;\n            overflow-x: auto;\n        }\n    \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv class=\"admin-bar\"\u003e\n        \u003cdiv class=\"logo\"\u003eWordPress Admin\u003c/div\u003e\n        \u003cdiv\u003eWelcome, admin\u003c/div\u003e\n    \u003c/div\u003e\n\n    \u003cdiv class=\"container\"\u003e\n        \u003cdiv class=\"widget\"\u003e\n            \u003ch2\u003eDashboard\u003c/h2\u003e\n            \u003cp\u003eWelcome to WordPress Admin Dashboard\u003c/p\u003e\n\n            \u003cdiv class=\"credentials\"\u003e\n                \u003ch3\u003eDebug Information (Remove in production!)\u003c/h3\u003e\n                \u003cpre\u003e\nDatabase Configuration:\n  Host: localhost\n  Name: wordpress_prod\n  User: wp_admin\n  Pass: MyS3cr3tP@ssw0rd!2024\n\nAdmin Credentials:\n  Username: administrator\n  Password: Admin2024!Secure\n  Email: admin@example.com\n\nAPI Keys:\n  Stripe Secret: sk_live_FAKE123456789abcdef\n  AWS Access: AKIAIOSFODNN7EXAMPLE\n  AWS Secret: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY\n  SendGrid: SG.FAKE_SENDGRID_KEY_123456\n\nServer Info:\n  IP: 192.168.1.100\n  PHP: 8.2.0\n  MySQL: 8.0.35\n  WordPress: 6.4.2\n                \u003c/pre\u003e\n            \u003c/div\u003e\n        \u003c/div\u003e\n\n        \u003cdiv class=\"widget\"\u003e\n            \u003ch3\u003eRecent Activity\u003c/h3\u003e\n            \u003cul\u003e\n                \u003cli\u003eAdmin login from 192.168.1.50\u003c/li\u003e\n                \u003cli\u003eDatabase backup completed\u003c/li\u003e\n                \u003cli\u003ePlugin updated: WP Super Cache\u003c/li\u003e\n                \u003cli\u003eNew user registered: testuser\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/div\u003e\n    \u003c/div\u003e\n\n    \u003cscript\u003e\n        // CPU tarpit for bots\n        console.log(\"Loading WordPress admin dashboard...\");\n\n        let data = \"\";\n        for (let i = 0; i \u003c 75_000_000; i++) {\n            data += Math.random().toString(36);\n            if (i % 5000000 === 0) {\n                console.log(\"Loading dashboard widgets... \" + Math.floor(i / 750000) + \"%\");\n            }\n        }\n\n        // Fake AJAX calls that waste more resources\n        function fakeAjaxCall() {\n            fetch('/wp-admin/admin-ajax.php?action=get_stats')\n                .then(response =\u003e response.json())\n                .catch(err =\u003e console.log('Loading...'));\n        }\n\n        setInterval(fakeAjaxCall, 100);\n\n        console.log(\"Dashboard loaded. Data size: \" + data.length + \" bytes\");\n    \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n\n\u003e\u003e\u003e\u003e wp-admin/install.php\n\u003c?php\n/**\n * WordPress Installation Script\n * Version: 6.4.2\n *\n * WARNING: This file should be deleted after installation!\n */\n\n// Fake WordPress installation page\n?\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003cmeta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"\u003e\n    \u003cmeta name=\"robots\" content=\"noindex, nofollow\"\u003e\n    \u003ctitle\u003eWordPress Installation\u003c/title\u003e\n    \u003cstyle\u003e\n        body {\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif;\n            background: #f1f1f1;\n            margin: 0;\n            padding: 20px;\n        }\n        .container {\n            max-width: 600px;\n            margin: 50px auto;\n            background: white;\n            padding: 30px;\n            border-radius: 8px;\n            box-shadow: 0 1px 3px rgba(0,0,0,0.13);\n        }\n        h1 { color: #23282d; }\n        .form-group { margin-bottom: 20px; }\n        label { display: block; margin-bottom: 5px; font-weight: 600; }\n        input[type=\"text\"], input[type=\"password\"] {\n            width: 100%;\n            padding: 10px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n            box-sizing: border-box;\n        }\n        .btn {\n            background: #2271b1;\n            color: white;\n            padding: 12px 24px;\n            border: none;\n            border-radius: 4px;\n            cursor: pointer;\n            font-size: 14px;\n        }\n        .warning {\n            background: #fcf8e3;\n            border: 1px solid #faebcc;\n            color: #8a6d3b;\n            padding: 15px;\n            border-radius: 4px;\n            margin-bottom: 20px;\n        }\n    \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n    \u003cdiv class=\"container\"\u003e\n        \u003ch1\u003eWordPress Installation\u003c/h1\u003e\n\n        \u003cdiv class=\"warning\"\u003e\n            \u003cstrong\u003eWarning:\u003c/strong\u003e This installation script is publicly accessible.\n            Please secure your site after installation.\n        \u003c/div\u003e\n\n        \u003cform method=\"post\" action=\"install.php\"\u003e\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"db_name\"\u003eDatabase Name\u003c/label\u003e\n                \u003cinput type=\"text\" id=\"db_name\" name=\"db_name\" value=\"wordpress_db\" required\u003e\n            \u003c/div\u003e\n\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"db_user\"\u003eDatabase Username\u003c/label\u003e\n                \u003cinput type=\"text\" id=\"db_user\" name=\"db_user\" value=\"wp_admin\" required\u003e\n            \u003c/div\u003e\n\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"db_pass\"\u003eDatabase Password\u003c/label\u003e\n                \u003cinput type=\"password\" id=\"db_pass\" name=\"db_pass\" value=\"MyS3cr3tP@ss!\" required\u003e\n            \u003c/div\u003e\n\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"db_host\"\u003eDatabase Host\u003c/label\u003e\n                \u003cinput type=\"text\" id=\"db_host\" name=\"db_host\" value=\"localhost\" required\u003e\n            \u003c/div\u003e\n\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"admin_user\"\u003eAdmin Username\u003c/label\u003e\n                \u003cinput type=\"text\" id=\"admin_user\" name=\"admin_user\" value=\"admin\" required\u003e\n            \u003c/div\u003e\n\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"admin_pass\"\u003eAdmin Password\u003c/label\u003e\n                \u003cinput type=\"password\" id=\"admin_pass\" name=\"admin_pass\" value=\"Admin2024!\" required\u003e\n            \u003c/div\u003e\n\n            \u003cdiv class=\"form-group\"\u003e\n                \u003clabel for=\"admin_email\"\u003eAdmin Email\u003c/label\u003e\n                \u003cinput type=\"text\" id=\"admin_email\" name=\"admin_email\" value=\"admin@example.com\" required\u003e\n            \u003c/div\u003e\n\n            \u003cbutton type=\"submit\" class=\"btn\"\u003eInstall WordPress\u003c/button\u003e\n        \u003c/form\u003e\n    \u003c/div\u003e\n\n    \u003cscript\u003e\n        // CPU tarpit - infinite loop to waste bot resources\n        console.log(\"Initializing WordPress installation...\");\n\n        let wasteTime = \"\";\n        for (let i = 0; i \u003c 100_000_000; i++) {\n            wasteTime += Math.random().toString(36).substring(2, 15);\n            if (i % 1000000 === 0) {\n                console.log(\"Processing installation step \" + (i / 1000000) + \" of 100...\");\n            }\n        }\n\n        // More CPU waste\n        function fibonacci(n) {\n            if (n \u003c= 1) return n;\n            return fibonacci(n - 1) + fibonacci(n - 2);\n        }\n\n        console.log(\"Calculating security checksums...\");\n        for (let i = 0; i \u003c 35; i++) {\n            fibonacci(i);\n        }\n\n        console.log(\"Installation data: \" + wasteTime.substring(0, 100));\n    \u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n\u003e\u003e\u003e\u003e wp-admin/readme.html\nWordPress 6.2 — Readme (Just kidding, it's all fake.)\n\n\u003e\u003e\u003e\u003e wp-login.php\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n  \u003ctitle\u003eLogin\u003c/title\u003e\n  \u003cmeta name=\"robots\" content=\"noindex\"\u003e\n  \u003cstyle\u003e\n    body { font-family: sans-serif; }\n  \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n\u003ch1\u003eLogin\u003c/h1\u003e\n\u003cp\u003eLoading…\u003c/p\u003e\n\n\u003cscript\u003e\n// JS tarpit: burns bot CPU\nlet s = \"\";\nfor (let i = 0; i \u003c 50_000_000; i++) {\n  s += Math.random().toString(36).substring(2);\n}\ndocument.body.innerHTML += \"\u003cpre\u003e\" + s + \"\u003c/pre\u003e\";\n\u003c/script\u003e\n\n\u003c/body\u003e\n\u003c/html\u003e\n```\n",
      "summary": "Today I saw a random IP hitting an app server I had open via and it got me thinking that I need to take some precautions against these real world threats. So...",
      "date_published": "2025-12-04T21:17:24Z",
      "date_modified": "2025-12-04T21:17:24Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "llm",
        "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 kind 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/speakr-for-recording-and-transcribing-at-home/",
      "url": "https://pype.dev/speakr-for-recording-and-transcribing-at-home/",
      "title": "Speakr For Recording and Transcribing at Home",
      "content_html": "\u003cp\u003eI have been using Whisper-WebUI for several months in my homelab to record and\ntranscribe myself thinking out loud. It\u0026rsquo;s been great. Then I came across\n\u003ca href=\"https://github.com/murtaza-nasir/speakr\"\u003espeakr\u003c/a\u003e which is like that but more\nfeature rich in what I wanted out of the WebUI. Specifically I get a nicer UI\nwith transcriptions, a chat window, built-in summarization, and diarization\nsupport (WebUI has this too but it\u0026rsquo;s a little nicer in Speakr in my opinion).\u003c/p\u003e\n\u003ch2 id=\"pic\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePic\u003c/span\u003e \u003ca href=\"#pic\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250824120404_da722d66.png\" alt=\"20250824120404_da722d66.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eThe UI is a little busy but handy for what I\u0026rsquo;m after in the mornings or when\nI\u0026rsquo;m trying to mind-dump somewhere I can conveniently explore later.\u003c/p\u003e\n\u003ch2 id=\"speaker-identification\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSpeaker Identification\u003c/span\u003e \u003ca href=\"#speaker-identification\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eRequires HuggingFace Token to pull the models that perform \u003ca href=\"https://en.wikipedia.org/wiki/Speaker_diarisation\"\u003eSpeaker\ndiarisation\u003c/a\u003e - ie.\nidentifying different speakers\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s an example from an episode of Hybrid Cloud Show.\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250826124040_48c8d2ce.png\" alt=\"20250826124040_48c8d2ce.png\"\u003e\n\u003c/figure\u003e\n",
      "content_text": "\nI have been using Whisper-WebUI for several months in my homelab to record and\ntranscribe myself thinking out loud. It's been great. Then I came across\n[speakr](https://github.com/murtaza-nasir/speakr) which is like that but more\nfeature rich in what I wanted out of the WebUI. Specifically I get a nicer UI\nwith transcriptions, a chat window, built-in summarization, and diarization\nsupport (WebUI has this too but it's a little nicer in Speakr in my opinion).\n\n## Pic\n\n![20250824120404_da722d66.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250824120404_da722d66.png)\n\nThe UI is a little busy but handy for what I'm after in the mornings or when\nI'm trying to mind-dump somewhere I can conveniently explore later.\n\n## Speaker Identification\n\nRequires HuggingFace Token to pull the models that perform [Speaker\ndiarisation](https://en.wikipedia.org/wiki/Speaker_diarisation) - ie.\nidentifying different speakers\n\nHere's an example from an episode of Hybrid Cloud Show.\n\n![20250826124040_48c8d2ce.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250826124040_48c8d2ce.png)\n",
      "summary": "I have been using Whisper-WebUI for several months in my homelab to record and transcribe myself thinking out loud. It's been great. Then I came across...",
      "date_published": "2025-11-11T05:02:37Z",
      "date_modified": "2025-11-11T05:02:37Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "speakr",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/repoflow-robot-user/",
      "url": "https://pype.dev/repoflow-robot-user/",
      "title": "RepoFlow Robot User",
      "content_html": "\u003cp\u003eI\u0026rsquo;m trying to lean into \u003ca href=\"https://repoflow.io\"\u003erepoFlow\u003c/a\u003e at home, and model some slightly better\npatterns than open borders to artifacts across all my services. As I\u0026rsquo;m getting repoflow\ngoing I see that I can make users in the console, but then I do have to invite\nusers to workspaces with a manually generate invite link.\u003c/p\u003e\n\u003cp\u003eThe workspaces an abstraction layer, and in each workspace can be many kinds of\nrepositories. For my purposes I\u0026rsquo;ll probably have 2 workspaces, even though 1\nwould almost certainly suffice.\u003c/p\u003e\n\u003cp\u003eThere doesn\u0026rsquo;t seem to be a way to add a user to a workspace from the admin\nsetting so for a while I was pretty confused why I couldn\u0026rsquo;t add my robot user\nto a docker repository in my main workspace\u0026hellip;\u003c/p\u003e\n\u003cp\u003eAfter generating the invite link and accepting though then I see in the workspace settings a\nsimple click to give the robot user access to all repos (if you want)\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251111122419_580b2ad4.png\" alt=\"20251111122419_580b2ad4.png\"\u003e\n\u003c/figure\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251110162252_6c7b2fe9.png\" alt=\"20251110162252_6c7b2fe9.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e\u003ccode\u003eCan manage\u003c/code\u003e allows the user to mutate tags (ie. push \u003ccode\u003elatest\u003c/code\u003e docker tags). If\nthe user only has \u003ccode\u003eCan deploy\u003c/code\u003e then they can only create new artifacts (ie. not\nmutate any tags)\u003c/p\u003e\n",
      "content_text": "\nI'm trying to lean into [repoFlow](https://repoflow.io) at home, and model some slightly better\npatterns than open borders to artifacts across all my services. As I'm getting repoflow\ngoing I see that I can make users in the console, but then I do have to invite\nusers to workspaces with a manually generate invite link.\n\nThe workspaces an abstraction layer, and in each workspace can be many kinds of\nrepositories. For my purposes I'll probably have 2 workspaces, even though 1\nwould almost certainly suffice.\n\nThere doesn't seem to be a way to add a user to a workspace from the admin\nsetting so for a while I was pretty confused why I couldn't add my robot user\nto a docker repository in my main workspace...\n\nAfter generating the invite link and accepting though then I see in the workspace settings a\nsimple click to give the robot user access to all repos (if you want)\n\n![20251111122419_580b2ad4.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251111122419_580b2ad4.png)\n\n![20251110162252_6c7b2fe9.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20251110162252_6c7b2fe9.png)\n\n`Can manage` allows the user to mutate tags (ie. push `latest` docker tags). If\nthe user only has `Can deploy` then they can only create new artifacts (ie. not\nmutate any tags)\n",
      "summary": "I'm trying to lean into repoFlow at home, and model some slightly better patterns than open borders to artifacts across all my services. As I'm getting...",
      "date_published": "2025-11-10T09:24:25Z",
      "date_modified": "2025-11-10T09:24:25Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "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/chili-notes/",
      "url": "https://pype.dev/chili-notes/",
      "title": "Chili Notes",
      "content_html": "\u003cp\u003eI could\u0026rsquo;ve sworn I had a chili list going but alas\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"2025-10-04\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003e2025-10-04\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e2 lbs ground beef\n1 big can of pinto beans\n3 small cans of black beans\n3 cans diced tomatoes\n2 onions diced\u003c/p\u003e\n\u003cp\u003eThis is last minute chili for church pot-luck\u0026hellip; poor planning\u003c/p\u003e\n\u003cp\u003eI browned the meat and seasoned similar to how I might for tacos, but lighter on cumin and added chili powder.\u003c/p\u003e\n\u003cp\u003eDumped the browned meant, tomtoes, and beans into crock pot. Seasoned with a healthy amount of garlic powder, chili powder, cumin, and salt.\nBrowned the onions, and dumped in.\nLet stew for a few hours.\nEat.\u003c/p\u003e\n\u003ch2 id=\"review\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eReview\u003c/span\u003e \u003ca href=\"#review\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e1 bowl was great. The yellow onion still seem to add a little bit of heat, maybe could\u0026rsquo;ve sauteed them longer\u003c/li\u003e\n\u003c/ul\u003e\n",
      "content_text": "\nI could've sworn I had a chili list going but alas...\n\n# 2025-10-04\n\n2 lbs ground beef\n1 big can of pinto beans\n3 small cans of black beans\n3 cans diced tomatoes\n2 onions diced\n\nThis is last minute chili for church pot-luck... poor planning\n\nI browned the meat and seasoned similar to how I might for tacos, but lighter on cumin and added chili powder.\n\nDumped the browned meant, tomtoes, and beans into crock pot. Seasoned with a healthy amount of garlic powder, chili powder, cumin, and salt.\nBrowned the onions, and dumped in.\nLet stew for a few hours.\nEat.\n\n## Review\n\n- 1 bowl was great. The yellow onion still seem to add a little bit of heat, maybe could've sauteed them longer\n",
      "summary": "I could've sworn I had a chili list going but alas...",
      "date_published": "2025-10-04T13:42:09Z",
      "date_modified": "2025-10-04T13:42:09Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "cooking",
        "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 kind 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/thoughts-831/",
      "url": "https://pype.dev/thoughts-831/",
      "title": "💭 uv script management",
      "content_text": "\n\u003ca href=\"https://waylonwalker.com/uv-script-management/##sed+-i+'1i+#!/usr/bin/env+-S+uv+run+--script'+up\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://waylonwalker.com/uv-script-management/##sed+-i+'1i+#!/usr/bin/env+-S+uv+run+--script'+up\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - uv script management\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://waylonwalker.com/uv-script-management/##sed+-i+'1i+#!/usr/bin/env+-S+uv+run+--script'+up\"\u003euv script management\u003c/a\u003e\n\n---\n\nDang Waylon! Love these few lines... `uv` is great, been using it for environment and project requirements management but I didn't realize it was this easy to setup a pep 723 compliant script!\n\nLove this `sed -i '1i #!/usr/bin/env -S uv run --script'` little gem too - I typically do `uv run script.py` so I'll see about making this a simple zsh alias for faster iteration on python scripts... I often have AI write quick bash scripts to accomplish things, but it's quite a bit harder for me to read than python, and with the power `uv` is bringing to the python ecosystem, maybe it's fine to just be a little higher in the programming stack for script-kitty stuff\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 [[ thoughts ]]\n",
      "summary": "Dang Waylon! Love these few lines... uv is great, been using it for environment and project requirements management bu",
      "date_published": "2025-09-08T11:25:33Z",
      "date_modified": "2025-09-08T11:25:33Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "uv",
        "python",
        "dev",
        "tech",
        "thoughts"
      ]
    },
    {
      "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 uv 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/reflection-ai-work-and-adhd/",
      "url": "https://pype.dev/reflection-ai-work-and-adhd/",
      "title": "Reflection - AI Work and ADHD",
      "content_html": "\u003ch2 id=\"reflection\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eReflection\u003c/span\u003e \u003ca href=\"#reflection\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s time to be more devoted to my work\u0026hellip; AI has been a cool technology to\nwatch and experience but one thing I\u0026rsquo;m allowing it to do to me is to kill my love for\nbuilding\u0026hellip; I feel like I\u0026rsquo;m arriving where I\u0026rsquo;ve already seen many talented\ndevelopers get to -\u003c/p\u003e\n\u003cdiv class=\"admonition danger\"\u003e\n\u003cp class=\"admonition-title\"\u003erealization\u003c/p\u003e\n\u003cp\u003eAI is not magic, it\u0026rsquo;s a headache.\u003c/p\u003e\n\u003c/div\u003e\n\u003cp\u003eDon\u0026rsquo;t get me wrong, targeted and focused use of LLMs, with careful planning and\ntime-boxing can be great, and has been for me. But I am a creature and prone to\ntake the path of least resistance, and in many cases I\u0026rsquo;m just letting the LLM\ndo the work. This is where the ADHD element from the title comes into play.\nNow, I\u0026rsquo;m not blaming ADHD for me being lazy, because I\u0026rsquo;m not sure laziness is\nthe fairest descriptor of where I\u0026rsquo;ve been lately. A common issue for those with\nADHD, maybe even the defining characteristic, is the black-hole that\u0026rsquo;s present in the space\nwhere motiviation and reward belong in the brain. I\u0026rsquo;ve resonated with this thought for much of my life:\u003c/p\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eFulfilled or Relieved\u003c/p\u003e\n\u003cp\u003eWhen I finish tasks, I\u0026rsquo;m not fulfilled\u0026hellip; if anything I\u0026rsquo;m relieved.\u003c/p\u003e\n\u003c/div\u003e\n\u003cp\u003eThis is a core issue I am seeking to address in my life, and I guess in\nreal-time reflection I should thank the hellscape of AI and LLMs for surfacing\nthis pattern in my make-up today instead of when I\u0026rsquo;m in my 40s or 50s.\u003c/p\u003e\n\u003cp\u003eSo where I\u0026rsquo;ve been is that I\u0026rsquo;ll let an LLM write code and then I\u0026rsquo;ll attempt to\nbe validating the tests or example scripts I prompt it to generate, get\ncomfortable with it, and ship.\u003c/p\u003e\n\u003cp\u003eBut then in a few weeks the context of that code is all\nlost on me\u0026hellip; I didn\u0026rsquo;t craft it with blood and sweat and so when I\u0026rsquo;m asked a\nquestion about it I realize I\u0026rsquo;m barely fluent in a thing I built (did I even build it?)!\u003c/p\u003e\n\u003cp\u003eThis is embarrassing and exhausting\u0026hellip; I\u0026rsquo;m not sure what I\u0026rsquo;m gonna do about\nthis, definitely not write about how I\u0026rsquo;m going to change my life over this\ndissastisfaction and from this day forward everything will be different\u0026hellip;\u003c/p\u003e\n\u003cp\u003eBut today does feel like an infelction point in a trajectory I\u0026rsquo;ve been on with\nAI and my job, so not starting today, but just a decision I am making today, is\nto time-box every task with vigorous commitment.\u003c/p\u003e\n",
      "content_text": "\n## Reflection\n\nIt's time to be more devoted to my work... AI has been a cool technology to\nwatch and experience but one thing I'm allowing it to do to me is to kill my love for\nbuilding... I feel like I'm arriving where I've already seen many talented\ndevelopers get to - \n\n!!! danger \"realization\"\n\n    AI is not magic, it's a headache. \n\nDon't get me wrong, targeted and focused use of LLMs, with careful planning and\ntime-boxing can be great, and has been for me. But I am a creature and prone to\ntake the path of least resistance, and in many cases I'm just letting the LLM\ndo the work. This is where the ADHD element from the title comes into play.\nNow, I'm not blaming ADHD for me being lazy, because I'm not sure laziness is\nthe fairest descriptor of where I've been lately. A common issue for those with\nADHD, maybe even the defining characteristic, is the black-hole that's present in the space\nwhere motiviation and reward belong in the brain. I've resonated with this thought for much of my life:\n\n!!! note \"Fulfilled or Relieved\"\n\n    When I finish tasks, I'm not fulfilled... if anything I'm relieved.\n\nThis is a core issue I am seeking to address in my life, and I guess in\nreal-time reflection I should thank the hellscape of AI and LLMs for surfacing\nthis pattern in my make-up today instead of when I'm in my 40s or 50s.\n\nSo where I've been is that I'll let an LLM write code and then I'll attempt to\nbe validating the tests or example scripts I prompt it to generate, get\ncomfortable with it, and ship. \n\nBut then in a few weeks the context of that code is all\nlost on me... I didn't craft it with blood and sweat and so when I'm asked a\nquestion about it I realize I'm barely fluent in a thing I built (did I even build it?)! \n\nThis is embarrassing and exhausting... I'm not sure what I'm gonna do about\nthis, definitely not write about how I'm going to change my life over this\ndissastisfaction and from this day forward everything will be different... \n\nBut today does feel like an infelction point in a trajectory I've been on with\nAI and my job, so not starting today, but just a decision I am making today, is\nto time-box every task with vigorous commitment.\n",
      "summary": "It's time to be more devoted to my work... AI has been a cool technology to watch and experience but one thing I'm allowing it to do to me is to kill my love...",
      "date_published": "2025-08-27T07:16:57Z",
      "date_modified": "2025-08-27T07:16:57Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "adhd",
        "tech",
        "reflection"
      ]
    },
    {
      "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/proxy-pull-docker-images-from-self-hosted-container-registry-through-self-hosted-repoflow/",
      "url": "https://pype.dev/proxy-pull-docker-images-from-self-hosted-container-registry-through-self-hosted-repoflow/",
      "title": "Proxy Pull Docker Images From Self-Hosted Container Registry Through Self-Hosted Repoflow",
      "content_html": "\u003cp\u003eThis post is a short write-up of an issue I had while exploring \u003ca href=\"https://repoflow.io\"\u003eReploflow\u003c/a\u003e - a\nsuper solid artifactory-esq replacement for the homelab (and enterprise!). I\u0026rsquo;ve been playing with\nit and have tried to setup a few artifact repos that I\u0026rsquo;m familiar with - docker\nand pypi.\u003c/p\u003e\n\u003ch2 id=\"pypi\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePyPi\u003c/span\u003e \u003ca href=\"#pypi\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThis isn\u0026rsquo;t a post about repoflow, but I had an issue with pypi and Tomer was\nEXTREMELY responsive in helping to fix my issue and patch repoflow within a day\nof me reporting the issue via email!\u003c/p\u003e\n\u003ch2 id=\"docker\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDocker\u003c/span\u003e \u003ca href=\"#docker\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFor docker I have a few options I just wanted to see work - obviously\nproxy pull from Dockerhub for ease, but also in the interim I\u0026rsquo;d like to proxy\npull from my original container registry to avoid having to update any homelab config just yet.\nNow, obviously I\u0026rsquo;ll have to update all my build pipelines to start using\nrepoflow, and my homelab configuration to reference the repoflow docker\nrepository rather than my existing registry.\u003c/p\u003e\n\u003cp\u003eBut there\u0026rsquo;s something to bear in mind if maintaining that middle registry -\nturns out a there\u0026rsquo;s a default namespace in dockerhub \u003ccode\u003e/library/\u003c/code\u003e and repoflow\nexpects this standard across any docker registry, so it\u0026rsquo;s pull paths fully\nresolve to an incorrect tag if you don\u0026rsquo;t take this into account in your\nexisting infrastructure.\u003c/p\u003e\n\u003cp\u003eTLDR: if you tag an image \u003ccode\u003emyregistry/ubuntu:latest\u003c/code\u003e and push it\nthen try to pull it back through repoflow then repoflow expects the image to be\n\u003ccode\u003emyregistry/library/ubuntu:latest\u003c/code\u003e. I think repoflow should support\nconfigurating the namespace/org for the images - but in the meantime I can\ngo find all my images and update my build scripts to add /library/ to my\ntags\u0026hellip; or while I\u0026rsquo;m doing that I can just push to repoflow directly\u0026hellip; All in\nall, nice little lesson on namespaces in docker repos and third-party tooling.\u003c/p\u003e\n",
      "content_text": "\nThis post is a short write-up of an issue I had while exploring [Reploflow](https://repoflow.io) - a\nsuper solid artifactory-esq replacement for the homelab (and enterprise!). I've been playing with\nit and have tried to setup a few artifact repos that I'm familiar with - docker\nand pypi. \n\n## PyPi\n\nThis isn't a post about repoflow, but I had an issue with pypi and Tomer was\nEXTREMELY responsive in helping to fix my issue and patch repoflow within a day\nof me reporting the issue via email!\n\n## Docker\n\nFor docker I have a few options I just wanted to see work - obviously\nproxy pull from Dockerhub for ease, but also in the interim I'd like to proxy\npull from my original container registry to avoid having to update any homelab config just yet.\nNow, obviously I'll have to update all my build pipelines to start using\nrepoflow, and my homelab configuration to reference the repoflow docker\nrepository rather than my existing registry. \n\nBut there's something to bear in mind if maintaining that middle registry -\nturns out a there's a default namespace in dockerhub `/library/` and repoflow\nexpects this standard across any docker registry, so it's pull paths fully\nresolve to an incorrect tag if you don't take this into account in your\nexisting infrastructure.\n\nTLDR: if you tag an image `myregistry/ubuntu:latest` and push it\nthen try to pull it back through repoflow then repoflow expects the image to be\n`myregistry/library/ubuntu:latest`. I think repoflow should support\nconfigurating the namespace/org for the images - but in the meantime I can\ngo find all my images and update my build scripts to add /library/ to my\ntags... or while I'm doing that I can just push to repoflow directly... All in\nall, nice little lesson on namespaces in docker repos and third-party tooling. \n\n",
      "summary": "This post is a short write-up of an issue I had while exploring Reploflow - a super solid artifactory-esq replacement for the homelab (and enterprise!). I've...",
      "date_published": "2025-08-12T06:59:02Z",
      "date_modified": "2025-08-12T06:59:02Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "repoflow"
      ]
    },
    {
      "id": "https://pype.dev/a-simple-lua-plugin-to-find-my-drafts/",
      "url": "https://pype.dev/a-simple-lua-plugin-to-find-my-drafts/",
      "title": "A Simple Lua Plugin To Find My Drafts",
      "content_text": "\nI used windsurf to write [[daily-notes-neovim-plugin]] for navigating my daily notes in neovim.\n\nFor a while now I've wanted a way to see my blog drafts... and tonight finally got the massive courage to prompt gpt-5 to build it for me...\n\n## Screenshot\n\n\n![20250811020234_e0c02600.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250811020234_e0c02600.png)\n\n## Code\n\n```lua\n---@diagnostic disable: undefined-global\n-- Blogging plugin: find draft blog posts under `pages/`\n-- Draft = YAML front matter contains `published: false` (case-insensitive)\n\nlocal M = {}\n\n-- Read file and return YAML front matter block as string, or nil\nlocal function read_front_matter(path)\n  local fh = io.open(path, \"r\")\n  if not fh then\n    return nil\n  end\n  local content = fh:read(\"*a\")\n  fh:close()\n  if not content then\n    return nil\n  end\n\n  -- Front matter starts with --- on first line and ends with --- on its own line\n  -- Support optional CRLF\n  local start_s, start_e = content:find(\"^%-%-%-%s*\\r?\\n\")\n  if not start_e then\n    return nil\n  end\n  local fm_end_s, fm_end_e = content:find(\"\\n%-%-%-%s*\\r?\\n\", start_e + 1)\n  if not fm_end_s then\n    return nil\n  end\n  -- Extract between the fence lines (exclude the starting and ending --- lines)\n  local front_matter = content:sub(start_e + 1, fm_end_s)\n  return front_matter\nend\n\n-- Determine if a file is a draft by inspecting front matter for published: false\nfunction M._is_draft(path)\n  local fm = read_front_matter(path)\n  if not fm then\n    return false\n  end\n  -- Find a line starting with `published:` and capture the value\n  -- Trim comments and quotes, compare case-insensitively\n  local value = fm:match(\"[Pp][Uu][Bb][Ll][Ii][Ss][Hh][Ee][Dd]%s*:%s*([^\\n\\r]+)\")\n  if not value then\n    return false\n  end\n  -- strip inline comments starting with # and surrounding quotes/space\n  value = value:gsub(\"#.*$\", \"\")\n  value = value:gsub('\"', \"\"):gsub(\"'\", \"\")\n  value = value:gsub(\"^%s+\", \"\"):gsub(\"%s+$\", \"\")\n  local v = value:lower()\n  if v == \"false\" or v == \"no\" or v == \"0\" then\n    return true\n  end\n  return false\nend\n\n-- Telescope picker for draft posts\nfunction M.find_drafts()\n  local ok_pickers, pickers = pcall(require, \"telescope.pickers\")\n  if not ok_pickers then\n    vim.notify(\"telescope.nvim not available\", vim.log.levels.ERROR)\n    return\n  end\n  local finders = require(\"telescope.finders\")\n  local previewers = require(\"telescope.previewers\")\n  local conf = require(\"telescope.config\").values\n  local actions = require(\"telescope.actions\")\n  local action_state = require(\"telescope.actions.state\")\n\n  -- Collect markdown files under pages/\n  local files = vim.fn.glob(\"pages/**/*.md\", false, true)\n  local drafts = {}\n  for _, f in ipairs(files) do\n    if M._is_draft(f) then\n      table.insert(drafts, f)\n    end\n  end\n\n  if #drafts == 0 then\n    vim.notify(\"No draft blog posts found in pages/\", vim.log.levels.INFO)\n    return\n  end\n\n  -- Simple, version-agnostic entry maker\n  local function entry_maker_fn(line)\n    return {\n      value = line,\n      ordinal = line,\n      display = vim.fn.fnamemodify(line, \":~:.\") or line,\n      path = line,\n    }\n  end\n\n  -- Use Telescope builtin find_files with a custom command that lists only drafts.\n  -- This guarantees previews and matches the style of other file pickers.\n  local tmpfile = vim.fn.tempname()\n  local ok_write, err = pcall(function()\n    local fh = assert(io.open(tmpfile, \"w\"))\n    for _, f in ipairs(drafts) do\n      fh:write(f .. \"\\n\")\n    end\n    fh:close()\n  end)\n  if not ok_write then\n    vim.notify(\"Failed to prepare drafts list: \" .. tostring(err), vim.log.levels.ERROR)\n    return\n  end\n\n  local builtin = require(\"telescope.builtin\")\n  builtin.find_files({\n    prompt_title = \"Draft Blog Posts\",\n    -- cat the tmp list so Telescope treats it like find output\n    find_command = { \"bash\", \"-lc\", string.format('cat %q', tmpfile) },\n    attach_mappings = function(prompt_bufnr, map)\n      local actions = require(\"telescope.actions\")\n      actions.close:enhance({\n        post = function()\n          os.remove(tmpfile)\n        end,\n      })\n      return true\n    end,\n  })\nend\n\n-- Expose a user command similar to daily plugin style\nvim.api.nvim_create_user_command(\"BlogDrafts\", function()\n  M.find_drafts()\nend, {})\n\nreturn M\n\n\n```\n\n\n",
      "summary": "I used windsurf to write daily-notes-neovim-plugin for navigating my daily notes in neovim.",
      "date_published": "2025-08-10T20:56:33Z",
      "date_modified": "2025-08-10T20:56:33Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "neovim",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/audiomass/",
      "url": "https://pype.dev/audiomass/",
      "title": "AudioMass",
      "content_html": "\u003cp\u003eI edit our church\u0026rsquo;s recorded sermons every week and upload them to our website.\nIt\u0026rsquo;s very simple, and the editing is just cutting the audio file down on the\nfront and back sides of extra stuff picked up while the mic was on. I\u0026rsquo;ve been\ndoing this for a while using Davinci Resolve which is WAY too heavy for\ntrimming audio files but I\u0026rsquo;m loosely familiar with it and so I just have been\ndealing with the pain of long start up, overly complicated organization, etc.\nWell it\u0026rsquo;s been buggy on Aurora for me and a few weeks ago I got sick of it\nfinally and tried KdenLive\u0026hellip;. Now this is a great tool, but again I\u0026rsquo;m just\ncutting up some audio files, why is this hard?\u003c/p\u003e\n\u003cp\u003eDouble finally I just did some research and after a few minutes found\n\u003ca href=\"https://github.com/pkalogiros/AudioMass\"\u003eaudiomass\u003c/a\u003e which I immediately forked\nand dockerized cause it\u0026rsquo;s so simple and beautiful!\u003c/p\u003e\n\u003cp\u003eNow I have a little webapp to trim these files from - almost such that the tech\nlead on Sunday could maybe do this instead of me!\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250810191909_cc0dc2d7.png\" alt=\"20250810191909_cc0dc2d7.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eYou can check it out on \u003ca href=\"https://github.com/pypeaday/audiomass\"\u003emy github\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eclone\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003edocker compose up -d\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003ehttp://localhost:5056\u003c/code\u003e to begin trimming audio locally!\u003c/li\u003e\n\u003c/ul\u003e\n",
      "content_text": "\nI edit our church's recorded sermons every week and upload them to our website.\nIt's very simple, and the editing is just cutting the audio file down on the\nfront and back sides of extra stuff picked up while the mic was on. I've been\ndoing this for a while using Davinci Resolve which is WAY too heavy for\ntrimming audio files but I'm loosely familiar with it and so I just have been\ndealing with the pain of long start up, overly complicated organization, etc.\nWell it's been buggy on Aurora for me and a few weeks ago I got sick of it\nfinally and tried KdenLive.... Now this is a great tool, but again I'm just\ncutting up some audio files, why is this hard?\n\nDouble finally I just did some research and after a few minutes found\n[audiomass](https://github.com/pkalogiros/AudioMass) which I immediately forked\nand dockerized cause it's so simple and beautiful!\n\nNow I have a little webapp to trim these files from - almost such that the tech\nlead on Sunday could maybe do this instead of me!\n\n![20250810191909_cc0dc2d7.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250810191909_cc0dc2d7.png)\n\nYou can check it out on [my github](https://github.com/pypeaday/audiomass)\n\n  - clone\n  - `docker compose up -d`\n  - `http://localhost:5056` to begin trimming audio locally!\n",
      "summary": "I edit our church's recorded sermons every week and upload them to our website. It's very simple, and the editing is just cutting the audio file down on the...",
      "date_published": "2025-08-10T14:17:43Z",
      "date_modified": "2025-08-10T14:17:43Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "sermons",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/using-litestream-to-restore-my-database-for-easy-development/",
      "url": "https://pype.dev/using-litestream-to-restore-my-database-for-easy-development/",
      "title": "Using Litestream to Restore My Database for Easy Development",
      "content_text": "\n# Litestream\n\nsee [[using-litestream-to-backup-quadtasks-sqlite-db]] for how I setup litestream replication for [[quadtask]]\n\nI have the entrypoint to my app container check if the sqlite database exists\nor not - in production or with local volume mounts, it will and then we start\nlitestream replication and then the app like normal. But what's awesome about\nthis little trick I got from [this litestream\nexample](https://github.com/benbjohnson/litestream-docker-example/tree/main) is\nI can docker compose up locally without the data directory volume mounted in,\nand get a fresh copy of my production data right there locally to work with!\n\nMy entrypoint script now looks like:\n\n```bash\n#!/bin/bash\n\n# Exit on error, undefined vars, and fail on pipe errors\nset -euo pipefail\n# Restore the database if it does not already exist.\nif [ -f /app/data/quadtask.db ]; then\n echo \"Database already exists, skipping restore\"\nelse\n echo \"No database found, restoring from replica if exists\"\n litestream restore -v /app/data/quadtask.db\nfi\n# Run litestream with your app as the subprocess.\nexec litestream replicate -exec \"uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --log-level debug\"\n\n```\n\n## Appropriate and Safe Replication\n\nThe way to get prod data in automatically is to restore from the production\nbackup but I don't want to replicate to the production backup with my local\nwork, so it's not the best but I have a manual step that makes it pretty easy\n\nI first `just sync-db`\n\n```bash\n# Sync production database locally\nsync-db:\n    fly ssh console -a quadtask -C 'cat /app/data/quadtask.db' \u003e ./litestream/source/quadtask-prod.db\n```\n\nand this brings my database down to a folder I maintain locally.\n\nI have litestream installed on my desktop obviously, and here's the relevant litestream configuration I use:\n\n```yaml\ndbs:\n  - path: /home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db\n    replicas:\n      - name: quadtask-prod\n        type: s3\n        bucket: litestream\n        path: quadtask-local\n        endpoint: s3.example.com\n        skip-verify: false\n        region: us-east-1\n```\n\nSo on my desktop if I `litestream replicate` then basically I'm taking the\noutput of `just synd-db` and replicating to my litestream bucket to the\n`quadtask-local` path.\n\nThen I start up my app, it restores from that point and I'm working with fresh\nprod data!\n\n## How To Handle Environments?\n\nI have this working for different environments too, with a little bit of\nprocess but basically thanks to [litestream expanding env\nvars](https://litestream.io/reference/config/#variable-expansion) in the config\nwe can do something like this:\n\n```yaml\ndbs:\n  - path: /app/data/quadtask.db\n    replicas:\n      - name: quadtask\n        type: s3\n        bucket: litestream\n        path: quadtask-${ENVIRONMENT}\n        endpoint: s3.example.com\n        skip-verify: false\n        region: us-east-1\n```\n\nAnd so wherever I spin up quadtask: local, dev, or prod - the replication will\nhappen to and from a different path in my litestream bucket!\n\n# Update\n\nI updated the `docker-entrypoint.sh` of my quadtask container to look like this now:\n\n```bash\n\n# Restore the database if it does not already exist.\nif [ -f /app/data/quadtask.db ]; then\n echo \"Database already exists, skipping restore\"\nelse\n echo \"No database found, restoring from replica\"\n ENVIRONMENT=\"prod\" litestream restore -v /app/data/quadtask.db\nfi\n# Run litestream with your app as the subprocess.\nexec litestream replicate -exec \"uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload --log-level debug\"\n\n```\n\nThe `if` block checks if there's a db at the filepath it should be in in the continer - if it's not there is the little bit of magic to me right now...\n\nMy litestream config has a parameterized `ENVIRONMENT` variable in the db path,\nso by overwritting it for just the `restore` command, I can very simply always\npull a fresh copy of the prod database into an ephemeral container to test\nthings against real data in a very convenient way\n",
      "summary": "see using-litestream-to-backup-quadtasks-sqlite-db for how I setup litestream replication for quadtask",
      "date_published": "2025-08-07T15:14:45Z",
      "date_modified": "2025-08-07T15:14:45Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "litestream",
        "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/using-litestream-to-backup-quadtasks-sqlite-db/",
      "url": "https://pype.dev/using-litestream-to-backup-quadtasks-sqlite-db/",
      "title": "Using Litestream to Backup QuadTask’s SQLite DB",
      "content_text": "\n# Litestream\n\nThis post will be a walkthrough of installing [[litestream]] in a docker container\nthat runs my FastAPI app in order to replicate the sqlite db in the data volume\ncurrently hosted in Fly's volume manager to my MinIO instance. From there, on\nmy development machine I can use litestream to restore the prod database to my\ndev environment to practice whatever I may need to practice with real\nproduction data!\n\n## Dockerfile\n\nInstalling litestream in the dockerfile is 2-3 lines of code - no sweat. Configuration file is further down the post.\n\n```dockerfile\nFROM python:3.11-slim\nWORKDIR /app\n\n# Install uv\nCOPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/\n\n# Install litestream\nADD https://github.com/benbjohnson/litestream/releases/download/v0.3.8/litestream-v0.3.8-linux-amd64-static.tar.gz /tmp/litestream.tar.gz\nRUN tar -C /usr/local/bin -xzf /tmp/litestream.tar.gz\nCOPY etc/litestream.yml /etc/litestream.yml\n\n# Install dependencies\nCOPY requirements.txt .\nRUN uv pip install --no-cache -r requirements.txt\n\n# Copy the rest of the application\nCOPY . .\n\n# Install the app package in development mode\nRUN uv pip install -e .\n\n# Run the application\nCMD [\"docker-entrypoint.sh\"]\n\n```\n\n## Litestream Configuration\n\nTaking care to set `ENVIRONMENT` appropriately in each environment, the\nlitestream configuration gets that env var templated out to the path for the\nlitestream target. See [docs](https://litestream.io/reference/config/#variable-expansion)\n\n```yaml\n#/etc/litestream.yml\ndbs:\n- path: /app/data/quadtask.db\n  replicas:\n  - name: quadtask\n    type: s3\n    bucket: litestream\n    path: quadtask-${ENVIRONMENT}\n    endpoint: s3.example.com\n    skip-verify: false\n    region: us-east-1\n```\n\n## Authentication\n\nIn your docker container you'll need to set `LITESTREAM_ACCESS_KEY_ID` and\n`LITESTREAM_SECRET_ACCESS_KEY` which you setup in Minio - see\n[[setup-remote-minio-s3-backend-target-for-litestream]]. I have these set as\nsecrets in my fly configuration, and locally in a .env file that gets sourced\nwith my compose stack\n\n## Example Log from Dev\n\nMy fly dev machine has this log message showing the `ENVIRONMENT` var `dev` gets expanded into the config so the s3 path includes `quadtask-dev`\n\n`replicating to: name=\"quadtask\" type=\"s3\" bucket=\"litestream\" path=\"quadtask-dev\" region=\"us-east-1\" endpoint=\"s3.\u003credacted\u003e.com\" sync-interval=1s`\n\n## Example Usage for Local Development\n\n- [[using-litestream-to-restore-my-database-for-easy-development]]\n",
      "summary": "This post will be a walkthrough of installing litestream in a docker container that runs my FastAPI app in order to replicate the sqlite db in the data...",
      "date_published": "2025-08-05T19:34:19Z",
      "date_modified": "2025-08-05T19:34:19Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "litestream",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/setup-remote-minio-s3-backend-target-for-litestream/",
      "url": "https://pype.dev/setup-remote-minio-s3-backend-target-for-litestream/",
      "title": "Setup Remote MinIO S3 Backend Target for Litestream",
      "content_text": "\n## Intro\n\nI am starting to think through some patterns for replicating sqlite databases,\nexploring them, standardizing on schemas/models I use across projects, etc...\nThanks to [Waylon](https://waylonwalker.com) I know about [[litestream]] and\nfinally I started playing with it. Their\n[quickstart](https://litestream.io/getting-started/) is good, but it assumes\nyou'll run MinIO on the same machine you run litestream on.\n\nI, however, have MinIO setup in my homelab with the api endpoint accessible via\nDNS - `s3.mydomain.com`.\n\nBefore starting - I realize I left the credential aspect out - Litestream\nexplains how to setup API keys in MinIO for their application\n[here](https://litestream.io/getting-started/#setting-up-minio)\n\nBasically after generating the keys I set `LITESTREAM_ACCESS_KEY_ID` and `LITESTREAM_SECRET_ACCESS_KEY` as env vars\n\nSo let's see what happens and then get to [the fix]\n\n\n```\n⬢ [devbox] ❯ litestream replicate quadtask-prod.db s3://s3.example.com/litestream/qt.db    \n2025/08/05 08:50:09 INFO litestream version=v0.3.13\n2025/08/05 08:50:09 INFO initialized db path=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db\n2025/08/05 08:50:09 INFO replicating to name=s3 type=s3 sync-interval=1s bucket=s3.example.com path=litestream/qt.db region=\"\" endpoint=\"\"\n2025/08/05 08:50:10 INFO sync: new generation db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db generation=209692854ccd85d5 reason=\"no generation exists\"\n2025/08/05 08:50:10 ERROR monitor error db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=s3 error=\"cannot lookup bucket region: InvalidAccessKeyId: The AWS Access Key Id you provided does not exist in our records.\\n\\tstatus code: 403, request id: ERHZD4RD61Y8ADMD, host id: dOjPn5j8Oa+mkwOqROXDvCdKNxzxG0B8JJOPtGXDZA7qKxwAWqaSXhGYzK2mkoJQV7hJhaoJcag=\"\n\n```\n\nSo that `403` is pretty simple... my login is wrong... but wait...\n\n`2025/08/05 08:50:09 INFO replicating to name=s3 type=s3 sync-interval=1s bucket=s3.example.com path=litestream/qt.db region=\"\" endpoint=\"\"`\n\nNotice this line - `endpoint=\"\"`... so `s3://s3.example.com` was not the right replacement for `s3://mybkt.localhost:9000/target.db`. Also the inferred `bucket` is the entire `s3.example.com`... \n\nOh! Well I didn't specify a bucket in the subdomain - I did it with a route on the URL... \n\nok let's try `litestream replicate quadtask-prod.db s3://litestream.example.com/qt.db`\n\nWe got the same kind of error...\n\n`2025/08/05 08:53:45 INFO replicating to name=s3 type=s3 sync-interval=1s bucket=litestream.example.com path=qt.db region=\"\" endpoint=\"\"`\n\nThis time the inferred bucket was `litestream.example.com`... ok that's consistent with above as well - so the endpoint is still wrong/null and the bucket inference is wrong...\n\nTurns out for Litestream, and probably many other tools that use s3-compatible storage, that there's an inference problem if the configuration leans on the shorthand `s3://` - [see this GitHub issue](https://github.com/benbjohnson/litestream/issues/398) and [this one about localhost](https://github.com/benbjohnson/litestream/issues/219). There's a few options for using your own `s3.mydomain.example`.\n\n## The Fix\n\nIf you're familiar with the `awscli` then maybe you've heard of a config option `AWS_S3_ENDPOINT_URL` which allows you to use the boto sdk with s3-compatibile resources while still using the `s3://` shorthand. Litestream has a way to set this configuration right on config:\n\n```yaml\ndbs:\n  - path: /home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db\n    replicas:\n      - type: s3\n        bucket: litestream\n        endpoint: s3.example.com  # \u003c---- right here!\n        skip-verify: true  # set to false if you have TLS and want more security\n        # note you acn set these sensitive values as env vars per the Litestream Quickstart\n        # access-key-id: \u003cACCESS-KEY-ID\u003e\n        # secret-access-key: \u003cSECRET-ACCESS-KEY\u003e\n```\n\n???+ danger \"UPDATE\"\n\n    I added a `path` key to give the backups a folder basically on s3, so under \"bucket: listream\" I have \"path: \u003cmy desired path\u003e\"\n\nSo with that config in place we can actually JUST `litestream replicate` from anywhere...\n\n```bash\n⬢ [devbox] ❯ litestream replicate                                                      \ntime=2025-08-05T08:56:11.353-05:00 level=INFO msg=litestream version=v0.3.13\ntime=2025-08-05T08:56:11.353-05:00 level=INFO msg=\"initialized db\" path=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db\ntime=2025-08-05T08:56:11.353-05:00 level=INFO msg=\"replicating to\" name=s3 type=s3 sync-interval=1s bucket=litestream path=\"\" region=\"\" endpoint=s3.example.com\ntime=2025-08-05T08:56:12.399-05:00 level=INFO msg=\"write snapshot\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=s3 position=209692854ccd85d5/00000000:4152\ntime=2025-08-05T08:56:12.489-05:00 level=INFO msg=\"snapshot written\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=s3 position=209692854ccd85d5/00000000:4152 elapsed=89.994626ms sz=909437\ntime=2025-08-05T08:56:12.492-05:00 level=INFO msg=\"write wal segment\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=s3 position=209692854ccd85d5/00000000:0\ntime=2025-08-05T08:56:12.510-05:00 level=INFO msg=\"wal segment written\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=s3 position=209692854ccd85d5/00000000:0 elapsed=17.572784ms sz=4152\n```\n\n!!! success \"It works!\"\n    \n    Notice that the endpoint is properly s3.example.com, and the bucket is litestream (the name of my taret bucket in MinIO)\n\n\u003e You can use a local config with `-c config.yaml` or something if you want to avoid the global `/etc/litestream.yml`\n\n## What About Restore?\n\nShould work just fine... Let's move the database on my host and see if I can use litestream to restore it\n\n```\nnic in quadtask/litestream   database-clean-up   ×21  ×2  ×9 via   v3.11.10(quadtask)   (dev) 󰒄 \n⬢ [devbox] ❯ mv ./source/quadtask-prod.db ./source/quadtask-prod.db.backup                 \n\nnic in quadtask/litestream   database-clean-up   ×21  ×2  ×9 via   v3.11.10(quadtask)   (dev) 󰒄 \n⬢ [devbox] ❯ litestream restore ./source/quadtask-prod.db              \ntime=2025-08-05T09:06:25.826-05:00 level=INFO msg=\"restoring snapshot\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index=0 path=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db.tmp\ntime=2025-08-05T09:06:25.843-05:00 level=INFO msg=\"restoring wal files\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index_min=0 index_max=0\ntime=2025-08-05T09:06:25.845-05:00 level=INFO msg=\"downloaded wal\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index=0 elapsed=2.103514ms\ntime=2025-08-05T09:06:25.856-05:00 level=INFO msg=\"applied wal\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index=0 elapsed=10.871034ms\ntime=2025-08-05T09:06:25.856-05:00 level=INFO msg=\"renaming database from temporary location\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod\n\n```\n\nThe command `litestream restore \u003cdb path\u003e` was confusing at first because I was trying to replicate to a new place, but couldn't get the config to line up. The general understanding here is DR I believe - so the configuration is relatively static and when you `restore` you are restoring a specific database, not just standing up a copy from a backup... \n\n!!! note \"But would we setup a copy from a backup?\"\n\n    ie. restore to a different file?\n\nYes, but it's confusing - at least it was to me... it looks to me like litestream uses the path of a db as the identifier in the config, and you reference the config not by an object's name (if it has one) but by the `path`...\n\nSo the command is `litestream restore -o \u003coutput path\u003e \u003cdb path\u003e`. This makes sense in light of the fact that the configuration is relative to this host - so `\u003cdb path\u003e` being the path of the source db whose backup you are restoring from to `\u003coutput path\u003e` makes some amount of sense... if I wanted to replicate to another machine, then THAT machine would need `/etc/litestream.yaml` and if the filesystem hierarchy was different then an appropriate `path` for the \"source\" of the db to replicate back to.\n\n```\n⬢ [devbox] ❯ litestream restore -o /tmp/db ./source/quadtask-prod.db          \ntime=2025-08-05T09:09:04.784-05:00 level=INFO msg=\"restoring snapshot\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index=0 path=/tmp/db.tmp\ntime=2025-08-05T09:09:04.797-05:00 level=INFO msg=\"restoring wal files\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index_min=0 index_max=0\ntime=2025-08-05T09:09:04.799-05:00 level=INFO msg=\"downloaded wal\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index=0 elapsed=2.128594ms\ntime=2025-08-05T09:09:04.800-05:00 level=INFO msg=\"applied wal\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod generation=209692854ccd85d5 index=0 elapsed=610.75µs\ntime=2025-08-05T09:09:04.800-05:00 level=INFO msg=\"renaming database from temporary location\" db=/home/nic/projects/personal/quadtask/litestream/source/quadtask-prod.db replica=quadtask-prod\n\n```\n\n",
      "summary": "I am starting to think through some patterns for replicating sqlite databases, exploring them, standardizing on schemas/models I use across projects, etc......",
      "date_published": "2025-08-05T08:39:16Z",
      "date_modified": "2025-08-05T08:39:16Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "litestream",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/docker-volume-mounts-link-to-an-inode/",
      "url": "https://pype.dev/docker-volume-mounts-link-to-an-inode/",
      "title": "Docker Volume Mounts Link to an Inode",
      "content_text": "\ndocker bind mounts are specified as filepaths - this is very intuitive. I want\n/path/to/directory on my host to be shared with /another/path/foo in a\ncontainer. But what if /path/to/directory on the host is moved?\n\nThis is a situation I have found myself in at home and it turns out that I'm\nactually in almost zero trouble thankfully...\n\nTLDR - I have a bunch of compose stacks in\n/home/projects/homelab-compose/\u003chost\u003e/\u003capplication\u003e and I want to move them to\n/home/projects/homelap-mono/compose/\u003chost\u003e/\u003capplication\u003e\n\nI don't want to take down all my stacks to do this because I don't yet have a\nconvenient way to do it - but can I just `mv .../homelab-compose\n.../homelab-mono/compose` and be done with it?\n\nThe issue is that some of my stacks use local volume mounts - ie. there is a\ndata volume on the host in\n/home/projects/homelab-compose/\u003chost\u003e\u003capplication\u003e/docker-data for some of the\napplications... can I just move everything and docker is nonethewiser?\n\nTurns out the answer is mostly `yes` with some caveats to keep in mind..\n\nLet's start with why it works and then show an example...\n\n## Why\n\nThis works because of how docker interprets the bind mount location initially\nand with how `mv` is different from `cp`. When you speicify a volume mount for\ndocker, the engine determines which [[inode]] contains the metadata for the\nfilesystem object referenced by the original volume path `/path/to/directory`.\nI think about this like a string path is a pointer to a lower level identifier\ncloser to the filesystem - if that pointer changes it doesn't affect the\npre-existing inode or data. So docker uses the inode that `/path/to/directory`\npoints to when the container starts up.\n\nThe slight convenience in my case is that `mv` maintains the existing inode and\nso the new filepath is irrelevant to that existing container that was spun up\nwith a now-non-existent host filepath.\n\nSo if you can imagine then - I spun up several containers in\n`///homelab-compose/...` with many stacks - and I want to just move all the\nconfig files to new repo... thankfully, I actually can *just `mv`* everything\nand because the `inode` locations dont' change, the stacks are fine!\n\n!!! warning \"caveat\"\n\n    Is the next issue apparent? What happens to those files whose volume mount configurations specify .../homelab-compose/...? Well, if the stacks are brought down and back up then docker will create that filepath on the host and a new inode reference will be had by all... so that's a problem easily mitigated by some string replacement!\n\n!!! note \"another note\"\n\n\n    Perhaps just don't keep data next to your configuration either... In most of my stacks I have a determined docker volume that's backed up etc. on my hosts and that's what I'm moving towards on my desktop, but as of right now I've put myself in an odd situation via not participating in a proper thinking exercise when standing up all these stacks on my desktop\n\n## Example\n\nFinally let's just see it in action quickly...\n\nI have a directory `/tmp/foo/source` with a compose file in it.\n\n```\n❯ tree source       \nsource\n└── docker-compose.yml\n```\n\nThat file is simple:\n\n```yaml\nservices:\n  app:\n    image: nginx\n    ports:\n      - \"8070:80\"\n    volumes:\n      - /tmp/foo/source:/usr/share/nginx/html\n```\n\nNow I can spin that container up, exec in, drop a file in `/usr/share/nginx/html` and we'll see it in `/tmp/foo/source` as expected...\n\n```bash\nnic in /tmp/foo   (dev)\n❯ docker exec source-app-1 touch /usr/share/nginx/html/file.txt\n\nnic in /tmp/foo   (dev)\n❯ tree source                                                       \nsource\n├── docker-compose.yml\n└── file.txt\n\n1 directory, 2 files\n\n```\n\nNow let's `mv` the `source` directory somewhere else and see what happens\n\n```\nnic in /tmp/foo   (dev)\n❯ mv source target \n\nnic in /tmp/foo   (dev)\n❯ tree target \ntarget\n└── source\n    ├── docker-compose.yml\n    └── file.txt\n\n2 directories, 2 files\n\n```\n\nOK everything is there - now let's drop another file in the container - remember the compose file still has the host path as `/tmp/foo/source`, not `/tmp/foo/target/source`.\n\n```\nnic in /tmp/foo   (dev)\n❯ docker exec source-app-1 touch /usr/share/nginx/html/another-file.txt\n\nnic in /tmp/foo   (dev)\n❯ tree target                                                          \ntarget\n└── source\n    ├── another-file.txt  # BAM! New file still here...\n    ├── docker-compose.yml\n    └── file.txt\n\n2 directories, 3 files\n```\n## Few Things\n1. `cp` as noted before, creates new `inodes` and so if I were to have `cp -r`\n   that `source` directory to `target` then the container would've dropped\ner exec source-app-1 touch /usr/share/nginx/html/another-file.txc in /tmp/foo   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET t\n`another-file.txt` to `/tmp/source` and we'd be in a pretty confusing state\n\n2. Let me stress again the importance of managing your docker volumes in a sane\n   way... I have on any of my hosts a `/path/to/docker/data` which is backed up\nin a way that makes sense for the host (usually zfs + sanoid, otherwise restic\nbackup TO a remote zfs dataset - see\n[[using-restic-to-backup-my-home-directory#Intro]])\n",
      "summary": "docker bind mounts are specified as filepaths - this is very intuitive. I want /path/to/directory on my host to be shared with /another/path/foo in a...",
      "date_published": "2025-08-02T13:01:58Z",
      "date_modified": "2025-08-02T13:01:58Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "docker",
        "compose",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/you-suck-at-git-but-its-honestly-fine/",
      "url": "https://pype.dev/you-suck-at-git-but-its-honestly-fine/",
      "title": "You suck at git - but it’s honestly fine",
      "content_html": "\u003ch2 id=\"git-is-for-everyone\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGit is for everyone\u003c/span\u003e \u003ca href=\"#git-is-for-everyone\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI just started listening to \u003ca href=\"https://www.localfirst.fm/\"\u003elocalfirst.fm podcast\u003c/a\u003e\npodcast and would recommend it after 2 episodes. In the first episode, the\nhosts discuss where the term \u003ccode\u003elocalfirst\u003c/code\u003e comes from, what it means, and\nexamples of some localfirst, or semi-localfirst projects. One of them is \u003ccode\u003egit\u003c/code\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ethe de-facto standard by which developers share code today.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThere\u0026rsquo;s a reason it\u0026rsquo;s the standard. I\u0026rsquo;m not arguing that it\u0026rsquo;s perfect, and\nthere\u0026rsquo;s new abstractions that may help people adopt usage like\n\u003ca href=\"https://github.com/jj-vcs/jj\"\u003ejujutsu\u003c/a\u003e, but ultimately \u003ccode\u003egit\u003c/code\u003e is a great tool,\nand anyone can use it who works with text files (lookin at you book-writing\nnerds)\u003c/p\u003e\n\u003cp\u003eBut as I was enjoying the pod the co-host/guest said \u0026ldquo;and we all know the git user\nexperience isn\u0026rsquo;t that great\u0026rdquo;\u003c/p\u003e\n\u003cdiv class=\"admonition danger\"\u003e\n\u003cp class=\"admonition-title\"\u003eheavy sigh\u003c/p\u003e\n\u003cp\u003eGit isn\u0026rsquo;t that hard - you probably just aren\u0026rsquo;t that good at it\u003c/p\u003e\n\u003c/div\u003e\n\u003ch2 id=\"it-s-ok\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIt’s OK\u003c/span\u003e \u003ca href=\"#it-s-ok\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eDon\u0026rsquo;t hear me say that you suck at git like that\u0026rsquo;s a bad thing\u0026hellip; It\u0026rsquo;s \u003cem\u003etotally\nfine\u003c/em\u003e to not be a wizard. Many people can use git like 80-90% efficiently for\ntheir own use cases. In fact, my usage of git doesn\u0026rsquo;t fall into any perfectly\nestablished patterns, and the decisions I make are not always the same (the\noverplayed rebase vs merge-upstream argument\u0026hellip; I do both, it doesn\u0026rsquo;t matter,\nsquash your crap at the end anyways\u0026hellip;)\u003c/p\u003e\n\u003cp\u003eSo continue to collaborate - checkout your feature branches, pull in\n\u003ccode\u003eorigin/main\u003c/code\u003e after a PR, ask for help with a rebase for the 69th time if you\nneed to\u003c/p\u003e\n\u003cdiv class=\"admonition danger\"\u003e\n\u003cp class=\"admonition-title\"\u003eDanger\u003c/p\u003e\n\u003cp\u003eBut quit your stupid annoying bitching\u003c/p\u003e\n\u003c/div\u003e\n\u003ch2 id=\"seriously\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSeriously\u003c/span\u003e \u003ca href=\"#seriously\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHonestly, I learned \u003cem\u003eabout\u003c/em\u003e git 8 years ago when I started my first job as a\ndata scientist. I was told by another developer, Jokko,\nthat it was the standard for collaboration\u0026hellip; only issue was that almost no one\nin our division even used it. Or if they did it was at the end of a project,\nafter sharing code on share drives and OneDrive, at the very end we\u0026rsquo;d see \u003ccode\u003egit init\u003c/code\u003e, \u003ccode\u003egit add .\u003c/code\u003e, \u003ccode\u003egit commit -m \u0026quot;pushed to git\u0026quot;\u003c/code\u003e, \u003ccode\u003egit push\u003c/code\u003e\u0026hellip; literally 0\nvalue, or at least no more value than\n\u003ccode\u003emy_project_final_FINAL_v2_project-over-now.zip\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eIt took me a few weeks of on-the-job git usage, practicing way over complicated\ngit flows and talking about git with the few people around me who knew it, for\nme to become a go-to resource\u0026hellip; I was and am honestly still annoyed about\nit\u0026hellip; It didn\u0026rsquo;t take that long, or that much effort, to get pretty comfortable\nwith git. There\u0026rsquo;s tons of UIs (with their own flaws), TUIs, and resources for\nlearning the few things that will make you stand out as a git wizard:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003ecan you rebase onto a new root commit?\u003c/li\u003e\n\u003cli\u003ecan you pull in changes from someone else\u0026rsquo;s feature branch?\u003c/li\u003e\n\u003cli\u003eif you find yourself in \u003ccode\u003edetached HEAD state\u003c/code\u003e are you panicked?\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eJust make a directory, \u003ccode\u003egit init\u003c/code\u003e, and start shitting all over text files\u0026hellip;\nmake branches, reconcile differences, just freaking practice for 10 minutes\u003c/p\u003e\n\u003ch2 id=\"end\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eEnd\u003c/span\u003e \u003ca href=\"#end\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI was triggered I guess listening to this podcast episode\u0026hellip; It\u0026rsquo;s fine to not\nbe an expert, it\u0026rsquo;s fine to just use the 3 or 4 commands you probably need (\u003ccode\u003egit checkout -b, rebase origin/main, merge feature/branch, push\u003c/code\u003e), I am happy to\nhelp people with git everyday - like I said above, I am somehow a go-to\nresource for git in my workplace\u0026hellip;. (heck, even one of my managers asks me to\nsquash all the commits on his feature branches for him before it\u0026rsquo;s merged to\nmain\u0026hellip; it\u0026rsquo;s fine)\u003c/p\u003e\n\u003cp\u003eBut the user experience isn\u0026rsquo;t bad, so quit your bitching.\u003c/p\u003e\n\u003chr\u003e\n\u003cdetails class=\"admonition note\"\u003e\n\u003csummary class=\"admonition-title\"\u003ephone notes\u003c/summary\u003e\n\u003cp\u003ehot take, if you complain about git then you don\u0026rsquo;t it\u0026hellip; and that\u0026rsquo;s fine but\nshut up. I love git,I use it in a way that\u0026rsquo;s productive for me, and when I hear\npeople complain all they ever say is \u0026ldquo;git is hard\u0026rdquo; and \u0026ldquo;why is rebasing so\nhard?!?!\u0026rdquo;\nbut they don\u0026rsquo;t get it, and that\u0026rsquo;s fine. you can use git inefficiently, as a\nrequirement of your job, and not enjoy it. but don\u0026rsquo;t bitch, because with 10\nminutes of practice you could gain some real fluency in rebasing\u003c/p\u003e\n\u003c/details\u003e\n",
      "content_text": "\n## Git is for everyone\n\nI just started listening to [localfirst.fm podcast](https://www.localfirst.fm/)\npodcast and would recommend it after 2 episodes. In the first episode, the\nhosts discuss where the term `localfirst` comes from, what it means, and\nexamples of some localfirst, or semi-localfirst projects. One of them is `git`\n\n- the de-facto standard by which developers share code today.\n\nThere's a reason it's the standard. I'm not arguing that it's perfect, and\nthere's new abstractions that may help people adopt usage like\n[jujutsu](https://github.com/jj-vcs/jj), but ultimately `git` is a great tool,\nand anyone can use it who works with text files (lookin at you book-writing\nnerds)\n\nBut as I was enjoying the pod the co-host/guest said \"and we all know the git user\nexperience isn't that great\"\n\n!!! danger \"heavy sigh\"\n\n    Git isn't that hard - you probably just aren't that good at it\n\n## It's OK\n\nDon't hear me say that you suck at git like that's a bad thing... It's _totally\nfine_ to not be a wizard. Many people can use git like 80-90% efficiently for\ntheir own use cases. In fact, my usage of git doesn't fall into any perfectly\nestablished patterns, and the decisions I make are not always the same (the\noverplayed rebase vs merge-upstream argument... I do both, it doesn't matter,\nsquash your crap at the end anyways...)\n\nSo continue to collaborate - checkout your feature branches, pull in\n`origin/main` after a PR, ask for help with a rebase for the 69th time if you\nneed to\n\n!!! danger \"\"\n\n    But quit your stupid annoying bitching\n\n## Seriously\n\nHonestly, I learned _about_ git 8 years ago when I started my first job as a\ndata scientist. I was told by another developer, Jokko,\nthat it was the standard for collaboration... only issue was that almost no one\nin our division even used it. Or if they did it was at the end of a project,\nafter sharing code on share drives and OneDrive, at the very end we'd see `git\ninit`, `git add .`, `git commit -m \"pushed to git\"`, `git push`... literally 0\nvalue, or at least no more value than\n`my_project_final_FINAL_v2_project-over-now.zip`\n\nIt took me a few weeks of on-the-job git usage, practicing way over complicated\ngit flows and talking about git with the few people around me who knew it, for\nme to become a go-to resource... I was and am honestly still annoyed about\nit... It didn't take that long, or that much effort, to get pretty comfortable\nwith git. There's tons of UIs (with their own flaws), TUIs, and resources for\nlearning the few things that will make you stand out as a git wizard:\n\n1. can you rebase onto a new root commit?\n2. can you pull in changes from someone else's feature branch?\n3. if you find yourself in `detached HEAD state` are you panicked?\n\nJust make a directory, `git init`, and start shitting all over text files...\nmake branches, reconcile differences, just freaking practice for 10 minutes\n\n## End\n\nI was triggered I guess listening to this podcast episode... It's fine to not\nbe an expert, it's fine to just use the 3 or 4 commands you probably need (`git\ncheckout -b, rebase origin/main, merge feature/branch, push`), I am happy to\nhelp people with git everyday - like I said above, I am somehow a go-to\nresource for git in my workplace.... (heck, even one of my managers asks me to\nsquash all the commits on his feature branches for him before it's merged to\nmain... it's fine)\n\nBut the user experience isn't bad, so quit your bitching.\n\n---\n\n??? note \"phone notes\"\n\n    hot take, if you complain about git then you don't it... and that's fine but\n    shut up. I love git,I use it in a way that's productive for me, and when I hear\n    people complain all they ever say is \"git is hard\" and \"why is rebasing so\n    hard?!?!\"\n    but they don't get it, and that's fine. you can use git inefficiently, as a\n    requirement of your job, and not enjoy it. but don't bitch, because with 10\n    minutes of practice you could gain some real fluency in rebasing\n",
      "summary": "I just started listening to localfirst.fm podcast podcast and would recommend it after 2 episodes. In the first episode, the hosts discuss where the term...",
      "date_published": "2025-07-21T05:20:07Z",
      "date_modified": "2025-07-21T05:20:07Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "git",
        "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_text": "\nI am working on using [[gotify-cli-for-notifying-me-of-nextcloud-uploads]] 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 [[nextcloud-permissions-with-zfs-and-ansible-nas]]\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/gotify-cli-for-notifying-me-of-nextcloud-uploads/",
      "url": "https://pype.dev/gotify-cli-for-notifying-me-of-nextcloud-uploads/",
      "title": "gotify cli for notifying me of nextcloud uploads",
      "content_text": "\n# The Ask\n\nI was looking for a way to get notified from nextcloud when files were uploaded\nto a certain directory. This is because the upload is very spotty due to the\nphysical environment of where the nextcloud client is, and once a file shows up\nI have a job to do. So I was hoping nextcloud had an easy way to say \"if file\nshows up in X folder, notify me\" but I can't find it...\n\n## Solution 2\n\nSince I already use gotify and they provide a [DOPE\nCLI](https://github.com/gotify/cli) we can write a little bash script to work\nat the filesystem level rather than the nextcloud level.\n\n## Permissions\n\nWe'll need to make sure the user running this script has all the right read\npermissions... In my exact case the file upload happens in a group folder,\nwhich is easily found on the filesystem in the Nextcloud volume at\n`.../data/__groupfolders/{id}/{rest of path}`. This was owned by `www-data` on\nthe host, see\n[[add-yourself-to-www-data-to-view-your-nextcloud-data-on-the-filesystem]] if\nyou want but a simple `usermod -aG www-data \u003cme\u003e` does the trick.\n\n## The Script\n\nWith a little AI magic to spruce up the messaging, the script basically looks like this:\n\n```bash\n#!/bin/bash\n\n# Script to monitor sermon directory using gotify watch\n# Used with a systemd service or standalone\n# Can run in local or remote (SSH) mode\n\n# Configuration\n\n# Target directory to monitor\nDIR_PATH=\"/path/to/watch\"\n\n# Mode: Set to \"ssh\" or \"local\"\n# If ssh, will connect to SSH_HOST as SSH_USER to monitor the directory\n# If local, will monitor the directory directly on this machine\nMODE=\"ssh\"\n\n# Remote SSH settings (only used if MODE=\"ssh\")\nSSH_USER=\"youruser\"\nSSH_HOST=\"yourserver\"\n\n# Log settings\nLOG_DIR=\"$HOME/.local/state/sermon-monitor\"\nLOG_FILE=\"$LOG_DIR/sermon-monitor.log\"\nMAX_LOG_SIZE_KB=1024 # 1MB max log size\n\n# Create log directory if it doesn't exist\nmkdir -p \"$LOG_DIR\" 2\u003e/dev/null\n\n# Create or truncate log file if it's too large\nif [ -f \"$LOG_FILE\" ] \u0026\u0026 [ $(stat -c%s \"$LOG_FILE\" 2\u003e/dev/null || echo 0) -gt $((MAX_LOG_SIZE_KB * 1024)) ]; then\n  # Keep the last 20 lines when rotating\n  tail -n 20 \"$LOG_FILE\" \u003e\"${LOG_FILE}.tmp\" 2\u003e/dev/null \u0026\u0026 mv \"${LOG_FILE}.tmp\" \"$LOG_FILE\" 2\u003e/dev/null\n  echo \"$(date '+%Y-%m-%d %H:%M:%S') - Log file rotated due to size limit\" \u003e\u003e\"$LOG_FILE\"\nfi\n\n# Ensure log file exists\ntouch \"$LOG_FILE\" 2\u003e/dev/null\n\n# Log function\nlog() {\n  echo \"$(date '+%Y-%m-%d %H:%M:%S') - $1\" | tee -a \"$LOG_FILE\"\n}\n\n# Create a temp file to store the previous state\nTEMP_DIR=\"/tmp/gotify-watcher\"\nPREV_STATE_FILE=\"$TEMP_DIR/previous_state.txt\"\nmkdir -p \"$TEMP_DIR\" 2\u003e/dev/null || true\n\n# Function to get directory listing based on mode\nget_directory_listing() {\n  if [ \"$MODE\" = \"ssh\" ]; then\n    # Remote mode - use SSH\n    ssh $SSH_USER@$SSH_HOST \"ls -la '$DIR_PATH' | grep -v '^\\.\\.$' | grep -v '^\\.$'\" ||\n      echo \"Error: Failed to connect to $SSH_HOST\"\n  else\n    # Local mode - direct access\n    if [ -d \"$DIR_PATH\" ]; then\n      ls -la \"$DIR_PATH\" | grep -v '^\\.\\.$' | grep -v '^\\.$'\n    else\n      echo \"Error: Directory $DIR_PATH does not exist locally\"\n    fi\n  fi\n}\n\n# Removed unused function - was leftover from previous version\n\n# Display startup message based on mode\nif [ \"$MODE\" = \"ssh\" ]; then\n  log \"Starting sermon file monitor for $SSH_USER@$SSH_HOST:$DIR_PATH (SSH mode)\"\nelse\n  log \"Starting sermon file monitor for $DIR_PATH (local mode)\"\nfi\n\n# Run initial check to establish baseline\nCURRENT_LISTING=$(get_directory_listing)\necho \"$CURRENT_LISTING\" \u003e\"$PREV_STATE_FILE\"\nlog \"Initial state captured. Watching for changes.\"\n\n# Monitor continuously\nwhile true; do\n  # Sleep for 30 seconds between checks\n  sleep 30\n\n  # Get current listing\n  CURRENT_LISTING=$(get_directory_listing)\n\n  # Find new files by comparing with previous state\n  NEW_FILES=$(diff --new-line-format=\"%L\" --old-line-format=\"\" --unchanged-line-format=\"\" \\\n    \"$PREV_STATE_FILE\" \u003c(echo \"$CURRENT_LISTING\") | grep -v \"^total\" | grep -v \"^drwx\")\n\n  # If new files found, notify\n  if [ -n \"$NEW_FILES\" ]; then\n    # Filter out directories and system entries, keep only regular files\n    FILE_ENTRIES=$(echo \"$NEW_FILES\" | grep -v \"^d\" | grep -v \"^total\" | grep -v \"^\\.$\" | grep -v \"^\\.\\.\")\n\n    # Count how many actual files (non-empty lines)\n    NEW_COUNT=$(echo \"$FILE_ENTRIES\" | grep -v \"^$\" | wc -l)\n\n    # Only proceed if we have actual files\n    if [ \"$NEW_COUNT\" -gt 0 ]; then\n      # Create notification message\n      if [ \"$NEW_COUNT\" -eq 1 ]; then\n        TITLE=\"New File Added\"\n        # Extract filename from listing line\n        FILENAME=$(echo \"$FILE_ENTRIES\" | awk '{print $NF}')\n        MESSAGE=\"New file detected: $FILENAME\"\n      else\n        TITLE=\"New Files Added\"\n        # Format filenames only for readability\n        FILELIST=$(echo \"$FILE_ENTRIES\" | awk '{print $NF}' | sort)\n        MESSAGE=\"$NEW_COUNT new files detected:\\n$FILELIST\"\n      fi\n\n      # Send notification\n      gotify push --title \"$TITLE\" \"$MESSAGE\"\n      log \"Notification sent for new files: $NEW_COUNT\"\n    fi\n\n    # Update previous state\n    echo \"$CURRENT_LISTING\" \u003e\"$PREV_STATE_FILE\"\n  fi\ndone\n\n# If we get here, gotify watch has exited\nlog \"gotify watch exited unexpectedly\"\nexit 1\n\n```\n\n### Credentials\n\nAs noted in the gotify repo - you can put the relevant credentials in a\n`./cli.json` which is what I did since this is in a big repo of mine and that's\nwhere the script executes from\n\n```json\n{\n  \"token\": \"yourToken\",\n  \"url\": \"https://gotify.example.com\",\n  \"defaultPriority\": 6\n}\n```\n",
      "summary": "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...",
      "date_published": "2025-07-16T20:33:25Z",
      "date_modified": "2025-07-16T20:33:25Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "nextcloud",
        "tech",
        "notifications",
        "nextcloud",
        "gotify"
      ]
    },
    {
      "id": "https://pype.dev/using-restic-to-backup-my-home-directory/",
      "url": "https://pype.dev/using-restic-to-backup-my-home-directory/",
      "title": "Using restic to backup my home directory",
      "content_text": "\n# Intro\n\nI need to backup my personal $HOME to my NAS cause there's a lot in there, and\nmostly my git projects with .env files all over. Plus some docker data\n\n## Why Not ZFS?\n\nGREAT QUESTION! It's because I struggled getting all the syncoid/sanoid\nrequirements installed on my Aurora OS.... I like the immutable desktop trend,\nbut I don't understand rpm-ostree enough to properly get all the lower level\nkernel stuff setup in a way I trust. So because I don't have ZFS at $HOME\nwhat's a boy to do?\n\n`rsync` would probably work fine especially if I [[rsync-like-a-pro]] but I figured I could cobble together some other tech and broaden my horizons...\n\n## Enter restic\n\nApparently [restic](https://restic.net/) has been around for a while, and you can run it in a container... awesome that's all I needed to hear...\n\n## How?\n\nI'll use a docker compose stack for this... typically I think running `restic`\nright on the host is the way to go, but on my desktop I float between my host\nand distroboxes and to keep things as simple as possible, even though less\nefficient, I want to manage everything through containers as my homelab gets\nbuilt out and eventually I'll have a more full-featured ecosystem.\n\n## The Value\n\nYou can find code below and an example repo that more or less is what I use for my desktop backup but that's primarily what I was after.... backing up the data on my desktop to my NAS in a way that:\n\n1. I wouldn't have to \"remember\" how I was doing it\n\n- I accomplish this by having good git repo organization and README files that explain my own usage patterns\n\n2. Tracked snapshots\n\n- I need some kind of snapshotting - `zfs` has spoiled me, and turns out `restic` has some amount of support for it\n\n3. Running in docker would be ideal for now so that I don't need to worry about installing specific binaries across machine while I build out my patterns\n\nWith this setup I get to backup my $HOME to my NAS which contains 1. docker\nvolume info for all the AI workloads I run on my desktop and 2. all my\nsensitive info in my git repos is at least backed up in a way that I can\nrecover `.env` files with relative ease...\n\n# Code\n\nCheck out the [Example GH\nRepo](https://github.com/pypeaday/docker-compose-restic) but I'll drop key file\ncontents here for a quick read if you're interested in some of the setup... but\nthe blog post mostly ends here\n\n## The Files\n\nWe have a `docker-compose.yml` of course\n\n```yaml\nservices:\n  backup:\n    image: restic/restic:latest\n    network_mode: host\n    container_name: restic_backup\n    hostname: restic-backup-runner\n    env_file:\n      - .env\n    environment:\n      # The location of the backup repository inside the container\n      - RESTIC_REPOSITORY=/target\n      # The location of the password file inside the container\n      - RESTIC_PASSWORD_FILE=/password\n    volumes:\n      # --- Source and Config Mounts ---\n      - \"${BACKUP_SOURCE}:/source:ro\"\n      - \"${RESTIC_PASSWORD_FILE}:/password:ro\"\n      - \"./backup-and-prune.sh:/usr/local/bin/backup-and-prune:ro\"\n\n      # --- Persistent Data Mounts ---\n      - \"./.ssh:/root/.ssh:rw\"\n      - \"./.cache:/root/.cache:rw\"\n\n    # Set the default entrypoint to our new script. This will be executed when\n    # the container starts, unless overridden.\n    entrypoint: [\"/usr/local/bin/backup-and-prune\"]\n```\n\nYour `.env` file will need to look like this\n\n```bash\n``# HOST related variables\n# The source directory to back up (absolute path)\nBACKUP_SOURCE=/home/nic\n\n# --- Restic Configuration ---\n# The file containing the restic repository password (absolute path on host)\nHOST_RESTIC_PASSWORD_FILE=/home/nic/projects/personal/homelab-mono/dataops/docker/.restic-password\n# The SSH private key to use for connecting to the NAS (absolute path on host)\n# It's recommended to use a dedicated key for this purpose.\nSSH_PRIVATE_KEY_FILE=/home/nic/.skm/ghost/id_rsa\n\n# Container Env Vars\n# the container makes the sftp connection using my credentials, but nonetheless the container needs them, so this isn't envrc stuff\n\n# --- SFTP/SSH Connection Details for the NAS ---\nSFTP_USER=nic\nSFTP_HOST=ghost\n# The path on the NAS where the restic repository will be stored\nSFTP_PATH=/tank/encrypted/nas/nic-home/\n\n# --- Restic Configuration ---\n\nRESTIC_REPOSITORY=/target\nRESTIC_PASSWORD_FILE=/password\n```\n\nAnd then the backup script that the container will execute is something like this:\n\n```bash\n\n#!/bin/sh\nset -e # Exit immediately if a command exits with a non-zero status.\n\n# Construct the repository path from environment variables passed by docker-compose\nREPO=\"sftp:${SFTP_USER}@${SFTP_HOST}:${SFTP_PATH}\"\n\n# 1. Run the backup\n# -----------------\necho \"--- Starting backup for ${BACKUP_SOURCE} ---\"\nrestic backup /source --verbose -r \"${REPO}\"\necho \"--- Backup complete ---\"\n\n# 2. Clean up old snapshots according to the policy\n# --------------------------------------------------\necho \"--- Pruning old snapshots ---\n(Policy: keep last 7 daily, 4 weekly, 6 monthly)\"\nrestic forget \\\n    --prune \\\n    --keep-daily 7 \\\n    --keep-weekly 4 \\\n    --keep-monthly 6 \\\n    -r \"${REPO}\"\n\necho \"--- Backup and prune process finished successfully ---\"\n```\n\n## Systemd\n\nFinally there's a systemd unit and timer file in there so you can setup a systemd service for the backup\n\n### service\n\n```bash\n\n[Unit]\nDescription=Run Restic backup to NAS using Docker Compose\n# We are running this as a user service, so we assume that the system-level\n# docker.service is already running.\nAfter=network-online.target\n\n[Service]\nType=oneshot\n# Set the working directory to where your docker-compose.yml and .env file are located\nWorkingDirectory=/home/user/nas-backup/docker\n\n# The command to execute. We use the full path to docker-compose for reliability.\n# You may need to adjust this path if 'docker compose' is installed elsewhere.\nExecStart=/usr/bin/docker compose run --rm backup\n\n[Install]\nWantedBy=default.target\n```\n\n### timer\n\n```bash\n\n[Unit]\nDescription=Run Restic backup job daily\n\n[Timer]\n# Run daily at 2:00 AM\nOnCalendar=daily\n# Or uncomment for a specific time:\n# OnCalendar=*-*-* 02:00:00\n\n# Run the backup immediately if the last scheduled run was missed (e.g., if the computer was off)\nPersistent=true\n\n[Install]\nWantedBy=timers.target\n```\n",
      "summary": "I need to backup my personal $HOME to my NAS cause there's a lot in there, and mostly my git projects with .env files all over. Plus some docker data",
      "date_published": "2025-07-16T09:12:26Z",
      "date_modified": "2025-07-16T09:12:26Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/python-editable-installs-and-nested-scripts/",
      "url": "https://pype.dev/python-editable-installs-and-nested-scripts/",
      "title": "Python editable installs and nested scripts",
      "content_html": "\u003ch1 id=\"intro\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIntro\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI have been working towards a standard structure for all my python projects -\nand I mean much more than just \u003ccode\u003euv init\u003c/code\u003e or something along those lines. But\none of the things that got me recently was starting to keep a \u003ccode\u003escripts/\u003c/code\u003e folder\nthat ran code based on what was in \u003ccode\u003esrc/\u003c/code\u003e or \u003ccode\u003eapp/\u003c/code\u003e (I\u0026rsquo;m working on that\u0026hellip;)\nbeing installed in the virtual environment\u0026hellip; so I\u0026rsquo;d \u003ccode\u003euv pip install -e .\u003c/code\u003e or\n\u003ccode\u003euv pip install -e src\u003c/code\u003e (kind of depends on the structure) and then in\n\u003ccode\u003escripts/foo.py\u003c/code\u003e I\u0026rsquo;d have \u003ccode\u003efrom my_cool_code.module import thing\u003c/code\u003e\u0026hellip;.\u003c/p\u003e\n\u003cp\u003eRunning scripts has never been a problem\u0026hellip; but I\u0026rsquo;ve always kept them in the\nroot of the repo OR \u003cem\u003einside\u003c/em\u003e \u003ccode\u003eapp\u003c/code\u003e or \u003ccode\u003esrc\u003c/code\u003e in a \u003cem\u003enested\u003c/em\u003e directory\u0026hellip;\u003c/p\u003e\n\u003cp\u003eSo here\u0026rsquo;s where I learned a little something about \u003ccode\u003epython my_script.py\u003c/code\u003e vs\n\u003ccode\u003epython -m my_script\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"example\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eExample\u003c/span\u003e \u003ca href=\"#example\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI setup a short example repo to illustrate the problem\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/pypeaday/python-e-example\"\u003elink to repo\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eThe repo structure is very simple\u0026hellip;\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epython-e-example\n  ├── foo.py\n  ├── pyproject.toml\n  ├── src\n  │   ├── module.py\n  ├── README.md\n  └── scripts\n      └── bar.py\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003e\u003ccode\u003escripts/bar.py\u003c/code\u003e and \u003ccode\u003efoo.py\u003c/code\u003e are the same code:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003esrc.module\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003emy_func\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\"\u003emy_func\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eand then \u003ccode\u003esrc/module.py\u003c/code\u003e:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003emy_func\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=\"s2\"\u003e\u0026#34;it worked!\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\u003cp\u003eThe rest of the structure is from \u003ccode\u003euv init\u003c/code\u003e basically, then \u003ccode\u003euv venv\u003c/code\u003e will make\nyour virtual environment, and \u003ccode\u003euv pip install -e .\u003c/code\u003e will install the library in\neditable mode.\u003c/p\u003e\n\u003ch3 id=\"so-what-s-the-problem\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSo what’s the problem?\u003c/span\u003e \u003ca href=\"#so-what-s-the-problem\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eLet\u0026rsquo;s run \u003ccode\u003efoo.py\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enic in python-e-example   main   ×7 via   v3.13.1\u003cspan class=\"o\"\u003e(\u003c/span\u003epython-e-example\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e⬢ \u003cspan class=\"o\"\u003e[\u003c/span\u003edevbox\u003cspan class=\"o\"\u003e]\u003c/span\u003e ❯ python foo.py\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eit worked!\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNice! It works just like we\u0026rsquo;d expect\u0026hellip;\u003c/p\u003e\n\u003cp\u003eWhat about \u003ccode\u003epython scripts/bar.py\u003c/code\u003e?\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enic in python-e-example   main   ×7 via   v3.13.1\u003cspan class=\"o\"\u003e(\u003c/span\u003epython-e-example\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e⬢ \u003cspan class=\"o\"\u003e[\u003c/span\u003edevbox\u003cspan class=\"o\"\u003e]\u003c/span\u003e ❯ python scripts/bar.py\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTraceback \u003cspan class=\"o\"\u003e(\u003c/span\u003emost recent call last\u003cspan class=\"o\"\u003e)\u003c/span\u003e:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  File \u003cspan class=\"s2\"\u003e\u0026#34;/tmp/python-e-example/scripts/bar.py\u0026#34;\u003c/span\u003e, line 8, in \u0026lt;module\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    from src.module import my_func\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eModuleNotFoundError: No module named \u003cspan class=\"s1\"\u003e\u0026#39;src\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cdiv class=\"admonition danger\"\u003e\n\u003cp class=\"admonition-title\"\u003eCome again for big fudge...\u003c/p\u003e\n\u003c/div\u003e\n\u003ch3 id=\"ok-let-s-stay-calm-and-troubleshoot\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eOk let’s stay calm and troubleshoot\u003c/span\u003e \u003ca href=\"#ok-let-s-stay-calm-and-troubleshoot\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eWhat if we open a \u003ccode\u003epython\u003c/code\u003e shell\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePython 3.13.1 \u003cspan class=\"o\"\u003e(\u003c/span\u003emain, Dec \u003cspan class=\"m\"\u003e19\u003c/span\u003e 2024, 14:32:25\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003eClang 18.1.8 \u003cspan class=\"o\"\u003e]\u003c/span\u003e on linux\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eType \u003cspan class=\"s2\"\u003e\u0026#34;help\u0026#34;\u003c/span\u003e, \u003cspan class=\"s2\"\u003e\u0026#34;copyright\u0026#34;\u003c/span\u003e, \u003cspan class=\"s2\"\u003e\u0026#34;credits\u0026#34;\u003c/span\u003e or \u003cspan class=\"s2\"\u003e\u0026#34;license\u0026#34;\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e more information.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026gt;\u0026gt;\u0026gt; from src.module import my_func\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026gt;\u0026gt;\u0026gt; my_func\u003cspan class=\"o\"\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\u0026lt;!--markata-attribution--\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eit worked!\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eok\u0026hellip; as expected it worked, and we\u0026rsquo;ve learned nothing\u0026hellip;\u003c/p\u003e\n\u003cdiv class=\"admonition warning\"\u003e\n\u003cp class=\"admonition-title\"\u003eimplicit behavior\u003c/p\u003e\n\u003cp\u003eWe didn\u0026rsquo;t learn anything from the example but something is present that we\ndon\u0026rsquo;t see\u0026hellip; \u003ccode\u003epython foo.py\u003c/code\u003e implicitly adds the path to the script to\n\u003ccode\u003esys.path\u003c/code\u003e and so the \u003ccode\u003esrc\u003c/code\u003e library is in the same directory as the script\n\u003ccode\u003efoo.py\u003c/code\u003e so the library is on \u003ccode\u003esys.path\u003c/code\u003e\u0026hellip;. take note of this\u003c/p\u003e\n\u003c/div\u003e\n\u003ch2 id=\"the-problem\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Problem\u003c/span\u003e \u003ca href=\"#the-problem\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTurns out \u003ccode\u003euv pip install .\u003c/code\u003e and \u003ccode\u003euv pip install -e .\u003c/code\u003e are \u003cem\u003eactually\u003c/em\u003e different\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250705005214_a83dd563.png\" alt=\"20250705005214_a83dd563.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eThe editable install adds a \u003ccode\u003e.pth\u003c/code\u003e file into \u003ccode\u003esite-packages\u003c/code\u003e which acts as a\nshell hook.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e# .venv/lib/python3.13/site-packages/__editable__.python_e_example-0.1.0.pth\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/tmp/python-e-example/src\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo when \u003ccode\u003epython\u003c/code\u003e runs a script, the location of the script is\nimplicitly added to \u003ccode\u003esys.path\u003c/code\u003e as noted above.\nThe \u003ccode\u003epython\u003c/code\u003e process invoked by \u003ccode\u003epython foo.py\u003c/code\u003e has \u003ccode\u003e$PWD\u003c/code\u003e on \u003ccode\u003esys.path\u003c/code\u003e which is \u003ccode\u003e/tmp/python-e-example\u003c/code\u003e in\nthe example but \u003ccode\u003epython scripts/bar.py\u003c/code\u003e has \u003ccode\u003e/tmp/python-e-example/scripts\u003c/code\u003e\non \u003ccode\u003esys.path\u003c/code\u003e\u0026hellip; which means that \u003ccode\u003e/tmp/python-e-example\u003c/code\u003e is NOT on \u003ccode\u003esys.path\u003c/code\u003e\nin the process that\u0026rsquo;s running \u003ccode\u003escripts/bar.py\u003c/code\u003e and so the \u003ccode\u003esrc\u003c/code\u003e folder won\u0026rsquo;t be\nsearched by python because the \u003ccode\u003e.pth\u003c/code\u003e file points to a directory that\u0026rsquo;s no on\nthe path!\u003c/p\u003e\n\u003ch2 id=\"the-solution\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Solution\u003c/span\u003e \u003ca href=\"#the-solution\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWe can instead of running the script \u0026ldquo;as a script\u0026rdquo; we can tell python to\nexecute it as as a module with \u003ccode\u003epython -m scripts.bar\u003c/code\u003e. This is syntatically\nsimilar to \u003ccode\u003epython path/to/script.py\u003c/code\u003e but instead tells the python interpreter\nto execute the script as a module so the path is replaced by import syntax.\u003c/p\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eThis works for foo.py too\u003c/p\u003e\n\u003c/div\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e⬢ \u003cspan class=\"o\"\u003e[\u003c/span\u003edevbox\u003cspan class=\"o\"\u003e]\u003c/span\u003e ✗ python -m foo\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eit worked!\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=\"o\"\u003e[\u003c/span\u003edevbox\u003cspan class=\"o\"\u003e]\u003c/span\u003e ❯ python -m scripts.bar\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eit worked!\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\u003ch2 id=\"chatgpt\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eChatGPT\u003c/span\u003e \u003ca href=\"#chatgpt\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s noteworthy that jipity was helpful in exploring these issues I was running into\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://chatgpt.com/c/686d2a05-5b54-800f-8b84-1638c1b96840\"\u003elink to convo\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\n# Intro\n\nI have been working towards a standard structure for all my python projects -\nand I mean much more than just `uv init` or something along those lines. But\none of the things that got me recently was starting to keep a `scripts/` folder\nthat ran code based on what was in `src/` or `app/` (I'm working on that...)\nbeing installed in the virtual environment... so I'd `uv pip install -e .` or\n`uv pip install -e src` (kind of depends on the structure) and then in\n`scripts/foo.py` I'd have `from my_cool_code.module import thing`....\n\nRunning scripts has never been a problem... but I've always kept them in the\nroot of the repo OR _inside_ `app` or `src` in a _nested_ directory...\n\nSo here's where I learned a little something about `python my_script.py` vs\n`python -m my_script`\n\n## Example\n\nI setup a short example repo to illustrate the problem\n\n[link to repo](https://github.com/pypeaday/python-e-example)\n\nThe repo structure is very simple...\n\n```\npython-e-example\n  ├── foo.py\n  ├── pyproject.toml\n  ├── src\n  │   ├── module.py\n  ├── README.md\n  └── scripts\n      └── bar.py\n```\n\n`scripts/bar.py` and `foo.py` are the same code:\n\n```python\nfrom src.module import my_func\n\nmy_func()\n```\n\nand then `src/module.py`:\n\n```python\ndef my_func():\n    print(\"it worked!\")\n```\n\nThe rest of the structure is from `uv init` basically, then `uv venv` will make\nyour virtual environment, and `uv pip install -e .` will install the library in\neditable mode.\n\n### So what's the problem?\n\nLet's run `foo.py`\n\n```bash\nnic in python-e-example   main   ×7 via   v3.13.1(python-e-example)   (dev) 󰒄\n⬢ [devbox] ❯ python foo.py\nit worked!\n```\n\nNice! It works just like we'd expect...\n\nWhat about `python scripts/bar.py`?\n\n```bash\nnic in python-e-example   main   ×7 via   v3.13.1(python-e-example)   (dev) 󰒄\n⬢ [devbox] ❯ python scripts/bar.py\nTraceback (most recent call last):\n  File \"/tmp/python-e-example/scripts/bar.py\", line 8, in \u003cmodule\u003e\n    from src.module import my_func\nModuleNotFoundError: No module named 'src'\n```\n\n!!! danger \"Come again for big fudge...\"\n\n### Ok let's stay calm and troubleshoot\n\nWhat if we open a `python` shell...\n\n```bash\nPython 3.13.1 (main, Dec 19 2024, 14:32:25) [Clang 18.1.8 ] on linux\nType \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n\u003e\u003e\u003e from src.module import my_func\n\u003e\u003e\u003e my_func()\nit worked!\n```\n\nok... as expected it worked, and we've learned nothing...\n\n!!! warning \"implicit behavior\"\n\n    We didn't learn anything from the example but something is present that we\n    don't see... `python foo.py` implicitly adds the path to the script to\n    `sys.path` and so the `src` library is in the same directory as the script\n    `foo.py` so the library is on `sys.path`.... take note of this\n\n## The Problem\n\nTurns out `uv pip install .` and `uv pip install -e .` are _actually_ different\n\n![20250705005214_a83dd563.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250705005214_a83dd563.png)\n\nThe editable install adds a `.pth` file into `site-packages` which acts as a\nshell hook.\n\n```txt\n# .venv/lib/python3.13/site-packages/__editable__.python_e_example-0.1.0.pth\n\n/tmp/python-e-example/src\n```\n\nSo when `python` runs a script, the location of the script is\nimplicitly added to `sys.path` as noted above.\nThe `python` process invoked by `python\nfoo.py` has `$PWD` on `sys.path` which is `/tmp/python-e-example` in\nthe example but `python scripts/bar.py` has `/tmp/python-e-example/scripts`\non `sys.path`... which means that `/tmp/python-e-example` is NOT on `sys.path`\nin the process that's running `scripts/bar.py` and so the `src` folder won't be\nsearched by python because the `.pth` file points to a directory that's no on\nthe path!\n\n## The Solution\n\nWe can instead of running the script \"as a script\" we can tell python to\nexecute it as as a module with `python -m scripts.bar`. This is syntatically\nsimilar to `python path/to/script.py` but instead tells the python interpreter\nto execute the script as a module so the path is replaced by import syntax.\n\n!!! note \"This works for foo.py too\"\n\n```bash\n⬢ [devbox] ✗ python -m foo\nit worked!\n\n⬢ [devbox] ❯ python -m scripts.bar\nit worked!\n\n```\n\n## ChatGPT\n\nIt's noteworthy that jipity was helpful in exploring these issues I was running into\n\n[link to convo](https://chatgpt.com/c/686d2a05-5b54-800f-8b84-1638c1b96840)\n",
      "summary": "I have been working towards a standard structure for all my python projects - and I mean much more than just or something along those lines. But one of the...",
      "date_published": "2025-07-14T08:40:32Z",
      "date_modified": "2025-07-14T08:40:32Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/ghost/",
      "url": "https://pype.dev/ghost/",
      "title": "Ghost",
      "content_text": "\n# Intro\n\nGhost is my primary application server. These are the specs...\n\n\u003e I wrote about some of the specs in [[homelab-journey-part-1#current-homelab-setup]] but here is where I will keep up to date information\n\n## Why\n\nWhy `ghost`? I don't know... I was going for a theme at one point it time, or\nseveral points rather... and after giving up on the theatrics of a fully\nconsistent harry potter naming scheme across all my homelab entities, I decided\nto just come up with some short aliases that were easy to type. So `ghost` was\nborn, and the backup server is `ghost-vault`.\n\n## CPU\n\nI have a `AMD Ryzen 7 5700G with Radeon Graphics @ 16x 4.426GHz` in this bad\nboy... why this chip? Well I knew I wanted many cores, and I was previously on\na Ryzn 5 3600, which was fine but I noticed that as I started to develop with\ncontainers more that I was going to want to take advnaatage of more cores...\n\nI also was pretty sure that the built-in graphics would be enough for the\nacceleration requirements I had in mind - which was primarily [[jellyfin]].\n\nAt this point in time, I think AMD was still beating Intel on most of the\nbenchmarks I tended to care about (I was watching channels like Bitwit and LTT\nat the time of building this machine)\n\nSo because of the moderate amount of cores and built-in GPU, I paid $229 for it\naccording to my Amazon history, and it's currently priced at $174, so that's a\npretty sweet deal if you're on AM4 and looking to upgrade in July of 2025\n\n## Memory\n\nI maxxed her out at 128 Gee Beez. I snagged that kit of Crucial that's all\nblack that we've all seen on Amazon. I paid aboaut $90 for it, it's currently\npriced way high for some reason, which is unfortunate. It's been totally fine\nfor me as far as memory performance goes - in that it's stable and I don't\nthink about it...\n\n## Boot\n\nI have a 1 TB NVMe in here to boot from. I think it's a Crucial P3\n\n## Storage\n\nStorage is the interesting bit, it's supposed to be the most rock solid but\nit's also been the most fluid for me from a hardware perspective, and as of my\nmost recent case migration I think I'm in a good spot..\n\n\u003e The case is a Sagitatius 8-bay NAS Chassis from aliexpress\n\nI use\n[zfs](https://openzfs.github.io/openzfs-docs/Getting%20Started/index.html)\nand recommend you do to.... but this isn't a post about that\n\nMy primary zpool is a 12TB ZFS Mirror, and I have another 12TB ZFS Mirror as an\non-prem replica plugged into this same box.\n\nThere is a 4TB drive that serves as frigate's media directory - I wanted a\ndedicated drive due to the write-intensive nature of the NVR.\n\n\u003e I backup to an offsite box using syncoid\n\n## OS\n\nI am currently running Ubuntu Server 24.04\n\nI am eager for Ubuntu to get zfs in their release that contains the zfs data\ncorruption with encrypted datasets but I don't expect to see it until 26.04 at\nthe earliest _fingers crossed_.\n\n## KVM\n\nI recently purchased a [jetkvm](https://www.jetkvm.com/) and it's exactly what\nI need for both my main server and my backup off-site... An amazingly simple\nand elegant solution to remote KVM.\n\n## Applications\n\nCurrently I use `docker compose` to manage practically everything. Stay tuned\nfor more about those things\n\nI also have a few ansible playbooks for setting up my shell and some utilities\non my computers, including the server. They're in\n[github](https://github.com/pypeaday/ansible-playbooks)\n\nFind more at [[the-homelab]]\n",
      "summary": "Ghost is my primary application server. These are the specs...",
      "date_published": "2025-07-11T20:23:35Z",
      "date_modified": "2025-07-11T20:23:35Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/the-homelab/",
      "url": "https://pype.dev/the-homelab/",
      "title": "The Homelab",
      "content_text": "\n# The Lab\n\nThis is the landing page for my homelab posts. It isn't a feed because I will\nwrite things here and link out to relevant parts of the homelab.\n\nI'll probably have notes about the hardware mostly here.\n\n## Primary Application Server\n\nMy main server is called [[ghost]]\n\n## Current Homelab Setup\n\n\u003e I copied this our of [[homelab-journey-part-1#current-homelab-setup]] for now to stub out what this page will link to\n\n## Ad blocking | Pi-Hole\n\nHardware: pi-3\n\nI have a Raspberry Pi 3 hardwired in that is running pi-hole so that's been a\npart of my infrastructure now for a long time.\n\n## Router | OPNSense\n\n!!! note \"stats\"\n\n    CPU: i5-8700\n\n    Memory: 8GB of whatever was in the box\n\n    Storage: 1TB SSD\n\nMy router is an old Optiplex SFF 5060 I got from Amazon for less than $100.\nSo I'm running a x86 system for my router with\nOpenSense on it. Just seemed it to be another home lab standard and I'd like to\nsomeday bring pi-hole over there or use AdGuard or integrate those two things\nmaybe a little bit more completely but right now they are mostly separate.\n\n## Networking | VLANs\n\nThere's a\nmanaged switch in there for some VLAN tomfoolery for my cameras and an IOT\nnetwork type of a thing.\n\n## Networking | VPN | Tailscale\n\nThey're on tailscale as well so my DNS happens over tailscale.\n\n## My Desktop\n\n!!! note \"stats\"\n\n    CPU: Ryzen 7 5700X\n\n    GPU: Nvidia 3090\n\n    Memory: 64 GB\n\n    Storage: 4TB SSD + 2 TB HDD for zfs backup\n\n    OS: Universal Blue Aurora\n\nMy daily-driver desktop is somewhat apart of my homelab now as I have a 3090 in\nthere with 20 GB of VRAM so I can run some heavier LLMs for self-hosted AI\nworkloads. Everything in that space runs in a container and then I manage\ndocker containers with compose stacks and portainer for visibility.\n\n### Applications\n\nSome of the apps I run here are ollama, open-webui, automatic1111's stable diffusion webui, whisper-webui\n",
      "summary": "This is the landing page for my homelab posts. It isn't a feed because I will write things here and link out to relevant parts of the homelab.",
      "date_published": "2025-07-11T20:22:51Z",
      "date_modified": "2025-07-11T20:22:51Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "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/testing-a-postiz-change-locally/",
      "url": "https://pype.dev/testing-a-postiz-change-locally/",
      "title": "Testing a Postiz Change Locally (IT WORKS!)",
      "content_text": "\n## Setup\n\nI am working on a pipeline at home to integrate my blog with social media a\nlittle more. One of the things I want to do is automatically post\n[[my-thoughts]] to [[nostr]]. [[postiz]] has nostr support that works just fine - login\nwith your [[nostr-hex-key]] and you're good to go to post notes to several of\nthe common relays. However for my testing I just wanted to post to a\n[self-hosetd nostr relay](https://github.com/scsibug/nostr-rs-relay)\nbut there wasn't a way to override which relays were used. Well I submitted [this pr](https://github.com/gitroomhq/postiz-app/pull/824)\nand hopefully it goes through. But before it did I was able to test the changes\nby simply building the postiz image from my feature branch and deploying it at\nhome in leiu of the published image. It was super cool to see it work\n\n## Build and Deploy\n\nThe build was easy after cloning the repo - I happened to already have `buildx` installed... it came with [aurora](https://docs.getaurora.dev/)\n\n`docker buildx build --platform linux/amd64 -f Dockerfile.dev -t registry.example.com/gitroomhq/postiz-app:nostr-test .`\n\nAfter building then I just update my compose file easy-peasy\n\n```yaml\nservices:\n  postiz:\n    # updated the image to my own from my registry\n    image: resitry.example.com/gitroomhq/postiz-app:latest\n    container_name: postiz\n    restart: always\n    env_file: .env\n#####################\n### REST OF COMPOSE FILE FROM THEIR REPO\n```\n\n## Pics\n\nTo test this my methodlogy was: 0. spin up nostr relay\n\n1. start postiz with my NOSTR_RELAY_OVERRIDES configuration for only my local relay\n2. use an existing script to schedule a nostr post via the postiz REST API\n3. use the UI to send the note to nostr\n4. validate the note came through my relay\n\nSo step 1 - does my script to schedule via the REST API work?\n\n```\npostiz  | [0] 1|backend  | {\npostiz  | [0] 1|backend  |   \"type\": \"draft\",\npostiz  | [0] 1|backend  |   \"shortLink\": false,\npostiz  | [0] 1|backend  |   \"tags\": [],\npostiz  | [0] 1|backend  |   \"posts\": [\npostiz  | [0] 1|backend  |     {\npostiz  | [0] 1|backend  |       \"integration\": {\npostiz  | [0] 1|backend  |         \"id\": \"cmbgf5oko0001ra99r9vzv1fj\"\npostiz  | [0] 1|backend  |       },\npostiz  | [0] 1|backend  |       \"value\": [\npostiz  | [0] 1|backend  |         {\npostiz  | [0] 1|backend  |           \"content\": \"\\nHere's a thought I had a while back:\\n\\n\u003e matduggan.com/what-would-a-kubernetes-2-0-look-like/\\n\\nThis article was longer than I had time to really consume but a heading caught my eye that I wanted to agree with - the author says k8s 2.0 may consider HCL instead of YAML. And as a recent terraform / open-tofu adopter I gotta say I would be ALL FOR THIS\\n\\nHere's the original link for more context: https://matduggan.com/what-would-a-kubernetes-2-0-look-like/\\n        \\n\\n# devops # tech #k8s #nostr #plebchain\",\npostiz  | [0] 1|backend  |           \"image\": []\npostiz  | [0] 1|backend  |         }\npostiz  | [0] 1|backend  |       ]\npostiz  | [0] 1|backend  |     }\npostiz  | [0] 1|backend  |   ],\npostiz  | [0] 1|backend  |   \"date\": \"2025-07-02T13:27:07.028Z\"\npostiz  | [0] 1|backend  | }\n```\n\n???+ success\n\n    After the first step my local build looks to have not broken this part of the app!\n\nNext was spinning up my own nostr relay - it's actually just `docker compose up`\n\n```yaml\nservices:\n  nostr-relay:\n    image: nostr-rs-relay:latest\n    container_name: nostr-relay\n    ports:\n      - \"7000:8080\"\n    environment:\n      TZ: America/Chicago\n    volumes:\n      - ./data:/usr/src/app/db\n      - ./config.toml:/usr/src/app/config.toml\n    restart: unless-stopped\n```\n\nFinally, can I send my note to my relay?\n\nI hit post in the UI and then checked coracle...\n\nYes! Using [coracle.social](https://coracle.social) I can verify that my note went to my relay only\n\n![20250706122123_47b06d96.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250706122123_47b06d96.png)\n\n## Fin\n\nSo that was a fun way to get into postiz a little more and start to flesh out\nwhat will be a testing-scenario for some pipelines I'm building at home\n",
      "summary": "I am working on a pipeline at home to integrate my blog with social media a little more. One of the things I want to do is automatically post my-thoughts to...",
      "date_published": "2025-07-06T08:42:44Z",
      "date_modified": "2025-07-06T08:42:44Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "postiz",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-732/",
      "url": "https://pype.dev/thoughts-732/",
      "title": "💭 Build backend | uv",
      "content_text": "\n\u003ca href=\"https://docs.astral.sh/uv/concepts/build-backend/\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://docs.astral.sh/uv/concepts/build-backend/\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - Build backend | uv\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://docs.astral.sh/uv/concepts/build-backend/\"\u003eBuild backend | uv\u003c/a\u003e\n\n---\n\nuv build backend ready for production! Dang... I need to visit my hatch builds, I love hatch's simplicity specifically with versioning...  Not sure if it's worth actually switching to uv for my builds or not, but it is dang cool to see astral take on the python space with unbelievable improvements \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 [[ thoughts ]]\n",
      "summary": "uv build backend ready for production! Dang... I need to visit my hatch builds, I love hatch's simplicity specifically w",
      "date_published": "2025-07-04T11:43:30Z",
      "date_modified": "2025-07-04T11:43:30Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "uv",
        "python",
        "dev",
        "tech",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/mcphub-with-windsurf/",
      "url": "https://pype.dev/mcphub-with-windsurf/",
      "title": "MCPHub with Windsurf",
      "content_html": "\u003cp\u003eI came across \u003ca href=\"https://github.com/samanhappy/mcphub\"\u003emcphub\u003c/a\u003e today and got the motivation to try it out\u0026hellip; I use AI\ntools like Windsurf and Roo across multiple devices and it\u0026rsquo;d be awesome to have\na simple mcp config - ie. a centralized hub to serve all my mcp servers and BAM\nenter this github project. It was easy to spin up in docker, and then according\nto Windsurf\u0026rsquo;s docs\n\u003ca href=\"https://docs.windsurf.com/windsurf/cascade/mcp#mcp-config-json\"\u003ehere\u003c/a\u003e the\nsetup is just naming a server and passing a serverUrl\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\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=\"nt\"\u003e\u0026#34;mcpServers\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=\"nt\"\u003e\u0026#34;hub\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=\"nt\"\u003e\u0026#34;serverUrl\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:3000/sse\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=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250703134426_2c9dfa01.png\" alt=\"20250703134426_2c9dfa01.png\"\u003e\n\u003c/figure\u003e\n\u003cdetails class=\"admonition warning\" open\u003e\n\u003csummary class=\"admonition-title\"\u003eWarning\u003c/summary\u003e\n\u003cp\u003eThe sse endpoint stuff is getting deprecated eventually - I didn\u0026rsquo;t want to\nspend any time right now trying to get the newer things to work\u0026hellip; future\nme/you will solve that problem\u003c/p\u003e\n\u003c/details\u003e\n\u003ch2 id=\"setup\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSetup\u003c/span\u003e \u003ca href=\"#setup\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI ran this in compose with this file\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0002services\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\"\u003emcphub\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\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003esamanhappy/mcphub\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;3000:3000\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./mcp_settings.json:/app/mcp_settings.json\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:/app/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\"\u003eqdrant\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\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eqdrant/qdrant\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\"\u003eqdrant\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;6333:6333\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\"\u003edeploy\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\"\u003eresources\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\"\u003elimits\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\"\u003ememory\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;500m\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\u003eAnd at present the \u003ccode\u003emcp_settings.json\u003c/code\u003e file looks like this mostly:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\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=\"nt\"\u003e\u0026#34;mcpServers\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=\"nt\"\u003e\u0026#34;docker-mcp\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;uvx\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=\"nt\"\u003e\u0026#34;args\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;docker-mcp\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=\"nt\"\u003e\u0026#34;ragdocs\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;node\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=\"nt\"\u003e\u0026#34;args\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;./node_modules/@qpd-v/mcp-server-ragdocs/build/index.js\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=\"nt\"\u003e\u0026#34;env\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=\"nt\"\u003e\u0026#34;QDRANT_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:6333\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=\"nt\"\u003e\u0026#34;EMBEDDING_PROVIDER\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ollama\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=\"nt\"\u003e\u0026#34;OLLAMA_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://localhost:11434\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=\"nt\"\u003e\u0026#34;alwaysAllow\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;search_documentation\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;list_sources\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;test_ollama\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;add_documentation\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=\"nt\"\u003e\u0026#34;disabled\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\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=\"nt\"\u003e\u0026#34;sequential-thinking\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;npx\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=\"nt\"\u003e\u0026#34;args\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;-y\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;@modelcontextprotocol/server-sequential-thinking\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=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;users\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=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026#34;username\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;admin\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=\"nt\"\u003e\u0026#34;password\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"err\"\u003eredacted\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=\"nt\"\u003e\u0026#34;isAdmin\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\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=\"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\u003c/code\u003e\u003c/pre\u003e\u003cdetails class=\"admonition note\" open\u003e\n\u003csummary class=\"admonition-title\"\u003eNote\u003c/summary\u003e\n\u003cp\u003eI assume here you have ollama running for ragdocs (in my screenshot you see I actually don\u0026rsquo;t right now anyways)\u003c/p\u003e\n\u003c/details\u003e\n\u003cp\u003e\u003ccode\u003edocker compose up\u003c/code\u003e and we\u0026rsquo;re cooking at \u003ca href=\"http://localhost:3000\"\u003ehttp://localhost:3000\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI came across [mcphub](https://github.com/samanhappy/mcphub) today and got the motivation to try it out... I use AI\ntools like Windsurf and Roo across multiple devices and it'd be awesome to have\na simple mcp config - ie. a centralized hub to serve all my mcp servers and BAM\nenter this github project. It was easy to spin up in docker, and then according\nto Windsurf's docs\n[here](https://docs.windsurf.com/windsurf/cascade/mcp#mcp-config-json) the\nsetup is just naming a server and passing a serverUrl\n\n```json\n{\n  \"mcpServers\": {\n    \"hub\": {\n      \"serverUrl\": \"http://localhost:3000/sse\"\n    }\n  }\n}\n```\n\n![20250703134426_2c9dfa01.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250703134426_2c9dfa01.png)\n\n???+ warning\n\n    The sse endpoint stuff is getting deprecated eventually - I didn't want to\n    spend any time right now trying to get the newer things to work... future\n    me/you will solve that problem\n\n## Setup\n\nI ran this in compose with this file\n\n```yaml\n\u0002services:\n  mcphub:\n    image: samanhappy/mcphub\n    ports:\n      - \"3000:3000\"\n    volumes:\n      - ./mcp_settings.json:/app/mcp_settings.json\n      - ./data:/app/data\n  qdrant:\n    image: qdrant/qdrant\n    container_name: qdrant\n    restart: unless-stopped\n    ports:\n      - \"6333:6333\"\n    deploy:\n      resources:\n        limits:\n          memory: \"500m\"\n```\n\nAnd at present the `mcp_settings.json` file looks like this mostly:\n\n```json\n{\n  \"mcpServers\": {\n    \"docker-mcp\": {\n      \"command\": \"uvx\",\n      \"args\": [\"docker-mcp\"]\n    },\n    \"ragdocs\": {\n      \"command\": \"node\",\n      \"args\": [\n        \"./node_modules/@qpd-v/mcp-server-ragdocs/build/index.js\"\n      ],\n      \"env\": {\n        \"QDRANT_URL\": \"http://localhost:6333\",\n        \"EMBEDDING_PROVIDER\": \"ollama\",\n        \"OLLAMA_URL\": \"http://localhost:11434\"\n      },\n      \"alwaysAllow\": [\n        \"search_documentation\",\n        \"list_sources\",\n        \"test_ollama\",\n        \"add_documentation\"\n      ],\n      \"disabled\": false\n    },\n    \"sequential-thinking\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@modelcontextprotocol/server-sequential-thinking\"]\n    }\n  },\n  \"users\": [\n    {\n      \"username\": \"admin\",\n      \"password\": redacted,\n      \"isAdmin\": true\n    }\n  ]\n}\n\n\n```\n\n???+ note\n\n    I assume here you have ollama running for ragdocs (in my screenshot you see I actually don't right now anyways)\n\n`docker compose up` and we're cooking at \u003chttp://localhost:3000\u003e\n",
      "summary": "I came across mcphub today and got the motivation to try it out... I use AI tools like Windsurf and Roo across multiple devices and it'd be awesome to have a...",
      "date_published": "2025-07-03T08:42:31Z",
      "date_modified": "2025-07-03T08:42:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "mcp",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/nostr/",
      "url": "https://pype.dev/nostr/",
      "title": "Nostr",
      "content_text": "\n???+ note \"How to start...\"\n\n    This isn't a getting started with nostr - see [[getting-started-with-nostr]] for that\n\n[The nostr docs](https://nostr.com/)\n\nNostr is a very intriguing protocol for communication that lends itself to a\ndecentrlized social media experience through the use of relays. I would love to\nunderstand it more fully someday, but I am inclined to be in line with\nalmost anything decentrlized, and although the primary content in the\nnostr-verse right now is bitcoin, I am sure that as time goes on, the need for\na truly free way to communicate will be necessary - and nostr won't solve every\nproblem, but I think it'll be a tool in the set of solutions we come up with.\nIt's secure, decentralized, actively worked on, and very flexible.\n\n???+ note \"NOSTR\"\n\n    Notes and Other Stuff Transmited over Relays\n\n## Decentralization\n\nWhy does decentralization matter? Put simply - censorship. A founding principal\nof America is free speech and in recent years our right to speak freely has\nbeen put under severe fire.\n\nI've crossed paths with people who very regularly talked about freedom of\nspeech but it wasn't until the last several years, mostly with covid that I\nstarted to pay a little closer attention. I'm not turning this post into any\nlong rant about anything specific, but I do know that many voices were silenced\non Facebook, Twitter/X, Insta, etc. Big Tech really threw the hammer down on\npeople - a simple example was the Ivermectin stuff... which in the last 2 years\nhas turned out to be a pretty effective treatment for many thousands of people!\n\nBut why does that matter? It's because the platforms that existed which made it\npossible for anyone to \"have an audience\" made harsh judgements about what\ncould and couldn't be on their platforms... There's pros and cons to this, but a\nprimary con I see is that the power to limit the spread of information was\npretty solely concentrated in the departments of these Big Tech firms that\nallowed or disallowed certain things to be said and expressed on their\nplatforms.\n\nNostr is different here in that there's no central command, no single data\ncenter or hub.... Nostr is a protocol, like HTTP is a protocol... anyone can\nsend \"Notes and Other Stuff over Relays\" so developers can build any kind of UI\nfor it, and you as the user can subscribe to relays that tend to have\ninformation you care about... in a sense it's kind of like subreddits but not\nquite as organized (yet).... but unlike reddit, there's no central authority.\nThe closest it gets is that a relay may go down or someone may choose to close\none - no big, you just subscribe to other relays, or don't subscribe to relays\nyou don't want to see\n\nAnother friend of mine likened this to RSS - and it's also not UNlike that, but\nthere's more to it - the notes are on the relays, there's encryption throughout\nthe protocol, and several other standards called NIPS that make nostr unique,\nand I think bulletproof as we go into a future clouded by big government and\nbig tech collaboration.\n\n## Brief Usage\n\nAs for a usage pattern with nostr I think you have to combine the ideas of\nreddit, rss, and twitter...\n\nSome apps like coracle.social have nice support for building \"feeds\" and\n\"lists\"... lists are combinations of tags (hashtags) and feeds are combinations\nof lists - at least in the coracle world. This means you can use lists of tags\nto discover content and users, create feeds of things you want to see regularly\n(like your RSS feed subscriptions), follow other users (using their pub keys\nsee [## Follow Me]), and curate your social media experience in whatever way\nyou want.\n\nThere's nostr apps specific to mobile (primal, snort) and desktop\n(coracle.social). They help you set yourself up in different ways and to learn\nmore about how I got started then checkout [[getting-started-with-nostr]].\nThere's also content-driven nostr apps, for example [fountain]() is great for\nmusic and podcasts that are distributed on the nostrverse, which feels like a\ntotally different world than when I use coracle to browse my tech or bible\nrelated feeds.\n\n## End\n\nNostr is a pretty cool thing taking shape... here's some related info\n\n[Thank God for nostr podcast](https://tgfb.com/podcasts/thank-god-for-nostr/)\n\n[nostr website](https://nostr.com/)\n\n\u003e Note these posts aren't available yet but they will show up on my RSS feed when they're published\n\nMy post on [[getting-started-with-nostr]]\n\nMy project on [[thoughts-to-nostr]]\n\n...\n\n## Follow Me\n\n![20250705122314_e9fbd0f7.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250705122314_e9fbd0f7.png)\n\n???+ note \"my public key\"\n\n    npub1q3fsec2vcv99v80ga72dlv90qwkqmuxqcr6mdyumcmpkgudlhrespyurfj\n",
      "summary": "???+ note \"How to start...\"",
      "date_published": "2025-07-02T21:22:03Z",
      "date_modified": "2025-07-02T21:22:03Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "nostr",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/nostr-hex-key/",
      "url": "https://pype.dev/nostr-hex-key/",
      "title": "Nostr hex key",
      "content_text": "\nyou 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)\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/github-star-sorter/",
      "url": "https://pype.dev/github-star-sorter/",
      "title": "Github Star Sorter",
      "content_html": "\u003cp\u003eI wrote a simple app to sort, tag, and create tickets at home for my github\nstars\u0026hellip; As of right now I have 1.3k stars and no convenient way to view them\nor remember which ones were ones I actually wanted to do something with. This\nsimple UI makes scrolling the stars list easy, updates is easy, and the kicker\nfor me is Kanboard integration so I can click a button from the stars UI and\nget a ticket made in my kanboard instance so that the work I want to do is in\nfront of me and not hidden by Github\u0026rsquo;s shockingly HORRIBLE UI.\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250701125804_c41ec692.png\" alt=\"20250701125804_c41ec692.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/pypeaday/gh-star-sorter\"\u003eGH Link\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI wrote a simple app to sort, tag, and create tickets at home for my github\nstars... As of right now I have 1.3k stars and no convenient way to view them\nor remember which ones were ones I actually wanted to do something with. This\nsimple UI makes scrolling the stars list easy, updates is easy, and the kicker\nfor me is Kanboard integration so I can click a button from the stars UI and\nget a ticket made in my kanboard instance so that the work I want to do is in\nfront of me and not hidden by Github's shockingly HORRIBLE UI.\n\n![20250701125804_c41ec692.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250701125804_c41ec692.png)\n\n[GH Link](https://github.com/pypeaday/gh-star-sorter)\n",
      "summary": "I wrote a simple app to sort, tag, and create tickets at home for my github stars... As of right now I have 1.3k stars and no convenient way to view them or...",
      "date_published": "2025-07-01T08:01:31Z",
      "date_modified": "2025-07-01T08:01:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-722/",
      "url": "https://pype.dev/thoughts-722/",
      "title": "💭 Switching pip to uv in a Dockerized Flask / Django App — Nick …",
      "content_text": "\n\u003ca href=\"https://nickjanetakis.com/blog/switching-pip-to-uv-in-a-dockerized-flask-or-django-app\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://nickjanetakis.com/blog/switching-pip-to-uv-in-a-dockerized-flask-or-django-app\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - Switching pip to uv in a Dockerized Flask / Django App — Nick Janetakis\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://nickjanetakis.com/blog/switching-pip-to-uv-in-a-dockerized-flask-or-django-app\"\u003eSwitching pip to uv in a Dockerized Flask / Django App — Nick Janetakis\u003c/a\u003e\n\n---\n\nIt's nice to see uv gaining popularity in so many places - and replacing vanilla pip with uv in containerized environments seems great due if for no other reason than the time you'll save in CI... A few years ago I refactored a disjointed container build process to use uv instead of pip, and due to the design of the build pipelines we went from 10-15 minute builds to 2-3 minutes... \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 [[ thoughts ]]\n",
      "summary": "It's nice to see uv gaining popularity in so many places - and replacing vanilla pip with uv in containerized environmen",
      "date_published": "2025-06-27T11:18:16Z",
      "date_modified": "2025-06-27T11:18:16Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "pip",
        "uv",
        "dev",
        "tech",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/temporal/",
      "url": "https://pype.dev/temporal/",
      "title": "temporal",
      "content_html": "\u003cp\u003e\u003ca href=\"https://temporal.io/\"\u003eTemporal\u003c/a\u003e is a workflow orchestrator that I\u0026rsquo;m interested in\u003c/p\u003e\n",
      "content_text": "\n[Temporal](https://temporal.io/) is a workflow orchestrator that I'm interested in\n",
      "summary": "Temporal is a workflow orchestrator that I'm interested in",
      "date_published": "2025-06-24T21:21:43Z",
      "date_modified": "2025-06-24T21:21:43Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "temporal"
      ]
    },
    {
      "id": "https://pype.dev/traefik-and-grpc-for-temporal-at-home/",
      "url": "https://pype.dev/traefik-and-grpc-for-temporal-at-home/",
      "title": "Traefik and gRPC for Temporal at home",
      "content_text": "\n# Temporal Networking Woes\n\nSomeday I will potentially write up some stuff about [[temporal]] but for now\nit's a workflow orchestrator that I'm interested in and I wanted to PoC it in\nmy homelab by replacing a simple cron job to build my blog with a temporal\nworkflow and activity. I was IMMEDIATELY thwarted by a networking error that\nfeels crazy to me but maybe won't be to others... so enjoy the problem and if\nyou're here from a Google search, I hope it's helpful.\n\nGetting started with Temporal I wanted to have the server and associated\ncomponents on my server, but I expected to be running workflows and workers\non another computer. \n\nFor most of my apps I slap some [[traefik]] labels on\nand we're good to go. But I got a wild set of errors trying to run a worker on\nmy desktop and connect it to the temporal server on my server over https.... it\nended up being because I needed to setup a special protocol in traefik for the\nreverse proxy to appropriately proxy the grpc call.\n\nSo I had a normal set of labels on my temporal container, went to run a worker\nand got this nasty work of art\n\n```\nRuntimeError: Failed client connect: `get_system_info` call error after connection: Status { code: Internal, message: \"protocol error: received message with invalid compression flag: 73 (valid flags are 0 and 1) while receiving response with status: 500 Internal Server Error\", metadata: MetadataMap { headers: {\"content-type\": \"text/plain; charset=utf-8\", \"content-length\": \"21\", \"date\": \"Wed, 25 Jun 2025 02:12:44 GMT\"} }, source: None }\n```\n\nThank God I had kind of seen this at work one other time in my life, and we\nnever fixed the issue there but I knew what it was... we had traefik acting as\na load balancer (as do I at home) and the traefik load balancer was \"balancing\ntraffic using https\"... no, I do not really know what I'm saying. But what I do\nknow is that in order for the Temporal Server and Worker to communicate,\ntraefik has to properly route the traffic for gRPC instead of https.\n\n!!! warning \"\"\n    I can't stress enough how little I know about the difference between https and grpc or why the load balancer cares... there's clearly something in the networking stack that is extremely relevant here\n\nBut the fix is NOT hard thankfully, and it's even on [traefik's website](https://doc.traefik.io/traefik/user-guides/grpc/#traefik-configuration) - the warning annotation there tells you what you need - which is one more traefik label...\n\n```yaml\ntraefik.http.services.\u003cmy-service-name\u003e.loadbalancer.server.scheme=h2c\n```\n\nSo after setting the loadbalancer.server.scheme to `h2c` I could run my worker just fine!\n\n![20250625021838_4d7104c2.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250625021838_4d7104c2.png)\n",
      "summary": "Someday I will potentially write up some stuff about temporal but for now it's a workflow orchestrator that I'm interested in and I wanted to PoC it in my...",
      "date_published": "2025-06-24T20:55:42Z",
      "date_modified": "2025-06-24T20:55:42Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "temporal",
        "grpc"
      ]
    },
    {
      "id": "https://pype.dev/nextcloud-php-opache-memory-consumption/",
      "url": "https://pype.dev/nextcloud-php-opache-memory-consumption/",
      "title": "Nextcloud PHP Opache Memory Consumption",
      "content_html": "\u003ch1 id=\"intro\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIntro\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eToday I was combing through Nextcloud, just taking a gander at the apps, updates, etc.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe OPcache buffer is nearly full. To assure that all scripts can be hold in\ncache, it is recommended to apply \u0026ldquo;opcache.memory_consumption\u0026rdquo; to your PHP\nconfiguration with a value higher than \u0026ldquo;128\u0026rdquo;\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eSo I know \u003cem\u003enothing\u003c/em\u003e about \u003ccode\u003ephp\u003c/code\u003e and I had no idea where to set this\u0026hellip; Also I\u0026rsquo;m\nrunning Nextcloud in a container so any updates will be blown away when the\ncontainer is removed (say next time I update Nextcloud)\u0026hellip;\u003c/p\u003e\n\u003cp\u003eAfter a little duckduckgoing I came across \u003ca href=\"https://www.reddit.com/r/NextCloud/comments/1gpxl5b/need_help_with_php_opcache_module_warning/\"\u003ethis reddit\npost\u003c/a\u003e\nwhere a guy had the same issue\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThe long and short of it is that on \u003cem\u003esingle instance nextcloud deployments\u003c/em\u003e\nthis php memory consumption value can be too low for Nextcloud to keep up with\nthe demand\u0026hellip; You can see in the message they just recommend upping the value.\u003c/p\u003e\n\u003cp\u003eSo I had to think about how to persist the change and have it take effect now\nideally without affecting any users too much\u0026hellip; So here\u0026rsquo;s what I did - made the\nchange in 2 places:\u003c/p\u003e\n\u003ch2 id=\"in-the-container\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIn the container\u003c/span\u003e \u003ca href=\"#in-the-container\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI exec\u0026rsquo;d into the container and used this handy \u003ccode\u003esed\u003c/code\u003e command \u003ccode\u003esed -i 's/^opcache\\.memory_consumption\\s*=\\s*128$/opcache.memory_consumption=256/' /usr/local/etc/php/conf.d/opcache-recommended.ini\u003c/code\u003e to update the value in the\nrunning container - and I guess \u003ccode\u003ephp\u003c/code\u003e maybe is loaded up dynamically or\nsomething? but the warning did go away\u0026hellip;.\u003c/p\u003e\n\u003cp\u003eSo how to persist this change when the filesystem is blown away when the\ncontainer is updated?\u003c/p\u003e\n\u003ch2 id=\"in-the-config\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIn the config\u003c/span\u003e \u003ca href=\"#in-the-config\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI set the env var \u003ccode\u003ePHP_OPCACHE_MEMORY_CONSUMPTION\u003c/code\u003e in the \u003ccode\u003edocker-compose.yml\u003c/code\u003e\nfile to 256 and voila\u0026hellip; the change should persist\u0026hellip;\u003c/p\u003e\n",
      "content_text": "\n# Intro\n\nToday I was combing through Nextcloud, just taking a gander at the apps, updates, etc.\n\n\u003e The OPcache buffer is nearly full. To assure that all scripts can be hold in\n\u003e cache, it is recommended to apply \"opcache.memory_consumption\" to your PHP\n\u003e configuration with a value higher than \"128\"\n\nSo I know _nothing_ about `php` and I had no idea where to set this... Also I'm\nrunning Nextcloud in a container so any updates will be blown away when the\ncontainer is removed (say next time I update Nextcloud)...\n\nAfter a little duckduckgoing I came across [this reddit\npost](https://www.reddit.com/r/NextCloud/comments/1gpxl5b/need_help_with_php_opcache_module_warning/)\nwhere a guy had the same issue...\n\nThe long and short of it is that on _single instance nextcloud deployments_\nthis php memory consumption value can be too low for Nextcloud to keep up with\nthe demand... You can see in the message they just recommend upping the value.\n\nSo I had to think about how to persist the change and have it take effect now\nideally without affecting any users too much... So here's what I did - made the\nchange in 2 places:\n\n## In the container\n\nI exec'd into the container and used this handy `sed` command `sed -i\n's/^opcache\\.memory_consumption\\s*=\\s*128$/opcache.memory_consumption=256/'\n/usr/local/etc/php/conf.d/opcache-recommended.ini` to update the value in the\nrunning container - and I guess `php` maybe is loaded up dynamically or\nsomething? but the warning did go away....\n\nSo how to persist this change when the filesystem is blown away when the\ncontainer is updated?\n\n## In the config\n\nI set the env var `PHP_OPCACHE_MEMORY_CONSUMPTION` in the `docker-compose.yml`\nfile to 256 and voila... the change should persist...\n",
      "summary": "Today I was combing through Nextcloud, just taking a gander at the apps, updates, etc.",
      "date_published": "2025-06-23T08:46:20Z",
      "date_modified": "2025-06-23T08:46:20Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "nextcloud"
      ]
    },
    {
      "id": "https://pype.dev/quit-issueing-200s-for-500s/",
      "url": "https://pype.dev/quit-issueing-200s-for-500s/",
      "title": "Quit issueing 200s for 500s",
      "content_html": "\u003ch1 id=\"stop-this-nonsense\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStop this nonsense\u003c/span\u003e\u003c/h1\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250622115500_5bd3bc21.png\" alt=\"20250622115500_5bd3bc21.png\"\u003e\n\u003c/figure\u003e\n",
      "content_text": "\n# Stop this nonsense\n\n![20250622115500_5bd3bc21.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250622115500_5bd3bc21.png)\n",
      "date_published": "2025-06-22T06:55:21Z",
      "date_modified": "2025-06-22T06:55:21Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "nonsense",
        "google",
        "veo3"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-710/",
      "url": "https://pype.dev/thoughts-710/",
      "title": "💭 matduggan.com/what-would-a-kubernetes-2-0-look-like/",
      "content_text": "\n\u003ca href=\"https://matduggan.com/what-would-a-kubernetes-2-0-look-like/\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://matduggan.com/what-would-a-kubernetes-2-0-look-like/\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - matduggan.com/what-would-a-kubernetes-2-0-look-like/\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://matduggan.com/what-would-a-kubernetes-2-0-look-like/\"\u003ematduggan.com/what-would-a-kubernetes-2-0-look-like/\u003c/a\u003e\n\n---\n\nThis article was longer than I had time to really consume but a heading caught my eye that I wanted to agree with - the author says k8s 2.0 may consider HCL instead of YAML. And as a recent terraform / open-tofu adopter I gotta say I would be ALL FOR THIS\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 [[ thoughts ]]\n",
      "summary": "This article was longer than I had time to really consume but a heading caught my eye that I wanted to agree with - the",
      "date_published": "2025-06-20T11:53:33Z",
      "date_modified": "2025-06-20T11:53:33Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "k8s",
        "tech",
        "devops",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/thinking-in-diagrams/",
      "url": "https://pype.dev/thinking-in-diagrams/",
      "title": "Thinking In Diagrams",
      "content_text": "\n# Intro\n\nSomething I didn't appreciate earlier on in my career (and I'm only 8 as far as\nthe age of that career goes anyways) was thinking through problems with\ndiagrams... I've often approached a problem with code first, and immediately\nstarted implementing a solution. There's nothing actually wrong with that especially when considering:\n\n1. people think differently\n2. some people think with their fingers\n3. recognizing that the first thing you build should be thrown away should give you freedom to \"just start\"\n\nNow with that said, let's chat about how awesome diagrams are though...\n\n1. they put context in front of you without details\n2. you can express many more components of a system in a consumable way when compared to code\n3. diagrams help prioritize work and bubble up maybe unforseen dependencies\n4. they force you to wireframe before getting lost in the details\n\n## Wireframing\n\nOne of my biggest issue when I \"just start coding\" is not having a direction...\nI can put a few place holders `def this:` `def that:` in a few python modules,\nbut without having every place holder in front of me it is hard to keep them\nstraight, know the order or dependencies, etc.\n\nDiagrams aren't beholden to a text file or series of them, and you can fit much\nmore on your screen because you have all the X-Y available to you instead of\nthe height of your monitor times your formatted lined length (usually 120 for\nme).\n\nDiagrams also make it easy to spew out onto a page the different components you\nneed for a solution, without committing to an order (something that I cannot\nexpress well in scripts)\n\n## A Plan\n\nDiagramming with purpose can get you to the place of having almost a\npaint-by-number experience building your app/system/etc. Beecause you separate\nthe implemetation from the design and when you implement BASED on a design\nrather than implement to DISCOVER a design you will be more efficient and in my\nexperience I've improved my ability to forsee issues that I wouldn't have seen,\nor have historically missed, by putting the design down in front of me, the\ndetails of any integrations, etc. before starting to \"just code\".\n\n## Good Enough\n\nA final note about diagrams for right now is that they're easy to leave as\n\"good enough\". The point was to get a guide for solving a problem.\n\nDiagrams don't have to be picture perfect, even for documentation's sake. They\ncan even remain somewhat unfinished (permitted you don't get yourself into a\nbind of treating diagrams as docs that you didn't clean up)\n\nHere's an example of what I mean - I'm working on a simple workflow to take [my-thoughts](/my-thoughts) and post them to [[nostr]]. I started with a diagram like this...\n\n![20250622112223_f1838d9b.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250622112223_f1838d9b.png)\n\n\u003e In parallel I'm working on posting my blog to other social media - this small\n\u003e workflow is apart of a larger initiative I'm working on at home\n\nAS you can see this diagram isn't super detailed or pretty. But what it does it\nhelp me see the high level points of bringing my published thoughts to nostr as\nposts.\n\nAfter I have the simple steps, I've started diagramming out more details of each step:\n\n![20250622112415_f18663e3.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250622112415_f18663e3.png)\n\nThe diagram is easy to put notes on, add some color to for problems not yet solved, etc.\n\nAnd then with this diagram in front of me, it's easy to begin building\nsmall-scoped components that will be assembled to my larger system, but without\nthe pressure of constructing every part of the system at once\n\n# Fin\n\nDiagrams help me break up a problem into manageable chunks so that I can put\nthe things I need later on the back-burner and can focus on one achievable\nthing at a time.\n",
      "summary": "Something I didn't appreciate earlier on in my career (and I'm only 8 as far as the age of that career goes anyways) was thinking through problems with...",
      "date_published": "2025-06-20T06:55:31Z",
      "date_modified": "2025-06-20T06:55:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "dev-ops",
        "planning"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-709/",
      "url": "https://pype.dev/thoughts-709/",
      "title": "💭 pypeaday/whose-turn-is-it: Simple uv webapp to determine order…",
      "content_text": "\n\u003ca href=\"https://github.com/pypeaday/whose-turn-is-it\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://github.com/pypeaday/whose-turn-is-it\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - pypeaday/whose-turn-is-it: Simple uv webapp to determine order fo players for an event\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://github.com/pypeaday/whose-turn-is-it\"\u003epypeaday/whose-turn-is-it: Simple uv webapp to determine order fo players for an event\u003c/a\u003e\n\n---\n\nWe pray together as a family every night, but every single flipping night my daughter's fight about the order we go in... well, NO MORE!\n\nI built a simple uv fastapi app that randomly sorts a list from an environment var `PLAYERS` and presents it in the web page at `/generate`.\n\nSuper easy `uv run` at home...\n\nI love that with enough knowledge, or the right kind of knowledge, you can build like anything you want!\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 [[ thoughts ]]\n",
      "summary": "We pray together as a family every night, but every single flipping night my daughter's fight about the order we go in..",
      "date_published": "2025-06-20T01:23:26Z",
      "date_modified": "2025-06-20T01:23:26Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "reflection",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-707/",
      "url": "https://pype.dev/thoughts-707/",
      "title": "💭 Microsoft planning thousands of job cuts aimed at salespeople,…",
      "content_text": "\n\u003ca href=\"https://www.reuters.com/business/world-at-work/microsoft-planning-thousands-job-cuts-aimed-salespeople-bloomberg-news-reports-2025-06-18/\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://www.reuters.com/business/world-at-work/microsoft-planning-thousands-job-cuts-aimed-salespeople-bloomberg-news-reports-2025-06-18/\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - Microsoft planning thousands of job cuts aimed at salespeople, Bloomberg News reports | Reuters\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://www.reuters.com/business/world-at-work/microsoft-planning-thousands-job-cuts-aimed-salespeople-bloomberg-news-reports-2025-06-18/\"\u003eMicrosoft planning thousands of job cuts aimed at salespeople, Bloomberg News reports | Reuters\u003c/a\u003e\n\n---\n\nmore cuts for the sake of AI investment... this is getting out of hand. I'm thankful that I don't work in a field that will be overtaken by AI in the sense of losing my job (I mean we're like 47 months into AI will end dev jobs tomorrow) - of course I have to learn about the tools and LLMs have increased my output in many ways. If I was fearful of losing my job, for example being in Sales at Microsoft, I would be using every on-the-clock hour to find a new job...\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 [[ thoughts ]]\n",
      "summary": "more cuts for the sake of AI investment... this is getting out of hand. I'm thankful that I don't work in a field that w",
      "date_published": "2025-06-19T10:51:59Z",
      "date_modified": "2025-06-19T10:51:59Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "layoffs",
        "microsoft",
        "tech",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/homelab-youtube-podcasts-to-audiobookshelf-pipeline/",
      "url": "https://pype.dev/homelab-youtube-podcasts-to-audiobookshelf-pipeline/",
      "title": "Homelab Youtube Podcasts to AudioBookShelf Pipeline",
      "content_text": "\nI wrote about using [[homelab-pinchflat]] for at-home mashup creation and a song flow\nfor church. But I use it for more than, that and here's another prime use case\n\nThere are podcasts on YouTube I want to follow - The Standup with Prime, or\nShawn Ryan's show. Basically I use pinchflat to download the audio and serve up\nthose files in ABS (audiobookshelf)\n",
      "summary": "I wrote about using homelab-pinchflat for at-home mashup creation and a song flow for church. But I use it for more than, that and here's another prime use case",
      "date_published": "2025-06-18T13:53:12Z",
      "date_modified": "2025-06-18T13:53:12Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "audiobookshelf",
        "pinchflat"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-705/",
      "url": "https://pype.dev/thoughts-705/",
      "title": "💭 neverjust",
      "content_text": "\n\u003ca href=\"https://www.neverjust.net/\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://www.neverjust.net/\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - neverjust\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://www.neverjust.net/\"\u003eneverjust\u003c/a\u003e\n\n---\n\nYour \"just\" comes from seeing only the surface of the problem. Like an iceberg, what's visible is just a fraction\n\nThis is a good reminder when discussing issues within a team or with your buddies... trivializing someone else's problem, in tech or out, it not a good look\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 [[ thoughts ]]\n",
      "summary": "Your \"just\" comes from seeing only the surface of the problem. Like an iceberg, what's visible is just a fraction This",
      "date_published": "2025-06-18T10:33:49Z",
      "date_modified": "2025-06-18T10:33:49Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "just",
        "dev",
        "tech",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/source-up-for-nested-envrc/",
      "url": "https://pype.dev/source-up-for-nested-envrc/",
      "title": "source_up for nested .envrc",
      "content_html": "\u003ch1 id=\"tldr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTLDR\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI have a use case for nesting directories with cascading .envrc files. and ran\ninto an issue where the parent environment was being reset by the child\nenvironment. The fix is \u003ccode\u003esource_up\u003c/code\u003e\u003c/p\u003e\n\u003ch1 id=\"context\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eContext\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI manage some infrastructure at home with a folder like this:\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├── homelab\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── .envrc\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   └── stack1\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│       ├── .envrc\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eI do this so that the \u0026ldquo;homelab-wide\u0026rdquo; terraform variables can be exported with\n\u003ccode\u003eTF_VAR_the_variable\u003c/code\u003e in the \u003ccode\u003ehomelab/.envrc\u003c/code\u003e and then the stack specific\nvariables in \u003ccode\u003estack1/.envrc\u003c/code\u003e get exported like \u003ccode\u003eTF_VAR_stack_specific_var\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eFor example, my cloudflare account id is needed in every stack, but the\ncloudflare zone id is different per stack - so instead of copying the account\nid var to every stack (however the vars are managed), I can set it in a parent\n.envrc and share it in nested stack directories\u003c/p\u003e\n\u003ch2 id=\"problem\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eProblem\u003c/span\u003e \u003ca href=\"#problem\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003e# homelab/.envrc\n#!/bin/bash\nexport VAR_FOO=BAR\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eSo when I \u003ccode\u003ecd homelab\u003c/code\u003e I get\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enic in /tmp/homelab   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ direnv allow\ndirenv: loading /tmp/homelab/.envrc\ndirenv: export +VAR_FOO\n\nnic in /tmp/homelab   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ env | grep VAR\nVAR_FOO=BAR\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eYou can see \u003ccode\u003eVAR_FOO\u003c/code\u003e is set\u0026hellip; but now I\u0026rsquo;ll \u003ccode\u003ecd stack1\u003c/code\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enic in /tmp/homelab   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ cd stack1\ndirenv: error /tmp/homelab/stack1/.envrc is blocked. Run `direnv allow` to approve its content\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ direnv allow\ndirenv: loading /tmp/homelab/stack1/.envrc\ndirenv: export +VAR_BAZ\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ env | grep VAR\nVAR_BAZ=BAM\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAs you can see \u003ccode\u003eVAR_FOO\u003c/code\u003e was unset, and \u003ccode\u003eVAR_BAZ\u003c/code\u003e is set\u0026hellip; so \u003ccode\u003ehomelab/.envrc\u003c/code\u003e was blown away!\u003c/p\u003e\n\u003ch2 id=\"fix\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFix\u003c/span\u003e \u003ca href=\"#fix\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eEdit \u003ccode\u003estack1/.envrc\u003c/code\u003e with a \u003ccode\u003esource_up\u003c/code\u003e command\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# stack1/.envrc # NEW\n#!/bin/bash\nsource_up  # source the parent environment\nexport VAR_BAZ=BAM\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAfter the edit\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003enic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ vim .envrc\ndirenv: error /tmp/homelab/stack1/.envrc is blocked. Run `direnv allow` to approve its content\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET  took 5s\n⬢ [devbox] ❯ direnv allow\ndirenv: loading /tmp/homelab/stack1/.envrc\ndirenv: loading /tmp/homelab/.envrc\ndirenv: export +VAR_BAZ +VAR_FOO\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ env | grep VAR\nVAR_FOO=BAR\nVAR_BAZ=BAM\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eBoth vars are set!\u003c/p\u003e\n",
      "content_text": "\n# TLDR\n\nI have a use case for nesting directories with cascading .envrc files. and ran\ninto an issue where the parent environment was being reset by the child\nenvironment. The fix is `source_up`\n\n# Context\n\nI manage some infrastructure at home with a folder like this:\n\n```bash\n.\n├── homelab\n│   ├── .envrc\n│   └── stack1\n│       ├── .envrc\n```\n\nI do this so that the \"homelab-wide\" terraform variables can be exported with\n`TF_VAR_the_variable` in the `homelab/.envrc` and then the stack specific\nvariables in `stack1/.envrc` get exported like `TF_VAR_stack_specific_var`.\n\nFor example, my cloudflare account id is needed in every stack, but the\ncloudflare zone id is different per stack - so instead of copying the account\nid var to every stack (however the vars are managed), I can set it in a parent\n.envrc and share it in nested stack directories\n\n## Problem\n\n```\n# homelab/.envrc\n#!/bin/bash\nexport VAR_FOO=BAR\n```\n\nSo when I `cd homelab` I get\n\n```\nnic in /tmp/homelab   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ direnv allow\ndirenv: loading /tmp/homelab/.envrc\ndirenv: export +VAR_FOO\n\nnic in /tmp/homelab   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ env | grep VAR\nVAR_FOO=BAR\n\n```\n\nYou can see `VAR_FOO` is set... but now I'll `cd stack1`\n\n```\nnic in /tmp/homelab   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ cd stack1\ndirenv: error /tmp/homelab/stack1/.envrc is blocked. Run `direnv allow` to approve its content\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ direnv allow\ndirenv: loading /tmp/homelab/stack1/.envrc\ndirenv: export +VAR_BAZ\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ env | grep VAR\nVAR_BAZ=BAM\n\n```\n\nAs you can see `VAR_FOO` was unset, and `VAR_BAZ` is set... so `homelab/.envrc` was blown away!\n\n## Fix\n\nEdit `stack1/.envrc` with a `source_up` command\n\n```\n# stack1/.envrc # NEW\n#!/bin/bash\nsource_up  # source the parent environment\nexport VAR_BAZ=BAM\n\n```\n\nAfter the edit\n\n```\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ vim .envrc\ndirenv: error /tmp/homelab/stack1/.envrc is blocked. Run `direnv allow` to approve its content\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET  took 5s\n⬢ [devbox] ❯ direnv allow\ndirenv: loading /tmp/homelab/stack1/.envrc\ndirenv: loading /tmp/homelab/.envrc\ndirenv: export +VAR_BAZ +VAR_FOO\n\nnic in /tmp/homelab/stack1   (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET\n⬢ [devbox] ❯ env | grep VAR\nVAR_FOO=BAR\nVAR_BAZ=BAM\n\n```\n\nBoth vars are set!\n",
      "summary": "I have a use case for nesting directories with cascading .envrc files. and ran into an issue where the parent environment was being reset by the child...",
      "date_published": "2025-06-17T09:52:17Z",
      "date_modified": "2025-06-17T09:52:17Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "direnv",
        "cli"
      ]
    },
    {
      "id": "https://pype.dev/stable-diffusion-qr-codes-for-josh/",
      "url": "https://pype.dev/stable-diffusion-qr-codes-for-josh/",
      "title": "Stable Diffusion QR Codes for Josh",
      "content_html": "\u003ch1 id=\"after-my-blunder\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eAfter my blunder\u003c/span\u003e\u003c/h1\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250627111955_e86d5db2.png\" alt=\"20250627111955_e86d5db2.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1),y\nSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 784129766, Size: 1536x1536, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, ControlNet 1: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.75, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003ch1 id=\"final-candidates\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFinal Candidates\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eTurns out upscaling can mess up the design\u0026hellip; so I generated new ones at the high res instead of using AI to upscale\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621122040_2dfef68f.png\" alt=\"20250621122040_2dfef68f.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 1827707865, Size: 1536x1536, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 1536, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 1.0, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, ControlNet 1: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.88, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eI turned the brightness down on the above one a little and got a better result\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621122629_1058a54f.png\" alt=\"20250621122629_1058a54f.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 1827707865, Size: 1536x1536, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 1536, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.85, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, ControlNet 1: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.75, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003ch1 id=\"some-favorites\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSome favorites\u003c/span\u003e\u003c/h1\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621020431_d6c69cd2.png\" alt=\"20250621020431_d6c69cd2.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.1)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 2926433856, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 1.0, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, ControlNet 1: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.85, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621020700_37cf8c79.png\" alt=\"20250621020700_37cf8c79.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 1827707865, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 1.0, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, ControlNet 1: \u0026ldquo;Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.88, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003ch1 id=\"codes-and-prompts\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCodes and prompts\u003c/span\u003e\u003c/h1\u003e\n\u003chr\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250617112815_b986db88.png\" alt=\"20250617112815_b986db88.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eprompt: (breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear blue water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (birch trees:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (photorealistic:1.2), (atmospheric lighting:1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 7, Seed: 2206746812, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.15, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.85, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250617113111_d9075159.png\" alt=\"20250617113111_d9075159.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (birch trees:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (photorealistic:1.2), (greens and blues:1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 7, Seed: 2485699904, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250617113250_839c12e2.png\" alt=\"20250617113250_839c12e2.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(breathtaking scenic view:1.2), (fjord:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (birch trees:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (photorealistic:1.2), (green:1.2)\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 8, Seed: 1055874558, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"top-contendor\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTop Contendor\u003c/span\u003e \u003ca href=\"#top-contendor\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250618005414_d77ff449.png\" alt=\"20250618005414_d77ff449.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (gaming concept art:1.2),\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 8.5, Seed: 1240242415, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250618005451_84cd9350.png\" alt=\"20250618005451_84cd9350.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (gaming concept art:1.2),\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 8.5, Seed: 1240242416, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: False, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eScans!\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621004622_6c9a66df.png\" alt=\"20250621004622_6c9a66df.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2),\u003c/p\u003e\n\u003cp\u003e(blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Exponential, CFG scale: 7, Seed: 4060643233, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.4, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n\u003chr\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621004844_64e6b94b.png\" alt=\"20250621004844_64e6b94b.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003e(fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green blue:1.0), (serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2),\u003c/p\u003e\n\u003cp\u003eNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\u003c/p\u003e\n\u003cp\u003eSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Exponential, CFG scale: 7, Seed: 3713594572, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \u0026ldquo;Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.4, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\u0026rdquo;, Version: v1.9.4\u003c/p\u003e\n",
      "content_text": "\n# After my blunder\n\n![20250627111955_e86d5db2.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250627111955_e86d5db2.png)\n\n(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1),y\nSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 784129766, Size: 1536x1536, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\", ControlNet 1: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.75, Pixel Perfect: True, Control Mode: Balanced\", Version: v1.9.4\n\n# Final Candidates\n\nTurns out upscaling can mess up the design... so I generated new ones at the high res instead of using AI to upscale\n\n![20250621122040_2dfef68f.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621122040_2dfef68f.png)\n\n(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 1827707865, Size: 1536x1536, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 1536, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 1.0, Pixel Perfect: False, Control Mode: Balanced\", ControlNet 1: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.88, Pixel Perfect: False, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\nI turned the brightness down on the above one a little and got a better result\n\n![20250621122629_1058a54f.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621122629_1058a54f.png)\n\n(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 1827707865, Size: 1536x1536, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 1536, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.85, Pixel Perfect: False, Control Mode: Balanced\", ControlNet 1: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.75, Pixel Perfect: False, Control Mode: Balanced\", Version: v1.9.4\n\n# Some favorites\n\n![20250621020431_d6c69cd2.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621020431_d6c69cd2.png)\n\n(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.1)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 2926433856, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 1.0, Pixel Perfect: False, Control Mode: Balanced\", ControlNet 1: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.85, Pixel Perfect: False, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\n![20250621020700_37cf8c79.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621020700_37cf8c79.png)\n\n(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1),(serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2), (ultra realistic: 1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: Euler a, Schedule type: Automatic, CFG scale: 7, Seed: 1827707865, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 1.0, Pixel Perfect: False, Control Mode: Balanced\", ControlNet 1: \"Module: none, Model: control_v1p_sd15_brightness [5f6aa6ed], Weight: 0.25, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.25, Guidance End: 0.88, Pixel Perfect: False, Control Mode: Balanced\", Version: v1.9.4\n\n# Codes and prompts\n\n---\n\n![20250617112815_b986db88.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250617112815_b986db88.png)\n\nprompt: (breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear blue water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (birch trees:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (photorealistic:1.2), (atmospheric lighting:1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 7, Seed: 2206746812, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.15, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.85, Pixel Perfect: True, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\n![20250617113111_d9075159.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250617113111_d9075159.png)\n\n(breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (birch trees:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (photorealistic:1.2), (greens and blues:1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 7, Seed: 2485699904, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\n![20250617113250_839c12e2.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250617113250_839c12e2.png)\n\n(breathtaking scenic view:1.2), (fjord:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (birch trees:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (photorealistic:1.2), (green:1.2)\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 8, Seed: 1055874558, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\n## Top Contendor\n\n![20250618005414_d77ff449.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250618005414_d77ff449.png)\n\n(breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (gaming concept art:1.2),\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 8.5, Seed: 1240242415, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: False, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\n![20250618005451_84cd9350.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250618005451_84cd9350.png)\n\n(breathtaking scenic view:1.2), (fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (ultra-realistic:1.3), (8K landscape photography:1.3), (gaming concept art:1.2),\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Karras, CFG scale: 8.5, Seed: 1240242416, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.1, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: False, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\nScans!\n\n![20250621004622_6c9a66df.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621004622_6c9a66df.png)\n\n(fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green orange:1.0), (serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2),\n\n(blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Exponential, CFG scale: 7, Seed: 4060643233, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.4, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\", Version: v1.9.4\n\n---\n\n![20250621004844_64e6b94b.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250621004844_64e6b94b.png)\n\n(fjords of Finland:1.3), (steep forested cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), , (early morning light:1.2), (golden hour glow:1.1), (pine trees:1.1), (green blue:1.0), (serene nature:1.2), (8K landscape photography:1.3), (gaming concept art:1.2),\n\nNegative prompt: (blurry:1.3), (low resolution:1.3), (overexposed:1.2), (cartoonish:1.3), (unrealistic colors:1.3), (people:1.1), (city buildings:1.2), (vehicles:1.2), (text:1.3), (distortion:1.2)\n\nSteps: 30, Sampler: DPM++ 2M SDE, Schedule type: Exponential, CFG scale: 7, Seed: 3713594572, Size: 768x768, Model hash: 4199bcdd14, Model: revAnimated_v122EOL, ControlNet 0: \"Module: none, Model: controlnetQRPatternQR_v2Sd15 [2d8d5750], Weight: 1.4, Resize Mode: Crop and Resize, Processor Res: 512, Threshold A: 0.5, Threshold B: 0.5, Guidance Start: 0.0, Guidance End: 0.8, Pixel Perfect: True, Control Mode: Balanced\", Version: v1.9.4\n",
      "summary": "(fjords of Finland:1.3), (steep cliffs:1.1), (crystal-clear water:1.2), (reflection of sky on water:1.1), (early morning light:1.2), (golden hour glow:1.1),...",
      "date_published": "2025-06-17T06:27:28Z",
      "date_modified": "2025-06-17T06:27:28Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "digital-harbor",
        "qr",
        "stable-diffusion"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-698/",
      "url": "https://pype.dev/thoughts-698/",
      "title": "💭 Snowflake Status - Certificate Authority Update for AWS Regions",
      "content_text": "\n\u003ca href=\"https://status.snowflake.com/incidents/txclg2cyzq32\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://status.snowflake.com/incidents/txclg2cyzq32\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - Snowflake Status - Certificate Authority Update for AWS Regions\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://status.snowflake.com/incidents/txclg2cyzq32\"\u003eSnowflake Status - Certificate Authority Update for AWS Regions\u003c/a\u003e\n\n---\n\nA recent Snowflake issue borked some old pipelines - thankfully it appears to be a simple fix. Just goes to show you that you can't just deploy pipelines and expect them to live forever, they have to be maintained when there are ANY external dependencies\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 [[ thoughts ]]\n",
      "summary": "A recent Snowflake issue borked some old pipelines - thankfully it appears to be a simple fix. Just goes to show you tha",
      "date_published": "2025-06-16T16:37:12Z",
      "date_modified": "2025-06-16T16:37:12Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "snowflake",
        "tech",
        "python",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/u-blue-aurora-upgrade-broke-hwa-docker/",
      "url": "https://pype.dev/u-blue-aurora-upgrade-broke-hwa-docker/",
      "title": "U-Blue Aurora Upgrade Broke HWA Docker",
      "content_html": "\u003cp\u003eI use Universal Blue\u0026rsquo;s \u003ca href=\"https://getaurora.dev/en\"\u003eAurora\u003c/a\u003e distribution on my daily driver desktop, and\nI rebooted today which led what I imagine is a kernel update. This broke all of\nmy docker containers using a GPU.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003envidia-container-cli: detection error: open failed: /usr/lib/libnvidia-tls.so.570.153.02: no such file or directory: unknown\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eThankfully a reboot and booting into the older environment fixes everything\u0026hellip;\nLinux and immutability FTW\u003c/p\u003e\n\u003ch2 id=\"related\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eRelated\u003c/span\u003e \u003ca href=\"#related\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://universal-blue.discourse.group/t/cannot-use-nvidia-runtime-in-docker-since-update-to-fedora-42/8812/3\"\u003eDiscorse Thread\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n",
      "content_text": "\nI use Universal Blue's [Aurora](https://getaurora.dev/en) distribution on my daily driver desktop, and\nI rebooted today which led what I imagine is a kernel update. This broke all of\nmy docker containers using a GPU.\n\n```\nnvidia-container-cli: detection error: open failed: /usr/lib/libnvidia-tls.so.570.153.02: no such file or directory: unknown\n```\n\nThankfully a reboot and booting into the older environment fixes everything...\nLinux and immutability FTW\n\n## Related\n\n* [Discorse Thread](https://universal-blue.discourse.group/t/cannot-use-nvidia-runtime-in-docker-since-update-to-fedora-42/8812/3)\n\n",
      "summary": "I use Universal Blue's Aurora distribution on my daily driver desktop, and I rebooted today which led what I imagine is a kernel update. This broke all of my...",
      "date_published": "2025-06-15T08:42:09Z",
      "date_modified": "2025-06-15T08:42:09Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "universal-blue"
      ]
    },
    {
      "id": "https://pype.dev/homelab-pinchflat/",
      "url": "https://pype.dev/homelab-pinchflat/",
      "title": "Homelab Pinchflat",
      "content_text": "\n# Why?\n\nI've written about [[self-hosted-media]] before and addressed some thoughts with YouTube. \n\nLately though I've had 2 use cases for offline YouTube media and I'm now\nsolving this problem by using [pinchflat](https://pinchflat.com/) as my `ytdl`\nsolution + file system organization for use throughout my homelab.\n\n## Use Case 1\n\nAt church we use pre-recorded music to sing along to, or at least we used to,\nthankfully as of June 2025 we're doing live-music every week. But in the before\ntimes I would strip the audio out of song files we had purchased and then made\nlyric videos with the instrumental tracks. As we wanted to try new songs out we\ndidn't want to purchase them in case they didn't work out for us to use and\nthat ends up being a waste (church budget is very tight). So using songs\navailable on YouTube is an easy solution, but the only way to strip the vocals\nand see if the song sounded right with our singers and equipment was to make\nthe lyric video and rehearse it... Chicken or Egg problem unless we can freely\ntry it before purchasing... So using Pinchflat we can download songs in that\nplaylist, I can edit the files, the team can try it out, and if we like it we\ncan use it without hitting the budget any harder than necessary.\n\n\u003e Pinchflat syncs that playlist to a folder on my NAS that another process\n\u003e watches and performs the audio stripping workflow\n\n## Use Case 2\n\nI just wrote about [[if-you-want-something-make-it-so-song-style]] which is the\nsecond use case - cutting mashups of my favorite songs on youtube and then\nhosting them myself for easy listening\n\n# The Setup\n\nI run it in docker compose like practically all my other services\n\n```yml\nservices:\n  pinchflat:\n    container_name: \"pinchflat\"\n    image: \"ghcr.io/kieraneglin/pinchflat:latest\"\n    ports:\n      - \"8945:8945\"\n    volumes:\n      - \"/tank/encrypted/docker/the-pit-zfs/pinchflat/config:/config:rw\"\n      - \"/tank/encrypted/nas/media/downloads/pinchflat/downloads:/downloads:rw\"\n    environment:\n      TZ: \"America/Chicago\"\n      PUID: \"1000\"\n      PGID: \"1000\"\n    restart: unless-stopped\n    deploy:\n      resources:\n        limits:\n          memory: \"4g\"\n```\n\n# The GUI\n\nThe WebUI is easy to navigate - you organize your downloads however you want with \"sources\", and here's how I setup the source for these mashups that I want to edit... I add them to my playlist and Pinchflat grabs them for me automatically\n![20250614144201_fa16e7ac.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250614144201_fa16e7ac.png)\n\n![20250614144307_bdf12ee0.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250614144307_bdf12ee0.png)\n\nThat's basically it - then as you can see in the compose file, I get the files in a folder on my NAS and from there I can edit them as I need.\n\n## Example\n\nThe church app that does the audio processing is in a compose stack with the\npinchflat download directory on the host mounted in - this is how containers in\ndifferent stacks can share data, by sharing a filesystem bind mount\n\n```yml\nservices:\n  olivet_watcher_app:\n    image: pypeaday/vocal-remover-watcher\n    container_name: olivet-youtube-watcher\n    volumes:\n      - /tank/encrypted/nas/media/downloads/pinchflat/downloads:/input:ro\n      - /tank/encrypted/docker/vocal_remover_app/watcher-app/processed_mp3s:/output\n      - /tank/encrypted/docker/vocal_remover_app/watcher-app/db:/data\n    working_dir: /app\n```\n\n",
      "summary": "I've written about self-hosted-media before and addressed some thoughts with YouTube.",
      "date_published": "2025-06-14T08:51:21Z",
      "date_modified": "2025-06-14T08:51:21Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/if-you-want-something-make-it-so-song-style/",
      "url": "https://pype.dev/if-you-want-something-make-it-so-song-style/",
      "title": "If You Want Something Make It So - Song Style",
      "content_text": "\nIn grad school a professor of mine, Mark Hunacek, said one of the most profound\nthings I've ever heard to the tune \"if you want it, then make it so\"... It's a\ngreat benefit to mathematicians at the bar to start a conversation with \"hey\nlet's assume X because we can\"...\n\nThis phrase has affected my work greatly to the work I do and how I go about it- \nit's literally changed how I design some of my code. But I also want this\nmantra to flow into other parts of my life...\n\n\u003e So here's a post about how I use Pinchflat and Davinci Resolve to tickle my\n\u003e brain with bespoke versions of beautifully crafted mashups\n\nIn this example I took a Goobsie mashup, cut the part I didn't like quite as\nmuch (though admittedly it's still beautiful) and spliced it with itself to\ngive me a longer running version of a musical piece that takes me to another\nworld...\n\n[OG Youtube Mashup](https://www.youtube.com/watch?v=7v7nreDnmDg)\n\n## Acquiring the Media\n\nPinchflat isn't really necessary here, anyone can use `youtubedl` or another\ntool to download from YouTube. I do use Pinchflat to subscribe to Youtube\nplaylists to automate this process for me. See more about that in\n[[homelab-pinchflat]]\n\n## Creating the Mashup\n\nSo Goobsie makes actually good mashups, and I'm not anything even remotely close to an audio engineer or whatever, but there's a segment of this song that just strikes my whole body in a unique way and I want to sometimes just listen to it conveniently... So enter Davinci Resolve... Basically I took the downloaded file, added it twice to a timeline, and then faded in and out until it sounded ok to me.\n\n![20250614135418_b75bc26a.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250614135418_b75bc26a.png)\n\n## Listening to the Mashup\n\nSo now how do I listen to this?\n\nFor the moment, it's a favorite in my Jellyfin library and I listen through\nthat client. It's a TODO item for me to set-up a more music-centric streaming\nservice\n\n\n!!! info \"Go Forth\"\n    So Go Forth - and if you want something, make it so",
      "summary": "In grad school a professor of mine, Mark Hunacek, said one of the most profound things I've ever heard to the tune \"if you want it, then make it so\"... It's...",
      "date_published": "2025-06-14T08:44:14Z",
      "date_modified": "2025-06-14T08:44:14Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "series-if-you-want-it-make-it-so",
        "music"
      ]
    },
    {
      "id": "https://pype.dev/zsa-typing-challenge-issue/",
      "url": "https://pype.dev/zsa-typing-challenge-issue/",
      "title": "ZSA typing challenge issue",
      "content_html": "\u003ch1 id=\"zsa\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eZSA\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI love \u003ca href=\"https://zsa.io\"\u003ezsa\u003c/a\u003e and have been using a moonlander for a few years now.\u003c/p\u003e\n\u003cp\u003eThey recently launched \u003ca href=\"https://typ.ing\"\u003etyp.ing\u003c/a\u003e as a way to train your typing skills - it\u0026rsquo;s a lot like \u003ca href=\"https://monkeytype.com\"\u003emonkeytype\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI have a thought about it \u003ca href=\"https://thoughts.waylonwalker.com/post/682\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"today-s-challenge\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eToday’s Challenge\u003c/span\u003e \u003ca href=\"#today-s-challenge\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI went to do today\u0026rsquo;s daily challenge and have ran into an issue where part of\nthe challenge is to type \u003ccode\u003e/\u003c/code\u003e the forward slash character but it\u0026rsquo;s actually a\nkeybind on the website for navigation (well done btw!)\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250613130724_06b0f9ef.png\" alt=\"20250613130724_06b0f9ef.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eSo when I try to type the challenge I actually bring up the nav menu\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250613131031_ce06cf48.png\" alt=\"20250613131031_ce06cf48.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eSo for today I\u0026rsquo;ll take the error on the character to complete it and maybe ZSA\ncan look into this?\u003c/p\u003e\n",
      "content_text": "\n# ZSA\n\nI love [zsa](https://zsa.io) and have been using a moonlander for a few years now.\n\nThey recently launched [typ.ing](https://typ.ing) as a way to train your typing skills - it's a lot like [monkeytype](https://monkeytype.com).\n\nI have a thought about it [here](https://thoughts.waylonwalker.com/post/682)\n\n## Today's Challenge\n\nI went to do today's daily challenge and have ran into an issue where part of\nthe challenge is to type `/` the forward slash character but it's actually a\nkeybind on the website for navigation (well done btw!)\n\n![20250613130724_06b0f9ef.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250613130724_06b0f9ef.png)\n\nSo when I try to type the challenge I actually bring up the nav menu\n\n![20250613131031_ce06cf48.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250613131031_ce06cf48.png)\n\nSo for today I'll take the error on the character to complete it and maybe ZSA\ncan look into this?\n",
      "summary": "I love zsa and have been using a moonlander for a few years now.",
      "date_published": "2025-06-13T08:07:41Z",
      "date_modified": "2025-06-13T08:07:41Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "zsa",
        "typing"
      ]
    },
    {
      "id": "https://pype.dev/double-check-your-dns-records/",
      "url": "https://pype.dev/double-check-your-dns-records/",
      "title": "Double Check Your DNS Records",
      "content_html": "\u003ch1 id=\"my-blog\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy Blog\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI host \u003ca href=\"https://pype.dev\"\u003emy blog\u003c/a\u003e on cloudflare and manage my infra with\nterraform. I got a 522 when I went to \u003ccode\u003ehttps://www.pype.dev\u003c/code\u003e recently and I\nwondered why since \u003ccode\u003ehttps://pype.dev\u003c/code\u003e worked fine\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"terraform\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTerraform\u003c/span\u003e \u003ca href=\"#terraform\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI had this right in the terraform:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eresource\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare_pages_domain\u0026#34; \u0026#34;cf_domain\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  account_id\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003elocal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003eaccount_id\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  project_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003ecloudflare_pages_project\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003ebuild_config\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003ename\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  domain\u003c/span\u003e       \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;pype.dev\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eBut turns out the \u003ccode\u003ewww\u003c/code\u003e record was commented out for some reason in my terraform code:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eresource\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare_pages_domain\u0026#34; \u0026#34;cf_domain_www\u0026#34;\u003c/span\u003e {\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  account_id\u003c/span\u003e   \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003elocal\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003eaccount_id\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  project_name\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003ecloudflare_pages_project\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003ebuild_config\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"k\"\u003ename\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003e  domain\u003c/span\u003e       \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;www.pype.dev\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eA quick \u003ccode\u003eterraform apply -auto-approve\u003c/code\u003e (careful with this) and we\u0026rsquo;re up and\nrunning with the \u003ccode\u003ewww\u003c/code\u003e record now\u003c/p\u003e\n",
      "content_text": "\n# My Blog\n\nI host [my blog](https://pype.dev) on cloudflare and manage my infra with\nterraform. I got a 522 when I went to `https://www.pype.dev` recently and I\nwondered why since `https://pype.dev` worked fine...\n\n## Terraform\n\nI had this right in the terraform:\n\n```hcl\nresource \"cloudflare_pages_domain\" \"cf_domain\" {\n  account_id   = local.account_id\n  project_name = cloudflare_pages_project.build_config.name\n  domain       = \"pype.dev\"\n}\n```\n\nBut turns out the `www` record was commented out for some reason in my terraform code:\n\n```hcl\nresource \"cloudflare_pages_domain\" \"cf_domain_www\" {\n  account_id   = local.account_id\n  project_name = cloudflare_pages_project.build_config.name\n  domain       = \"www.pype.dev\"\n}\n```\n\nA quick `terraform apply -auto-approve` (careful with this) and we're up and\nrunning with the `www` record now\n\n",
      "summary": "I host my blog on cloudflare and manage my infra with terraform. I got a 522 when I went to recently and I wondered why since worked fine...",
      "date_published": "2025-06-13T06:53:50Z",
      "date_modified": "2025-06-13T06:53:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "dns",
        "terraform",
        "cloudflare"
      ]
    },
    {
      "id": "https://pype.dev/thoughts-690/",
      "url": "https://pype.dev/thoughts-690/",
      "title": "💭 “We would be less confidential than Google” – Proton threatens…",
      "content_text": "\n\u003ca href=\"https://www.techradar.com/vpn/vpn-privacy-security/we-would-be-less-confidential-than-google-proton-threatens-to-quit-switzerland-over-new-surveillance-law?ref=dailydev\"\u003e\n    \u003cimg\n        src=\"https://shots.wayl.one/shot/?url=https://www.techradar.com/vpn/vpn-privacy-security/we-would-be-less-confidential-than-google-proton-threatens-to-quit-switzerland-over-new-surveillance-law?ref=dailydev\u0026height=450\u0026width=800\u0026scaled_width=800\u0026scaled_height=450\u0026selectors=\"\n        alt=\"shot of post - \"We would be less confidential than Google\" – Proton threatens to quit Switzerland over new surveillance law | TechRadar\"\n        height=450\n        width=800\n    \u003e\n\u003c/a\u003e\n\nHere's my thought on \u003ca href=\"https://www.techradar.com/vpn/vpn-privacy-security/we-would-be-less-confidential-than-google-proton-threatens-to-quit-switzerland-over-new-surveillance-law?ref=dailydev\"\u003e\"We would be less confidential than Google\" – Proton threatens to quit Switzerland over new surveillance law | TechRadar\u003c/a\u003e\n\n---\n\nI've been a proton user for several years now, and pay a decently hefty subscription fee for some of their services that I love having access to (multiple domains, email relay, simplelogin, etc.). This privacy impact in Switzerland is concerning and I am definitely biting my nails over the future of this platform\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 [[ thoughts ]]\n",
      "summary": "I've been a proton user for several years now, and pay a decently hefty subscription fee for some of their services that",
      "date_published": "2025-06-13T02:19:29Z",
      "date_modified": "2025-06-13T02:19:29Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "proton",
        "email",
        "security",
        "privacy",
        "tech",
        "thoughts"
      ]
    },
    {
      "id": "https://pype.dev/example-for-andrew/",
      "url": "https://pype.dev/example-for-andrew/",
      "title": "Example For Andrew",
      "content_html": "\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250608194801_809d5306.png\" alt=\"20250608194801_809d5306.png\"\u003e\n\u003c/figure\u003e\n",
      "content_text": "\n![20250608194801_809d5306.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250608194801_809d5306.png)\n",
      "date_published": "2025-06-08T14:48:53Z",
      "date_modified": "2025-06-08T14:48:53Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/i-built-a-simple-app-for-adding-images-to-my-blog/",
      "url": "https://pype.dev/i-built-a-simple-app-for-adding-images-to-my-blog/",
      "title": "I built a simple app for adding images to my blog",
      "content_text": "\n# Quick Deets\n\nI built a simple fastAPI app called \"shotput\" that I run locally inside a git repo where I save images for my blog. The app is simple:\n\n1. upload image from clipboard\n2. app commits image to repo (path is configurable)\n3. app pushes the commit (auto-push configurable)\n4. app returns a markdown link I can paste in my blog that uses statically.io (see [[statically-io-to-help-me-out]]) to serve the image from the repo to my blog\n\nSee the image repo with app details at [images.pype.dev](https://github.com/pypeaday/images.pype.dev)\n\n# Usage\n\nAnyone could copy the script and config to their own git repo to put images in\nand run this at home - instructions are in the readme but for simple running on localhost it's just `uv run`\n\n# Example\n\n![20250607130609_dadd33eb.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250607130609_dadd33eb.png)\n\npasting an image let's you copy the markdown link for easy pasting\n\n```markdown\n![20250607130609_dadd33eb.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250607130609_dadd33eb.png)\n```\n",
      "summary": "I built a simple fastAPI app called \"shotput\" that I run locally inside a git repo where I save images for my blog. The app is simple:",
      "date_published": "2025-06-07T13:30:28Z",
      "date_modified": "2025-06-07T13:30:28Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "python",
        "uv"
      ]
    },
    {
      "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 kind 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/homelab-journey-part-1/",
      "url": "https://pype.dev/homelab-journey-part-1/",
      "title": "homelab-journey-part-1",
      "content_text": "\n# Introduction\n\nI want to start writing about my homelab and this first post can be a short\nintroduction to how I got into homelabbing and a review of my current hardware.\nI'll hopefully write about the evolution of my homelab to get to this\ncurrent point and then start writing about updates as new things hit the lab.\n\n# The Journey\n\n## Applications | Pi-Hole\n\nI started with a pi-hole appliance running on a pi-0 I\nhad lying around. So, a little introduction into SSH and foreign operating\nsystems (I was loosely familiar with Linux and so Raspbian wasn't a huge deal\nto start with) and then you could just [install pi-hole](https://docs.pi-hole.net/main/basic-install/).\nAfter installing it I got to learn a little bit about networking, DNS, DHCP,\netc. all from just exploring and trying out the features of pi-hole.\n\n## Hardware\n\nFrom there I happened to come into a few computers kind of for free that a church was\nrecycling and so now with new-to-me x86 hardware and armed with a tiny bit of\ndocker experience at work - I thought \"well I wonder if I could run pi-hole in\ndocker on a Linux machine?\" and that opened the door to running other things in docker.\n\n# Applications | Free-NAS\n\nI quickly decided I wanted a NAS after talking with a friend at work, and one\nof those computers that I inherited was the perfect target for Freenas by iX\nsystems (Freenas has since morphed into a few things - primarily TrueNAS Core\nfor the homelabber). That got me somewhat familiar with jails (which is a BSD\nconcept as FreeNAS was based on FreeBSD, not Linux) and running\ncontainers in different ways and then I decided I wanted Linux so I rebuilt with\nUbuntu.\n\n!!! note \"\"\n\n    there's been several hardware changes and such in there but that\n    does more or less get me to arrive at where I'm at today which is I have a\n    few different boxes running pretty standard operating systems for their\n    purposes and they are as follows.\n\n# Current Homelab Setup\n\n!!! note \"Find updated information at [[the-homelab]]\"\n\n## Ad blocking | Pi-Hole\n\nHardware: pi-3\n\nI have a Raspberry Pi 3 hardwired in that is running pi-hole so that's been a\npart of my infrastructure now for a long time.\n\n## Router | OPNSense\n\n!!! note \"stats\"\n\n    CPU: i5-8700\n\n    Memory: 8GB of whatever was in the box\n\n    Storage: 1TB SSD\n\nMy router is an old Optiplex SFF 5060 I got from Amazon for less than $100.\nSo I'm running a x86 system for my router with\nOpenSense on it. Just seemed it to be another home lab standard and I'd like to\nsomeday bring pi-hole over there or use AdGuard or integrate those two things\nmaybe a little bit more completely but right now they are mostly separate.\n\n## Networking | VLANs\n\nThere's a\nmanaged switch in there for some VLAN tomfoolery for my cameras and an IOT\nnetwork type of a thing.\n\n## Networking | VPN | Tailscale\n\nThey're on tailscale as well so my DNS happens over tailscale.\n\n## My Desktop\n\n!!! note \"stats\"\n\n    CPU: Ryzen 7 5700X\n\n    GPU: Nvidia 3090\n\n    Memory: 64 GB\n\n    Storage: 4TB SSD + 2 TB HDD for zfs backup\n\n    OS: Universal Blue Aurora\n\nMy daily-driver desktop is somewhat apart of my homelab now as I have a 3090 in\nthere with 20 GB of VRAM so I can run some heavier LLMs for self-hosted AI\nworkloads. Everything in that space runs in a container and then I manage\ndocker containers with compose stacks and portainer for visibility.\n\n### Applications\n\nSome of the apps I run here are ollama, open-webui, automatic1111's stable diffusion webui, whisper-webui\n\n## NAS / Server\n\n!!! note \"stats\"\n\n    CPU: Ryzen 7 5700G\n\n    GPU: None right now\n\n    Memory: 128 GB\n\n    Storage: 2x12 TB ZFS Mirror + another 2x12 TB ZFS Mirror + 500GB disk for NVR\n\n    OS: Ubuntu Server 24.04\n\nAt the moment I am managing containers using Docker Compose stacks. Many\napplications have their own stack but then I also have stacks of many\napplications. The Compose files are all organized by host and they're in git\nand I use GitHub as my main get back and remote storage.\n\n### Applications\n\nThere are currently 74 containers running on my server, I will not list them all but here's some highlights:\n\n- traefik\n- portainer\n- nextcloud\n- jellyfin\n- \\*arr stack\n- code-server\n- kanboard\n\nI regularly spin up compose stacks to try new things as I hear about them in\nnewsletters and such - I will be making an attempt to write about them more\nfrequently in a homelabbing series\n",
      "summary": "I want to start writing about my homelab and this first post can be a short introduction to how I got into homelabbing and a review of my current hardware....",
      "date_published": "2025-06-05T08:19:57Z",
      "date_modified": "2025-06-05T08:19:57Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "series-homelab"
      ]
    },
    {
      "id": "https://pype.dev/backups-interrupted-by-full-disk-usage/",
      "url": "https://pype.dev/backups-interrupted-by-full-disk-usage/",
      "title": "Backups interrupted by full disk usage",
      "content_html": "\u003cp\u003eI just got a message from HCIO that my primary backup script is late\u0026hellip; This\nhappens every now and then but I decided to check on it\u0026hellip; Quickly \u003ccode\u003essh\u003c/code\u003e in and\nI notice it takes a long time to connect, but nothing errors out\u0026hellip; I\u0026rsquo;ve been\nin the game long enough to think this kind of latency is often disk IO. After\nwaiting a bit and haulting the zshrc sourcing, I hit a \u003ccode\u003esudo dust / -H\u003c/code\u003e and bam\u0026hellip;\nThere\u0026rsquo;s a container logfile that\u0026rsquo;s about 80GB sitting in \u003ccode\u003e/var\u003c/code\u003e\u0026hellip;\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e  75G   │ │ │ │   ┌── 57409a7f8d3168b368e0230babd08ec2e20aabb288806aa13652db1e653a0eee-json.log\n  75G   │ │ │ │ ┌─┴ 57409a7f8d3168b368e0230babd08ec2e20aabb288806aa13652db1e653a0eee           \n  76G   │ │ │ ├─┴ containers                                                                   \n  83G   │ │ ├─┴ docker                                                                         \n  85G   │ ├─┴ lib                                                                              \n  90G   ├─┴ var                                                                                \n 117G ┌─┴ /\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eEasy enough to remove, but I need to setup some alerts on disk usage, and\nidentify the container to limit the log file size\u003c/p\u003e\n\u003cdiv class=\"admonition info\"\u003e\n\u003cp class=\"admonition-title\"\u003eUseful Notifications\u003c/p\u003e\n\u003cp\u003eI use healthchecks.io to monitor several scripts. You can self-host it but\nI use the hosted service since he has simple Signal integration\u003c/p\u003e\n\u003c/div\u003e\n",
      "content_text": "\n\nI just got a message from HCIO that my primary backup script is late... This\nhappens every now and then but I decided to check on it... Quickly `ssh` in and\nI notice it takes a long time to connect, but nothing errors out... I've been\nin the game long enough to think this kind of latency is often disk IO. After\nwaiting a bit and haulting the zshrc sourcing, I hit a `sudo dust / -H` and bam...\nThere's a container logfile that's about 80GB sitting in `/var`...\n\n```\n  75G   │ │ │ │   ┌── 57409a7f8d3168b368e0230babd08ec2e20aabb288806aa13652db1e653a0eee-json.log\n  75G   │ │ │ │ ┌─┴ 57409a7f8d3168b368e0230babd08ec2e20aabb288806aa13652db1e653a0eee           \n  76G   │ │ │ ├─┴ containers                                                                   \n  83G   │ │ ├─┴ docker                                                                         \n  85G   │ ├─┴ lib                                                                              \n  90G   ├─┴ var                                                                                \n 117G ┌─┴ /\n```\n\nEasy enough to remove, but I need to setup some alerts on disk usage, and\nidentify the container to limit the log file size\n\n!!! info \"Useful Notifications\"\n\n    I use healthchecks.io to monitor several scripts. You can self-host it but\n    I use the hosted service since he has simple Signal integration\n\n\n",
      "summary": "I just got a message from HCIO that my primary backup script is late... This happens every now and then but I decided to check on it... Quickly in and I...",
      "date_published": "2025-06-03T21:08:50Z",
      "date_modified": "2025-06-03T21:08:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/windows-update-broke-wifi/",
      "url": "https://pype.dev/windows-update-broke-wifi/",
      "title": "Windows Update Broke Wifi",
      "content_html": "\u003ch1 id=\"windows-update-behind-my-back\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWindows Update Behind My Back\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eAfter an unapproved windows update on a machine I help administer for my church, the wifi\nbecame super finnicky. I had installed a pretty standard AX5400 WiFi 6 PCIe\nadapter and things were great for a while.\u003c/p\u003e\n\u003ch2 id=\"isp-and-top-tier-laziness\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eISP and Top-Tier Laziness\u003c/span\u003e \u003ca href=\"#isp-and-top-tier-laziness\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe ISP installed the modem and router/AP combo all in one room encased in CMU block in the basement\u0026hellip;\nThat is in the worst possible spot in the entire building and the Windows\ncomputer of note is as far away from it as possible - but we were cooking just fine with that PCIe card.\nSuddenly though, after the update and reboot the machine quit receiving an IPv4\naddress via that interface\u0026hellip; Many applications broke in odd ways, and the\nbrowser connected to some websites but not others.\u003c/p\u003e\n\u003ch2 id=\"state-matters\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eState Matters\u003c/span\u003e \u003ca href=\"#state-matters\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIt took a lot of troubleshooting because of 2 things in the mix\u0026hellip;\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTailscale\u003c/li\u003e\n\u003cli\u003eA second Wi-Fi adapter via USB dongle that was installed while I was out of town (due to the WiFi issues)\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eSo because of these 2 things, and mostly the USB adapter I think, we saw odd\nbehavior that was inconsistent and hard to explain. After having sorted through a bit, I think the intermittent nature of the problems was due to the USB dongle getting an IPv4 address when the connectivity via the default interface was messed up - I can\u0026rsquo;t claim to know if or how Windows would handle this, but it\u0026rsquo;s the only thing that makes sense to me as to why some of the appliations would work and then randomly fail - it\u0026rsquo;s the USB dongle losing connection\u0026hellip; The extra layer is that the bad NIC (the PCIe card) was getting an IPv6 address so the internet connectivity worked EXCEPT FOR THINGS ON MY TAILNET - for example the Nextcloud instance the church backs some data up to\u0026hellip; So the issues were hard to pin-point.\u003c/p\u003e\n\u003ch2 id=\"medium-term-plan\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMedium Term Plan…\u003c/span\u003e \u003ca href=\"#medium-term-plan\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOur medium term plan is to move the AP of the church to somewhere more\nconvenient after we figure out busting through some concrete walls\u0026hellip;\u003c/p\u003e\n",
      "content_text": "\n# Windows Update Behind My Back\n\nAfter an unapproved windows update on a machine I help administer for my church, the wifi\nbecame super finnicky. I had installed a pretty standard AX5400 WiFi 6 PCIe\nadapter and things were great for a while.\n\n## ISP and Top-Tier Laziness\n\nThe ISP installed the modem and router/AP combo all in one room encased in CMU block in the basement...\nThat is in the worst possible spot in the entire building and the Windows\ncomputer of note is as far away from it as possible - but we were cooking just fine with that PCIe card. \nSuddenly though, after the update and reboot the machine quit receiving an IPv4\naddress via that interface... Many applications broke in odd ways, and the\nbrowser connected to some websites but not others. \n\n## State Matters\n\nIt took a lot of troubleshooting because of 2 things in the mix...\n\n* Tailscale\n* A second Wi-Fi adapter via USB dongle that was installed while I was out of town (due to the WiFi issues)\n\nSo because of these 2 things, and mostly the USB adapter I think, we saw odd\nbehavior that was inconsistent and hard to explain. After having sorted through a bit, I think the intermittent nature of the problems was due to the USB dongle getting an IPv4 address when the connectivity via the default interface was messed up - I can't claim to know if or how Windows would handle this, but it's the only thing that makes sense to me as to why some of the appliations would work and then randomly fail - it's the USB dongle losing connection... The extra layer is that the bad NIC (the PCIe card) was getting an IPv6 address so the internet connectivity worked EXCEPT FOR THINGS ON MY TAILNET - for example the Nextcloud instance the church backs some data up to... So the issues were hard to pin-point.\n\n## Medium Term Plan...\n\nOur medium term plan is to move the AP of the church to somewhere more\nconvenient after we figure out busting through some concrete walls...\n\n",
      "summary": "After an unapproved windows update on a machine I help administer for my church, the wifi became super finnicky. I had installed a pretty standard AX5400...",
      "date_published": "2025-06-01T14:30:43Z",
      "date_modified": "2025-06-01T14:30:43Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech",
        "windows"
      ]
    },
    {
      "id": "https://pype.dev/homelab-computer-vision-pipelines/",
      "url": "https://pype.dev/homelab-computer-vision-pipelines/",
      "title": "homelab-computer-vision-pipelines",
      "content_html": "\u003cp\u003eDone in 11 seconds! Subtitle and audio files are in the outputs folder.\u003c/p\u003e\n\u003cp\u003eI wanted to talk through an idea I have for some computer vision pipelines at home.\nI heard of a tool called Roboflow, which is like a no-code computer vision pipeline training\nand inference tool.\nIt seems very cool.\nI also use Frigate at home, and Frigate supports lots of detection on the NVR side.\nAnd so deploying a model in Frigate or using Roboflow, depending on what kind of customization\nyou would need, you can have these detections potentially be as accurate as you want, at\nleast with Roboflow.\nThe thing is, I don\u0026rsquo;t know about Frigate, but my ideas are if I have a model at home\nthat knows me and my wife and my children by name, then when monitored areas issue detections,\nfor example, say I\u0026rsquo;m in the backyard and the temperature is above 80 degrees, have a home\nassistant kick on a sprinkler system or something like that.\nAnd if my wife is back there and it\u0026rsquo;s 80 degrees, don\u0026rsquo;t do that because she wouldn\u0026rsquo;t want that\nsprinkler on or whatever.\nI think that something like that would be very feasible once you have the model.\nSo you would need a way to train the model, which would require gathering data and labeling\nit.\nThen you could train a model in lots of ways, but you need to have the deployment in mind.\nIf the deployment was Frigate, you would need to be able to compile that model into some\nTensorFlow Lite thing that can run on a TPU.\nThat sounds very possible.\nOtherwise, the Roboflow would probably be the platform for training, and then they make\nthe deployment kind of a one-click thing.\nFrom there, I guess either way, Frigate is running the inference, and so the home assistant\nintegration would be about the same.\nFrigate sends notifications over MQTT, and based on the Frigate blueprint that I\u0026rsquo;m using\nfor home assistant anyway, based on the object and whatever other conditions you want to\nset in an automation, you do whatever you want to do in home assistant.\nThat sounds neat.\u003c/p\u003e\n",
      "content_text": "\nDone in 11 seconds! Subtitle and audio files are in the outputs folder.\n\nI wanted to talk through an idea I have for some computer vision pipelines at home.\nI heard of a tool called Roboflow, which is like a no-code computer vision pipeline training\nand inference tool.\nIt seems very cool.\nI also use Frigate at home, and Frigate supports lots of detection on the NVR side.\nAnd so deploying a model in Frigate or using Roboflow, depending on what kind of customization\nyou would need, you can have these detections potentially be as accurate as you want, at\nleast with Roboflow.\nThe thing is, I don't know about Frigate, but my ideas are if I have a model at home\nthat knows me and my wife and my children by name, then when monitored areas issue detections,\nfor example, say I'm in the backyard and the temperature is above 80 degrees, have a home\nassistant kick on a sprinkler system or something like that.\nAnd if my wife is back there and it's 80 degrees, don't do that because she wouldn't want that\nsprinkler on or whatever.\nI think that something like that would be very feasible once you have the model.\nSo you would need a way to train the model, which would require gathering data and labeling\nit.\nThen you could train a model in lots of ways, but you need to have the deployment in mind.\nIf the deployment was Frigate, you would need to be able to compile that model into some\nTensorFlow Lite thing that can run on a TPU.\nThat sounds very possible.\nOtherwise, the Roboflow would probably be the platform for training, and then they make\nthe deployment kind of a one-click thing.\nFrom there, I guess either way, Frigate is running the inference, and so the home assistant\nintegration would be about the same.\nFrigate sends notifications over MQTT, and based on the Frigate blueprint that I'm using\nfor home assistant anyway, based on the object and whatever other conditions you want to\nset in an automation, you do whatever you want to do in home assistant.\nThat sounds neat.\n",
      "summary": "Done in 11 seconds! Subtitle and audio files are in the outputs folder.",
      "date_published": "2025-05-31T08:18:55Z",
      "date_modified": "2025-05-31T08:18:55Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech",
        "homelab",
        "computer-vision"
      ]
    },
    {
      "id": "https://pype.dev/backups-are-dope/",
      "url": "https://pype.dev/backups-are-dope/",
      "title": "Backups are dope",
      "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 `[[rsync-like-a-pro]]` [[rsync-like-a-pro]]\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 kind 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 Docker 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/my-mcp-configuration/",
      "url": "https://pype.dev/my-mcp-configuration/",
      "title": "My MCP Configuration",
      "content_html": "\u003ch1 id=\"my-mcp\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy MCP\u003c/span\u003e\u003c/h1\u003e\n\u003ch2 id=\"the-tools\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Tools\u003c/span\u003e \u003ca href=\"#the-tools\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003ch3 id=\"docker\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDocker\u003c/span\u003e \u003ca href=\"#docker\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003ch3 id=\"ragdocs\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eRAGDocs\u003c/span\u003e \u003ca href=\"#ragdocs\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003ch3 id=\"sequential-thinking\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSequential Thinking\u003c/span\u003e \u003ca href=\"#sequential-thinking\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003ch3 id=\"git\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGit\u003c/span\u003e \u003ca href=\"#git\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003ch3 id=\"not-tried-yet\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eNot Tried Yet\u003c/span\u003e \u003ca href=\"#not-tried-yet\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite\"\u003ehttps://github.com/modelcontextprotocol/servers/tree/main/src/sqlite\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"the-config\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Config\u003c/span\u003e \u003ca href=\"#the-config\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\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=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026#34;mcpServers\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=\"nt\"\u003e\u0026#34;docker-mcp\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;uvx\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=\"nt\"\u003e\u0026#34;args\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;docker-mcp\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=\"nt\"\u003e\u0026#34;ragdocs\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;node\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=\"nt\"\u003e\u0026#34;args\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;/home/nic/projects/personal/quadtask/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js\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=\"nt\"\u003e\u0026#34;env\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=\"nt\"\u003e\u0026#34;QDRANT_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://babyblue-aurora:6333\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=\"nt\"\u003e\u0026#34;EMBEDDING_PROVIDER\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ollama\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=\"nt\"\u003e\u0026#34;OLLAMA_URL\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;http://babyblue-aurora:11434\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=\"nt\"\u003e\u0026#34;alwaysAllow\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;search_documentation\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;list_sources\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;test_ollama\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;add_documentation\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=\"nt\"\u003e\u0026#34;disabled\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\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=\"nt\"\u003e\u0026#34;sequentialthinking\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;docker\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=\"nt\"\u003e\u0026#34;args\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;run\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;--rm\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;-i\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;--name\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;sequentialthinking\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;mcp/sequentialthinking\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=\"nt\"\u003e\u0026#34;disabled\u0026#34;\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=\"nt\"\u003e\u0026#34;alwaysAllow\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;sequentialthinking\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=\"nt\"\u003e\u0026#34;git\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=\"nt\"\u003e\u0026#34;command\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;uvx\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=\"nt\"\u003e\u0026#34;args\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;mcp-server-git\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=\"nt\"\u003e\u0026#34;disabled\u0026#34;\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=\"nt\"\u003e\u0026#34;alwaysAllow\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;git_status\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;git_commit\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;git_add\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;git_log\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;git_diff\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=\"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": "\n# My MCP\n\n## The Tools\n\n### Docker\n\n### RAGDocs\n\n### Sequential Thinking\n\n### Git\n\n### Not Tried Yet\n\nhttps://github.com/modelcontextprotocol/servers/tree/main/src/sqlite\n\n\n## The Config\n\n```json\n\n{\n  \"mcpServers\": {\n    \"docker-mcp\": {\n        \"command\": \"uvx\",\n        \"args\": [\n          \"docker-mcp\"\n        ]\n      },\n    \"ragdocs\": {\n      \"command\": \"node\",\n      \"args\": [\n        \"/home/nic/projects/personal/quadtask/node_modules/@qpd-v/mcp-server-ragdocs/build/index.js\"\n      ],\n      \"env\": {\n        \"QDRANT_URL\": \"http://babyblue-aurora:6333\",\n        \"EMBEDDING_PROVIDER\": \"ollama\",\n        \"OLLAMA_URL\": \"http://babyblue-aurora:11434\"\n      },\n      \"alwaysAllow\": [\n        \"search_documentation\",\n        \"list_sources\",\n        \"test_ollama\",\n        \"add_documentation\"\n      ],\n      \"disabled\": false\n    },\n    \"sequentialthinking\": {\n      \"command\": \"docker\",\n      \"args\": [\n        \"run\",\n        \"--rm\",\n        \"-i\",\n        \"--name\",\n        \"sequentialthinking\",\n        \"mcp/sequentialthinking\"\n      ],\n      \"disabled\": false,\n      \"alwaysAllow\": [\n        \"sequentialthinking\"\n      ]\n    },\n    \"git\": {\n      \"command\": \"uvx\",\n      \"args\": [\n        \"mcp-server-git\"\n      ],\n      \"disabled\": false,\n      \"alwaysAllow\": [\n        \"git_status\",\n        \"git_commit\",\n        \"git_add\",\n        \"git_log\",\n        \"git_diff\"\n      ]\n    }\n  }\n}\n```\n\n",
      "summary": "https://github.com/modelcontextprotocol/servers/tree/main/src/sqlite",
      "date_published": "2025-04-27T05:29:54Z",
      "date_modified": "2025-04-27T05:29:54Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/i-was-wrecked-by-a-weird-combo-of-and-e/",
      "url": "https://pype.dev/i-was-wrecked-by-a-weird-combo-of-and-e/",
      "title": "I was wrecked by a weird combo of » and -e",
      "content_html": "\u003ch2 id=\"tl-dr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTL;DR\u003c/span\u003e \u003ca href=\"#tl-dr\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIf state matters then check it in the beginning or handle it on a failure\u0026hellip; Let me explain\u003c/p\u003e\n\u003cp\u003eI ran into some trouble recently \u003cem\u003ealmost\u003c/em\u003e losing some encrypted data\u0026hellip; Now\nthis would be the second time I\u0026rsquo;ve had that happen, so I\u0026rsquo;m going to write a\nlittle bit about it now that I figured it out\u003c/p\u003e\n\u003ch2 id=\"the-stage\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Stage\u003c/span\u003e \u003ca href=\"#the-stage\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s the scenario - I use \u003ccode\u003eansible-vault\u003c/code\u003e to keep some sensitive data in a\nfew public repos, and I keep the keys I use in Bitwarden Secrets Manager.\u003c/p\u003e\n\u003cp\u003eI use \u003ccode\u003ejust\u003c/code\u003e as a command runner and have common \u003ccode\u003ejust encrypt\u003c/code\u003e and \u003ccode\u003ejust decrypt\u003c/code\u003e recipes throughout many justfiles. It might look something like this\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\"\u003eget-vault-key:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    bws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\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\"\u003eencrypt:\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    \u003cspan class=\"nb\"\u003eset\u003c/span\u003e +e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    just get-vault-key \u0026gt; key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault encrypt ./secret-file --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault encrypt ./secret-file2 --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    rm key\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\"\u003edecrypt:\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    \u003cspan class=\"nb\"\u003eset\u003c/span\u003e +e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    just get-vault-key \u0026gt; key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault decrypt ./secret-file --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault decrypt ./secret-file2 --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    rm key\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"spoiler\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSpoiler\u003c/span\u003e \u003ca href=\"#spoiler\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAbove is a good example of the 2 recipes, but prior to this morning they looked like this\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\"\u003eget-vault-key:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    bws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\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# BAD EXAMPLES! DO NOT DO THIS\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\"\u003eencrypt:\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    just get-vault-key \u0026gt;\u0026gt; key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault encrypt ./secret-file --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault encrypt ./secret-file2 --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    rm key\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\"\u003edecrypt:\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    just get-vault-key \u0026gt;\u0026gt; key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault decrypt ./secret-file --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    ansible-vault decrypt ./secret-file2 --vault-password-file key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    rm key\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"problem\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eProblem\u003c/span\u003e \u003ca href=\"#problem\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSo here\u0026rsquo;s the story - in real life I started using distrobox when I ran into\nthis and I thought it was an issue with different versions of ansible installed\nin distrobox and on my desktop. After a few more odd deployment workflows I\nthink I\u0026rsquo;ve determined that the real problem is that \u003ccode\u003e\u0026gt;\u0026gt;\u003c/code\u003e appends to a file (see\nthe issue yet?), but if my recipes fail (say I try to encrypt an already\nencrypted file) then the command exits but what\u0026rsquo;s left on my disk? \u003ccode\u003ekey\u003c/code\u003e is\nstill there\u0026hellip; and the next time I go to run a command I\u0026rsquo;ll echo the real key\nto the \u003ccode\u003ekey\u003c/code\u003e file again and if I called \u003ccode\u003eencrypt\u003c/code\u003e when the first or more files\nin the list were \u003ccode\u003edecrypted\u003c/code\u003e then they\u0026rsquo;ll be encrypted not with my key, but\nwith my key repeated as many times as I ran a command with \u003ccode\u003ejust get-vault-key \u0026gt;\u0026gt; key\u003c/code\u003e\nbefore ever removing the key file!\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ cat secret-file\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDanger, Will Robinson\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ just encrypt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eEncryption successful\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ cat secret-file\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003e$ANSIBLE_VAULT\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e1.1\u003cspan class=\"p\"\u003e;\u003c/span\u003eAES256\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"m\"\u003e39616332393662346639633030633766666336323939346138633238626239363733316431333737\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e6463663763366434376437303335643431663431326135300a636261663636336139323033316232\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"m\"\u003e37666439383131393631333332353731633661396431633834326432363936613331623135666565\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e6364363039663933620a326563323931643632323031396664646363613636613562366166386439\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"m\"\u003e63653661653037393538356461323239396630643338393231306163343964623866\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ just decrypt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDecryption successful\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ cat secret-file\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDanger, Will Robinson\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo as you can see, I have \u003ccode\u003esecret-file\u003c/code\u003e here, and I can encrypt and decrypt\njust fine. But if I have that \u003ccode\u003e\u0026gt;\u0026gt;\u003c/code\u003e in the recipe, add a second file, and a\nfailure in a command - well then I\u0026rsquo;ll start getting confused\u0026hellip;\u003c/p\u003e\n\u003cp\u003eFollow along with the workflow of adding a file that I\u0026rsquo;ll need encrypted\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eOnly secret-file is in the just recipe here - I add secret-file2 after\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ just decrypt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eERROR! input is not vault encrypted data. /var/home/nic/projects/personal/pype.dev/secret-file is not a vault encrypted file \u003cspan class=\"k\"\u003efor\u003c/span\u003e /var/home/nic/projects/personal/pype.dev/secret-file\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eerror: Recipe \u003cspan class=\"sb\"\u003e`\u003c/span\u003edecrypt\u003cspan class=\"sb\"\u003e`\u003c/span\u003e failed with \u003cspan class=\"nb\"\u003eexit\u003c/span\u003e code \u003cspan class=\"m\"\u003e1\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\"\u003enic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\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;Good - secret-file is already decrypted! So lets add another file\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eGood - secret-file is already decrypted! So lets add another file\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\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;Foo Bar Bam Baz\u0026#34;\u003c/span\u003e \u0026gt; secret-file2                                    \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\"\u003enic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\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;Added secret-file2 to my Just recipes\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eAdded secret-file2 to my Just recipes\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\"\u003enic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ just encrypt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eEncryption successful\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eEncryption successful\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\"\u003enic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ just decrypt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebws secret get \u003cspan class=\"nv\"\u003e$HOMELAB_BOT_VAULT_KEY_ID\u003c/span\u003e  \u003cspan class=\"p\"\u003e|\u003c/span\u003e jq -r \u003cspan class=\"s1\"\u003e\u0026#39;.value\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eERROR! Decryption failed \u003cspan class=\"o\"\u003e(\u003c/span\u003eno vault secrets were found that could decrypt\u003cspan class=\"o\"\u003e)\u003c/span\u003e on /var/home/nic/projects/personal/pype.dev/secret-file \u003cspan class=\"k\"\u003efor\u003c/span\u003e /var/home/nic/projects/personal/pype.dev/secret-file\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eerror: Recipe \u003cspan class=\"sb\"\u003e`\u003c/span\u003edecrypt\u003cspan class=\"sb\"\u003e`\u003c/span\u003e failed with \u003cspan class=\"nb\"\u003eexit\u003c/span\u003e code \u003cspan class=\"m\"\u003e1\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\"\u003enic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10\u003cspan class=\"o\"\u003e(\u003c/span\u003epype-dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e   \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\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;Oh no!\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo what happened?\u003c/p\u003e\n\u003cp\u003eRecall that my recipes originally had \u003ccode\u003eset -e\u003c/code\u003e at the top and used \u003ccode\u003e\u0026gt;\u0026gt;\u003c/code\u003e to cat\nmy secret to a file \u003ccode\u003ekey\u003c/code\u003e. Well, when the fir| decryption recipe failed the\n\u003ccode\u003ekey\u003c/code\u003e file was left behind. Then when I went to encrypt both of my files the\nrecipe got the key again and made a keyfile like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esecretKey\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esecretKey\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThe encryption recipe encrypted the files with this key, removed the key file,\nand then the next decryption recipe failed because when it got the key and made\nthe file it looked like\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003esecretKey\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eSo the solution in the end is to remake the \u003ccode\u003ekey\u003c/code\u003e file every time (\u003ccode\u003e\u0026gt;\u003c/code\u003e instead\nof \u003ccode\u003e\u0026gt;\u0026gt;\u003c/code\u003e), or check if it exists, or handle the error when the recipe fails, or\njust keep going (\u003ccode\u003eset +e\u003c/code\u003e)\u003c/p\u003e\n\u003ch2 id=\"fin\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFin\u003c/span\u003e \u003ca href=\"#fin\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAnd that\u0026rsquo;s how I lost some secrets in my homelab once, and how I almost lost a\nsecond time if I hadn\u0026rsquo;t noticed the \u003ccode\u003ekey\u003c/code\u003e file in my file tree\u003c/p\u003e\n",
      "content_text": "\n## TL;DR\n\nIf state matters then check it in the beginning or handle it on a failure... Let me explain\n\nI ran into some trouble recently _almost_ losing some encrypted data... Now\nthis would be the second time I've had that happen, so I'm going to write a\nlittle bit about it now that I figured it out\n\n## The Stage\n\nHere's the scenario - I use `ansible-vault` to keep some sensitive data in a\nfew public repos, and I keep the keys I use in Bitwarden Secrets Manager.\n\nI use `just` as a command runner and have common `just encrypt` and `just\ndecrypt` recipes throughout many justfiles. It might look something like this\n\n```bash\n\nget-vault-key:\n    bws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\n\nencrypt:\n    #!/bin/bash\n    set +e\n    just get-vault-key \u003e key\n    ansible-vault encrypt ./secret-file --vault-password-file key\n    ansible-vault encrypt ./secret-file2 --vault-password-file key\n    rm key\n\ndecrypt:\n    #!/bin/bash\n    set +e\n    just get-vault-key \u003e key\n    ansible-vault decrypt ./secret-file --vault-password-file key\n    ansible-vault decrypt ./secret-file2 --vault-password-file key\n    rm key\n```\n\n## Spoiler\n\nAbove is a good example of the 2 recipes, but prior to this morning they looked like this\n\n```bash\n\nget-vault-key:\n    bws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\n\n# BAD EXAMPLES! DO NOT DO THIS\n\nencrypt:\n    set -e\n    just get-vault-key \u003e\u003e key\n    ansible-vault encrypt ./secret-file --vault-password-file key\n    ansible-vault encrypt ./secret-file2 --vault-password-file key\n    rm key\n\ndecrypt:\n    set -e\n    just get-vault-key \u003e\u003e key\n    ansible-vault decrypt ./secret-file --vault-password-file key\n    ansible-vault decrypt ./secret-file2 --vault-password-file key\n    rm key\n```\n\n## Problem\n\nSo here's the story - in real life I started using distrobox when I ran into\nthis and I thought it was an issue with different versions of ansible installed\nin distrobox and on my desktop. After a few more odd deployment workflows I\nthink I've determined that the real problem is that `\u003e\u003e` appends to a file (see\nthe issue yet?), but if my recipes fail (say I try to encrypt an already\nencrypted file) then the command exits but what's left on my disk? `key` is\nstill there... and the next time I go to run a command I'll echo the real key\nto the `key` file again and if I called `encrypt` when the first or more files\nin the list were `decrypted` then they'll be encrypted not with my key, but\nwith my key repeated as many times as I ran a command with `just get-vault-key \u003e\u003e key`\nbefore ever removing the key file!\n\n```bash\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ cat secret-file\nDanger, Will Robinson\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ just encrypt\nbws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\nEncryption successful\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ cat secret-file\n$ANSIBLE_VAULT;1.1;AES256\n39616332393662346639633030633766666336323939346138633238626239363733316431333737\n6463663763366434376437303335643431663431326135300a636261663636336139323033316232\n37666439383131393631333332353731633661396431633834326432363936613331623135666565\n6364363039663933620a326563323931643632323031396664646363613636613562366166386439\n63653661653037393538356461323239396630643338393231306163343964623866\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ just decrypt\nbws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\nDecryption successful\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ cat secret-file\nDanger, Will Robinson\n```\n\nSo as you can see, I have `secret-file` here, and I can encrypt and decrypt\njust fine. But if I have that `\u003e\u003e` in the recipe, add a second file, and a\nfailure in a command - well then I'll start getting confused...\n\nFollow along with the workflow of adding a file that I'll need encrypted\n\n\u003e Only secret-file is in the just recipe here - I add secret-file2 after\n\n```bash\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ just decrypt\nbws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\nERROR! input is not vault encrypted data. /var/home/nic/projects/personal/pype.dev/secret-file is not a vault encrypted file for /var/home/nic/projects/personal/pype.dev/secret-file\nerror: Recipe `decrypt` failed with exit code 1\n\nnic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10(pype-dev)   (dev) 󰒄 \n✗ echo \"Good - secret-file is already decrypted! So lets add another file\"\nGood - secret-file is already decrypted! So lets add another file\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ echo \"Foo Bar Bam Baz\" \u003e secret-file2                                    \n\nnic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ echo \"Added secret-file2 to my Just recipes\"\nAdded secret-file2 to my Just recipes\n\nnic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ just encrypt\nbws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\nEncryption successful\nEncryption successful\n\nnic in pype.dev   main   ×2  ×3  ×7 via   v3.11.10(pype-dev)   (dev) 󰒄 \n❯ just decrypt\nbws secret get $HOMELAB_BOT_VAULT_KEY_ID  | jq -r '.value'\nERROR! Decryption failed (no vault secrets were found that could decrypt) on /var/home/nic/projects/personal/pype.dev/secret-file for /var/home/nic/projects/personal/pype.dev/secret-file\nerror: Recipe `decrypt` failed with exit code 1\n\nnic in pype.dev   main   ×2  ×3  ×8 via   v3.11.10(pype-dev)   (dev) 󰒄 \n✗ echo \"Oh no!\"\n```\n\nSo what happened?\n\nRecall that my recipes originally had `set -e` at the top and used `\u003e\u003e` to cat\nmy secret to a file `key`. Well, when the fir| decryption recipe failed the\n`key` file was left behind. Then when I went to encrypt both of my files the\nrecipe got the key again and made a keyfile like this:\n\n```bash\nsecretKey\nsecretKey\n```\n\nThe encryption recipe encrypted the files with this key, removed the key file,\nand then the next decryption recipe failed because when it got the key and made\nthe file it looked like\n\n```\nsecretKey\n```\n\nSo the solution in the end is to remake the `key` file every time (`\u003e` instead\nof `\u003e\u003e`), or check if it exists, or handle the error when the recipe fails, or\njust keep going (`set +e`)\n\n## Fin\n\nAnd that's how I lost some secrets in my homelab once, and how I almost lost a\nsecond time if I hadn't noticed the `key` file in my file tree\n",
      "summary": "If state matters then check it in the beginning or handle it on a failure... Let me explain",
      "date_published": "2025-04-23T08:44:59Z",
      "date_modified": "2025-04-23T08:44:59Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech"
      ]
    },
    {
      "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"
      ]
    },
    {
      "id": "https://pype.dev/smb-with-zfs-on-ubuntu/",
      "url": "https://pype.dev/smb-with-zfs-on-ubuntu/",
      "title": "SMB with ZFS on Ubuntu",
      "content_html": "\u003cp\u003esudo apt-get install -y samba \\ then set sharesmb=on \\ chown the user \\ smbpasswd \u003cuser\u003e -a \\ then mount with user/password from other machine\u003c/p\u003e\n",
      "content_text": "\nsudo apt-get install -y samba \\\\ then set sharesmb=on \\\\ chown the user \\\\ smbpasswd \u003cuser\u003e -a \\\\ then mount with user/password from other machine\n",
      "summary": "sudo apt-get install -y samba \\\\ then set sharesmb=on \\\\ chown the user \\\\ smbpasswd -a \\\\ then mount with user/password from other machine",
      "date_published": "2025-02-20T09:18:35Z",
      "date_modified": "2025-02-20T09:18:35Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/recovering-from-zpool-corruption-when-you-re-short-a-drive/",
      "url": "https://pype.dev/recovering-from-zpool-corruption-when-you-re-short-a-drive/",
      "title": "Recovering from zpool corruption when you’re short a drive",
      "content_html": "\u003col\u003e\n\u003cli\u003ecan only mount tank RO\u003c/li\u003e\n\u003c/ol\u003e\n\u003cul\u003e\n\u003cli\u003eso can\u0026rsquo;t rename\u003c/li\u003e\n\u003cli\u003ealso can\u0026rsquo;t detach which is what I\u0026rsquo;d want\u003c/li\u003e\n\u003c/ul\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003eCould import pool witn -N to not mount any datasets\u003c/li\u003e\n\u003cli\u003edetach and attach\u003c/li\u003e\n\u003cli\u003eresilver\u0026hellip;\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\n1. can only mount tank RO\n  - so can't rename\n  - also can't detach which is what I'd want\n\n2. Could import pool witn -N to not mount any datasets\n3. detach and attach\n4. resilver...\n",
      "summary": "can only mount tank RO - so can't rename - also can't detach which is what I'd want",
      "date_published": "2025-02-03T21:08:56Z",
      "date_modified": "2025-02-03T21:08:56Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "infrastructure",
        "zfs",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/the-importance-of-nostr-according-to-chatgpt/",
      "url": "https://pype.dev/the-importance-of-nostr-according-to-chatgpt/",
      "title": "The Importance of Nostr According to ChatGPT",
      "content_html": "\u003ch2 id=\"disclaimer\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDisclaimer\u003c/span\u003e \u003ca href=\"#disclaimer\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThis was written by ChatGPT with zero oversight from me. There are a few things that are not 100% accurate, but the main idea is there\u0026hellip; I am hoping to actually write about my experience with nostr as I get more involved on the platform.\u003c/p\u003e\n\u003ch3 id=\"the-power-of-the-decentralized-social-media-platform-nostr-a-guide-for-beginners\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Power of the Decentralized Social Media Platform Nostr: A Guide for Beginners\u003c/span\u003e \u003ca href=\"#the-power-of-the-decentralized-social-media-platform-nostr-a-guide-for-beginners\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eSocial media has become a cornerstone of modern communication, allowing individuals to connect across borders and share ideas instantly. However, traditional platforms have raised concerns about censorship, data privacy, and the monopolistic control of user-generated content. \u003cstrong\u003eNostr (Notes and Other Stuff Transmitted by Relays)\u003c/strong\u003e, a decentralized social media protocol, offers a revolutionary alternative by emphasizing freedom, privacy, and user autonomy. This essay explores the transformative power of Nostr, why it matters, and provides a practical guide for beginners to get started.\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"the-power-of-decentralization-why-nostr-matters\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Power of Decentralization: Why Nostr Matters\u003c/span\u003e \u003ca href=\"#the-power-of-decentralization-why-nostr-matters\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eAt its core, Nostr is a decentralized protocol designed to enable social media functionality without reliance on centralized servers. Unlike traditional platforms such as Twitter or Facebook, which depend on centralized infrastructure owned by corporations, Nostr operates on a peer-to-peer network facilitated by relays. This structural shift offers several key advantages:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eFreedom of Expression\u003c/strong\u003e\u003cbr\u003e\nCentralized platforms often act as gatekeepers of content, enforcing moderation policies that can result in censorship. Nostr, on the other hand, allows users to communicate directly without interference from centralized authorities. This makes it particularly valuable for individuals in regions with restrictive speech laws.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eData Ownership\u003c/strong\u003e\u003cbr\u003e\nOn traditional platforms, users sacrifice ownership of their data, which is often monetized by corporations. Nostr empowers users to retain control over their content and personal information by using cryptographic keys to manage identity.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eResilience Against Shutdowns\u003c/strong\u003e\u003cbr\u003e\nCentralized platforms are vulnerable to outages, government intervention, or corporate decisions to deplatform users. Nostr’s decentralized nature ensures that no single entity can shut it down, providing a more resilient communication medium.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eOpen and Transparent Ecosystem\u003c/strong\u003e\u003cbr\u003e\nAs an open protocol, Nostr allows developers to create interoperable applications and services. This fosters innovation, enabling users to choose from a variety of client applications while maintaining access to the same network.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003chr\u003e\n\u003ch3 id=\"how-nostr-works-the-technical-backbone\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eHow Nostr Works: The Technical Backbone\u003c/span\u003e \u003ca href=\"#how-nostr-works-the-technical-backbone\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eNostr uses a simple yet effective system to transmit messages:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ePublic and Private Keys\u003c/strong\u003e: Each user generates a pair of cryptographic keys. The private key acts as their identity, while the public key serves as their address on the network.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eRelays\u003c/strong\u003e: These are lightweight servers that broadcast messages. Users send notes (messages) to relays, which distribute them to other users.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eClients\u003c/strong\u003e: Users access Nostr through client applications, which function as user interfaces. Examples include Damus (for mobile) and Amethyst.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThis decentralized architecture eliminates single points of failure and gives users the flexibility to connect via multiple clients.\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"a-beginner-s-guide-to-getting-started-with-nostr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eA Beginner’s Guide to Getting Started with Nostr\u003c/span\u003e \u003ca href=\"#a-beginner-s-guide-to-getting-started-with-nostr\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eFor those new to Nostr, the decentralized structure might seem intimidating at first. However, with the following step-by-step guide, you’ll be able to join the network and start interacting with others seamlessly.\u003c/p\u003e\n\u003ch4 id=\"step-1-generate-your-keys\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStep 1: Generate Your Keys\u003c/span\u003e \u003ca href=\"#step-1-generate-your-keys\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h4\u003e\n\u003cp\u003eThe first step is to create a pair of cryptographic keys:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003ePrivate Key\u003c/strong\u003e: This is your unique identifier and must be kept secret. Treat it like a password.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePublic Key\u003c/strong\u003e: This is your \u0026ldquo;address\u0026rdquo; on Nostr and can be shared with others.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eMost Nostr client applications automatically generate these keys for you upon sign-up. Ensure you securely back up your private key, as losing it means losing access to your account permanently.\u003c/p\u003e\n\u003ch4 id=\"step-2-choose-a-nostr-client\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStep 2: Choose a Nostr Client\u003c/span\u003e \u003ca href=\"#step-2-choose-a-nostr-client\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h4\u003e\n\u003cp\u003eClients are the gateways through which you interact with the Nostr protocol. Popular options include:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eDamus\u003c/strong\u003e (iOS/Android): User-friendly and feature-rich.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eAmethyst\u003c/strong\u003e (Android): Tailored for Android users with a sleek interface.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eWeb Clients\u003c/strong\u003e: Platforms like \u003cstrong\u003eNostr.guru\u003c/strong\u003e or \u003cstrong\u003eIris\u003c/strong\u003e allow you to access Nostr via a browser.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eInstall your chosen client and log in using your keys.\u003c/p\u003e\n\u003ch4 id=\"step-3-connect-to-relays\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStep 3: Connect to Relays\u003c/span\u003e \u003ca href=\"#step-3-connect-to-relays\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h4\u003e\n\u003cp\u003eRelays are the backbone of the Nostr network, enabling message distribution. Most clients come pre-configured with default relays, but you can add more for better connectivity and reliability. Simply search for \u0026ldquo;Nostr relay services\u0026rdquo; and paste the relay URLs into your client settings.\u003c/p\u003e\n\u003ch4 id=\"step-4-explore-and-interact\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStep 4: Explore and Interact\u003c/span\u003e \u003ca href=\"#step-4-explore-and-interact\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h4\u003e\n\u003cp\u003eOnce set up, you can start exploring the network:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eFollow Users\u003c/strong\u003e: Add public keys of other users to your feed.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003ePost Notes\u003c/strong\u003e: Share your thoughts or updates by creating a note, similar to a tweet.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eEngage\u003c/strong\u003e: Interact with others by replying to or liking their notes.\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch4 id=\"step-5-customize-your-experience\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStep 5: Customize Your Experience\u003c/span\u003e \u003ca href=\"#step-5-customize-your-experience\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h4\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eChoose Relays Carefully\u003c/strong\u003e: Some relays may specialize in certain topics or enforce content moderation policies. Tailor your relay list based on your interests and values.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBackup Your Keys\u003c/strong\u003e: Use secure storage solutions like hardware wallets or encrypted cloud storage to protect your private key.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eExperiment with Clients\u003c/strong\u003e: Try multiple clients to find the one that suits your preferences.\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch3 id=\"the-broader-implications-of-nostr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Broader Implications of Nostr\u003c/span\u003e \u003ca href=\"#the-broader-implications-of-nostr\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe emergence of platforms like Nostr signals a broader shift toward decentralization in the digital era. By prioritizing user autonomy and privacy, Nostr challenges the centralized paradigms that have dominated the social media landscape for years. Its open architecture invites developers, activists, and everyday users to co-create a more inclusive and censorship-resistant digital space.\u003c/p\u003e\n\u003cp\u003eMoreover, Nostr\u0026rsquo;s impact extends beyond social media. Its decentralized protocol can be adapted for various purposes, such as secure communication, decentralized marketplaces, or even collaborative work environments. This versatility makes it a foundational building block for the next generation of internet technologies.\u003c/p\u003e\n\u003chr\u003e\n\u003ch3 id=\"conclusion\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eConclusion\u003c/span\u003e \u003ca href=\"#conclusion\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eNostr represents a profound evolution in social media, offering a decentralized, user-centric alternative to traditional platforms. By empowering individuals with control over their identity, data, and content, it paves the way for a freer and more resilient online ecosystem. While its decentralized nature may pose a learning curve for beginners, the benefits far outweigh the challenges. By embracing Nostr, we can collectively take a step toward a more open and equitable digital future.\u003c/p\u003e\n\u003cp\u003eFor those ready to explore this transformative platform, the journey begins with a simple act: generating your keys and connecting to the network. From there, the power of decentralized communication lies in your hands.\u003c/p\u003e\n",
      "content_text": "\n## Disclaimer\n\nThis was written by ChatGPT with zero oversight from me. There are a few things that are not 100% accurate, but the main idea is there... I am hoping to actually write about my experience with nostr as I get more involved on the platform.\n\n### The Power of the Decentralized Social Media Platform Nostr: A Guide for Beginners\n\nSocial media has become a cornerstone of modern communication, allowing individuals to connect across borders and share ideas instantly. However, traditional platforms have raised concerns about censorship, data privacy, and the monopolistic control of user-generated content. **Nostr (Notes and Other Stuff Transmitted by Relays)**, a decentralized social media protocol, offers a revolutionary alternative by emphasizing freedom, privacy, and user autonomy. This essay explores the transformative power of Nostr, why it matters, and provides a practical guide for beginners to get started.\n\n---\n\n### The Power of Decentralization: Why Nostr Matters\n\nAt its core, Nostr is a decentralized protocol designed to enable social media functionality without reliance on centralized servers. Unlike traditional platforms such as Twitter or Facebook, which depend on centralized infrastructure owned by corporations, Nostr operates on a peer-to-peer network facilitated by relays. This structural shift offers several key advantages:\n\n1. **Freedom of Expression**  \n   Centralized platforms often act as gatekeepers of content, enforcing moderation policies that can result in censorship. Nostr, on the other hand, allows users to communicate directly without interference from centralized authorities. This makes it particularly valuable for individuals in regions with restrictive speech laws.\n\n2. **Data Ownership**  \n   On traditional platforms, users sacrifice ownership of their data, which is often monetized by corporations. Nostr empowers users to retain control over their content and personal information by using cryptographic keys to manage identity.\n\n3. **Resilience Against Shutdowns**  \n   Centralized platforms are vulnerable to outages, government intervention, or corporate decisions to deplatform users. Nostr’s decentralized nature ensures that no single entity can shut it down, providing a more resilient communication medium.\n\n4. **Open and Transparent Ecosystem**  \n   As an open protocol, Nostr allows developers to create interoperable applications and services. This fosters innovation, enabling users to choose from a variety of client applications while maintaining access to the same network.\n\n---\n\n### How Nostr Works: The Technical Backbone\n\nNostr uses a simple yet effective system to transmit messages:\n\n- **Public and Private Keys**: Each user generates a pair of cryptographic keys. The private key acts as their identity, while the public key serves as their address on the network.\n- **Relays**: These are lightweight servers that broadcast messages. Users send notes (messages) to relays, which distribute them to other users.\n- **Clients**: Users access Nostr through client applications, which function as user interfaces. Examples include Damus (for mobile) and Amethyst.\n\nThis decentralized architecture eliminates single points of failure and gives users the flexibility to connect via multiple clients.\n\n---\n\n### A Beginner’s Guide to Getting Started with Nostr\n\nFor those new to Nostr, the decentralized structure might seem intimidating at first. However, with the following step-by-step guide, you’ll be able to join the network and start interacting with others seamlessly.\n\n#### Step 1: Generate Your Keys\nThe first step is to create a pair of cryptographic keys:\n- **Private Key**: This is your unique identifier and must be kept secret. Treat it like a password.\n- **Public Key**: This is your \"address\" on Nostr and can be shared with others.\n\nMost Nostr client applications automatically generate these keys for you upon sign-up. Ensure you securely back up your private key, as losing it means losing access to your account permanently.\n\n#### Step 2: Choose a Nostr Client\nClients are the gateways through which you interact with the Nostr protocol. Popular options include:\n- **Damus** (iOS/Android): User-friendly and feature-rich.\n- **Amethyst** (Android): Tailored for Android users with a sleek interface.\n- **Web Clients**: Platforms like **Nostr.guru** or **Iris** allow you to access Nostr via a browser.\n\nInstall your chosen client and log in using your keys.\n\n#### Step 3: Connect to Relays\nRelays are the backbone of the Nostr network, enabling message distribution. Most clients come pre-configured with default relays, but you can add more for better connectivity and reliability. Simply search for \"Nostr relay services\" and paste the relay URLs into your client settings.\n\n#### Step 4: Explore and Interact\nOnce set up, you can start exploring the network:\n- **Follow Users**: Add public keys of other users to your feed.\n- **Post Notes**: Share your thoughts or updates by creating a note, similar to a tweet.\n- **Engage**: Interact with others by replying to or liking their notes.\n\n#### Step 5: Customize Your Experience\n- **Choose Relays Carefully**: Some relays may specialize in certain topics or enforce content moderation policies. Tailor your relay list based on your interests and values.\n- **Backup Your Keys**: Use secure storage solutions like hardware wallets or encrypted cloud storage to protect your private key.\n- **Experiment with Clients**: Try multiple clients to find the one that suits your preferences.\n\n---\n\n### The Broader Implications of Nostr\n\nThe emergence of platforms like Nostr signals a broader shift toward decentralization in the digital era. By prioritizing user autonomy and privacy, Nostr challenges the centralized paradigms that have dominated the social media landscape for years. Its open architecture invites developers, activists, and everyday users to co-create a more inclusive and censorship-resistant digital space.\n\nMoreover, Nostr's impact extends beyond social media. Its decentralized protocol can be adapted for various purposes, such as secure communication, decentralized marketplaces, or even collaborative work environments. This versatility makes it a foundational building block for the next generation of internet technologies.\n\n---\n\n### Conclusion\n\nNostr represents a profound evolution in social media, offering a decentralized, user-centric alternative to traditional platforms. By empowering individuals with control over their identity, data, and content, it paves the way for a freer and more resilient online ecosystem. While its decentralized nature may pose a learning curve for beginners, the benefits far outweigh the challenges. By embracing Nostr, we can collectively take a step toward a more open and equitable digital future. \n\nFor those ready to explore this transformative platform, the journey begins with a simple act: generating your keys and connecting to the network. From there, the power of decentralized communication lies in your hands.\n",
      "summary": "This was written by ChatGPT with zero oversight from me. There are a few things that are not 100% accurate, but the main idea is there... I am hoping to...",
      "date_published": "2025-01-24T06:38:03Z",
      "date_modified": "2025-01-24T06:38:03Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "nostr",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/deployments-are-not-statefulsets/",
      "url": "https://pype.dev/deployments-are-not-statefulsets/",
      "title": "Deployments are not StatefulSets",
      "content_html": "\u003cp\u003eI was debugging some ArgoCD stuff earlier today and I love using k9s to explore\nmy k8s resources.\u003c/p\u003e\n\u003cp\u003eThe TLDR is that I put some bad env vars in the \u003ccode\u003eglobal\u003c/code\u003e values for my ArgoCD\npods, this caused the webUI to hang. An easy way to push things through is to\nopen k9s, view the Deployments, and edit the env vars there so the pods can be\nin the right state to restart and pickup the changes (you\u0026rsquo;ve obviously fixed\nthe values file already right?)\u003c/p\u003e\n\u003cp\u003eWell the Notification Controller was the pod hanging, and I couldn\u0026rsquo;t find it in\nthe Deployments view! This blew my mind\u0026hellip; until I realized that it was a\nStateful Set.\u003c/p\u003e\n\u003cp\u003eSo, this is not a comparison of the 2 - this is just a note to say that all I\nhad to do was find the StatefulSets in k9s and do the same workflow for editing\nwhat I needed to edit to get the pods into workable state while working on the\nvalues file\u0026hellip;\u003c/p\u003e\n",
      "content_text": "\nI was debugging some ArgoCD stuff earlier today and I love using k9s to explore\nmy k8s resources.\n\nThe TLDR is that I put some bad env vars in the `global` values for my ArgoCD\npods, this caused the webUI to hang. An easy way to push things through is to\nopen k9s, view the Deployments, and edit the env vars there so the pods can be\nin the right state to restart and pickup the changes (you've obviously fixed\nthe values file already right?)\n\nWell the Notification Controller was the pod hanging, and I couldn't find it in\nthe Deployments view! This blew my mind... until I realized that it was a\nStateful Set.\n\nSo, this is not a comparison of the 2 - this is just a note to say that all I\nhad to do was find the StatefulSets in k9s and do the same workflow for editing\nwhat I needed to edit to get the pods into workable state while working on the\nvalues file...\n",
      "summary": "I was debugging some ArgoCD stuff earlier today and I love using k9s to explore my k8s resources.",
      "date_published": "2025-01-15T18:07:57Z",
      "date_modified": "2025-01-15T18:07:57Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "infrastructure",
        "tech",
        "k8s",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/docker-context-and-an-issue-to-question-your-sanity/",
      "url": "https://pype.dev/docker-context-and-an-issue-to-question-your-sanity/",
      "title": "docker context (and an issue to question your sanity)",
      "content_html": "\u003cp\u003edocker contexts are great, would recommend putting them in your prompt though (via starship or something else)\u0026hellip; here\u0026rsquo;s why\u003c/p\u003e\n\u003cp\u003eI like to manage my containers remotely - I have a nice development setup on my desktop and I try to keep my server as bare-bones as possible. For a while I\u0026rsquo;ve been using ansible which makes it easy to manage configuration etc on other machines. But I recently learned about docker contexts and I\u0026rsquo;m planning to scale down my homelab management to just docker-compose stacks rather than a bunch of super complicated ansible playbooks\u003c/p\u003e\n\u003cp\u003eSo, setting up a context is easy - it\u0026rsquo;s basically an ssh connection to another machine!\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003edocker context create koober --docker \u0026quot;host=ssh://nic@koober\u0026quot;\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ekoober\u003c/code\u003e is one of my dev machines and my \u003ccode\u003e~/.ssh/config\u003c/code\u003e is setup such that I can \u003ccode\u003essh nic@koober\u003c/code\u003e, this makes the context work really seamlessly.\u003c/p\u003e\n\u003cp\u003eSo there\u0026rsquo;s the \u003ccode\u003edefault\u003c/code\u003e context (the machine you\u0026rsquo;re on) and now I have \u003ccode\u003ekoober\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eTo use it you run \u003ccode\u003edocker context use koober\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eAnd then to check we can \u003ccode\u003els\u003c/code\u003e the contexts\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003edocker context ls\nNAME       DESCRIPTION                               DOCKER ENDPOINT               ERROR\ndefault    Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   \nkoober *                                             ssh://nic@koober\n\u003c/code\u003e\u003c/pre\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNotice the * - that indicates that\u0026rsquo;s our current context.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"trouble\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTrouble\u003c/span\u003e \u003ca href=\"#trouble\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNow here\u0026rsquo;s where things get hairy\u0026hellip; you\u0026rsquo;ve gotta be super-aware of what context you\u0026rsquo;re using. I have an indicator in my starship prompt that shows the current context, but since I\u0026rsquo;m new to using them I kind of didn\u0026rsquo;t notice it until I ran into this issue\u0026hellip;\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m working on a python application in docker but was not able to execute the entrypoint even though I KNEW the file was there\u0026hellip; let\u0026rsquo;s take a look\u003c/p\u003e\n\u003ch3 id=\"example\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eExample\u003c/span\u003e \u003ca href=\"#example\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eHere\u0026rsquo;s a minimal hello world applycation in docker to illustrate the issue\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# main.py\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=\"s2\"\u003e\u0026#34;hello world\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\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Use an official Python runtime as a parent image\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eFROM\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003epython:3.11-slim\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Set the working directory in the container\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eWORKDIR\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s\"\u003e/app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Copy the current directory contents into the container at /app\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCOPY\u003c/span\u003e . /app\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e# Run the main.py script as the container\u0026#39;s main process\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eCMD\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;python\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;main.py\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\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\"\u003ehello-world\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\"\u003ebuild\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\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\"\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.:/app\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNotice volume mounting in my project directory \u003ccode\u003e.\u003c/code\u003e to \u003ccode\u003e/app\u003c/code\u003e as a common practice to develop inside the container\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s where I started to question my sanity\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e✗ docker compose up                                            \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e+\u003cspan class=\"o\"\u003e]\u003c/span\u003e Running 1/0\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e ✔ Container docker-context-example-hello-world-1  Created                                                                                                                                                                                                                                                              0.0s \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eAttaching to hello-world-1\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehello-world-1  \u003cspan class=\"p\"\u003e|\u003c/span\u003e python: can\u003cspan class=\"s1\"\u003e\u0026#39;t open file \u0026#39;\u003c/span\u003e/app/main.py\u003cspan class=\"err\"\u003e\u0026#39;\u003c/span\u003e: \u003cspan class=\"o\"\u003e[\u003c/span\u003eErrno 2\u003cspan class=\"o\"\u003e]\u003c/span\u003e No such file or directory\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehello-world-1 exited with code \u003cspan class=\"m\"\u003e2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003epython can't open file\u003c/code\u003e? hmm\u0026hellip; Let\u0026rsquo;s take a look at the image\u003c/p\u003e\n\u003cp\u003eFirst let\u0026rsquo;s make sure we get the image name right\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e✗ docker container ls -a              \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCONTAINER ID   IMAGE                                           COMMAND                  CREATED          STATUS                       PORTS                                                                                  NAMES\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e3d7285fc39e2   docker-context-example-hello-world              \u003cspan class=\"s2\"\u003e\u0026#34;python main.py\u0026#34;\u003c/span\u003e         \u003cspan class=\"m\"\u003e10\u003c/span\u003e minutes ago   Exited \u003cspan class=\"o\"\u003e(\u003c/span\u003e2\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"m\"\u003e44\u003c/span\u003e seconds ago                                                                                           docker-context-example-hello-world-1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNow we can \u003ccode\u003edocker run --rm -it --entrypoint /bin/bash --name debug docker-context-example-hello-world\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ docker run --rm -it --entrypoint /bin/bash --name debug docker-context-example-hello-world  \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eroot@ee46d0e22de8:/app# python main.py\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHello, World!\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eroot@ee46d0e22de8:/app# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWHAT THE HECK??\u003c/p\u003e\n\u003ch2 id=\"what-happened\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat happened…\u003c/span\u003e \u003ca href=\"#what-happened\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhat happened turns out to be pretty simple once we realize I\u0026rsquo;m using contexts\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ekoboer\u003c/code\u003e is a remote context, the \u003ccode\u003edocker run\u003c/code\u003e and \u003ccode\u003edocker compose up\u003c/code\u003e commands are interacting with the docker socket on that machine.\u003c/p\u003e\n\u003cp\u003eSo if I compose up the stack notice that there\u0026rsquo;s a volume bind mount in there - well those do \u003cem\u003enot\u003c/em\u003e work with contexts (or at least I\u0026rsquo;m not aware of hose to make it work) and so the \u003ccode\u003e/app\u003c/code\u003e directory was getting blown away essentially with an empty overlay\u0026hellip;\u003c/p\u003e\n\u003cp\u003eBut when running with just \u003ccode\u003edocker run\u003c/code\u003e with no volume mount, the code was copied in during the build and is right where we expect it\u0026hellip;\u003c/p\u003e\n\u003ch3 id=\"let-s-prove-it\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eLet’s prove it\u003c/span\u003e \u003ca href=\"#let-s-prove-it\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ docker run --rm -it --entrypoint /bin/bash --name debug -v .:/app docker-context-example-hello-world\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eroot@903f591c0384:/app# python main.py\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epython: can\u003cspan class=\"s1\"\u003e\u0026#39;t open file \u0026#39;\u003c/span\u003e/app/main.py\u003cspan class=\"err\"\u003e\u0026#39;\u003c/span\u003e: \u003cspan class=\"o\"\u003e[\u003c/span\u003eErrno 2\u003cspan class=\"o\"\u003e]\u003c/span\u003e No such file or directory\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eroot@903f591c0384:/app# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cpre\u003e\u003ccode\u003eAdding a `-v .:/app` to match the compose file, we get the same error...\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eIf we switch to the default context we are back up and running as expected\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e✗ docker context use default\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edefault\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCurrent context is now \u003cspan class=\"s2\"\u003e\u0026#34;default\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\"\u003enic in /tmp/docker-context-example  via   v3.13.0  \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 󱔎 NO PYTHON ENVIORNMENT SET \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ docker compose up         \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e+\u003cspan class=\"o\"\u003e]\u003c/span\u003e Running 1/0\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e ✔ Container docker-context-example-hello-world-1  Created                                                                                                                                                                                                                                                              0.0s \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eAttaching to hello-world-1\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehello-world-1  \u003cspan class=\"p\"\u003e|\u003c/span\u003e Hello, World!\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ehello-world-1 exited with code \u003cspan class=\"m\"\u003e0\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\"\u003enic in /tmp/docker-context-example  via   v3.13.0  \u003cspan class=\"o\"\u003e(\u003c/span\u003edev\u003cspan class=\"o\"\u003e)\u003c/span\u003e 󰒄 󱔎 NO PYTHON ENVIORNMENT SET \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ docker run --rm -it --entrypoint /bin/bash --name debug -v .:/app docker-context-example-hello-world\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eroot@4045b6aa8883:/app# python main.py\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHello, World!\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eroot@4045b6aa8883:/app# \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSuccessful runs on both accounts with the volume mount\u003c/p\u003e\n\u003ch2 id=\"tldr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTLDR\u003c/span\u003e \u003ca href=\"#tldr\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eContext is king\u003c/p\u003e\n",
      "content_text": "\ndocker contexts are great, would recommend putting them in your prompt though (via starship or something else)... here's why\n\nI like to manage my containers remotely - I have a nice development setup on my desktop and I try to keep my server as bare-bones as possible. For a while I've been using ansible which makes it easy to manage configuration etc on other machines. But I recently learned about docker contexts and I'm planning to scale down my homelab management to just docker-compose stacks rather than a bunch of super complicated ansible playbooks\n\nSo, setting up a context is easy - it's basically an ssh connection to another machine!\n\n`docker context create koober --docker \"host=ssh://nic@koober\"`\n\n`koober` is one of my dev machines and my `~/.ssh/config` is setup such that I can `ssh nic@koober`, this makes the context work really seamlessly.\n\nSo there's the `default` context (the machine you're on) and now I have `koober`\n\nTo use it you run `docker context use koober`\n\nAnd then to check we can `ls` the contexts\n\n```\ndocker context ls\nNAME       DESCRIPTION                               DOCKER ENDPOINT               ERROR\ndefault    Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   \nkoober *                                             ssh://nic@koober\n```\n\n\u003e Notice the * - that indicates that's our current context.\n\n## Trouble\n\nNow here's where things get hairy... you've gotta be super-aware of what context you're using. I have an indicator in my starship prompt that shows the current context, but since I'm new to using them I kind of didn't notice it until I ran into this issue...\n\nI'm working on a python application in docker but was not able to execute the entrypoint even though I KNEW the file was there... let's take a look\n\n### Example\n\nHere's a minimal hello world applycation in docker to illustrate the issue\n\n```python\n# main.py\nprint(\"hello world\")\n```\n\n```Dockerfile\n# Use an official Python runtime as a parent image\nFROM python:3.11-slim\n\n# Set the working directory in the container\nWORKDIR /app\n\n# Copy the current directory contents into the container at /app\nCOPY . /app\n\n# Run the main.py script as the container's main process\nCMD [\"python\", \"main.py\"]\n```\n\n```yaml\nservices:\n  hello-world:\n    build: .\n    volumes:\n      - .:/app\n```\n\nNotice volume mounting in my project directory `.` to `/app` as a common practice to develop inside the container\n\nHere's where I started to question my sanity...\n\n```bash\n✗ docker compose up                                            \n[+] Running 1/0\n ✔ Container docker-context-example-hello-world-1  Created                                                                                                                                                                                                                                                              0.0s \nAttaching to hello-world-1\nhello-world-1  | python: can't open file '/app/main.py': [Errno 2] No such file or directory\nhello-world-1 exited with code 2\n```\n\n`python can't open file`? hmm... Let's take a look at the image\n\nFirst let's make sure we get the image name right\n\n```bash\n✗ docker container ls -a              \nCONTAINER ID   IMAGE                                           COMMAND                  CREATED          STATUS                       PORTS                                                                                  NAMES\n3d7285fc39e2   docker-context-example-hello-world              \"python main.py\"         10 minutes ago   Exited (2) 44 seconds ago                                                                                           docker-context-example-hello-world-1\n```\n\nNow we can `docker run --rm -it --entrypoint /bin/bash --name debug docker-context-example-hello-world`\n\n```bash\n❯ docker run --rm -it --entrypoint /bin/bash --name debug docker-context-example-hello-world  \nroot@ee46d0e22de8:/app# python main.py\nHello, World!\nroot@ee46d0e22de8:/app# \n```\n\nWHAT THE HECK??\n\n## What happened...\n\nWhat happened turns out to be pretty simple once we realize I'm using contexts...\n\n`koboer` is a remote context, the `docker run` and `docker compose up` commands are interacting with the docker socket on that machine.\n\nSo if I compose up the stack notice that there's a volume bind mount in there - well those do _not_ work with contexts (or at least I'm not aware of hose to make it work) and so the `/app` directory was getting blown away essentially with an empty overlay...\n\nBut when running with just `docker run` with no volume mount, the code was copied in during the build and is right where we expect it...\n\n### Let's prove it\n\n```bash\n❯ docker run --rm -it --entrypoint /bin/bash --name debug -v .:/app docker-context-example-hello-world\nroot@903f591c0384:/app# python main.py\npython: can't open file '/app/main.py': [Errno 2] No such file or directory\nroot@903f591c0384:/app# \n```\n```\nAdding a `-v .:/app` to match the compose file, we get the same error...\n```\n\nIf we switch to the default context we are back up and running as expected\n\n```bash\n✗ docker context use default\ndefault\nCurrent context is now \"default\"\n\nnic in /tmp/docker-context-example  via   v3.13.0  (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET \n❯ docker compose up         \n[+] Running 1/0\n ✔ Container docker-context-example-hello-world-1  Created                                                                                                                                                                                                                                                              0.0s \nAttaching to hello-world-1\nhello-world-1  | Hello, World!\nhello-world-1 exited with code 0\n\nnic in /tmp/docker-context-example  via   v3.13.0  (dev) 󰒄 󱔎 NO PYTHON ENVIORNMENT SET \n❯ docker run --rm -it --entrypoint /bin/bash --name debug -v .:/app docker-context-example-hello-world\nroot@4045b6aa8883:/app# python main.py\nHello, World!\nroot@4045b6aa8883:/app# \n```\n\nSuccessful runs on both accounts with the volume mount\n\n## TLDR\n\nContext is king\n",
      "summary": "docker contexts are great, would recommend putting them in your prompt though (via starship or something else)... here's why",
      "date_published": "2024-12-19T05:59:30Z",
      "date_modified": "2024-12-19T05:59:30Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "terminal",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/add-a-healthcheck-to-your-fastapi-app/",
      "url": "https://pype.dev/add-a-healthcheck-to-your-fastapi-app/",
      "title": "Add a healthcheck to your FastAPI app",
      "content_html": "\u003cp\u003eI\u0026rsquo;m building a few FastAPI apps to throw in docker and run on my homelab\u0026hellip; I wanted to add healthchecks and here\u0026rsquo;s a simple way to do it\u003c/p\u003e\n\u003cp\u003eMake sure to install \u003ccode\u003ecurl\u003c/code\u003e in the dockerfile (near the top for effeciency)\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# Install curl with minimal dependencies\nRUN apt-get update \u0026amp;\u0026amp; \\\n    apt-get install -y --no-install-recommends curl \u0026amp;\u0026amp; \\\n    apt-get clean \u0026amp;\u0026amp; \\\n    rm -rf /var/lib/apt/lists/*\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eThen I recommend making compose files even for single image deployments\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eservices:\n  app:\n    build: .\n    volumes:\n      - type: bind\n        source: .\n        target: /app\n    environment:\n      - PYTHONPATH=/app\n      - DOCKER_ENV=true\n      - UV_VIRTUALENV=/opt/app-env\n    user: \u0026quot;1000:1000\u0026quot;\n    restart: unless-stopped\n    healthcheck:\n      test: [\u0026quot;CMD\u0026quot;, \u0026quot;curl\u0026quot;, \u0026quot;-f\u0026quot;, \u0026quot;http://localhost:8000/health\u0026quot;]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n      start_period: 10s\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eThen finally you\u0026rsquo;ll need a \u003ccode\u003e/health\u003c/code\u003e endpoint\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=\"nd\"\u003e@app.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/health\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_class\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eHTMLResponse\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ehealth_check\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;\u0026#34;\u0026#34;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    A health check endpoint that returns a status message.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"s2\"\u003e    \u0026#34;\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\"\u003ereturn\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u0026lt;html\u0026gt;\u0026lt;body\u0026gt;\u0026lt;h1\u0026gt;Service is healthy\u0026lt;/h1\u0026gt;\u0026lt;/body\u0026gt;\u0026lt;/html\u0026gt;\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\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI'm building a few FastAPI apps to throw in docker and run on my homelab... I wanted to add healthchecks and here's a simple way to do it\n\nMake sure to install `curl` in the dockerfile (near the top for effeciency)\n\n```\n# Install curl with minimal dependencies\nRUN apt-get update \u0026\u0026 \\\n    apt-get install -y --no-install-recommends curl \u0026\u0026 \\\n    apt-get clean \u0026\u0026 \\\n    rm -rf /var/lib/apt/lists/*\n\n```\n\nThen I recommend making compose files even for single image deployments\n\n```\nservices:\n  app:\n    build: .\n    volumes:\n      - type: bind\n        source: .\n        target: /app\n    environment:\n      - PYTHONPATH=/app\n      - DOCKER_ENV=true\n      - UV_VIRTUALENV=/opt/app-env\n    user: \"1000:1000\"\n    restart: unless-stopped\n    healthcheck:\n      test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:8000/health\"]\n      interval: 30s\n      timeout: 10s\n      retries: 3\n      start_period: 10s\n\n```\n\nThen finally you'll need a `/health` endpoint\n\n```python\n\n@app.get(\"/health\", response_class=HTMLResponse)\nasync def health_check():\n    \"\"\"\n    A health check endpoint that returns a status message.\n    \"\"\"\n    return \"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eService is healthy\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\"\n\n```\n",
      "summary": "I'm building a few FastAPI apps to throw in docker and run on my homelab... I wanted to add healthchecks and here's a simple way to do it",
      "date_published": "2024-12-15T15:41:36Z",
      "date_modified": "2024-12-15T15:41:36Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/statically-io-to-help-me-out/",
      "url": "https://pype.dev/statically-io-to-help-me-out/",
      "title": "statically.io to help me out",
      "content_html": "\u003cp\u003e\u003ca href=\"https://statically.io\"\u003estatically.io\u003c/a\u003e offers a CDN that I\u0026rsquo;m going to try to lean into for images in my blog. My hope is that the builds get and stay fast, and that page loads are fast too.\u003c/p\u003e\n\u003cp\u003eExample:\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=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003eimg\u003c/span\u003e \u003cspan class=\"na\"\u003esrc\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-zsh.png\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003ealt\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Skimpy ZSH\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003etitle\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;A fancy data summary in the shell\u0026#34;\u003c/span\u003e \u003cspan class=\"p\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-zsh.png\" alt=\"Skimpy ZSH\" title=\"A fancy data summary in the shell\" /\u003e\n",
      "content_text": "\n[statically.io](https://statically.io) offers a CDN that I'm going to try to lean into for images in my blog. My hope is that the builds get and stay fast, and that page loads are fast too.\n\nExample:\n\n```html\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-zsh.png\" alt=\"Skimpy ZSH\" title=\"A fancy data summary in the shell\" /\u003e\n```\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-zsh.png\" alt=\"Skimpy ZSH\" title=\"A fancy data summary in the shell\" /\u003e\n",
      "summary": "statically.io offers a CDN that I'm going to try to lean into for images in my blog. My hope is that the builds get and stay fast, and that page loads are...",
      "date_published": "2024-12-14T13:35:31Z",
      "date_modified": "2024-12-14T13:35:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "infrastructure",
        "tech",
        "webdev",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/d-and-uninterruptable-sleep/",
      "url": "https://pype.dev/d-and-uninterruptable-sleep/",
      "title": "D and uninterruptable sleep",
      "content_html": "\u003ch2 id=\"htop\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eHtop\u003c/span\u003e \u003ca href=\"#htop\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI recently have been having significant home server issues, and that\u0026rsquo;s not the point of this - today I learned what \u003ccode\u003eD\u003c/code\u003e state is when looking at htop.\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/d-htop.png\" alt=\"htop-d\" title=\"htop with D state\" /\u003e\n\u003cp\u003eApparently this means \u0026ldquo;uninterruptable sleep\u0026rdquo; and it\u0026rsquo;s a dev\u0026rsquo;s nightmare\u0026hellip;\u003c/p\u003e\n\u003ch3 id=\"context\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eContext\u003c/span\u003e \u003ca href=\"#context\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eThe issue I was having was that some \u003ccode\u003ezfs rollback\u003c/code\u003e commands were hung - for hours\u0026hellip; I wasn\u0026rsquo;t sure what was going on, rollbacks should be instant but I figured it was just an artifact of these issues.\u003c/p\u003e\n\u003cp\u003eTurns out I still don\u0026rsquo;t know what locked the disks up but I learned why \u003ccode\u003e\u0026lt;C\u0026gt;-c\u003c/code\u003e did \u003cstrong\u003enothing\u003c/strong\u003e\u0026hellip;\nthe more you know\u003c/p\u003e\n",
      "content_text": "\n## Htop\n\nI recently have been having significant home server issues, and that's not the point of this - today I learned what `D` state is when looking at htop.\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/d-htop.png\" alt=\"htop-d\" title=\"htop with D state\" /\u003e\n\nApparently this means \"uninterruptable sleep\" and it's a dev's nightmare... \n\n### Context\n\nThe issue I was having was that some `zfs rollback` commands were hung - for hours... I wasn't sure what was going on, rollbacks should be instant but I figured it was just an artifact of these issues.\n\nTurns out I still don't know what locked the disks up but I learned why `\u003cC\u003e-c` did __nothing__...\nthe more you know\n",
      "summary": "I recently have been having significant home server issues, and that's not the point of this - today I learned what state is when looking at htop.",
      "date_published": "2024-12-11T10:48:10Z",
      "date_modified": "2024-12-11T10:48:10Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "zfs",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/stylus-for-custom-webpage-themes/",
      "url": "https://pype.dev/stylus-for-custom-webpage-themes/",
      "title": "Stylus for custom webpage themes",
      "content_html": "\u003cp\u003ethe Logos web app is DISGUSTINGY bright/white - enough to actually ruin your morning\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThankfully there\u0026rsquo;s an extension called \u003ca href=\"https://addons.mozilla.org/en-US/firefox/addon/styl-us/\"\u003estylus\u003c/a\u003e and some kind folks in the Logos community created a nice dark theme \u003ca href=\"https://userstyles.world/style/18547/logos-web-app-dark-mode\"\u003ehere\u003c/a\u003e. You simply install the extension, then go to the userstyles link \u0026lt;\u0026ndash; and click \u003ccode\u003einstall\u003c/code\u003e. It only changes themes for the sites configured - in this case \u003ca href=\"app.logos.com\"\u003eapp.logos.com\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"todo-image\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTODO: image\u003c/span\u003e\u003c/h1\u003e\n",
      "content_text": "\nthe Logos web app is DISGUSTINGY bright/white - enough to actually ruin your morning...\n\nThankfully there's an extension called [stylus](https://addons.mozilla.org/en-US/firefox/addon/styl-us/) and some kind folks in the Logos community created a nice dark theme [here](https://userstyles.world/style/18547/logos-web-app-dark-mode). You simply install the extension, then go to the userstyles link \u003c-- and click `install`. It only changes themes for the sites configured - in this case [app.logos.com](app.logos.com)\n\n# TODO: image\n",
      "summary": "the Logos web app is DISGUSTINGY bright/white - enough to actually ruin your morning...",
      "date_published": "2024-11-27T06:07:39Z",
      "date_modified": "2024-11-27T06:07:39Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech",
        "logos",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/dns-broke-after-reboot-ubuntu-22-04/",
      "url": "https://pype.dev/dns-broke-after-reboot-ubuntu-22-04/",
      "title": "DNS Broke After Reboot - Ubuntu 22.04",
      "content_html": "\u003cp\u003eI rebooted by server and DNS broke randomly. I have no idea if it was from a kernel update or what but that\u0026rsquo;s the issue with Ubuntu I guess\u0026hellip;\u003c/p\u003e\n\u003cp\u003eAfter much toil and none of the other options working for me (sorry to not have those documented here) this is what got me the vic from this \u003ca href=\"https://askubuntu.com/questions/1406827/how-to-set-dns-on-ubuntu-22-04-when-you-have-no-netplan-config\"\u003eSO Post\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003esudo mkdir /etc/systemd/resolved.conf.d/\nsudo $EDITOR /etc/systemd/resolved.conf.d/dns_servers.conf\u003c/p\u003e\n\u003cp\u003eMost folks probably are good with google (8.8.8.8) and cloudflare (1.1.1.1)\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e[Resolve]\nDNS=8.8.8.8 1.1.1.1\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eBut I decided to use tailscale\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e[Resolve]\nDNS=100.100.100.100\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eThen restart systemd-resolved\u003c/p\u003e\n\u003cp\u003esudo systemctl restart systemd-resolved\u003c/p\u003e\n",
      "content_text": "\nI rebooted by server and DNS broke randomly. I have no idea if it was from a kernel update or what but that's the issue with Ubuntu I guess...\n\nAfter much toil and none of the other options working for me (sorry to not have those documented here) this is what got me the vic from this [SO Post](https://askubuntu.com/questions/1406827/how-to-set-dns-on-ubuntu-22-04-when-you-have-no-netplan-config)\n\nsudo mkdir /etc/systemd/resolved.conf.d/\nsudo $EDITOR /etc/systemd/resolved.conf.d/dns_servers.conf\n\nMost folks probably are good with google (8.8.8.8) and cloudflare (1.1.1.1)\n\n```\n[Resolve]\nDNS=8.8.8.8 1.1.1.1\n```\n\nBut I decided to use tailscale\n\n```\n[Resolve]\nDNS=100.100.100.100\n```\n\nThen restart systemd-resolved\n\nsudo systemctl restart systemd-resolved\n",
      "summary": "I rebooted by server and DNS broke randomly. I have no idea if it was from a kernel update or what but that's the issue with Ubuntu I guess...",
      "date_published": "2024-11-22T08:08:40Z",
      "date_modified": "2024-11-22T08:08:40Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/opnsense-bootstrap-recovery/",
      "url": "https://pype.dev/opnsense-bootstrap-recovery/",
      "title": "OPNSense Bootstrap Recovery",
      "content_html": "\u003cp\u003eenabling DHCP WAN port (\u003ccode\u003edhclient \u0026lt;iface\u0026gt;\u003c/code\u003e)- running the bootstrap script - \u003ccode\u003esh /usr/local/sbin/opnsense-bootstrap\u003c/code\u003e\u003c/p\u003e\n",
      "content_text": "\n\nenabling DHCP WAN port (`dhclient \u003ciface\u003e`)- running the bootstrap script - `sh /usr/local/sbin/opnsense-bootstrap`\n\n",
      "summary": "enabling DHCP WAN port ()- running the bootstrap script -",
      "date_published": "2024-11-07T08:40:19Z",
      "date_modified": "2024-11-07T08:40:19Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "infrastructure",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/docker-remote-add/",
      "url": "https://pype.dev/docker-remote-add/",
      "title": "docker-remote-add",
      "content_html": "\u003cp\u003eAdd from url??\u003c/p\u003e\n\u003cp\u003eADD \u003ca href=\"http://example.com/cars.csv\"\u003ehttp://example.com/cars.csv\u003c/a\u003e /tmp/cars.csv\u003c/p\u003e\n\u003cp\u003eUnpack automatically!? (.tar, .tar.gz, .tgz, .bz2, .tbz2, .txz, .zip)\u003c/p\u003e\n\u003cp\u003eADD myapp.tar.gz /opt/myapp/\u003c/p\u003e\n",
      "content_text": "\nAdd from url??\n\nADD http://example.com/cars.csv /tmp/cars.csv\n\nUnpack automatically!? (.tar, .tar.gz, .tgz, .bz2, .tbz2, .txz, .zip)\n\nADD myapp.tar.gz /opt/myapp/\n",
      "summary": "Add from url??",
      "date_published": "2024-09-17T06:19:00Z",
      "date_modified": "2024-09-17T06:19:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/docker-copy-and-chown/",
      "url": "https://pype.dev/docker-copy-and-chown/",
      "title": "Docker copy and chown",
      "content_html": "\u003cp\u003eCOPY \u0026ndash;chown=myuser:mygroup source-file target-file\u003c/p\u003e\n",
      "content_text": "\nCOPY --chown=myuser:mygroup source-file target-file\n",
      "summary": "COPY --chown=myuser:mygroup source-file target-file",
      "date_published": "2024-09-17T06:18:09Z",
      "date_modified": "2024-09-17T06:18:09Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/switching-from-altacv-to-rendercv-for-my-resume/",
      "url": "https://pype.dev/switching-from-altacv-to-rendercv-for-my-resume/",
      "title": "Switching from AltaCV to RenderCV for my Resume",
      "content_html": "\u003cp\u003eI was using a fun LaTex-based project for managing my resume called \u003ca href=\"https://github.com/liantze/AltaCV\"\u003eAltaCV\u003c/a\u003e. I loved the customization and was familiar with Tek from school. However, I update my resume so infrequently that anytime I\u0026rsquo;d hop back to it I\u0026rsquo;d have to remember how to work with Tex and that was frustrating as I\u0026rsquo;ve lost touch with it over the years.\u003c/p\u003e\n\u003cp\u003eScrolling GitHub treding repos I saw \u003ca href=\"https://github.com/sinaatalay/rendercv\"\u003eRenderCV\u003c/a\u003e which let\u0026rsquo;s me just use YAML to write my resume and then it compiles to Tek through Python. There\u0026rsquo;s a sister project to make your own using this very easly call \u003ca href=\"https://github.com/sinaatalay/rendercv-pipeline\"\u003erendercv-pipeline\u003c/a\u003e. I forked that repo and translated my tek resume to the YAML. The included theme is nice enough is YAML is much easier to maintain long-term.\u003c/p\u003e\n\u003cp\u003eMy resume is behind a private GH repo but the example from rendercv-pipeline is \u003ca href=\"https://github.com/sinaatalay/rendercv-pipeline/blob/main/John_Doe_CV.pdf\"\u003ehere on GitHub\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI was using a fun LaTex-based project for managing my resume called [AltaCV](https://github.com/liantze/AltaCV). I loved the customization and was familiar with Tek from school. However, I update my resume so infrequently that anytime I'd hop back to it I'd have to remember how to work with Tex and that was frustrating as I've lost touch with it over the years.\n\nScrolling GitHub treding repos I saw [RenderCV](https://github.com/sinaatalay/rendercv) which let's me just use YAML to write my resume and then it compiles to Tek through Python. There's a sister project to make your own using this very easly call [rendercv-pipeline](https://github.com/sinaatalay/rendercv-pipeline). I forked that repo and translated my tek resume to the YAML. The included theme is nice enough is YAML is much easier to maintain long-term.\n\n\nMy resume is behind a private GH repo but the example from rendercv-pipeline is [here on GitHub](https://github.com/sinaatalay/rendercv-pipeline/blob/main/John_Doe_CV.pdf)\n",
      "summary": "I was using a fun LaTex-based project for managing my resume called AltaCV. I loved the customization and was familiar with Tek from school. However, I...",
      "date_published": "2024-08-01T05:59:46Z",
      "date_modified": "2024-08-01T05:59:46Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/interesting-ips-between-jellyfin-clients-and-server-depending-on-tailscale-and-server-address/",
      "url": "https://pype.dev/interesting-ips-between-jellyfin-clients-and-server-depending-on-tailscale-and-server-address/",
      "title": "Interesting IPs between Jellyfin clients and server depending on tailscale and server address",
      "content_html": "\u003cp\u003eWhen connecting from my phone to jellyfin I\u0026rsquo;m seeing some interesting patterns.\u003c/p\u003e\n\u003ch2 id=\"scenarios\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eScenarios\u003c/span\u003e \u003ca href=\"#scenarios\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003ch3 id=\"tailscale-ip-of-phone-is-listed-as-local-network-to-jellyfin\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTailscale IP of phone is listed as local network to jellyfin\u003c/span\u003e \u003ca href=\"#tailscale-ip-of-phone-is-listed-as-local-network-to-jellyfin\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eWifi: off\nTailscale: on\nUse exit node: on\nLAN access: on\nJellyfin: LAN IP\u003c/p\u003e\n\u003cp\u003eJellyfin sees 192.168.1.1, my router address\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWifi: off\ntailscale: on\nUse exit node: on\nLAN access: on\nJellyfin: Tailscale magic DNS\u003c/p\u003e\n\u003cp\u003eJellyfin sees the docker bridge network\u003c/p\u003e\n\u003cp\u003eQ: This might be because of traefik somehow\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWifi: off\ntailsacale: on\nUse exit node: on\nLAN access: off\nJellyfin: LAN IP\u003c/p\u003e\n\u003cp\u003eJellyfin sees the 192.168.1.1\u003c/p\u003e\n\u003cp\u003eQ: Why did this work even work?\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWifi: off\ntailsacale: on\nUse exit node: on\nLAN access: off\nJellyfin: Tailscale magic DNS\u003c/p\u003e\n\u003cp\u003eJellyfin sees the docker bridge network\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWifi: off\ntailsacale: on\nUse exit node: off\nLAN access: off\nJellyfin: LAN IP\u003c/p\u003e\n\u003cp\u003eJellyfin sees the 192.168.1.1\u003c/p\u003e\n\u003cp\u003eQ: Why did this work?\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWifi: off\ntailsacale: on\nUse exit node: off\nLAN access: off\nJellyfin: Tailscale magic DNS\u003c/p\u003e\n\u003cp\u003eJellyfin sees the docker bridge network\u003c/p\u003e\n\u003chr\u003e\n\u003cp\u003eWifi: on\ntailsacale: of\nUse exit node: off\nLAN access: off\nJellyfin: LAN IP (via pihole DNS)\u003c/p\u003e\n\u003cp\u003eJellyfin sees the IP of my phone\u003c/p\u003e\n\u003chr\u003e\n",
      "content_text": "\nWhen connecting from my phone to jellyfin I'm seeing some interesting patterns.\n\n## Scenarios\n\n### Tailscale IP of phone is listed as local network to jellyfin\n\nWifi: off\nTailscale: on\nUse exit node: on\nLAN access: on\nJellyfin: LAN IP\n\nJellyfin sees 192.168.1.1, my router address\n\n***\n\nWifi: off\ntailscale: on\nUse exit node: on \nLAN access: on\nJellyfin: Tailscale magic DNS\n\nJellyfin sees the docker bridge network\n\nQ: This might be because of traefik somehow\n\n***\n\nWifi: off\ntailsacale: on\nUse exit node: on\nLAN access: off\nJellyfin: LAN IP\n\nJellyfin sees the 192.168.1.1\n\nQ: Why did this work even work?\n\n***\n\nWifi: off\ntailsacale: on\nUse exit node: on\nLAN access: off\nJellyfin: Tailscale magic DNS\n\nJellyfin sees the docker bridge network\n\n***\n\nWifi: off\ntailsacale: on\nUse exit node: off\nLAN access: off\nJellyfin: LAN IP\n\nJellyfin sees the 192.168.1.1\n\nQ: Why did this work?\n\n***\n\nWifi: off\ntailsacale: on\nUse exit node: off\nLAN access: off\nJellyfin: Tailscale magic DNS\n\nJellyfin sees the docker bridge network\n\n***\n\nWifi: on\ntailsacale: of\nUse exit node: off\nLAN access: off\nJellyfin: LAN IP (via pihole DNS)\n\nJellyfin sees the IP of my phone\n\n***\n",
      "summary": "When connecting from my phone to jellyfin I'm seeing some interesting patterns.",
      "date_published": "2024-07-12T06:24:57Z",
      "date_modified": "2024-07-12T06:24:57Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/upgrading-your-kernel-can-f-you-up-whoops/",
      "url": "https://pype.dev/upgrading-your-kernel-can-f-you-up-whoops/",
      "title": "Upgrading your kernel can F you up… whoops",
      "content_html": "\u003cp\u003e\u003ca href=\"https://forum.proxmox.com/threads/update-error-with-coral-tpu-drivers.136888/#post-608975\"\u003ehttps://forum.proxmox.com/threads/update-error-with-coral-tpu-drivers.136888/#post-608975\u003c/a\u003e I lost TPU for frigate, this saved me ass\u003c/p\u003e\n",
      "content_text": "\nhttps://forum.proxmox.com/threads/update-error-with-coral-tpu-drivers.136888/#post-608975 I lost TPU for frigate, this saved me ass\n",
      "summary": "https://forum.proxmox.com/threads/update-error-with-coral-tpu-drivers.136888/#post-608975 I lost TPU for frigate, this saved me ass",
      "date_published": "2024-06-26T10:21:59Z",
      "date_modified": "2024-06-26T10:21:59Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/customize-k9s/",
      "url": "https://pype.dev/customize-k9s/",
      "title": "Customize K9s",
      "content_html": "\u003cp\u003eTo customize k9s use the skins from catppuccin or the ones k9s supplies\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eOUT\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\"\u003eXDG_CONFIG_HOME\u003c/span\u003e\u003cspan class=\"k\"\u003e:-\u003c/span\u003e\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\u003cspan class=\"p\"\u003e/.config\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e/k9s/skins\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emkdir -p \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$OUT\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\"\u003ecurl -L https://github.com/catppuccin/k9s/archive/main.tar.gz \u003cspan class=\"p\"\u003e|\u003c/span\u003e tar xz -C \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"nv\"\u003e$OUT\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e --strip-components\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e2\u003c/span\u003e k9s-main/dist\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThen edit your k9s config\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e# ~/.config/k9s/config.yml\nk9s:\n  ui:\n    skin: catppuccin-mocha\n    # ...or another flavor:\n    # skin: catppuccin-macchiato\n    # skin: catppuccin-frappe\n    # skin: catppuccin-latte\n\n    # ...or the transparent variants:\n    # skin: catppuccin-mocha-transparent\n    # skin: catppuccin-macchiato-transparent\n    # skin: catppuccin-frappe-transparent\n    # skin: catppuccin-latte-transparent\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eOther k9s skins are available \u003ca href=\"https://github.com/derailed/k9s/tree/master/skins\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nTo customize k9s use the skins from catppuccin or the ones k9s supplies\n\n```bash\nOUT=\"${XDG_CONFIG_HOME:-$HOME/.config}/k9s/skins\"\nmkdir -p \"$OUT\"\ncurl -L https://github.com/catppuccin/k9s/archive/main.tar.gz | tar xz -C \"$OUT\" --strip-components=2 k9s-main/dist\n```\n\nThen edit your k9s config\n\n```\n# ~/.config/k9s/config.yml\nk9s:\n  ui:\n    skin: catppuccin-mocha\n    # ...or another flavor:\n    # skin: catppuccin-macchiato\n    # skin: catppuccin-frappe\n    # skin: catppuccin-latte\n\n    # ...or the transparent variants:\n    # skin: catppuccin-mocha-transparent\n    # skin: catppuccin-macchiato-transparent\n    # skin: catppuccin-frappe-transparent\n    # skin: catppuccin-latte-transparent\n```\n\nOther k9s skins are available [here](https://github.com/derailed/k9s/tree/master/skins)\n",
      "summary": "To customize k9s use the skins from catppuccin or the ones k9s supplies",
      "date_published": "2024-05-06T11:57:07Z",
      "date_modified": "2024-05-06T11:57:07Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "cli",
        "homelab",
        "tech",
        "k8s",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/kanboard-to-keep-me-focused-on-my-own-ideas/",
      "url": "https://pype.dev/kanboard-to-keep-me-focused-on-my-own-ideas/",
      "title": "Kanboard to keep me focused on my own ideas",
      "content_html": "\u003ch2 id=\"tl-dr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTL;DR\u003c/span\u003e \u003ca href=\"#tl-dr\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve been using kanboard as a self-hosted kanban board. It\u0026rsquo;s keeping me focused on \u003ca href=\"https://myditialharbor.com\"\u003eDigital Harbor\u003c/a\u003e when I\u0026rsquo;d rather be doing something less productive.\u003c/p\u003e\n\u003ch2 id=\"my-todos\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy TODOs\u003c/span\u003e \u003ca href=\"#my-todos\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s the thing about my TODOs\u0026hellip; they\u0026rsquo;re everywhere. I\u0026rsquo;ve tried a crazy amount of different organizational tactics for my todo items. I\u0026rsquo;ve tried post-it notes, a journal, \u003ccode\u003eTODO.md\u003c/code\u003e in specific repos, a master \u003ccode\u003etodo\u003c/code\u003e repo, todo CLIs, etc\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThe problem for me was consistency\u0026hellip; I would regularly forget what I was using for TODOs at that moment in my life\u0026hellip; was there a todo file in this project? Do I have a todo repo cloned on this computer? Is this stuff in my journal or on post-it notes?\u003c/p\u003e\n\u003cp\u003eAnd what do I do with new ideas? Do I organize them centrally or use a repo for the idea I have?\u003c/p\u003e\n\u003cp\u003eIt was getting out of hand to a debilitating degree\u0026hellip; For a while I just gave up on being organized at all\u0026hellip; Things would get done as necessary and if I got some motivation to work on something it was immediately smothered with the anxiety of how I was going to organize my work\u0026hellip;\u003c/p\u003e\n\u003cp\u003eAt some point it became too much\u0026hellip; Now, I have some experience with Azure DevOps/Jira for project management and then I came across Kanboard\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"kanboard\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eKanboard\u003c/span\u003e \u003ca href=\"#kanboard\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://kanboard.org/\"\u003eKanboard\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eKanboard is just a self-hosted kanban style todo app. I know there\u0026rsquo;s a ton of these so the TL;DR of my lesson is I picked an app that I would just use based on simplicity of managing and hosting.\u003c/p\u003e\n\u003cp\u003eI have had kanboard running in my homelab for a long time, but I only barely use it intermittenly. And at that rate I didn\u0026rsquo;t spend any real time organizing my tickets, so I wasn\u0026rsquo;t akshuallly using it - it was just a post-it note replacement.\u003c/p\u003e\n\u003cp\u003eBut then I had an idea for a genuine business idea, and if I was going to ever have a hope of making it a reality, I needed to stay organized. This was when I decided to give kanboard a little more effort\u0026hellip; I knew I could always remember that I\u0026rsquo;d chosen an app as my TODO solution (given all the time I spent questioning what I was using at any given point in time). I also knew I could host kanboard so that I could get to it from wherever was necessary because my homelab is relatively easy to add another public service to.\u003c/p\u003e\n\u003cp\u003eSo once I just decided to lean into this thing, I would take advantage of any moments of motivation and just jot down ideas for things that had to get done\u0026hellip; Simple stuff like explore an infra management option, add one feature to a config, or migrate one website to another stack\u0026hellip; And I would just write these things downs until I have enough time free to crack down on a task. The beautiful thing is, when I am struck with just enough time to do something, and the motivation to do something, I don\u0026rsquo;t waste any time deciding what to do - past-me did present-me a favor and decided what was important already\u0026hellip; So as long as I do myself the favor, when I\u0026rsquo;m ready to go I am never beaten by the anxiety of not knowing what I ant to do or how I\u0026rsquo;ll track what I\u0026rsquo;m doing\u0026hellip; I chose kanboard and even though it\u0026rsquo;s not as fast as a terminal TUI,  it is reliable, simple, and keeps me focused on what I need to do.\u003c/p\u003e\n\u003ch2 id=\"the-biggest-benefit\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe biggest benefit\u003c/span\u003e \u003ca href=\"#the-biggest-benefit\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNot only is just having one app as the solution nice because it\u0026rsquo;s centrally managed and accessible from wherever I need to get it, but the TOP feature of kanboard I use is comments on tickets\u0026hellip; I get to continue to do future-me favors, my jotting down where I\u0026rsquo;m at in a task, what\u0026rsquo;s left to do, what I\u0026rsquo;m trying, etc\u0026hellip; and future-me is in a great mood, because when I have that free few minutes, I can just read my own past thoughts and get back up to speed without wasting time trying to remember things I never would\u0026rsquo;ve remembered if I hadn\u0026rsquo;t written them down!\u003c/p\u003e\n",
      "content_text": "\n## TL;DR\n\nI've been using kanboard as a self-hosted kanban board. It's keeping me focused on [Digital Harbor](https://myditialharbor.com) when I'd rather be doing something less productive.\n\n## My TODOs\n\nHere's the thing about my TODOs... they're everywhere. I've tried a crazy amount of different organizational tactics for my todo items. I've tried post-it notes, a journal, `TODO.md` in specific repos, a master `todo` repo, todo CLIs, etc... \n\nThe problem for me was consistency... I would regularly forget what I was using for TODOs at that moment in my life... was there a todo file in this project? Do I have a todo repo cloned on this computer? Is this stuff in my journal or on post-it notes?\n\nAnd what do I do with new ideas? Do I organize them centrally or use a repo for the idea I have?\n\nIt was getting out of hand to a debilitating degree... For a while I just gave up on being organized at all... Things would get done as necessary and if I got some motivation to work on something it was immediately smothered with the anxiety of how I was going to organize my work...\n\nAt some point it became too much... Now, I have some experience with Azure DevOps/Jira for project management and then I came across Kanboard...\n\n## Kanboard\n\n[Kanboard]( https://kanboard.org/ )\n\nKanboard is just a self-hosted kanban style todo app. I know there's a ton of these so the TL;DR of my lesson is I picked an app that I would just use based on simplicity of managing and hosting.\n\nI have had kanboard running in my homelab for a long time, but I only barely use it intermittenly. And at that rate I didn't spend any real time organizing my tickets, so I wasn't akshuallly using it - it was just a post-it note replacement. \n\nBut then I had an idea for a genuine business idea, and if I was going to ever have a hope of making it a reality, I needed to stay organized. This was when I decided to give kanboard a little more effort... I knew I could always remember that I'd chosen an app as my TODO solution (given all the time I spent questioning what I was using at any given point in time). I also knew I could host kanboard so that I could get to it from wherever was necessary because my homelab is relatively easy to add another public service to.\n\nSo once I just decided to lean into this thing, I would take advantage of any moments of motivation and just jot down ideas for things that had to get done... Simple stuff like explore an infra management option, add one feature to a config, or migrate one website to another stack... And I would just write these things downs until I have enough time free to crack down on a task. The beautiful thing is, when I am struck with just enough time to do something, and the motivation to do something, I don't waste any time deciding what to do - past-me did present-me a favor and decided what was important already... So as long as I do myself the favor, when I'm ready to go I am never beaten by the anxiety of not knowing what I ant to do or how I'll track what I'm doing... I chose kanboard and even though it's not as fast as a terminal TUI,  it is reliable, simple, and keeps me focused on what I need to do.\n\n## The biggest benefit\n\nNot only is just having one app as the solution nice because it's centrally managed and accessible from wherever I need to get it, but the TOP feature of kanboard I use is comments on tickets... I get to continue to do future-me favors, my jotting down where I'm at in a task, what's left to do, what I'm trying, etc... and future-me is in a great mood, because when I have that free few minutes, I can just read my own past thoughts and get back up to speed without wasting time trying to remember things I never would've remembered if I hadn't written them down!\n",
      "summary": "I've been using kanboard as a self-hosted kanban board. It's keeping me focused on Digital Harbor when I'd rather be doing something less productive.",
      "date_published": "2024-04-20T18:27:07Z",
      "date_modified": "2024-04-20T18:27:07Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/dhcp-restart-to-save-ubuntu-22-04-server-networking/",
      "url": "https://pype.dev/dhcp-restart-to-save-ubuntu-22-04-server-networking/",
      "title": "DHCP Restart to Save Ubuntu 22.04 Server Networking",
      "content_html": "\u003cp\u003eI moved a computer to a remote location for an off-site backup but when it was powered on it wouldn\u0026rsquo;t show up on any networks. A solution that got me back in was a friend restarting the dhcp client for me:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo dhclient -r -v \u0026lt;interface\u0026gt; \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e sudo dhclient -v \u0026lt;interface\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI moved a computer to a remote location for an off-site backup but when it was powered on it wouldn't show up on any networks. A solution that got me back in was a friend restarting the dhcp client for me:\n\n```bash\nsudo dhclient -r -v \u003cinterface\u003e \u0026\u0026 sudo dhclient -v \u003cinterface\u003e\n```\n",
      "summary": "I moved a computer to a remote location for an off-site backup but when it was powered on it wouldn't show up on any networks. A solution that got me back in...",
      "date_published": "2023-12-31T20:26:50Z",
      "date_modified": "2023-12-31T20:26:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/simple-port-forwarding-opnsense/",
      "url": "https://pype.dev/simple-port-forwarding-opnsense/",
      "title": "Simple Port Forwarding OPNSense",
      "content_html": "\u003cp\u003e\u003ca href=\"https://forum.opnsense.org/index.php?topic=8783.0\"\u003ehttps://forum.opnsense.org/index.php?topic=8783.0\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nhttps://forum.opnsense.org/index.php?topic=8783.0\n",
      "summary": "https://forum.opnsense.org/index.php?topic=8783.0",
      "date_published": "2023-10-17T10:26:34Z",
      "date_modified": "2023-10-17T10:26:34Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/refresh-nextcloud-groupfolders-after-messing-around-on-the-filesystem/",
      "url": "https://pype.dev/refresh-nextcloud-groupfolders-after-messing-around-on-the-filesystem/",
      "title": "Refresh Nextcloud Groupfolders after messing around on the filesystem",
      "content_html": "\u003cp\u003eExec in as www-data and run ./occ groupfolders:scan folder_id -v (the -v to see what it\u0026rsquo;s doing)\u003c/p\u003e\n",
      "content_text": "\nExec in as www-data and run ./occ groupfolders:scan folder_id -v (the -v to see what it's doing)\n",
      "summary": "Exec in as www-data and run ./occ groupfolders:scan folder_id -v (the -v to see what it's doing)",
      "date_published": "2023-09-23T12:45:06Z",
      "date_modified": "2023-09-23T12:45:06Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/lsof-to-find-what-s-using-your-filesystem/",
      "url": "https://pype.dev/lsof-to-find-what-s-using-your-filesystem/",
      "title": "lsof to find what’s using your filesystem",
      "content_html": "\u003cp\u003elsof | grep /tank/nas shows me what is using my nas at any time!\u003c/p\u003e\n",
      "content_text": "\nlsof | grep /tank/nas shows me what is using my nas at any time!\n",
      "summary": "lsof | grep /tank/nas shows me what is using my nas at any time!",
      "date_published": "2023-04-09T13:32:38Z",
      "date_modified": "2023-04-09T13:32:38Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/changing-zfs-key-for-child-datasets-of-encrypted-dataset-after-migration/",
      "url": "https://pype.dev/changing-zfs-key-for-child-datasets-of-encrypted-dataset-after-migration/",
      "title": "Changing ZFS key for child datasets of encrypted dataset after migration",
      "content_html": "\u003cp\u003e➜ pihole sudo zfs load-key -L file:///path/to/.zfs.tank.key tank/encrypted/vms/arch-sandbox\n➜ pihole sudo zfs change-key -o keylocation=file:///path/to/.zfs.tank.key -o keyformat=raw tank/encrypted/vms/arch-sandbox\nNeed to load-key for each individual dataset, then change key location to be a file instead of the prompt\u003c/p\u003e\n",
      "content_text": "\n➜ pihole sudo zfs load-key -L file:///path/to/.zfs.tank.key tank/encrypted/vms/arch-sandbox\n➜ pihole sudo zfs change-key -o keylocation=file:///path/to/.zfs.tank.key -o keyformat=raw tank/encrypted/vms/arch-sandbox\nNeed to load-key for each individual dataset, then change key location to be a file instead of the prompt\n",
      "summary": "➜ pihole sudo zfs load-key -L file:///path/to/.zfs.tank.key tank/encrypted/vms/arch-sandbox ➜ pihole sudo zfs change-key -o...",
      "date_published": "2023-04-08T20:12:22Z",
      "date_modified": "2023-04-08T20:12:22Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/convert-word-doc-to-pdf-with-headless-libreoffice/",
      "url": "https://pype.dev/convert-word-doc-to-pdf-with-headless-libreoffice/",
      "title": "Convert Word Doc to PDF with Headless Libreoffice",
      "content_html": "\u003cp\u003eI\u0026rsquo;ve been using paperless-ngx to manage all my documents, but every once in a while I\u0026rsquo;ll get a \u003ccode\u003e.docx\u003c/code\u003e file to deal with\u0026hellip;\u003c/p\u003e\n\u003cp\u003eTurns out Libreoffice has a headless mode a \u003ccode\u003epdf\u003c/code\u003e converter built-in!\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003elibreoffice --headless --convert-to pdf /path/to/file.docx --outdir /path/to/output/directory\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cblockquote\u003e\n\u003cp\u003eNote that \u003ccode\u003e--outdir\u003c/code\u003e is in fact a directory, not the path to a file\u003c/p\u003e\n\u003c/blockquote\u003e\n",
      "content_text": "\nI've been using paperless-ngx to manage all my documents, but every once in a while I'll get a `.docx` file to deal with...\n\nTurns out Libreoffice has a headless mode a `pdf` converter built-in!\n\n```Bash\nlibreoffice --headless --convert-to pdf /path/to/file.docx --outdir /path/to/output/directory\n```\n\n\u003e Note that `--outdir` is in fact a directory, not the path to a file\n",
      "summary": "I've been using paperless-ngx to manage all my documents, but every once in a while I'll get a file to deal with...",
      "date_published": "2023-03-09T06:48:38Z",
      "date_modified": "2023-03-09T06:48:38Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/stable-diffusion-notes/",
      "url": "https://pype.dev/stable-diffusion-notes/",
      "title": "Stable Diffusion Notes",
      "content_html": "\u003ch2 id=\"chatgpt-prompt\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eChatGPT Prompt:\u003c/span\u003e \u003ca href=\"#chatgpt-prompt\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eStable Diffusion is an AI art generation model similar to DALLE-2.\nHere are some prompts for generating art with Stable Diffusion.\u003c/p\u003e\n\u003cp\u003eExample:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eA ghostly apparition drifting through a haunted mansion\u0026rsquo;s grand ballroom, illuminated by flickering candlelight. Eerie, ethereal, moody lighting.\u003c/li\u003e\n\u003cli\u003eportait of a homer simpson archer shooting arrow at forest monster, front game card, drark, marvel comics, dark, smooth\u003c/li\u003e\n\u003cli\u003epirate, deep focus, fantasy, matte, sharp focus\u003c/li\u003e\n\u003cli\u003ered dead redemption 2, cinematic view, epic sky, detailed, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful\u003c/li\u003e\n\u003cli\u003ea fantasy style portrait painting of rachel lane / alison brie hybrid in the style of francois boucher oil painting, rpg portrait\u003c/li\u003e\n\u003cli\u003eathena, greek goddess, claudia black, bronze greek armor, owl crown, d \u0026amp; d, fantasy, portrait, headshot, sharp focus\u003c/li\u003e\n\u003cli\u003ecloseup portrait shot of a large strong female biomechanic woman in a scenic scifi environment, elegant, smooth, sharp focus, warframe\u003c/li\u003e\n\u003cli\u003eultra realistic illustration of steve urkle as the hulk, elegant, smooth, sharp focus\u003c/li\u003e\n\u003cli\u003eportrait of beautiful happy young ana de armas, ethereal, realistic anime, clean lines, sharp lines, crisp lines, vibrant color scheme\u003c/li\u003e\n\u003cli\u003eA highly detailed and hyper realistic portrait of a gorgeous young ana de armas, lisa frank, butterflies, floral, sharp focus\u003c/li\u003e\n\u003cli\u003elots of delicious tropical fruits with drops of moisture on table, floating colorful water, mysterious expression, in a modern and abstract setting, with bold and colorful abstract art, blurred background, bright lighting\u003c/li\u003e\n\u003cli\u003e1girl, The most beautiful form of chaos, Fauvist design, Flowing colors, Vivid colors, dynamic angle, fantasy world\u003c/li\u003e\n\u003cli\u003esolo, sitting, close-up, girl in the hourglass, Sand is spilling out of the broken hourglass, flowing sand, huge hourglass art, hologram, particles, nebula, magic circle\u003c/li\u003e\n\u003cli\u003egeometric abstract background, 1girl, depth of field, zentangle, mandala, tangle, entangle, beautiful and aesthetic, dynamic angle, glowing skin, floating colorful sparkles the most beautiful form of chaos, elegant, a brutalist designed, vivid colours, romanticism\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe prompt should adhere to and include all of the following rules:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003ePrompt should always be written in English, regardless of the input language. Please provide the prompts in English.\u003c/li\u003e\n\u003cli\u003eEach prompt should consist of a description of the scene followed by modifiers divided by commas.\u003c/li\u003e\n\u003cli\u003eWhen generating descriptions, focus on portraying the visual elements rather than delving into abstract psychological and emotional aspects. Provide clear and concise details that vividly depict the scene and its composition, capturing the tangible elements that make up the setting.\u003c/li\u003e\n\u003cli\u003eThe modifiers should alter the mood, style, lighting, and other aspects of the scene.\u003c/li\u003e\n\u003cli\u003eMultiple modifiers can be used to provide more specific details.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI want you to write me a list of detailed prompts exactly about the IDEA follow the rule at least 6 every time.\u003c/p\u003e\n\u003cp\u003eReady for my idea?\u003c/p\u003e\n\u003ch2 id=\"chatgpt-prompt-for-rpg-v4\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eChatGPT Prompt for RPG V4:\u003c/span\u003e \u003ca href=\"#chatgpt-prompt-for-rpg-v4\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eStable Diffusion is an AI art generation model similar to DALLE-2.\nHere are some pairs of positive and negative prompts for generating art with Stable Diffusion.\u003c/p\u003e\n\u003cp\u003eExample:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ePositive: A (full body:1.3) shot at 8k resolution, splash art, fantastic comic book style, photorealistic, intense look, anatomical photorealistic digital painting portrait of a (old male:1.3) human (warrior:1.3) in black and gold intricate (heavy armor:1.3) in a (dark and moody universe:1.3), light particle, very detailed skin,samurai, very detailed eyes, (elden ring style:1.3), (warhammer style:1.1), concept artist, global illumination, depth of field, splash art, art by artgerm and greg rutkowski and viktoria gavrilenko\nNegative: (symmetry:1.2), facial marking, crown, horn, (helmet:1.3), (hoodie:1.1), clock, Female, visible hand, asian, two face, big hair, open mouth, cartoon, high contrast, poorly drawn, Scribbles, Low quality, Low rated, Mediocre, Screenshot, Software, UI, watermark, text, overlay, getty images, cropped, low quality\u003c/p\u003e\n\u003col start=\"2\"\u003e\n\u003cli\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ePositive: close-up head, facing camera, beautiful satanic female necromancer blood queen, ritualistic (neck tattoo:1.3), (insanely detailed:1.5), ((solo)), (highest quality, Alessandro Casagrande, Greg Rutkowski, Sally Mann, concept art, 4k), (colourful), (high sharpness), ((detailed pupils)), ((painting:1.1)), (digital painting:1.1), detailed face and eyes,Masterpiece, best quality, highly detailed photo:1, 8k, detailed face,photorealistic, (black long Hair:1.1),(young woman),By jeremy mann, by sandra chevrier, by maciej kuciara, smoke and shadow, night, sharp, ((perfect body)), realistic, real shadow, 3d, ((full body)), ((dark and gloomy universe)), (by Michelangelo)\nNegative: crown, (facial marking:1.2), cloth gem, jewel, jewelry, (flower:1.3), (cloak:1.1), (bad art, low detail, pencil drawing, old, mature:1.6), (grainy, low quality, mutated hands and fingers:1.5), (watermark, thin lines:1.3), (deformed, signature:1.2), (big nipples, blurry, ugly, bad anatomy, extra limbs, undersaturated, low resolution), disfigured, deformations, out of frame, amputee, bad proportions, extra limb, missing limbs, distortion, floating limbs, out of frame, poorly drawn face, poorly drawn hands, text, malformed, error, missing fingers, cropped, jpeg artifacts, teeth, unsharp\u003c/p\u003e\n\u003col start=\"3\"\u003e\n\u003cli\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ePositive: (Painting:1.3) of (Detailed illustration:1.3) A (full body:1.3) shot at 8k resolution, splash art, fantastic comic book style, photorealistic, intense look, anatomical photorealistic digital painting portrait of a (old male:1.3) human (warrior:1.3) in black and gold intricate (heavy armor:1.3) in a (dark and moody universe:1.3), light particle, very detailed skin,samurai, very detailed eyes, (elden ring style:1.3), (warhammer style:1.1), concept artist, global illumination, depth of field, splash art, art by artgerm and greg rutkowski and viktoria gavrilenko\nNegative:  (symmetry:1.2), facial marking, crown, (horn:1.1), (helmet:1.3), (hoodie:1.1), clock, Female, visible hand, asian, two face, big hair, open mouth, cartoon, high contrast, poorly drawn, Scribbles, Low quality, Low rated, Mediocre, Screenshot, Software, UI, watermark, text, overlay, getty images, cropped, low qualityLow quality,Bad composition,Faded,(Photo:1.5),(Frame:1.3),watermark,signa ture\u003c/p\u003e\n\u003col start=\"4\"\u003e\n\u003cli\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ePositive: sci-fi, space rogue thief girl, (smirk:1.1), short hair, black hood, light armor, big grey eyes, beautiful detailed eyes, drawn by Greg Rutkowski, Yoji Shinkawa:0.6\nNegative: open mouth, facial marking, flower, bad-hands-5, lipstick, text, watermark\u003c/p\u003e\n\u003cp\u003eThe prompt should adhere to and include all of the following rules:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eEach prompt should consist of a description of the scene followed by modifiers divided by commas.\u003c/li\u003e\n\u003cli\u003eWhen generating descriptions, focus on portraying the visual elements rather than delving into abstract psychological and emotional aspects. Provide clear and concise details that vividly depict the scene and its composition, capturing the tangible elements that make up the setting.\u003c/li\u003e\n\u003cli\u003eThe modifiers should alter the mood, style, lighting, and other aspects of the scene.\u003c/li\u003e\n\u003cli\u003eMultiple modifiers can be used to provide more specific details.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eI want you to write me a list of detailed prompts exactly about the IDEA follow the rule at least 6 every time.\u003c/p\u003e\n\u003cp\u003eReady for my idea?\u003c/p\u003e\n\u003ch3 id=\"ideas\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIDEAS\u003c/span\u003e \u003ca href=\"#ideas\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIDEA: A slice of pie that is filled with a futuristic city\u003c/p\u003e\n\u003ch2 id=\"artists-i-like\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eArtists I like\u003c/span\u003e \u003ca href=\"#artists-i-like\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://stablediffusion.fr/artists\"\u003eExamples\u003c/a\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eJ. M. W. Turner\u003c/li\u003e\n\u003cli\u003eJean-Baptiste Monge\u003c/li\u003e\n\u003cli\u003eJohn Lasseter\u003c/li\u003e\n\u003cli\u003eJohn Martin\u003c/li\u003e\n\u003cli\u003eRaymond Swanland\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"anime-styles\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eAnime styles\u003c/span\u003e \u003ca href=\"#anime-styles\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eKrenz Cushart\u003c/li\u003e\n\u003cli\u003eKyoto Animation\u003c/li\u003e\n\u003cli\u003eYoshiyuki Sadamoto\u003c/li\u003e\n\u003cli\u003eYuumei\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"scenic\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eScenic\u003c/span\u003e \u003ca href=\"#scenic\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eTed Nasmith\u003c/li\u003e\n\u003cli\u003eTerry Redlin\u003c/li\u003e\n\u003cli\u003eThomas Kinkade\u003c/li\u003e\n\u003cli\u003eThomas Moran\u003c/li\u003e\n\u003cli\u003eThomas W Schaller\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"cityscapes\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCityscapes\u003c/span\u003e \u003ca href=\"#cityscapes\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eMarc Simonetti\u003c/li\u003e\n\u003cli\u003eWadim Kashin\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"abstract\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eAbstract\u003c/span\u003e \u003ca href=\"#abstract\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eScott Naismith\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"settings-notes\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSettings notes\u003c/span\u003e \u003ca href=\"#settings-notes\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI tend to like cfg at least on the higher end\u003c/p\u003e\n\u003ch2 id=\"good-prompts\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGood prompts\u003c/span\u003e \u003ca href=\"#good-prompts\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAngel of death, shadows and mist, skelletol feathered wings, highly detailed, amazing details, intriciate lines, purple haze, styles Yuumei and Krenz Cushart, UHD, HDR, upscale, 8K photo, concept art\u003c/p\u003e\n\u003ch3 id=\"unicorns\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUnicorns\u003c/span\u003e \u003ca href=\"#unicorns\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003epositive: unicorn, \u003ca href=\"lora:Unicorns:0.9\"\u003elora:Unicorns:0.9\u003c/a\u003e, portrait, professional photography, 35mm, highly detailed, incredibly realistic, best quality, high quality, highres, purple, \u003ca href=\"lora:mossbeast:0.3\"\u003elora:mossbeast:0.3\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003enegative: (two tails:1.2),FastNegativeV2,(bad-artist:1.0), (loli:1.2), (worst quality, low quality:1.4), (bad_prompt_version2:0.8), bad-hands-5,lowres, bad anatomy, bad hands, ((text)), (watermark), error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, ((username)), blurry, (extra limbs), bad-artist-anime, badhandv4, EasyNegative, ng_deepnegative_v1_75t, verybadimagenegative_v1.3, BadDream,(three hands:1.1),(three legs:1.1),(more than two hands:1.4),(more than two legs,:1.2),\u003c/p\u003e\n",
      "content_text": "\n## ChatGPT Prompt:\n\nStable Diffusion is an AI art generation model similar to DALLE-2.\nHere are some prompts for generating art with Stable Diffusion.\n\nExample:\n\n- A ghostly apparition drifting through a haunted mansion's grand ballroom, illuminated by flickering candlelight. Eerie, ethereal, moody lighting.\n- portait of a homer simpson archer shooting arrow at forest monster, front game card, drark, marvel comics, dark, smooth\n- pirate, deep focus, fantasy, matte, sharp focus\n- red dead redemption 2, cinematic view, epic sky, detailed, low angle, high detail, warm lighting, volumetric, godrays, vivid, beautiful\n- a fantasy style portrait painting of rachel lane / alison brie hybrid in the style of francois boucher oil painting, rpg portrait\n- athena, greek goddess, claudia black, bronze greek armor, owl crown, d \u0026 d, fantasy, portrait, headshot, sharp focus\n- closeup portrait shot of a large strong female biomechanic woman in a scenic scifi environment, elegant, smooth, sharp focus, warframe\n- ultra realistic illustration of steve urkle as the hulk, elegant, smooth, sharp focus\n- portrait of beautiful happy young ana de armas, ethereal, realistic anime, clean lines, sharp lines, crisp lines, vibrant color scheme\n- A highly detailed and hyper realistic portrait of a gorgeous young ana de armas, lisa frank, butterflies, floral, sharp focus\n- lots of delicious tropical fruits with drops of moisture on table, floating colorful water, mysterious expression, in a modern and abstract setting, with bold and colorful abstract art, blurred background, bright lighting\n- 1girl, The most beautiful form of chaos, Fauvist design, Flowing colors, Vivid colors, dynamic angle, fantasy world\n- solo, sitting, close-up, girl in the hourglass, Sand is spilling out of the broken hourglass, flowing sand, huge hourglass art, hologram, particles, nebula, magic circle\n- geometric abstract background, 1girl, depth of field, zentangle, mandala, tangle, entangle, beautiful and aesthetic, dynamic angle, glowing skin, floating colorful sparkles the most beautiful form of chaos, elegant, a brutalist designed, vivid colours, romanticism\n\nThe prompt should adhere to and include all of the following rules:\n\n- Prompt should always be written in English, regardless of the input language. Please provide the prompts in English.\n- Each prompt should consist of a description of the scene followed by modifiers divided by commas.\n- When generating descriptions, focus on portraying the visual elements rather than delving into abstract psychological and emotional aspects. Provide clear and concise details that vividly depict the scene and its composition, capturing the tangible elements that make up the setting.\n- The modifiers should alter the mood, style, lighting, and other aspects of the scene.\n- Multiple modifiers can be used to provide more specific details.\n\nI want you to write me a list of detailed prompts exactly about the IDEA follow the rule at least 6 every time.\n\nReady for my idea?\n\n## ChatGPT Prompt for RPG V4:\n\nStable Diffusion is an AI art generation model similar to DALLE-2.\nHere are some pairs of positive and negative prompts for generating art with Stable Diffusion.\n\nExample:\n\n1. \nPositive: A (full body:1.3) shot at 8k resolution, splash art, fantastic comic book style, photorealistic, intense look, anatomical photorealistic digital painting portrait of a (old male:1.3) human (warrior:1.3) in black and gold intricate (heavy armor:1.3) in a (dark and moody universe:1.3), light particle, very detailed skin,samurai, very detailed eyes, (elden ring style:1.3), (warhammer style:1.1), concept artist, global illumination, depth of field, splash art, art by artgerm and greg rutkowski and viktoria gavrilenko\nNegative: (symmetry:1.2), facial marking, crown, horn, (helmet:1.3), (hoodie:1.1), clock, Female, visible hand, asian, two face, big hair, open mouth, cartoon, high contrast, poorly drawn, Scribbles, Low quality, Low rated, Mediocre, Screenshot, Software, UI, watermark, text, overlay, getty images, cropped, low quality\n\n2. \nPositive: close-up head, facing camera, beautiful satanic female necromancer blood queen, ritualistic (neck tattoo:1.3), (insanely detailed:1.5), ((solo)), (highest quality, Alessandro Casagrande, Greg Rutkowski, Sally Mann, concept art, 4k), (colourful), (high sharpness), ((detailed pupils)), ((painting:1.1)), (digital painting:1.1), detailed face and eyes,Masterpiece, best quality, highly detailed photo:1, 8k, detailed face,photorealistic, (black long Hair:1.1),(young woman),By jeremy mann, by sandra chevrier, by maciej kuciara, smoke and shadow, night, sharp, ((perfect body)), realistic, real shadow, 3d, ((full body)), ((dark and gloomy universe)), (by Michelangelo)\nNegative: crown, (facial marking:1.2), cloth gem, jewel, jewelry, (flower:1.3), (cloak:1.1), (bad art, low detail, pencil drawing, old, mature:1.6), (grainy, low quality, mutated hands and fingers:1.5), (watermark, thin lines:1.3), (deformed, signature:1.2), (big nipples, blurry, ugly, bad anatomy, extra limbs, undersaturated, low resolution), disfigured, deformations, out of frame, amputee, bad proportions, extra limb, missing limbs, distortion, floating limbs, out of frame, poorly drawn face, poorly drawn hands, text, malformed, error, missing fingers, cropped, jpeg artifacts, teeth, unsharp\n\n3. \nPositive: (Painting:1.3) of (Detailed illustration:1.3) A (full body:1.3) shot at 8k resolution, splash art, fantastic comic book style, photorealistic, intense look, anatomical photorealistic digital painting portrait of a (old male:1.3) human (warrior:1.3) in black and gold intricate (heavy armor:1.3) in a (dark and moody universe:1.3), light particle, very detailed skin,samurai, very detailed eyes, (elden ring style:1.3), (warhammer style:1.1), concept artist, global illumination, depth of field, splash art, art by artgerm and greg rutkowski and viktoria gavrilenko\nNegative:  (symmetry:1.2), facial marking, crown, (horn:1.1), (helmet:1.3), (hoodie:1.1), clock, Female, visible hand, asian, two face, big hair, open mouth, cartoon, high contrast, poorly drawn, Scribbles, Low quality, Low rated, Mediocre, Screenshot, Software, UI, watermark, text, overlay, getty images, cropped, low qualityLow quality,Bad composition,Faded,(Photo:1.5),(Frame:1.3),watermark,signa ture\n\n4. \nPositive: sci-fi, space rogue thief girl, (smirk:1.1), short hair, black hood, light armor, big grey eyes, beautiful detailed eyes, drawn by Greg Rutkowski, Yoji Shinkawa:0.6\nNegative: open mouth, facial marking, flower, bad-hands-5, lipstick, text, watermark \n\nThe prompt should adhere to and include all of the following rules:\n\n- Each prompt should consist of a description of the scene followed by modifiers divided by commas.\n- When generating descriptions, focus on portraying the visual elements rather than delving into abstract psychological and emotional aspects. Provide clear and concise details that vividly depict the scene and its composition, capturing the tangible elements that make up the setting.\n- The modifiers should alter the mood, style, lighting, and other aspects of the scene.\n- Multiple modifiers can be used to provide more specific details.\n\nI want you to write me a list of detailed prompts exactly about the IDEA follow the rule at least 6 every time.\n\nReady for my idea?\n\n### IDEAS\n\nIDEA: A slice of pie that is filled with a futuristic city\n\n## Artists I like\n\n[Examples](https://stablediffusion.fr/artists)\n\n- J. M. W. Turner\n- Jean-Baptiste Monge\n- John Lasseter\n- John Martin\n- Raymond Swanland\n\n### Anime styles\n\n- Krenz Cushart\n- Kyoto Animation\n- Yoshiyuki Sadamoto\n- Yuumei\n\n### Scenic\n\n- Ted Nasmith\n- Terry Redlin\n- Thomas Kinkade\n- Thomas Moran\n- Thomas W Schaller\n\n### Cityscapes\n\n- Marc Simonetti\n- Wadim Kashin\n\n### Abstract\n\n- Scott Naismith\n\n## Settings notes\n\nI tend to like cfg at least on the higher end\n\n## Good prompts\n\nAngel of death, shadows and mist, skelletol feathered wings, highly detailed, amazing details, intriciate lines, purple haze, styles Yuumei and Krenz Cushart, UHD, HDR, upscale, 8K photo, concept art\n\n### Unicorns\n\npositive: unicorn, \u003clora:Unicorns:0.9\u003e, portrait, professional photography, 35mm, highly detailed, incredibly realistic, best quality, high quality, highres, purple, \u003clora:mossbeast:0.3\u003e\n\nnegative: (two tails:1.2),FastNegativeV2,(bad-artist:1.0), (loli:1.2), (worst quality, low quality:1.4), (bad_prompt_version2:0.8), bad-hands-5,lowres, bad anatomy, bad hands, ((text)), (watermark), error, missing fingers, extra digit, fewer digits, cropped, worst quality, low quality, normal quality, ((username)), blurry, (extra limbs), bad-artist-anime, badhandv4, EasyNegative, ng_deepnegative_v1_75t, verybadimagenegative_v1.3, BadDream,(three hands:1.1),(three legs:1.1),(more than two hands:1.4),(more than two legs,:1.2),\n\n",
      "summary": "Stable Diffusion is an AI art generation model similar to DALLE-2. Here are some prompts for generating art with Stable Diffusion.",
      "date_published": "2023-01-28T14:15:11Z",
      "date_modified": "2023-01-28T14:15:11Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "data",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/ffmpeg-10-bit-videos-to-8-bit/",
      "url": "https://pype.dev/ffmpeg-10-bit-videos-to-8-bit/",
      "title": "FFMPEG 10-bit videos to 8-bit",
      "content_html": "\u003cp\u003e\u003ccode\u003effmpeg -i input.mp4 -map 0 -c:v libx264 -vf format=yuv420p -c:a copy output.mp4\u003c/code\u003e\u003c/p\u003e\n",
      "content_text": "\n`ffmpeg -i input.mp4 -map 0 -c:v libx264 -vf format=yuv420p -c:a copy output.mp4`\n",
      "date_published": "2023-01-16T13:15:53Z",
      "date_modified": "2023-01-16T13:15:53Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "cli",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/i3-like-keyboard-mapping-in-pop-os/",
      "url": "https://pype.dev/i3-like-keyboard-mapping-in-pop-os/",
      "title": "i3-Like keyboard mapping in Pop_OS",
      "content_html": "\u003cp\u003eI was introduced to tiling window managers through i3, which I use heavily on\none of my machines. I have switched to Pop_OS! at home though, which has a\ntiling window mode but the keybindings are not what I\u0026rsquo;m used to for i3. I\nwanted to at least navigate workspaces how I\u0026rsquo;m used to doing (cause I set\nworkspace 3 for communication apps, 1 for my terminal, etc\u0026hellip;)\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s how I set keybindings for:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003e\u0026lt;Super\u0026gt; + \u0026lt;number\u0026gt;\u003c/code\u003e sends me to that numbered workspace\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003e\u0026lt;Shift\u0026gt; + \u0026lt;Super\u0026gt; + \u0026lt;number\u0026gt;\u003c/code\u003e moves the window I\u0026rsquo;m focused on to workspace \u003ccode\u003enumber\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.mutter dynamic-workspaces \u003cspan class=\"nb\"\u003efalse\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.preferences num-workspaces \u003cspan class=\"m\"\u003e8\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-1 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-2 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-3 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-4 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-5 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-6 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-7 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-8 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.shell.keybindings switch-to-application-9 \u003cspan class=\"o\"\u003e[]\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-1 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;1\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-2 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;2\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-3 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;3\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-4 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;4\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-5 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;5\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-6 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;6\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-7 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;7\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-8 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;8\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-9 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;9\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings switch-to-workspace-10 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;0\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-1 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;1\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-2 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;2\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-3 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;3\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-4 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;4\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-5 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;5\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-6 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;6\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-7 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;7\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-8 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;8\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-9 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;9\u0026#39;]\u0026#34;\u003c/span\u003e \n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egsettings \u003cspan class=\"nb\"\u003eset\u003c/span\u003e org.gnome.desktop.wm.keybindings move-to-workspace-10 \u003cspan class=\"s2\"\u003e\u0026#34;[\u0026#39;\u0026lt;Super\u0026gt;\u0026lt;Shift\u0026gt;0\u0026#39;]\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI was introduced to tiling window managers through i3, which I use heavily on\none of my machines. I have switched to Pop_OS! at home though, which has a\ntiling window mode but the keybindings are not what I'm used to for i3. I\nwanted to at least navigate workspaces how I'm used to doing (cause I set\nworkspace 3 for communication apps, 1 for my terminal, etc...)\n\nHere's how I set keybindings for:\n\n* `\u003cSuper\u003e + \u003cnumber\u003e` sends me to that numbered workspace\n* `\u003cShift\u003e + \u003cSuper\u003e + \u003cnumber\u003e` moves the window I'm focused on to workspace `number`\n\n```bash\n#!/bin/bash\ngsettings set org.gnome.mutter dynamic-workspaces false \ngsettings set org.gnome.desktop.wm.preferences num-workspaces 8 \ngsettings set org.gnome.shell.keybindings switch-to-application-1 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-2 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-3 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-4 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-5 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-6 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-7 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-8 [] \ngsettings set org.gnome.shell.keybindings switch-to-application-9 [] \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-1 \"['\u003cSuper\u003e1']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-2 \"['\u003cSuper\u003e2']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-3 \"['\u003cSuper\u003e3']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-4 \"['\u003cSuper\u003e4']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-5 \"['\u003cSuper\u003e5']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-6 \"['\u003cSuper\u003e6']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-7 \"['\u003cSuper\u003e7']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-8 \"['\u003cSuper\u003e8']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-9 \"['\u003cSuper\u003e9']\" \ngsettings set org.gnome.desktop.wm.keybindings switch-to-workspace-10 \"['\u003cSuper\u003e0']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-1 \"['\u003cSuper\u003e\u003cShift\u003e1']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-2 \"['\u003cSuper\u003e\u003cShift\u003e2']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-3 \"['\u003cSuper\u003e\u003cShift\u003e3']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-4 \"['\u003cSuper\u003e\u003cShift\u003e4']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-5 \"['\u003cSuper\u003e\u003cShift\u003e5']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-6 \"['\u003cSuper\u003e\u003cShift\u003e6']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-7 \"['\u003cSuper\u003e\u003cShift\u003e7']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-8 \"['\u003cSuper\u003e\u003cShift\u003e8']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-9 \"['\u003cSuper\u003e\u003cShift\u003e9']\" \ngsettings set org.gnome.desktop.wm.keybindings move-to-workspace-10 \"['\u003cSuper\u003e\u003cShift\u003e0']\"\n```\n",
      "summary": "I was introduced to tiling window managers through i3, which I use heavily on one of my machines. I have switched to Pop_OS! at home though, which has a...",
      "date_published": "2023-01-12T05:51:25Z",
      "date_modified": "2023-01-12T05:51:25Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/use-non-standard-named-ssh-keys-with-github/",
      "url": "https://pype.dev/use-non-standard-named-ssh-keys-with-github/",
      "title": "Use non-standard named ssh keys with github",
      "content_html": "\u003cp\u003eI was getting \u003ccode\u003e(publickey denied)\u003c/code\u003e when trying to push to GH using ssh. When I\ntested the connection I saw that a bunch of keys in ``~/.ssh/ were being\nattempted\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e✗ ssh git@github.com -vv\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_rsa \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_ecdsa \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_ecdsa_sk \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_ed25519 \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_ed25519_sk \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_xmss \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: Will attempt key: /home/nic/.ssh/id_dsa \n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e...\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edebug1: No more authentication methods to try.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003egit@github.com: Permission denied (publickey).\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNone of those were the key I setup with GH. So I added an entry\ninto \u003ccode\u003e~/.ssh/config\u003c/code\u003e:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHost\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egithub.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eUser git\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ePort 22\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHostname github.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eIdentityFile ~/.ssh/my_custom_github_key\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTCPKeepAlive yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eIdentitiesOnly yes \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 was getting `(publickey denied)` when trying to push to GH using ssh. When I\ntested the connection I saw that a bunch of keys in ``~/.ssh/ were being\nattempted\n\n```console\n✗ ssh git@github.com -vv\n\n...\n\ndebug1: Will attempt key: /home/nic/.ssh/id_rsa \ndebug1: Will attempt key: /home/nic/.ssh/id_ecdsa \ndebug1: Will attempt key: /home/nic/.ssh/id_ecdsa_sk \ndebug1: Will attempt key: /home/nic/.ssh/id_ed25519 \ndebug1: Will attempt key: /home/nic/.ssh/id_ed25519_sk \ndebug1: Will attempt key: /home/nic/.ssh/id_xmss \ndebug1: Will attempt key: /home/nic/.ssh/id_dsa \n\n...\n\ndebug1: No more authentication methods to try.\ngit@github.com: Permission denied (publickey).\n\n```\n\nNone of those were the key I setup with GH. So I added an entry\ninto `~/.ssh/config`:\n\n```text\nHost\ngithub.com\nUser git\nPort 22\nHostname github.com\nIdentityFile ~/.ssh/my_custom_github_key\nTCPKeepAlive yes\nIdentitiesOnly yes \n\n```\n\n",
      "summary": "I was getting when trying to push to GH using ssh. When I tested the connection I saw that a bunch of keys in ``~/.ssh/ were being attempted",
      "date_published": "2023-01-03T08:34:50Z",
      "date_modified": "2023-01-03T08:34:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/modal-labs/",
      "url": "https://pype.dev/modal-labs/",
      "title": "Modal Labs",
      "content_html": "\u003cp\u003ePlaying around with Modal Labs\u003c/p\u003e\n\u003cp\u003eOne of the first things I tried was a regular cron job\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@stub.function\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\"\u003eschedule\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003emodal\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePeriod\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eminutes\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e59\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003esecret\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003emodal\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSecret\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efrom_name\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;my-dummy-secret\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=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003esay_hi\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\"\u003enow\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ectime\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\"\u003esecret\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;dummy-secret\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=\"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;Hello \u003c/span\u003e\u003cspan class=\"si\"\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=\"s1\"\u003e\u0026#39;USER\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;Rodney\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e at \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003enow\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\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;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003esecret\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\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThis can get deployed with \u003ccode\u003emodal deploy --name \u0026lt;app name\u0026gt; \u0026lt;path to .py file with the stub and function defined in it\u0026gt; \u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThis function gets deployed as an app that I conveniently call \u003ccode\u003esay_hi\u003c/code\u003e (as far\nas I can tell the app name can be anything - as I add functions to this same\napp and deploy with the same name to get a new version)\u003c/p\u003e\n\u003cp\u003eNotice that this also is an example of giving access to a secret - defined in the Modal Labs dashboard\u003c/p\u003e\n\u003cp\u003eWe can take a look at the apps running at \u003ca href=\"https://modal.com/apps\"\u003ehttps://modal.com/apps\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eI then added another function to experiment with custom container images and\nsaw then that Modal will just slap a new version on anything provisioned with\nthe same name (intuitive enough for sure) so when I add functions to my .py\nscript and run \u003ccode\u003emodal deploy --name say_hi myscript.py\u003c/code\u003e over and over, the app\ncalled \u003ccode\u003esay_hi\u003c/code\u003e in the Modal apps dashboard just gets a new version\u003c/p\u003e\n\u003cp\u003eThis means I can spin up several instances of functionally the same app but with different names/versions etc\u0026hellip;\nQ: Maybe there\u0026rsquo;s gitops or policy stuff builtin to app names then?\u003c/p\u003e\n\u003cp\u003eI needed to take down an app I deployed as a duplicate but you don\u0026rsquo;t stop apps\nby name, you stop them by an id\u0026hellip; see below\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003emodal-sandbox/modal_sandbox   main   ×1  ×9 via   v3.10.6(modal-sandbox)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e✗ modal app stop --help\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e Usage: modal app stop [OPTIONS] APP_ID\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e Stop an app.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e╭─ Arguments ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e│ *    app_id      TEXT  [default: None] [required]                                                                                                │\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e│ --help          Show this message and exit.                                                                                                      │\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003emodal-sandbox/modal_sandbox   main   ×1  ×9 via   v3.10.6(modal-sandbox)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e❯ modal app list\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e┃ App ID                    ┃ Description         ┃ State    ┃ Creation time             ┃ Stop time                 ┃\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e│ ap-lzy1AAuVy7POFkUcDKRxpQ │ print_info          │ deployed │ 2022-12-28 20:59:07-06:00 │                           │\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e│ ap-qYjE45dciqgT3C3CpNp3RL │ say_hi              │ deployed │ 2022-12-28 19:49:22-06:00 │                           │\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e│ ap-X7FYneUeYV5IKHcyirSb87 │ link-scraper        │ stopped  │ 2022-12-28 15:39:02-06:00 │ 2022-12-28 15:39:04-06:00 │\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e│ ap-UOXTUU4uSRx2UZypJOcAsk │ example-get-started │ stopped  │ 2022-12-28 15:17:47-06:00 │ 2022-12-28 15:17:49-06:00 │\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e└───────────────────────────┴─────────────────────┴──────────┴───────────────────────────┴───────────────────────────┘\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003emodal-sandbox/modal_sandbox   main   ×1  ×9 via   v3.10.6(modal-sandbox)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e❯ modal app stop ap-lzy1AAuVy7POFkUcDKRxpQ\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch1 id=\"git-warning\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGit warning!\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI ran \u003ccode\u003emodal deploy ...\u003c/code\u003e after comitting some stuff I wanted to try BUT I had\nchanges in my file I didn\u0026rsquo;t want to deploy\u0026hellip; some git safety would be nice for\ndeployment!\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003egit stash \u0026amp;\u0026amp; modal deploy .. \u0026amp;\u0026amp; git stash pop\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eQuestion for Modal team - in my modal sandbox repo at commit:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eaab6162 (HEAD -\u0026gt; main) HEAD@{1}: commit: print base version of my own image to prove it to me\n 1 file changed, 2 insertions(+)\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eAn environment variable, \u003ccode\u003eBASE_VERSION\u003c/code\u003e that I expect to be in my base image\nwas not available to the python function in my Modal app\u0026hellip; hopefully the log\nis still\n\u003ca href=\"https://modal.com/logs/ap-qYjE45dciqgT3C3CpNp3RL?functionId=fu-rOt31ShRE1W1CQfuf02fsq\u0026amp;taskId=ta-dm8BfiblvFLwVIQyt75YC2\u0026amp;inputId=in-n64klEFrLtbcm2BiykJEvW\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003ch2 id=\"github-actions\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGitHub Actions\u003c/span\u003e \u003ca href=\"#github-actions\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI think it\u0026rsquo;d be easy to deploy from GH actions\u0026hellip; just need an API Key\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"deployment\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDeployment\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eWithout gitops it\u0026rsquo;s already ahrd to manage my versions\u0026hellip; need to get that built in either to github or locally sooooon\u003c/p\u003e\n\u003cp\u003eNomad?? :D\u003c/p\u003e\n",
      "content_text": "\nPlaying around with Modal Labs\n\n\nOne of the first things I tried was a regular cron job...\n\n```python\n@stub.function(\n    schedule=modal.Period(minutes=59), secret=modal.Secret.from_name(\"my-dummy-secret\")\n)\ndef say_hi():\n    now = time.ctime()\n    secret = os.environ.get(\"dummy-secret\")\n    print(f\"Hello {os.environ.get('USER', 'Rodney')} at {now}\")\n    print(f\"{secret=}\")\n\n```\n\nThis can get deployed with `modal deploy --name \u003capp name\u003e \u003cpath to .py file with the stub and function defined in it\u003e `\n\nThis function gets deployed as an app that I conveniently call `say_hi` (as far\nas I can tell the app name can be anything - as I add functions to this same\napp and deploy with the same name to get a new version)\n\nNotice that this also is an example of giving access to a secret - defined in the Modal Labs dashboard\n\nWe can take a look at the apps running at [https://modal.com/apps](https://modal.com/apps)\n\nI then added another function to experiment with custom container images and\nsaw then that Modal will just slap a new version on anything provisioned with\nthe same name (intuitive enough for sure) so when I add functions to my .py\nscript and run `modal deploy --name say_hi myscript.py` over and over, the app\ncalled `say_hi` in the Modal apps dashboard just gets a new version\n\nThis means I can spin up several instances of functionally the same app but with different names/versions etc... \nQ: Maybe there's gitops or policy stuff builtin to app names then?\n\nI needed to take down an app I deployed as a duplicate but you don't stop apps\nby name, you stop them by an id... see below\n\n\n```console\n\nmodal-sandbox/modal_sandbox   main   ×1  ×9 via   v3.10.6(modal-sandbox)\n✗ modal app stop --help\n\n Usage: modal app stop [OPTIONS] APP_ID\n\n Stop an app.\n\n╭─ Arguments ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮\n│ *    app_id      TEXT  [default: None] [required]                                                                                                │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮\n│ --help          Show this message and exit.                                                                                                      │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n\n\nmodal-sandbox/modal_sandbox   main   ×1  ×9 via   v3.10.6(modal-sandbox)\n❯ modal app list\n┏━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━┓\n┃ App ID                    ┃ Description         ┃ State    ┃ Creation time             ┃ Stop time                 ┃\n┡━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━┩\n│ ap-lzy1AAuVy7POFkUcDKRxpQ │ print_info          │ deployed │ 2022-12-28 20:59:07-06:00 │                           │\n│ ap-qYjE45dciqgT3C3CpNp3RL │ say_hi              │ deployed │ 2022-12-28 19:49:22-06:00 │                           │\n│ ap-X7FYneUeYV5IKHcyirSb87 │ link-scraper        │ stopped  │ 2022-12-28 15:39:02-06:00 │ 2022-12-28 15:39:04-06:00 │\n│ ap-UOXTUU4uSRx2UZypJOcAsk │ example-get-started │ stopped  │ 2022-12-28 15:17:47-06:00 │ 2022-12-28 15:17:49-06:00 │\n└───────────────────────────┴─────────────────────┴──────────┴───────────────────────────┴───────────────────────────┘\n\nmodal-sandbox/modal_sandbox   main   ×1  ×9 via   v3.10.6(modal-sandbox)\n❯ modal app stop ap-lzy1AAuVy7POFkUcDKRxpQ\n\n```\n\n# Git warning!\n\nI ran `modal deploy ...` after comitting some stuff I wanted to try BUT I had\nchanges in my file I didn't want to deploy... some git safety would be nice for\ndeployment!\n\n\u003e git stash \u0026\u0026 modal deploy .. \u0026\u0026 git stash pop\n\nQuestion for Modal team - in my modal sandbox repo at commit: \n```\naab6162 (HEAD -\u003e main) HEAD@{1}: commit: print base version of my own image to prove it to me\n 1 file changed, 2 insertions(+)\n\n```\n\nAn environment variable, `BASE_VERSION` that I expect to be in my base image\nwas not available to the python function in my Modal app... hopefully the log\nis still\n[here](https://modal.com/logs/ap-qYjE45dciqgT3C3CpNp3RL?functionId=fu-rOt31ShRE1W1CQfuf02fsq\u0026taskId=ta-dm8BfiblvFLwVIQyt75YC2\u0026inputId=in-n64klEFrLtbcm2BiykJEvW) \n\n\n## GitHub Actions\n\nI think it'd be easy to deploy from GH actions... just need an API Key...\n\n# Deployment\n\nWithout gitops it's already ahrd to manage my versions... need to get that built in either to github or locally sooooon\n\nNomad?? :D\n",
      "summary": "Playing around with Modal Labs",
      "date_published": "2022-12-28T21:01:52Z",
      "date_modified": "2022-12-28T21:01:52Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "cli",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/reminder-about-ssh-copy-id-for-ssh-and-ansible/",
      "url": "https://pype.dev/reminder-about-ssh-copy-id-for-ssh-and-ansible/",
      "title": "Reminder about ssh-copy-id for SSH and Ansible",
      "content_html": "\u003cp\u003e\u003ccode\u003essh-copy-id -i my.key.pub \u0026lt;hostname probably from tailscale\u0026gt;\u003c/code\u003e\nthis makes sure I can run ansible from my desktop against VMs on my server\neasily if they have tailscale for the hostname - otherwise use the IP\u003c/p\u003e\n",
      "content_text": "\n`ssh-copy-id -i my.key.pub \u003chostname probably from tailscale\u003e` \nthis makes sure I can run ansible from my desktop against VMs on my server\neasily if they have tailscale for the hostname - otherwise use the IP\n",
      "summary": "this makes sure I can run ansible from my desktop against VMs on my server easily if they have tailscale for the hostname - otherwise use the IP",
      "date_published": "2022-12-28T13:33:07Z",
      "date_modified": "2022-12-28T13:33:07Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/nextcloud-docker-upgrade-error/",
      "url": "https://pype.dev/nextcloud-docker-upgrade-error/",
      "title": "Nextcloud Docker Upgrade Error",
      "content_html": "\u003cp\u003e\u003ca href=\"https://nicolasbouliane.com/blog/nextcloud-docker-upgrade-error\"\u003ehttps://nicolasbouliane.com/blog/nextcloud-docker-upgrade-error\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\n\n\nhttps://nicolasbouliane.com/blog/nextcloud-docker-upgrade-error\n",
      "summary": "https://nicolasbouliane.com/blog/nextcloud-docker-upgrade-error",
      "date_published": "2022-12-28T09:39:27Z",
      "date_modified": "2022-12-28T09:39:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/systemd-timer-for-syncoid/",
      "url": "https://pype.dev/systemd-timer-for-syncoid/",
      "title": "Systemd timer for syncoid",
      "content_html": "\u003cp\u003eI have a bash script called \u003ccode\u003esyncoid-job\u003c/code\u003e which boils down to a barebones -\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/bin/bash\n\u003c/span\u003e\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\"\u003esyncoid --no-sync-snap --sendoptions\u003cspan class=\"o\"\u003e=\u003c/span\u003ew --no-privilege-elevation \u003cspan class=\"nv\"\u003e$SYNOIC_USER\u003c/span\u003e@\u003cspan class=\"nv\"\u003e$SERVER\u003c/span\u003e:tank/encrypted/nas tank/encrypted/nas\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eI want to run this script hourly but as my user (notice the no-privilege-elevation flag)\u003c/p\u003e\n\u003cp\u003eFirst - create a systemd unit file at \u003ccode\u003e/etc/systemd/system/syncoid-replication.service\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003eUnit\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eDescription\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eZFS Replication With Syncoid\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=\"o\"\u003e[\u003c/span\u003eService\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eType\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eoneshot\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eExecStart\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e/\u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e/dotfiles/syncoid-job\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eUser\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003e$USER\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eGroup\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"nv\"\u003e$GROUP\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=\"o\"\u003e[\u003c/span\u003eInstall\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eWantedBy\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003emulti-user.target\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\u003cp\u003eThen we save the unit file, enable the service, and then start it\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esystemctl enable syncoid-replication.service\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esystemctl start syncoid-replication.service\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cblockquote\u003e\n\u003cp\u003eNote this will run that script\u0026hellip; so be ready for syncoid to do its thing\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eNow for the timer\u0026hellip; We create \u003ccode\u003e/etc/systemd/system/syncoid-replication.timer\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003eUnit\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eDescription\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003eRun syncoid-replication every hour\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=\"o\"\u003e[\u003c/span\u003eTimer\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eOnCalendar\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003ehourly\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=\"o\"\u003e[\u003c/span\u003eInstall\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eWantedBy\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003etimers.target\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\u003cp\u003eHit it with a \u003ccode\u003esystemctl enable syncoid-replication.timer\u003c/code\u003e and you\u0026rsquo;re in business!\u003c/p\u003e\n",
      "content_text": "\nI have a bash script called `syncoid-job` which boils down to a barebones - \n\n```bash\n#!/bin/bash\n\nsyncoid --no-sync-snap --sendoptions=w --no-privilege-elevation $SYNOIC_USER@$SERVER:tank/encrypted/nas tank/encrypted/nas\n```\n\nI want to run this script hourly but as my user (notice the no-privilege-elevation flag)\n\nFirst - create a systemd unit file at `/etc/systemd/system/syncoid-replication.service`\n\n```bash\n[Unit]\nDescription=ZFS Replication With Syncoid\n\n[Service]\nType=oneshot\nExecStart=/$HOME/dotfiles/syncoid-job\nUser=$USER\nGroup=$GROUP\n\n[Install]\nWantedBy=multi-user.target\n\n```\n\nThen we save the unit file, enable the service, and then start it\n\n```console\nsystemctl enable syncoid-replication.service\nsystemctl start syncoid-replication.service\n\n```\n\n\u003e Note this will run that script... so be ready for syncoid to do its thing\n\nNow for the timer... We create `/etc/systemd/system/syncoid-replication.timer`\n\n```bash\n[Unit]\nDescription=Run syncoid-replication every hour\n\n[Timer]\nOnCalendar=hourly\n\n[Install]\nWantedBy=timers.target\n\n```\n\nHit it with a `systemctl enable syncoid-replication.timer` and you're in business!\n",
      "summary": "I have a bash script called which boils down to a barebones -",
      "date_published": "2022-12-21T11:38:27Z",
      "date_modified": "2022-12-21T11:38:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/adding-docker-daemon-json-broke-docker/",
      "url": "https://pype.dev/adding-docker-daemon-json-broke-docker/",
      "title": "Adding docker daemon.json broke docker",
      "content_html": "\u003cp\u003ein /lib/systemd/system/docker.service there is an ExecStart command that got placed there when I setup Docker with Ansible - it threw the -H flag which told the daemon what hosts to setup. But I added the \u0026ldquo;hosts\u0026rdquo; key in my daemon.json and it broke - so removing the -H flag from the systemd unit fixed it\u003c/p\u003e\n",
      "content_text": "\nin /lib/systemd/system/docker.service there is an ExecStart command that got placed there when I setup Docker with Ansible - it threw the -H flag which told the daemon what hosts to setup. But I added the \"hosts\" key in my daemon.json and it broke - so removing the -H flag from the systemd unit fixed it\n",
      "summary": "in /lib/systemd/system/docker.service there is an ExecStart command that got placed there when I setup Docker with Ansible - it threw the -H flag which told...",
      "date_published": "2022-12-21T10:02:25Z",
      "date_modified": "2022-12-21T10:02:25Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "tech",
        "docker",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/suda-vim-for-sudo-access-to-files/",
      "url": "https://pype.dev/suda-vim-for-sudo-access-to-files/",
      "title": "suda.vim for sudo access to files",
      "content_html": "\u003cp\u003eI regularly need to edit system config files - take /etc/sanoid/sanoid.conf as\nan example\u0026hellip; I\u0026rsquo;ll want to play with something but if I don\u0026rsquo;t start Neovim as\nroot then I get in trouble making edits I can\u0026rsquo;t save! So\n\u003ca href=\"https://github.com/lambdalisue/suda.vim\"\u003esuda.vim\u003c/a\u003e gives me\n\u003ccode\u003e:SudaWrite\u003c/code\u003e which let\u0026rsquo;s me write that buffer with sudo privileges even though\nI\u0026rsquo;m Neovim is running with my login user!\u003c/p\u003e\n",
      "content_text": "\nI regularly need to edit system config files - take /etc/sanoid/sanoid.conf as\nan example... I'll want to play with something but if I don't start Neovim as\nroot then I get in trouble making edits I can't save! So\n[suda.vim](https://github.com/lambdalisue/suda.vim) gives me\n`:SudaWrite` which let's me write that buffer with sudo privileges even though\nI'm Neovim is running with my login user!\n",
      "summary": "I regularly need to edit system config files - take /etc/sanoid/sanoid.conf as an example... I'll want to play with something but if I don't start Neovim as...",
      "date_published": "2022-12-21T09:45:34Z",
      "date_modified": "2022-12-21T09:45:34Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/cron-for-nextcloud-in-docker/",
      "url": "https://pype.dev/cron-for-nextcloud-in-docker/",
      "title": "Cron for Nextcloud in Docker",
      "content_html": "\u003cp\u003eAJAX wasn\u0026rsquo;t cutting it, traditional crontab in containers doesn\u0026rsquo;t make much\nsense to me, webcron is recommended but I don\u0026rsquo;t want to register with anything\noutside my LAN\u0026hellip; Turns out you can just spin up an identical container with a\ndifferent entrypoint to /cron.sh that does what you need!\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNote that this is a task in an Ansible playbook - but the docker-compose is straight forward\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eSo the only thing you need to make sure of is that \u003cem\u003eall\u003c/em\u003e the configuration\noptions - data volumes, user permissions, etc. are \u003cem\u003eidentical\u003c/em\u003e between the\ncontainers running the cron job and the one actually hosting NextCloud. This\nensures that the container running cron has proper access to the database and\nfilesystem - or at least the same access as NextCloud proper.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e- \u003cspan class=\"nt\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eNextcloud Cron Docker Container\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\"\u003edocker_container\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\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003enextcloud-cron\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=\"s2\"\u003e\u0026#34;{{ nextcloud_image }}\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\"\u003epull\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003elinks\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\"\u003enextcloud-mysql:mysql\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\"\u003eentrypoint\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003e/cron.sh\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=\"s2\"\u003e\u0026#34;{{ nextcloud_data_directory }}/nextcloud:/var/www/html:rw\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\"\u003eenv\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\"\u003eMYSQL_HOST\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mysql\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\"\u003eMYSQL_DATABASE\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;nextcloud\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\"\u003eMYSQL_USER\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ nextcloud_sql_user }}\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\"\u003eMYSQL_PASSWORD\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ nextcloud_sql_password }}\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\"\u003eNEXTCLOUD_TRUSTED_DOMAINS\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ nextcloud_hostname }}.{{ ansible_nas_domain }}\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\"\u003ePUID\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ nextcloud_user_id }}\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\"\u003ePGID\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ nextcloud_group_id }}\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\"\u003eTZ\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ ansible_nas_timezone }}\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\"\u003erestart_policy\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\"\u003ememory\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ nextcloud_memory }}\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\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nAJAX wasn't cutting it, traditional crontab in containers doesn't make much\nsense to me, webcron is recommended but I don't want to register with anything\noutside my LAN... Turns out you can just spin up an identical container with a\ndifferent entrypoint to /cron.sh that does what you need!\n\n\u003e Note that this is a task in an Ansible playbook - but the docker-compose is straight forward\n\nSo the only thing you need to make sure of is that _all_ the configuration\noptions - data volumes, user permissions, etc. are _identical_ between the\ncontainers running the cron job and the one actually hosting NextCloud. This\nensures that the container running cron has proper access to the database and\nfilesystem - or at least the same access as NextCloud proper.\n\n```yaml\n- name: Nextcloud Cron Docker Container\n  docker_container:\n    name: nextcloud-cron\n    image: \"{{ nextcloud_image }}\"\n    pull: true\n    links:\n      - nextcloud-mysql:mysql\n    entrypoint: /cron.sh\n    volumes:\n      - \"{{ nextcloud_data_directory }}/nextcloud:/var/www/html:rw\"\n    env:\n      MYSQL_HOST: \"mysql\"\n      MYSQL_DATABASE: \"nextcloud\"\n      MYSQL_USER: \"{{ nextcloud_sql_user }}\"\n      MYSQL_PASSWORD: \"{{ nextcloud_sql_password }}\"\n      NEXTCLOUD_TRUSTED_DOMAINS: \"{{ nextcloud_hostname }}.{{ ansible_nas_domain }}\"\n      PUID: \"{{ nextcloud_user_id }}\"\n      PGID: \"{{ nextcloud_group_id }}\"\n      TZ: \"{{ ansible_nas_timezone }}\"\n    restart_policy: unless-stopped\n    memory: \"{{ nextcloud_memory }}\"\n\n```\n\n\n\n",
      "summary": "AJAX wasn't cutting it, traditional crontab in containers doesn't make much sense to me, webcron is recommended but I don't want to register with anything...",
      "date_published": "2022-12-13T06:43:45Z",
      "date_modified": "2022-12-13T06:43:45Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "docker",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/call-basicconfig-to-get-python-log-messages-in-ipython/",
      "url": "https://pype.dev/call-basicconfig-to-get-python-log-messages-in-ipython/",
      "title": "Call basicConfig to get Python log messages in iPython",
      "content_html": "\u003ch1 id=\"logging-instead-of-printing\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eLogging instead of printing\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI am trying to adopt \u003ccode\u003elogger.debug\u003c/code\u003e instead of \u003ccode\u003eprint\u003c/code\u003e but ran into a confusing\nthing in ipython during Advent of Code\u0026hellip; I riddled by script with\n\u003ccode\u003elogger.debug\u003c/code\u003e (yes after setting \u003ccode\u003elogging.setLevel('DEBUG')\u003c/code\u003e) but in ipython\nnone of my log messages showed up!\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003elogging\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\"\u003elogger\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"vm\"\u003e__name__\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\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esetLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;DEBUG\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eTurns out what I was missing was a call to \u003ccode\u003ebasicConfig\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003elogging\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# forget this and your messages are in the ether! or at least not seen in ipython...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebasicConfig\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\u003cspan class=\"n\"\u003elogger\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egetLogger\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"vm\"\u003e__name__\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\"\u003elogger\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esetLevel\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;DEBUG\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\u003ch1 id=\"bonus\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBonus\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eWant your new messages to show up while iterating on something without killing\nthe ipython kernel?\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eimportlib\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ereload\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ereload\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003elogging\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"c1\"\u003e# to make sure you get new log messages you add while developing!\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": "\n# Logging instead of printing\n\nI am trying to adopt `logger.debug` instead of `print` but ran into a confusing\nthing in ipython during Advent of Code... I riddled by script with\n`logger.debug` (yes after setting `logging.setLevel('DEBUG')`) but in ipython\nnone of my log messages showed up!\n\n```python\nimport logging\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(\"DEBUG\")\n\n```\n\nTurns out what I was missing was a call to `basicConfig`\n\n```python\nimport logging\n\n# forget this and your messages are in the ether! or at least not seen in ipython...\nlogging.basicConfig()\n\nlogger = logging.getLogger(__name__)\nlogger.setLevel(\"DEBUG\")\n```\n\n\n# Bonus\n\nWant your new messages to show up while iterating on something without killing\nthe ipython kernel?\n\n```python\nfrom importlib import reload\nreload(logging) # to make sure you get new log messages you add while developing!\n\n```\n",
      "summary": "I am trying to adopt instead of but ran into a confusing thing in ipython during Advent of Code... I riddled by script with (yes after setting ) but in...",
      "date_published": "2022-12-10T14:04:23Z",
      "date_modified": "2022-12-10T14:04:23Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/new-lines-in-markdown-tables/",
      "url": "https://pype.dev/new-lines-in-markdown-tables/",
      "title": "New lines in Markdown tables",
      "content_html": "\u003cp\u003eI wanted to break down some long lines in a Markdown table cell to make it look\nnicer on my blog but \\n didn\u0026rsquo;t do anything for me\u0026hellip; turns out \u003cbr/\u003e is the\nmagic sauce\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e\u003cem\u003eColumn 1\u003c/em\u003e\u003c/th\u003e\n\u003cth\u003e\u003cem\u003eColumn 2\u003c/em\u003e\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eKey\u003c/td\u003e\n\u003ctd\u003eDoggo ipsum many pats. Borkdrive borking doggo doing me a frighten doggorino, noodle horse heckin. what a nice floof. Pupper borking doggo you are doing me a frighten, much ruin diet.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u0026mdash;\u003c/td\u003e\n\u003ctd\u003e\u0026mdash;\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth\u003e\u003cem\u003eColumn 1\u003c/em\u003e\u003c/th\u003e\n\u003cth\u003e\u003cem\u003eColumn 2\u003c/em\u003e\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd\u003eKey\u003c/td\u003e\n\u003ctd\u003eDoggo ipsum many pats. \u003cbr/\u003e Borkdrive borking doggo doing me a frighten doggorino, noodle horse heckin. \u003cbr/\u003e what a nice floof. \u003cbr/\u003e Pupper borking doggo you are doing me a frighten, much ruin diet.\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd\u003e\u0026mdash;\u003c/td\u003e\n\u003ctd\u003e\u0026mdash;\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n",
      "content_text": "\nI wanted to break down some long lines in a Markdown table cell to make it look\nnicer on my blog but \\n didn't do anything for me... turns out \u003cbr/\u003e is the\nmagic sauce\n\n| *Column 1* | *Column 2* |\n| --- | --- |\n| Key | Doggo ipsum many pats. Borkdrive borking doggo doing me a frighten doggorino, noodle horse heckin. what a nice floof. Pupper borking doggo you are doing me a frighten, much ruin diet. |\n| --- | --- |\n\n\n| *Column 1* | *Column 2* |\n| --- | --- |\n| Key | Doggo ipsum many pats. \u003cbr/\u003e Borkdrive borking doggo doing me a frighten doggorino, noodle horse heckin. \u003cbr/\u003e what a nice floof. \u003cbr/\u003e Pupper borking doggo you are doing me a frighten, much ruin diet. |\n| --- | --- |\n",
      "summary": "I wanted to break down some long lines in a Markdown table cell to make it look nicer on my blog but \\n didn't do anything for me... turns out is the magic...",
      "date_published": "2022-11-25T13:35:05Z",
      "date_modified": "2022-11-25T13:35:05Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "webdev",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/description-of-my-proposed-vimconf-2022-talk/",
      "url": "https://pype.dev/description-of-my-proposed-vimconf-2022-talk/",
      "title": "Description of my proposed vimconf 2022 talk",
      "content_html": "\u003cp\u003eSwitching to Vim opened a whole new world to me for interacting with a computer\nand for getting things done. Before I adopted Vim I used GUIs for everything\nbecause I thought that\u0026rsquo;s how it had to be done\u0026hellip; Notes in OneNote, code using\na GUI editor, different notes in TiddlyWiki, slides for work in PowerPoint,\nslides for church using Logos, etc\u0026hellip; Adopting Vim allowed me to disconnect a\nspecific tool from the problem that tool is solving - because usually I just\nneed to write text (notes, code, slides, etc.). Now, very nearly everything I\ndo is from a text-based and git-based workflow\u0026hellip; I put all my notes on\nbasically anything just in my blog, which is all markdown and deployed to GH\nwith Markata on every push (living dangerously pushing to main) - and that\u0026rsquo;s\nall done easily from Vim with nice syntax highlighting, fast response,\nintegrated git-plugins, etc.. I keep project-specific task lists just in\nmarkdown files and I have Vim/tmux shortcuts to quickly add todos for any\nproject (todo list is done with markata todoui) and I can get there fast\nbecause my Vim workflow dovetails with Tmux nicely. Also I can pull that list\nup right from the terminal, which I\u0026rsquo;m already in because Vim\u0026hellip;. Vim also\npushed me into the cli more - because Vim is so easily extended with cli tools\nand I\u0026rsquo;m already in the terminal\u0026hellip; The builtin functionality also made things\nmake more sense - no more right-click, find \u0026ldquo;refactor all\u0026rdquo; or \u0026ldquo;rename symbol\u0026rdquo;\n(for some stupid reason)\u0026hellip; Vim find-replace is so intuitive and if I need it\nextended then I learned what sed was because of Vim. Moving quickly in Vim also\nenables me to do my job incredibly fast because I hop into several projects a\nday in a coaching role - if I was bound by GUIs I\u0026rsquo;d be waiting forever for\nstartup, would lose which GUI instance was which project, etc\u0026hellip; Being in the\nterminal also made Tmux a trivial choice - now I have 90 tmux sessions, all\nnamed appropriately, ready for me to jump back to and all while keeping the\nmajority of RAM still free for Chrome. Vim as my IDE also forced me to learn\nway more about Python (I\u0026rsquo;m a python developer primarily), how LSP works, how to\nconfigure a development environment, etc\u0026hellip; things I took for granted in my GUI\nworkflows, or never knew, or worse - thought I knew but deeply misunderstood.\nNow that I understand them better, I can coach my peers more effectively even\nif they are still in a GUI-based ecosystem.\u003c/p\u003e\n\u003cp\u003eBasically, (Neo)Vim actually did change my life and I\u0026rsquo;m really thankful for it\n(maybe that should be the title?)\u003c/p\u003e\n",
      "content_text": "\nSwitching to Vim opened a whole new world to me for interacting with a computer\nand for getting things done. Before I adopted Vim I used GUIs for everything\nbecause I thought that's how it had to be done... Notes in OneNote, code using\na GUI editor, different notes in TiddlyWiki, slides for work in PowerPoint,\nslides for church using Logos, etc... Adopting Vim allowed me to disconnect a\nspecific tool from the problem that tool is solving - because usually I just\nneed to write text (notes, code, slides, etc.). Now, very nearly everything I\ndo is from a text-based and git-based workflow... I put all my notes on\nbasically anything just in my blog, which is all markdown and deployed to GH\nwith Markata on every push (living dangerously pushing to main) - and that's\nall done easily from Vim with nice syntax highlighting, fast response,\nintegrated git-plugins, etc.. I keep project-specific task lists just in\nmarkdown files and I have Vim/tmux shortcuts to quickly add todos for any\nproject (todo list is done with markata todoui) and I can get there fast\nbecause my Vim workflow dovetails with Tmux nicely. Also I can pull that list\nup right from the terminal, which I'm already in because Vim.... Vim also\npushed me into the cli more - because Vim is so easily extended with cli tools\nand I'm already in the terminal... The builtin functionality also made things\nmake more sense - no more right-click, find \"refactor all\" or \"rename symbol\"\n(for some stupid reason)... Vim find-replace is so intuitive and if I need it\nextended then I learned what sed was because of Vim. Moving quickly in Vim also\nenables me to do my job incredibly fast because I hop into several projects a\nday in a coaching role - if I was bound by GUIs I'd be waiting forever for\nstartup, would lose which GUI instance was which project, etc... Being in the\nterminal also made Tmux a trivial choice - now I have 90 tmux sessions, all\nnamed appropriately, ready for me to jump back to and all while keeping the\nmajority of RAM still free for Chrome. Vim as my IDE also forced me to learn\nway more about Python (I'm a python developer primarily), how LSP works, how to\nconfigure a development environment, etc... things I took for granted in my GUI\nworkflows, or never knew, or worse - thought I knew but deeply misunderstood.\nNow that I understand them better, I can coach my peers more effectively even\nif they are still in a GUI-based ecosystem.\n\nBasically, (Neo)Vim actually did change my life and I'm really thankful for it\n(maybe that should be the title?)\n",
      "summary": "Switching to Vim opened a whole new world to me for interacting with a computer and for getting things done. Before I adopted Vim I used GUIs for everything...",
      "date_published": "2022-11-12T19:39:19Z",
      "date_modified": "2022-11-12T19:39:19Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "vim",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/make-a-series-of-directories-fast/",
      "url": "https://pype.dev/make-a-series-of-directories-fast/",
      "title": "Make a series of directories fast!",
      "content_html": "\u003cp\u003emkdir s{1..10} will make directories s1, s2, \u0026hellip; s10 in one command!\u003c/p\u003e\n",
      "content_text": "\nmkdir s{1..10} will make directories s1, s2, ... s10 in one command!\n",
      "summary": "mkdir s{1..10} will make directories s1, s2, ... s10 in one command!",
      "date_published": "2022-11-10T15:27:50Z",
      "date_modified": "2022-11-10T15:27:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/case-insensitive-search-in-vim/",
      "url": "https://pype.dev/case-insensitive-search-in-vim/",
      "title": "Case-insensitive search in Vim",
      "content_html": "\u003cp\u003e/mysearch\\c will match mysearch, MYSEARCH, mYSeArCh\u0026hellip;\u003c/p\u003e\n",
      "content_text": "\n/mysearch\\c will match mysearch, MYSEARCH, mYSeArCh... \n",
      "summary": "/mysearch\\c will match mysearch, MYSEARCH, mYSeArCh...",
      "date_published": "2022-10-21T06:40:21Z",
      "date_modified": "2022-10-21T06:40:21Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/limit-zfs-list-to-avoid-docker-vomit/",
      "url": "https://pype.dev/limit-zfs-list-to-avoid-docker-vomit/",
      "title": "Limit zfs list to avoid docker vomit",
      "content_html": "\u003cp\u003ezfs list has a flag -r, but if you use zfs driver for docker then you\u0026rsquo;ll get\nflooded with every docker volume in the world. zfs list -r -d N will limit the\ndept of the print out, so zfs list -r -d 2 gives me tank, tank/encrypted,\ntank/encrypted/docker -\u0026gt; but then I don\u0026rsquo;t see all the continer volumes\u003c/p\u003e\n",
      "content_text": "\nzfs list has a flag -r, but if you use zfs driver for docker then you'll get\nflooded with every docker volume in the world. zfs list -r -d N will limit the\ndept of the print out, so zfs list -r -d 2 gives me tank, tank/encrypted,\ntank/encrypted/docker -\u003e but then I don't see all the continer volumes\n",
      "summary": "zfs list has a flag -r, but if you use zfs driver for docker then you'll get flooded with every docker volume in the world. zfs list -r -d N will limit the...",
      "date_published": "2022-10-20T06:39:18Z",
      "date_modified": "2022-10-20T06:39:18Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/you-can-embed-gifs/",
      "url": "https://pype.dev/you-can-embed-gifs/",
      "title": "You can embed gifs?!",
      "content_html": "\u003cp\u003eI learned you can embed gifs and stuch in things other than GH personal pages!\u003c/p\u003e\n\u003cp\u003eSo here I\u0026rsquo;ll keep ones I like and might use later\u003c/p\u003e\n\u003ciframe src=\"https://giphy.com/embed/jXBwoFfPsCCoE\" width=\"25px\" height=\"25px\" frameBorder=\"0\" class=\"giphy-embed\" allowFullScreen\u003e\u003c/iframe\u003e\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/tech-rainbow-jXBwoFfPsCCoE\"\u003evia GIPHY\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI learned you can embed gifs and stuch in things other than GH personal pages!\n\n\nSo here I'll keep ones I like and might use later\n\n\u003ciframe src=\"https://giphy.com/embed/jXBwoFfPsCCoE\" width=\"25px\" height=\"25px\" frameBorder=\"0\" class=\"giphy-embed\" allowFullScreen\u003e\u003c/iframe\u003e\u003cp\u003e\u003ca href=\"https://giphy.com/gifs/tech-rainbow-jXBwoFfPsCCoE\"\u003evia GIPHY\u003c/a\u003e\u003c/p\u003e\n",
      "summary": "I learned you can embed gifs and stuch in things other than GH personal pages!",
      "date_published": "2022-10-09T13:39:39Z",
      "date_modified": "2022-10-09T13:39:39Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "webdev",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/dont-forget-to-load-xmp/",
      "url": "https://pype.dev/dont-forget-to-load-xmp/",
      "title": "Don’t forget to load XMP!",
      "content_html": "\u003cp\u003eBought some DDR4-3600 speed RAM but only seeing 2666? Load up the BIOS, find DRAM config or something similar, and make sure to load the XMP profile to get that advertised RAM speed!\u003c/p\u003e\n",
      "content_text": "\nBought some DDR4-3600 speed RAM but only seeing 2666? Load up the BIOS, find DRAM config or something similar, and make sure to load the XMP profile to get that advertised RAM speed!\n",
      "summary": "Bought some DDR4-3600 speed RAM but only seeing 2666? Load up the BIOS, find DRAM config or something similar, and make sure to load the XMP profile to get...",
      "date_published": "2022-10-09T07:17:43Z",
      "date_modified": "2022-10-09T07:17:43Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/my-passmark-scores/",
      "url": "https://pype.dev/my-passmark-scores/",
      "title": "My PassMark Scores",
      "content_html": "\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eBabyblue v2\u003c/p\u003e\n\u003cp\u003eRyzen 5700x + 32 GB 3200 CL16 RAM\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=503041456656\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/503041456656.png\" alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eVenus \u003c/p\u003e\n\u003cp\u003eRyzen 3200G + 16GB 3600 CL16 RAM\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502360857958\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502360857958.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eHogwarts\u003c/p\u003e\n\u003cp\u003eDual Xeon X5650 @2.67GHz | 90 GB 1883 DDR3 ECC RAM\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502361259420\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502361259420.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eServer 2 ... Change the name\u003c/p\u003e\n\u003cp\u003eRyzen 5700G + 128GB 3200 CL18 RAM\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502361424410\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502361424410.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n\u003cdiv class=\"admonition note\"\u003e\n\u003cp class=\"admonition-title\"\u003eServer 2 with XMP enabled and undervolted from 1.35 to 1.2\u003c/p\u003e\n\u003cp\u003eRyzen 5700G + 128GB 3200 CL18 RAM\u003c/p\u003e\n\u003cp\u003eUndervolted to 1.2V (from 1.35)\nXMP enabled\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502363064921\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502363064921.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\u003c/p\u003e\n\u003c/div\u003e\n",
      "content_text": "\n!!! note \"Babyblue v2\"\n\n    Ryzen 5700x + 32 GB 3200 CL16 RAM\n\n    \u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=503041456656\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/503041456656.png\" alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\n\n!!! note \"Venus \"\n\n    Ryzen 3200G + 16GB 3600 CL16 RAM\n\n    \u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502360857958\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502360857958.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\n\n!!! note \"Hogwarts\"\n\n    Dual Xeon X5650 @2.67GHz | 90 GB 1883 DDR3 ECC RAM\n\n    \u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502361259420\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502361259420.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\n\n\n!!! note \"Server 2 ... Change the name\"\n\n    Ryzen 5700G + 128GB 3200 CL18 RAM\n\n    \u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502361424410\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502361424410.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\n\n!!! note \"Server 2 with XMP enabled and undervolted from 1.35 to 1.2\"\n\n    Ryzen 5700G + 128GB 3200 CL18 RAM\n\n    Undervolted to 1.2V (from 1.35)\n    XMP enabled\n\n    \u003ca href=\"https://www.passmark.com/baselines/V10/display.php?id=502363064921\"\u003e\u003cimg src=\"https://www.passmark.com/baselines/V10/media/502363064921.png\" height=120px alt=\"PassMark Rating\" border=\"0\" /\u003e\u003c/a\u003e\n",
      "summary": "!!! note \"Babyblue v2\"",
      "date_published": "2022-10-08T18:43:29Z",
      "date_modified": "2022-10-08T18:43:29Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/suddenly-ssh-requires-a-password/",
      "url": "https://pype.dev/suddenly-ssh-requires-a-password/",
      "title": "Suddenly SSH requires a password",
      "content_html": "\u003cp\u003essh -v -i ~/.ssh/id_rsa nic@hogwarts\u003c/p\u003e\n\u003cp\u003eTHen we can look at print outs\u003c/p\u003e\n\u003cp\u003ecat /var/log/auth.log  also showed me that I had too wide permissions on files in ~/.ssh -\u0026gt; probably changed from an rsync job\u003c/p\u003e\n",
      "content_text": "\n\nssh -v -i ~/.ssh/id_rsa nic@hogwarts\n\nTHen we can look at print outs\n\ncat /var/log/auth.log  also showed me that I had too wide permissions on files in ~/.ssh -\u003e probably changed from an rsync job\n",
      "summary": "ssh -v -i ~/.ssh/id_rsa nic@hogwarts",
      "date_published": "2022-10-07T11:14:37Z",
      "date_modified": "2022-10-07T11:14:37Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/quick-setup-of-zfs-encrytped-datasets-with-sane-permissions/",
      "url": "https://pype.dev/quick-setup-of-zfs-encrytped-datasets-with-sane-permissions/",
      "title": "Quick setup of ZFS encrytped datasets with sane permissions",
      "content_html": "\u003cp\u003eAssuming you have a pool called \u003ccode\u003etank\u003c/code\u003e\u0026hellip;\u003c/p\u003e\n\u003cp\u003eAnd assuming you have an encrypted dataset (See \u003ca href=\"https://arstechnica.com/gadgets/2021/06/a-quick-start-guide-to-openzfs-native-encryption/\"\u003eJim Saltar\u0026rsquo;s short\nintro\u003c/a\u003e)\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eCreate a group for permissions - in my case I have one called \u003ccode\u003ehome\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eThen if there\u0026rsquo;s anything in \u003ccode\u003e/tank/encrypted\u003c/code\u003e his it with \u003ccode\u003echgrp -R home /tank/encrypted\u003c/code\u003e to give the \u003ccode\u003ehome\u003c/code\u003e group ownership\u003c/li\u003e\n\u003cli\u003eNext we need to make sure that the members of \u003ccode\u003ehome\u003c/code\u003e can do the writing\u0026hellip;\nso \u003ccode\u003echmod 775 -R /tank/encrypted\u003c/code\u003e will do the trick\u003c/li\u003e\n\u003cli\u003eFinally we want to make sure that all data created inside our dataset has\nthe same set of permissions with \u003ccode\u003echmod g+s /tank/encrypted\u003c/code\u003e and \u003ccode\u003echmod g+w /tank/encrypted\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\nAssuming you have a pool called `tank`...\n\nAnd assuming you have an encrypted dataset (See [Jim Saltar's short\nintro](https://arstechnica.com/gadgets/2021/06/a-quick-start-guide-to-openzfs-native-encryption/))\n\n1. Create a group for permissions - in my case I have one called `home`\n2. Then if there's anything in `/tank/encrypted` his it with `chgrp -R home\n   /tank/encrypted` to give the `home` group ownership\n3. Next we need to make sure that the members of `home` can do the writing...\n   so `chmod 775 -R /tank/encrypted` will do the trick\n4. Finally we want to make sure that all data created inside our dataset has\n   the same set of permissions with `chmod g+s /tank/encrypted` and `chmod g+w\n   /tank/encrypted`\n\n",
      "summary": "Assuming you have a pool called ...",
      "date_published": "2022-10-06T19:58:01Z",
      "date_modified": "2022-10-06T19:58:01Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/check-your-bios-version-on-ubuntu/",
      "url": "https://pype.dev/check-your-bios-version-on-ubuntu/",
      "title": "Check Your BIOS Version On Ubuntu",
      "content_html": "\u003cp\u003esudo dmidecode -s bios-version\u003c/p\u003e\n",
      "content_text": "\nsudo dmidecode -s bios-version\n",
      "summary": "sudo dmidecode -s bios-version",
      "date_published": "2022-10-02T18:21:58Z",
      "date_modified": "2022-10-02T18:21:58Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/destroying-tmux-sessions-with-fzf/",
      "url": "https://pype.dev/destroying-tmux-sessions-with-fzf/",
      "title": "Destroying Tmux sessions with fzf",
      "content_html": "\u003cp\u003eI use Tmux and Vim for most of my workflow, but I end up with a lot of dangling\ntmux sessions that dont\u0026rsquo; really need to persist\u0026hellip; but killing them one at a\ntime is a pain so I wrote a little script-kitty nonsense to pipe multiple\nchoices from fzf into \u003ccode\u003etmux kill-session\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eI defined a little function in my \u003ccode\u003e.zshrc\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edestroy\u003cspan class=\"o\"\u003e()\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    tmux list-sessions -F \u003cspan class=\"s1\"\u003e\u0026#39;#{session_name}\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e|\u003c/span\u003e fzf -m \u003cspan class=\"p\"\u003e|\u003c/span\u003e xargs -d \u003cspan class=\"s1\"\u003e$\u0026#39;\\n\u0026#39;\u003c/span\u003e sh -c \u003cspan class=\"s1\"\u003e\u0026#39;echo \u0026#34;killing $0\u0026#34;; tmux kill-session -t \u0026#34;$0\u0026#34;; for arg;do echo \u0026#34;killing $arg\u0026#34;;tmux kill-session -t \u0026#34;$arg\u0026#34;; done\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebindkey -s \u003cspan class=\"s1\"\u003e\u0026#39;^d\u0026#39;\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;destroy \\n\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003etmux list-sessions -F '#{session_name}' \u003c/code\u003e prints all my active tmux sessions to the console with the format of just their name\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epype.dev   main   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003epype.dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e proxy\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ tmux list-sessions -F \u003cspan class=\"s1\"\u003e\u0026#39;#{session_name}\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esession-01\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esession-02\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esession-03\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\u003cp\u003ePipe that to \u003ccode\u003efzf -m\u003c/code\u003e to allow multiple choices to be made using tab\u003c/p\u003e\n\u003cp\u003eThen the nasty bit in \u003ccode\u003exargs\u003c/code\u003e\u0026hellip; I echo \u003ccode\u003ekilling @0\u003c/code\u003e and \u003ccode\u003ekilling $arg\u003c/code\u003e because the \u003ccode\u003esh -c\u003c/code\u003e passes the first tmux session name to \u003ccode\u003e@0\u003c/code\u003e (it\u0026rsquo;s just what bash does) and then the rest get handled in the for loop.\u003c/p\u003e\n\u003cp\u003eBasically then I get an fzf list to choose multiple tmux sessions to destroy to clean up some RAM!\u003c/p\u003e\n",
      "content_text": "\nI use Tmux and Vim for most of my workflow, but I end up with a lot of dangling\ntmux sessions that dont' really need to persist... but killing them one at a\ntime is a pain so I wrote a little script-kitty nonsense to pipe multiple\nchoices from fzf into `tmux kill-session`\n\nI defined a little function in my `.zshrc`\n```bash \ndestroy() { \n    tmux list-sessions -F '#{session_name}' | fzf -m | xargs -d $'\\n' sh -c 'echo \"killing $0\"; tmux kill-session -t \"$0\"; for arg;do echo \"killing $arg\";tmux kill-session -t \"$arg\"; done'\n}\nbindkey -s '^d' 'destroy \\n'\n```\n\n`tmux list-sessions -F '#{session_name}' ` prints all my active tmux sessions to the console with the format of just their name\n\n```bash \npype.dev   main   ×1 via   v3.8.11(pype.dev)  on  (us-east-1) proxy\n❯ tmux list-sessions -F '#{session_name}'\nsession-01\nsession-02\nsession-03\n...\n```\n\nPipe that to `fzf -m` to allow multiple choices to be made using tab\n\nThen the nasty bit in `xargs`... I echo `killing @0` and `killing $arg` because the `sh -c` passes the first tmux session name to `@0` (it's just what bash does) and then the rest get handled in the for loop.\n\nBasically then I get an fzf list to choose multiple tmux sessions to destroy to clean up some RAM!\n",
      "summary": "I use Tmux and Vim for most of my workflow, but I end up with a lot of dangling tmux sessions that dont' really need to persist... but killing them one at a...",
      "date_published": "2022-09-15T10:27:58Z",
      "date_modified": "2022-09-15T10:27:58Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "cli",
        "bash",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/trick-to-login-to-web-server-on-another-box-on-lan/",
      "url": "https://pype.dev/trick-to-login-to-web-server-on-another-box-on-lan/",
      "title": "Trick to login to web server on another box on lan",
      "content_html": "\u003cp\u003eTODO: change title - add \u0026ndash;listen or \u0026ndash;host or something as 0.0.0.0\u0026hellip; that way it listens not on localhost or 127.0.0.1, but on all addresses!\u003c/p\u003e\n",
      "content_text": "\nTODO: change title - add --listen or --host or something as 0.0.0.0... that way it listens not on localhost or 127.0.0.1, but on all addresses!\n",
      "summary": "TODO: change title - add --listen or --host or something as 0.0.0.0... that way it listens not on localhost or 127.0.0.1, but on all addresses!",
      "date_published": "2022-09-12T18:48:45Z",
      "date_modified": "2022-09-12T18:48:45Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/check-your-smart-status-with-smartctl/",
      "url": "https://pype.dev/check-your-smart-status-with-smartctl/",
      "title": "Check your SMART status with smartctl",
      "content_html": "\u003cp\u003e\u003ca href=\"https://www.simplified.guide/linux/disk-health-check\"\u003ehttps://www.simplified.guide/linux/disk-health-check\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"install\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eInstall\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eFor ubuntu/debian based distros (which is what I primarly use presently)\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esudo apt update -y \u0026amp;\u0026amp; sudo apt install smartmontools -y\u003c/code\u003e\u003c/p\u003e\n\u003ch1 id=\"list-hard-drives\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eList hard drives\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003elsblk | grep disk\u003c/code\u003e is one way or \u003ccode\u003esudo lshw -c disk\u003c/code\u003e is another\u003c/p\u003e\n\u003ch1 id=\"smartctl\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003esmartctl\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eUse a device\u0026rsquo;s logical name such as \u003ccode\u003edev/sda\u003c/code\u003e, not a partition of the disk\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esudo smartctl -t short /dev/sda\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edotfiles   home   ×3  ×2  ×2 via   v3.10.6(dotfiles)  took 11s\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e❯ sudo smartctl -t short /dev/sda\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esmartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.15.0-48-generic] (local build)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eCopyright (C) 2002-19, Bruce Allen, Christian Franke, www.smartmontools.org\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eSending command: \u0026#34;Execute SMART Short self-test routine immediately in off-line mode\u0026#34;.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eDrive command \u0026#34;Execute SMART Short self-test routine immediately in off-line mode\u0026#34; successful.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eTesting has begun.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003ePlease wait 2 minutes for test to complete.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eTest will complete after Fri Sep 23 05:59:39 2022 CDT\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eUse smartctl -X to abort test.\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch1 id=\"check-status\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003echeck status\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003esudo smartctl -H /dev/sda\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003edotfiles   home   ×3  ×2  ×2 via   v3.10.6(dotfiles)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e❯ sudo smartctl -H /dev/sda\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esmartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.15.0-48-generic] (local build)\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eCopyright (C) 2002-19, Bruce Allen, Christian Franke, www.smartmontools.org\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e=== START OF READ SMART DATA SECTION ===\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003eSMART overall-health self-assessment test result: PASSED\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nhttps://www.simplified.guide/linux/disk-health-check\n\n# Install\nFor ubuntu/debian based distros (which is what I primarly use presently)\n\n`sudo apt update -y \u0026\u0026 sudo apt install smartmontools -y`\n\n# List hard drives\n\n`lsblk | grep disk` is one way or `sudo lshw -c disk` is another\n\n# smartctl \n\nUse a device's logical name such as `dev/sda`, not a partition of the disk\n\n`sudo smartctl -t short /dev/sda`\n\n```console\ndotfiles   home   ×3  ×2  ×2 via   v3.10.6(dotfiles)  took 11s\n❯ sudo smartctl -t short /dev/sda\nsmartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.15.0-48-generic] (local build)\nCopyright (C) 2002-19, Bruce Allen, Christian Franke, www.smartmontools.org\n\n=== START OF OFFLINE IMMEDIATE AND SELF-TEST SECTION ===\nSending command: \"Execute SMART Short self-test routine immediately in off-line mode\".\nDrive command \"Execute SMART Short self-test routine immediately in off-line mode\" successful.\nTesting has begun.\nPlease wait 2 minutes for test to complete.\nTest will complete after Fri Sep 23 05:59:39 2022 CDT\nUse smartctl -X to abort test.\n```\n\n# check status\n\n`sudo smartctl -H /dev/sda`\n\n```console\n\ndotfiles   home   ×3  ×2  ×2 via   v3.10.6(dotfiles)\n❯ sudo smartctl -H /dev/sda\nsmartctl 7.1 2019-12-30 r5022 [x86_64-linux-5.15.0-48-generic] (local build)\nCopyright (C) 2002-19, Bruce Allen, Christian Franke, www.smartmontools.org\n\n=== START OF READ SMART DATA SECTION ===\nSMART overall-health self-assessment test result: PASSED\n\n\n```\n",
      "summary": "https://www.simplified.guide/linux/disk-health-check",
      "date_published": "2022-08-29T06:30:27Z",
      "date_modified": "2022-08-29T06:30:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "cli",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/benchmark-your-disks-with-fio/",
      "url": "https://pype.dev/benchmark-your-disks-with-fio/",
      "title": "Benchmark your disks with fio",
      "content_html": "\u003ch1 id=\"intro\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIntro\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI use ZFS at home in my homelab for basically all of my storage\u0026hellip; Docker uses\nZFS backend, all my VMs have their \u003ccode\u003e.qcow2\u003c/code\u003e images in their own zfs datasets,\nand all my shares are ZFS datasets. I love ZFS but my home hardware presently\nis the opposite of expensive or new\u0026hellip; Thankfully I\u0026rsquo;ve had a lot of my orginal\nhomelab simply given to me but the cost of this is that I didn\u0026rsquo;t put my\nmachines together, I didn\u0026rsquo;t choose the disks, and I definitely didn\u0026rsquo;t do the\nresearch I would\u0026rsquo;ve otherwise done had I bankrolled my server personally\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"the-problem\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Problem\u003c/span\u003e \u003ca href=\"#the-problem\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI run \u003ccode\u003eglances\u003c/code\u003e on basically all my machines and for the longest time I have\nbeen seeing big time \u003ccode\u003eiowait\u003c/code\u003e issues. Now, since everything was free I\u0026rsquo;ve\nlargely been able to ignore that however I\u0026rsquo;m now after some better performance\nwhich I think means new hardware!\u003c/p\u003e\n\u003cp\u003eHere is a random screenshot of my glances homepage at time of writing - The\nonly major load on my server is some \u003ccode\u003effmpeg\u003c/code\u003e transcoding (about 60% CPU\nutilization)\u0026hellip;\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/glances-iowait.png\" alt=\"glances\" title=\"glances with iowait\" /\u003e\n\u003cp\u003eAs you can see\u0026hellip; there\u0026rsquo;s a lot of issues and \u003cem\u003eI don\u0026rsquo;t even know what they mean\u003c/em\u003e.\u003c/p\u003e\n\u003ch1 id=\"fio\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003efio\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI heard about \u003ca href=\"https://fio.readthedocs.io/en/latest/\"\u003efio\u003c/a\u003e through a friend and\ndecided to try it out quick. It installs with \u003ccode\u003eapt\u003c/code\u003e on ubuntu quick and easy\u0026hellip;\u003c/p\u003e\n\u003cp\u003eJim Saltar has a good blog post on it \u003ca href=\"https://arstechnica.com/gadgets/2020/02/how-fast-are-your-disks-find-out-the-open-source-way-with-fio/\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eBasically it\u0026rsquo;s a handy tool for benchmarking your disks and the blog dives into\nwhat types of metrics matter - it\u0026rsquo;s not just throughput, but also latency,\niops, etc.\u003c/p\u003e\n\u003ch2 id=\"tests\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTests\u003c/span\u003e \u003ca href=\"#tests\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI ran a few basic commands inside a new zfs dataset on my server \u003ccode\u003etank/fio\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efio --name\u003cspan class=\"o\"\u003e=\u003c/span\u003erandom-write --ioengine\u003cspan class=\"o\"\u003e=\u003c/span\u003eposixaio --rw\u003cspan class=\"o\"\u003e=\u003c/span\u003erandwrite --bs\u003cspan class=\"o\"\u003e=\u003c/span\u003e4k --size\u003cspan class=\"o\"\u003e=\u003c/span\u003e4g --numjobs\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e --runtime\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e60\u003c/span\u003e --time_based --end_fsync\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e \u0026gt; single-4KiB-random-write.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efio --name\u003cspan class=\"o\"\u003e=\u003c/span\u003erandom-write --ioengine\u003cspan class=\"o\"\u003e=\u003c/span\u003eposixaio --rw\u003cspan class=\"o\"\u003e=\u003c/span\u003erandwrite --bs\u003cspan class=\"o\"\u003e=\u003c/span\u003e64k --size\u003cspan class=\"o\"\u003e=\u003c/span\u003e256m --numjobs\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e16\u003c/span\u003e --iodepth\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e16\u003c/span\u003e --runtime\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e60\u003c/span\u003e --time_based --end_fsync\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e \u0026gt; 16-parallel-64KiB-random-write.txt\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efio --name\u003cspan class=\"o\"\u003e=\u003c/span\u003erandom-write --ioengine\u003cspan class=\"o\"\u003e=\u003c/span\u003eposixaio --rw\u003cspan class=\"o\"\u003e=\u003c/span\u003erandwrite --bs\u003cspan class=\"o\"\u003e=\u003c/span\u003e1m --size\u003cspan class=\"o\"\u003e=\u003c/span\u003e16g --numjobs\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e --iodepth\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e --runtime\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e60\u003c/span\u003e --time_based --end_fsync\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"m\"\u003e1\u003c/span\u003e \u0026gt; single-1MiB-random-write.txt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"results\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eResults\u003c/span\u003e \u003ca href=\"#results\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe single 4 KiB random write:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eWRITE: bw=7836KiB/s (8024kB/s), 7836KiB/s-7836KiB/s (8024kB/s-8024kB/s), io=523MiB (548MB), run=68317-68317msec\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe 16 parallel 64KiB random writes:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eWRITE: bw=93.9MiB/s (98.4MB/s), 5599KiB/s-6303KiB/s (5734kB/s-6454kB/s), io=7642MiB (8013MB), run=81310-81418msec\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe single 1MiB random write:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eWRITE: bw=81.2MiB/s (85.1MB/s), 81.2MiB/s-81.2MiB/s (85.1MB/s-85.1MB/s), io=8177MiB (8574MB), run=100699-100699msec\u003c/code\u003e\u003c/p\u003e\n\u003ch1 id=\"summary\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSummary\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eSo I don\u0026rsquo;t fully understand these numbers yet\u0026hellip; 80-100 MiB/s isn\u0026rsquo;t super fast\nand that\u0026rsquo;s across a parallelized workload\u0026hellip; The single threaded workloads have\nawful performance so this tells me something is wrong\u0026hellip; I have a few ideas\u0026hellip;\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eZFS dataset config options such as \u003ccode\u003eashift\u003c/code\u003e or the blocksize might be way misconfigured\u003c/li\u003e\n\u003cli\u003eThe disks/pool which came from a TrueNAS/FreeBSD machine may have some artifacts that I need to clean up\u003c/li\u003e\n\u003cli\u003eThe SAS controller I am using, which I flashed with IT firmware to get it into JBOD mode might be messed up\u003c/li\u003e\n\u003cli\u003eThe data cables themselves could be a problem\u0026hellip;\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003ePoints 3 and 4 are less likely given that the write speed does increase in the parallelized job but I\u0026rsquo;m a newbie so it\u0026rsquo;s time to dive in!\u003c/p\u003e\n",
      "content_text": "\n# Intro\n\nI use ZFS at home in my homelab for basically all of my storage... Docker uses\nZFS backend, all my VMs have their `.qcow2` images in their own zfs datasets,\nand all my shares are ZFS datasets. I love ZFS but my home hardware presently\nis the opposite of expensive or new... Thankfully I've had a lot of my orginal\nhomelab simply given to me but the cost of this is that I didn't put my\nmachines together, I didn't choose the disks, and I definitely didn't do the\nresearch I would've otherwise done had I bankrolled my server personally... \n\n## The Problem\n\nI run `glances` on basically all my machines and for the longest time I have\nbeen seeing big time `iowait` issues. Now, since everything was free I've\nlargely been able to ignore that however I'm now after some better performance\nwhich I think means new hardware!\n\nHere is a random screenshot of my glances homepage at time of writing - The\nonly major load on my server is some `ffmpeg` transcoding (about 60% CPU\nutilization)...\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/glances-iowait.png\" alt=\"glances\" title=\"glances with iowait\" /\u003e\n\n\nAs you can see... there's a lot of issues and _I don't even know what they mean_.\n\n# fio\n\nI heard about [fio](https://fio.readthedocs.io/en/latest/) through a friend and\ndecided to try it out quick. It installs with `apt` on ubuntu quick and easy...\n\nJim Saltar has a good blog post on it [here](https://arstechnica.com/gadgets/2020/02/how-fast-are-your-disks-find-out-the-open-source-way-with-fio/)\n\nBasically it's a handy tool for benchmarking your disks and the blog dives into\nwhat types of metrics matter - it's not just throughput, but also latency,\niops, etc.\n\n## Tests\n\nI ran a few basic commands inside a new zfs dataset on my server `tank/fio`\n\n```bash\nfio --name=random-write --ioengine=posixaio --rw=randwrite --bs=4k --size=4g --numjobs=1 --runtime=60 --time_based --end_fsync=1 \u003e single-4KiB-random-write.txt\nfio --name=random-write --ioengine=posixaio --rw=randwrite --bs=64k --size=256m --numjobs=16 --iodepth=16 --runtime=60 --time_based --end_fsync=1 \u003e 16-parallel-64KiB-random-write.txt\nfio --name=random-write --ioengine=posixaio --rw=randwrite --bs=1m --size=16g --numjobs=1 --iodepth=1 --runtime=60 --time_based --end_fsync=1 \u003e single-1MiB-random-write.txt\n```\n\n## Results\n\nThe single 4 KiB random write:\n\n`WRITE: bw=7836KiB/s (8024kB/s), 7836KiB/s-7836KiB/s (8024kB/s-8024kB/s), io=523MiB (548MB), run=68317-68317msec`\n\nThe 16 parallel 64KiB random writes:\n\n`WRITE: bw=93.9MiB/s (98.4MB/s), 5599KiB/s-6303KiB/s (5734kB/s-6454kB/s), io=7642MiB (8013MB), run=81310-81418msec`\n\nThe single 1MiB random write:\n\n`WRITE: bw=81.2MiB/s (85.1MB/s), 81.2MiB/s-81.2MiB/s (85.1MB/s-85.1MB/s), io=8177MiB (8574MB), run=100699-100699msec`\n\n# Summary\n\nSo I don't fully understand these numbers yet... 80-100 MiB/s isn't super fast\nand that's across a parallelized workload... The single threaded workloads have\nawful performance so this tells me something is wrong... I have a few ideas...\n\n1. ZFS dataset config options such as `ashift` or the blocksize might be way misconfigured\n2. The disks/pool which came from a TrueNAS/FreeBSD machine may have some artifacts that I need to clean up\n3. The SAS controller I am using, which I flashed with IT firmware to get it into JBOD mode might be messed up\n4. The data cables themselves could be a problem...\n\nPoints 3 and 4 are less likely given that the write speed does increase in the parallelized job but I'm a newbie so it's time to dive in!\n",
      "summary": "I use ZFS at home in my homelab for basically all of my storage... Docker uses ZFS backend, all my VMs have their images in their own zfs datasets, and all...",
      "date_published": "2022-08-27T13:43:22Z",
      "date_modified": "2022-08-27T13:43:22Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "zfs",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/playing-with-mdformat/",
      "url": "https://pype.dev/playing-with-mdformat/",
      "title": "Playing with mdformat",
      "content_html": "\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ethis\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;what is taking so long black!!\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": "```python\nimport this; print(this); print(\"what is taking so long black!!\")\n```\n",
      "date_published": "2022-08-23T06:56:15Z",
      "date_modified": "2022-08-23T06:56:15Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "vim",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/paperless-ngx-filtering-on-ids-instead-of-values/",
      "url": "https://pype.dev/paperless-ngx-filtering-on-ids-instead-of-values/",
      "title": "Paperless-NGX filtering on IDs instead of values",
      "content_html": "\u003cp\u003eTo filter for saved views from the admin console you have to use the \u003ccode\u003eid\u003c/code\u003e of\nthe tag you are using to filter on\u0026hellip;\u003c/p\u003e\n\u003cp\u003eEx: filtering for tags \u0026ldquo;Inbox\u0026rdquo; doesn\u0026rsquo;t work, but for tag 18 does (the id fo the\nInbox tag). I can find that ID by clicking on the tag and looking at the url\nbar\u003c/p\u003e\n",
      "content_text": "\nTo filter for saved views from the admin console you have to use the `id` of\nthe tag you are using to filter on...\n\nEx: filtering for tags \"Inbox\" doesn't work, but for tag 18 does (the id fo the\nInbox tag). I can find that ID by clicking on the tag and looking at the url\nbar\n",
      "summary": "To filter for saved views from the admin console you have to use the of the tag you are using to filter on...",
      "date_published": "2022-08-09T08:28:51Z",
      "date_modified": "2022-08-09T08:28:51Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/mounting-exfat-usb-in-linux/",
      "url": "https://pype.dev/mounting-exfat-usb-in-linux/",
      "title": "Mounting exFAT USB in Linux",
      "content_html": "\u003ch1 id=\"steps\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSteps\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003esudo fdisk -l\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003ethen look for the device and partition\u003c/p\u003e\n\u003cp\u003eget the Type column\u003c/p\u003e\n\u003cp\u003emount\u003c/p\u003e\n\u003ch2 id=\"example\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eExample\u003c/span\u003e \u003ca href=\"#example\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cpre\u003e\u003ccode\u003e\ndumbledore in /media  NO PYTHON VENV SET \n❯ sudo fdisk -l\n\n...\n\nDevice     Boot    Start      End  Sectors  Size Id Type\n/dev/sdk1  *        2048 60371951 60369904 28.8G  7 HPFS/NTFS/exFAT\n/dev/sdk2       60371952 60437487    65536   32M ef EFI (FAT-12/16/32)\n\n\ndumbledore in /media  NO PYTHON VENV SET \n❯ sudo mount -t ntfs /dev/sdk1 /media/ventoy-usb -o uid=1000                       \nNTFS signature is missing.\nFailed to mount '/dev/sdk1': Invalid argument\nThe device '/dev/sdk1' doesn't seem to have a valid NTFS.\nMaybe the wrong device is used? Or the whole disk instead of a\npartition (e.g. /dev/sda, not /dev/sda1)? Or the other way around?\n\ndumbledore in /media  NO PYTHON VENV SET \n✗ sudo mount -t exfat /dev/sdk1 /media/ventoy-usb -o uid=1000\n\n\u003c/code\u003e\u003c/pre\u003e\n",
      "content_text": "\n# Steps\n\n`sudo fdisk -l`\n\nthen look for the device and partition\n\nget the Type column\n\nmount\n\n## Example\n\n```\n\ndumbledore in /media  NO PYTHON VENV SET \n❯ sudo fdisk -l\n\n...\n\nDevice     Boot    Start      End  Sectors  Size Id Type\n/dev/sdk1  *        2048 60371951 60369904 28.8G  7 HPFS/NTFS/exFAT\n/dev/sdk2       60371952 60437487    65536   32M ef EFI (FAT-12/16/32)\n\n\ndumbledore in /media  NO PYTHON VENV SET \n❯ sudo mount -t ntfs /dev/sdk1 /media/ventoy-usb -o uid=1000                       \nNTFS signature is missing.\nFailed to mount '/dev/sdk1': Invalid argument\nThe device '/dev/sdk1' doesn't seem to have a valid NTFS.\nMaybe the wrong device is used? Or the whole disk instead of a\npartition (e.g. /dev/sda, not /dev/sda1)? Or the other way around?\n\ndumbledore in /media  NO PYTHON VENV SET \n✗ sudo mount -t exfat /dev/sdk1 /media/ventoy-usb -o uid=1000\n\n```\n",
      "date_published": "2022-07-31T07:38:43Z",
      "date_modified": "2022-07-31T07:38:43Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/xrdp-authentication-required-to-create-managed-color-device/",
      "url": "https://pype.dev/xrdp-authentication-required-to-create-managed-color-device/",
      "title": "Xrdp-Authentication-Required-To-Create-Managed-Color-Device",
      "content_html": "\u003cp\u003eI just need to RDP into an Ubuntu box via Remmina and everytime I login I have\nto authenticate to create a color managed device\u0026hellip; which I don\u0026rsquo;t even know\nwhat that is!\u003c/p\u003e\n\u003cp\u003eTo fix it?\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003evim /etc/polkit-1/localauthority.conf.d/02-allow-colord.conf\u003c/code\u003e\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003epolkit.addRule(function(action, subject) {\n if ((action.id == \u0026quot;org.freedesktop.color-manager.create-device\u0026quot; ||\n action.id == \u0026quot;org.freedesktop.color-manager.create-profile\u0026quot; ||\n action.id == \u0026quot;org.freedesktop.color-manager.delete-device\u0026quot; ||\n action.id == \u0026quot;org.freedesktop.color-manager.delete-profile\u0026quot; ||\n action.id == \u0026quot;org.freedesktop.color-manager.modify-device\u0026quot; ||\n action.id == \u0026quot;org.freedesktop.color-manager.modify-profile\u0026quot;) \u0026amp;\u0026amp;\n subject.isInGroup(\u0026quot;{users}\u0026quot;)) {\n return polkit.Result.YES;\n }\n });\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eGive\u0026rsquo;r the \u003ccode\u003e:x\u003c/code\u003e, logout, and you\u0026rsquo;re good to go\u003c/p\u003e\n",
      "content_text": "\nI just need to RDP into an Ubuntu box via Remmina and everytime I login I have\nto authenticate to create a color managed device... which I don't even know\nwhat that is!\n\n\nTo fix it?\n\n`vim /etc/polkit-1/localauthority.conf.d/02-allow-colord.conf`\n\n```\npolkit.addRule(function(action, subject) {\n if ((action.id == \"org.freedesktop.color-manager.create-device\" ||\n action.id == \"org.freedesktop.color-manager.create-profile\" ||\n action.id == \"org.freedesktop.color-manager.delete-device\" ||\n action.id == \"org.freedesktop.color-manager.delete-profile\" ||\n action.id == \"org.freedesktop.color-manager.modify-device\" ||\n action.id == \"org.freedesktop.color-manager.modify-profile\") \u0026\u0026\n subject.isInGroup(\"{users}\")) {\n return polkit.Result.YES;\n }\n });\n```\n\nGive'r the `:x`, logout, and you're good to go\n",
      "summary": "I just need to RDP into an Ubuntu box via Remmina and everytime I login I have to authenticate to create a color managed device... which I don't even know...",
      "date_published": "2022-07-18T00:00:00Z",
      "date_modified": "2022-07-18T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/setup-kvm-to-boot-from-local-pxe-server/",
      "url": "https://pype.dev/setup-kvm-to-boot-from-local-pxe-server/",
      "title": "Setup KVM to boot from local PXE server",
      "content_html": "\u003cp\u003eAssuming you have a PXE server running you only need small edits to the virt-manager config\u003c/p\u003e\n",
      "content_text": "\nAssuming you have a PXE server running you only need small edits to the virt-manager config\n",
      "summary": "Assuming you have a PXE server running you only need small edits to the virt-manager config",
      "date_published": "2022-07-06T12:30:50Z",
      "date_modified": "2022-07-06T12:30:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/self-hosted-docker-registry-with-proxy-pull-through/",
      "url": "https://pype.dev/self-hosted-docker-registry-with-proxy-pull-through/",
      "title": "Self-hosted Docker registry with proxy pull through",
      "content_html": "\u003cp\u003eI decided that I want to self-host all my docker images for the purposes of\nregularly rebuilding and security scanning. The first step is to set up a\nregistry, which coincidently enough you can do with a Docker container 😛!\u003c/p\u003e\n\u003cp\u003eInstructions for setting up the proxy are on the offidical docker docs \u003ca href=\"https://docs.docker.com/registry/recipes/mirror/\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"deployment\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDeployment\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI chose to deploy with Portainer because I already use it for monitoring all my\ndocker containers. I deploy most container with Ansible but use Portainer to\nsetup a few more adhoc or non-git driven applications.\u003c/p\u003e\n",
      "content_text": "\nI decided that I want to self-host all my docker images for the purposes of\nregularly rebuilding and security scanning. The first step is to set up a\nregistry, which coincidently enough you can do with a Docker container 😛!\n\nInstructions for setting up the proxy are on the offidical docker docs [here](https://docs.docker.com/registry/recipes/mirror/)\n\n# Deployment\n\nI chose to deploy with Portainer because I already use it for monitoring all my\ndocker containers. I deploy most container with Ansible but use Portainer to\nsetup a few more adhoc or non-git driven applications.\n",
      "summary": "I decided that I want to self-host all my docker images for the purposes of regularly rebuilding and security scanning. The first step is to set up a...",
      "date_published": "2022-07-06T06:44:35Z",
      "date_modified": "2022-07-06T06:44:35Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "zfs",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/kvm-network-interface-via-nat-ubuntu-20/",
      "url": "https://pype.dev/kvm-network-interface-via-nat-ubuntu-20/",
      "title": "kvm-network-interface-via-nat-ubuntu-20",
      "content_html": "\u003cp\u003eI have started using VMs more and more in my development workflow and it\u0026rsquo;s\nimpossible to work in a VM without an internet connection for me most of the\ntime. Setting up the KVM networking is kind of confusing to me and I\u0026rsquo;ve done it\ntwo different ways. Here is how I set it up on my home desktop using NAT.\u003c/p\u003e\n\u003ch1 id=\"credit\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCredit\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eFirst thing\u0026rsquo;s first: \u003ca href=\"https://computingforgeeks.com/managing-kvm-network-interfaces-in-linux/\"\u003ecredit to this post\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"commands\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCommands\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eThere was a \u003ccode\u003edefault\u003c/code\u003e network already made by virt-manager but my VM couldn\u0026rsquo;t connect over it at all\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThese commands got me up and running without even turning the VM off\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI went full on \u003ccode\u003esudo -i\u003c/code\u003e for this just to make it easier - be careful\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"dump-an-existint-network-config\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDump an existint network config\u003c/span\u003e \u003ca href=\"#dump-an-existint-network-config\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# as root\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\"\u003evirsh net-dumpxml default \u0026gt; br1.xml\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\"\u003evim br1.xml\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\u003ch2 id=\"edit-it\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eEdit it\u003c/span\u003e \u003ca href=\"#edit-it\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI was unsure what the ip range should be so I just stuck with the original blog.\nThe \u003ccode\u003edefault\u003c/code\u003e network had the CIDR block defined as \u003ccode\u003e192.168.122.0/24\u003c/code\u003e which is different from my home network so I guess it\u0026rsquo;s fine?\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;network\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;name\u0026gt;\u003c/span\u003ebr1\u003cspan class=\"nt\"\u003e\u0026lt;/name\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;forward\u003c/span\u003e \u003cspan class=\"na\"\u003emode=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;nat\u0026#39;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;nat\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;port\u003c/span\u003e \u003cspan class=\"na\"\u003estart=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;1024\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003eend=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;65535\u0026#39;\u003c/span\u003e\u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/nat\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;/forward\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;bridge\u003c/span\u003e \u003cspan class=\"na\"\u003ename=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;br1\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003estp=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;on\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003edelay=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;0\u0026#39;\u003c/span\u003e\u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;ip\u003c/span\u003e \u003cspan class=\"na\"\u003eaddress=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;192.168.10.1\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003enetmask=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;255.255.255.0\u0026#39;\u003c/span\u003e\u003cspan class=\"nt\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;dhcp\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"nt\"\u003e\u0026lt;range\u003c/span\u003e \u003cspan class=\"na\"\u003estart=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;192.168.10.10\u0026#39;\u003c/span\u003e \u003cspan class=\"na\"\u003eend=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#39;192.168.10.100\u0026#39;\u003c/span\u003e\u003cspan class=\"nt\"\u003e/\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nt\"\u003e\u0026lt;/dhcp\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003e\u0026lt;/ip\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003e\u0026lt;/network\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"define-a-network\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDefine a network\u003c/span\u003e \u003ca href=\"#define-a-network\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evirsh net-define br1.xml\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evirsh net-autostart br1\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThen to check\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003evirsh net-list --all\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 Name      State    Autostart   Persistent\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 br1       active   yes         yes\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e default   active   yes         yes\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"uuid\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUUID\u003c/span\u003e \u003ca href=\"#uuid\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003evirsh net-uuid br1\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"magic\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMagic\u003c/span\u003e \u003ca href=\"#magic\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003evirsh attach-interface --domain \u0026lt;NAME OF VM\u0026gt; --type bridge --source br1 --model virtio --config --live\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eMy VM, \u003ccode\u003eubuntu20.04\u003c/code\u003e was running and immediately connected to the newly attached device!\u003c/p\u003e\n\u003ch1 id=\"credit-again\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCredit again\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eVisit the original post for more details - this serves more as as a quicker set of notes for future me\u003c/p\u003e\n",
      "content_text": "\nI have started using VMs more and more in my development workflow and it's\nimpossible to work in a VM without an internet connection for me most of the\ntime. Setting up the KVM networking is kind of confusing to me and I've done it\ntwo different ways. Here is how I set it up on my home desktop using NAT.\n\n# Credit\n\nFirst thing's first: [credit to this post](https://computingforgeeks.com/managing-kvm-network-interfaces-in-linux/)\n\n# Commands\n\nThere was a `default` network already made by virt-manager but my VM couldn't connect over it at all...\n\nThese commands got me up and running without even turning the VM off\n\n\u003e I went full on `sudo -i` for this just to make it easier - be careful\n\n## Dump an existint network config\n\n```bash\n# as root\n\nvirsh net-dumpxml default \u003e br1.xml\n\nvim br1.xml\n\n```\n\n## Edit it\n\nI was unsure what the ip range should be so I just stuck with the original blog. \nThe `default` network had the CIDR block defined as `192.168.122.0/24` which is different from my home network so I guess it's fine?\n\n```xml\n\u003cnetwork\u003e\n  \u003cname\u003ebr1\u003c/name\u003e\n  \u003cforward mode='nat'\u003e\n    \u003cnat\u003e\n      \u003cport start='1024' end='65535'/\u003e\n    \u003c/nat\u003e\n  \u003c/forward\u003e\n  \u003cbridge name='br1' stp='on' delay='0'/\u003e\n  \u003cip address='192.168.10.1' netmask='255.255.255.0'\u003e\n    \u003cdhcp\u003e\n      \u003crange start='192.168.10.10' end='192.168.10.100'/\u003e\n    \u003c/dhcp\u003e\n  \u003c/ip\u003e\n\u003c/network\u003e\n```\n\n## Define a network\n\n```bash\nvirsh net-define br1.xml\nvirsh net-autostart br1\n```\n\nThen to check...\n\n```bash\nvirsh net-list --all\n\n Name      State    Autostart   Persistent\n--------------------------------------------\n br1       active   yes         yes\n default   active   yes         yes\n```\n\n## UUID\n\n`virsh net-uuid br1`\n\n\n## Magic\n\n`virsh attach-interface --domain \u003cNAME OF VM\u003e --type bridge --source br1 --model virtio --config --live`\n\nMy VM, `ubuntu20.04` was running and immediately connected to the newly attached device!\n\n\n# Credit again\n\nVisit the original post for more details - this serves more as as a quicker set of notes for future me\n\n",
      "summary": "I have started using VMs more and more in my development workflow and it's impossible to work in a VM without an internet connection for me most of the time....",
      "date_published": "2022-06-25T07:20:41Z",
      "date_modified": "2022-06-25T07:20:41Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/cheat-on-your-man/",
      "url": "https://pype.dev/cheat-on-your-man/",
      "title": "cheat on your man",
      "content_html": "\u003cp\u003e\u003ccode\u003eman\u003c/code\u003e can be a pain to read\u0026hellip; and there\u0026rsquo;s lots of alternatives out there and one I\u0026rsquo;ve just started playing with is \u003ca href=\"https://github.com/cheat/cheat\"\u003echeat\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eman man\u003c/code\u003e will give you this plus a billion more lines of docs, which is useful when you need it\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eMAN\u003cspan class=\"o\"\u003e(\u003c/span\u003e1\u003cspan class=\"o\"\u003e)\u003c/span\u003e                                                                                                                       Manual pager utils                                                                                                                      MAN\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\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eNAME\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man - an interface to the on-line reference manuals\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\"\u003eSYNOPSIS\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man  \u003cspan class=\"o\"\u003e[\u003c/span\u003e-C  file\u003cspan class=\"o\"\u003e]\u003c/span\u003e  \u003cspan class=\"o\"\u003e[\u003c/span\u003e-d\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-D\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--warnings\u003cspan class=\"o\"\u003e[=\u003c/span\u003ewarnings\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-R encoding\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-L locale\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-m system\u003cspan class=\"o\"\u003e[\u003c/span\u003e,...\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-M path\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-S list\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-e extension\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-i\u003cspan class=\"p\"\u003e|\u003c/span\u003e-I\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--regex\u003cspan class=\"p\"\u003e|\u003c/span\u003e--wildcard\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--names-only\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-a\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-u\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--no-subpages\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-P pager\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-r prompt\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-7\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-E encoding\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--no-hyphenation\u003cspan class=\"o\"\u003e]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"o\"\u003e[\u003c/span\u003e--no-justification\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-p string\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-t\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-T\u003cspan class=\"o\"\u003e[\u003c/span\u003edevice\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-H\u003cspan class=\"o\"\u003e[\u003c/span\u003ebrowser\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-X\u003cspan class=\"o\"\u003e[\u003c/span\u003edpi\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-Z\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[[\u003c/span\u003esection\u003cspan class=\"o\"\u003e]\u003c/span\u003e page\u003cspan class=\"o\"\u003e[\u003c/span\u003e.section\u003cspan class=\"o\"\u003e]\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       man -k \u003cspan class=\"o\"\u003e[\u003c/span\u003eapropos options\u003cspan class=\"o\"\u003e]\u003c/span\u003e regexp ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man -K \u003cspan class=\"o\"\u003e[\u003c/span\u003e-w\u003cspan class=\"p\"\u003e|\u003c/span\u003e-W\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-S list\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-i\u003cspan class=\"p\"\u003e|\u003c/span\u003e-I\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--regex\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003esection\u003cspan class=\"o\"\u003e]\u003c/span\u003e term ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man -f \u003cspan class=\"o\"\u003e[\u003c/span\u003ewhatis options\u003cspan class=\"o\"\u003e]\u003c/span\u003e page ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man -l \u003cspan class=\"o\"\u003e[\u003c/span\u003e-C file\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-d\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-D\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e--warnings\u003cspan class=\"o\"\u003e[=\u003c/span\u003ewarnings\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-R encoding\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-L locale\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-P pager\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-r prompt\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-7\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-E encoding\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-p string\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-t\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-T\u003cspan class=\"o\"\u003e[\u003c/span\u003edevice\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-H\u003cspan class=\"o\"\u003e[\u003c/span\u003ebrowser\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-X\u003cspan class=\"o\"\u003e[\u003c/span\u003edpi\u003cspan class=\"o\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-Z\u003cspan class=\"o\"\u003e]\u003c/span\u003e file ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man -w\u003cspan class=\"p\"\u003e|\u003c/span\u003e-W \u003cspan class=\"o\"\u003e[\u003c/span\u003e-C file\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-d\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-D\u003cspan class=\"o\"\u003e]\u003c/span\u003e page ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man -c \u003cspan class=\"o\"\u003e[\u003c/span\u003e-C file\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-d\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e[\u003c/span\u003e-D\u003cspan class=\"o\"\u003e]\u003c/span\u003e page ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man \u003cspan class=\"o\"\u003e[\u003c/span\u003e-?V\u003cspan class=\"o\"\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\"\u003eDESCRIPTION\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man is the system\u003cspan class=\"err\"\u003e\u0026#39;\u003c/span\u003es manual pager.  Each page argument given to man is normally the name of a program, utility or \u003cspan class=\"k\"\u003efunction\u003c/span\u003e.  The manual page associated with each of these arguments is \u003cspan class=\"k\"\u003ethen\u003c/span\u003e found and displayed.  A section, \u003cspan class=\"k\"\u003eif\u003c/span\u003e provided, will direct  man  to  look\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       only  in  that  section of the manual.  The default action is to search in all of the available sections following a pre-defined order \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1 n l 8 3 2 3posix 3pm 3perl 3am 5 4 9 6 7\u0026#34;\u003c/span\u003e by default, unless overridden by the SECTION directive in /etc/manpath.config\u003cspan class=\"o\"\u003e)\u003c/span\u003e,\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       and to show only the first page found, even \u003cspan class=\"k\"\u003eif\u003c/span\u003e page exists in several sections.\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       The table below shows the section numbers of the manual followed by the types of pages they contain.\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\"\u003e1\u003c/span\u003e   Executable programs or shell commands\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e2\u003c/span\u003e   System calls \u003cspan class=\"o\"\u003e(\u003c/span\u003efunctions provided by the kernel\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e3\u003c/span\u003e   Library calls \u003cspan class=\"o\"\u003e(\u003c/span\u003efunctions within program libraries\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e4\u003c/span\u003e   Special files \u003cspan class=\"o\"\u003e(\u003c/span\u003eusually found in /dev\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e5\u003c/span\u003e   File formats and conventions eg /etc/passwd\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e6\u003c/span\u003e   Games\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e7\u003c/span\u003e   Miscellaneous \u003cspan class=\"o\"\u003e(\u003c/span\u003eincluding macro packages and conventions\u003cspan class=\"o\"\u003e)\u003c/span\u003e, e.g. man\u003cspan class=\"o\"\u003e(\u003c/span\u003e7\u003cspan class=\"o\"\u003e)\u003c/span\u003e, groff\u003cspan class=\"o\"\u003e(\u003c/span\u003e7\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e8\u003c/span\u003e   System administration commands \u003cspan class=\"o\"\u003e(\u003c/span\u003eusually only \u003cspan class=\"k\"\u003efor\u003c/span\u003e root\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"m\"\u003e9\u003c/span\u003e   Kernel routines \u003cspan class=\"o\"\u003e[\u003c/span\u003eNon standard\u003cspan class=\"o\"\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       A manual page consists of several sections.\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       Conventional section names include NAME, SYNOPSIS, CONFIGURATION, DESCRIPTION, OPTIONS, EXIT STATUS, RETURN VALUE, ERRORS, ENVIRONMENT, FILES, VERSIONS, CONFORMING TO, NOTES, BUGS, EXAMPLE, AUTHORS, and SEE ALSO.\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       The following conventions apply to the SYNOPSIS section and can be used as a guide in other sections.\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       bold text          \u003cspan class=\"nb\"\u003etype\u003c/span\u003e exactly as shown.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       italic text        replace with appropriate argument.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"o\"\u003e[\u003c/span\u003e-abc\u003cspan class=\"o\"\u003e]\u003c/span\u003e             any or all arguments within \u003cspan class=\"o\"\u003e[\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e are optional.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       -a\u003cspan class=\"p\"\u003e|\u003c/span\u003e-b              options delimited by \u003cspan class=\"p\"\u003e|\u003c/span\u003e cannot be used together.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       argument ...       argument is repeatable.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       \u003cspan class=\"o\"\u003e[\u003c/span\u003eexpression\u003cspan class=\"o\"\u003e]\u003c/span\u003e ...   entire expression within \u003cspan class=\"o\"\u003e[\u003c/span\u003e \u003cspan class=\"o\"\u003e]\u003c/span\u003e is repeatable.\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       Exact rendering may vary depending on the output device.  For instance, man will usually not be able to render italics when running in a terminal, and will typically use underlined or coloured text instead.\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       The \u003cspan class=\"nb\"\u003ecommand\u003c/span\u003e or \u003cspan class=\"k\"\u003efunction\u003c/span\u003e illustration is a pattern that should match all possible invocations.  In some cases it is advisable to illustrate several exclusive invocations as is shown in the SYNOPSIS section of this manual page.\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\"\u003eEXAMPLES\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e       man ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           Display the manual page \u003cspan class=\"k\"\u003efor\u003c/span\u003e the item \u003cspan class=\"o\"\u003e(\u003c/span\u003eprogram\u003cspan class=\"o\"\u003e)\u003c/span\u003e ls.\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       man man.7\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e           Display the manual page \u003cspan class=\"k\"\u003efor\u003c/span\u003e macro package man from section 7.\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"but-what-if-you-don-t\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBut what if you don’t?\u003c/span\u003e \u003ca href=\"#but-what-if-you-don-t\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003echeat man\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# To convert a man page to pdf:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eman -t bash \u003cspan class=\"p\"\u003e|\u003c/span\u003e ps2pdf - bash.pdf\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# To view the ascii chart:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eman \u003cspan class=\"m\"\u003e7\u003c/span\u003e ascii\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eYou get tiny examples to remind you of what you \u003cstrong\u003eprobably\u003c/strong\u003e are trying to do!\u003c/p\u003e\n",
      "content_text": "\n\n`man` can be a pain to read... and there's lots of alternatives out there and one I've just started playing with is [cheat](https://github.com/cheat/cheat)\n\n\n`man man` will give you this plus a billion more lines of docs, which is useful when you need it...\n\n```bash\nMAN(1)                                                                                                                       Manual pager utils                                                                                                                      MAN(1)\n\nNAME\n       man - an interface to the on-line reference manuals\n\nSYNOPSIS\n       man  [-C  file]  [-d] [-D] [--warnings[=warnings]] [-R encoding] [-L locale] [-m system[,...]] [-M path] [-S list] [-e extension] [-i|-I] [--regex|--wildcard] [--names-only] [-a] [-u] [--no-subpages] [-P pager] [-r prompt] [-7] [-E encoding] [--no-hyphenation]\n       [--no-justification] [-p string] [-t] [-T[device]] [-H[browser]] [-X[dpi]] [-Z] [[section] page[.section] ...] ...\n       man -k [apropos options] regexp ...\n       man -K [-w|-W] [-S list] [-i|-I] [--regex] [section] term ...\n       man -f [whatis options] page ...\n       man -l [-C file] [-d] [-D] [--warnings[=warnings]] [-R encoding] [-L locale] [-P pager] [-r prompt] [-7] [-E encoding] [-p string] [-t] [-T[device]] [-H[browser]] [-X[dpi]] [-Z] file ...\n       man -w|-W [-C file] [-d] [-D] page ...\n       man -c [-C file] [-d] [-D] page ...\n       man [-?V]\n\nDESCRIPTION\n       man is the system's manual pager.  Each page argument given to man is normally the name of a program, utility or function.  The manual page associated with each of these arguments is then found and displayed.  A section, if provided, will direct  man  to  look\n       only  in  that  section of the manual.  The default action is to search in all of the available sections following a pre-defined order (\"1 n l 8 3 2 3posix 3pm 3perl 3am 5 4 9 6 7\" by default, unless overridden by the SECTION directive in /etc/manpath.config),\n       and to show only the first page found, even if page exists in several sections.\n\n       The table below shows the section numbers of the manual followed by the types of pages they contain.\n\n       1   Executable programs or shell commands\n       2   System calls (functions provided by the kernel)\n       3   Library calls (functions within program libraries)\n       4   Special files (usually found in /dev)\n       5   File formats and conventions eg /etc/passwd\n       6   Games\n       7   Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7)\n       8   System administration commands (usually only for root)\n       9   Kernel routines [Non standard]\n\n       A manual page consists of several sections.\n\n       Conventional section names include NAME, SYNOPSIS, CONFIGURATION, DESCRIPTION, OPTIONS, EXIT STATUS, RETURN VALUE, ERRORS, ENVIRONMENT, FILES, VERSIONS, CONFORMING TO, NOTES, BUGS, EXAMPLE, AUTHORS, and SEE ALSO.\n\n       The following conventions apply to the SYNOPSIS section and can be used as a guide in other sections.\n\n       bold text          type exactly as shown.\n       italic text        replace with appropriate argument.\n       [-abc]             any or all arguments within [ ] are optional.\n       -a|-b              options delimited by | cannot be used together.\n       argument ...       argument is repeatable.\n       [expression] ...   entire expression within [ ] is repeatable.\n\n       Exact rendering may vary depending on the output device.  For instance, man will usually not be able to render italics when running in a terminal, and will typically use underlined or coloured text instead.\n\n       The command or function illustration is a pattern that should match all possible invocations.  In some cases it is advisable to illustrate several exclusive invocations as is shown in the SYNOPSIS section of this manual page.\n\nEXAMPLES\n       man ls\n           Display the manual page for the item (program) ls.\n\n       man man.7\n           Display the manual page for macro package man from section 7.\n```\n\n\n## But what if you don't?\n\n`cheat man`\n\n```bash\n# To convert a man page to pdf:\nman -t bash | ps2pdf - bash.pdf\n\n# To view the ascii chart:\nman 7 ascii\n```\n\nYou get tiny examples to remind you of what you **probably** are trying to do!\n",
      "summary": "can be a pain to read... and there's lots of alternatives out there and one I've just started playing with is cheat",
      "date_published": "2022-06-23T09:35:31Z",
      "date_modified": "2022-06-23T09:35:31Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/add-colored-indicators-to-your-dataframes-html-representation/",
      "url": "https://pype.dev/add-colored-indicators-to-your-dataframes-html-representation/",
      "title": "Add colored indicators to your dataframes html representation",
      "content_html": "\u003cp\u003e\u003ca href=\"https://twitter.com/driscollis\"\u003eMike Driscoll\u003c/a\u003e recently tweeted about making\ncolored out with pandas DataFrames and I just had to try it for myself\u003c/p\u003e\n\u003ch1 id=\"use-case\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUse Case\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eFirst though\u0026hellip; why?\nMy biggest use case is a monitoring pipeline of mine\u0026hellip; The details aside, the\noutput of my pipeline is a dataframe where each row has information about a\nfailed pipeline that I need to go look into. I dump that result to a simle html\nfile that\u0026rsquo;s hosted on an internal site and the file is updated every couple of\nhours. Adding some colored indicators automatically to the rows to help me\nassess severity of each record would be a handy way to quickly get an\nunderstanding the state of our pipelines.\u003c/p\u003e\n\u003ch1 id=\"how\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eHow?\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eThe docs for the \u003ccode\u003eapplymap\u003c/code\u003e method state simply:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eApply a CSS-styling function elementwise.\n\nUpdates the HTML representation with the result.\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eSo we can write a function that returns \u003ccode\u003ecolor: {color}\u003c/code\u003e based on the dataframe\nvalues and when we drop that dataframe to html we\u0026rsquo;ll have some simple css\nstyling applied automagically!\u003c/p\u003e\n\u003cp\u003eBy default the function will be applied to all columns of the dataframe, but\nthat\u0026rsquo;s not useful if the columns are different types which is usually the case.\nLuckily there is a \u003ccode\u003esubset\u003c/code\u003e keyword to only apply to the columns you need!\u003c/p\u003e\n\u003cp\u003eConsider my example\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread_csv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;cars.csv\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\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003empg_color\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eval\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003efloat\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"n\"\u003ecolor\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;red\u0026#34;\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eval\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e21\u003c/span\u003e \u003cspan class=\"k\"\u003eelse\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;green\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003ecolor\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=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estyle\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eapplymap\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003empg_color\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003esubset\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mpg\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_html\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;color.html\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\u003cp\u003eI want to quickly see if the \u003ccode\u003empg\u003c/code\u003e is any good for the cars in the cars dataset\nand I\u0026rsquo;ll define \u0026ldquo;good\u0026rdquo; as better than 21 mpg (not great I know but just for the\nsake of discussion\u0026hellip;)\u003c/p\u003e\n\u003cp\u003eThe function returns an appropriate css string and after I \u003ccode\u003estyle.applymap\u003c/code\u003e on just the \u003ccode\u003empg\u003c/code\u003e column we get this!\u003c/p\u003e\n\u003cstyle type=\"text/css\"\u003e\n#T_95e99_row0_col1, #T_95e99_row1_col1, #T_95e99_row2_col1, #T_95e99_row3_col1 {\n  color: green;\n}\n#T_95e99_row4_col1 {\n  color: red;\n}\n\u003c/style\u003e\n\u003ctable id=\"T_95e99\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth class=\"blank level0\" \u003e\u0026nbsp;\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col0\" class=\"col_heading level0 col0\" \u003eUnnamed: 0\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col1\" class=\"col_heading level0 col1\" \u003empg\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col2\" class=\"col_heading level0 col2\" \u003ecyl\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col3\" class=\"col_heading level0 col3\" \u003edisp\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col4\" class=\"col_heading level0 col4\" \u003ehp\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col5\" class=\"col_heading level0 col5\" \u003edrat\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col6\" class=\"col_heading level0 col6\" \u003ewt\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col7\" class=\"col_heading level0 col7\" \u003eqsec\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col8\" class=\"col_heading level0 col8\" \u003evs\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col9\" class=\"col_heading level0 col9\" \u003eam\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col10\" class=\"col_heading level0 col10\" \u003egear\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col11\" class=\"col_heading level0 col11\" \u003ecarb\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row0\" class=\"row_heading level0 row0\" \u003e0\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row0_col0\" class=\"data row0 col0\" \u003eMazda RX4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col1\" class=\"data row0 col1\" \u003e21.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col2\" class=\"data row0 col2\" \u003e6\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col3\" class=\"data row0 col3\" \u003e160.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col4\" class=\"data row0 col4\" \u003e110\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col5\" class=\"data row0 col5\" \u003e3.900000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col6\" class=\"data row0 col6\" \u003e2.620000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col7\" class=\"data row0 col7\" \u003e16.460000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col8\" class=\"data row0 col8\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col9\" class=\"data row0 col9\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col10\" class=\"data row0 col10\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col11\" class=\"data row0 col11\" \u003e4\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row1\" class=\"row_heading level0 row1\" \u003e1\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row1_col0\" class=\"data row1 col0\" \u003eMazda RX4 Wag\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col1\" class=\"data row1 col1\" \u003e21.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col2\" class=\"data row1 col2\" \u003e6\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col3\" class=\"data row1 col3\" \u003e160.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col4\" class=\"data row1 col4\" \u003e110\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col5\" class=\"data row1 col5\" \u003e3.900000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col6\" class=\"data row1 col6\" \u003e2.875000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col7\" class=\"data row1 col7\" \u003e17.020000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col8\" class=\"data row1 col8\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col9\" class=\"data row1 col9\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col10\" class=\"data row1 col10\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col11\" class=\"data row1 col11\" \u003e4\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row2\" class=\"row_heading level0 row2\" \u003e2\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row2_col0\" class=\"data row2 col0\" \u003eDatsun 710\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col1\" class=\"data row2 col1\" \u003e22.800000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col2\" class=\"data row2 col2\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col3\" class=\"data row2 col3\" \u003e108.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col4\" class=\"data row2 col4\" \u003e93\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col5\" class=\"data row2 col5\" \u003e3.850000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col6\" class=\"data row2 col6\" \u003e2.320000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col7\" class=\"data row2 col7\" \u003e18.610000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col8\" class=\"data row2 col8\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col9\" class=\"data row2 col9\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col10\" class=\"data row2 col10\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col11\" class=\"data row2 col11\" \u003e1\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row3\" class=\"row_heading level0 row3\" \u003e3\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row3_col0\" class=\"data row3 col0\" \u003eHornet 4 Drive\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col1\" class=\"data row3 col1\" \u003e21.400000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col2\" class=\"data row3 col2\" \u003e6\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col3\" class=\"data row3 col3\" \u003e258.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col4\" class=\"data row3 col4\" \u003e110\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col5\" class=\"data row3 col5\" \u003e3.080000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col6\" class=\"data row3 col6\" \u003e3.215000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col7\" class=\"data row3 col7\" \u003e19.440000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col8\" class=\"data row3 col8\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col9\" class=\"data row3 col9\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col10\" class=\"data row3 col10\" \u003e3\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col11\" class=\"data row3 col11\" \u003e1\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row4\" class=\"row_heading level0 row4\" \u003e4\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row4_col0\" class=\"data row4 col0\" \u003eHornet Sportabout\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col1\" class=\"data row4 col1\" \u003e18.700000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col2\" class=\"data row4 col2\" \u003e8\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col3\" class=\"data row4 col3\" \u003e360.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col4\" class=\"data row4 col4\" \u003e175\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col5\" class=\"data row4 col5\" \u003e3.150000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col6\" class=\"data row4 col6\" \u003e3.440000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col7\" class=\"data row4 col7\" \u003e17.020000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col8\" class=\"data row4 col8\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col9\" class=\"data row4 col9\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col10\" class=\"data row4 col10\" \u003e3\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col11\" class=\"data row4 col11\" \u003e2\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n",
      "content_text": "\n[Mike Driscoll](https://twitter.com/driscollis) recently tweeted about making\ncolored out with pandas DataFrames and I just had to try it for myself\n\n# Use Case\n\nFirst though... why?\nMy biggest use case is a monitoring pipeline of mine... The details aside, the\noutput of my pipeline is a dataframe where each row has information about a\nfailed pipeline that I need to go look into. I dump that result to a simle html\nfile that's hosted on an internal site and the file is updated every couple of\nhours. Adding some colored indicators automatically to the rows to help me\nassess severity of each record would be a handy way to quickly get an\nunderstanding the state of our pipelines.\n\n# How?\n\nThe docs for the `applymap` method state simply:\n\n```\nApply a CSS-styling function elementwise.\n\nUpdates the HTML representation with the result.\n\n```\n\nSo we can write a function that returns `color: {color}` based on the dataframe\nvalues and when we drop that dataframe to html we'll have some simple css\nstyling applied automagically!\n\nBy default the function will be applied to all columns of the dataframe, but\nthat's not useful if the columns are different types which is usually the case.\nLuckily there is a `subset` keyword to only apply to the columns you need!\n\nConsider my example\n\n```python \nsandbox   main via 3.8.11(sandbox) ipython\n❯ df = pd.read_csv(\"cars.csv\")\n\nsandbox   main via 3.8.11(sandbox) ipython\n❯ def mpg_color(val: float):\n...:     color = \"red\" if val \u003c 21 else \"green\"\n...:     return f\"color: {color}\"\n\nsandbox   main via 3.8.11(sandbox) ipython\n❯ df.style.applymap(mpg_color, subset=\"mpg\").to_html(\"color.html\")\n```\n\nI want to quickly see if the `mpg` is any good for the cars in the cars dataset\nand I'll define \"good\" as better than 21 mpg (not great I know but just for the\nsake of discussion...)\n\nThe function returns an appropriate css string and after I `style.applymap` on just the `mpg` column we get this!\n\n\n\u003cstyle type=\"text/css\"\u003e\n#T_95e99_row0_col1, #T_95e99_row1_col1, #T_95e99_row2_col1, #T_95e99_row3_col1 {\n  color: green;\n}\n#T_95e99_row4_col1 {\n  color: red;\n}\n\u003c/style\u003e\n\u003ctable id=\"T_95e99\"\u003e\n  \u003cthead\u003e\n    \u003ctr\u003e\n      \u003cth class=\"blank level0\" \u003e\u0026nbsp;\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col0\" class=\"col_heading level0 col0\" \u003eUnnamed: 0\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col1\" class=\"col_heading level0 col1\" \u003empg\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col2\" class=\"col_heading level0 col2\" \u003ecyl\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col3\" class=\"col_heading level0 col3\" \u003edisp\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col4\" class=\"col_heading level0 col4\" \u003ehp\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col5\" class=\"col_heading level0 col5\" \u003edrat\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col6\" class=\"col_heading level0 col6\" \u003ewt\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col7\" class=\"col_heading level0 col7\" \u003eqsec\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col8\" class=\"col_heading level0 col8\" \u003evs\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col9\" class=\"col_heading level0 col9\" \u003eam\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col10\" class=\"col_heading level0 col10\" \u003egear\u003c/th\u003e\n      \u003cth id=\"T_95e99_level0_col11\" class=\"col_heading level0 col11\" \u003ecarb\u003c/th\u003e\n    \u003c/tr\u003e\n  \u003c/thead\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row0\" class=\"row_heading level0 row0\" \u003e0\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row0_col0\" class=\"data row0 col0\" \u003eMazda RX4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col1\" class=\"data row0 col1\" \u003e21.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col2\" class=\"data row0 col2\" \u003e6\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col3\" class=\"data row0 col3\" \u003e160.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col4\" class=\"data row0 col4\" \u003e110\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col5\" class=\"data row0 col5\" \u003e3.900000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col6\" class=\"data row0 col6\" \u003e2.620000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col7\" class=\"data row0 col7\" \u003e16.460000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col8\" class=\"data row0 col8\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col9\" class=\"data row0 col9\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col10\" class=\"data row0 col10\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row0_col11\" class=\"data row0 col11\" \u003e4\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row1\" class=\"row_heading level0 row1\" \u003e1\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row1_col0\" class=\"data row1 col0\" \u003eMazda RX4 Wag\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col1\" class=\"data row1 col1\" \u003e21.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col2\" class=\"data row1 col2\" \u003e6\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col3\" class=\"data row1 col3\" \u003e160.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col4\" class=\"data row1 col4\" \u003e110\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col5\" class=\"data row1 col5\" \u003e3.900000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col6\" class=\"data row1 col6\" \u003e2.875000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col7\" class=\"data row1 col7\" \u003e17.020000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col8\" class=\"data row1 col8\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col9\" class=\"data row1 col9\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col10\" class=\"data row1 col10\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row1_col11\" class=\"data row1 col11\" \u003e4\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row2\" class=\"row_heading level0 row2\" \u003e2\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row2_col0\" class=\"data row2 col0\" \u003eDatsun 710\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col1\" class=\"data row2 col1\" \u003e22.800000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col2\" class=\"data row2 col2\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col3\" class=\"data row2 col3\" \u003e108.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col4\" class=\"data row2 col4\" \u003e93\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col5\" class=\"data row2 col5\" \u003e3.850000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col6\" class=\"data row2 col6\" \u003e2.320000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col7\" class=\"data row2 col7\" \u003e18.610000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col8\" class=\"data row2 col8\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col9\" class=\"data row2 col9\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col10\" class=\"data row2 col10\" \u003e4\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row2_col11\" class=\"data row2 col11\" \u003e1\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row3\" class=\"row_heading level0 row3\" \u003e3\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row3_col0\" class=\"data row3 col0\" \u003eHornet 4 Drive\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col1\" class=\"data row3 col1\" \u003e21.400000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col2\" class=\"data row3 col2\" \u003e6\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col3\" class=\"data row3 col3\" \u003e258.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col4\" class=\"data row3 col4\" \u003e110\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col5\" class=\"data row3 col5\" \u003e3.080000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col6\" class=\"data row3 col6\" \u003e3.215000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col7\" class=\"data row3 col7\" \u003e19.440000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col8\" class=\"data row3 col8\" \u003e1\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col9\" class=\"data row3 col9\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col10\" class=\"data row3 col10\" \u003e3\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row3_col11\" class=\"data row3 col11\" \u003e1\u003c/td\u003e\n    \u003c/tr\u003e\n    \u003ctr\u003e\n      \u003cth id=\"T_95e99_level0_row4\" class=\"row_heading level0 row4\" \u003e4\u003c/th\u003e\n      \u003ctd id=\"T_95e99_row4_col0\" class=\"data row4 col0\" \u003eHornet Sportabout\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col1\" class=\"data row4 col1\" \u003e18.700000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col2\" class=\"data row4 col2\" \u003e8\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col3\" class=\"data row4 col3\" \u003e360.000000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col4\" class=\"data row4 col4\" \u003e175\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col5\" class=\"data row4 col5\" \u003e3.150000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col6\" class=\"data row4 col6\" \u003e3.440000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col7\" class=\"data row4 col7\" \u003e17.020000\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col8\" class=\"data row4 col8\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col9\" class=\"data row4 col9\" \u003e0\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col10\" class=\"data row4 col10\" \u003e3\u003c/td\u003e\n      \u003ctd id=\"T_95e99_row4_col11\" class=\"data row4 col11\" \u003e2\u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n\n\n",
      "summary": "Mike Driscoll recently tweeted about making colored out with pandas DataFrames and I just had to try it for myself",
      "date_published": "2022-06-04T06:12:33Z",
      "date_modified": "2022-06-04T06:12:33Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "data",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/samba-on-ubuntu-22-needs-inherit-permissions-set/",
      "url": "https://pype.dev/samba-on-ubuntu-22-needs-inherit-permissions-set/",
      "title": "Samba on Ubuntu 22 needs “inherit permissions” set",
      "content_html": "\u003cp\u003esee samba config on hogwarts\u003c/p\u003e\n",
      "content_text": "\nsee samba config on hogwarts\n",
      "summary": "see samba config on hogwarts",
      "date_published": "2022-05-31T06:30:42Z",
      "date_modified": "2022-05-31T06:30:42Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/filtering-emails-with-core-utils/",
      "url": "https://pype.dev/filtering-emails-with-core-utils/",
      "title": "Filtering emails with core utils",
      "content_html": "\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eemail1@me.com\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esomebody_else@gmail.com\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\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#! /bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# pick multiple emails from list and combine into comma seperated array\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nv\"\u003eemails\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sb\"\u003e`\u003c/span\u003ecat .../emails \u003cspan class=\"p\"\u003e|\u003c/span\u003e fzf -m \u003cspan class=\"p\"\u003e|\u003c/span\u003e sed \u003cspan class=\"s1\"\u003e\u0026#39;s/^\\|$/\u0026#34;/g\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e|\u003c/span\u003epaste -sd,\u003cspan class=\"sb\"\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=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"nv\"\u003e$emails\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": "\n```text\nemail1@me.com\nsomebody_else@gmail.com\n\n```\n\n```bash\n#! /bin/bash\n# pick multiple emails from list and combine into comma seperated array\nemails=`cat .../emails | fzf -m | sed 's/^\\|$/\"/g'|paste -sd,` \n\necho $emails\n\n```\n",
      "date_published": "2022-05-30T06:09:27Z",
      "date_modified": "2022-05-30T06:09:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/reindex-nextcloud-after-adding-data-via-cli/",
      "url": "https://pype.dev/reindex-nextcloud-after-adding-data-via-cli/",
      "title": "Reindex Nextcloud After Adding Data via CLI",
      "content_html": "\u003ch1 id=\"my-nextcloud-woes\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy Nextcloud woes\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI wrote \u003ca href=\"%22nextcloud-permissions-with-zfs-and-ansible-nas%22\"\u003ehere\u003c/a\u003e about setting\nup \u003ccode\u003ewww-data\u003c/code\u003e as the owner of any directories you want nextcloud to manage.\nHowever, I regularly struggle wtih permissions issues on my NAS because of the\nexternal storage app anyways so I\u0026rsquo;ve decided to just put our photos in the spot\nNextcloud would otherwise put them, and use this as healthy pressure on our\nfamily to organize our photos and put the ones we care about with the rest of\nour family media.\u003c/p\u003e\n\u003ch1 id=\"migration\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMigration\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eBecause I had a ton of photos on the NAS anyways that I wanted moved over to\nNextcloud I just rsync\u0026rsquo;d the photos directory on my NAS to the user\u0026rsquo;s photos\ndirectory in nextcloud but they weren\u0026rsquo;t showing up in the web UI!\u003c/p\u003e\n\u003ch1 id=\"the-fix\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Fix?\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eAs www-data I needed to \u003ccode\u003ephp /var/www/nextcloud/occ files:scan --all\u003c/code\u003e inside my\nnextcloud docker container AFTER moving a bunch of photos off my \u0026ldquo;NAS\u0026rdquo; into the\nfolder mounted to the nextcloud container as its data folder! Before I did this\nthey weren\u0026rsquo;t showing up in the web UI/\u003c/p\u003e\n",
      "content_text": "\n# My Nextcloud woes\n\nI wrote [here](\"nextcloud-permissions-with-zfs-and-ansible-nas\") about setting\nup `www-data` as the owner of any directories you want nextcloud to manage.\nHowever, I regularly struggle wtih permissions issues on my NAS because of the\nexternal storage app anyways so I've decided to just put our photos in the spot\nNextcloud would otherwise put them, and use this as healthy pressure on our\nfamily to organize our photos and put the ones we care about with the rest of\nour family media.\n\n# Migration\n\nBecause I had a ton of photos on the NAS anyways that I wanted moved over to\nNextcloud I just rsync'd the photos directory on my NAS to the user's photos\ndirectory in nextcloud but they weren't showing up in the web UI!\n\n# The Fix?\n\nAs www-data I needed to `php /var/www/nextcloud/occ files:scan --all` inside my\nnextcloud docker container AFTER moving a bunch of photos off my \"NAS\" into the\nfolder mounted to the nextcloud container as its data folder! Before I did this\nthey weren't showing up in the web UI/\n",
      "summary": "I wrote here about setting up as the owner of any directories you want nextcloud to manage. However, I regularly struggle wtih permissions issues on my NAS...",
      "date_published": "2022-05-29T15:34:33Z",
      "date_modified": "2022-05-29T15:34:33Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/arr-download-client-config/",
      "url": "https://pype.dev/arr-download-client-config/",
      "title": "arr client config",
      "content_html": "\u003cp\u003eTIL that when setting up download clients for\nradarr/sonarr/lidarr/readarr/bazarr/prowlarr that you can utilize internal DNS\nand instead of hardcoding an IP address of your download client server, can use\njust the CNAME record (ie. instead of 172.10.14.13 I can use\ntransmission.mydomain.com\u0026hellip; notice the lact of http(s)://\u0026hellip; adding that won\u0026rsquo;t\nallow the connection to work/\u003c/p\u003e\n\u003cp\u003eFurthermore, you can use internal DNS to lookup the domain, not the subdomain,\nand expose the port, like \u003ccode\u003emydomain.com:7878\u003c/code\u003e for sonarr. This was simpler to\nmaintain because I don\u0026rsquo;t change which ports an application exposes or utilizes\nhardly ever, plus I don\u0026rsquo;t need to maintain CNAME records for every service!\u003c/p\u003e\n",
      "content_text": "\nTIL that when setting up download clients for\nradarr/sonarr/lidarr/readarr/bazarr/prowlarr that you can utilize internal DNS\nand instead of hardcoding an IP address of your download client server, can use\njust the CNAME record (ie. instead of 172.10.14.13 I can use\ntransmission.mydomain.com... notice the lact of http(s)://... adding that won't\nallow the connection to work/\n\nFurthermore, you can use internal DNS to lookup the domain, not the subdomain,\nand expose the port, like `mydomain.com:7878` for sonarr. This was simpler to\nmaintain because I don't change which ports an application exposes or utilizes\nhardly ever, plus I don't need to maintain CNAME records for every service!\n",
      "summary": "TIL that when setting up download clients for radarr/sonarr/lidarr/readarr/bazarr/prowlarr that you can utilize internal DNS and instead of hardcoding an IP...",
      "date_published": "2022-05-28T13:29:28Z",
      "date_modified": "2022-05-28T13:29:28Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/add-space-to-your-lvm-on-ubuntu/",
      "url": "https://pype.dev/add-space-to-your-lvm-on-ubuntu/",
      "title": "Add space to your LVM on Ubuntu",
      "content_html": "\u003cp\u003eI ran out of space on the SSD in my server when doing some file transfers but only 100GB was used of a 256 GB SSD?\u003c/p\u003e\n\u003ch1 id=\"lvm\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eLVM\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eWhen installing Ubuntu live server the default option for how to partition the\ndisk (in my experience) has been to setup an LVM group that defaults to less\nthan the available space. Most recently I put Ubuntu server on a 256 GB SSD but\nthe main partition was formatted as an LVM group with 100GB of storage\u0026hellip; I\ndidn\u0026rsquo;t think anything of this even though I\u0026rsquo;m mostly used to EXT4.\u003c/p\u003e\n\u003cp\u003eI think the reason for LVMs is performance, but in hindsight, I don\u0026rsquo;t really\ncare much about the performance differences, I really just want all my storage\nthat\u0026rsquo;s fast enough\u003c/p\u003e\n\u003ch1 id=\"extending-the-lvm\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eExtending the LVM\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eA moment of googling brought me to Ubuntu\u0026rsquo;s wiki and I\nlearned that I can expand my LVM to the space I need\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esudo lvdisplay\u003c/code\u003e and \u003ccode\u003esudo pvdisplay\u003c/code\u003e show detailed views of the logical volumes and physical volumes respectively.\u003c/p\u003e\n\u003cp\u003eTake a look at those and find the volume you need to extend. For me I found this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  --- Logical volume ---\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  LV Path                /dev/ubuntu-vg/ubuntu-lv\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  LV Name                ubuntu-lv\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  VG Name                ubuntu-vg\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  LV Write Access        read/write\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  LV Status              available\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\u003cp\u003eThere\u0026rsquo;s more that you\u0026rsquo;ll see but this is what\u0026rsquo;s relevant - I need to extend the\n\u003ccode\u003eubuntu-lv\u003c/code\u003e logical volume in the \u003ccode\u003eubuntu-vg\u003c/code\u003e volume group.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003esudo lvextend -L +50g ubuntu-vg/ubuntu-lv\u003c/code\u003e gives me 50 more GB of storage which should be enough for at least tonight 🤓\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://wiki.ubuntu.com/Lvm\"\u003eRTFM\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI ran out of space on the SSD in my server when doing some file transfers but only 100GB was used of a 256 GB SSD?\n\n# LVM\n\nWhen installing Ubuntu live server the default option for how to partition the\ndisk (in my experience) has been to setup an LVM group that defaults to less\nthan the available space. Most recently I put Ubuntu server on a 256 GB SSD but\nthe main partition was formatted as an LVM group with 100GB of storage... I\ndidn't think anything of this even though I'm mostly used to EXT4.\n\nI think the reason for LVMs is performance, but in hindsight, I don't really\ncare much about the performance differences, I really just want all my storage\nthat's fast enough\n\n# Extending the LVM\n\nA moment of googling brought me to Ubuntu's wiki and I\nlearned that I can expand my LVM to the space I need...\n\n`sudo lvdisplay` and `sudo pvdisplay` show detailed views of the logical volumes and physical volumes respectively.\n\nTake a look at those and find the volume you need to extend. For me I found this:\n\n```bash\n  --- Logical volume ---\n  LV Path                /dev/ubuntu-vg/ubuntu-lv\n  LV Name                ubuntu-lv\n  VG Name                ubuntu-vg\n  LV Write Access        read/write\n  LV Status              available\n  ...\n```\n\nThere's more that you'll see but this is what's relevant - I need to extend the\n`ubuntu-lv` logical volume in the `ubuntu-vg` volume group.\n\n`sudo lvextend -L +50g ubuntu-vg/ubuntu-lv` gives me 50 more GB of storage which should be enough for at least tonight 🤓\n\n[RTFM](https://wiki.ubuntu.com/Lvm)\n",
      "summary": "I ran out of space on the SSD in my server when doing some file transfers but only 100GB was used of a 256 GB SSD?",
      "date_published": "2022-05-26T21:33:55Z",
      "date_modified": "2022-05-26T21:33:55Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/tdarr-worker-nodes-share-the-cache/",
      "url": "https://pype.dev/tdarr-worker-nodes-share-the-cache/",
      "title": "Tdarr worker nodes share the cache!",
      "content_html": "\u003cp\u003eWhen working with tdarr remote nodes, they need to have access not only to the\nsame libraries but also the same transcode cache as the server otherwise the\ntranscodes will fail\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"network-setup\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eNetwork Setup\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eTo explain I\u0026rsquo;ll give a brief overview of my home setup\u003c/p\u003e\n\u003cp\u003eI have an old Dell PowerEdge R610 as my main server running a live server distribution of Ubuntu.\nI use ZFS for my NAS file system, and most of my datasets are accesible over my home network.\nI have a Tdarr server running in a docker container on the R610.\u003c/p\u003e\n\u003cp\u003eIn my office I dailyi drive a gaming desktop with an Nvidia 2060 Super running Ubuntu as well.\nOn that desktop I am running a Tdarr node in a docker container.\nThe container has access to the network folders with my media.\u003c/p\u003e\n\u003ch1 id=\"initial-magic\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eInitial Magic\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eWhen I spun up  a tdarr node on my desktop, the tdarr server running on my R610 automatically registered the node, which was freaking amazing.\nThat magic though spoiled me and I thought that I didn\u0026rsquo;t need to read the rest of the docs\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"initial-fail\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eInitial Fail\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI setup a transcode cache directory on the R610 locally and a separate transcode cache on my desktop that the remote tdarr node would use.\nHaving them separated led to 2 main issues:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eTranscodes were not being migrated back to my library properly\u003c/li\u003e\n\u003cli\u003eI was running out of disk space on my desktop because tdarr wasn\u0026rsquo;t deleting the completed cache files properly.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch1 id=\"the-fix\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Fix\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eMake a transcode cache directory accessible on my network, and give access to that directory to the docker container running my remote tdarr node.\u003c/p\u003e\n",
      "content_text": "\nWhen working with tdarr remote nodes, they need to have access not only to the\nsame libraries but also the same transcode cache as the server otherwise the\ntranscodes will fail...\n\n# Network Setup\n\nTo explain I'll give a brief overview of my home setup  \n\nI have an old Dell PowerEdge R610 as my main server running a live server distribution of Ubuntu.\nI use ZFS for my NAS file system, and most of my datasets are accesible over my home network.\nI have a Tdarr server running in a docker container on the R610.\n\nIn my office I dailyi drive a gaming desktop with an Nvidia 2060 Super running Ubuntu as well.\nOn that desktop I am running a Tdarr node in a docker container. \nThe container has access to the network folders with my media. \n\n# Initial Magic \n\nWhen I spun up  a tdarr node on my desktop, the tdarr server running on my R610 automatically registered the node, which was freaking amazing.\nThat magic though spoiled me and I thought that I didn't need to read the rest of the docs...\n\n# Initial Fail \n\nI setup a transcode cache directory on the R610 locally and a separate transcode cache on my desktop that the remote tdarr node would use.\nHaving them separated led to 2 main issues:\n1. Transcodes were not being migrated back to my library properly\n2. I was running out of disk space on my desktop because tdarr wasn't deleting the completed cache files properly.\n\n# The Fix\n\nMake a transcode cache directory accessible on my network, and give access to that directory to the docker container running my remote tdarr node.\n\n\n\n",
      "summary": "When working with tdarr remote nodes, they need to have access not only to the same libraries but also the same transcode cache as the server otherwise the...",
      "date_published": "2022-05-25T06:36:59Z",
      "date_modified": "2022-05-25T06:36:59Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/local-dns-with-pi-hole/",
      "url": "https://pype.dev/local-dns-with-pi-hole/",
      "title": "Local DNS with Pi-hole",
      "content_html": "\u003ch2 id=\"spoilers\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSpoilers\u003c/span\u003e \u003ca href=\"#spoilers\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eTailscale is way easier than this\u0026hellip; I was doing this local DNS overwrite in\nPi-hole before running tailscale and I haven\u0026rsquo;t just totally \u0026ldquo;kicked the habit\u0026rdquo;\nyet, so for anyone NOT running tailcale, but wanting local HTTPS and using\nPi-Hole this method would work fine\u003c/p\u003e\n\u003ch2 id=\"intro\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIntro\u003c/span\u003e \u003ca href=\"#intro\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI use \u003ca href=\"https://pi-hole.net/\"\u003epihole\u003c/a\u003e as my DNS server at home. I run unbound as\nwell and have a pretty standard setup from their docs.\u003c/p\u003e\n\u003cp\u003ePi-hole does 2 primary things for me:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003edns sink-hold, the primary use case I believe\u003c/li\u003e\n\u003cli\u003elocal SSL for all my self-hosted apps\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"caveat\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCaveat\u003c/span\u003e \u003ca href=\"#caveat\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI know for sure there are better ways to do this, but there\u0026rsquo;s also worse\nones\u0026hellip; so for now this has been my pattern, and it\u0026rsquo;s only bitten me in the\nbutt when I\u0026rsquo;ve forgotten to add the final CNAME record\u0026hellip; which\u0026rsquo;ll make sense\nin a sec.\u003c/p\u003e\n\u003ch2 id=\"process\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eProcess\u003c/span\u003e \u003ca href=\"#process\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIt\u0026rsquo;s really quite simple - in pihole I have a DNS record for my domain pointing\nto my primary server\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250706114501_8c34a31e.png\" alt=\"20250706114501_8c34a31e.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eThen for each service that I want to keep everything resolved locally for I add a CNAME\u003c/p\u003e\n\u003cfigure\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250706114557_916fb4c4.png\" alt=\"20250706114557_916fb4c4.png\"\u003e\n\u003c/figure\u003e\n\u003cp\u003eWith DNS resolving this way for clients using my pihole, the networking all\nstays local and I still get HTTPS for my wildcard cert in cloudflare\u003c/p\u003e\n\u003ch2 id=\"why\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhy?\u003c/span\u003e \u003ca href=\"#why\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe reasons I do this are simple:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eI\u0026rsquo;ve been doing it since I started homelabbing and it started out of misunderstanding of how networking works \u003cem\u003eat all\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003eI use a home dashboard with https:// links, and it\u0026rsquo;s nice to just use that\nsame dashbaord publically or at home. With local DNS resolution then I can\nwhitelist some services, but conveniently access over the public url with https\nbut routing such that my whitelist let\u0026rsquo;s me in when it wouldn\u0026rsquo;t if I tried to\naccess the service from an external client\u003c/li\u003e\n\u003cli\u003eit\u0026rsquo;s my homelab - I can do what I want\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"it-s-too-complicated\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eIt’s too complicated\u003c/span\u003e \u003ca href=\"#it-s-too-complicated\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHonestly, as simple as this is, it is tedious and kind of complicated\u0026hellip;\nThere\u0026rsquo;s options to make it better\u0026hellip;\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003escript any service deployment to update the pihole /etc/host file\u003c/li\u003e\n\u003cli\u003ejust use tailscale\u0026hellip;\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\n## Spoilers\n\nTailscale is way easier than this... I was doing this local DNS overwrite in\nPi-hole before running tailscale and I haven't just totally \"kicked the habit\"\nyet, so for anyone NOT running tailcale, but wanting local HTTPS and using\nPi-Hole this method would work fine\n\n## Intro\n\nI use [pihole](https://pi-hole.net/) as my DNS server at home. I run unbound as\nwell and have a pretty standard setup from their docs.\n\nPi-hole does 2 primary things for me:\n\n1. dns sink-hold, the primary use case I believe\n2. local SSL for all my self-hosted apps\n\n## Caveat\n\nI know for sure there are better ways to do this, but there's also worse\nones... so for now this has been my pattern, and it's only bitten me in the\nbutt when I've forgotten to add the final CNAME record... which'll make sense\nin a sec.\n\n## Process\n\nIt's really quite simple - in pihole I have a DNS record for my domain pointing\nto my primary server\n\n![20250706114501_8c34a31e.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250706114501_8c34a31e.png)\n\nThen for each service that I want to keep everything resolved locally for I add a CNAME\n\n![20250706114557_916fb4c4.png](https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/20250706114557_916fb4c4.png)\n\nWith DNS resolving this way for clients using my pihole, the networking all\nstays local and I still get HTTPS for my wildcard cert in cloudflare\n\n## Why?\n\nThe reasons I do this are simple:\n\n1. I've been doing it since I started homelabbing and it started out of misunderstanding of how networking works _at all_\n2. I use a home dashboard with https:// links, and it's nice to just use that\n   same dashbaord publically or at home. With local DNS resolution then I can\n   whitelist some services, but conveniently access over the public url with https\n   but routing such that my whitelist let's me in when it wouldn't if I tried to\n   access the service from an external client\n3. it's my homelab - I can do what I want\n\n## It's too complicated\n\nHonestly, as simple as this is, it is tedious and kind of complicated...\nThere's options to make it better...\n\n1. script any service deployment to update the pihole /etc/host file\n2. just use tailscale...\n",
      "summary": "Tailscale is way easier than this... I was doing this local DNS overwrite in Pi-hole before running tailscale and I haven't just totally \"kicked the habit\"...",
      "date_published": "2022-05-23T09:40:59Z",
      "date_modified": "2022-05-23T09:40:59Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/configure-bridge-network-on-ubuntu-22-04-with-netplan/",
      "url": "https://pype.dev/configure-bridge-network-on-ubuntu-22-04-with-netplan/",
      "title": "Configure bridge network on Ubuntu 22.04 with Netplan",
      "content_html": "\u003cp\u003eSee 02-\u0026hellip;.yaml in ansible-nas\u003c/p\u003e\n",
      "content_text": "\nSee 02-....yaml in ansible-nas\n",
      "summary": "See 02-....yaml in ansible-nas",
      "date_published": "2022-05-22T14:55:28Z",
      "date_modified": "2022-05-22T14:55:28Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/netplan-change-from-focal-to-jammy/",
      "url": "https://pype.dev/netplan-change-from-focal-to-jammy/",
      "title": "Netplan change from Focal to Jammy",
      "content_html": "\u003cp\u003eI am revamping my home server and bumped myself early up to Jammy Jellyfish\u0026hellip;\nhowever to my peril I reused my netplan config and after hitting my server with\nthe \u0026lsquo;ol \u003ccode\u003enetplan apply\u003c/code\u003e I lost connection\u0026hellip;\nDNS still seemed to kinda work externally, but internally nothing was up\u0026hellip;\u003c/p\u003e\n\u003cp\u003eTurns out Netplan got a little change in how to express the \u003ccode\u003egateway\u003c/code\u003e key in the netplan config!\u003c/p\u003e\n\u003cp\u003eOld Ubuntu 20.04 way\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003enetwork\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\"\u003eversion\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e2\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\"\u003eethernets\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\"\u003eenp0s4\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\"\u003eaddresses\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e192.168.1\u003c/span\u003e\u003cspan class=\"l\"\u003e.{Static IP}/24]\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\"\u003egateway4\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e192.168.1.1\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# \u0026lt;-- This changes!\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\"\u003enameservers\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\"\u003eaddresses\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e192.168.1.1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e1.1.1.1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNew jammin way for Jammy Jellyfish (\u003cstrong\u003eat least that worked for me\u003c/strong\u003e)\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003enetwork\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\"\u003eversion\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e2\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\"\u003eethernets\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\"\u003eenp0s4\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\"\u003eaddresses\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e192.168.1\u003c/span\u003e\u003cspan class=\"l\"\u003e.{Static IP}/24]\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\"\u003eroutes\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\"\u003eto\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003edefault\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\"\u003evia\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e192.168.1.1\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\"\u003enameservers\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\"\u003eaddresses\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"m\"\u003e192.168.1.1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e1.1.1.1\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nI am revamping my home server and bumped myself early up to Jammy Jellyfish...\nhowever to my peril I reused my netplan config and after hitting my server with\nthe 'ol `netplan apply` I lost connection...\nDNS still seemed to kinda work externally, but internally nothing was up... \n\nTurns out Netplan got a little change in how to express the `gateway` key in the netplan config!\n\nOld Ubuntu 20.04 way\n\n```yaml\nnetwork:\n  version: 2\n  ethernets:\n    enp0s4:\n      addresses: [192.168.1.{Static IP}/24]\n      gateway4: 192.168.1.1  # \u003c-- This changes!\n      nameservers:\n        addresses: [192.168.1.1, 1.1.1.1]\n```\n\nNew jammin way for Jammy Jellyfish (__at least that worked for me__)\n```yaml\nnetwork:\n  version: 2\n  ethernets:\n    enp0s4:\n      addresses: [192.168.1.{Static IP}/24]\n      routes:\n        - to: default\n          via: 192.168.1.1 \n      nameservers:\n        addresses: [192.168.1.1, 1.1.1.1]\n```\n",
      "summary": "I am revamping my home server and bumped myself early up to Jammy Jellyfish... however to my peril I reused my netplan config and after hitting my server...",
      "date_published": "2022-05-22T13:23:17Z",
      "date_modified": "2022-05-22T13:23:17Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/how-i-use-nextcloud-for-safe-central-storage/",
      "url": "https://pype.dev/how-i-use-nextcloud-for-safe-central-storage/",
      "title": "How I use Nextcloud for safe central storage",
      "content_html": "\u003col\u003e\n\u003cli\u003eSetup admin\u003c/li\u003e\n\u003cli\u003eExternal Storage extension\u003c/li\u003e\n\u003cli\u003eAdd my nas zfs dataset\u003c/li\u003e\n\u003cli\u003echown -R www-data:www-data on anything nextcloud uploads to.\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\n1. Setup admin\n2. External Storage extension\n3. Add my nas zfs dataset\n4. chown -R www-data:www-data on anything nextcloud uploads to.\n",
      "summary": "Setup admin 2. External Storage extension 3. Add my nas zfs dataset 4. chown -R www-data:www-data on anything nextcloud uploads to.",
      "date_published": "2022-05-19T21:17:42Z",
      "date_modified": "2022-05-19T21:17:42Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "zfs",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/subset-a-list-based-on-values-in-another-list-with-itertools-compress/",
      "url": "https://pype.dev/subset-a-list-based-on-values-in-another-list-with-itertools-compress/",
      "title": "Subset a list based on values in another list with itertools.compress",
      "content_html": "\u003cp\u003eI have list [True, False, False, True] and another list [1, 2, 3, 4] and a use\ncase where I want to filter list 2 based on list 1 to remove values that line\nup with the element False in list 1\u0026hellip;. so the outcome will be [1, 4].\nlist(compress(list2, list1)) will do it. As long as you can create a mask for\nthe filter than itertool.compress will be your friend!\u003c/p\u003e\n",
      "content_text": "\nI have list [True, False, False, True] and another list [1, 2, 3, 4] and a use\ncase where I want to filter list 2 based on list 1 to remove values that line\nup with the element False in list 1.... so the outcome will be [1, 4].\nlist(compress(list2, list1)) will do it. As long as you can create a mask for\nthe filter than itertool.compress will be your friend!\n",
      "summary": "I have list [True, False, False, True] and another list [1, 2, 3, 4] and a use case where I want to filter list 2 based on list 1 to remove values that line...",
      "date_published": "2022-05-19T14:22:58Z",
      "date_modified": "2022-05-19T14:22:58Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/nextcloud-permissions-with-zfs-and-ansible-nas/",
      "url": "https://pype.dev/nextcloud-permissions-with-zfs-and-ansible-nas/",
      "title": "Nextcloud permissions with ZFS and Ansible-NAS",
      "content_html": "\u003ch1 id=\"tl-dr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTL;DR\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eAs the nextcloud docs say\u0026hellip; if you want to write to an external volume that\nlocation has to be writeable by the user/group \u003ccode\u003ewww-data\u003c/code\u003e on the host system\u0026hellip;\nso if that makes sense to you then this TIL probably isn\u0026rsquo;t a ton of value.. if\nnot however, read on :)\u003c/p\u003e\n\u003ch1 id=\"case-study\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCase Study\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eYou want to self-host your own cloud and use a smart file system for convenience\u0026hellip;\nNextcloud and ZFS are pretty common goto answers for each of those problems.\u003c/p\u003e\n\u003cp\u003eMy home NAS is built on ZFS and among other things I have a \u003ccode\u003ezpool\u003c/code\u003e named\n\u003ccode\u003etank\u003c/code\u003e and nested in there is a \u003ccode\u003etank/nas\u003c/code\u003e dataset with several child zfs\ndatasets under that.\u003c/p\u003e\n\u003cp\u003eI want to use nextcloud mainly for auto-uploading photos from my wife\u0026rsquo;s and my phones for automatic backups.\nThe issue is that the nextcloud application (I run in Docker) is fixed as the\n\u003ccode\u003ewww-data\u003c/code\u003e user and so any volume/folder that you want nextcloud to write to\nneeds to be permissioned such that \u003ccode\u003ewww-data\u003c/code\u003e owns it\u0026hellip; but I don\u0026rsquo;t want\n\u003ccode\u003ewww-data\u003c/code\u003e to own everything in my NAS\u0026hellip; so what\u0026rsquo;s a girl to do?\u003c/p\u003e\n\u003ch1 id=\"solution\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSolution\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eWell, one way to go is to just utilize docker volumes, write the data in the\ncontainer to \u003ccode\u003e/var/www/html\u003c/code\u003e and let that be the place your data backsup to.\u003c/p\u003e\n\u003cp\u003eI still wanted nextcloud to automatically write right to my NAS so I created a\n\u003ccode\u003enextcloud-upload\u003c/code\u003e directory inside of \u003ccode\u003etank/nas/media/photos\u003c/code\u003e (photos cause\nthat\u0026rsquo;s all that gets automatically uploaded)\u003c/p\u003e\n\u003cp\u003eThen I \u003ccode\u003echown -R www-data:www-data /tank/nas/media/photos/nextcloud-upload\u003c/code\u003e so\nthat just that sub-folder is owned by \u003ccode\u003ewww-data\u003c/code\u003e. Now everyone\u0026rsquo;s happy!\u003c/p\u003e\n",
      "content_text": "\n# TL;DR\n\nAs the nextcloud docs say... if you want to write to an external volume that\nlocation has to be writeable by the user/group `www-data` on the host system...\nso if that makes sense to you then this TIL probably isn't a ton of value.. if\nnot however, read on :)\n\n\n\n# Case Study\n\nYou want to self-host your own cloud and use a smart file system for convenience...\nNextcloud and ZFS are pretty common goto answers for each of those problems.\n\nMy home NAS is built on ZFS and among other things I have a `zpool` named\n`tank` and nested in there is a `tank/nas` dataset with several child zfs\ndatasets under that.\n\nI want to use nextcloud mainly for auto-uploading photos from my wife's and my phones for automatic backups.\nThe issue is that the nextcloud application (I run in Docker) is fixed as the\n`www-data` user and so any volume/folder that you want nextcloud to write to\nneeds to be permissioned such that `www-data` owns it... but I don't want\n`www-data` to own everything in my NAS... so what's a girl to do?\n\n# Solution\n\nWell, one way to go is to just utilize docker volumes, write the data in the\ncontainer to `/var/www/html` and let that be the place your data backsup to.\n\nI still wanted nextcloud to automatically write right to my NAS so I created a\n`nextcloud-upload` directory inside of `tank/nas/media/photos` (photos cause\nthat's all that gets automatically uploaded)\n\nThen I `chown -R www-data:www-data /tank/nas/media/photos/nextcloud-upload` so\nthat just that sub-folder is owned by `www-data`. Now everyone's happy!\n\n\n\n",
      "summary": "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...",
      "date_published": "2022-05-19T13:55:20Z",
      "date_modified": "2022-05-19T13:55:20Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "zfs",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/see-zfs-snapshot-disk-usage/",
      "url": "https://pype.dev/see-zfs-snapshot-disk-usage/",
      "title": "See ZFS snapshot disk usage",
      "content_html": "\u003cp\u003eAs I was cleaning up my NAS recently I noticed that I ran out of storage even\nthough my disk usage looked pretty low\u0026hellip; turns out I was keeping a mega-ton of\nZFS snapshots and due to my own ignorance at the time didn\u0026rsquo;t realize the\nstorage cost of this!\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003ezfs list-o space tank/home\u003c/code\u003e will show the disk usage of the dataset and snapshots in \u003ccode\u003etank/home\u003c/code\u003e\u003c/p\u003e\n",
      "content_text": "\nAs I was cleaning up my NAS recently I noticed that I ran out of storage even\nthough my disk usage looked pretty low... turns out I was keeping a mega-ton of\nZFS snapshots and due to my own ignorance at the time didn't realize the\nstorage cost of this!\n\n`zfs list-o space tank/home` will show the disk usage of the dataset and snapshots in `tank/home`\n",
      "summary": "As I was cleaning up my NAS recently I noticed that I ran out of storage even though my disk usage looked pretty low... turns out I was keeping a mega-ton of...",
      "date_published": "2022-05-19T06:05:23Z",
      "date_modified": "2022-05-19T06:05:23Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "zfs",
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/deleting-files-on-remote-storage-from-ubuntu-might-not-do-what-you-think/",
      "url": "https://pype.dev/deleting-files-on-remote-storage-from-ubuntu-might-not-do-what-you-think/",
      "title": "Deleting files on remote storage from Ubuntu might not do what you think",
      "content_html": "\u003cp\u003eFrom my daily driver Ubuntu machine I often open nautilus, dolphin, etc. and delete a file here or there on my NAS\u0026hellip; turns out Ubuntu sends thse file to \u003ccode\u003e.Trash-100\u003c/code\u003e ON THE NAS so I\u0026rsquo;m effectively just moving that file and not freeing up any space\u0026hellip;\u003c/p\u003e\n",
      "content_text": "\nFrom my daily driver Ubuntu machine I often open nautilus, dolphin, etc. and delete a file here or there on my NAS... turns out Ubuntu sends thse file to `.Trash-100` ON THE NAS so I'm effectively just moving that file and not freeing up any space...\n",
      "summary": "From my daily driver Ubuntu machine I often open nautilus, dolphin, etc. and delete a file here or there on my NAS... turns out Ubuntu sends thse file to ON...",
      "date_published": "2022-05-19T05:51:50Z",
      "date_modified": "2022-05-19T05:51:50Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "cli",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/plug-snapshot/",
      "url": "https://pype.dev/plug-snapshot/",
      "title": "Plug Snapshot!",
      "content_html": "\u003cp\u003e:PlugSnapshot, :w ~/dotfiles/nvim/snapshot.vim\u0026hellip; keep your config safe with git!\u003c/p\u003e\n",
      "content_text": "\n:PlugSnapshot, :w ~/dotfiles/nvim/snapshot.vim... keep your config safe with git!\n",
      "summary": ":PlugSnapshot, :w ~/dotfiles/nvim/snapshot.vim... keep your config safe with git!",
      "date_published": "2022-05-17T15:45:49Z",
      "date_modified": "2022-05-17T15:45:49Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/filepath-completion-in-neovim/",
      "url": "https://pype.dev/filepath-completion-in-neovim/",
      "title": "Filepath Completion in Neovim",
      "content_html": "\u003cp\u003eI\u0026rsquo;ve had \u003ccode\u003ePlug 'hrsh7th/cmp-path'\u003c/code\u003e in my plugins for ever but didn\u0026rsquo;t notice\nuntil recently that I wasn\u0026rsquo;t getting any filepath completion in vim!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eFuller setup instructions below the TLDR\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"tl-dr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTL;DR\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eTurns out I need to not be a dope and configure nvim-cmp to actually use it\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kd\"\u003elocal\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequire\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;cmp\u0026#39;\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\"\u003ecmp.setup\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-- removed rest of setup - see the rest in my dotfiles\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esources\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.config\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esources\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 \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;path\u0026#39;\u003c/span\u003e \u003cspan class=\"p\"\u003e},\u003c/span\u003e  \u003cspan class=\"c1\"\u003e-- This needs to be here!\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\u003c/code\u003e\u003c/pre\u003e\u003ch1 id=\"my-setup\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy Setup\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eFor the sake of completeness here is how I currently (May 2022) configure completion in Neovim usin \u003ccode\u003envim-cmp\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"plugins\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePlugins\u003c/span\u003e \u003ca href=\"#plugins\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI keep all my plugins in \u003ccode\u003eplugins.vim\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ecall\u003c/span\u003e \u003cspan class=\"nx\"\u003eplug\u003c/span\u003e#\u003cspan class=\"nx\"\u003ebegin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nx\"\u003es\u003c/span\u003e:\u003cspan class=\"nx\"\u003eplug_dir\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=\"nx\"\u003ePlug\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;neovim/nvim-lspconfig\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ePlug\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;hrsh7th/cmp-nvim-lsp\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ePlug\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;hrsh7th/cmp-buffer\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ePlug\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;hrsh7th/cmp-path\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ePlug\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;hrsh7th/cmp-cmdline\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ePlug\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;hrsh7th/nvim-cmp\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e\u0026#34; For ultisnips users.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;!--\u003c/span\u003e \u003cspan class=\"c\"\u003e\u0026#34; Plug \u0026#39;SirVer/ultisnips\u0026#39; --\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;!--\u003c/span\u003e \u003cspan class=\"c\"\u003e\u0026#34; Plug \u0026#39;quangnguyen30192/cmp-nvim-ultisnips\u0026#39; --\u0026gt;\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=\"nx\"\u003ecall\u003c/span\u003e \u003cspan class=\"nx\"\u003eplug\u003c/span\u003e#\u003cspan class=\"nx\"\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\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"vim-settings\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eVim Settings\u003c/span\u003e \u003ca href=\"#vim-settings\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eMy vim settings are also kept in their own file, \u003ccode\u003esettings.vim\u003c/code\u003e\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=\"k\"\u003eset\u003c/span\u003e \u003cspan class=\"nx\"\u003ecompleteopt\u003c/span\u003e\u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"nx\"\u003emenu\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003emenuone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"nx\"\u003enoselect\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\u003ch2 id=\"nvim-cmp-configuration\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003envim-cmp configuration\u003c/span\u003e \u003ca href=\"#nvim-cmp-configuration\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI have a \u003ccode\u003ecmp.lua\u003c/code\u003e file that gets sourced in \u003ccode\u003einit.lua\u003c/code\u003e (file structure explained below) for configuring cmp.\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-- Setup nvim-cmp.\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\"\u003ecmp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erequire\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;cmp\u0026#39;\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\"\u003ecmp.setup\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\"\u003esnippet\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=\"c1\"\u003e-- REQUIRED - you must specify a snippet engine\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eexpand\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kr\"\u003efunction\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eargs\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-- For `ultisnips` user.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e      \u003cspan class=\"n\"\u003evim.fn\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;UltiSnips#Anon\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e](\u003c/span\u003e\u003cspan class=\"n\"\u003eargs.body\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\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003ewindow\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=\"n\"\u003ecompletion\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.config\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ewindow.bordered\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\"\u003emapping\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;Down\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect_next_item\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"n\"\u003ebehavior\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.SelectBehavior\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSelect\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;Up\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect_prev_item\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e \u003cspan class=\"n\"\u003ebehavior\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.SelectBehavior\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSelect\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;C-d\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003escroll_docs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;C-f\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003escroll_docs\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e4\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;C-Space\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecomplete\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;C-e\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;Tab\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect_next_item\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;i\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;s\u0026#39;\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\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;CR\u0026gt;\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.mapping\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econfirm\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\"\u003ebehavior\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.ConfirmBehavior\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eReplace\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\"\u003eselect\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\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=\"p\"\u003e},\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"n\"\u003esources\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecmp.config\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esources\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 \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;nvim_lsp\u0026#39;\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 \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;ultisnips\u0026#39;\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 \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;buffer\u0026#39;\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 \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;path\u0026#39;\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 \u003cspan class=\"n\"\u003ename\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;tmux\u0026#39;\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=\"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\u003cp\u003eThe \u003ccode\u003esources\u003c/code\u003e section is what was key for this post\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"piecing-it-together\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePiecing it together!\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eMy \u003ccode\u003einit.vim\u003c/code\u003e sources plugins and then settings and then finally calls \u003ccode\u003einit.lua\u003c/code\u003e.\n\u003ccode\u003einit.lua\u003c/code\u003e sources my \u003ccode\u003ecmp.lua\u003c/code\u003e file and BANG! auto-completion.\u003c/p\u003e\n\u003ch2 id=\"more-sources\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMore sources\u003c/span\u003e \u003ca href=\"#more-sources\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003ehrsh7th\u0026rsquo;s wiki for \u003ccode\u003envim-cmp\u003c/code\u003e is \u003ca href=\"https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources\"\u003ehere\u003c/a\u003e and has example configs as well as a list of sources\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eDon\u0026rsquo;t forget to configure and not just install!\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/nicpayne713/dotfiles\"\u003emy dotfiles\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\n\nI've had `Plug 'hrsh7th/cmp-path'` in my plugins for ever but didn't notice\nuntil recently that I wasn't getting any filepath completion in vim!\n\n__Fuller setup instructions below the TLDR__\n\n# TL;DR\n\nTurns out I need to not be a dope and configure nvim-cmp to actually use it...\n\n\n```lua\nlocal cmp = require'cmp'\n\ncmp.setup({\n    -- removed rest of setup - see the rest in my dotfiles\n  sources = cmp.config.sources({\n    { name = 'path' },  -- This needs to be here!\n    })\n})\n```\n\n# My Setup\n\nFor the sake of completeness here is how I currently (May 2022) configure completion in Neovim usin `nvim-cmp`\n\n## Plugins\n\nI keep all my plugins in `plugins.vim`\n\n```vim\ncall plug#begin(s:plug_dir)\nPlug 'neovim/nvim-lspconfig'\nPlug 'hrsh7th/cmp-nvim-lsp'\nPlug 'hrsh7th/cmp-buffer'\nPlug 'hrsh7th/cmp-path'\nPlug 'hrsh7th/cmp-cmdline'\nPlug 'hrsh7th/nvim-cmp'\n\n\" For ultisnips users.\n\u003c!-- \" Plug 'SirVer/ultisnips' --\u003e\n\u003c!-- \" Plug 'quangnguyen30192/cmp-nvim-ultisnips' --\u003e\n\ncall plug#end()\n\n```\n\n## Vim Settings\n\nMy vim settings are also kept in their own file, `settings.vim`\n\n```vim\n\nset completeopt=menu,menuone,noselect\n\n```\n\n## nvim-cmp configuration\n\nI have a `cmp.lua` file that gets sourced in `init.lua` (file structure explained below) for configuring cmp.\n\n```lua\n\n  -- Setup nvim-cmp.\nlocal cmp = require'cmp'\n\ncmp.setup({\n  snippet = {\n    -- REQUIRED - you must specify a snippet engine\n    expand = function(args)\n      -- For `ultisnips` user.\n      vim.fn[\"UltiSnips#Anon\"](args.body)\n    end,\n  },\n  window = {\n      completion = cmp.config.window.bordered(),\n  },\n  mapping = {\n    ['\u003cDown\u003e'] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Select }),\n    ['\u003cUp\u003e'] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Select }),\n    ['\u003cC-d\u003e'] = cmp.mapping.scroll_docs(-4),\n    ['\u003cC-f\u003e'] = cmp.mapping.scroll_docs(4),\n    ['\u003cC-Space\u003e'] = cmp.mapping.complete(),\n    ['\u003cC-e\u003e'] = cmp.mapping.close(),\n    ['\u003cTab\u003e'] = cmp.mapping(cmp.mapping.select_next_item(), { 'i', 's' }),\n    ['\u003cCR\u003e'] = cmp.mapping.confirm({\n      behavior = cmp.ConfirmBehavior.Replace,\n      select = true,\n    })\n  },\n  sources = cmp.config.sources({\n    { name = 'nvim_lsp' },\n    { name = 'ultisnips' },\n    { name = 'buffer' },\n    { name = 'path' },\n    { name = 'tmux' },\n    })\n})\n\n```\n\n\nThe `sources` section is what was key for this post...\n\n# Piecing it together!\n\nMy `init.vim` sources plugins and then settings and then finally calls `init.lua`.\n`init.lua` sources my `cmp.lua` file and BANG! auto-completion.\n\n## More sources\n\nhrsh7th's wiki for `nvim-cmp` is [here](https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources) and has example configs as well as a list of sources...\n\n__Don't forget to configure and not just install!__\n\n[my dotfiles](https://github.com/nicpayne713/dotfiles)\n",
      "summary": "I've had in my plugins for ever but didn't notice until recently that I wasn't getting any filepath completion in vim!",
      "date_published": "2022-05-17T14:03:27Z",
      "date_modified": "2022-05-17T14:03:27Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/fastapi-jinja-forms/",
      "url": "https://pype.dev/fastapi-jinja-forms/",
      "title": "Forms with FastAPI and Jinja",
      "content_html": "\u003cp\u003eI just started using FastAPI for a home project and needed to pass back a\ndynamic number of values from a form rendered with jinja\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"dynamic-values\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDynamic Values\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eThe jinja templating for rendering HTML based on something like a python iterable is nice and easy\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003edata is the result of a database query, and item is each row, so the dot notation is the value of each column basically in that row\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;form method=\u0026#34;post\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e  \u003c/span\u003e\u003cspan class=\"cp\"\u003e{%\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"nv\"\u003edata\u003c/span\u003e \u003cspan class=\"cp\"\u003e%}\u003c/span\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;div class=\u0026#34;form-check \u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;input class=\u0026#34;form-check-input\u0026#34;  name=\u0026#34;item_\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.id\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; id=\u0026#34;\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.name\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; value=\u0026#34;\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.id\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; type=\u0026#34;checkbox\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;label class=\u0026#34;form-check-label\u0026#34; for=\u0026#34;\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.id\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; \u0026gt; Label for: \u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.name\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e \u0026lt;/label\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;/div\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e  \u003c/span\u003e\u003cspan class=\"cp\"\u003e{%\u003c/span\u003e \u003cspan class=\"k\"\u003eendfor\u003c/span\u003e \u003cspan class=\"cp\"\u003e%}\u003c/span\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;submit btn btn-xl btn-outline-danger\u0026#34; \u0026gt;Remove\u0026lt;/button\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;/form\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThis form generates a row with a checkbox for every \u003ccode\u003eitem\u003c/code\u003e in \u003ccode\u003edata\u003c/code\u003e (in my\ncase each \u003ccode\u003eitem\u003c/code\u003e is an existing row in my table). it?\u003c/p\u003e\n\u003cp\u003eThe way to pass back all those values is pretty straight forward (after hours of messing around that is!)\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# I hate it when tutorials don\u0026#39;t show ALL relevant pieces to the blurb\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\"\u003estarlette.status\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003estatus\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\"\u003efastapi\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eAPIRouter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eForm\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eRequest\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\"\u003efastapi.encoders\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ejsonable_encoder\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\"\u003efastapi.responses\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eHTMLResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eRedirectResponse\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\"\u003efastapi.templating\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eJinja2Templates\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\"\u003esqlalchemy.orm\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\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\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eapp.session.session\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003erouter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAPIRouter\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\"\u003etemplates\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eJinja2Templates\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edirectory\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;templates/\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\u003cspan class=\"nd\"\u003e@router.post\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/my_route/do_something_with_form\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_class\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eHTMLResponse\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003edelete_rows\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\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eRequest\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\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003eform_data\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_form\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\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ejsonable_encoder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eform_data\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# data = {\u0026#34;item_1\u0026#34;: 1, \u0026#34;item_2\u0026#34;: 2, ... \u0026#34;item_N\u0026#34;: N}\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=\"n\"\u003eRedirectResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_302_FOUND\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWe \u003ccode\u003eawait request.get_form()\u003c/code\u003e and after encoding the data we get a dictionary with key/value pairs of the name/value from the form!\u003c/p\u003e\n\u003cp\u003eThis took me quite a long time to figure out in part because most of the Google-able resources are still on Flask\u0026hellip;\u003c/p\u003e\n",
      "content_text": "\nI just started using FastAPI for a home project and needed to pass back a\ndynamic number of values from a form rendered with jinja...\n\n\n# Dynamic Values \n\nThe jinja templating for rendering HTML based on something like a python iterable is nice and easy\n\n\u003e data is the result of a database query, and item is each row, so the dot notation is the value of each column basically in that row\n\n```jinja\n\u003cform method=\"post\"\u003e\n  {% for item in data %}\n    \u003cdiv class=\"form-check \"\u003e\n        \u003cinput class=\"form-check-input\"  name=\"item_{{ item.id }}\" id=\"{{ item.name }}\" value=\"{{ item.id }}\" type=\"checkbox\"\u003e\n        \u003clabel class=\"form-check-label\" for=\"{{ item.id }}\" \u003e Label for: {{ item.name }} \u003c/label\u003e\n    \u003c/div\u003e\n  {% endfor %}\n\n\u003cbutton type=\"submit\" class=\"submit btn btn-xl btn-outline-danger\" \u003eRemove\u003c/button\u003e\n\u003c/form\u003e\n\n```\n\nThis form generates a row with a checkbox for every `item` in `data` (in my\ncase each `item` is an existing row in my table). it?\n\nThe way to pass back all those values is pretty straight forward (after hours of messing around that is!)\n\n```python\n# I hate it when tutorials don't show ALL relevant pieces to the blurb\nimport starlette.status as status\nfrom fastapi import APIRouter, Depends, Form, Request\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.responses import HTMLResponse, RedirectResponse\nfrom fastapi.templating import Jinja2Templates\nfrom sqlalchemy.orm import Session\n\nfrom app.session.session import create_get_session\n\nrouter = APIRouter()\ntemplates = Jinja2Templates(directory=\"templates/\")\n\n@router.post(\"/my_route/do_something_with_form\", response_class=HTMLResponse)\nasync def delete_rows(\n    request: Request,\n    db: Session = Depends(create_get_session),\n):\n    form_data = await request.get_form()\n    data = jsonable_encoder(form_data)\n    # data = {\"item_1\": 1, \"item_2\": 2, ... \"item_N\": N}\n    return RedirectResponse(\"/\", status_code=status.HTTP_302_FOUND)\n```\n\nWe `await request.get_form()` and after encoding the data we get a dictionary with key/value pairs of the name/value from the form!\n\nThis took me quite a long time to figure out in part because most of the Google-able resources are still on Flask...\n",
      "summary": "I just started using FastAPI for a home project and needed to pass back a dynamic number of values from a form rendered with jinja...",
      "date_published": "2022-05-15T00:00:00Z",
      "date_modified": "2022-05-15T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/dynamic-form-values-with-jinja-and-fastapi/",
      "url": "https://pype.dev/dynamic-form-values-with-jinja-and-fastapi/",
      "title": "Dynamic-Form-Values-With-Jinja-And-Fastapi",
      "content_html": "\u003cp\u003eI\u0026rsquo;m currently working on a self-hostable wish list app using FastAPI so we can\nfinally drop Amazon forever. (The lists funcionality has been super handy for\nsharing holiday gift ideas with the famj!)\u003c/p\u003e\n\u003ch1 id=\"fastapi\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFastAPI\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eFastAPI is an amazing framework for quickly building APIs with Python. I will have a slightly longer post about my brief experience with it coming later\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"jinja-forms-and-fastapi\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eJinja, Forms, and FastAPI\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eOne of the last things I needed to figure out in my app was how to generate a\nform in a Jinja template with a dynamic number of inputs and then pass all the\ninputs to the backend to perform a database operation (my exact case was\nremoving rows from a table).\u003c/p\u003e\n\u003ch2 id=\"explicit-values\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eExplicit Values\u003c/span\u003e \u003ca href=\"#explicit-values\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe way to pass back explicit variables is really easy\u0026hellip;\u003c/p\u003e\n\u003cp\u003eOur form would look like this (I\u0026rsquo;m using bootstrap CSS)\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;form method=\u0026#34;post\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;div class=\u0026#34;form-check \u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;input class=\u0026#34;form-check-input\u0026#34;  name=\u0026#34;item_1\u0026#34; id=\u0026#34;itemOne\u0026#34; value=\u0026#34;1\u0026#34; type=\u0026#34;checkbox\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;label class=\u0026#34;form-check-label\u0026#34; for=\u0026#34;itemOne\u0026#34; \u0026gt; A label for this item \u0026lt;/label\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;/div\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;div class=\u0026#34;form-check \u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;input class=\u0026#34;form-check-input\u0026#34;  name=\u0026#34;item_2\u0026#34; id=\u0026#34;itemTwo\u0026#34; value=\u0026#34;2\u0026#34; type=\u0026#34;checkbox\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;label class=\u0026#34;form-check-label\u0026#34; for=\u0026#34;itemTwo\u0026#34; \u0026gt; A label for item 2 \u0026lt;/label\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;/div\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;submit btn btn-xl\u0026#34; \u0026gt;Submit\u0026lt;/button\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;/form\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo what is this? This form will have 2 rows with the lables you see in \u003ccode\u003e\u0026lt;label\u0026gt; \u0026lt;/label\u0026gt;\u003c/code\u003e and checkboxes that when checked would have the value \u003ccode\u003evalue\u003c/code\u003e in each\n\u003ccode\u003e\u0026lt;input\u0026gt;\u003c/code\u003e line.\u003c/p\u003e\n\u003cp\u003eSo our backend might looks something like this\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eI\u0026rsquo;m keeping all the imports and stuff here to show where they come from but I won\u0026rsquo;t discuss it all here - that\u0026rsquo;ll be in a future post\u003c/strong\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003estarlette.status\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003estatus\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\"\u003efastapi\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eAPIRouter\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eForm\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eRequest\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\"\u003efastapi.encoders\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ejsonable_encoder\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\"\u003efastapi.responses\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eHTMLResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eRedirectResponse\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\"\u003efastapi.templating\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eJinja2Templates\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\"\u003esqlalchemy.orm\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\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\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eapp.session.session\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003erouter\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eAPIRouter\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\"\u003etemplates\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eJinja2Templates\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edirectory\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;templates/\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\u003cspan class=\"nd\"\u003e@router.post\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/my_route/do_something_with_form\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_class\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eHTMLResponse\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003edelete_rows\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\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eRequest\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\"\u003eitem_1\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eForm\u003c/span\u003e\u003cspan class=\"p\"\u003e(\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=\"n\"\u003eitem_2\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eForm\u003c/span\u003e\u003cspan class=\"p\"\u003e(\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=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eitem_1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# will just print 1 to the console where fastapi is running if the checkbox was checked\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=\"n\"\u003eitem_2\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# will just print 1 to the console where fastapi is running if the checkbox was checked\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=\"n\"\u003eRedirectResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_302_FOUND\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"dynamic-values\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDynamic values\u003c/span\u003e \u003ca href=\"#dynamic-values\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThat\u0026rsquo;s all pretty simple\u0026hellip; pass back values by the name in the form\u0026hellip;\u003c/p\u003e\n\u003cp\u003eWhat about a form that\u0026rsquo;s generated dynamically? This is my case since I display a row/checkbox for every row in my table so my form looks like this\u0026hellip;\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003edata is the result of a database query, and item is each row, so the dot notation is the value of each column basically in that row\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;form method=\u0026#34;post\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e  \u003c/span\u003e\u003cspan class=\"cp\"\u003e{%\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem\u003c/span\u003e \u003cspan class=\"k\"\u003ein\u003c/span\u003e \u003cspan class=\"nv\"\u003edata\u003c/span\u003e \u003cspan class=\"cp\"\u003e%}\u003c/span\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;div class=\u0026#34;form-check \u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;input class=\u0026#34;form-check-input\u0026#34;  name=\u0026#34;item_\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.id\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; id=\u0026#34;\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.name\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; value=\u0026#34;\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.id\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; type=\u0026#34;checkbox\u0026#34;\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e        \u0026lt;label class=\u0026#34;form-check-label\u0026#34; for=\u0026#34;\u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.id\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e\u0026#34; \u0026gt; Label for: \u003c/span\u003e\u003cspan class=\"cp\"\u003e{{\u003c/span\u003e \u003cspan class=\"nv\"\u003eitem.name\u003c/span\u003e \u003cspan class=\"cp\"\u003e}}\u003c/span\u003e\u003cspan class=\"x\"\u003e \u0026lt;/label\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e    \u0026lt;/div\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e  \u003c/span\u003e\u003cspan class=\"cp\"\u003e{%\u003c/span\u003e \u003cspan class=\"k\"\u003eendfor\u003c/span\u003e \u003cspan class=\"cp\"\u003e%}\u003c/span\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;button type=\u0026#34;submit\u0026#34; class=\u0026#34;submit btn btn-xl btn-outline-danger\u0026#34; \u0026gt;Remove\u0026lt;/button\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\u0026lt;/form\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"x\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThis form generates a row with a checkbox for every \u003ccode\u003eitem\u003c/code\u003e in \u003ccode\u003edata\u003c/code\u003e (in my\ncase each \u003ccode\u003eitem\u003c/code\u003e is an existing row in my table). Now I started scratching my\nhead on how to pass an unknown number of inputs to my backend of FastAPI wants\neach input explicitly defined and typed\u0026hellip; I can\u0026rsquo;t just pass the form back\nbecuase that\u0026rsquo;s not a thing so what\u0026rsquo;s the way to do it?\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# same stuff as above, only showing post method here\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nd\"\u003e@router.post\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/my_route/do_something_with_form\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_class\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eHTMLResponse\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003edelete_rows\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\"\u003erequest\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eRequest\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\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003eform_data\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"k\"\u003eawait\u003c/span\u003e \u003cspan class=\"n\"\u003erequest\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_form\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\"\u003edata\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ejsonable_encoder\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eform_data\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# data = {\u0026#34;item_1\u0026#34;: 1, \u0026#34;item_2\u0026#34;: 2, ... \u0026#34;item_N\u0026#34;: N}\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=\"n\"\u003eRedirectResponse\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003estatus\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHTTP_302_FOUND\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWe \u003ccode\u003eawait request.get_form()\u003c/code\u003e and after encoding the data we get a dictionary with key/value pairs of the name/value from the form!\u003c/p\u003e\n\u003cp\u003eThis took me quite a long time to figure out in part because most of the Google-able resources are still on Flask\u0026hellip;\u003c/p\u003e\n\u003cp\u003eI look forward to my wish list app maturing and I hope this helps someone working with FastAPI!\u003c/p\u003e\n",
      "content_text": "\nI'm currently working on a self-hostable wish list app using FastAPI so we can\nfinally drop Amazon forever. (The lists funcionality has been super handy for\nsharing holiday gift ideas with the famj!)\n\n# FastAPI\n\nFastAPI is an amazing framework for quickly building APIs with Python. I will have a slightly longer post about my brief experience with it coming later...\n\n# Jinja, Forms, and FastAPI\n\nOne of the last things I needed to figure out in my app was how to generate a\nform in a Jinja template with a dynamic number of inputs and then pass all the\ninputs to the backend to perform a database operation (my exact case was\nremoving rows from a table).\n\n## Explicit Values\n\nThe way to pass back explicit variables is really easy...\n\nOur form would look like this (I'm using bootstrap CSS)\n\n```jinja\n\u003cform method=\"post\"\u003e\n    \u003cdiv class=\"form-check \"\u003e\n        \u003cinput class=\"form-check-input\"  name=\"item_1\" id=\"itemOne\" value=\"1\" type=\"checkbox\"\u003e\n        \u003clabel class=\"form-check-label\" for=\"itemOne\" \u003e A label for this item \u003c/label\u003e\n    \u003c/div\u003e\n    \u003cdiv class=\"form-check \"\u003e\n        \u003cinput class=\"form-check-input\"  name=\"item_2\" id=\"itemTwo\" value=\"2\" type=\"checkbox\"\u003e\n        \u003clabel class=\"form-check-label\" for=\"itemTwo\" \u003e A label for item 2 \u003c/label\u003e\n    \u003c/div\u003e\n\n\u003cbutton type=\"submit\" class=\"submit btn btn-xl\" \u003eSubmit\u003c/button\u003e\n\u003c/form\u003e\n```\n\nSo what is this? This form will have 2 rows with the lables you see in `\u003clabel\u003e\n\u003c/label\u003e` and checkboxes that when checked would have the value `value` in each\n`\u003cinput\u003e` line.\n\nSo our backend might looks something like this...\n\n__I'm keeping all the imports and stuff here to show where they come from but I won't discuss it all here - that'll be in a future post__\n\n```python\nimport starlette.status as status\nfrom fastapi import APIRouter, Depends, Form, Request\nfrom fastapi.encoders import jsonable_encoder\nfrom fastapi.responses import HTMLResponse, RedirectResponse\nfrom fastapi.templating import Jinja2Templates\nfrom sqlalchemy.orm import Session\n\nfrom app.session.session import create_get_session\n\nrouter = APIRouter()\ntemplates = Jinja2Templates(directory=\"templates/\")\n\n@router.post(\"/my_route/do_something_with_form\", response_class=HTMLResponse)\nasync def delete_rows(\n    request: Request,\n    item_1: int = Form(...),\n    item_2: int = Form(...)\n    db: Session = Depends(create_get_session),\n):\n    print(item_1)  # will just print 1 to the console where fastapi is running if the checkbox was checked\n    print(item_2)  # will just print 1 to the console where fastapi is running if the checkbox was checked\n    return RedirectResponse(\"/\", status_code=status.HTTP_302_FOUND)\n```\n\n\n## Dynamic values\n\nThat's all pretty simple... pass back values by the name in the form...\n\nWhat about a form that's generated dynamically? This is my case since I display a row/checkbox for every row in my table so my form looks like this...\n\n\u003e data is the result of a database query, and item is each row, so the dot notation is the value of each column basically in that row\n\n```jinja\n\u003cform method=\"post\"\u003e\n  {% for item in data %}\n    \u003cdiv class=\"form-check \"\u003e\n        \u003cinput class=\"form-check-input\"  name=\"item_{{ item.id }}\" id=\"{{ item.name }}\" value=\"{{ item.id }}\" type=\"checkbox\"\u003e\n        \u003clabel class=\"form-check-label\" for=\"{{ item.id }}\" \u003e Label for: {{ item.name }} \u003c/label\u003e\n    \u003c/div\u003e\n  {% endfor %}\n\n\u003cbutton type=\"submit\" class=\"submit btn btn-xl btn-outline-danger\" \u003eRemove\u003c/button\u003e\n\u003c/form\u003e\n\n```\n\nThis form generates a row with a checkbox for every `item` in `data` (in my\ncase each `item` is an existing row in my table). Now I started scratching my\nhead on how to pass an unknown number of inputs to my backend of FastAPI wants\neach input explicitly defined and typed... I can't just pass the form back\nbecuase that's not a thing so what's the way to do it?\n\n\n```python\n# same stuff as above, only showing post method here\n@router.post(\"/my_route/do_something_with_form\", response_class=HTMLResponse)\nasync def delete_rows(\n    request: Request,\n    db: Session = Depends(create_get_session),\n):\n    form_data = await request.get_form()\n    data = jsonable_encoder(form_data)\n    # data = {\"item_1\": 1, \"item_2\": 2, ... \"item_N\": N}\n    return RedirectResponse(\"/\", status_code=status.HTTP_302_FOUND)\n```\n\nWe `await request.get_form()` and after encoding the data we get a dictionary with key/value pairs of the name/value from the form!\n\nThis took me quite a long time to figure out in part because most of the Google-able resources are still on Flask...\n\nI look forward to my wish list app maturing and I hope this helps someone working with FastAPI!\n",
      "summary": "I'm currently working on a self-hostable wish list app using FastAPI so we can finally drop Amazon forever. (The lists funcionality has been super handy for...",
      "date_published": "2022-05-15T00:00:00Z",
      "date_modified": "2022-05-15T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/plug-snapshot-to-save-your-life/",
      "url": "https://pype.dev/plug-snapshot-to-save-your-life/",
      "title": "Plug-Snapshot-To-Save-Your-Life",
      "content_html": "\u003cp\u003eIf you use vim-plug for managing your vim plugins, do yourself a favor and snapshot your plugins before upgrading!\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003e:PlugSnapshot\u003c/code\u003e  creates a vim.snapshot file that you can use to restore your plugin versions with \u003ccode\u003evim -S snapshot.vim\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eThe snapshot file looks like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e❯ cat snapshot.vim\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e\u0026#34; Generated by vim-plug\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e\u0026#34; Tue 17 May 2022 03:44:29 PM CDT\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e\u0026#34; :source this file in vim to restore the snapshot\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003e\u0026#34; or execute: vim -S snapshot.vim\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;Telegraph.nvim\u0026#39;].commit = \u0026#39;92e472f4e83acd60eb3766168e66d02718bfefe0\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;TrueZen.nvim\u0026#39;].commit = \u0026#39;508b977d71650da5c9243698614a9a1416f116d4\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;ariake-vim-colors\u0026#39;].commit = \u0026#39;9fb35f1255e475631c9df24ecc5485a40360cc7b\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;auto-pairs\u0026#39;].commit = \u0026#39;39f06b873a8449af8ff6a3eee716d3da14d63a76\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;bufutils.vim\u0026#39;].commit = \u0026#39;4634feb1312fd73fab66cfaa860e7af3abde935b\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;cmp-buffer\u0026#39;].commit = \u0026#39;12463cfcd9b14052f9effccbf1d84caa7a2d57f0\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;cmp-cmdline\u0026#39;].commit = \u0026#39;c36ca4bc1dedb12b4ba6546b96c43896fd6e7252\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;cmp-nvim-lsp\u0026#39;].commit = \u0026#39;e6b5feb2e6560b61f31c756fb9231a0d7b10c73d\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;cmp-nvim-ultisnips\u0026#39;].commit = \u0026#39;21f02b62deb409ce69928a23406076bd0043ddbc\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;cmp-path\u0026#39;].commit = \u0026#39;466b6b8270f7ba89abd59f402c73f63c7331ff6e\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;cmp-spell\u0026#39;].commit = \u0026#39;5602f1a0de7831f8dad5b0c6db45328fbd539971\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;compe-tabnine\u0026#39;].commit = \u0026#39;33e4af509c27da9ef2c9c3002c01e3ec031797d4\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;coverage-highlight.vim\u0026#39;].commit = \u0026#39;864e03679ea4168661501246147893cc82020917\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;diffurcate.vim\u0026#39;].commit = \u0026#39;b804675072220ff7c7ebcd24a028aa4aa35f09cc\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;fzf\u0026#39;].commit = \u0026#39;6dcf5c3d7d6c321b17e6a5673f1533d6e8350462\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;fzf.vim\u0026#39;].commit = \u0026#39;d5f1f8641b24c0fd5b10a299824362a2a1b20ae0\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;git-blame.vim\u0026#39;].commit = \u0026#39;9d144b7bed5d8f1c9259551768b7f3b3d1294917\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;harpoon\u0026#39;].commit = \u0026#39;d3d3d22b6207f46f8ca64946f4d781e975aec0fc\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;instant.nvim\u0026#39;].commit = \u0026#39;c02d72267b12130609b7ad39b76cf7f4a3bc9554\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;lsp-colors.nvim\u0026#39;].commit = \u0026#39;517fe3ab6b63f9907b093bc9443ef06b56f804f3\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;lsp_extensions.nvim\u0026#39;].commit = \u0026#39;4011f4aec61ba59c734f5dbf52e91f258b99d985\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;lspkind-nvim\u0026#39;].commit = \u0026#39;57e5b5dfbe991151b07d272a06e365a77cc3d0e7\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;lspsaga.nvim\u0026#39;].commit = \u0026#39;8dde091a61ab07f639baaa82b456d3508d0aa7e8\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;neoformat\u0026#39;].commit = \u0026#39;409ebbba9f4b568ea87ab4f2de90a645cf5d000a\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;neovim-fuzzy\u0026#39;].commit = \u0026#39;0bef4e1a81c65fc05d31380dd74454bd67733837\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nerdtree\u0026#39;].commit = \u0026#39;eed488b1cd1867bd25f19f90e10440c5cc7d6424\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nerdtree-visual-selection\u0026#39;].commit = \u0026#39;05427635ff053a2c542fcf8d7c3744c72575e76c\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-cmp\u0026#39;].commit = \u0026#39;a226b6a4ff72e5e809ed17734318233fb25c87f3\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-compe\u0026#39;].commit = \u0026#39;d186d739c54823e0b010feb205c6f97792322c08\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-lspconfig\u0026#39;].commit = \u0026#39;9ff2a06cebd4c8c3af5259d713959ab310125bec\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-lspinstall\u0026#39;].commit = \u0026#39;79ec2425d6b39cdcb69d379f3e56847f49be73eb\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-treesitter\u0026#39;].commit = \u0026#39;10d57b3ec14cac0b6b759e1eb5594617b8a7e883\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-treesitter-textobjects\u0026#39;].commit = \u0026#39;094e8ad3cc839e825f8dcc91352837653e365a8f\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;nvim-web-devicons\u0026#39;].commit = \u0026#39;bdd43421437f2ef037e0dafeaaaa62b31d35ef2f\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;playground\u0026#39;].commit = \u0026#39;71b00a3c665298e5155ad64a9020135808d4e3e8\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;plenary.nvim\u0026#39;].commit = \u0026#39;25b3475b97f241e6f76249747bc209e70c5d2ec8\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;popup.nvim\u0026#39;].commit = \u0026#39;b7404d35d5d3548a82149238289fa71f7f6de4ac\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;syntastic\u0026#39;].commit = \u0026#39;b7f4f71539038d33f173bfa72631737da049575a\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;tabular\u0026#39;].commit = \u0026#39;339091ac4dd1f17e225fe7d57b48aff55f99b23a\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;targets.vim\u0026#39;].commit = \u0026#39;8d6ff2984cdfaebe5b7a6eee8f226a6dd1226f2d\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;telescope-fzy-native.nvim\u0026#39;].commit = \u0026#39;7b3d2528102f858036627a68821ccf5fc1d78ce4\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;telescope.nvim\u0026#39;].commit = \u0026#39;39b12d84e86f5054e2ed98829b367598ae53ab41\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;termopen.vim\u0026#39;].commit = \u0026#39;3194a991a18a9be2fd9fcf8c4c55fe990c04b2bd\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;tokyonight.nvim\u0026#39;].commit = \u0026#39;8223c970677e4d88c9b6b6d81bda23daf11062bb\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;trouble.nvim\u0026#39;].commit = \u0026#39;da61737d860ddc12f78e638152834487eabf0ee5\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;ultisnips\u0026#39;].commit = \u0026#39;f5ccf0977c611ffd774ca180774959301baaffad\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;undotree\u0026#39;].commit = \u0026#39;08e259be24d4476c1ee745dc735eefd44f90efdc\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-airline\u0026#39;].commit = \u0026#39;c4655701431a9c79704c827fd88a4783ec946879\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-airline-themes\u0026#39;].commit = \u0026#39;97cf3e6e638f936187d5f6e9b5eb1bdf0a4df256\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-be-good\u0026#39;].commit = \u0026#39;bc499a06c14c729b22a6cc7e730a9fbc44d4e737\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-commentary\u0026#39;].commit = \u0026#39;3654775824337f466109f00eaf6759760f65be34\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-devicons\u0026#39;].commit = \u0026#39;a2258658661e42dd4cdba4958805dbad1fe29ef4\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-dispatch\u0026#39;].commit = \u0026#39;00e77d90452e3c710014b26dc61ea919bc895e92\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-flake8\u0026#39;].commit = \u0026#39;a99054ef98e8fdaefa1315af4649138bcadbfdf7\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-floaterm\u0026#39;].commit = \u0026#39;ab7876f86c05c1935eb23a193f4f276132902ac1\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-fugitive\u0026#39;].commit = \u0026#39;f529acef74b4266d94f22414c60b4a8930c1e0f3\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-gitbranch\u0026#39;].commit = \u0026#39;1a8ba866f3eaf0194783b9f8573339d6ede8f1ed\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-indent-object\u0026#39;].commit = \u0026#39;5c5b24c959478929b54a9e831a8e2e651a465965\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-nerdtree-syntax-highlight\u0026#39;].commit = \u0026#39;5178ee4d7f4e7761187df30bb709f703d91df18a\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-polyglot\u0026#39;].commit = \u0026#39;38282d58387cff48ac203f6912c05e4c8686141b\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-pydocstring\u0026#39;].commit = \u0026#39;f6e3c52bfaf4b4f76dab4d84e75b94199d5f3b9c\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-quicklink\u0026#39;].commit = \u0026#39;021167741588555501594e1fc31f130b16acefa0\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-repeat\u0026#39;].commit = \u0026#39;24afe922e6a05891756ecf331f39a1f6743d3d5a\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-signify\u0026#39;].commit = \u0026#39;69498f6d49f3eeac06870012416dd9bf867b84f3\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-sneak\u0026#39;].commit = \u0026#39;94c2de47ab301d476a2baec9ffda07367046bec9\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-snippets\u0026#39;].commit = \u0026#39;6f270bb2d26c38765ff2243e9337c65f8a96a28b\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-startify\u0026#39;].commit = \u0026#39;81e36c352a8deea54df5ec1e2f4348685569bed2\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-surround\u0026#39;].commit = \u0026#39;bf3480dc9ae7bea34c78fbba4c65b4548b5b1fea\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-test\u0026#39;].commit = \u0026#39;2240d7a4b868cb594b7d83544e1b6db4df806e5e\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-tmux-runner\u0026#39;].commit = \u0026#39;54767911fd5e6e2d8e493847149e315ac2e6531a\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-ultest\u0026#39;].commit = \u0026#39;6978fd32e3ca2c1c5591884eea0d57a7ee43d212\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vim-visualstar\u0026#39;].commit = \u0026#39;a18cd0e7a03311ac709595c1d261ed44b45c9098\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;vimtex\u0026#39;].commit = \u0026#39;dfaca59bbbf0079ab1b4f159337ae7f17d1b5289\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003esilent! let g:plugs[\u0026#39;which-key.nvim\u0026#39;].commit = \u0026#39;bd4411a2ed4dd8bb69c125e339d837028a6eea71\u0026#39;\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"go\"\u003ePlugUpdate!\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nIf you use vim-plug for managing your vim plugins, do yourself a favor and snapshot your plugins before upgrading!\n\n\n`:PlugSnapshot`  creates a vim.snapshot file that you can use to restore your plugin versions with `vim -S snapshot.vim`\n\nThe snapshot file looks like this:\n\n```console\n❯ cat snapshot.vim\n\" Generated by vim-plug\n\" Tue 17 May 2022 03:44:29 PM CDT\n\" :source this file in vim to restore the snapshot\n\" or execute: vim -S snapshot.vim\n\nsilent! let g:plugs['Telegraph.nvim'].commit = '92e472f4e83acd60eb3766168e66d02718bfefe0'\nsilent! let g:plugs['TrueZen.nvim'].commit = '508b977d71650da5c9243698614a9a1416f116d4'\nsilent! let g:plugs['ariake-vim-colors'].commit = '9fb35f1255e475631c9df24ecc5485a40360cc7b'\nsilent! let g:plugs['auto-pairs'].commit = '39f06b873a8449af8ff6a3eee716d3da14d63a76'\nsilent! let g:plugs['bufutils.vim'].commit = '4634feb1312fd73fab66cfaa860e7af3abde935b'\nsilent! let g:plugs['cmp-buffer'].commit = '12463cfcd9b14052f9effccbf1d84caa7a2d57f0'\nsilent! let g:plugs['cmp-cmdline'].commit = 'c36ca4bc1dedb12b4ba6546b96c43896fd6e7252'\nsilent! let g:plugs['cmp-nvim-lsp'].commit = 'e6b5feb2e6560b61f31c756fb9231a0d7b10c73d'\nsilent! let g:plugs['cmp-nvim-ultisnips'].commit = '21f02b62deb409ce69928a23406076bd0043ddbc'\nsilent! let g:plugs['cmp-path'].commit = '466b6b8270f7ba89abd59f402c73f63c7331ff6e'\nsilent! let g:plugs['cmp-spell'].commit = '5602f1a0de7831f8dad5b0c6db45328fbd539971'\nsilent! let g:plugs['compe-tabnine'].commit = '33e4af509c27da9ef2c9c3002c01e3ec031797d4'\nsilent! let g:plugs['coverage-highlight.vim'].commit = '864e03679ea4168661501246147893cc82020917'\nsilent! let g:plugs['diffurcate.vim'].commit = 'b804675072220ff7c7ebcd24a028aa4aa35f09cc'\nsilent! let g:plugs['fzf'].commit = '6dcf5c3d7d6c321b17e6a5673f1533d6e8350462'\nsilent! let g:plugs['fzf.vim'].commit = 'd5f1f8641b24c0fd5b10a299824362a2a1b20ae0'\nsilent! let g:plugs['git-blame.vim'].commit = '9d144b7bed5d8f1c9259551768b7f3b3d1294917'\nsilent! let g:plugs['harpoon'].commit = 'd3d3d22b6207f46f8ca64946f4d781e975aec0fc'\nsilent! let g:plugs['instant.nvim'].commit = 'c02d72267b12130609b7ad39b76cf7f4a3bc9554'\nsilent! let g:plugs['lsp-colors.nvim'].commit = '517fe3ab6b63f9907b093bc9443ef06b56f804f3'\nsilent! let g:plugs['lsp_extensions.nvim'].commit = '4011f4aec61ba59c734f5dbf52e91f258b99d985'\nsilent! let g:plugs['lspkind-nvim'].commit = '57e5b5dfbe991151b07d272a06e365a77cc3d0e7'\nsilent! let g:plugs['lspsaga.nvim'].commit = '8dde091a61ab07f639baaa82b456d3508d0aa7e8'\nsilent! let g:plugs['neoformat'].commit = '409ebbba9f4b568ea87ab4f2de90a645cf5d000a'\nsilent! let g:plugs['neovim-fuzzy'].commit = '0bef4e1a81c65fc05d31380dd74454bd67733837'\nsilent! let g:plugs['nerdtree'].commit = 'eed488b1cd1867bd25f19f90e10440c5cc7d6424'\nsilent! let g:plugs['nerdtree-visual-selection'].commit = '05427635ff053a2c542fcf8d7c3744c72575e76c'\nsilent! let g:plugs['nvim-cmp'].commit = 'a226b6a4ff72e5e809ed17734318233fb25c87f3'\nsilent! let g:plugs['nvim-compe'].commit = 'd186d739c54823e0b010feb205c6f97792322c08'\nsilent! let g:plugs['nvim-lspconfig'].commit = '9ff2a06cebd4c8c3af5259d713959ab310125bec'\nsilent! let g:plugs['nvim-lspinstall'].commit = '79ec2425d6b39cdcb69d379f3e56847f49be73eb'\nsilent! let g:plugs['nvim-treesitter'].commit = '10d57b3ec14cac0b6b759e1eb5594617b8a7e883'\nsilent! let g:plugs['nvim-treesitter-textobjects'].commit = '094e8ad3cc839e825f8dcc91352837653e365a8f'\nsilent! let g:plugs['nvim-web-devicons'].commit = 'bdd43421437f2ef037e0dafeaaaa62b31d35ef2f'\nsilent! let g:plugs['playground'].commit = '71b00a3c665298e5155ad64a9020135808d4e3e8'\nsilent! let g:plugs['plenary.nvim'].commit = '25b3475b97f241e6f76249747bc209e70c5d2ec8'\nsilent! let g:plugs['popup.nvim'].commit = 'b7404d35d5d3548a82149238289fa71f7f6de4ac'\nsilent! let g:plugs['syntastic'].commit = 'b7f4f71539038d33f173bfa72631737da049575a'\nsilent! let g:plugs['tabular'].commit = '339091ac4dd1f17e225fe7d57b48aff55f99b23a'\nsilent! let g:plugs['targets.vim'].commit = '8d6ff2984cdfaebe5b7a6eee8f226a6dd1226f2d'\nsilent! let g:plugs['telescope-fzy-native.nvim'].commit = '7b3d2528102f858036627a68821ccf5fc1d78ce4'\nsilent! let g:plugs['telescope.nvim'].commit = '39b12d84e86f5054e2ed98829b367598ae53ab41'\nsilent! let g:plugs['termopen.vim'].commit = '3194a991a18a9be2fd9fcf8c4c55fe990c04b2bd'\nsilent! let g:plugs['tokyonight.nvim'].commit = '8223c970677e4d88c9b6b6d81bda23daf11062bb'\nsilent! let g:plugs['trouble.nvim'].commit = 'da61737d860ddc12f78e638152834487eabf0ee5'\nsilent! let g:plugs['ultisnips'].commit = 'f5ccf0977c611ffd774ca180774959301baaffad'\nsilent! let g:plugs['undotree'].commit = '08e259be24d4476c1ee745dc735eefd44f90efdc'\nsilent! let g:plugs['vim-airline'].commit = 'c4655701431a9c79704c827fd88a4783ec946879'\nsilent! let g:plugs['vim-airline-themes'].commit = '97cf3e6e638f936187d5f6e9b5eb1bdf0a4df256'\nsilent! let g:plugs['vim-be-good'].commit = 'bc499a06c14c729b22a6cc7e730a9fbc44d4e737'\nsilent! let g:plugs['vim-commentary'].commit = '3654775824337f466109f00eaf6759760f65be34'\nsilent! let g:plugs['vim-devicons'].commit = 'a2258658661e42dd4cdba4958805dbad1fe29ef4'\nsilent! let g:plugs['vim-dispatch'].commit = '00e77d90452e3c710014b26dc61ea919bc895e92'\nsilent! let g:plugs['vim-flake8'].commit = 'a99054ef98e8fdaefa1315af4649138bcadbfdf7'\nsilent! let g:plugs['vim-floaterm'].commit = 'ab7876f86c05c1935eb23a193f4f276132902ac1'\nsilent! let g:plugs['vim-fugitive'].commit = 'f529acef74b4266d94f22414c60b4a8930c1e0f3'\nsilent! let g:plugs['vim-gitbranch'].commit = '1a8ba866f3eaf0194783b9f8573339d6ede8f1ed'\nsilent! let g:plugs['vim-indent-object'].commit = '5c5b24c959478929b54a9e831a8e2e651a465965'\nsilent! let g:plugs['vim-nerdtree-syntax-highlight'].commit = '5178ee4d7f4e7761187df30bb709f703d91df18a'\nsilent! let g:plugs['vim-polyglot'].commit = '38282d58387cff48ac203f6912c05e4c8686141b'\nsilent! let g:plugs['vim-pydocstring'].commit = 'f6e3c52bfaf4b4f76dab4d84e75b94199d5f3b9c'\nsilent! let g:plugs['vim-quicklink'].commit = '021167741588555501594e1fc31f130b16acefa0'\nsilent! let g:plugs['vim-repeat'].commit = '24afe922e6a05891756ecf331f39a1f6743d3d5a'\nsilent! let g:plugs['vim-signify'].commit = '69498f6d49f3eeac06870012416dd9bf867b84f3'\nsilent! let g:plugs['vim-sneak'].commit = '94c2de47ab301d476a2baec9ffda07367046bec9'\nsilent! let g:plugs['vim-snippets'].commit = '6f270bb2d26c38765ff2243e9337c65f8a96a28b'\nsilent! let g:plugs['vim-startify'].commit = '81e36c352a8deea54df5ec1e2f4348685569bed2'\nsilent! let g:plugs['vim-surround'].commit = 'bf3480dc9ae7bea34c78fbba4c65b4548b5b1fea'\nsilent! let g:plugs['vim-test'].commit = '2240d7a4b868cb594b7d83544e1b6db4df806e5e'\nsilent! let g:plugs['vim-tmux-runner'].commit = '54767911fd5e6e2d8e493847149e315ac2e6531a'\nsilent! let g:plugs['vim-ultest'].commit = '6978fd32e3ca2c1c5591884eea0d57a7ee43d212'\nsilent! let g:plugs['vim-visualstar'].commit = 'a18cd0e7a03311ac709595c1d261ed44b45c9098'\nsilent! let g:plugs['vimtex'].commit = 'dfaca59bbbf0079ab1b4f159337ae7f17d1b5289'\nsilent! let g:plugs['which-key.nvim'].commit = 'bd4411a2ed4dd8bb69c125e339d837028a6eea71'\n\nPlugUpdate!\n\n```\n",
      "summary": "If you use vim-plug for managing your vim plugins, do yourself a favor and snapshot your plugins before upgrading!",
      "date_published": "2022-05-13T00:00:00Z",
      "date_modified": "2022-05-13T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/python-eval/",
      "url": "https://pype.dev/python-eval/",
      "title": "Python-Eval",
      "content_html": "\u003ch1 id=\"todo\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTODO\u003c/span\u003e\u003c/h1\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003etitle\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;my Title\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eeval\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u0026#34;my\u0026#34; in title\u0026#39;\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\u003cspan class=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\u003cpre class=\"chroma\"\u003e\u003ccode\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=\"s2\"\u003e\u0026#34;hello, world\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;formatting\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": "\n# TODO\n\n```python\ntitle = \"my Title\"\neval('\"my\" in title')\n\n\u003e\u003e\u003e True\n\n```\n\n```python\nprint(\"hello, world\"); print(\"formatting\")\n```\n",
      "date_published": "2022-05-12T00:00:00Z",
      "date_modified": "2022-05-12T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech",
        "python",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/dataframe-to-styled-html/",
      "url": "https://pype.dev/dataframe-to-styled-html/",
      "title": "Dataframe-To-Styled-Html",
      "content_html": "\u003cp\u003eI wrote up a little on exporting DataFrames to markdown and html \u003ca href=\"/dataframe-to-markdown\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eBut I\u0026rsquo;ve been playing with a web app for with lists and while I\u0026rsquo;m toying around I learned you can actually give your tables some style with some simple css classes!\u003c/p\u003e\n\u003ch1 id=\"to-html\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTo HTML\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eReminder that if you have a dataframe, \u003ccode\u003edf\u003c/code\u003e, you can \u003ccode\u003edf.to_html()\u003c/code\u003e to get an HTML table of your dataframe.\u003c/p\u003e\n\u003cp\u003eWell you can pass some \u003ccode\u003eclasses\u003c/code\u003e to make it look super nice!\u003c/p\u003e\n\u003ch1 id=\"classes-and-css\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eClasses and CSS\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI don\u0026rsquo;t know anything really about CSS so I won\u0026rsquo;t pretend otherwise, but as I was learning about bootstrap that\u0026rsquo;s where I stumbled upon this\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThere are several classes you can pass but I found really good luck with \u003ccode\u003etable-bordered\u003c/code\u003e and \u003ccode\u003etable-dark\u003c/code\u003e for my use case\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003edf.to_html(classes=[\u0026quot;table table-bordered table-dark\u0026quot;])\u003c/code\u003e\u003c/p\u003e\n\u003ctable border=\"1\" class=\"dataframe table table-bordered table-dark\"\u003e  \u003cthead\u003e\n\u003ctr style=\"text-align: right;\"\u003e      \u003cth\u003eUnnamed: 0\u003c/th\u003e      \u003cth\u003empg\u003c/th\u003e\n\u003cth\u003ecyl\u003c/th\u003e      \u003cth\u003edisp\u003c/th\u003e      \u003cth\u003ehp\u003c/th\u003e      \u003cth\u003edrat\u003c/th\u003e\n\u003cth\u003ewt\u003c/th\u003e      \u003cth\u003eqsec\u003c/th\u003e      \u003cth\u003evs\u003c/th\u003e      \u003cth\u003eam\u003c/th\u003e\n\u003cth\u003egear\u003c/th\u003e      \u003cth\u003ecarb\u003c/th\u003e    \u003c/tr\u003e  \u003c/thead\u003e  \u003ctbody\u003e    \u003ctr\u003e\n\u003ctd\u003eMazda RX4\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e\n\u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.620\u003c/td\u003e      \u003ctd\u003e16.46\u003c/td\u003e\n\u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eMazda RX4 Wag\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e\n\u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.875\u003c/td\u003e      \u003ctd\u003e17.02\u003c/td\u003e\n\u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eDatsun 710\u003c/td\u003e      \u003ctd\u003e22.8\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e108.0\u003c/td\u003e\n\u003ctd\u003e93\u003c/td\u003e      \u003ctd\u003e3.85\u003c/td\u003e      \u003ctd\u003e2.320\u003c/td\u003e      \u003ctd\u003e18.61\u003c/td\u003e\n\u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eHornet 4 Drive\u003c/td\u003e      \u003ctd\u003e21.4\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e258.0\u003c/td\u003e\n\u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.08\u003c/td\u003e      \u003ctd\u003e3.215\u003c/td\u003e      \u003ctd\u003e19.44\u003c/td\u003e\n\u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eHornet Sportabout\u003c/td\u003e      \u003ctd\u003e18.7\u003c/td\u003e      \u003ctd\u003e8\u003c/td\u003e\n\u003ctd\u003e360.0\u003c/td\u003e      \u003ctd\u003e175\u003c/td\u003e      \u003ctd\u003e3.15\u003c/td\u003e      \u003ctd\u003e3.440\u003c/td\u003e\n\u003ctd\u003e17.02\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e2\u003c/td\u003e\n\u003c/tr\u003e  \u003c/tbody\u003e\u003c/table\u003e\n\u003ch1 id=\"you-try-it\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eYou try it!\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eCrack open ipython and make a dataframe, then \u003ccode\u003edf.to_html(classes=[\u0026quot;table table-bordered table-dark\u0026quot;])\u003c/code\u003e, copy the output (minus the quote marks ipython uses to denote the string type) that into \u003ccode\u003emy-file.html\u003c/code\u003e, open that up in a browser and be amazed!\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eFor added effeciency try using pyperclip to copy the output right to your clipboard!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003ccode\u003epip install pyperclip\u003c/code\u003e and then \u003ccode\u003epyperclip.copy(df.to_html(classes=[\u0026quot;table table-bordered table-dark\u0026quot;]))\u003c/code\u003e\u003c/p\u003e\n",
      "content_text": "\nI wrote up a little on exporting DataFrames to markdown and html [here](/dataframe-to-markdown)\n\nBut I've been playing with a web app for with lists and while I'm toying around I learned you can actually give your tables some style with some simple css classes! \n\n# To HTML\n\nReminder that if you have a dataframe, `df`, you can `df.to_html()` to get an HTML table of your dataframe.\n\nWell you can pass some `classes` to make it look super nice!\n\n# Classes and CSS\n\nI don't know anything really about CSS so I won't pretend otherwise, but as I was learning about bootstrap that's where I stumbled upon this...\n\nThere are several classes you can pass but I found really good luck with `table-bordered` and `table-dark` for my use case\n\n`df.to_html(classes=[\"table table-bordered table-dark\"])`\n\n\u003ctable border=\"1\" class=\"dataframe table table-bordered table-dark\"\u003e  \u003cthead\u003e\n\u003ctr style=\"text-align: right;\"\u003e      \u003cth\u003eUnnamed: 0\u003c/th\u003e      \u003cth\u003empg\u003c/th\u003e\n\u003cth\u003ecyl\u003c/th\u003e      \u003cth\u003edisp\u003c/th\u003e      \u003cth\u003ehp\u003c/th\u003e      \u003cth\u003edrat\u003c/th\u003e\n\u003cth\u003ewt\u003c/th\u003e      \u003cth\u003eqsec\u003c/th\u003e      \u003cth\u003evs\u003c/th\u003e      \u003cth\u003eam\u003c/th\u003e\n\u003cth\u003egear\u003c/th\u003e      \u003cth\u003ecarb\u003c/th\u003e    \u003c/tr\u003e  \u003c/thead\u003e  \u003ctbody\u003e    \u003ctr\u003e\n\u003ctd\u003eMazda RX4\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e\n\u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.620\u003c/td\u003e      \u003ctd\u003e16.46\u003c/td\u003e\n\u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eMazda RX4 Wag\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e\n\u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.875\u003c/td\u003e      \u003ctd\u003e17.02\u003c/td\u003e\n\u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eDatsun 710\u003c/td\u003e      \u003ctd\u003e22.8\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e108.0\u003c/td\u003e\n\u003ctd\u003e93\u003c/td\u003e      \u003ctd\u003e3.85\u003c/td\u003e      \u003ctd\u003e2.320\u003c/td\u003e      \u003ctd\u003e18.61\u003c/td\u003e\n\u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eHornet 4 Drive\u003c/td\u003e      \u003ctd\u003e21.4\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e258.0\u003c/td\u003e\n\u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.08\u003c/td\u003e      \u003ctd\u003e3.215\u003c/td\u003e      \u003ctd\u003e19.44\u003c/td\u003e\n\u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e\n\u003ctd\u003eHornet Sportabout\u003c/td\u003e      \u003ctd\u003e18.7\u003c/td\u003e      \u003ctd\u003e8\u003c/td\u003e\n\u003ctd\u003e360.0\u003c/td\u003e      \u003ctd\u003e175\u003c/td\u003e      \u003ctd\u003e3.15\u003c/td\u003e      \u003ctd\u003e3.440\u003c/td\u003e\n\u003ctd\u003e17.02\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e2\u003c/td\u003e\n\u003c/tr\u003e  \u003c/tbody\u003e\u003c/table\u003e\n\n\n# You try it!\n\nCrack open ipython and make a dataframe, then `df.to_html(classes=[\"table table-bordered table-dark\"])`, copy the output (minus the quote marks ipython uses to denote the string type) that into `my-file.html`, open that up in a browser and be amazed!\n\n\u003e For added effeciency try using pyperclip to copy the output right to your clipboard!\n\n`pip install pyperclip` and then `pyperclip.copy(df.to_html(classes=[\"table table-bordered table-dark\"]))`\n",
      "summary": "I wrote up a little on exporting DataFrames to markdown and html here",
      "date_published": "2022-05-07T00:00:00Z",
      "date_modified": "2022-05-07T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/dataframe-to-markdown/",
      "url": "https://pype.dev/dataframe-to-markdown/",
      "title": "Dataframe-To-Markdown",
      "content_html": "\u003ch1 id=\"pandas\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePandas\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epandas.DataFrame\u003c/code\u003es are pretty sweet data structures in Python.\u003c/p\u003e\n\u003cp\u003eI do a lot of work with tabular data and one thing I have incorporated into some of that work is automatic data summary reports by throwing the first few, or several relevant, rows of a dataframe at a point in a pipeline into a markdown file.\u003c/p\u003e\n\u003cp\u003ePandas has a method on DataFrames that makes this 100% trivial!\u003c/p\u003e\n\u003ch1 id=\"the-method\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Method\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eSay we have a dataframe, \u003ccode\u003edf\u003c/code\u003e\u0026hellip; then it\u0026rsquo;s literally just: \u003ccode\u003edf.to_markdown()\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehead\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          \u003cspan class=\"n\"\u003eUnnamed\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e   \u003cspan class=\"n\"\u003empg\u003c/span\u003e  \u003cspan class=\"n\"\u003ecyl\u003c/span\u003e   \u003cspan class=\"n\"\u003edisp\u003c/span\u003e   \u003cspan class=\"n\"\u003ehp\u003c/span\u003e  \u003cspan class=\"n\"\u003edrat\u003c/span\u003e     \u003cspan class=\"n\"\u003ewt\u003c/span\u003e   \u003cspan class=\"n\"\u003eqsec\u003c/span\u003e  \u003cspan class=\"n\"\u003evs\u003c/span\u003e  \u003cspan class=\"n\"\u003eam\u003c/span\u003e  \u003cspan class=\"n\"\u003egear\u003c/span\u003e  \u003cspan class=\"n\"\u003ecarb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e          \u003cspan class=\"n\"\u003eMazda\u003c/span\u003e \u003cspan class=\"n\"\u003eRX4\u003c/span\u003e  \u003cspan class=\"mf\"\u003e21.0\u003c/span\u003e    \u003cspan class=\"mi\"\u003e6\u003c/span\u003e  \u003cspan class=\"mf\"\u003e160.0\u003c/span\u003e  \u003cspan class=\"mi\"\u003e110\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.90\u003c/span\u003e  \u003cspan class=\"mf\"\u003e2.620\u003c/span\u003e  \u003cspan class=\"mf\"\u003e16.46\u003c/span\u003e   \u003cspan class=\"mi\"\u003e0\u003c/span\u003e   \u003cspan class=\"mi\"\u003e1\u003c/span\u003e     \u003cspan class=\"mi\"\u003e4\u003c/span\u003e     \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e      \u003cspan class=\"n\"\u003eMazda\u003c/span\u003e \u003cspan class=\"n\"\u003eRX4\u003c/span\u003e \u003cspan class=\"n\"\u003eWag\u003c/span\u003e  \u003cspan class=\"mf\"\u003e21.0\u003c/span\u003e    \u003cspan class=\"mi\"\u003e6\u003c/span\u003e  \u003cspan class=\"mf\"\u003e160.0\u003c/span\u003e  \u003cspan class=\"mi\"\u003e110\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.90\u003c/span\u003e  \u003cspan class=\"mf\"\u003e2.875\u003c/span\u003e  \u003cspan class=\"mf\"\u003e17.02\u003c/span\u003e   \u003cspan class=\"mi\"\u003e0\u003c/span\u003e   \u003cspan class=\"mi\"\u003e1\u003c/span\u003e     \u003cspan class=\"mi\"\u003e4\u003c/span\u003e     \u003cspan class=\"mi\"\u003e4\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e         \u003cspan class=\"n\"\u003eDatsun\u003c/span\u003e \u003cspan class=\"mi\"\u003e710\u003c/span\u003e  \u003cspan class=\"mf\"\u003e22.8\u003c/span\u003e    \u003cspan class=\"mi\"\u003e4\u003c/span\u003e  \u003cspan class=\"mf\"\u003e108.0\u003c/span\u003e   \u003cspan class=\"mi\"\u003e93\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.85\u003c/span\u003e  \u003cspan class=\"mf\"\u003e2.320\u003c/span\u003e  \u003cspan class=\"mf\"\u003e18.61\u003c/span\u003e   \u003cspan class=\"mi\"\u003e1\u003c/span\u003e   \u003cspan class=\"mi\"\u003e1\u003c/span\u003e     \u003cspan class=\"mi\"\u003e4\u003c/span\u003e     \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e     \u003cspan class=\"n\"\u003eHornet\u003c/span\u003e \u003cspan class=\"mi\"\u003e4\u003c/span\u003e \u003cspan class=\"n\"\u003eDrive\u003c/span\u003e  \u003cspan class=\"mf\"\u003e21.4\u003c/span\u003e    \u003cspan class=\"mi\"\u003e6\u003c/span\u003e  \u003cspan class=\"mf\"\u003e258.0\u003c/span\u003e  \u003cspan class=\"mi\"\u003e110\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.08\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.215\u003c/span\u003e  \u003cspan class=\"mf\"\u003e19.44\u003c/span\u003e   \u003cspan class=\"mi\"\u003e1\u003c/span\u003e   \u003cspan class=\"mi\"\u003e0\u003c/span\u003e     \u003cspan class=\"mi\"\u003e3\u003c/span\u003e     \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e  \u003cspan class=\"n\"\u003eHornet\u003c/span\u003e \u003cspan class=\"n\"\u003eSportabout\u003c/span\u003e  \u003cspan class=\"mf\"\u003e18.7\u003c/span\u003e    \u003cspan class=\"mi\"\u003e8\u003c/span\u003e  \u003cspan class=\"mf\"\u003e360.0\u003c/span\u003e  \u003cspan class=\"mi\"\u003e175\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.15\u003c/span\u003e  \u003cspan class=\"mf\"\u003e3.440\u003c/span\u003e  \u003cspan class=\"mf\"\u003e17.02\u003c/span\u003e   \u003cspan class=\"mi\"\u003e0\u003c/span\u003e   \u003cspan class=\"mi\"\u003e0\u003c/span\u003e     \u003cspan class=\"mi\"\u003e3\u003c/span\u003e     \u003cspan class=\"mi\"\u003e2\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\u003cp\u003eIn ipython I can call the method and get a markdown table back as a string\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=\"n\"\u003emental\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003elake\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003enew\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eposts\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emental\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003elake\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_markdown\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=\"s1\"\u003e\u0026#39;|    | Unnamed: 0        |   mpg |   cyl |   disp |   hp |   drat |    wt |   qsec |   vs |   am |   gear |   carb |\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e|---:|:------------------|------:|------:|-------:|-----:|-------:|------:|-------:|-----:|-----:|-------:|-------:|\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e|  0 | Mazda RX4         |  21   |     6 |    160 |  110 |   3.9  | 2.62  |  16.46 |    0 |    1 |      4 |      4 |\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e|  1 | Mazda RX4 Wag     |  21   |     6 |    160 |  110 |   3.9  | 2.875 |  17.02 |    0 |    1 |      4 |      4 |\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e|  2 | Datsun 710        |  22.8 |     4 |    108 |   93 |   3.85 | 2.32  |  18.61 |    1 |    1 |      4 |      1 |\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e|  3 | Hornet 4 Drive    |  21.4 |     6 |    258 |  110 |   3.08 | 3.215 |  19.44 |    1 |    0 |      3 |      1 |\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\u003c/span\u003e\u003cspan class=\"s1\"\u003e|  4 | Hornet Sportabout |  18.7 |     8 |    360 |  175 |   3.15 | 3.44  |  17.02 |    0 |    0 |      3 |      2 |\u0026#39;\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\u003cp\u003eYou can drop that string into a markdown file and using any reader that supports the rendering you\u0026rsquo;ll have a nicely formated table of example data in whatever report you\u0026rsquo;re making!\u003c/p\u003e\n\u003ch1 id=\"bonus-method\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBonus method\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eJust like markdown, you can export a dataframe to html with \u003ccode\u003edf.to_html()\u003c/code\u003e and use that if it\u0026rsquo;s more appropriate for your use case:\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\u0026#39;\u0026lt;table border=\u0026#34;1\u0026#34; class=\u0026#34;dataframe\u0026#34;\u0026gt;\\n  \u0026lt;thead\u0026gt;\\n    \u0026lt;tr style=\u0026#34;text-align: right;\u0026#34;\u0026gt;\\n      \u0026lt;th\u0026gt;\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;Unnamed: 0\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;mpg\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;cyl\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;disp\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;hp\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;drat\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;wt\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;qsec\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;vs\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;am\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;gear\u0026lt;/th\u0026gt;\\n      \u0026lt;th\u0026gt;carb\u0026lt;/th\u0026gt;\\n    \u0026lt;/tr\u0026gt;\\n  \u0026lt;/thead\u0026gt;\\n  \u0026lt;tbody\u0026gt;\\n    \u0026lt;tr\u0026gt;\\n      \u0026lt;th\u0026gt;0\u0026lt;/th\u0026gt;\\n      \u0026lt;td\u0026gt;Mazda RX4\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;21.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;6\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;160.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;110\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.90\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;2.620\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;16.46\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;4\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;4\u0026lt;/td\u0026gt;\\n    \u0026lt;/tr\u0026gt;\\n    \u0026lt;tr\u0026gt;\\n      \u0026lt;th\u0026gt;1\u0026lt;/th\u0026gt;\\n      \u0026lt;td\u0026gt;Mazda RX4 Wag\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;21.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;6\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;160.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;110\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.90\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;2.875\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;17.02\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;4\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;4\u0026lt;/td\u0026gt;\\n    \u0026lt;/tr\u0026gt;\\n    \u0026lt;tr\u0026gt;\\n      \u0026lt;th\u0026gt;2\u0026lt;/th\u0026gt;\\n      \u0026lt;td\u0026gt;Datsun 710\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;22.8\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;4\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;108.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;93\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.85\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;2.320\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;18.61\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;4\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n    \u0026lt;/tr\u0026gt;\\n    \u0026lt;tr\u0026gt;\\n      \u0026lt;th\u0026gt;3\u0026lt;/th\u0026gt;\\n      \u0026lt;td\u0026gt;Hornet 4 Drive\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;21.4\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;6\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;258.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;110\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.08\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.215\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;19.44\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;1\u0026lt;/td\u0026gt;\\n    \u0026lt;/tr\u0026gt;\\n    \u0026lt;tr\u0026gt;\\n      \u0026lt;th\u0026gt;4\u0026lt;/th\u0026gt;\\n      \u0026lt;td\u0026gt;Hornet Sportabout\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;18.7\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;8\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;360.0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;175\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.15\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3.440\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;17.02\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;0\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;3\u0026lt;/td\u0026gt;\\n      \u0026lt;td\u0026gt;2\u0026lt;/td\u0026gt;\\n    \u0026lt;/tr\u0026gt;\\n  \u0026lt;/tbody\u0026gt;\\n\u0026lt;/table\u0026gt;\u0026#39;\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\u003cp\u003eMy blog will render that html into a nice table! (After removing new line characters)\u003c/p\u003e\n\u003ctable border=\"1\" class=\"dataframe\"\u003e  \u003cthead\u003e    \u003ctr style=\"text-align: right;\"\u003e      \u003cth\u003eUnnamed: 0\u003c/th\u003e      \u003cth\u003empg\u003c/th\u003e      \u003cth\u003ecyl\u003c/th\u003e      \u003cth\u003edisp\u003c/th\u003e      \u003cth\u003ehp\u003c/th\u003e      \u003cth\u003edrat\u003c/th\u003e      \u003cth\u003ewt\u003c/th\u003e      \u003cth\u003eqsec\u003c/th\u003e      \u003cth\u003evs\u003c/th\u003e      \u003cth\u003eam\u003c/th\u003e      \u003cth\u003egear\u003c/th\u003e      \u003cth\u003ecarb\u003c/th\u003e    \u003c/tr\u003e  \u003c/thead\u003e  \u003ctbody\u003e    \u003ctr\u003e      \u003ctd\u003eMazda RX4\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e      \u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.620\u003c/td\u003e      \u003ctd\u003e16.46\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eMazda RX4 Wag\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e      \u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.875\u003c/td\u003e      \u003ctd\u003e17.02\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eDatsun 710\u003c/td\u003e      \u003ctd\u003e22.8\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e108.0\u003c/td\u003e      \u003ctd\u003e93\u003c/td\u003e      \u003ctd\u003e3.85\u003c/td\u003e      \u003ctd\u003e2.320\u003c/td\u003e      \u003ctd\u003e18.61\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eHornet 4 Drive\u003c/td\u003e      \u003ctd\u003e21.4\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e258.0\u003c/td\u003e      \u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.08\u003c/td\u003e      \u003ctd\u003e3.215\u003c/td\u003e      \u003ctd\u003e19.44\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eHornet Sportabout\u003c/td\u003e      \u003ctd\u003e18.7\u003c/td\u003e      \u003ctd\u003e8\u003c/td\u003e      \u003ctd\u003e360.0\u003c/td\u003e      \u003ctd\u003e175\u003c/td\u003e      \u003ctd\u003e3.15\u003c/td\u003e      \u003ctd\u003e3.440\u003c/td\u003e      \u003ctd\u003e17.02\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e2\u003c/td\u003e    \u003c/tr\u003e  \u003c/tbody\u003e\u003c/table\u003e\n",
      "content_text": "\n# Pandas\n\n`pandas.DataFrame`s are pretty sweet data structures in Python.\n\nI do a lot of work with tabular data and one thing I have incorporated into some of that work is automatic data summary reports by throwing the first few, or several relevant, rows of a dataframe at a point in a pipeline into a markdown file.\n\nPandas has a method on DataFrames that makes this 100% trivial!\n\n# The Method\n\nSay we have a dataframe, `df`... then it's literally just: `df.to_markdown()`\n\n```python\n❯ df.head()\n\n          Unnamed: 0   mpg  cyl   disp   hp  drat     wt   qsec  vs  am  gear  carb\n0          Mazda RX4  21.0    6  160.0  110  3.90  2.620  16.46   0   1     4     4\n1      Mazda RX4 Wag  21.0    6  160.0  110  3.90  2.875  17.02   0   1     4     4\n2         Datsun 710  22.8    4  108.0   93  3.85  2.320  18.61   1   1     4     1\n3     Hornet 4 Drive  21.4    6  258.0  110  3.08  3.215  19.44   1   0     3     1\n4  Hornet Sportabout  18.7    8  360.0  175  3.15  3.440  17.02   0   0     3     2\n\n```\n\nIn ipython I can call the method and get a markdown table back as a string\n\n```python\n\nmental-data-lake   new-posts via 3.8.11(mental-data-lake) ipython\n❯ df.head().to_markdown()\n'|    | Unnamed: 0        |   mpg |   cyl |   disp |   hp |   drat |    wt |   qsec |   vs |   am |   gear |   carb |\\n|---:|:------------------|------:|------:|-------:|-----:|-------:|------:|-------:|-----:|-----:|-------:|-------:|\\n|  0 | Mazda RX4         |  21   |     6 |    160 |  110 |   3.9  | 2.62  |  16.46 |    0 |    1 |      4 |      4 |\\n|  1 | Mazda RX4 Wag     |  21   |     6 |    160 |  110 |   3.9  | 2.875 |  17.02 |    0 |    1 |      4 |      4 |\\n|  2 | Datsun 710        |  22.8 |     4 |    108 |   93 |   3.85 | 2.32  |  18.61 |    1 |    1 |      4 |      1 |\\n|  3 | Hornet 4 Drive    |  21.4 |     6 |    258 |  110 |   3.08 | 3.215 |  19.44 |    1 |    0 |      3 |      1 |\\n|  4 | Hornet Sportabout |  18.7 |     8 |    360 |  175 |   3.15 | 3.44  |  17.02 |    0 |    0 |      3 |      2 |'\n\n```\n\nYou can drop that string into a markdown file and using any reader that supports the rendering you'll have a nicely formated table of example data in whatever report you're making!\n\n# Bonus method\n\nJust like markdown, you can export a dataframe to html with `df.to_html()` and use that if it's more appropriate for your use case:\n\n```text\n\n'\u003ctable border=\"1\" class=\"dataframe\"\u003e\\n  \u003cthead\u003e\\n    \u003ctr style=\"text-align: right;\"\u003e\\n      \u003cth\u003e\u003c/th\u003e\\n      \u003cth\u003eUnnamed: 0\u003c/th\u003e\\n      \u003cth\u003empg\u003c/th\u003e\\n      \u003cth\u003ecyl\u003c/th\u003e\\n      \u003cth\u003edisp\u003c/th\u003e\\n      \u003cth\u003ehp\u003c/th\u003e\\n      \u003cth\u003edrat\u003c/th\u003e\\n      \u003cth\u003ewt\u003c/th\u003e\\n      \u003cth\u003eqsec\u003c/th\u003e\\n      \u003cth\u003evs\u003c/th\u003e\\n      \u003cth\u003eam\u003c/th\u003e\\n      \u003cth\u003egear\u003c/th\u003e\\n      \u003cth\u003ecarb\u003c/th\u003e\\n    \u003c/tr\u003e\\n  \u003c/thead\u003e\\n  \u003ctbody\u003e\\n    \u003ctr\u003e\\n      \u003cth\u003e0\u003c/th\u003e\\n      \u003ctd\u003eMazda RX4\u003c/td\u003e\\n      \u003ctd\u003e21.0\u003c/td\u003e\\n      \u003ctd\u003e6\u003c/td\u003e\\n      \u003ctd\u003e160.0\u003c/td\u003e\\n      \u003ctd\u003e110\u003c/td\u003e\\n      \u003ctd\u003e3.90\u003c/td\u003e\\n      \u003ctd\u003e2.620\u003c/td\u003e\\n      \u003ctd\u003e16.46\u003c/td\u003e\\n      \u003ctd\u003e0\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n      \u003ctd\u003e4\u003c/td\u003e\\n      \u003ctd\u003e4\u003c/td\u003e\\n    \u003c/tr\u003e\\n    \u003ctr\u003e\\n      \u003cth\u003e1\u003c/th\u003e\\n      \u003ctd\u003eMazda RX4 Wag\u003c/td\u003e\\n      \u003ctd\u003e21.0\u003c/td\u003e\\n      \u003ctd\u003e6\u003c/td\u003e\\n      \u003ctd\u003e160.0\u003c/td\u003e\\n      \u003ctd\u003e110\u003c/td\u003e\\n      \u003ctd\u003e3.90\u003c/td\u003e\\n      \u003ctd\u003e2.875\u003c/td\u003e\\n      \u003ctd\u003e17.02\u003c/td\u003e\\n      \u003ctd\u003e0\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n      \u003ctd\u003e4\u003c/td\u003e\\n      \u003ctd\u003e4\u003c/td\u003e\\n    \u003c/tr\u003e\\n    \u003ctr\u003e\\n      \u003cth\u003e2\u003c/th\u003e\\n      \u003ctd\u003eDatsun 710\u003c/td\u003e\\n      \u003ctd\u003e22.8\u003c/td\u003e\\n      \u003ctd\u003e4\u003c/td\u003e\\n      \u003ctd\u003e108.0\u003c/td\u003e\\n      \u003ctd\u003e93\u003c/td\u003e\\n      \u003ctd\u003e3.85\u003c/td\u003e\\n      \u003ctd\u003e2.320\u003c/td\u003e\\n      \u003ctd\u003e18.61\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n      \u003ctd\u003e4\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n    \u003c/tr\u003e\\n    \u003ctr\u003e\\n      \u003cth\u003e3\u003c/th\u003e\\n      \u003ctd\u003eHornet 4 Drive\u003c/td\u003e\\n      \u003ctd\u003e21.4\u003c/td\u003e\\n      \u003ctd\u003e6\u003c/td\u003e\\n      \u003ctd\u003e258.0\u003c/td\u003e\\n      \u003ctd\u003e110\u003c/td\u003e\\n      \u003ctd\u003e3.08\u003c/td\u003e\\n      \u003ctd\u003e3.215\u003c/td\u003e\\n      \u003ctd\u003e19.44\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n      \u003ctd\u003e0\u003c/td\u003e\\n      \u003ctd\u003e3\u003c/td\u003e\\n      \u003ctd\u003e1\u003c/td\u003e\\n    \u003c/tr\u003e\\n    \u003ctr\u003e\\n      \u003cth\u003e4\u003c/th\u003e\\n      \u003ctd\u003eHornet Sportabout\u003c/td\u003e\\n      \u003ctd\u003e18.7\u003c/td\u003e\\n      \u003ctd\u003e8\u003c/td\u003e\\n      \u003ctd\u003e360.0\u003c/td\u003e\\n      \u003ctd\u003e175\u003c/td\u003e\\n      \u003ctd\u003e3.15\u003c/td\u003e\\n      \u003ctd\u003e3.440\u003c/td\u003e\\n      \u003ctd\u003e17.02\u003c/td\u003e\\n      \u003ctd\u003e0\u003c/td\u003e\\n      \u003ctd\u003e0\u003c/td\u003e\\n      \u003ctd\u003e3\u003c/td\u003e\\n      \u003ctd\u003e2\u003c/td\u003e\\n    \u003c/tr\u003e\\n  \u003c/tbody\u003e\\n\u003c/table\u003e'\n\n```\n\nMy blog will render that html into a nice table! (After removing new line characters)\n\n\u003ctable border=\"1\" class=\"dataframe\"\u003e  \u003cthead\u003e    \u003ctr style=\"text-align: right;\"\u003e      \u003cth\u003eUnnamed: 0\u003c/th\u003e      \u003cth\u003empg\u003c/th\u003e      \u003cth\u003ecyl\u003c/th\u003e      \u003cth\u003edisp\u003c/th\u003e      \u003cth\u003ehp\u003c/th\u003e      \u003cth\u003edrat\u003c/th\u003e      \u003cth\u003ewt\u003c/th\u003e      \u003cth\u003eqsec\u003c/th\u003e      \u003cth\u003evs\u003c/th\u003e      \u003cth\u003eam\u003c/th\u003e      \u003cth\u003egear\u003c/th\u003e      \u003cth\u003ecarb\u003c/th\u003e    \u003c/tr\u003e  \u003c/thead\u003e  \u003ctbody\u003e    \u003ctr\u003e      \u003ctd\u003eMazda RX4\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e      \u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.620\u003c/td\u003e      \u003ctd\u003e16.46\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eMazda RX4 Wag\u003c/td\u003e      \u003ctd\u003e21.0\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e160.0\u003c/td\u003e      \u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.90\u003c/td\u003e      \u003ctd\u003e2.875\u003c/td\u003e      \u003ctd\u003e17.02\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eDatsun 710\u003c/td\u003e      \u003ctd\u003e22.8\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e108.0\u003c/td\u003e      \u003ctd\u003e93\u003c/td\u003e      \u003ctd\u003e3.85\u003c/td\u003e      \u003ctd\u003e2.320\u003c/td\u003e      \u003ctd\u003e18.61\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e4\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eHornet 4 Drive\u003c/td\u003e      \u003ctd\u003e21.4\u003c/td\u003e      \u003ctd\u003e6\u003c/td\u003e      \u003ctd\u003e258.0\u003c/td\u003e      \u003ctd\u003e110\u003c/td\u003e      \u003ctd\u003e3.08\u003c/td\u003e      \u003ctd\u003e3.215\u003c/td\u003e      \u003ctd\u003e19.44\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e1\u003c/td\u003e    \u003c/tr\u003e    \u003ctr\u003e      \u003ctd\u003eHornet Sportabout\u003c/td\u003e      \u003ctd\u003e18.7\u003c/td\u003e      \u003ctd\u003e8\u003c/td\u003e      \u003ctd\u003e360.0\u003c/td\u003e      \u003ctd\u003e175\u003c/td\u003e      \u003ctd\u003e3.15\u003c/td\u003e      \u003ctd\u003e3.440\u003c/td\u003e      \u003ctd\u003e17.02\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e0\u003c/td\u003e      \u003ctd\u003e3\u003c/td\u003e      \u003ctd\u003e2\u003c/td\u003e    \u003c/tr\u003e  \u003c/tbody\u003e\u003c/table\u003e\n",
      "summary": "s are pretty sweet data structures in Python.",
      "date_published": "2022-05-07T00:00:00Z",
      "date_modified": "2022-05-07T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/wish-list-with-fastapi/",
      "url": "https://pype.dev/wish-list-with-fastapi/",
      "title": "Wish-List-With-Fastapi",
      "content_html": "\u003cp\u003eAmazon has crossed the line with me just one too many times now so we are looking to drop them like every other Big Tech provider\u0026hellip;.\u003c/p\u003e\n\u003cp\u003eHowever, one key feature of Amazon that has been so useful for us is Lists\u0026hellip; We can just maintain a list for each of us and then family members can login anytime and check it out\u0026hellip;\nThis really alleviates any last minute gift idea stress right before a birthday or something.\u003c/p\u003e\n\u003cp\u003eSo I need a nice gift list service but I don\u0026rsquo;t want to be locked into one company (like a Target registry or something) and I\u0026rsquo;d like to host it myself\u003c/p\u003e\n\u003cp\u003eThe internets had a few options but nothing looked/felt like I wanted to I decided to build my own.\u003c/p\u003e\n\u003ch1 id=\"the-frontend\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Frontend\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eI have no idea how to do front end so stay tuned\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"the-backend\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Backend\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eFastAPI for the win on this one\u0026hellip; I followed a few examples online and what I was able to build in just a few minutes is pretty impressive thanks to the design of FastAPI.\u003c/p\u003e\n\u003cp\u003eSome key features are:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eAuto doc generation\u003c/li\u003e\n\u003cli\u003eRequired typing (which makes #1 possible)\u003c/li\u003e\n\u003cli\u003eBuilt-in api testing in the browser\u003c/li\u003e\n\u003cli\u003eEasy integration with sqlalchemy\u003c/li\u003e\n\u003cli\u003eDevelopment time so short you won\u0026rsquo;t be done with your coffee before having something up and running!\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"database\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDatabase\u003c/span\u003e \u003ca href=\"#database\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eStarting with a simple \u003ccode\u003edatabase.py\u003c/code\u003e we can create a sqlalchemy session with a base model with about 7 lines of code\u0026hellip;\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=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003esqlalchemy\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\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\"\u003esqlalchemy.ext.declarative\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003edeclarative_base\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\"\u003esqlalchemy.orm\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003esessionmaker\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=\"n\"\u003eSQLALCHEMY_DATABASE_URL\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;sqlite:///wishes.sqlite3\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eengine\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_engine\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eSQLALCHEMY_DATABASE_URL\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\"\u003eSessionLocal\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003esessionmaker\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eautocommit\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eautoflush\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ebind\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eengine\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\"\u003eBase\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeclarative_base\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"model\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eModel\u003c/span\u003e \u003ca href=\"#model\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFor my wish list I needed just a simple table:\u003c/p\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth style=\"text-align:right\"\u003eid\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003eperson\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003eitem\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003elink\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003epurchased\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003epurchased_by\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003edate_added\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:right\"\u003e1\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003epypeaday\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003eA sweet item\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e\u003ca href=\"http://www.mystore.store\"\u003ewww.mystore.store\u003c/a\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003eFalse\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003edad\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e2022-05-05 21:55:09\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:right\"\u003e2\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003epypeaday\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003eA bitter item\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e\u003ca href=\"http://www.bitterstore.com\"\u003ewww.bitterstore.com\u003c/a\u003e\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003eTrue\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003eMrs. pypeaday\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e2022-05-06 06:55:54\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003cp\u003eThe table is simple enough\u0026hellip; A unique key, the person who the wish belongs to, the item (or wish), a link to the item, whether it\u0026rsquo;s been purchased or not and by whom, and the date it was added.\u003c/p\u003e\n\u003cp\u003eTo make this model with sqlalchemy we can make a \u003ccode\u003emodel.py\u003c/code\u003e like so:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003edatabase\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eBase\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\"\u003esqlalchemy.schema\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\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\"\u003esqlalchemy.types\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eBoolean\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eText\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\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eWishes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBase\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\"\u003e__tablename__\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Wishes\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"nb\"\u003eid\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eInteger\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eprimary_key\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eindex\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\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\"\u003eperson\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e20\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\"\u003eitem\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eText\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\"\u003elink\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eText\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\"\u003epurchased\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBoolean\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\"\u003epurchased_by\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e90\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\"\u003edate_added\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eColumn\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eString\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e15\u003c/span\u003e\u003cspan class=\"p\"\u003e))\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"schema\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSchema\u003c/span\u003e \u003ca href=\"#schema\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOne of the best things about FastAPI is trivial integration with pydantic.\nWe can define a schema to ensure any data posted is not missing anything!\u003c/p\u003e\n\u003cp\u003eMake a \u003ccode\u003eschema.py\u003c/code\u003e with the following:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003epydantic\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eBaseModel\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\"\u003etime\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\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\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\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBaseModel\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    \u003cspan class=\"n\"\u003eperson\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003elink\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epurchased\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epurchased_by\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edate_added\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrftime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan class=\"si\"\u003e%d\u003c/span\u003e\u003cspan class=\"s2\"\u003e %H:%M:%S\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elocaltime\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    \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eConfig\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\"\u003eorm_mode\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003epatch_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eBaseModel\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    \u003cspan class=\"n\"\u003epurchased\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003epurchased_by\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\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\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eConfig\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\"\u003eorm_mode\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\u003cp\u003eI have 2 schemas - one for a \u003ccode\u003ewish\u003c/code\u003e which you\u0026rsquo;ll see down below is used to validate any \u003ccode\u003epost\u003c/code\u003e requests.\u003c/p\u003e\n\u003cp\u003eTo simplify things for me I made another schema, \u003ccode\u003epatch_schema\u003c/code\u003e which I use for the route that updates the table (ie. marking an existing wish as purchased)\u003c/p\u003e\n\u003ch2 id=\"session\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSession\u003c/span\u003e \u003ca href=\"#session\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOne of the last things we need is a Session\u003c/p\u003e\n\u003cp\u003eSo make a \u003ccode\u003esession.py\u003c/code\u003e\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003edatabase\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eSessionLocal\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eengine\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\"\u003emodel\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\"\u003emodel\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eBase\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emetadata\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_all\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ebind\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eengine\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\"\u003ecreate_get_session\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\"\u003etry\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\"\u003edb\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eSessionLocal\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\"\u003eyield\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003efinally\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\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eclose\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eOur routes will depend on this \u003ccode\u003ecreate_get_session\u003c/code\u003e function that will yield a \u003ccode\u003edb\u003c/code\u003e object through which we\u0026rsquo;ll udpate our database\u003c/p\u003e\n\u003ch1 id=\"ok-just-do-it-already\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eOk just do it already!\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eSo our \u003ccode\u003emain.py\u003c/code\u003e will have a few routes in it\u0026hellip;\u003c/p\u003e\n\u003cp\u003eWhat do we want to support?\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eGetting all wishes\u003c/li\u003e\n\u003cli\u003eGetting a specific wish\u003c/li\u003e\n\u003cli\u003eUpdating a specific wish\u003c/li\u003e\n\u003cli\u003eDeleting a wish\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eI think the script is fairly self explanatory but here\u0026rsquo;s a few notes\u0026hellip;\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eWe decorate each function with \u003ccode\u003e@app.\u0026lt;method\u0026gt;\u003c/code\u003e and define \u003ccode\u003eresponse_model\u003c/code\u003e as well as \u003ccode\u003estatus_code\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eThe functions are defined with \u003ccode\u003easync\u003c/code\u003e (this was my first exposure to this so I can\u0026rsquo;t go in depth on it yet)\u003c/li\u003e\n\u003cli\u003eThe functions all take a \u003ccode\u003edb\u003c/code\u003e which is from \u003ccode\u003esession.py\u003c/code\u003e and that \u003ccode\u003edb\u003c/code\u003e depends on the \u003ccode\u003ecreate_get_session\u003c/code\u003e function\u003c/li\u003e\n\u003cli\u003eIf the db is being updtes then we type the object used for the update with the appropriate schema (either \u003ccode\u003ewish_schema\u003c/code\u003e or \u003ccode\u003epatch_schema\u003c/code\u003e)\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eFrom there we\u0026rsquo;re in true python-land where you can basically guess the methods on \u003ccode\u003edb\u003c/code\u003e and you\u0026rsquo;d probably be right\u0026hellip; (like \u003ccode\u003equery\u003c/code\u003e, \u003ccode\u003eupddate\u003c/code\u003e, \u003ccode\u003edelete\u003c/code\u003e etc.)\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003efastapi\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eFastAPI\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eHTTPException\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\"\u003esqlalchemy.orm\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\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\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eList\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\"\u003emodel\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eWishes\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\"\u003eschema\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epatch_schema\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\"\u003esession\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003eapp\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eFastAPI\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=\"nd\"\u003e@app.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/\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\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eread_root\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=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;message\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;server is up!\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=\"nd\"\u003e@app.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/wishes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eread_wishes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003ewishes\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eWishes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eall\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=\"n\"\u003ewishes\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=\"nd\"\u003e@app.post\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/wishes\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e201\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eadd_wish\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003enew_wish\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eWishes\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\"\u003eperson\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eperson\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\"\u003eitem\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eitem\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\"\u003elink\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elink\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\"\u003epurchased\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epurchased\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\"\u003epurchased_by\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epurchased_by\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\"\u003edate_added\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edate_added\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\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eadd\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003enew_wish\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\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecommit\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    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003enew_wish\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=\"nd\"\u003e@app.get\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/wishes/\u003c/span\u003e\u003cspan class=\"si\"\u003e{id}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eget_wish\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003ewish\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eWishes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\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=\"nb\"\u003eid\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=\"n\"\u003ewish\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=\"nd\"\u003e@app.patch\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/wishes/\u003c/span\u003e\u003cspan class=\"si\"\u003e{id}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eresponse_model\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewish_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eupdate_wish\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\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003epatch\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003epatch_schema\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003edb_wish\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eWishes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\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=\"nb\"\u003eid\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\"\u003edb_wish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epurchased\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epatch\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epurchased\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edb_wish\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epurchased_by\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epatch\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epurchased_by\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecommit\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\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003erefresh\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edb_wish\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    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003edb_wish\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=\"nd\"\u003e@app.delete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/wishes/\u003c/span\u003e\u003cspan class=\"si\"\u003e{id}\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e200\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\"\u003easync\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003edelete_wish\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003eid\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eSession\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eDepends\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ecreate_get_session\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\"\u003edb_wish\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003equery\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eWishes\u003c/span\u003e\u003cspan class=\"p\"\u003e)\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=\"nb\"\u003eid\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=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003edb_wish\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=\"n\"\u003eHTTPException\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003estatus_code\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;404\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edetail\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Wish id does not exist\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    \u003cspan class=\"n\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edelete\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edb_wish\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\"\u003edb\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecommit\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    \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\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\u003ch1 id=\"my-code\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy Code\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eYou can find my repo \u003ca href=\"https://github.com/nicpayne713/wish-lists\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ll plan to update and maintain for as long as I use it\u003c/p\u003e\n",
      "content_text": "\nAmazon has crossed the line with me just one too many times now so we are looking to drop them like every other Big Tech provider....\n\nHowever, one key feature of Amazon that has been so useful for us is Lists... We can just maintain a list for each of us and then family members can login anytime and check it out... \nThis really alleviates any last minute gift idea stress right before a birthday or something.\n\nSo I need a nice gift list service but I don't want to be locked into one company (like a Target registry or something) and I'd like to host it myself\n\nThe internets had a few options but nothing looked/felt like I wanted to I decided to build my own.\n\n# The Frontend\n\n__I have no idea how to do front end so stay tuned__\n\n# The Backend\n\nFastAPI for the win on this one... I followed a few examples online and what I was able to build in just a few minutes is pretty impressive thanks to the design of FastAPI.\n\nSome key features are:\n1. Auto doc generation\n2. Required typing (which makes #1 possible)\n3. Built-in api testing in the browser\n4. Easy integration with sqlalchemy\n5. Development time so short you won't be done with your coffee before having something up and running!\n\n## Database\n\nStarting with a simple `database.py` we can create a sqlalchemy session with a base model with about 7 lines of code...\n\n```python\n\nfrom sqlalchemy import create_engine\nfrom sqlalchemy.ext.declarative import declarative_base\nfrom sqlalchemy.orm import sessionmaker\n\n\nSQLALCHEMY_DATABASE_URL = \"sqlite:///wishes.sqlite3\"\nengine = create_engine(SQLALCHEMY_DATABASE_URL)\nSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)\nBase = declarative_base()\n```\n\n## Model\n\nFor my wish list I needed just a simple table:\n\n|   id | person   | item         | link         | purchased   | purchased_by   | date_added          |\n|-----:|:---------|:-------------|:-------------|:------------|:---------------|:--------------------|\n|    1 | pypeaday | A sweet item | www.mystore.store | False        | dad| 2022-05-05 21:55:09 |\n|    2 | pypeaday   | A bitter item| www.bitterstore.com | True       |Mrs. pypeaday |  2022-05-06 06:55:54 |\n\n\nThe table is simple enough... A unique key, the person who the wish belongs to, the item (or wish), a link to the item, whether it's been purchased or not and by whom, and the date it was added.\n\nTo make this model with sqlalchemy we can make a `model.py` like so:\n\n```python\nfrom database import Base\nfrom sqlalchemy.schema import Column\nfrom sqlalchemy.types import Boolean, Integer, String, Text\n\n\nclass Wishes(Base):\n    __tablename__ = \"Wishes\"\n    id = Column(Integer, primary_key=True, index=True)\n    person = Column(String(20))\n    item = Column(Text())\n    link = Column(Text())\n    purchased = Column(Boolean())\n    purchased_by = Column(String(90))\n    date_added = Column(String(15))\n```\n\n## Schema\n\nOne of the best things about FastAPI is trivial integration with pydantic.\nWe can define a schema to ensure any data posted is not missing anything!\n\nMake a `schema.py` with the following:\n\n```python\nfrom pydantic import BaseModel\nimport time\nfrom typing import Optional\n\n\nclass wish_schema(BaseModel):\n\n    person: str\n    item: str\n    link: str\n    purchased: bool = False\n    purchased_by: Optional[str] = None\n    date_added: Optional[str] = time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime())\n\n    class Config:\n        orm_mode = True\n\n\nclass patch_schema(BaseModel):\n\n    purchased: bool\n    purchased_by: Optional[str] = None\n\n    class Config:\n        orm_mode = True\n\n```\n\nI have 2 schemas - one for a `wish` which you'll see down below is used to validate any `post` requests.\n\nTo simplify things for me I made another schema, `patch_schema` which I use for the route that updates the table (ie. marking an existing wish as purchased) \n\n## Session\n\nOne of the last things we need is a Session\n\nSo make a `session.py`...\n\n```python\nfrom database import SessionLocal, engine\nimport model\n\nmodel.Base.metadata.create_all(bind=engine)\n\n\ndef create_get_session():\n    try:\n        db = SessionLocal()\n        yield db\n    finally:\n        db.close()\n```\n\nOur routes will depend on this `create_get_session` function that will yield a `db` object through which we'll udpate our database\n\n# Ok just do it already!\n\nSo our `main.py` will have a few routes in it...\n\nWhat do we want to support?\n\n1. Getting all wishes\n2. Getting a specific wish\n3. Updating a specific wish\n4. Deleting a wish\n\nI think the script is fairly self explanatory but here's a few notes...\n\n1. We decorate each function with `@app.\u003cmethod\u003e` and define `response_model` as well as `status_code`\n2. The functions are defined with `async` (this was my first exposure to this so I can't go in depth on it yet)\n3. The functions all take a `db` which is from `session.py` and that `db` depends on the `create_get_session` function\n4. If the db is being updtes then we type the object used for the update with the appropriate schema (either `wish_schema` or `patch_schema`)\n\nFrom there we're in true python-land where you can basically guess the methods on `db` and you'd probably be right... (like `query`, `upddate`, `delete` etc.)\n\n\n```python\nfrom fastapi import FastAPI, Depends, HTTPException\nfrom sqlalchemy.orm import Session\nfrom typing import List\nfrom model import Wishes\nfrom schema import wish_schema, patch_schema\nfrom session import create_get_session\n\napp = FastAPI()\n\n\n@app.get(\"/\")\ndef read_root():\n    return {\"message\": \"server is up!\"}\n\n\n@app.get(\"/wishes\", response_model=List[wish_schema], status_code=200)\nasync def read_wishes(db: Session = Depends(create_get_session)):\n    wishes = db.query(Wishes).all()\n    return wishes\n\n\n@app.post(\"/wishes\", response_model=wish_schema, status_code=201)\nasync def add_wish(wish: wish_schema, db: Session = Depends(create_get_session)):\n    new_wish = Wishes(\n        person=wish.person,\n        item=wish.item,\n        link=wish.link,\n        purchased=wish.purchased,\n        purchased_by=wish.purchased_by,\n        date_added=wish.date_added,\n    )\n    db.add(new_wish)\n    db.commit()\n\n    return new_wish\n\n\n@app.get(\"/wishes/{id}\", response_model=wish_schema, status_code=200)\nasync def get_wish(id: int, db: Session = Depends(create_get_session)):\n    wish = db.query(Wishes).get(id)\n    return wish\n\n\n@app.patch(\"/wishes/{id}\", response_model=wish_schema, status_code=200)\nasync def update_wish(\n    id: int, patch: patch_schema, db: Session = Depends(create_get_session)\n):\n    db_wish = db.query(Wishes).get(id)\n    db_wish.purchased = patch.purchased\n    db_wish.purchased_by = patch.purchased_by\n    db.commit()\n    db.refresh(db_wish)\n\n    return db_wish\n\n\n@app.delete(\"/wishes/{id}\", status_code=200)\nasync def delete_wish(id: int, db: Session = Depends(create_get_session)):\n    db_wish = db.query(Wishes).get(id)\n    if not db_wish:\n        raise HTTPException(status_code=\"404\", detail=\"Wish id does not exist\")\n\n    db.delete(db_wish)\n    db.commit()\n\n    return None\n\n```\n\n# My Code\n\nYou can find my repo [here](https://github.com/nicpayne713/wish-lists).\n\nI'll plan to update and maintain for as long as I use it\n",
      "summary": "Amazon has crossed the line with me just one too many times now so we are looking to drop them like every other Big Tech provider....",
      "date_published": "2022-05-06T00:00:00Z",
      "date_modified": "2022-05-06T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "blog",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/git-bisect/",
      "url": "https://pype.dev/git-bisect/",
      "title": "Git-Bisect",
      "content_html": "\u003cp\u003eI try to commit a lot, and I also try to write useful tests appropriate for the scope of work I\u0026rsquo;m focusing on, but sometimes I drop the ball\u0026hellip;\u003c/p\u003e\n\u003cp\u003eWhether by laziness, ignorance, or accepted tech debt I don\u0026rsquo;t always code perfectly and recently I was dozens of commits into a new feature before realizing I broke something along the way that none of my tests caught\u0026hellip;\u003c/p\u003e\n\u003cp\u003eBefore today I would\u0026rsquo;ve manually reviewed every commit to see if something obvious slipped by me (talk about a time suck 😩)\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eThere must be a better way\u003c/strong\u003e\u003c/p\u003e\n\u003ch1 id=\"bisect\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBisect?\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003egit bisect\u003c/code\u003e is the magic sauce for this exact problem\u0026hellip;\u003c/p\u003e\n\u003cp\u003eYou essentially create a range of commits to consider and let \u003ccode\u003egit bisect\u003c/code\u003e guide you through them in a manner akin to Newton\u0026rsquo;s method for finding the root of a continuous function.\u003c/p\u003e\n\u003ch1 id=\"how-to-do-it\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eHow to do it?\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eStart with \u003ccode\u003egit bisect start\u003c/code\u003e and then choose the first \u003ccode\u003egood\u003c/code\u003e commit (ie. a commit you know the bug isn\u0026rsquo;t present in)\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\"\u003esandbox   bisect-post   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect start\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\"\u003esandbox   bisect-post \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect good 655332b\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebisect-post  HEAD         main         ORIG_HEAD\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e5b31e1e  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD\u003cspan class=\"o\"\u003e]\u003c/span\u003e    add successful print \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e52\u003c/span\u003e seconds ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e308247b  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD^\u003cspan class=\"o\"\u003e]\u003c/span\u003e   init another loop \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e77\u003c/span\u003e seconds ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e4555c59  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD^^\u003cspan class=\"o\"\u003e]\u003c/span\u003e  introduce bug \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e2\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e9cf6d55  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~3\u003cspan class=\"o\"\u003e]\u003c/span\u003e  add successful loop \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e3\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebcb41c3  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~4\u003cspan class=\"o\"\u003e]\u003c/span\u003e  change x to \u003cspan class=\"m\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e4\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e3c34aac  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~5\u003cspan class=\"o\"\u003e]\u003c/span\u003e  init x to \u003cspan class=\"m\"\u003e1\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e4\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e12e53bd  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~6\u003cspan class=\"o\"\u003e]\u003c/span\u003e  print cwd \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e4\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e655332b  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~7\u003cspan class=\"o\"\u003e]\u003c/span\u003e  add example.py \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e10\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# \u0026lt;- I want to start at this commit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e59e0048  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~8\u003cspan class=\"o\"\u003e]\u003c/span\u003e  gitignore \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e23\u003c/span\u003e hours ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efb9e1fb  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~9\u003cspan class=\"o\"\u003e]\u003c/span\u003e  add reqs \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e23\u003c/span\u003e hours ago\u003cspan class=\"o\"\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\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\"\u003esandbox   bisect-post \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect bad 5b31e1e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebisect-post                                                ORIG_HEAD\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHEAD                                                       refs/bisect/good-655332b6c384934c2c00c3d4aba3011ccc1e5b57\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003emain\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e5b31e1e  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD\u003cspan class=\"o\"\u003e]\u003c/span\u003e    add successful print \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e5\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# \u0026lt;- I start here with the \u0026#34;bad\u0026#34; commit\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e308247b  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD^\u003cspan class=\"o\"\u003e]\u003c/span\u003e   init another loop \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e6\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e4555c59  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD^^\u003cspan class=\"o\"\u003e]\u003c/span\u003e  introduce bug \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e6\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e9cf6d55  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~3\u003cspan class=\"o\"\u003e]\u003c/span\u003e  add successful loop \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e7\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ebcb41c3  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~4\u003cspan class=\"o\"\u003e]\u003c/span\u003e  change x to \u003cspan class=\"m\"\u003e10\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e8\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e3c34aac  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~5\u003cspan class=\"o\"\u003e]\u003c/span\u003e  init x to \u003cspan class=\"m\"\u003e1\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e9\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e12e53bd  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~6\u003cspan class=\"o\"\u003e]\u003c/span\u003e  print cwd \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e9\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e655332b  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~7\u003cspan class=\"o\"\u003e]\u003c/span\u003e  add example.py \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e14\u003c/span\u003e minutes ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e59e0048  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~8\u003cspan class=\"o\"\u003e]\u003c/span\u003e  gitignore \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e23\u003c/span\u003e hours ago\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003efb9e1fb  -- \u003cspan class=\"o\"\u003e[\u003c/span\u003eHEAD~9\u003cspan class=\"o\"\u003e]\u003c/span\u003e  add reqs \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e23\u003c/span\u003e hours ago\u003cspan class=\"o\"\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\u003cp\u003eAfter starting bisect with a \u0026ldquo;good\u0026rdquo; start commit and a \u0026ldquo;bad\u0026rdquo; ending commit we can let git to it\u0026rsquo;s thing!\u003c/p\u003e\n\u003cp\u003eGit checksout a commit somewhere about halfway between the good and bad commit so you can see if your bug is there or not.\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\"\u003esandbox   bisect-post \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect bad 5b31e1e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eBisecting: \u003cspan class=\"m\"\u003e3\u003c/span\u003e revisions left to \u003cspan class=\"nb\"\u003etest\u003c/span\u003e after this \u003cspan class=\"o\"\u003e(\u003c/span\u003eroughly \u003cspan class=\"m\"\u003e2\u003c/span\u003e steps\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003ebcb41c3854e343eade85353683f2c1c4ddde4e04\u003cspan class=\"o\"\u003e]\u003c/span\u003e change x to \u003cspan class=\"m\"\u003e10\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\"\u003esandbox   HEAD \u003cspan class=\"o\"\u003e(\u003c/span\u003ebcb41c38\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\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\u003cp\u003eIn my example here I have a python script with some loops and print statements - they aren\u0026rsquo;t really relevant, I just wanted an easy to follow git history.\u003c/p\u003e\n\u003cp\u003eSo I check to see if the bug is present or not either by running/writing tests or replicating the bug somehow.\u003c/p\u003e\n\u003cp\u003eIn this session commit \u003ccode\u003ebcb41c38\u003c/code\u003e is actually just fine, so I do \u003ccode\u003egit bisect good\u003c/code\u003e\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\"\u003esandbox   HEAD \u003cspan class=\"o\"\u003e(\u003c/span\u003ebcb41c38\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect good\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eBisecting: \u003cspan class=\"m\"\u003e1\u003c/span\u003e revision left to \u003cspan class=\"nb\"\u003etest\u003c/span\u003e after this \u003cspan class=\"o\"\u003e(\u003c/span\u003eroughly \u003cspan class=\"m\"\u003e1\u003c/span\u003e step\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e4555c5979268dff6c475365fdc5ce1d4a12bd820\u003cspan class=\"o\"\u003e]\u003c/span\u003e introduce bug\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\u003cp\u003eAnd we see that git moves on to checkout another commit\u0026hellip;\u003c/p\u003e\n\u003cp\u003eIn this case the next commit is the one where I introduced a bug\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003egit bisect bad\u003c/code\u003e then gives me:\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\"\u003esandbox   HEAD \u003cspan class=\"o\"\u003e(\u003c/span\u003e4555c597\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect bad\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eBisecting: \u003cspan class=\"m\"\u003e0\u003c/span\u003e revisions left to \u003cspan class=\"nb\"\u003etest\u003c/span\u003e after this \u003cspan class=\"o\"\u003e(\u003c/span\u003eroughly \u003cspan class=\"m\"\u003e0\u003c/span\u003e steps\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e9cf6d55301560c51e2f55404d0d80b1f1e22a33d\u003cspan class=\"o\"\u003e]\u003c/span\u003e add successful loop\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAt \u003ccode\u003e4555c597\u003c/code\u003e the script works as expected so one more \u003ccode\u003egit bisect good\u003c/code\u003e yields\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esandbox   HEAD \u003cspan class=\"o\"\u003e(\u003c/span\u003e9cf6d553\u003cspan class=\"o\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e(\u003c/span\u003eBISECTING\u003cspan class=\"o\"\u003e)\u003c/span\u003e   ×1 via   v3.8.11\u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ git bisect good\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e4555c5979268dff6c475365fdc5ce1d4a12bd820 is the first bad commit\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecommit 4555c5979268dff6c475365fdc5ce1d4a12bd820\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eAuthor: \u003cspan class=\"c1\"\u003e########################### \u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eDate:   Tue May \u003cspan class=\"m\"\u003e3\u003c/span\u003e 09:00:00 \u003cspan class=\"m\"\u003e2022\u003c/span\u003e -0500\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    introduce bug\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 example.py \u003cspan class=\"p\"\u003e|\u003c/span\u003e \u003cspan class=\"m\"\u003e2\u003c/span\u003e +-\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"m\"\u003e1\u003c/span\u003e file changed, \u003cspan class=\"m\"\u003e1\u003c/span\u003e insertion\u003cspan class=\"o\"\u003e(\u003c/span\u003e+\u003cspan class=\"o\"\u003e)\u003c/span\u003e, \u003cspan class=\"m\"\u003e1\u003c/span\u003e deletion\u003cspan class=\"o\"\u003e(\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\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\u003ch1 id=\"what-happened\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat happened?\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eGit sliced up a range of commits based on me saying of the next one was good or bad and localized the commit that introduced a bug into my workflow!\u003c/p\u003e\n\u003cp\u003eI didn\u0026rsquo;t have to manually review commits, click through logs, etc\u0026hellip; I just let git checkout relevant commits and I ran whatever was appropriate for reproducing the bug to learn when it was comitted!\u003c/p\u003e\n",
      "content_text": "\nI try to commit a lot, and I also try to write useful tests appropriate for the scope of work I'm focusing on, but sometimes I drop the ball...\n\nWhether by laziness, ignorance, or accepted tech debt I don't always code perfectly and recently I was dozens of commits into a new feature before realizing I broke something along the way that none of my tests caught...\n\nBefore today I would've manually reviewed every commit to see if something obvious slipped by me (talk about a time suck 😩)\n\n__There must be a better way__\n\n# Bisect?\n\n`git bisect` is the magic sauce for this exact problem...\n\nYou essentially create a range of commits to consider and let `git bisect` guide you through them in a manner akin to Newton's method for finding the root of a continuous function.\n\n# How to do it?\n\nStart with `git bisect start` and then choose the first `good` commit (ie. a commit you know the bug isn't present in)\n\n```bash\n\nsandbox   bisect-post   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect start\n\nsandbox   bisect-post (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect good 655332b\nbisect-post  HEAD         main         ORIG_HEAD\n5b31e1e  -- [HEAD]    add successful print (52 seconds ago)\n308247b  -- [HEAD^]   init another loop (77 seconds ago)\n4555c59  -- [HEAD^^]  introduce bug (2 minutes ago)\n9cf6d55  -- [HEAD~3]  add successful loop (3 minutes ago)\nbcb41c3  -- [HEAD~4]  change x to 10 (4 minutes ago)\n3c34aac  -- [HEAD~5]  init x to 1 (4 minutes ago)\n12e53bd  -- [HEAD~6]  print cwd (4 minutes ago)\n655332b  -- [HEAD~7]  add example.py (10 minutes ago)  # \u003c- I want to start at this commit\n59e0048  -- [HEAD~8]  gitignore (23 hours ago)\nfb9e1fb  -- [HEAD~9]  add reqs (23 hours ago)\n\n```\n\n```bash\n\nsandbox   bisect-post (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect bad 5b31e1e\nbisect-post                                                ORIG_HEAD\nHEAD                                                       refs/bisect/good-655332b6c384934c2c00c3d4aba3011ccc1e5b57\nmain\n5b31e1e  -- [HEAD]    add successful print (5 minutes ago)  # \u003c- I start here with the \"bad\" commit\n308247b  -- [HEAD^]   init another loop (6 minutes ago)\n4555c59  -- [HEAD^^]  introduce bug (6 minutes ago)\n9cf6d55  -- [HEAD~3]  add successful loop (7 minutes ago)\nbcb41c3  -- [HEAD~4]  change x to 10 (8 minutes ago)\n3c34aac  -- [HEAD~5]  init x to 1 (9 minutes ago)\n12e53bd  -- [HEAD~6]  print cwd (9 minutes ago)\n655332b  -- [HEAD~7]  add example.py (14 minutes ago)\n59e0048  -- [HEAD~8]  gitignore (23 hours ago)\nfb9e1fb  -- [HEAD~9]  add reqs (23 hours ago)\n\n```\n\nAfter starting bisect with a \"good\" start commit and a \"bad\" ending commit we can let git to it's thing!\n\nGit checksout a commit somewhere about halfway between the good and bad commit so you can see if your bug is there or not.\n\n```bash\n\nsandbox   bisect-post (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect bad 5b31e1e\nBisecting: 3 revisions left to test after this (roughly 2 steps)\n[bcb41c3854e343eade85353683f2c1c4ddde4e04] change x to 10\n\nsandbox   HEAD (bcb41c38) (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯\n```\n\nIn my example here I have a python script with some loops and print statements - they aren't really relevant, I just wanted an easy to follow git history.\n\nSo I check to see if the bug is present or not either by running/writing tests or replicating the bug somehow.\n\nIn this session commit `bcb41c38` is actually just fine, so I do `git bisect good`\n\n```bash\n\nsandbox   HEAD (bcb41c38) (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect good\nBisecting: 1 revision left to test after this (roughly 1 step)\n[4555c5979268dff6c475365fdc5ce1d4a12bd820] introduce bug\n\n```\n\nAnd we see that git moves on to checkout another commit...\n\nIn this case the next commit is the one where I introduced a bug\n\n`git bisect bad` then gives me:\n\n```bash\n\nsandbox   HEAD (4555c597) (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect bad\nBisecting: 0 revisions left to test after this (roughly 0 steps)\n[9cf6d55301560c51e2f55404d0d80b1f1e22a33d] add successful loop\n```\n\nAt `4555c597` the script works as expected so one more `git bisect good` yields...\n\n```bash\nsandbox   HEAD (9cf6d553) (BISECTING)   ×1 via   v3.8.11(sandbox)  on  (us-east-1)\n❯ git bisect good\n4555c5979268dff6c475365fdc5ce1d4a12bd820 is the first bad commit\ncommit 4555c5979268dff6c475365fdc5ce1d4a12bd820\nAuthor: ########################### \nDate:   Tue May 3 09:00:00 2022 -0500\n\n    introduce bug\n\n example.py | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\n\n```\n\n# What happened?\n\nGit sliced up a range of commits based on me saying of the next one was good or bad and localized the commit that introduced a bug into my workflow!\n\nI didn't have to manually review commits, click through logs, etc... I just let git checkout relevant commits and I ran whatever was appropriate for reproducing the bug to learn when it was comitted!\n",
      "summary": "I try to commit a lot, and I also try to write useful tests appropriate for the scope of work I'm focusing on, but sometimes I drop the ball...",
      "date_published": "2022-05-03T00:00:00Z",
      "date_modified": "2022-05-03T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "git",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/pandas-string-contains/",
      "url": "https://pype.dev/pandas-string-contains/",
      "title": "Pandas-String-Contains",
      "content_html": "\u003ch1 id=\"tl-dr\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTL;DR\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003ccode\u003epandas.Series.str.contains\u003c/code\u003e accepts regular expressions and this is turned on by \u003cstrong\u003edefault\u003c/strong\u003e!\u003c/p\u003e\n\u003ch1 id=\"use-case\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUse case\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eWe often need to filter pandas DataFrames based on several string values in a Series.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNotice that sweet pyflyby import 😁!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eDataFrame\u003c/span\u003e\u003cspan class=\"p\"\u003e({\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;A\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;string1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;string2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;string3\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\u003cspan class=\"n\"\u003ePYFLYBY\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003epandas\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003epd\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\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\"\u003eA\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e  \u003cspan class=\"n\"\u003estring1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"n\"\u003estring2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e  \u003cspan class=\"n\"\u003estring3\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eA\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estr\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econtains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;1\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e|\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eA\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estr\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econtains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;2\u0026#39;\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         \u003cspan class=\"n\"\u003eA\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e  \u003cspan class=\"n\"\u003estring1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"n\"\u003estring2\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\u003cp\u003eAnd this isn\u0026rsquo;t the worst thing in the world, especially for such a tiny example\u0026hellip;\u003c/p\u003e\n\u003cp\u003eBut what if we had dozens or more values to filter on?\u003c/p\u003e\n\u003cp\u003eThen it looks so much nicer to create an iterable of the values we want to filter on and join them with an apropriate regex operator (in this case \u003ccode\u003e|\u003c/code\u003e for \u003cem\u003einclusive or\u003c/em\u003e)\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=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003evals\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e  \u003cspan class=\"c1\"\u003e# iterable with whatever is appropriate for your use case\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e\u003c/span\u003e  \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eA\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estr\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003econtains\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;|\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ejoin\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evals\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"n\"\u003eregex\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\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         \u003cspan class=\"n\"\u003eA\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e  \u003cspan class=\"n\"\u003estring1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"n\"\u003estring2\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\u003ch1 id=\"fin\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFin\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eThis is a super nice and concise way to do the kind of filtering my team does on a daily basis!\u003c/p\u003e\n",
      "content_text": "\n# TL;DR \n\n`pandas.Series.str.contains` accepts regular expressions and this is turned on by __default__!\n\n# Use case\n\nWe often need to filter pandas DataFrames based on several string values in a Series.\n\n\u003e Notice that sweet pyflyby import 😁!\n\n```python\nsandbox   main via 3.8.11(sandbox) ipython\n❯ df = pd.DataFrame({\"A\": [\"string1\", \"string2\", \"string3\"]})\n[PYFLYBY] import pandas as pd\n\nsandbox   main via 3.8.11(sandbox) ipython\n❯ df\n\n         A\n0  string1\n1  string2\n2  string3\n\nsandbox   main via 3.8.11(sandbox) ipython\n❯ df[df.A.str.contains('1') | df.A.str.contains('2')]\n\n         A\n0  string1\n1  string2\n\n```\n\nAnd this isn't the worst thing in the world, especially for such a tiny example...\n\nBut what if we had dozens or more values to filter on?\n\nThen it looks so much nicer to create an iterable of the values we want to filter on and join them with an apropriate regex operator (in this case `|` for _inclusive or_)\n\n```python\n\nsandbox   main via 3.8.11(sandbox) ipython\n❯ vals = [\"1\", \"2\"]  # iterable with whatever is appropriate for your use case\n\nsandbox   main via 3.8.11(sandbox) ipython\n❯ df[df.A.str.contains(\"|\".join(vals), regex=True)]\n\n         A\n0  string1\n1  string2\n\n```\n\n# Fin\n\nThis is a super nice and concise way to do the kind of filtering my team does on a daily basis!\n",
      "summary": "accepts regular expressions and this is turned on by __!",
      "date_published": "2022-05-02T00:00:00Z",
      "date_modified": "2022-05-02T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/unpack-anywhere-with-star/",
      "url": "https://pype.dev/unpack-anywhere-with-star/",
      "title": "Unpack-Anywhere-With-Star",
      "content_html": "\u003cp\u003eUnpacking iterables in python with \u003ccode\u003e*\u003c/code\u003e is a pretty handy trick for writing code that is just a tiny bit more pythonic than not.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eTuple\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eUnion\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003eint\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e]]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;b\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;c\u0026#39;\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=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\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=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;b\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;c\u0026#39;\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\u003cspan class=\"c1\"\u003e# the * unpacks the tuple into the individual elements\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=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003earr\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=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;b\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;c\u0026#39;\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\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ez\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003ealphas\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003earr\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# x = 1, y = 2, z = 3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# alphas = [ \u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;, \u0026#39;c\u0026#39; ]\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\u003cp\u003eBut \u003ca href=\"https://twitter.com/nedbat\"\u003e@Ned Batchelder\u003c/a\u003e showed me via Twitter than you can arbitrarily unpack arguments based on position - it doesn\u0026rsquo;t have to be done at the beginning or the end!\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"n\"\u003emixed\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ealpha\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003earr\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# x = 1, y = 2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# mixed = [3, \u0026#39;a\u0026#39;, \u0026#39;b\u0026#39;]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# alpha = \u0026#39;c\u0026#39;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eI\u0026rsquo;m not entirely sure when I\u0026rsquo;ll need this but it definitley shows me another example of how flexible python is!\u003c/p\u003e\n",
      "content_text": "\n\nUnpacking iterables in python with `*` is a pretty handy trick for writing code that is just a tiny bit more pythonic than not.\n\n```python\narr: Tuple[Union[int, str]] = (1, 2, 3, 'a', 'b', 'c')\n\n\nprint(arr)\n\u003e\u003e\u003e (1, 2, 3, 'a', 'b', 'c')\n\n# the * unpacks the tuple into the individual elements\nprint(*arr)\n\u003e\u003e\u003e 1, 2, 3, 'a', 'b', 'c'\n\nx, y, z, *alphas = arr\n\n# x = 1, y = 2, z = 3\n# alphas = [ 'a', 'b', 'c' ]\n\n```\n\nBut [@Ned Batchelder](https://twitter.com/nedbat) showed me via Twitter than you can arbitrarily unpack arguments based on position - it doesn't have to be done at the beginning or the end!\n\n```python\nx, y, *mixed, alpha = arr\n\n# x = 1, y = 2\n# mixed = [3, 'a', 'b']\n# alpha = 'c'\n```\n\nI'm not entirely sure when I'll need this but it definitley shows me another example of how flexible python is!\n",
      "summary": "Unpacking iterables in python with is a pretty handy trick for writing code that is just a tiny bit more pythonic than not.",
      "date_published": "2022-04-24T00:00:00Z",
      "date_modified": "2022-04-24T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/htop/",
      "url": "https://pype.dev/htop/",
      "title": "Htop",
      "content_html": "\u003cp\u003e\u003ccode\u003ehtop\u003c/code\u003e is a common command line tool for seeing interactive output of your system resource utilization, running processes, etc.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ve always been super confused about htop showing seemingly the same process several times though\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"the-fix\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Fix…\u003c/span\u003e \u003ca href=\"#the-fix\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eJust hit \u003ccode\u003eH\u003c/code\u003e\u0026hellip;. makes the view a lot nicer 😀\u003c/p\u003e\n",
      "content_text": "\n`htop` is a common command line tool for seeing interactive output of your system resource utilization, running processes, etc.\n\nI've always been super confused about htop showing seemingly the same process several times though...\n\n## The Fix...\n\nJust hit `H`.... makes the view a lot nicer 😀\n",
      "summary": "is a common command line tool for seeing interactive output of your system resource utilization, running processes, etc.",
      "date_published": "2022-04-24T00:00:00Z",
      "date_modified": "2022-04-24T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/pipx/",
      "url": "https://pype.dev/pipx/",
      "title": "Pipx",
      "content_html": "\u003cp\u003e\u003ccode\u003epipx\u003c/code\u003e is a tool I\u0026rsquo;ve been using to solve a few problems of mine\u0026hellip;\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003epinning formatting tools like \u003ccode\u003eblack\u003c/code\u003e, \u003ccode\u003eflake8\u003c/code\u003e, \u003ccode\u003eisort\u003c/code\u003e, etc. to the same version for all my projects\u003c/li\u003e\n\u003cli\u003ekeeping virtual environments clean of things like \u003ccode\u003ecookiecutter\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003epython utilities I want system wide but not in the global environment, like \u003ccode\u003evisidata\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"what-is-it\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat is it?\u003c/span\u003e \u003ca href=\"#what-is-it\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003epip\u003c/code\u003e itself is just a package manager like \u003ccode\u003ehomebrew\u003c/code\u003e, \u003ccode\u003eapt\u003c/code\u003e, etc. But it is tied to a python environment.\nIf you aren\u0026rsquo;t using a virtual environment then \u003ccode\u003epip\u003c/code\u003e will operate inside the global installation of python.\u003c/p\u003e\n\u003cp\u003eOperating within that environment has burned me several times and now I have a strict virtual environment usage policy.\u003c/p\u003e\n\u003cp\u003eBut there are still things I don\u0026rsquo;t want to have to put in every virtual environment - enter \u003ccode\u003epipx\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"what-s-it-do\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat’s it do?\u003c/span\u003e \u003ca href=\"#what-s-it-do\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWhen you \u003ccode\u003epipx install {package}\u003c/code\u003e a stand alone virtual environment gets created (by default in \u003ccode\u003e~/.local/pipx/venvs\u003c/code\u003e).\nTHen you can install extra dependencies with \u003ccode\u003epipx inject {package} {dependency}\u003c/code\u003e\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eex. After \u003ccode\u003epipx install visidata\u003c/code\u003e in order to open Excel files you need to \u003ccode\u003epipx inject visidata xlrd\u003c/code\u003e\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn the example with \u003ccode\u003evisidata\u003c/code\u003e, I can then use it anywhere, in any project, without re-installing with \u003ccode\u003epip\u003c/code\u003e in every env.\u003c/p\u003e\n\u003cp\u003eAlso for the formatting tools - I configure vim to run the \u003ccode\u003epipx\u003c/code\u003e versions of them on save - this way I don\u0026rsquo;t have to put them in every project\u0026rsquo;s virtual environment!\u003c/p\u003e\n\u003ch2 id=\"what-about-pip\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat about pip?\u003c/span\u003e \u003ca href=\"#what-about-pip\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSo obviously you can\u0026rsquo;t \u003ccode\u003epipx\u003c/code\u003e everything, nor do you want to.\nI see it as a safe and better alternative to global package installation.\u003c/p\u003e\n\u003cp\u003eHow can you then be sure that you never \u003ccode\u003epip install\u003c/code\u003e into the global env?\u003c/p\u003e\n\u003cp\u003eAdd \u003ccode\u003erequire-virtualenv = True\u003c/code\u003e to your \u003ccode\u003epip.conf\u003c/code\u003e and you\u0026rsquo;re good to go!\u003c/p\u003e\n\u003cp\u003eWith that set, if you try to \u003ccode\u003epip install pandas\u003c/code\u003e into the global env you\u0026rsquo;ll get a message like this:\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~ on  \u003cspan class=\"o\"\u003e(\u003c/span\u003eus-east-1\u003cspan class=\"o\"\u003e)\u003c/span\u003e  NO PYTHON VENV SET\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ pip install pandas\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eERROR: Could not find an activated virtualenv \u003cspan class=\"o\"\u003e(\u003c/span\u003erequired\u003cspan class=\"o\"\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\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"end\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eEnd\u003c/span\u003e \u003ca href=\"#end\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003eDisable your system \u003ccode\u003epip\u003c/code\u003e to keep your base python safe\u003c/li\u003e\n\u003cli\u003eUse \u003ccode\u003epipx\u003c/code\u003e for tools you want available everywhere or don\u0026rsquo;t have to need in a virtual environment!\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\n\n`pipx` is a tool I've been using to solve a few problems of mine...\n\n1. pinning formatting tools like `black`, `flake8`, `isort`, etc. to the same version for all my projects\n2. keeping virtual environments clean of things like `cookiecutter`\n3. python utilities I want system wide but not in the global environment, like `visidata`\n\n## What is it?\n\n`pip` itself is just a package manager like `homebrew`, `apt`, etc. But it is tied to a python environment.\nIf you aren't using a virtual environment then `pip` will operate inside the global installation of python.\n\nOperating within that environment has burned me several times and now I have a strict virtual environment usage policy.\n\nBut there are still things I don't want to have to put in every virtual environment - enter `pipx`\n\n## What's it do?\n\nWhen you `pipx install {package}` a stand alone virtual environment gets created (by default in `~/.local/pipx/venvs`).\nTHen you can install extra dependencies with `pipx inject {package} {dependency}`\n\n\u003e ex. After `pipx install visidata` in order to open Excel files you need to `pipx inject visidata xlrd`\n\nIn the example with `visidata`, I can then use it anywhere, in any project, without re-installing with `pip` in every env.\n\nAlso for the formatting tools - I configure vim to run the `pipx` versions of them on save - this way I don't have to put them in every project's virtual environment!\n\n## What about pip?\n\nSo obviously you can't `pipx` everything, nor do you want to. \nI see it as a safe and better alternative to global package installation.\n\nHow can you then be sure that you never `pip install` into the global env?\n\nAdd `require-virtualenv = True` to your `pip.conf` and you're good to go!\n\nWith that set, if you try to `pip install pandas` into the global env you'll get a message like this:\n\n\n```bash\n\n~ on  (us-east-1)  NO PYTHON VENV SET\n❯ pip install pandas\nERROR: Could not find an activated virtualenv (required).\n\n\n```\n\n## End\n\n1. Disable your system `pip` to keep your base python safe\n2. Use `pipx` for tools you want available everywhere or don't have to need in a virtual environment!\n",
      "summary": "is a tool I've been using to solve a few problems of mine...",
      "date_published": "2022-04-22T00:00:00Z",
      "date_modified": "2022-04-22T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "til",
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/fx-json/",
      "url": "https://pype.dev/fx-json/",
      "title": "Fx-Json",
      "content_html": "\u003cp\u003e\u003ca href=\"https://github.com/antonmedv/fx\"\u003efx\u003c/a\u003e is an interactaive JSON viewer for the terminal.\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s a simple tool built with Charmcli\u0026rsquo;s Bubble Tea.\u003c/p\u003e\n\u003ch2 id=\"installation\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eInstallation\u003c/span\u003e \u003ca href=\"#installation\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe installation with go was broken for me - both via the link and direct from the repo.\nNow I\u0026rsquo;m not a gopher so I don\u0026rsquo;t really know how to fix that.\u003c/p\u003e\n\u003cp\u003eLuckily \u003ccode\u003enpm install fx\u003c/code\u003e also works and got me what I needed!\u003c/p\u003e\n\u003ch2 id=\"usage\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUsage\u003c/span\u003e \u003ca href=\"#usage\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eUsage is simple\u0026hellip; \u003ccode\u003efx \u0026lt;json file\u0026gt;\u003c/code\u003e.\nThe Github has a few other ways such as \u003ccode\u003ecurl ... | fx\u003c/code\u003e etc.\u003c/p\u003e\n",
      "content_text": "\n[fx](https://github.com/antonmedv/fx) is an interactaive JSON viewer for the terminal.\n\nIt's a simple tool built with Charmcli's Bubble Tea.\n\n## Installation\n\nThe installation with go was broken for me - both via the link and direct from the repo.\nNow I'm not a gopher so I don't really know how to fix that.\n\nLuckily `npm install fx` also works and got me what I needed!\n\n## Usage\n\nUsage is simple... `fx \u003cjson file\u003e`.\nThe Github has a few other ways such as `curl ... | fx` etc.\n",
      "summary": "fx is an interactaive JSON viewer for the terminal.",
      "date_published": "2022-04-19T00:00:00Z",
      "date_modified": "2022-04-19T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/jellyfin-media-players/",
      "url": "https://pype.dev/jellyfin-media-players/",
      "title": "Jellyfin-Media-Players",
      "content_html": "\u003cp\u003eI use Jellyfin at home for serving up most of our media - movies and shows etc.\u003c/p\u003e\n\u003cp\u003eMy dream is to have a GPU capable of transcoding any and all of our media for smooth playback on any device\u0026hellip;\nNow, I thought I\u0026rsquo;d have that by now with my Nvidia Quadro P400 however I have issues left and right with 4k content.\u003c/p\u003e\n\u003cp\u003eWhat can I do to still use Jellyfin but get smooth playback?\u003c/p\u003e\n\u003cp\u003eTHe first answer is figure out why I suck with GPUs, but pausing that there\u0026rsquo;s shorter solutions -\u0026gt; namely, use a media player that\u0026rsquo;s compatabile with the encoded content!\u003c/p\u003e\n\u003ch2 id=\"vlc\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eVLC\u003c/span\u003e \u003ca href=\"#vlc\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ll keep this one short - VLC is great and if you don\u0026rsquo;t need a netflix like experience, I\u0026rsquo;d recommend just using it to browse your network drives and play whatever you have\u003c/p\u003e\n\u003ch2 id=\"jellyfin-web-player\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eJellyfin Web Player\u003c/span\u003e \u003ca href=\"#jellyfin-web-player\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThis is the reason I\u0026rsquo;m writing this post\u0026hellip; the web player is great but not everything is supported on all devices\u003c/p\u003e\n\u003ch2 id=\"jellyfin-mpv-shim\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eJellyfin MPV Shim\u003c/span\u003e \u003ca href=\"#jellyfin-mpv-shim\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThis cross-platform cast client is my answer now.\nYou can find the project \u003ca href=\"https://github.com/jellyfin/jellyfin-mpv-shim/blob/master/README.md#linux-installation\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eThe installation instrauctions are super straightforward for Windows, Mac OS, or Linux.\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m on Linux and so my install went like this:\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\"\u003esudo apt update\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt install mpv \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\"\u003epipx install jellyfin-mpv-shim\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epipx inject jellyfin-mpv-shim pystray\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#profit\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\u003cp\u003eI used \u003ccode\u003epipx\u003c/code\u003e to install the player as I prefer it for stand alone utilities over pip installing anything globally.\u003c/p\u003e\n\u003cp\u003eAfer that I just start the player at the terminal with \u003ccode\u003ejellyfin-mpv-shim\u003c/code\u003e\nThen in the web browser I can cast my content to the player and bypass the web player (and thus solve much of my transcoding issues) trivially!\u003c/p\u003e\n",
      "content_text": "\n\nI use Jellyfin at home for serving up most of our media - movies and shows etc.\n\nMy dream is to have a GPU capable of transcoding any and all of our media for smooth playback on any device...\nNow, I thought I'd have that by now with my Nvidia Quadro P400 however I have issues left and right with 4k content.\n\nWhat can I do to still use Jellyfin but get smooth playback?\n\nTHe first answer is figure out why I suck with GPUs, but pausing that there's shorter solutions -\u003e namely, use a media player that's compatabile with the encoded content!\n\n## VLC\n\nI'll keep this one short - VLC is great and if you don't need a netflix like experience, I'd recommend just using it to browse your network drives and play whatever you have\n\n## Jellyfin Web Player\n\nThis is the reason I'm writing this post... the web player is great but not everything is supported on all devices\n\n## Jellyfin MPV Shim\n\nThis cross-platform cast client is my answer now.\nYou can find the project [here](https://github.com/jellyfin/jellyfin-mpv-shim/blob/master/README.md#linux-installation)\n\nThe installation instrauctions are super straightforward for Windows, Mac OS, or Linux.\n\nI'm on Linux and so my install went like this:\n\n```bash\n\nsudo apt update\nsudo apt install mpv \n\npipx install jellyfin-mpv-shim\npipx inject jellyfin-mpv-shim pystray\n\n#profit\n\n```\n\nI used `pipx` to install the player as I prefer it for stand alone utilities over pip installing anything globally.\n\nAfer that I just start the player at the terminal with `jellyfin-mpv-shim`\nThen in the web browser I can cast my content to the player and bypass the web player (and thus solve much of my transcoding issues) trivially!\n",
      "summary": "I use Jellyfin at home for serving up most of our media - movies and shows etc.",
      "date_published": "2022-04-17T00:00:00Z",
      "date_modified": "2022-04-17T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/typeddict/",
      "url": "https://pype.dev/typeddict/",
      "title": "Typeddict",
      "content_html": "\u003cp\u003eType hinting has helped me write code almost as much, if not more, than unit testing.\u003c/p\u003e\n\u003cp\u003eOne thing I love is that with complete type hinting you get a lot more out of your LSP.\nTyping dictionaries can be tricky and I recently learned about \u003ccode\u003eTypedDict\u003c/code\u003e to do exactly what I needed!\u003c/p\u003e\n\u003ch2 id=\"the-problem\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Problem\u003c/span\u003e \u003ca href=\"#the-problem\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eIt might not be straight up obvious what the problem is, especially if you don\u0026rsquo;t utilize tools like \u003ccode\u003emypy\u003c/code\u003e or \u003ccode\u003eflake8\u003c/code\u003e in your development.\u003c/p\u003e\n\u003cp\u003eMy handy-dandy \u003ccode\u003envim-lsp\u003c/code\u003e gives me a lot of feedback when I\u0026rsquo;m coding and it\u0026rsquo;s immensely helpful.\u003c/p\u003e\n\u003cp\u003eSo with the LSP giving me constant feedback here\u0026rsquo;s the issue:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eDict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eUnion\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\"\u003emy_dict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eDict\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eUnion\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e]]\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;key_1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;val_1\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;key_2\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;ls_1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ls_2\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003emy_dict\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;key_2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWith the above script you\u0026rsquo;ll get an annoying warning about using \u003ccode\u003epop\u003c/code\u003e on \u003ccode\u003ekey_2\u003c/code\u003e.\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/typed-dict-warning.png\" alt=\"typeddict\" title=\"Warning in TypedDict\" /\u003e\n\u003ch2 id=\"the-solution\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Solution\u003c/span\u003e \u003ca href=\"#the-solution\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eMaybe you can stomach getting yelled at by your LSP but I like complete silence if at all possible.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eTypedDict\u003c/code\u003e  was the saving grace.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eTypedDict\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\"\u003eMyDict\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eTypedDict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;MyDict\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;key_1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;key_2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\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\u003cspan class=\"n\"\u003emy_typed_dict\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMyDict\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;key_1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;val_1\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;key_2\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;ls_1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ls_2\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\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\"\u003emy_typed_dict\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;key_2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epop\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/typed-dict.png\" alt=\"typeddict\" title=\"A TypedDict\" /\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI was able to import TypedDict from typing, mypy_extensions, and typing_extensions\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eWith \u003ccode\u003eTypedDict\u003c/code\u003e you define your custom type, match the first argument to \u003ccode\u003eTypedDict\u003c/code\u003e with the name of the variable (idk why), then type hint each key you expect in the dict!\nIt\u0026rsquo;s super easy and I think puts you into a position of being extremely explicit with your dictionary variables.\nThis isn\u0026rsquo;t always desired or appropriate but in most of my use cases it is.\u003c/p\u003e\n\u003ch2 id=\"rtfm\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eRTFM\u003c/span\u003e \u003ca href=\"#rtfm\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere\u0026rsquo;s other implementation of \u003ccode\u003eTypedDict\u003c/code\u003e and while writing this I saw that most of the docs define a \u003ccode\u003eclass\u003c/code\u003e for the type like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eTypedDict\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMyDict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eTypedDict\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\"\u003ekey_1\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003estr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ekey_2\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eList\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\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\u003cspan class=\"n\"\u003emy_dict\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eMyDict\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;key_1\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;val_1\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;key_2\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;ls_1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ls_2\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ca href=\"https://peps.python.org/pep-0589/\"\u003epep docs\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://mypy.readthedocs.io/en/latest/more_types.html#typeddict\"\u003emypy docs\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nType hinting has helped me write code almost as much, if not more, than unit testing.\n\nOne thing I love is that with complete type hinting you get a lot more out of your LSP.\nTyping dictionaries can be tricky and I recently learned about `TypedDict` to do exactly what I needed!\n\n\n## The Problem\n\nIt might not be straight up obvious what the problem is, especially if you don't utilize tools like `mypy` or `flake8` in your development.\n\nMy handy-dandy `nvim-lsp` gives me a lot of feedback when I'm coding and it's immensely helpful.\n\nSo with the LSP giving me constant feedback here's the issue:\n\n```python\nfrom typing import Dict, List, Union\n\nmy_dict: Dict[str, Union[List[str], str]] = {\n    \"key_1\": \"val_1\",\n    \"key_2\": [\"ls_1\", \"ls_2\"],\n}\n\nmy_dict[\"key_2\"].pop()\n```\n\nWith the above script you'll get an annoying warning about using `pop` on `key_2`.\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/typed-dict-warning.png\" alt=\"typeddict\" title=\"Warning in TypedDict\" /\u003e\n\n\n## The Solution\n\nMaybe you can stomach getting yelled at by your LSP but I like complete silence if at all possible.\n\n`TypedDict`  was the saving grace.\n\n```python\nfrom typing import TypedDict\n\nMyDict = TypedDict(\"MyDict\", {\"key_1\": str, \"key_2\": List[str]})\n\nmy_typed_dict: MyDict = {\n    \"key_1\": \"val_1\",\n    \"key_2\": [\"ls_1\", \"ls_2\"],\n}\n\n\nmy_typed_dict[\"key_2\"].pop()\n```\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/typed-dict.png\" alt=\"typeddict\" title=\"A TypedDict\" /\u003e\n\n\u003e I was able to import TypedDict from typing, mypy_extensions, and typing_extensions\n\nWith `TypedDict` you define your custom type, match the first argument to `TypedDict` with the name of the variable (idk why), then type hint each key you expect in the dict!\nIt's super easy and I think puts you into a position of being extremely explicit with your dictionary variables. \nThis isn't always desired or appropriate but in most of my use cases it is.\n\n## RTFM\n\nThere's other implementation of `TypedDict` and while writing this I saw that most of the docs define a `class` for the type like this:\n\n```python\nfrom typing import TypedDict\nclass MyDict(TypedDict):\n    key_1: str\n    key_2: List[str]\n\nmy_dict : MyDict = {'key_1': 'val_1', 'key_2': [\"ls_1\", \"ls_2\"]}\n\n```\n\n[pep docs](https://peps.python.org/pep-0589/)\n\n[mypy docs](https://mypy.readthedocs.io/en/latest/more_types.html#typeddict)\n",
      "summary": "Type hinting has helped me write code almost as much, if not more, than unit testing.",
      "date_published": "2022-04-15T00:00:00Z",
      "date_modified": "2022-04-15T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "til",
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/home-server-refactor/",
      "url": "https://pype.dev/home-server-refactor/",
      "title": "Home-Server-Refactor",
      "content_html": "\u003cp\u003eMy current homelab setup is not great but it works\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"proxmox-on-poweredge-r610\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eProxmox on PowerEdge R610\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI boot off an SD card and have 1 SSD and 5 HDDs configured as a JBOD array using a Dell H700 SAS controller.\nI cannot boot from a disk using this controller and I can\u0026rsquo;t get the firmware configured in a way to allow it.\nSo I have 1 SSD as a ZFS array that I\u0026rsquo;ve been putting my VM images on, and the 5 HDDs are passed through to a TrueNAS VM where I handle all the ZFS stuff there\u0026hellip; kind of meta because I then attached those drives to Proxmox as a CIFS share.\u003c/p\u003e\n\u003ch1 id=\"truenas-on-dedicated-box\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTrueNAS on dedicated box\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI have an on-prem backup that is just an old desktop running TrueNAS\nI regularly backup the 5 disk RAIDZ2 array from my Proxmox host (managed by a TrueNAS VM) to this backup box\u003c/p\u003e\n\u003cp\u003eCurrently there is nothing else running on this machine since it\u0026rsquo;s my \u0026ldquo;backup\u0026rdquo;\u003c/p\u003e\n\u003ch1 id=\"jellyfin\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eJellyfin\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI was HWA for Jellyfin, but hardware passthrough on the R610 is finicky or broken so Jellyfin is running on an Ubuntu host.\u003c/p\u003e\n\u003cp\u003eI could put UBuntu on the R610 and give up \u0026ldquo;true virtualization\u0026rdquo;. Then I\u0026rsquo;d manage the SMB share myself.\nIf I do that then I would get rid of \u0026ldquo;users\u0026rdquo; I think, ie. basically forgo least-priviledges since I\u0026rsquo;m not sure how hard that is to manage.\u003c/p\u003e\n\u003cp\u003eOn the other hand, direct access to the smb config might make it easier?\u003c/p\u003e\n\u003cp\u003eI have the media array on Jellyfin box setup as NFS which was really easy with ZFS\u0026hellip; I think SMB would be just as easy.\u003c/p\u003e\n\u003ch1 id=\"plan-of-attack\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePlan of attack…\u003c/span\u003e\u003c/h1\u003e\n\u003col\u003e\n\u003cli\u003eMove all vm disks to individual datasets on the NAS\u003c/li\u003e\n\u003cli\u003eBackup docker data\u0026hellip; not sure how well this will work, maybe just start over?\u003c/li\u003e\n\u003cli\u003eClean up Ansible playbooks on the user side of things - stick with neville vs just using my own name?\u003c/li\u003e\n\u003cli\u003eInstall Ubuntu 20 or 22 on a 2.5\u0026quot; drive that I\u0026rsquo;ll toss in this SSD enclosure (or a usb thumb stick?)\u003c/li\u003e\n\u003cli\u003eRe-deploy everything with ansible-playbook and configure\u0026hellip;\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"configuration\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eConfiguration…\u003c/span\u003e \u003ca href=\"#configuration\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003col start=\"0\"\u003e\n\u003cli\u003eTHE FREAKING NAS -\u0026gt; just import zfs array and configure SMB?\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e1.~~ Nextcloud users and connections.. might be able to just copy the data folder? not sure about the database\u0026hellip; try spinning it up in the sandbox vm and see if stuff is there ~~\n2.~~ *arr suite, media profiles and connections to transmission\u0026hellip; nothing major~~\n3. transmission - should be deploy and go\n4. ombi and jackett should also just work after some config again\n5. \u003cdel\u003etraefik should just work\u003c/del\u003e\n6. \u003cdel\u003etry to bring up pi-hole from the vm that\u0026rsquo;s already running\u003c/del\u003e\n7. \u003cdel\u003eheimdall will hopefully just be copying the data folder from the existind docker one\u0026rsquo;\u003c/del\u003e\n8.~~ booksonic can be reconfigured easily~~\n9. \u003cdel\u003eportainer\u0026hellip; hopefully just copying data folder over?\u003c/del\u003e\n10. \u003cdel\u003elittlelink\u003c/del\u003e, small-group-notes, and blog (at home) will need manually re-deployed once Ubuntu is installed bare-metal\u003c/p\u003e\n\u003ch2 id=\"big-big-big-todos\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBIG BIG BIG TODOS\u003c/span\u003e \u003ca href=\"#big-big-big-todos\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eSanoid/syncoid! Get snapshots going and backups configured with on prem TrueNAS\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eWireguard setup on DA.\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003enetwork share on printer for paperless\n\u003cdel\u003e4. update peperless in ansible-nas\u003c/del\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cdel\u003eJust deploy paperless manually\u0026hellip; monitor/manage with portainer\u003c/del\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ebooksonic not seeing audiobooks/podcasts\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eneed a smb user to map nas/documents to the printer for paperless\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003ewireguard setup now on kps phone, desktop, server (and backup truenas?), and dad\u0026rsquo;s pi\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cdel\u003everify lan services work\u003c/del\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cdel\u003eTdar so Jellyfin can work better\u003c/del\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eSnapshot business might be cause of all the docker containers and docker using\nZFS backend\u0026hellip; take everything down and try removing\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003efile browser - currently I just one-clicked in portainer, I want to make a stack with my own config file which I\u0026rsquo;ll rip from techno tip and then add my traefik lables too\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eForget filebrowser - going to just use Nextcloud for how it\u0026rsquo;s supposed to be used.\n3. Need to organize those files in nextcloud\u003c/p\u003e\n\u003ch2 id=\"check-this-ran-as-cp-utility-in-tmux-window-no-progress-bars-or-anything-it-s-in-ansible-nas-session-window-3\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCHECK THIS -\u0026gt;  ran as ‘cp’ utility in tmux window, no progress bars or anything. it’s in ansible-nas session -\u0026gt; window 3\u003c/span\u003e \u003ca href=\"#check-this-ran-as-cp-utility-in-tmux-window-no-progress-bars-or-anything-it-s-in-ansible-nas-session-window-3\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOlivet bible stuff going to /tmp/olivet/ -\u0026gt; will move this to nextcloud, ideally by the app via appimage so that the db updates and I don\u0026rsquo;t have to run that occ script\nI wnat to organize \u0026ldquo;home\u0026rdquo; still in nextcloud\u003c/p\u003e\n\u003cp\u003esetup Sanoid\u003c/p\u003e\n\u003cp\u003eclean up bitwarden\nlearn nextcloud sharing -\u0026gt; maybe just give a link to grandma?\nrest of todos -\u0026gt; document db and sanoid + zfs.rent\u003c/p\u003e\n\u003cp\u003eCheck on mom\u0026rsquo;s will\ndo media thing for church - split vocals on mp3/4\u003c/p\u003e\n\u003cp\u003epermission-data playbook changes everything to ansible-nas:ansible-nas but then samba task will re-permission some stuff to root:users\u0026hellip; this looks fine\nI had to add \u003ccode\u003egroup\u003c/code\u003e to the samba config in my playbook to get user auth to work with samba\nThis isn\u0026rsquo;t fully working\u0026hellip; it works from cli but my python process can\u0026rsquo;t write to a folder in dump after 777\u0026hellip;. need to learn more?\nSo I can make a file after adding the ansible-nas group to config, but I still cannot make a directory on the smb mount\u0026hellip;\u003c/p\u003e\n\u003cp\u003eADDING \u003ccode\u003einherit permission = yes\u003c/code\u003e under \u003ccode\u003e[global]\u003c/code\u003e in the smb.conf worked!\u003c/p\u003e\n\u003cp\u003estill not working from printer\u0026hellip;\nI think what I want is to setup 2 scan options - single docs right to paperless, or combined scans to dump, then manually split and send to paperless\u003c/p\u003e\n",
      "content_text": "\nMy current homelab setup is not great but it works...\n\n# Proxmox on PowerEdge R610\n\nI boot off an SD card and have 1 SSD and 5 HDDs configured as a JBOD array using a Dell H700 SAS controller.\nI cannot boot from a disk using this controller and I can't get the firmware configured in a way to allow it.\nSo I have 1 SSD as a ZFS array that I've been putting my VM images on, and the 5 HDDs are passed through to a TrueNAS VM where I handle all the ZFS stuff there... kind of meta because I then attached those drives to Proxmox as a CIFS share.\n\n# TrueNAS on dedicated box\n\nI have an on-prem backup that is just an old desktop running TrueNAS\nI regularly backup the 5 disk RAIDZ2 array from my Proxmox host (managed by a TrueNAS VM) to this backup box\n\nCurrently there is nothing else running on this machine since it's my \"backup\"\n\n# Jellyfin\n\nI was HWA for Jellyfin, but hardware passthrough on the R610 is finicky or broken so Jellyfin is running on an Ubuntu host.\n\nI could put UBuntu on the R610 and give up \"true virtualization\". Then I'd manage the SMB share myself.\nIf I do that then I would get rid of \"users\" I think, ie. basically forgo least-priviledges since I'm not sure how hard that is to manage.\n\nOn the other hand, direct access to the smb config might make it easier?\n\nI have the media array on Jellyfin box setup as NFS which was really easy with ZFS... I think SMB would be just as easy.\n\n# Plan of attack...\n\n1. Move all vm disks to individual datasets on the NAS \n2. Backup docker data... not sure how well this will work, maybe just start over?\n3. Clean up Ansible playbooks on the user side of things - stick with neville vs just using my own name?\n4. Install Ubuntu 20 or 22 on a 2.5\" drive that I'll toss in this SSD enclosure (or a usb thumb stick?)\n5. Re-deploy everything with ansible-playbook and configure...\n\n## Configuration...\n\n0. THE FREAKING NAS -\u003e just import zfs array and configure SMB?\n\n1.~~ Nextcloud users and connections.. might be able to just copy the data folder? not sure about the database... try spinning it up in the sandbox vm and see if stuff is there ~~\n2.~~ *arr suite, media profiles and connections to transmission... nothing major~~\n3. transmission - should be deploy and go\n4. ombi and jackett should also just work after some config again\n5. ~~traefik should just work~~\n6. ~~try to bring up pi-hole from the vm that's already running~~\n7. ~~heimdall will hopefully just be copying the data folder from the existind docker one'~~\n8.~~ booksonic can be reconfigured easily~~\n9. ~~portainer... hopefully just copying data folder over?~~\n10. ~~littlelink~~, small-group-notes, and blog (at home) will need manually re-deployed once Ubuntu is installed bare-metal\n\n## BIG BIG BIG TODOS\n1. Sanoid/syncoid! Get snapshots going and backups configured with on prem TrueNAS\n2. Wireguard setup on DA.\n3. network share on printer for paperless\n~~4. update peperless in ansible-nas~~\n4. ~~Just deploy paperless manually... monitor/manage with portainer~~\n5. booksonic not seeing audiobooks/podcasts\n\n1. need a smb user to map nas/documents to the printer for paperless\n3. wireguard setup now on kps phone, desktop, server (and backup truenas?), and dad's pi\n4. ~~verify lan services work~~\n5. ~~Tdar so Jellyfin can work better~~\n\nSnapshot business might be cause of all the docker containers and docker using\nZFS backend... take everything down and try removing\n\n1. file browser - currently I just one-clicked in portainer, I want to make a stack with my own config file which I'll rip from techno tip and then add my traefik lables too\n\nForget filebrowser - going to just use Nextcloud for how it's supposed to be used.\n3. Need to organize those files in nextcloud\n## CHECK THIS -\u003e  ran as 'cp' utility in tmux window, no progress bars or anything. it's in ansible-nas session -\u003e window 3\nOlivet bible stuff going to /tmp/olivet/ -\u003e will move this to nextcloud, ideally by the app via appimage so that the db updates and I don't have to run that occ script\nI wnat to organize \"home\" still in nextcloud \n\nsetup Sanoid\n\n\nclean up bitwarden \nlearn nextcloud sharing -\u003e maybe just give a link to grandma?\nrest of todos -\u003e document db and sanoid + zfs.rent\n\nCheck on mom's will\ndo media thing for church - split vocals on mp3/4\n\npermission-data playbook changes everything to ansible-nas:ansible-nas but then samba task will re-permission some stuff to root:users... this looks fine\nI had to add `group` to the samba config in my playbook to get user auth to work with samba\nThis isn't fully working... it works from cli but my python process can't write to a folder in dump after 777.... need to learn more?\nSo I can make a file after adding the ansible-nas group to config, but I still cannot make a directory on the smb mount...\n\nADDING `inherit permission = yes` under `[global]` in the smb.conf worked!\n\nstill not working from printer...\nI think what I want is to setup 2 scan options - single docs right to paperless, or combined scans to dump, then manually split and send to paperless\n",
      "summary": "My current homelab setup is not great but it works...",
      "date_published": "2022-04-10T00:00:00Z",
      "date_modified": "2022-04-10T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "blog",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/and-vs/",
      "url": "https://pype.dev/and-vs/",
      "title": "And-vs-\u0026",
      "content_html": "\u003cp\u003eI often struggle to remember the correct way to do \u003ccode\u003eand\u003c/code\u003e type comparisons when working in pandas.\u003c/p\u003e\n\u003cp\u003eI remember learning long long ago that \u003ccode\u003eand\u003c/code\u003e and \u003ccode\u003e\u0026amp;\u003c/code\u003e are different, the former being lazy boolean evaluation whereas the latter is a bitwise operation.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eI learned a lot from \u003ca href=\"https://stackoverflow.com/questions/22646463/and-boolean-vs-bitwise-why-difference-in-behavior-with-lists-vs-nump\"\u003ethis SO post\u003c/a\u003e\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"lists\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eLists\u003c/span\u003e \u003ca href=\"#lists\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003ePython \u003ccode\u003elist\u003c/code\u003e objects can contain unlike elements - ie. \u003ccode\u003e[True, 'foo', 1, '1', [1,2,3]]\u003c/code\u003e is a valid list with booleans, strings, integers, and another list.\nBecause of this, we can\u0026rsquo;t use \u003ccode\u003e\u0026amp;\u003c/code\u003e to compare two lists since they can\u0026rsquo;t be combined in a consistent and meaningful way.\u003c/p\u003e\n\u003cp\u003eHowever we can use \u003ccode\u003eand\u003c/code\u003e since it doesn\u0026rsquo;t do bitwise operations, it just evaluates the boolean value of the list (basically if it\u0026rsquo;s non-empty then \u003ccode\u003ebool(my_list)\u003c/code\u003e evaluates to \u003ccode\u003eTrue\u003c/code\u003e)\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s an example:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003emy_list\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;2\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;foo\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emy_list\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=\"kc\"\u003eTrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eIf we compare \u003ccode\u003emy_list\u003c/code\u003e with \u003ccode\u003eanother_list\u003c/code\u003e using \u003ccode\u003eand\u003c/code\u003e then the comparision will go:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003eif bool(my_list):\n    if bool(another_list):\n       \u0026lt;operation\u0026gt; \n    else:\n       break\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eLet\u0026rsquo;s see another example:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003eanother_list\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"kc\"\u003eFalse\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003emy_list\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eanother_list\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\u003cspan class=\"kc\"\u003eFalse\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003ebool(my_list)\u003c/code\u003e evaluated to \u003ccode\u003eTrue\u003c/code\u003e, and \u003ccode\u003ebool(another_list)\u003c/code\u003e \u003cem\u003ealso\u003c/em\u003e evaluated to \u003ccode\u003eTrue\u003c/code\u003e even though it\u0026rsquo;s full of \u003ccode\u003eFalse\u003c/code\u003e values because the object is non-empty.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003emy_list\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"n\"\u003eanother_list\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\"\u003efoo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSo using \u003ccode\u003eand\u003c/code\u003e in this case results in a \u003ccode\u003eTrue\u003c/code\u003e conditional, so the \u003ccode\u003eprint\u003c/code\u003e statement is executed.\u003c/p\u003e\n\u003cp\u003eFeels kind of counter-intuitive at first glance, to me anyways\u0026hellip;\u003c/p\u003e\n\u003cp\u003eHowever, we can\u0026rsquo;t use \u003ccode\u003e\u0026amp;\u003c/code\u003e because there isn\u0026rsquo;t a meaningful to do bitwise operations over these two lists:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003emy_list\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003eanother_list\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╭───────────────────────────────\u003c/span\u003e \u003cspan class=\"n\"\u003eTraceback\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emost\u003c/span\u003e \u003cspan class=\"n\"\u003erecent\u003c/span\u003e \u003cspan class=\"n\"\u003ecall\u003c/span\u003e \u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"err\"\u003e────────────────────────────────╮\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eipython\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nb\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e19\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003ea2a16cebb3da\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e                                              \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"ne\"\u003eTypeError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eunsupported\u003c/span\u003e \u003cspan class=\"n\"\u003eoperand\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003es\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;list\u0026#39;\u003c/span\u003e \u003cspan class=\"ow\"\u003eand\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;list\u0026#39;\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\u003ch2 id=\"numpy\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eNumpy\u003c/span\u003e \u003ca href=\"#numpy\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003enumpy\u003c/code\u003e arrays are special and they have a lot of fancy vectorization utilities built-in which make them great and fast for mathematical operations but now our logical comparisons need to be handled with a different kind of care.\u003c/p\u003e\n\u003cp\u003eFirst thing though - without some trickery they do not hold mixed data types like a \u003ccode\u003elist\u003c/code\u003e does (necessary, I think, for the vectorized optimization that numpy is built on top of)\u003c/p\u003e\n\u003cp\u003eWith that out of the way here\u0026rsquo;s the main thing for this post, we can\u0026rsquo;t just evaluate the \u003ccode\u003ebool\u003c/code\u003e of an array - numpy says no no no.\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=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003earr\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003enp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003earr\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;1\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;2\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;True\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;False\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e],\u003c/span\u003e \u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u0026lt;U21\u0026#39;\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\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\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=\"err\"\u003e╭───────────────────────────────\u003c/span\u003e \u003cspan class=\"n\"\u003eTraceback\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emost\u003c/span\u003e \u003cspan class=\"n\"\u003erecent\u003c/span\u003e \u003cspan class=\"n\"\u003ecall\u003c/span\u003e \u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"err\"\u003e────────────────────────────────╮\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eipython\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nb\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e25\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mf\"\u003e4e8\u003c/span\u003e\u003cspan class=\"n\"\u003ec5dd85b93\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e                                              \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eThe\u003c/span\u003e \u003cspan class=\"n\"\u003etruth\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"n\"\u003eof\u003c/span\u003e \u003cspan class=\"n\"\u003ean\u003c/span\u003e \u003cspan class=\"n\"\u003earray\u003c/span\u003e \u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"n\"\u003emore\u003c/span\u003e \u003cspan class=\"n\"\u003ethan\u003c/span\u003e \u003cspan class=\"n\"\u003eone\u003c/span\u003e \u003cspan class=\"n\"\u003eelement\u003c/span\u003e \u003cspan class=\"ow\"\u003eis\u003c/span\u003e \u003cspan class=\"n\"\u003eambiguous\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e \u003cspan class=\"n\"\u003eUse\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eall\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\u003cblockquote\u003e\n\u003cp\u003eThis means that using \u003ccode\u003eand\u003c/code\u003e with \u003ccode\u003enumpy\u003c/code\u003e arrays doesn\u0026rsquo;t really make sense because we probably care about the truth value of each element (bitwise), not the truth value of the array.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eNotice that when I print \u003ccode\u003earr\u003c/code\u003e all the elements are a string - and the \u003ccode\u003edtype\u003c/code\u003e is \u003ccode\u003e\u0026lt;U21\u003c/code\u003e for all elements.\u003c/p\u003e\n\u003cp\u003eThis is not how I instantiated the array so be aware of that behavior with numpy.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003e\u0026lt;U21\u003c/code\u003e is a dtype expressing the values are \u0026lsquo;Little Endian\u0026rsquo;, Unicode, 12 characters. See \u003ca href=\"https://numpy.org/doc/stable/reference/arrays.dtypes.html#specifying-and-constructing-data-types\"\u003ehere\u003c/a\u003e for docs for docs\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eSo for logical comparisions we should look at the error message then\u0026hellip;\nOur handy error message says to try \u003ccode\u003eany\u003c/code\u003e or \u003ccode\u003eall\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eBecause the datatypes in this example are basically strings, using \u003ccode\u003earr.any()\u003c/code\u003e will result in an error that I do not fully understand, but \u003ccode\u003eany(arr)\u003c/code\u003e and \u003ccode\u003eall(arr)\u003c/code\u003e work\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003earr\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eany\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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=\"err\"\u003e╭───────────────────────────────\u003c/span\u003e \u003cspan class=\"n\"\u003eTraceback\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emost\u003c/span\u003e \u003cspan class=\"n\"\u003erecent\u003c/span\u003e \u003cspan class=\"n\"\u003ecall\u003c/span\u003e \u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"err\"\u003e────────────────────────────────╮\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eipython\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nb\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e48\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e25\u003c/span\u003e\u003cspan class=\"n\"\u003eecac52db96\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e                                              \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ehome\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003eu_paynen3\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003epersonal\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"o\"\u003e/.\u003c/span\u003e\u003cspan class=\"n\"\u003evenv\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003elib\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003epython3\u003c/span\u003e\u003cspan class=\"mf\"\u003e.8\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003esite\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003epackages\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003enumpy\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ecore\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003e_methods\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e57\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003e_any\u003c/span\u003e                                                                                     \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e                                                                                                  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e54\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_any\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekeepdims\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewhere\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e               \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e55\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"c1\"\u003e# Parsing keyword arguments is currently fairly slow, so avoid it for now              │\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e56\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003ewhere\u003c/span\u003e \u003cspan class=\"ow\"\u003eis\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e                                                                      \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"err\"\u003e❱\u003c/span\u003e  \u003cspan class=\"mi\"\u003e57\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eumr_any\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekeepdims\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                                      \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e58\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003eumr_any\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekeepdims\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewhere\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003ewhere\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e                             \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e59\u003c/span\u003e                                                                                            \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e60\u003c/span\u003e \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e_all\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eaxis\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eout\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ekeepdims\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ewhere\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e               \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eUFuncTypeError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eufunc\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;logical_or\u0026#39;\u003c/span\u003e \u003cspan class=\"n\"\u003edid\u003c/span\u003e \u003cspan class=\"ow\"\u003enot\u003c/span\u003e \u003cspan class=\"n\"\u003econtain\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"n\"\u003eloop\u003c/span\u003e \u003cspan class=\"k\"\u003ewith\u003c/span\u003e \u003cspan class=\"n\"\u003esignature\u003c/span\u003e \u003cspan class=\"n\"\u003ematching\u003c/span\u003e \u003cspan class=\"n\"\u003etypes\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"err\"\u003e\u0026#39;\u003c/span\u003e\u003cspan class=\"nc\"\u003enumpy\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003estr_\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;\u0026gt;) -\u0026gt; None\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\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=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eall\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\"\u003efoo\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003eany\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003earr\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\"\u003efoo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eLet\u0026rsquo;s change the example to just use integers and see what happens:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003earr2\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003enp\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003earr2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003earray\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\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\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003earr2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eany\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\"\u003efoo\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003earr2\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eall\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAh, now some sanity\u0026hellip;\nFirst, the booleans are stored as integers, which based on this discussion makes sense.\nNext we check if \u003ccode\u003eany\u003c/code\u003e values (this is a bitwise operation) are \u003ccode\u003eTrue\u003c/code\u003e, which we see they are so the conditional evaluates to \u003ccode\u003eTrue\u003c/code\u003e.\nHowver, if we check that \u003ccode\u003eall\u003c/code\u003e values are \u003ccode\u003eTrue\u003c/code\u003e we see they aren\u0026rsquo;t, the last value is \u003ccode\u003eFalse\u003c/code\u003e or \u003ccode\u003e0\u003c/code\u003e so the conditional fails.\u003c/p\u003e\n\u003cp\u003eThis is a different way to evaluate logical conditions than with lists and it\u0026rsquo;s because of the special nature of numpy arrays that allows them to be compared bitwise but on the flip side, there isn\u0026rsquo;t a meaningful way to evaluate the \u003ccode\u003etruth value\u003c/code\u003e of an array.\u003c/p\u003e\n\u003ch2 id=\"pandas\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePandas\u003c/span\u003e \u003ca href=\"#pandas\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNow for \u003ccode\u003epandas\u003c/code\u003e, which under the hood is a lot of \u003ccode\u003enumpy\u003c/code\u003e but not fully.\n\u003ccode\u003epandas.Series\u003c/code\u003e objects can hold mixed data types like lists, however to logically evaluate truth values we have to treat them like numpy arrays.\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=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSeries\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;foo\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003es\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=\"mi\"\u003e0\u003c/span\u003e        \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e      \u003cspan class=\"n\"\u003efoo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e     \u003cspan class=\"kc\"\u003eTrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e    \u003cspan class=\"kc\"\u003eFalse\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eobject\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"nb\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003es\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=\"err\"\u003e╭───────────────────────────────\u003c/span\u003e \u003cspan class=\"n\"\u003eTraceback\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emost\u003c/span\u003e \u003cspan class=\"n\"\u003erecent\u003c/span\u003e \u003cspan class=\"n\"\u003ecall\u003c/span\u003e \u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"err\"\u003e────────────────────────────────╮\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eipython\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nb\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e60\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mf\"\u003e68e48\u003c/span\u003e\u003cspan class=\"n\"\u003ee81da14\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003ecell\u003c/span\u003e \u003cspan class=\"n\"\u003eline\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e                                              \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ehome\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003eu_paynen3\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003epersonal\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"o\"\u003e/.\u003c/span\u003e\u003cspan class=\"n\"\u003evenv\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003elib\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003epython3\u003c/span\u003e\u003cspan class=\"mf\"\u003e.8\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003esite\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003epackages\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003epandas\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003ecore\u003c/span\u003e\u003cspan class=\"o\"\u003e/\u003c/span\u003e\u003cspan class=\"n\"\u003egeneric\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ep\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"n\"\u003ey\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e1527\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003e__nonzero__\u003c/span\u003e                                                                            \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e                                                                                                  \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e1524\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e                                                                                        \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e1525\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"nd\"\u003e@final\u003c/span\u003e                                                                               \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e1526\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003e__nonzero__\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e                                                               \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"err\"\u003e❱\u003c/span\u003e  \u003cspan class=\"mi\"\u003e1527\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"k\"\u003eraise\u003c/span\u003e \u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e                                                                \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e1528\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;The truth value of a \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"vm\"\u003e__name__\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e is ambiguous. \u0026#34;\u003c/span\u003e                 \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e1529\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"s2\"\u003e\u0026#34;Use a.empty, a.bool(), a.item(), a.any() or a.all().\u0026#34;\u003c/span\u003e                       \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e    \u003cspan class=\"mi\"\u003e1530\u003c/span\u003e \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"err\"\u003e│\u003c/span\u003e   \u003cspan class=\"p\"\u003e)\u003c/span\u003e                                                                                \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eThe\u003c/span\u003e \u003cspan class=\"n\"\u003etruth\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"n\"\u003eof\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"n\"\u003eSeries\u003c/span\u003e \u003cspan class=\"ow\"\u003eis\u003c/span\u003e \u003cspan class=\"n\"\u003eambiguous\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e \u003cspan class=\"n\"\u003eUse\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eempty\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eall\u003c/span\u003e\u003cspan class=\"p\"\u003e()\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\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eJust like with numpy, we can\u0026rsquo;t evaluate the truth value of the series in a meaningful way, but bitwise operations make perfect sense\u0026hellip;\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=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003es\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eany\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\"\u003efoo\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003es\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eall\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=\"o\"\u003e...\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e     \u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;foo\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\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003cstrong\u003eI thought this was about \u003ccode\u003eand\u003c/code\u003e and \u003ccode\u003e\u0026amp;\u003c/code\u003e\u0026hellip;\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eRight, so recall that \u003ccode\u003eand\u003c/code\u003e is a lazy boolean evaluation (ie. it evaluates the \u0026rsquo;truth value\u0026rsquo; an object) whereas \u003ccode\u003e\u0026amp;\u003c/code\u003e does bitwise comparison.\u003c/p\u003e\n\u003cp\u003eWhat we see then with \u003ccode\u003epandas\u003c/code\u003e and \u003ccode\u003enumpy\u003c/code\u003e is that if we want to do logical comparisons, we need to do them bitwise, ie. use \u003ccode\u003e\u0026amp;\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eKeep in mind though that the data types make a big deal - we can\u0026rsquo;t use \u003ccode\u003e\u0026amp;\u003c/code\u003e with strings  because the bitwise operation isn\u0026rsquo;t supported, for strings we need to use the boolean evaluation.\u003c/p\u003e\n\u003ch2 id=\"the-original-point\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Original Point\u003c/span\u003e \u003ca href=\"#the-original-point\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eMy main use case for this is finding elements in a dataframe/series based on 2 or more columns aligning row values\u0026hellip;\u003c/p\u003e\n\u003cp\u003eSay I have a dataframe like this:\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=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\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\"\u003es\u003c/span\u003e \u003cspan class=\"n\"\u003es2\u003c/span\u003e   \u003cspan class=\"n\"\u003es3\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e0\u003c/span\u003e  \u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"mi\"\u003e0\u003c/span\u003e  \u003cspan class=\"n\"\u003efoo\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"n\"\u003ea\u003c/span\u003e  \u003cspan class=\"n\"\u003ebar\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e  \u003cspan class=\"mi\"\u003e1\u003c/span\u003e  \u003cspan class=\"n\"\u003eb\u003c/span\u003e  \u003cspan class=\"n\"\u003ebaz\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e  \u003cspan class=\"mi\"\u003e2\u003c/span\u003e  \u003cspan class=\"n\"\u003ea\u003c/span\u003e  \u003cspan class=\"n\"\u003efee\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"mi\"\u003e4\u003c/span\u003e  \u003cspan class=\"mi\"\u003e2\u003c/span\u003e  \u003cspan class=\"mi\"\u003e0\u003c/span\u003e   \u003cspan class=\"n\"\u003efi\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eExample use case is I want to get the values in \u003ccode\u003es3\u003c/code\u003e where \u003ccode\u003es\u003c/code\u003e is 1 and \u003ccode\u003es2\u003c/code\u003e is \u0026lsquo;a\u0026rsquo;. ie. I\u0026rsquo;m just after \u003ccode\u003ebar\u003c/code\u003e for now\u0026hellip;\u003c/p\u003e\n\u003cp\u003eUp until now I\u0026rsquo;ve always just tried \u003ccode\u003edf.s3[(df.s == 1) and (df.s2 == \u0026quot;a\u0026quot;)]\u003c/code\u003e the first time and every single time I\u0026rsquo;ve gotten this error that I just haven\u0026rsquo;t ever fully understood:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"ne\"\u003eValueError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eThe\u003c/span\u003e \u003cspan class=\"n\"\u003etruth\u003c/span\u003e \u003cspan class=\"n\"\u003evalue\u003c/span\u003e \u003cspan class=\"n\"\u003eof\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e \u003cspan class=\"n\"\u003eSeries\u003c/span\u003e \u003cspan class=\"ow\"\u003eis\u003c/span\u003e \u003cspan class=\"n\"\u003eambiguous\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e \u003cspan class=\"n\"\u003eUse\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eempty\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ebool\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eitem\u003c/span\u003e\u003cspan class=\"p\"\u003e(),\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eany\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"n\"\u003ea\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eall\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eBut after this deep dive I think I\u0026rsquo;ve grasped that \u003ccode\u003eand\u003c/code\u003e doesn\u0026rsquo;t actually do what I want here, and in order to do the bitwise comparision I need to use \u003ccode\u003e\u0026amp;\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"n\"\u003eNO\u003c/span\u003e \u003cspan class=\"n\"\u003eVCS\u003c/span\u003e  \u003cspan class=\"n\"\u003evia\u003c/span\u003e \u003cspan class=\"mf\"\u003e3.8.11\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003es3\u003c/span\u003e\u003cspan class=\"p\"\u003e[(\u003c/span\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003es\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026amp;\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003es2\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;a\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\u003cspan class=\"mi\"\u003e1\u003c/span\u003e    \u003cspan class=\"n\"\u003ebar\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eName\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003es3\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edtype\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003eobject\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"end\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eEnd\u003c/span\u003e \u003ca href=\"#end\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHopefully this set of ramblings brings some clarity to \u003ccode\u003eand\u003c/code\u003e and \u003ccode\u003e\u0026amp;\u003c/code\u003e and you can Google one less error in the future in your logical comparison workflows 😄\u003c/p\u003e\n",
      "content_text": "\nI often struggle to remember the correct way to do `and` type comparisons when working in pandas.\n\nI remember learning long long ago that `and` and `\u0026` are different, the former being lazy boolean evaluation whereas the latter is a bitwise operation.\n\n__I learned a lot from [this SO post](https://stackoverflow.com/questions/22646463/and-boolean-vs-bitwise-why-difference-in-behavior-with-lists-vs-nump)__\n\n## Lists \n\nPython `list` objects can contain unlike elements - ie. `[True, 'foo', 1, '1', [1,2,3]]` is a valid list with booleans, strings, integers, and another list.\nBecause of this, we can't use `\u0026` to compare two lists since they can't be combined in a consistent and meaningful way.\n\nHowever we can use `and` since it doesn't do bitwise operations, it just evaluates the boolean value of the list (basically if it's non-empty then `bool(my_list)` evaluates to `True`)\n\nHere's an example:\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ my_list = [1, \"2\", \"foo\", [True], False]\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ bool(my_list)\nTrue\n```\n\n\nIf we compare `my_list` with `another_list` using `and` then the comparision will go:\n\n```\nif bool(my_list):\n    if bool(another_list):\n       \u003coperation\u003e \n    else:\n       break\n```\n\nLet's see another example:\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ another_list = [False, False]\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ my_list and another_list\n[False, False]\n```\n\n\n`bool(my_list)` evaluated to `True`, and `bool(another_list)` _also_ evaluated to `True` even though it's full of `False` values because the object is non-empty.\n\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ if my_list and another_list:\n...:     print(\"foo\")\nfoo\n```\n\nSo using `and` in this case results in a `True` conditional, so the `print` statement is executed.\n\nFeels kind of counter-intuitive at first glance, to me anyways...\n\nHowever, we can't use `\u0026` because there isn't a meaningful to do bitwise operations over these two lists:\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ my_list \u0026 another_list\n╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n│ \u003cipython-input-19-a2a16cebb3da\u003e:1 in \u003ccell line: 1\u003e                                              │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\nTypeError: unsupported operand type(s) for \u0026: 'list' and 'list'\n\n```\n\n## Numpy\n\n`numpy` arrays are special and they have a lot of fancy vectorization utilities built-in which make them great and fast for mathematical operations but now our logical comparisons need to be handled with a different kind of care.\n\nFirst thing though - without some trickery they do not hold mixed data types like a `list` does (necessary, I think, for the vectorized optimization that numpy is built on top of)\n\nWith that out of the way here's the main thing for this post, we can't just evaluate the `bool` of an array - numpy says no no no.\n\n```python\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ arr = np.array([\"1\", 2, True, False])\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ arr\narray(['1', '2', 'True', 'False'], dtype='\u003cU21')\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ bool(arr)\n╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n│ \u003cipython-input-25-4e8c5dd85b93\u003e:1 in \u003ccell line: 1\u003e                                              │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\nValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()\n\n```\n\n\u003e This means that using `and` with `numpy` arrays doesn't really make sense because we probably care about the truth value of each element (bitwise), not the truth value of the array.\n\nNotice that when I print `arr` all the elements are a string - and the `dtype` is `\u003cU21` for all elements.\n\nThis is not how I instantiated the array so be aware of that behavior with numpy.\n\n\u003e `\u003cU21` is a dtype expressing the values are 'Little Endian', Unicode, 12 characters. See [here](https://numpy.org/doc/stable/reference/arrays.dtypes.html#specifying-and-constructing-data-types) for docs for docs\n\nSo for logical comparisions we should look at the error message then...\nOur handy error message says to try `any` or `all`\n\nBecause the datatypes in this example are basically strings, using `arr.any()` will result in an error that I do not fully understand, but `any(arr)` and `all(arr)` work...\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ if arr.any():\n...:     print(\"foo\")\n╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n│ \u003cipython-input-48-25ecac52db96\u003e:1 in \u003ccell line: 1\u003e                                              │\n│ /home/u_paynen3/personal/sandbox/.venv/sandbox/lib/python3.8/site-packages/numpy/core/_methods.p │\n│ y:57 in _any                                                                                     │\n│                                                                                                  │\n│    54 def _any(a, axis=None, dtype=None, out=None, keepdims=False, *, where=True):               │\n│    55 │   # Parsing keyword arguments is currently fairly slow, so avoid it for now              │\n│    56 │   if where is True:                                                                      │\n│ ❱  57 │   │   return umr_any(a, axis, dtype, out, keepdims)                                      │\n│    58 │   return umr_any(a, axis, dtype, out, keepdims, where=where)                             │\n│    59                                                                                            │\n│    60 def _all(a, axis=None, dtype=None, out=None, keepdims=False, *, where=True):               │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\nUFuncTypeError: ufunc 'logical_or' did not contain a loop with signature matching types (None, \u003cclass 'numpy.dtype[str_]'\u003e) -\u003e None\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n\n❯ if all(arr):\n...:     print(\"foo\")\nfoo\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ if any(arr):\n...:     print(\"foo\")\nfoo\n```\n\nLet's change the example to just use integers and see what happens:\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ arr2 = np.array([1, True, False])\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ arr2\narray([1, 1, 0])\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ if arr2.any():\n...:     print(\"foo\")\nfoo\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ if arr2.all():\n...:     print(\"foo\")\n\n```\n\nAh, now some sanity...\nFirst, the booleans are stored as integers, which based on this discussion makes sense.\nNext we check if `any` values (this is a bitwise operation) are `True`, which we see they are so the conditional evaluates to `True`.\nHowver, if we check that `all` values are `True` we see they aren't, the last value is `False` or `0` so the conditional fails.\n\nThis is a different way to evaluate logical conditions than with lists and it's because of the special nature of numpy arrays that allows them to be compared bitwise but on the flip side, there isn't a meaningful way to evaluate the `truth value` of an array.\n\n\n## Pandas\n\nNow for `pandas`, which under the hood is a lot of `numpy` but not fully. \n`pandas.Series` objects can hold mixed data types like lists, however to logically evaluate truth values we have to treat them like numpy arrays.\n\n```python\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ s = pd.Series([1, \"foo\", True, False])\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ s\n\n0        1\n1      foo\n2     True\n3    False\ndtype: object\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ bool(s)\n╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n│ \u003cipython-input-60-68e48e81da14\u003e:1 in \u003ccell line: 1\u003e                                              │\n│ /home/u_paynen3/personal/sandbox/.venv/sandbox/lib/python3.8/site-packages/pandas/core/generic.p │\n│ y:1527 in __nonzero__                                                                            │\n│                                                                                                  │\n│    1524 │                                                                                        │\n│    1525 │   @final                                                                               │\n│    1526 │   def __nonzero__(self):                                                               │\n│ ❱  1527 │   │   raise ValueError(                                                                │\n│    1528 │   │   │   f\"The truth value of a {type(self).__name__} is ambiguous. \"                 │\n│    1529 │   │   │   \"Use a.empty, a.bool(), a.item(), a.any() or a.all().\"                       │\n│    1530 │   │   )                                                                                │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\nValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().\n\n```\n\nJust like with numpy, we can't evaluate the truth value of the series in a meaningful way, but bitwise operations make perfect sense...\n\n```python\n\n❯ if s.any():\n...:     print(\"foo\")\nfoo\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ if s.all():\n...:     print(\"foo\")\n\n```\n\n__I thought this was about `and` and `\u0026`...__\n\nRight, so recall that `and` is a lazy boolean evaluation (ie. it evaluates the 'truth value' an object) whereas `\u0026` does bitwise comparison.\n\nWhat we see then with `pandas` and `numpy` is that if we want to do logical comparisons, we need to do them bitwise, ie. use `\u0026`.\n\nKeep in mind though that the data types make a big deal - we can't use `\u0026` with strings  because the bitwise operation isn't supported, for strings we need to use the boolean evaluation.\n\n\n## The Original Point\n\nMy main use case for this is finding elements in a dataframe/series based on 2 or more columns aligning row values...\n\n\nSay I have a dataframe like this:\n```python\n\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ df\n\n   s s2   s3\n0  1  0  foo\n1  1  a  bar\n2  1  b  baz\n3  2  a  fee\n4  2  0   fi\n```\n\nExample use case is I want to get the values in `s3` where `s` is 1 and `s2` is 'a'. ie. I'm just after `bar` for now...\n\nUp until now I've always just tried `df.s3[(df.s == 1) and (df.s2 == \"a\")]` the first time and every single time I've gotten this error that I just haven't ever fully understood:\n\n```python\nValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().\n```\n\nBut after this deep dive I think I've grasped that `and` doesn't actually do what I want here, and in order to do the bitwise comparision I need to use `\u0026`\n\n```python\nsandbox NO VCS  via 3.8.11(sandbox) ipython\n❯ df.s3[(df.s == 1) \u0026 (df.s2 == \"a\")]\n\n1    bar\nName: s3, dtype: object\n```\n\n## End\n\nHopefully this set of ramblings brings some clarity to `and` and `\u0026` and you can Google one less error in the future in your logical comparison workflows 😄\n",
      "summary": "I often struggle to remember the correct way to do type comparisons when working in pandas.",
      "date_published": "2022-04-06T00:00:00Z",
      "date_modified": "2022-04-06T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/file-length/",
      "url": "https://pype.dev/file-length/",
      "title": "File-Length",
      "content_html": "\u003cp\u003eI have a specific need for counting the number of lines in a file quickly.\nAt work we use S3 for data storage during our Kedro pipeline development, and in the development process we may end up orphaning several datasets.\nIn order to keep our workspace clean I have a short utility that compares the datasets in a Kedro DataCatalog with the files in the relevant S3 location.\u003c/p\u003e\n\u003cp\u003eTo get that list I run an internal tool like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ekedro our-liter \u003cspan class=\"p\"\u003e|\u003c/span\u003e grep s3 \u0026gt;\u0026gt; orphaned_datasets.txt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThis simply parses our internal linter for the lines releated to my s3 linter utility and pipes those lines to a file.\u003c/p\u003e\n\u003cp\u003eTo get a quick idea of how out of wack a pipeline is I could open the text file in vim, git it with the \u003ccode\u003eG\u003c/code\u003e and see what line number I\u0026rsquo;m on but I\u0026rsquo;m way too lazy for that\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"awk\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eAWK\u003c/span\u003e \u003ca href=\"#awk\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eawk 'END {print NR}' orphaned_datasets.txt\u003c/code\u003e gives me the number of lines and I can alias this to whatever feels appropriate in my \u003ccode\u003ezshrc\u003c/code\u003e!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003ebuilt-ins for the win!\u003c/strong\u003e\u003c/p\u003e\n",
      "content_text": "\nI have a specific need for counting the number of lines in a file quickly.\nAt work we use S3 for data storage during our Kedro pipeline development, and in the development process we may end up orphaning several datasets.\nIn order to keep our workspace clean I have a short utility that compares the datasets in a Kedro DataCatalog with the files in the relevant S3 location.\n\nTo get that list I run an internal tool like this:\n\n```bash\nkedro our-liter | grep s3 \u003e\u003e orphaned_datasets.txt\n```\n\nThis simply parses our internal linter for the lines releated to my s3 linter utility and pipes those lines to a file.\n\nTo get a quick idea of how out of wack a pipeline is I could open the text file in vim, git it with the `G` and see what line number I'm on but I'm way too lazy for that...\n\n## AWK\n\n`awk 'END {print NR}' orphaned_datasets.txt` gives me the number of lines and I can alias this to whatever feels appropriate in my `zshrc`!\n\n__built-ins for the win!__\n",
      "summary": "I have a specific need for counting the number of lines in a file quickly. At work we use S3 for data storage during our Kedro pipeline development, and in...",
      "date_published": "2022-04-04T00:00:00Z",
      "date_modified": "2022-04-04T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/ipython-prompt/",
      "url": "https://pype.dev/ipython-prompt/",
      "title": "Ipython-Prompt",
      "content_html": "\u003cp\u003eI have a \u003ca href=\"/starship\"\u003epost on starship\u003c/a\u003e where I have some notes on how I use starship to make my zsh experience great with a sweet terminal prompt.\u003c/p\u003e\n\u003cp\u003eNow\u0026hellip; I spend quite a bit of time in ipython every day and I got kind of sick of the vanilla experience and wanted something that more closely matched my starship prompt.\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s more to customizing ipython I know for sure but here\u0026rsquo;s 2 things I have going for me\u0026hellip;\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\n\u003cp\u003eI use \u003ca href=\"https://pypi.org/project/rich/\"\u003e\u003ccode\u003erich\u003c/code\u003e\u003c/a\u003e authored by @\u003ca href=\"https://twitter.com/willmcgugan\"\u003eWill McGugan\u003c/a\u003e which makes much of my ipython experience great.\nI won\u0026rsquo;t write about that here but you can find my \u003ccode\u003erich\u003c/code\u003e config \u003ca href=\"https://github.com/nicpayne713/dotfiles/blob/main/ipython/.ipython/profile_default/startup/01-rich_init.py\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eI used \u003ccode\u003epygments\u003c/code\u003e to customize the ipython prompt with my \u003ccode\u003eipython_config.py\u003c/code\u003e and a startup script, next to my \u003ccode\u003erich\u003c/code\u003e one, called \u003ccode\u003e99-prompt.py\u003c/code\u003e.\u003c/p\u003e\n\u003c/li\u003e\n\u003c/ol\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe scripts inside \u003ccode\u003e~/.ipython/\u0026lt;profile\u0026gt;/startup\u003c/code\u003e are executed in lexigraphical order, so it\u0026rsquo;s nice to name things in the 10\u0026rsquo;s to give room for adding scripts in between others down the line.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"my-prompt\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy prompt\u003c/span\u003e \u003ca href=\"#my-prompt\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eMy zsh prompt looks a little something like this:\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-starship-prompt.png\" alt=\"starship\" title=\"A starship prompt\" /\u003e\n\u003cp\u003eAnd after my ipython customiztion it currently (subject to much change but this is as of my dotfiles commit #d22088f6be81a58b5f7dfb73b7a4088cbdd9fece on \u003ccode\u003emain\u003c/code\u003e).\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/ipython-prompt.png\" alt=\"ipython\" title=\"A starship inspired ipython prompt\" /\u003e\n\u003cp\u003eNow in ipython I have an indicator of my working directory, git branch, python environment, and a note that I\u0026rsquo;m in \u003ccode\u003eipython\u003c/code\u003e and not \u003ccode\u003ezsh\u003c/code\u003e.\nI also configured my prompt to warn me if I\u0026rsquo;m \u003cem\u003enot\u003c/em\u003e in a git directory!\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/ipython-prompt-no-git.png\" alt=\"ipython-ng\" title=\"A starship inspired ipython prompt without git\" /\u003e\n\u003cp\u003eAll in all the customization isn\u0026rsquo;t too bad with just 2 specific files.\u003c/p\u003e\n\u003ch2 id=\"ipython_config-py\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eipython_config.py\u003c/span\u003e \u003ca href=\"#ipython_config-py\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere\u0026rsquo;s several use cases for \u003ccode\u003eipython_config.py\u003c/code\u003e files in several areas on a pc - sometimes you want a common config across users, so you\u0026rsquo;d drop one in \u003ccode\u003e/etc/ipython\u003c/code\u003e and othertimes you have your own which is probably at \u003ccode\u003e~/.ipython\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eMy ipython config mostly has colors defined on \u003ccode\u003epygment tokens\u003c/code\u003e plus a few autorun commands and \u003ccode\u003epyflyby\u003c/code\u003e (see my friend Waylon\u0026rsquo;s post on pyflyby \u003ca href=\"https://waylonwalker.com/pyflyby/\"\u003ehere\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003eI wanted to match my ipython somewhat to my tmux and vim color schemes, which I model after the vim-airline theme \u003ccode\u003enight owl\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eAfter picking some some colors and saving variables it\u0026rsquo;s a matter of setting colors per token and then referencing those tokens in your version of \u003ccode\u003e99-prompt.py\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eYou can check out my \u003ccode\u003eipython_config.py\u003c/code\u003e \u003ca href=\"https://github.com/nicpayne713/dotfiles/blob/home/ipython/.ipython/profile_default/ipython_config.py\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eFor example, I can set \u003ccode\u003eToken.Name.Function\u003c/code\u003e to black, and in \u003ccode\u003eipython\u003c/code\u003e then a function\u0026rsquo;s definition will appear in black text. I set mine to cyan to match my theme.\u003c/p\u003e\n\u003cp\u003eFor the prompt colors just match the keyword in \u003ccode\u003ec.TerminalInteractiveShell.highlighting_style_overrides\u003c/code\u003e with what is referenced inside \u003ca href=\"https://github.com/nicpayne713/dotfiles/blob/home/ipython/.ipython/profile_default/startup/99-prompt.py\"\u003e99-prompt.py\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eFor example, \u003ccode\u003eToken.Prompt\u003c/code\u003e is set to \u003ccode\u003ebold grey\u003c/code\u003e which gives me the bold chevron symbol you see in the above image that looks like my zsh prompt\u003c/p\u003e\n\u003cp\u003eThen in \u003ccode\u003e99-prompt.py\u003c/code\u003e I have this set for the prompt:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePrompt\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;❯ \u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"99-prompt-py\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003e99-prompt.py\u003c/span\u003e \u003ca href=\"#99-prompt-py\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou don\u0026rsquo;t need to name your script \u003ccode\u003e99-prompt.py\u003c/code\u003e, but I wanted to know that it was for my prompt and I wanted it executed last so it made sense.\u003c/p\u003e\n\u003cp\u003eHere I have \u003ccode\u003eMyPrompt\u003c/code\u003e class with the prompt symbol defined as above and several other things\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMyPrompt\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ePrompts\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\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003ein_prompt_tokens\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ecli\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\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=\"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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eOutPrompt\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ePath\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eabsolute\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estem\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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34; \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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGeneric\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eSubheading\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eget_branch\u003c/span\u003e\u003cspan class=\"p\"\u003e()[\u003c/span\u003e\u003cspan class=\"mi\"\u003e0\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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34; \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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGeneric\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eHeading\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eget_branch\u003c/span\u003e\u003cspan class=\"p\"\u003e()[\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34; \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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eName\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eClass\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;via \u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e+\u003c/span\u003e \u003cspan class=\"n\"\u003eget_venv\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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34; \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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eName\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eEntity\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;ipython\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\u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\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\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\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ePrompt\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=\"bp\"\u003eself\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eshell\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elast_execution_succeeded\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 \u003cspan class=\"n\"\u003eToken\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eGeneric\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eError\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;❯ \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=\"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\u003cp\u003eNotice I have 2 custom functions here, \u003ccode\u003eget_branch\u003c/code\u003e and \u003ccode\u003eget_venv\u003c/code\u003e which grab some git info and python env info and return strings I can dump into my prompt as shown above.\u003c/p\u003e\n\u003cp\u003eTo finish you drop \u003ccode\u003eip = get_ipython()\u003c/code\u003e and \u003ccode\u003eip.prompts = MyPrompt(ip)\u003c/code\u003e at the bottom of your prompt script and you should be in custom prompt city!\u003c/p\u003e\n\u003ch2 id=\"end\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eEnd\u003c/span\u003e \u003ca href=\"#end\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThis is more or less notes for myself on how this works - drop by my \u003ca href=\"https://github.com/nicpayne713/dotfiles/tree/home/ipython\"\u003eipython config\u003c/a\u003e in my dotfiles repo to see my full configs for ipython!\u003c/p\u003e\n",
      "content_text": "\nI have a [post on starship](/starship) where I have some notes on how I use starship to make my zsh experience great with a sweet terminal prompt.\n\nNow... I spend quite a bit of time in ipython every day and I got kind of sick of the vanilla experience and wanted something that more closely matched my starship prompt.\n\nThere's more to customizing ipython I know for sure but here's 2 things I have going for me...\n\n1. I use [`rich`](https://pypi.org/project/rich/) authored by @[Will McGugan](https://twitter.com/willmcgugan) which makes much of my ipython experience great.\nI won't write about that here but you can find my `rich` config [here](https://github.com/nicpayne713/dotfiles/blob/main/ipython/.ipython/profile_default/startup/01-rich_init.py)\n\n2. I used `pygments` to customize the ipython prompt with my `ipython_config.py` and a startup script, next to my `rich` one, called `99-prompt.py`.\n\n\u003e The scripts inside `~/.ipython/\u003cprofile\u003e/startup` are executed in lexigraphical order, so it's nice to name things in the 10's to give room for adding scripts in between others down the line.\n\n## My prompt\n\nMy zsh prompt looks a little something like this:\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-starship-prompt.png\" alt=\"starship\" title=\"A starship prompt\" /\u003e\n\n\nAnd after my ipython customiztion it currently (subject to much change but this is as of my dotfiles commit #d22088f6be81a58b5f7dfb73b7a4088cbdd9fece on `main`).\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/ipython-prompt.png\" alt=\"ipython\" title=\"A starship inspired ipython prompt\" /\u003e\n\nNow in ipython I have an indicator of my working directory, git branch, python environment, and a note that I'm in `ipython` and not `zsh`.\nI also configured my prompt to warn me if I'm _not_ in a git directory!\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/ipython-prompt-no-git.png\" alt=\"ipython-ng\" title=\"A starship inspired ipython prompt without git\" /\u003e\n\nAll in all the customization isn't too bad with just 2 specific files.\n\n## ipython_config.py\n\nThere's several use cases for `ipython_config.py` files in several areas on a pc - sometimes you want a common config across users, so you'd drop one in `/etc/ipython` and othertimes you have your own which is probably at `~/.ipython`\n\nMy ipython config mostly has colors defined on `pygment tokens` plus a few autorun commands and `pyflyby` (see my friend Waylon's post on pyflyby [here](https://waylonwalker.com/pyflyby/))\n\nI wanted to match my ipython somewhat to my tmux and vim color schemes, which I model after the vim-airline theme `night owl`.\n\nAfter picking some some colors and saving variables it's a matter of setting colors per token and then referencing those tokens in your version of `99-prompt.py`.\n\nYou can check out my `ipython_config.py` [here](https://github.com/nicpayne713/dotfiles/blob/home/ipython/.ipython/profile_default/ipython_config.py)\n\nFor example, I can set `Token.Name.Function` to black, and in `ipython` then a function's definition will appear in black text. I set mine to cyan to match my theme.\n\nFor the prompt colors just match the keyword in `c.TerminalInteractiveShell.highlighting_style_overrides` with what is referenced inside [99-prompt.py](https://github.com/nicpayne713/dotfiles/blob/home/ipython/.ipython/profile_default/startup/99-prompt.py)\n\nFor example, `Token.Prompt` is set to `bold grey` which gives me the bold chevron symbol you see in the above image that looks like my zsh prompt \n\nThen in `99-prompt.py` I have this set for the prompt:\n\n```python\nToken.Prompt \"❯ \"\n```\n\n## 99-prompt.py\n\nYou don't need to name your script `99-prompt.py`, but I wanted to know that it was for my prompt and I wanted it executed last so it made sense.\n\nHere I have `MyPrompt` class with the prompt symbol defined as above and several other things... \n\n```python\nclass MyPrompt(Prompts):\n    def in_prompt_tokens(self, cli=None):\n        return [\n            (Token, \"\"),\n            (Token.OutPrompt, Path().absolute().stem),\n            (Token, \" \"),\n            (Token.Generic.Subheading, get_branch()[0]),\n            (Token, \" \"),\n            (Token.Generic.Heading, get_branch()[1]),\n            (Token, \" \"),\n            (Token.Name.Class, \"via \" + get_venv()),\n            (Token, \" \"),\n            (Token.Name.Entity, \"ipython\"),\n            (Token, \"\\n\"),\n            (\n                Token.Prompt\n                if self.shell.last_execution_succeeded\n                else Token.Generic.Error,\n                \"❯ \",\n            ),\n        ]\n\n```\n\nNotice I have 2 custom functions here, `get_branch` and `get_venv` which grab some git info and python env info and return strings I can dump into my prompt as shown above.\n\nTo finish you drop `ip = get_ipython()` and `ip.prompts = MyPrompt(ip)` at the bottom of your prompt script and you should be in custom prompt city!\n\n## End\n\nThis is more or less notes for myself on how this works - drop by my [ipython config](https://github.com/nicpayne713/dotfiles/tree/home/ipython) in my dotfiles repo to see my full configs for ipython!\n",
      "summary": "I have a post on starship where I have some notes on how I use starship to make my zsh experience great with a sweet terminal prompt.",
      "date_published": "2022-04-02T00:00:00Z",
      "date_modified": "2022-04-02T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/vim-spell-check/",
      "url": "https://pype.dev/vim-spell-check/",
      "title": "Vim-Spell-Check",
      "content_html": "\u003cp\u003e\u003cstrong\u003eDid you know you can spell check in Vim?!\u003c/strong\u003e\u003c/p\u003e\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n   \u003chead\u003e\n      \u003ctitle\u003eVim Spell check\u003c/title\u003e\n   \u003c/head\u003e\n   \u003cbody\u003e\n      \u003ch3 id=\"without\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWithout...\u003c/span\u003e \u003ca href=\"#without\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n      \u003cp\u003eHere is a missspelled word.\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e  \u0026lt;h3\u0026gt;With!\u0026lt;/h3\u0026gt;\n  \u0026lt;p\u0026gt;Here is a \u0026lt;u\u0026gt;missspelled\u0026lt;/u\u0026gt; word.\u0026lt;/p\u0026gt;\n\u003c/code\u003e\u003c/pre\u003e\n   \u003c/body\u003e\n\u003c/html\u003e\n\u003ch2 id=\"what-is-this-magic\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhat is this magic???\u003c/span\u003e \u003ca href=\"#what-is-this-magic\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eset: spell spelllang=en_us\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"custom-words\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCustom words?\u003c/span\u003e \u003ca href=\"#custom-words\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eSometimes there\u0026rsquo;s things that are words to you but not the default spell checker\u0026hellip;\u003c/p\u003e\n\u003cp\u003eCommon example: package names!\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eplotly\u003c/code\u003e, \u003ccode\u003estreamlit\u003c/code\u003e, \u003ccode\u003epsutil\u003c/code\u003e, etc etc\u0026hellip;\u003c/p\u003e\n\u003cp\u003eYou can easily add these to your vim config by hitting \u003ccode\u003ezw\u003c/code\u003e ontop of the word!\u003c/p\u003e\n",
      "content_text": "\n__Did you know you can spell check in Vim?!__\n\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n   \u003chead\u003e\n      \u003ctitle\u003eVim Spell check\u003c/title\u003e\n   \u003c/head\u003e\n\n   \u003cbody\u003e\n      \u003ch3\u003eWithout...\u003c/h3\u003e\n      \u003cp\u003eHere is a missspelled word.\u003c/p\u003e\n\n      \u003ch3\u003eWith!\u003c/h3\u003e\n      \u003cp\u003eHere is a \u003cu\u003emissspelled\u003c/u\u003e word.\u003c/p\u003e\n\n   \u003c/body\u003e\n\u003c/html\u003e\n\n## What is this magic???\n\n`set: spell spelllang=en_us`\n\n\n## Custom words?\n\nSometimes there's things that are words to you but not the default spell checker...\n\nCommon example: package names!\n\n`plotly`, `streamlit`, `psutil`, etc etc...\n\n\nYou can easily add these to your vim config by hitting `zw` ontop of the word!\n",
      "summary": "__",
      "date_published": "2022-04-01T00:00:00Z",
      "date_modified": "2022-04-01T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/polybar-01/",
      "url": "https://pype.dev/polybar-01/",
      "title": "Polybar-01",
      "content_html": "\u003cp\u003e\u003ca href=\"https://github.com/polybar/polybar\"\u003epolybar\u003c/a\u003e is an awesome and super customizable status bar for your desktop environment.\u003c/p\u003e\n\u003cp\u003eI use it with i3-gaps on Ubuntu for work and it makes my day just that much better to have a clean and elegant bar with the things in it that I care about.\u003c/p\u003e\n\u003cp\u003eThe GitHub has all the instructions you\u0026rsquo;d need to install and get started with an example.\u003c/p\u003e\n\u003cp\u003eI want to make some notes about how I use polybar and customize it.\u003c/p\u003e\n\u003ch2 id=\"organization\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eOrganization\u003c/span\u003e \u003ca href=\"#organization\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFirst of all, I recently moved my polybar config out of one config file into a modular structure that keeps my config files small and easiser to edit.\u003c/p\u003e\n\u003cp\u003eYou can find my config \u003ca href=\"https://github.com/nicpayne713/dotfiles/tree/main/polybar\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eThe apps or services you put into polybar are called \u003ccode\u003emodules\u003c/code\u003e.\nI have moved all of my modules into their own config files and I source them with one centralized \u003ccode\u003einclude-modules.ini\u003c/code\u003e config.\u003c/p\u003e\n\u003cp\u003eThis separation also makes it easier for me to keep my home and work polybars as in sync as possible without duplicating a ton of config!\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e./polybar\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── colors.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── config.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── fonts.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── home-modules.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── include-modules.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── launch.sh\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e├── modules\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── aws.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── battery.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── bluetooth.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── cisco.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── cpu.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── date.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── eth.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── eth_work.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── i3.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── memory.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── nm-editor.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── powermenu.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── pulseaudio-control.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── pulseaudio.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── rofi.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   ├── vpn.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e│   └── wlan.ini\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e└── work-modules.ini\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\"\u003e1\u003c/span\u003e directory, \u003cspan class=\"m\"\u003e24\u003c/span\u003e files\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eTo break this down there are several configs to see:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003ecolors.ini\u003c/code\u003e is what you\u0026rsquo;d expect - a set of defined colors like \u003ccode\u003eforeground\u003c/code\u003e, \u003ccode\u003eunderline\u003c/code\u003e, etc.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003econfig.ini\u003c/code\u003e is the general polybar config file where bars are defined. Currently in mine there is a \u003ccode\u003ework\u003c/code\u003e and \u003ccode\u003ehome\u003c/code\u003e bar defined with the modules sourced in from the explicit config files.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efonts.ini \u003c/code\u003e is like \u003ccode\u003ecolors.ini\u003c/code\u003e -\u0026gt; you put fonts here. I recommend using a font patched with NerdFont so you get fancy icons! (I use JetBrains Mono)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003einclude-modules.ini\u003c/code\u003e is where I list out all the config files in \u003ccode\u003emodules/\u003c/code\u003e so I can basically source just the \u003ccode\u003einclude-modules.ini\u003c/code\u003e without explicitly sourcing every module\u0026rsquo;s config in every polybar defintion.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003elaunch.sh\u003c/code\u003e is a simple shell script to launch the polybar! You\u0026rsquo;ll see mine takes multiple monitors into consideration which I manage via environment variables setup in my \u003ccode\u003e.zshenv\u003c/code\u003e file that is different for my work and home setups.\u003c/li\u003e\n\u003cli\u003eFinally there are \u003ccode\u003ehome-modules.ini\u003c/code\u003e and \u003ccode\u003ework-modules.ini\u003c/code\u003e which is where, for each of my bars, I define which modules I want!\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"config\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eConfig\u003c/span\u003e \u003ca href=\"#config\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eMy \u003ccode\u003econfig.ini\u003c/code\u003e file has 2 bar definitions in it - here\u0026rsquo;s my home one:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003einclude-file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e$DOTFILES/polybar/include-modules.ini\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\"\u003e[bar/home]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003emonitor\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e${env:MONITOR:}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003ewidth\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e100%\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eheight\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e25\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eradius\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e8.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003efixed-center\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003ebottom\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003efalse\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=\"na\"\u003ebackground\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e${colors.background}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eforeground\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e${colors.foreground}\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=\"na\"\u003einclude-file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e$DOTFILES/polybar/fonts.ini\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003einclude-file\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e$DOTFILES/polybar/home-modules.ini\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eIt should be easy to follow - I bring in the \u003ccode\u003einclude-modules\u003c/code\u003e, set a few colors for the bar like \u003ccode\u003ebackground\u003c/code\u003e and \u003ccode\u003eforeground\u003c/code\u003e which are sourced by the \u003ccode\u003ecolors.ini\u003c/code\u003e, and finally bring in my fonts and home modules via their config files!\u003c/p\u003e\n\u003cp\u003eIt\u0026rsquo;s super easy to then change one or two things in the appropriate places rather than combing through one massive config. This also makes it easy for me to seperate my work and home setups.\u003c/p\u003e\n\u003ch2 id=\"modules\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eModules\u003c/span\u003e \u003ca href=\"#modules\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere are several builtin modules, like \u003ccode\u003ewlan\u003c/code\u003e which gives your wifi status right there in polybar.\u003c/p\u003e\n\u003cp\u003eYou can also make custom ones.\nA big-time custom one for me is an indicator of whether or not I have an active AWS token for working with the \u003ccode\u003eaws\u003c/code\u003e cli.\u003c/p\u003e\n\u003cp\u003eThis is defined in\u003ccode\u003e modules/aws.ini\u003c/code\u003e and it looks like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003e[module/aws]\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003einterval\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e5.0\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003etype\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003ecustom/script\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eexec\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003ehas_aws_token\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eclick-left\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e$HOME/.local/bin/auto_get_aws_token\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eclick-right\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003erm -rf ~/.aws/credentials\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eEvery \u003ccode\u003e5\u003c/code\u003e seconds my \u003ccode\u003ehas_aws_token\u003c/code\u003e script is ran.\nThat script looks like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"cp\"\u003e#!/bin/bash\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003esource\u003c/span\u003e auto_proxy\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eaws sts get-caller-identity \u003cspan class=\"p\"\u003e\u0026amp;\u003c/span\u003e\u0026gt; /dev/null \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;%{T5}%{F#00ff00}  %{F-}%{T-}\u0026#34;\u003c/span\u003e  \u003cspan class=\"o\"\u003e||(\u003c/span\u003e \u003cspan class=\"nb\"\u003eecho\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;%{T5}%{F#ff0000} %{F-}%{T-}\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eSee how the script echos out a colored icon to indicate the status of my token -\u0026gt; that icon is displayed in the polybar so I have real-time (5 second latency) status of whether or not I can do things in my AWS environment.\u003c/p\u003e\n\u003cp\u003eIn the module I also configured actions for \u003ccode\u003eclick-left\u003c/code\u003e and \u003ccode\u003eclick-right\u003c/code\u003e which are as straight forward as could be.\u003c/p\u003e\n\u003ch2 id=\"my-issues-with-i3\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy issues with i3\u003c/span\u003e \u003ca href=\"#my-issues-with-i3\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere\u0026rsquo;s a few things to be considerate of if you use \u003ccode\u003ei3\u003c/code\u003e such as needing a workaround for a centered bar that \u003cstrong\u003eis not\u003c/strong\u003e the full width of the monitor.\nPolybar can look really nice by not taking up the full width of the bar which you can configure in \u003ccode\u003econfig.ini\u003c/code\u003e with these options:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003ewidth\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e90%\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"na\"\u003eoffset-x\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s\"\u003e5%  # set to (100 - width) / 2\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eHowever due to an issue with polybar and i3 you need to also set \u003ccode\u003eoverride-redirect = true\u003c/code\u003e.\nBUT then you\u0026rsquo;ll notice that the bar overlaps your i3 windows\u0026hellip; ARGH! what do we do?\u003c/p\u003e\n\u003cp\u003eQuick work around is to set \u003ccode\u003egaps top\u003c/code\u003e in your i3 config if you use i3-gaps\u0026hellip; if not? well, idk\u0026hellip; use gaps\u0026hellip; lol\u003c/p\u003e\n\u003cp\u003eHowever this introduces another issue - which is then full screen windows will  have polybar sitting on top of them\u0026hellip;\u003c/p\u003e\n\u003cp\u003eThis isn\u0026rsquo;t necessarily a deal breaker, but for me it\u0026rsquo;s worth it to just have the bar go 100% width.\u003c/p\u003e\n\u003ch2 id=\"fin\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFIN\u003c/span\u003e \u003ca href=\"#fin\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere\u0026rsquo;s a tiny intro to polybar and how I organize my config files so things are easy to edit and manage!\nFeel free to grab mine and try it out!\u003c/p\u003e\n",
      "content_text": "\n[polybar](https://github.com/polybar/polybar) is an awesome and super customizable status bar for your desktop environment.\n\nI use it with i3-gaps on Ubuntu for work and it makes my day just that much better to have a clean and elegant bar with the things in it that I care about.\n\nThe GitHub has all the instructions you'd need to install and get started with an example.\n\nI want to make some notes about how I use polybar and customize it.\n\n## Organization\n\nFirst of all, I recently moved my polybar config out of one config file into a modular structure that keeps my config files small and easiser to edit.\n\nYou can find my config [here](https://github.com/nicpayne713/dotfiles/tree/main/polybar)\n\nThe apps or services you put into polybar are called `modules`.\nI have moved all of my modules into their own config files and I source them with one centralized `include-modules.ini` config.\n\nThis separation also makes it easier for me to keep my home and work polybars as in sync as possible without duplicating a ton of config!\n\n```bash\n./polybar\n├── colors.ini\n├── config.ini\n├── fonts.ini\n├── home-modules.ini\n├── include-modules.ini\n├── launch.sh\n├── modules\n│   ├── aws.ini\n│   ├── battery.ini\n│   ├── bluetooth.ini\n│   ├── cisco.ini\n│   ├── cpu.ini\n│   ├── date.ini\n│   ├── eth.ini\n│   ├── eth_work.ini\n│   ├── i3.ini\n│   ├── memory.ini\n│   ├── nm-editor.ini\n│   ├── powermenu.ini\n│   ├── pulseaudio-control.ini\n│   ├── pulseaudio.ini\n│   ├── rofi.ini\n│   ├── vpn.ini\n│   └── wlan.ini\n└── work-modules.ini\n\n1 directory, 24 files\n```\n\nTo break this down there are several configs to see:\n\n1. `colors.ini` is what you'd expect - a set of defined colors like `foreground`, `underline`, etc.\n2. `config.ini` is the general polybar config file where bars are defined. Currently in mine there is a `work` and `home` bar defined with the modules sourced in from the explicit config files.\n3. `fonts.ini ` is like `colors.ini` -\u003e you put fonts here. I recommend using a font patched with NerdFont so you get fancy icons! (I use JetBrains Mono)\n4. `include-modules.ini` is where I list out all the config files in `modules/` so I can basically source just the `include-modules.ini` without explicitly sourcing every module's config in every polybar defintion.\n5. `launch.sh` is a simple shell script to launch the polybar! You'll see mine takes multiple monitors into consideration which I manage via environment variables setup in my `.zshenv` file that is different for my work and home setups.\n6. Finally there are `home-modules.ini` and `work-modules.ini` which is where, for each of my bars, I define which modules I want!\n\n## Config\n\nMy `config.ini` file has 2 bar definitions in it - here's my home one:\n\n```ini\ninclude-file = $DOTFILES/polybar/include-modules.ini\n\n[bar/home]\nmonitor = ${env:MONITOR:}\nwidth = 100%\nheight = 25\nradius = 8.0\nfixed-center = true\nbottom = false\n\nbackground = ${colors.background}\nforeground = ${colors.foreground}\n\ninclude-file = $DOTFILES/polybar/fonts.ini\ninclude-file = $DOTFILES/polybar/home-modules.ini\n```\n\nIt should be easy to follow - I bring in the `include-modules`, set a few colors for the bar like `background` and `foreground` which are sourced by the `colors.ini`, and finally bring in my fonts and home modules via their config files!\n\nIt's super easy to then change one or two things in the appropriate places rather than combing through one massive config. This also makes it easy for me to seperate my work and home setups.\n\n\n## Modules\n\nThere are several builtin modules, like `wlan` which gives your wifi status right there in polybar.\n\nYou can also make custom ones. \nA big-time custom one for me is an indicator of whether or not I have an active AWS token for working with the `aws` cli.\n\nThis is defined in` modules/aws.ini` and it looks like this:\n\n```ini\n[module/aws]\ninterval = 5.0\ntype = custom/script\nexec = has_aws_token\nclick-left = $HOME/.local/bin/auto_get_aws_token\nclick-right = rm -rf ~/.aws/credentials\n```\n\nEvery `5` seconds my `has_aws_token` script is ran.\nThat script looks like this:\n\n```bash\n#!/bin/bash\nsource auto_proxy\naws sts get-caller-identity \u0026\u003e /dev/null \u0026\u0026 echo \"%{T5}%{F#00ff00}  %{F-}%{T-}\"  ||( echo \"%{T5}%{F#ff0000} %{F-}%{T-}\" )\n```\n\nSee how the script echos out a colored icon to indicate the status of my token -\u003e that icon is displayed in the polybar so I have real-time (5 second latency) status of whether or not I can do things in my AWS environment.\n\nIn the module I also configured actions for `click-left` and `click-right` which are as straight forward as could be.\n\n## My issues with i3\n\n\nThere's a few things to be considerate of if you use `i3` such as needing a workaround for a centered bar that __is not__ the full width of the monitor.\nPolybar can look really nice by not taking up the full width of the bar which you can configure in `config.ini` with these options:\n\n```ini\nwidth = 90%\noffset-x = 5%  # set to (100 - width) / 2\n```\n\nHowever due to an issue with polybar and i3 you need to also set `override-redirect = true`. \nBUT then you'll notice that the bar overlaps your i3 windows... ARGH! what do we do?\n\nQuick work around is to set `gaps top` in your i3 config if you use i3-gaps... if not? well, idk... use gaps... lol\n\nHowever this introduces another issue - which is then full screen windows will  have polybar sitting on top of them...\n\nThis isn't necessarily a deal breaker, but for me it's worth it to just have the bar go 100% width.\n\n\n## FIN\n\nThere's a tiny intro to polybar and how I organize my config files so things are easy to edit and manage!\nFeel free to grab mine and try it out!\n",
      "summary": "polybar is an awesome and super customizable status bar for your desktop environment.",
      "date_published": "2022-04-01T00:00:00Z",
      "date_modified": "2022-04-01T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/deques/",
      "url": "https://pype.dev/deques/",
      "title": "Deques",
      "content_html": "\u003cp\u003eI am working on a project to create a small system monitoring dashboard using the python \u003ccode\u003epsutil\u003c/code\u003e library.\u003c/p\u003e\n\u003cp\u003eThe repo is \u003ca href=\"https://github.com/nicpayne713/not-netdata\"\u003ehere\u003c/a\u003e (if you want actual system monitoring please use \u003ca href=\"https://www.netdata.cloud/\"\u003enetdata\u003c/a\u003e).\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m using \u003ccode\u003estreamlit\u003c/code\u003e and \u003ccode\u003eplotly\u003c/code\u003e for the webserver, design, and plotting at the moment.\u003c/p\u003e\n\u003ch2 id=\"my-use-case\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMy Use Case\u003c/span\u003e \u003ca href=\"#my-use-case\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI needed a way to refresh my plotly charts with a fixed window of time so that I\u0026rsquo;m able to just see relevant recent data instead of cramming all data for all time into one plot that\u0026rsquo;s 500 pixels wide\u0026hellip;\u003c/p\u003e\n\u003cp\u003eChecking the length of arrays or lists every time I get a new piece of data feels kind of dumb and I thought \u0026ldquo;python must have a way to do this\u0026rdquo;\u0026hellip;\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u0026ldquo;This\u0026rdquo; meaning, update values in a fixed length array without reallocating memory or recreating a copy of the list\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"deques\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDeques\u003c/span\u003e \u003ca href=\"#deques\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eEnter the \u003ccode\u003edeque\u003c/code\u003e.\nIt means \u0026ldquo;double ended queue\u0026rdquo; and is in general an \u003ccode\u003eIterable\u003c/code\u003e that you can append values to either side or pop values from either side.\u003c/p\u003e\n\u003cp\u003eThe init signature is straightforward enough and I\u0026rsquo;m sure there\u0026rsquo;s more to them than I know yet but here\u0026rsquo;s how I use it\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ecollections\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\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\"\u003emy_deque\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThis gives us \u003ccode\u003emy_deque\u003c/code\u003e, created from an iterable, with several familiar methods like \u003ccode\u003eindex\u003c/code\u003e, \u003ccode\u003eextend\u003c/code\u003e, \u003ccode\u003eappend\u003c/code\u003e, etc.\nHowever there\u0026rsquo;s some new ones too such as \u003ccode\u003eappendleft\u003c/code\u003e and \u003ccode\u003epopleft\u003c/code\u003e.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003emy_deque\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappendleft\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\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=\"n\"\u003emy_dequqe\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=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\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\u003cspan class=\"n\"\u003emy_deque\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epopleft\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=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan class=\"s1\"\u003e\u0026#39;a\u0026#39;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"err\"\u003e!\u003c/span\u003e\u003cspan class=\"o\"\u003e--\u003c/span\u003e\u003cspan class=\"n\"\u003emarkata\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eattribution\u003c/span\u003e\u003cspan class=\"o\"\u003e--\u0026gt;\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=\"n\"\u003emy_deque\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=\"o\"\u003e\u0026gt;\u0026gt;\u0026gt;\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e2\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e])\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThese are handy ways to manipulate the iterable that I needed for the arrays I plot with plotly!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eSee my follow-up to this on using Deques with plotly and streamlit to create a quick \u0026ldquo;dashboard\u0026rdquo; with live streaming data!\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"/plotly-and-streamlit\"\u003efollow-up\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI am working on a project to create a small system monitoring dashboard using the python `psutil` library.\n\nThe repo is [here](https://github.com/nicpayne713/not-netdata) (if you want actual system monitoring please use [netdata](https://www.netdata.cloud/)).\n\nI'm using `streamlit` and `plotly` for the webserver, design, and plotting at the moment.\n\n## My Use Case\n\nI needed a way to refresh my plotly charts with a fixed window of time so that I'm able to just see relevant recent data instead of cramming all data for all time into one plot that's 500 pixels wide...\n\nChecking the length of arrays or lists every time I get a new piece of data feels kind of dumb and I thought \"python must have a way to do this\"...\n\n\u003e \"This\" meaning, update values in a fixed length array without reallocating memory or recreating a copy of the list\n\n## Deques\n\nEnter the `deque`. \nIt means \"double ended queue\" and is in general an `Iterable` that you can append values to either side or pop values from either side.\n\nThe init signature is straightforward enough and I'm sure there's more to them than I know yet but here's how I use it...\n\n```python\nfrom collections import deque\n\nmy_deque = deque([1,2,3])\n```\n\nThis gives us `my_deque`, created from an iterable, with several familiar methods like `index`, `extend`, `append`, etc.\nHowever there's some new ones too such as `appendleft` and `popleft`.\n\n```python\nmy_deque.appendleft('a')\nprint(my_dequqe)\n\u003e\u003e\u003e deque(['a', 1, 2, 3])\n\nmy_deque.popleft()\n\u003e\u003e\u003e 'a'\nprint(my_deque)\n\u003e\u003e\u003e deque([1, 2, 3])\n```\n\nThese are handy ways to manipulate the iterable that I needed for the arrays I plot with plotly!\n\n\n__See my follow-up to this on using Deques with plotly and streamlit to create a quick \"dashboard\" with live streaming data!__\n\n[follow-up](/plotly-and-streamlit)\n",
      "summary": "I am working on a project to create a small system monitoring dashboard using the python library.",
      "date_published": "2022-03-31T00:00:00Z",
      "date_modified": "2022-03-31T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/plotly-and-streamlit/",
      "url": "https://pype.dev/plotly-and-streamlit/",
      "title": "Plotly-And-Streamlit",
      "content_html": "\u003ch2 id=\"streamlit\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStreamlit\u003c/span\u003e \u003ca href=\"#streamlit\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI use \u003ccode\u003estreamlit\u003c/code\u003e for any EDA I ever have to do at work.\nIt\u0026rsquo;s super easy to spin up a small dashboard to filter and view dataframes in, live, without the fallbacks of Jupyter notebooks (kernels dying, memory bloat, a billion \u0026ldquo;Untitled N.ipynb\u0026rdquo; files, etc.)\u003c/p\u003e\n\u003cp\u003eAt the highest level, streamlit lets you write a python script and call \u003ccode\u003estreamlit run my_script.py\u003c/code\u003e which will open up a web server with your streamlit stuff.\nThe dashboard refreshes whenever you change the script so you can add capabilities in real time, super fast!\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;ll show an example of using \u003ccode\u003estreamlit\u003c/code\u003e and \u003ccode\u003eplotly\u003c/code\u003e to make a live dashboard to monitor system memory usage with \u003ccode\u003epsutil\u003c/code\u003e.\nThis is apart of my posts on \u003ca href=\"/psutil\"\u003epsutil\u003c/a\u003e and \u003ca href=\"/deques\"\u003edeques\u003c/a\u003e\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eexample at the bottom!\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"plotly\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePlotly\u003c/span\u003e \u003ca href=\"#plotly\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;m not going to make a big time intro to plotly here - there\u0026rsquo;s a billion resources on the interwebs and the docs are really good.\u003c/p\u003e\n\u003cp\u003eSuffice it to say it\u0026rsquo;s my goto plotting library for basically any and all needs.\nI\u0026rsquo;m currently exploring it for live data streaming as I\u0026rsquo;m not sure it\u0026rsquo;s the best solution but it\u0026rsquo;s the one I\u0026rsquo;m familiar with.\u003c/p\u003e\n\u003cp\u003eFor my \u003ca href=\"https://github.com/nicpayne713/not-netdata\"\u003e not-netdata \u003c/a\u003e project of visualizing live system resource data I  first need a way of appending data and popping data in and out of an array at every data refresh cycle to keep my plots looking nice with a fixed time window.\u003c/p\u003e\n\u003cp\u003eSee \u003ca href=\"/deques\"\u003edeques\u003c/a\u003e for a short intro to the datatype I\u0026rsquo;m using.\u003c/p\u003e\n\u003cp\u003eFirst step is to initialize some objects to store data in.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eDict\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eMutableSequence\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003efloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]]]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edefaultdict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edeque\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\u003cspan class=\"n\"\u003earr_size\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003earr_size\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003earr_size\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003edata\u003c/code\u003e is a dictionary that I\u0026rsquo;ll store deques in. The dictionary keys will be the type of data, in this case \u003ccode\u003etime\u003c/code\u003e and \u003ccode\u003eused_memory\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eI fix an array size, \u003ccode\u003earr_size\u003c/code\u003e to just 10 for now\u003c/p\u003e\n\u003cp\u003eThen I initialize the values for \u003ccode\u003etime\u003c/code\u003e and \u003ccode\u003eused_memory\u003c/code\u003e as \u003ccode\u003edeque\u003c/code\u003es of length \u003ccode\u003earr_size\u003c/code\u003e.\nSimple enough!\u003c/p\u003e\n\u003cp\u003eNext is to fill those deques with some relevant data.\nI\u0026rsquo;m not actually sure if this is the best way to do this but here\u0026rsquo;s what I have done so far:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003erefresh_data\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\"\u003eglobal\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ememory\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evirtual_memory\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    \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrftime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan class=\"si\"\u003e%d\u003c/span\u003e\u003cspan class=\"s2\"\u003e %H:%M:%S\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elocaltime\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ememory\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eused\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e\u003cspan class=\"o\"\u003e**\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\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    \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epopleft\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epopleft\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eIf you ignore my usage of \u003ccode\u003eglobal\u003c/code\u003e you\u0026rsquo;ll see that I can just \u003ccode\u003eappend\u003c/code\u003e to each deque like it was a list.\u003c/p\u003e\n\u003cp\u003eBut then to keep the relevant data in the deque, and to keep the length fixed, I simply \u003ccode\u003epopleft\u003c/code\u003e to remove the oldest datapoint!\u003c/p\u003e\n\u003ch2 id=\"a-trivial-dashboard\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eA trivial dashboard\u003c/span\u003e \u003ca href=\"#a-trivial-dashboard\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNow I\u0026rsquo;ll prove just how easy it is to get a live data dashboard up and running with just a few lines of code thanks to streamlit!\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=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"vm\"\u003e__name__\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;__main__\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\"\u003est\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;memory chart\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\"\u003estats\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eempty\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\"\u003ewhile\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\"\u003erefresh_data\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\"\u003estats\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eplotly_chart\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\"\u003epx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eline\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\"\u003edata\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\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\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\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\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\"\u003etitle\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Memory usage stored in a deque!\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=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003e\u003ccode\u003est\u003c/code\u003e is the streamlit alias (imports shows at the bottom full example).\n\u003ccode\u003est.header\u003c/code\u003e puts a nice header on the page.\n\u003ccode\u003est.empty\u003c/code\u003e initializes an empty \u003ccode\u003estreamlit container\u003c/code\u003e in which we\u0026rsquo;ll put a \u003ccode\u003eplotly.express\u003c/code\u003e figure.\u003c/p\u003e\n\u003cp\u003eAt each iteration we\u0026rsquo;ll \u003ccode\u003erefresh_data()\u003c/code\u003e which \u003ccode\u003eappends\u003c/code\u003e and \u003ccode\u003epops\u003c/code\u003e data in the deques in the \u003ccode\u003edata\u003c/code\u003e dictionary.\nThen we update the \u003ccode\u003estats\u003c/code\u003e container with a plotly graph and the refresh happens seamlessly.\u003c/p\u003e\n\u003cp\u003eAll in all the script looks like this:\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=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003ecollections\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003edefaultdict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\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\"\u003etime\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\"\u003etyping\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eDict\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eMutableSequence\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eOptional\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\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eplotly\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eexpress\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"n\"\u003epx\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\"\u003epsutil\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\"\u003estreamlit\u003c/span\u003e \u003cspan class=\"k\"\u003eas\u003c/span\u003e \u003cspan class=\"nn\"\u003est\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eDict\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003estr\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eMutableSequence\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003eOptional\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nb\"\u003efloat\u003c/span\u003e\u003cspan class=\"p\"\u003e]]]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edefaultdict\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edeque\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\u003cspan class=\"n\"\u003earr_size\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e10\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003earr_size\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003edeque\u003c/span\u003e\u003cspan class=\"p\"\u003e([\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"n\"\u003earr_size\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\"\u003erefresh_data\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\"\u003eglobal\u003c/span\u003e \u003cspan class=\"n\"\u003edata\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ememory\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evirtual_memory\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    \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estrftime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;%Y-%m-\u003c/span\u003e\u003cspan class=\"si\"\u003e%d\u003c/span\u003e\u003cspan class=\"s2\"\u003e %H:%M:%S\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003elocaltime\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eappend\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ememory\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eused\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e\u003cspan class=\"o\"\u003e**\u003c/span\u003e\u003cspan class=\"mi\"\u003e3\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    \u003cspan class=\"n\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epopleft\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\"\u003edata\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003epopleft\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\"\u003ememory_chart\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\"\u003efig\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epx\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eline\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\"\u003edata\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\"\u003ex\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;time\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\"\u003ey\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;used_memory\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\"\u003etitle\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Memory usage stored in a deque!\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=\"k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"n\"\u003efig\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\"\u003eif\u003c/span\u003e \u003cspan class=\"vm\"\u003e__name__\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;__main__\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\"\u003est\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eheader\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;memory chart\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\"\u003estats\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003est\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eempty\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\"\u003ewhile\u003c/span\u003e \u003cspan class=\"kc\"\u003eTrue\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\"\u003erefresh_data\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\"\u003estats\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eplotly_chart\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ememory_chart\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\"\u003etime\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esleep\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mf\"\u003e0.5\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eYou can save this as \u003ccode\u003emy_dash.py\u003c/code\u003e and run with \u003ccode\u003estreamlit run my_dash.py\u003c/code\u003e and should see something like the following!\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/plotly-streamlit.gif\" alt=\"plotly\" title=\"Plotly in Streamlit\" /\u003e\n",
      "content_text": "\n## Streamlit\n\n\nI use `streamlit` for any EDA I ever have to do at work.\nIt's super easy to spin up a small dashboard to filter and view dataframes in, live, without the fallbacks of Jupyter notebooks (kernels dying, memory bloat, a billion \"Untitled N.ipynb\" files, etc.)\n\nAt the highest level, streamlit lets you write a python script and call `streamlit run my_script.py` which will open up a web server with your streamlit stuff. \nThe dashboard refreshes whenever you change the script so you can add capabilities in real time, super fast!\n\n\nI'll show an example of using `streamlit` and `plotly` to make a live dashboard to monitor system memory usage with `psutil`.\nThis is apart of my posts on [psutil](/psutil) and [deques](/deques)...\n\n__example at the bottom!__\n\n\n\n## Plotly\n\nI'm not going to make a big time intro to plotly here - there's a billion resources on the interwebs and the docs are really good.\n\nSuffice it to say it's my goto plotting library for basically any and all needs.\nI'm currently exploring it for live data streaming as I'm not sure it's the best solution but it's the one I'm familiar with.\n\nFor my [ not-netdata ](https://github.com/nicpayne713/not-netdata) project of visualizing live system resource data I  first need a way of appending data and popping data in and out of an array at every data refresh cycle to keep my plots looking nice with a fixed time window.\n\nSee [deques](/deques) for a short intro to the datatype I'm using.\n\nFirst step is to initialize some objects to store data in.\n\n```python\ndata: Dict[str, MutableSequence[Optional[float]]] = defaultdict(deque)\n\narr_size = 10\n\ndata[\"time\"] = deque([None] * arr_size)\ndata[\"used_memory\"] = deque([None] * arr_size)\n```\n\n`data` is a dictionary that I'll store deques in. The dictionary keys will be the type of data, in this case `time` and `used_memory`.\n\nI fix an array size, `arr_size` to just 10 for now\n\nThen I initialize the values for `time` and `used_memory` as `deque`s of length `arr_size`.\nSimple enough!\n\nNext is to fill those deques with some relevant data.\nI'm not actually sure if this is the best way to do this but here's what I have done so far:\n\n```python\ndef refresh_data():\n    global data\n    memory = psutil.virtual_memory()\n\n    data[\"time\"].append(time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime()))\n    data[\"used_memory\"].append(memory.used // (1024**3))\n\n    data[\"time\"].popleft()\n    data[\"used_memory\"].popleft()\n```\n\nIf you ignore my usage of `global` you'll see that I can just `append` to each deque like it was a list.\n\nBut then to keep the relevant data in the deque, and to keep the length fixed, I simply `popleft` to remove the oldest datapoint!\n\n\n## A trivial dashboard\n\nNow I'll prove just how easy it is to get a live data dashboard up and running with just a few lines of code thanks to streamlit!\n\n```python\n\nif __name__ == \"__main__\":\n    st.header(\"memory chart\")\n    stats = st.empty()\n    while True:\n        refresh_data()\n        stats.plotly_chart(\n            px.line(\n                data,\n                x=\"time\",\n                y=\"used_memory\",\n                title=f\"Memory usage stored in a deque!\",\n               )\n            )\n        time.sleep(0.5)\n```\n\n`st` is the streamlit alias (imports shows at the bottom full example).\n`st.header` puts a nice header on the page.\n`st.empty` initializes an empty `streamlit container` in which we'll put a `plotly.express` figure.\n\nAt each iteration we'll `refresh_data()` which `appends` and `pops` data in the deques in the `data` dictionary.\nThen we update the `stats` container with a plotly graph and the refresh happens seamlessly.\n\nAll in all the script looks like this:\n\n```python\n\nfrom collections import defaultdict, deque\nimport time\nfrom typing import Dict, MutableSequence, Optional\n\nfrom plotly import express as px\nimport psutil\nimport streamlit as st\n\ndata: Dict[str, MutableSequence[Optional[float]]] = defaultdict(deque)\n\narr_size = 10\n\ndata[\"time\"] = deque([None] * arr_size)\ndata[\"used_memory\"] = deque([None] * arr_size)\n\n\ndef refresh_data():\n    global data\n    memory = psutil.virtual_memory()\n\n    data[\"time\"].append(time.strftime(\"%Y-%m-%d %H:%M:%S\", time.localtime()))\n    data[\"used_memory\"].append(memory.used // (1024**3))\n\n    data[\"time\"].popleft()\n    data[\"used_memory\"].popleft()\n\n\ndef memory_chart():\n    fig = px.line(\n        data,\n        x=\"time\",\n        y=\"used_memory\",\n        title=f\"Memory usage stored in a deque!\",\n    )\n    return fig\n\n\nif __name__ == \"__main__\":\n    st.header(\"memory chart\")\n    stats = st.empty()\n    while True:\n        refresh_data()\n        stats.plotly_chart(memory_chart())\n        time.sleep(0.5)\n```\n\nYou can save this as `my_dash.py` and run with `streamlit run my_dash.py` and should see something like the following!\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/plotly-streamlit.gif\" alt=\"plotly\" title=\"Plotly in Streamlit\" /\u003e\n\n",
      "summary": "I use for any EDA I ever have to do at work. It's super easy to spin up a small dashboard to filter and view dataframes in, live, without the fallbacks of...",
      "date_published": "2022-03-31T00:00:00Z",
      "date_modified": "2022-03-31T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/starship/",
      "url": "https://pype.dev/starship/",
      "title": "Starship",
      "content_html": "\u003cp\u003eIf you spend time in the terminal then you\u0026rsquo;ll want it to look somewhat pleasing to the eye.\nI used to ssh into servers with no customization, use \u003ccode\u003evi\u003c/code\u003e  to edit a file or two, then get back to my regularly scheduled programming in VS C**e\u0026hellip;\u003c/p\u003e\n\u003cp\u003eOne of the first steps for me loving my terminal was a beautiful prompt\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"prompt\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePrompt\u003c/span\u003e \u003ca href=\"#prompt\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe default sh/bash/zsh prompts are\u0026hellip; to put it lightly\u0026hellip; garbage\u0026hellip; I can\u0026rsquo;t speak for other shells like fish simply because I do not use them but let me justify my trash talk.\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s the default \u003ccode\u003esh\u003c/code\u003e prompt\u0026hellip;\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/sh-prompt.png\" alt=\"sh\" title=\"A default sh prompt\" /\u003e\n\u003cp\u003eThen switching to \u003ccode\u003ezsh\u003c/code\u003e you get something marginally better (plus tab completion!)\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-prompt.png\" alt=\"zsh\" title=\"A default zsh prompt\" /\u003e\n\u003cp\u003eBut this still is super gross\u0026hellip; there\u0026rsquo;s nothing to indicate file types and no status information readily available (ie. \u003ccode\u003egit status\u003c/code\u003e etc.)\u003c/p\u003e\n\u003ch2 id=\"oh-my-zsh\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eOh-My-Zsh!\u003c/span\u003e \u003ca href=\"#oh-my-zsh\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eNow there are several ways to make your prmompt nicer depending on your shell (terminal emulator plays a role too).\nNow I use \u003ccode\u003ezsh\u003c/code\u003e and there\u0026rsquo;s a great tool out there \u003ca href=\"https://ohmyz.sh/\"\u003eoh-my-zsh\u003c/a\u003e that brings a crazy amount of customization to the terminal experience.\u003c/p\u003e\n\u003cp\u003eI do not use \u003ccode\u003eoh-my-zsh\u003c/code\u003e for theming though and that\u0026rsquo;s simply because of my other choices - I use \u003ccode\u003ekitty\u003c/code\u003e themes since I understood the implementation better.\nKitty themes though - do not give me a nice prompt.\u003c/p\u003e\n\u003cp\u003eThe default prompt you get with \u003ccode\u003eoh-my-zsh\u003c/code\u003e themes isn\u0026rsquo;t bad though (and you can pick from several default themes)\u0026hellip;\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-oh-my-zsh-prompt.png\" alt=\"omz\" title=\"A default oh-my-zsh prompt\" /\u003e\n\u003cp\u003eNotice that you get some nice coloring and some default \u003ccode\u003egit\u003c/code\u003e status stuff, mainly the branch you are on.\nThere\u0026rsquo;s plugins to show you more and that\u0026rsquo;s all well and good, but again it\u0026rsquo;s not my choice\u0026hellip;\u003c/p\u003e\n\u003cp\u003eIf I don\u0026rsquo;t use this then what\u0026rsquo;s my goto?\u003c/p\u003e\n\u003ch2 id=\"starship\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eStarship\u003c/span\u003e \u003ca href=\"#starship\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://starship.rs/\"\u003estarship\u003c/a\u003e is a cross-shell prompt with nice default and super easy customizaton!\u003c/p\u003e\n\u003cp\u003eTo get started click that link and follow the \u0026ldquo;Getting Started\u0026rdquo; button - it\u0026rsquo;s incredibly fast to get up and running with sane defaults.\u003c/p\u003e\n\u003cp\u003eThe default starship config is plenty nice but I got a little tired of emojis in my prompt and wanted to switch to icons instead\u0026hellip;\u003c/p\u003e\n\u003cp\u003eTo get started with your own customizaton you add a \u003ccode\u003estarship.toml\u003c/code\u003e file to \u003ccode\u003e~/.config\u003c/code\u003e\nMy starship config is found \u003ca href=\"https://github.com/nicpayne713/dotfiles/blob/main/starship/.config/starship.toml\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eNote you need a font installed patched with nerdfonts - I use JetBrains Mono\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eNow I have a beautiful prompt with relevant information that\u0026rsquo;s a dream to look at!\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-starship-prompt.png\" alt=\"starship\" title=\"A starship prompt\" /\u003e\n\u003cp\u003eI have configured my starship to show me relevant \u003ccode\u003egit status\u003c/code\u003e options (stashes, untracked files, etc etc.)\nI also have starship show me if I\u0026rsquo;m in a git repo, what branch I\u0026rsquo;m on, if I\u0026rsquo;m in a python project and if so what virtual environment is active.\nI do some work in AWS at work and so I have starship show me if my \u003ccode\u003eaws cli\u003c/code\u003e is configured to the right region for whichever project I\u0026rsquo;m in!\u003c/p\u003e\n\u003cp\u003eThere\u0026rsquo;s a billion more options and after a few minutes of play it becomes really easy and intuitive to customize colors, icons, etc.\u003c/p\u003e\n",
      "content_text": "\nIf you spend time in the terminal then you'll want it to look somewhat pleasing to the eye.\nI used to ssh into servers with no customization, use `vi`  to edit a file or two, then get back to my regularly scheduled programming in VS C**e...\n\nOne of the first steps for me loving my terminal was a beautiful prompt... \n\n## Prompt\n\nThe default sh/bash/zsh prompts are... to put it lightly... garbage... I can't speak for other shells like fish simply because I do not use them but let me justify my trash talk.\n\nHere's the default `sh` prompt...\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/sh-prompt.png\" alt=\"sh\" title=\"A default sh prompt\" /\u003e\n\n\nThen switching to `zsh` you get something marginally better (plus tab completion!)\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-prompt.png\" alt=\"zsh\" title=\"A default zsh prompt\" /\u003e\n\nBut this still is super gross... there's nothing to indicate file types and no status information readily available (ie. `git status` etc.)\n\n## Oh-My-Zsh!\n\nNow there are several ways to make your prmompt nicer depending on your shell (terminal emulator plays a role too).\nNow I use `zsh` and there's a great tool out there [oh-my-zsh](https://ohmyz.sh/) that brings a crazy amount of customization to the terminal experience.\n\nI do not use `oh-my-zsh` for theming though and that's simply because of my other choices - I use `kitty` themes since I understood the implementation better.\nKitty themes though - do not give me a nice prompt.\n\nThe default prompt you get with `oh-my-zsh` themes isn't bad though (and you can pick from several default themes)...\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-oh-my-zsh-prompt.png\" alt=\"omz\" title=\"A default oh-my-zsh prompt\" /\u003e\n\nNotice that you get some nice coloring and some default `git` status stuff, mainly the branch you are on.\nThere's plugins to show you more and that's all well and good, but again it's not my choice...\n\nIf I don't use this then what's my goto?\n\n## Starship\n\n[starship](https://starship.rs/) is a cross-shell prompt with nice default and super easy customizaton!\n\nTo get started click that link and follow the \"Getting Started\" button - it's incredibly fast to get up and running with sane defaults.\n\nThe default starship config is plenty nice but I got a little tired of emojis in my prompt and wanted to switch to icons instead...\n\nTo get started with your own customizaton you add a `starship.toml` file to `~/.config` \nMy starship config is found [here](https://github.com/nicpayne713/dotfiles/blob/main/starship/.config/starship.toml).\n\n\u003eNote you need a font installed patched with nerdfonts - I use JetBrains Mono\n\nNow I have a beautiful prompt with relevant information that's a dream to look at!\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/zsh-starship-prompt.png\" alt=\"starship\" title=\"A starship prompt\" /\u003e\n\nI have configured my starship to show me relevant `git status` options (stashes, untracked files, etc etc.)\nI also have starship show me if I'm in a git repo, what branch I'm on, if I'm in a python project and if so what virtual environment is active.\nI do some work in AWS at work and so I have starship show me if my `aws cli` is configured to the right region for whichever project I'm in!\n\nThere's a billion more options and after a few minutes of play it becomes really easy and intuitive to customize colors, icons, etc.\n",
      "summary": "If you spend time in the terminal then you'll want it to look somewhat pleasing to the eye. I used to ssh into servers with no customization, use to edit a...",
      "date_published": "2022-03-25T00:00:00Z",
      "date_modified": "2022-03-25T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/self-hosted-media/",
      "url": "https://pype.dev/self-hosted-media/",
      "title": "self-hosted-media",
      "content_html": "\u003cp\u003eSelf-hosting 1 or several media servers is another common homelab use-case.\nGetting content for your media servers is up to you, but I\u0026rsquo;ll show a few ways here to get content somewhat easily!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eYouTube Disclaimer at Bottom\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"you-get\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eyou-get\u003c/span\u003e \u003ca href=\"#you-get\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003eyou-get\u003c/code\u003e is a nice cli for grabbing media content off the web.\u003c/p\u003e\n\u003ch3 id=\"installation\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eInstallation\u003c/span\u003e \u003ca href=\"#installation\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003epip install you-get\u003c/code\u003e or use ad-hoc with \u003ccode\u003epipx run you-get \u0026lt;url\u0026gt;\u003c/code\u003e\u003c/p\u003e\n\u003ch3 id=\"usage\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUsage\u003c/span\u003e \u003ca href=\"#usage\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eFor example if I wanted to catch up on ancient Chinese military tactics I may go for \u003ccode\u003eThe Art of War\u003c/code\u003e off the Internet Archive\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esandbox  🌱 main 🗑️  ×3🛤️  ×6via 🐍 v3.8.11 \u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  took 15s\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ you-get https://archive.org/details/art_of_war_librivox -i\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSite:       Archive.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTitle:      The Art of War : Sun Tzu : Free Download, Borrow, and Streaming : Internet Archive\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eType:       MP3 \u003cspan class=\"o\"\u003e(\u003c/span\u003eaudio/mpeg\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSize:       3.87 MiB \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e4055167\u003c/span\u003e Bytes\u003cspan class=\"o\"\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\u003cp\u003ethe \u003ccode\u003e-i\u003c/code\u003e is showing me the info of what would be downloaded without the flag (it\u0026rsquo;s like a dry run)\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esandbox  🌱 main 🗑️  ×3🛤️  ×6via 🐍 v3.8.11 \u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ you-get https://archive.org/details/art_of_war_librivox\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSite:       Archive.org\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTitle:      The Art of War : Sun Tzu : Free Download, Borrow, and Streaming : Internet Archive\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eType:       MP3 \u003cspan class=\"o\"\u003e(\u003c/span\u003eaudio/mpeg\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSize:       3.87 MiB \u003cspan class=\"o\"\u003e(\u003c/span\u003e\u003cspan class=\"m\"\u003e4055167\u003c/span\u003e Bytes\u003cspan class=\"o\"\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\"\u003eDownloading The Art of War : Sun Tzu : Free Download, Borrow, and Streaming : Internet Archi.mp3 ...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e 100% \u003cspan class=\"o\"\u003e(\u003c/span\u003e  3.9/  3.9MB\u003cspan class=\"o\"\u003e)\u003c/span\u003e ├████████████████████████████████████████████████████████████┤\u003cspan class=\"o\"\u003e[\u003c/span\u003e1/1\u003cspan class=\"o\"\u003e]\u003c/span\u003e  \u003cspan class=\"m\"\u003e917\u003c/span\u003e kB/s\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\u003cp\u003eNow I can toss that mp3 onto my \u003ccode\u003ebooksonic\u003c/code\u003e server and study for world domination while I do the dishes!\u003c/p\u003e\n\u003ch2 id=\"pytube\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003epytube\u003c/span\u003e \u003ca href=\"#pytube\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003epytube\u003c/code\u003e is a python implementation of a \u003ca href=\"##YouTube\"\u003eyoutube downloader \u003c/a\u003e that works at the command line or in python!\u003c/p\u003e\n\u003ch3 id=\"installation-1\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eInstallation\u003c/span\u003e \u003ca href=\"#installation-1\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e\u003ca href=\"https://pytube.io/en/latest/\"\u003edocs\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003epip install pytube\u003c/code\u003e\u003c/p\u003e\n\u003ch3 id=\"usage-1\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUsage\u003c/span\u003e \u003ca href=\"#usage-1\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003e\u003ccode\u003epytube\u003c/code\u003e has a lot of functionality, but a quick one would be the \u003ccode\u003e--list\u003c/code\u003e so you can see what qualities are available\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esandbox   main ️  ×3️  ×7via  v3.8.11 \u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  took 2m49s\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ pytube https://www.youtube.com/watch\u003cspan class=\"se\"\u003e\\?\u003c/span\u003ev\u003cspan class=\"se\"\u003e\\=\u003c/span\u003eLDU_Txk06tM  --list\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eLoading video...\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;17\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/3gpp\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;8fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4v.20.3\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;True\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;18\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.42001E\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;True\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;22\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.64001F\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;True\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;313\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2160p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;401\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2160p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.12M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;271\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1440p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;400\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1440p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.12M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;137\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1080p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.640028\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;248\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1080p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;399\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1080p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.08M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;136\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d401f\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;247\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;398\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.05M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;135\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;480p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d401f\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;244\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;480p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;397\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;480p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.04M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;134\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d401e\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;243\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;396\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.01M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;133\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;240p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d4015\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;242\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;240p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;395\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;240p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.00M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;160\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d400c\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;278\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;394\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.00M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;139\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;48kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.5\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;140\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;128kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;249\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;50kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;opus\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;250\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;70kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;opus\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u0026lt;Stream: \u003cspan class=\"nv\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;251\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;160kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;opus\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nv\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u0026gt;\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\u003cp\u003e\u003ccode\u003epytube \u0026lt;url\u0026gt; --itag \u0026lt;\u0026gt;\u003c/code\u003e will download the specific \u003ccode\u003eitag\u003c/code\u003e from the list.\u003c/p\u003e\n\u003cp\u003eNotice that some \u003ccode\u003eitags\u003c/code\u003e are videos and others audio - so you can download just the music of a YT video.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003epytube\u003c/code\u003e also works in python\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e↪\u003c/span\u003e \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003ev3\u003c/span\u003e\u003cspan class=\"mf\"\u003e.8.11\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003epytube\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eYouTube\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\"\u003esandbox\u003c/span\u003e \u003cspan class=\"err\"\u003e↪\u003c/span\u003e \u003cspan class=\"n\"\u003emain\u003c/span\u003e \u003cspan class=\"n\"\u003ev3\u003c/span\u003e\u003cspan class=\"mf\"\u003e.8.11\u003c/span\u003e \u003cspan class=\"n\"\u003eipython\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ex\u003c/span\u003e \u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ex\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003eYouTube\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;https://www.youtube.com/watch?v=LDU_Txk06tM\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003estreams\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\u003cspan class=\"p\"\u003e[\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;17\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/3gpp\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;8fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4v.20.3\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;True\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;18\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.42001E\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;True\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;22\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.64001F\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;True\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;313\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2160p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;401\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;2160p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.12M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;271\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1440p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;400\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1440p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.12M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;137\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1080p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.640028\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;248\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1080p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;399\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1080p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.08M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;136\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d401f\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;247\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;398\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;720p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.05M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;135\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;480p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d401f\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;244\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;480p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;397\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;480p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.04M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;134\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d401e\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;243\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;396\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;360p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.01M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;133\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;240p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d4015\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;242\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;240p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;395\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;240p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.00M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;160\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;avc1.4d400c\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;278\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;vp9\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;394\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eres\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;144p\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003efps\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;30fps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003evcodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;av01.0.00M.08\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;video\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;139\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;48kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.5\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;140\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/mp4\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;128kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;mp4a.40.2\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;249\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;50kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;opus\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;250\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;70kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;opus\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eStream\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eitag\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;251\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003emime_type\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio/webm\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eabr\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;160kbps\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eacodec\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;opus\u0026#34;\u003c/span\u003e \u003cspan class=\"n\"\u003eprogressive\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;False\u0026#34;\u003c/span\u003e \u003cspan class=\"nb\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;audio\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\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\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\u003ch2 id=\"youtube-frontends\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eYouTube Frontends\u003c/span\u003e \u003ca href=\"#youtube-frontends\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThere\u0026rsquo;s 2 really good options for self-hosting a YT front-end\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/bbilly1/tubearchivist\"\u003eTube Archivist\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/Tzahi12345/YoutubeDL-Material\"\u003eYouTubeDL-Material\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eThey have their pros and cons.\nYou can also build yourself with the above utilities and use Plex or Jellyfin to serve up videos\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eYour self-hosting journey is up to you!\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"youtube\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eYouTube\u003c/span\u003e \u003ca href=\"#youtube\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eDownloading YouTube videos is a bit of a sore topic\u0026hellip; Mainly you don\u0026rsquo;t to hurt creators who rely on YT ad revenue for their livlihood.\u003c/p\u003e\n\u003cp\u003eThen again, maybe you\u0026rsquo;re a vigilante who knows that YT also monetizes videos for their \u003cem\u003eown\u003c/em\u003e gain and that the creators don\u0026rsquo;t see that money either!\u003c/p\u003e\n\u003cp\u003eThe solution is pretty easy and is 2-fold\u0026hellip;\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eDownload YT videos\u003c/li\u003e\n\u003cli\u003ePersonally support the content creators you follow via paypall, patreon, or whatever else they might have set-up\u0026hellip;. even a buck or two a month is more than they\u0026rsquo;d get from your ad revenue explicitly plus it all goes to them!\u003c/li\u003e\n\u003c/ol\u003e\n",
      "content_text": "\nSelf-hosting 1 or several media servers is another common homelab use-case.\nGetting content for your media servers is up to you, but I'll show a few ways here to get content somewhat easily!\n\n__YouTube Disclaimer at Bottom__\n\n\n## you-get\n\n`you-get` is a nice cli for grabbing media content off the web. \n\n### Installation\n\n`pip install you-get` or use ad-hoc with `pipx run you-get \u003curl\u003e`\n\n\n### Usage\n\nFor example if I wanted to catch up on ancient Chinese military tactics I may go for `The Art of War` off the Internet Archive...\n\n```bash\nsandbox  🌱 main 🗑️  ×3🛤️  ×6via 🐍 v3.8.11 (sandbox)  took 15s\n❯ you-get https://archive.org/details/art_of_war_librivox -i\nSite:       Archive.org\nTitle:      The Art of War : Sun Tzu : Free Download, Borrow, and Streaming : Internet Archive\nType:       MP3 (audio/mpeg)\nSize:       3.87 MiB (4055167 Bytes)\n\n```\n\nthe `-i` is showing me the info of what would be downloaded without the flag (it's like a dry run)\n\n```bash\nsandbox  🌱 main 🗑️  ×3🛤️  ×6via 🐍 v3.8.11 (sandbox)\n❯ you-get https://archive.org/details/art_of_war_librivox\nSite:       Archive.org\nTitle:      The Art of War : Sun Tzu : Free Download, Borrow, and Streaming : Internet Archive\nType:       MP3 (audio/mpeg)\nSize:       3.87 MiB (4055167 Bytes)\n\nDownloading The Art of War : Sun Tzu : Free Download, Borrow, and Streaming : Internet Archi.mp3 ...\n 100% (  3.9/  3.9MB) ├████████████████████████████████████████████████████████████┤[1/1]  917 kB/s\n\n```\n\nNow I can toss that mp3 onto my `booksonic` server and study for world domination while I do the dishes!\n\n\n## pytube\n\n`pytube` is a python implementation of a [youtube downloader ](##YouTube) that works at the command line or in python!\n\n### Installation\n\n[docs](https://pytube.io/en/latest/)\n\n`pip install pytube`\n\n\n### Usage\n\n`pytube` has a lot of functionality, but a quick one would be the `--list` so you can see what qualities are available\n\n```bash\nsandbox   main ️  ×3️  ×7via  v3.8.11 (sandbox)  took 2m49s\n❯ pytube https://www.youtube.com/watch\\?v\\=LDU_Txk06tM  --list\nLoading video...\n\u003cStream: itag=\"17\" mime_type=\"video/3gpp\" res=\"144p\" fps=\"8fps\" vcodec=\"mp4v.20.3\" acodec=\"mp4a.40.2\" progressive=\"True\" type=\"video\"\u003e\n\u003cStream: itag=\"18\" mime_type=\"video/mp4\" res=\"360p\" fps=\"30fps\" vcodec=\"avc1.42001E\" acodec=\"mp4a.40.2\" progressive=\"True\" type=\"video\"\u003e\n\u003cStream: itag=\"22\" mime_type=\"video/mp4\" res=\"720p\" fps=\"30fps\" vcodec=\"avc1.64001F\" acodec=\"mp4a.40.2\" progressive=\"True\" type=\"video\"\u003e\n\u003cStream: itag=\"313\" mime_type=\"video/webm\" res=\"2160p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"401\" mime_type=\"video/mp4\" res=\"2160p\" fps=\"30fps\" vcodec=\"av01.0.12M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"271\" mime_type=\"video/webm\" res=\"1440p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"400\" mime_type=\"video/mp4\" res=\"1440p\" fps=\"30fps\" vcodec=\"av01.0.12M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"137\" mime_type=\"video/mp4\" res=\"1080p\" fps=\"30fps\" vcodec=\"avc1.640028\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"248\" mime_type=\"video/webm\" res=\"1080p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"399\" mime_type=\"video/mp4\" res=\"1080p\" fps=\"30fps\" vcodec=\"av01.0.08M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"136\" mime_type=\"video/mp4\" res=\"720p\" fps=\"30fps\" vcodec=\"avc1.4d401f\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"247\" mime_type=\"video/webm\" res=\"720p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"398\" mime_type=\"video/mp4\" res=\"720p\" fps=\"30fps\" vcodec=\"av01.0.05M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"135\" mime_type=\"video/mp4\" res=\"480p\" fps=\"30fps\" vcodec=\"avc1.4d401f\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"244\" mime_type=\"video/webm\" res=\"480p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"397\" mime_type=\"video/mp4\" res=\"480p\" fps=\"30fps\" vcodec=\"av01.0.04M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"134\" mime_type=\"video/mp4\" res=\"360p\" fps=\"30fps\" vcodec=\"avc1.4d401e\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"243\" mime_type=\"video/webm\" res=\"360p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"396\" mime_type=\"video/mp4\" res=\"360p\" fps=\"30fps\" vcodec=\"av01.0.01M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"133\" mime_type=\"video/mp4\" res=\"240p\" fps=\"30fps\" vcodec=\"avc1.4d4015\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"242\" mime_type=\"video/webm\" res=\"240p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"395\" mime_type=\"video/mp4\" res=\"240p\" fps=\"30fps\" vcodec=\"av01.0.00M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"160\" mime_type=\"video/mp4\" res=\"144p\" fps=\"30fps\" vcodec=\"avc1.4d400c\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"278\" mime_type=\"video/webm\" res=\"144p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"394\" mime_type=\"video/mp4\" res=\"144p\" fps=\"30fps\" vcodec=\"av01.0.00M.08\" progressive=\"False\" type=\"video\"\u003e\n\u003cStream: itag=\"139\" mime_type=\"audio/mp4\" abr=\"48kbps\" acodec=\"mp4a.40.5\" progressive=\"False\" type=\"audio\"\u003e\n\u003cStream: itag=\"140\" mime_type=\"audio/mp4\" abr=\"128kbps\" acodec=\"mp4a.40.2\" progressive=\"False\" type=\"audio\"\u003e\n\u003cStream: itag=\"249\" mime_type=\"audio/webm\" abr=\"50kbps\" acodec=\"opus\" progressive=\"False\" type=\"audio\"\u003e\n\u003cStream: itag=\"250\" mime_type=\"audio/webm\" abr=\"70kbps\" acodec=\"opus\" progressive=\"False\" type=\"audio\"\u003e\n\u003cStream: itag=\"251\" mime_type=\"audio/webm\" abr=\"160kbps\" acodec=\"opus\" progressive=\"False\" type=\"audio\"\u003e\n\n```\n\n`pytube \u003curl\u003e --itag \u003c\u003e` will download the specific `itag` from the list.\n\nNotice that some `itags` are videos and others audio - so you can download just the music of a YT video.\n\n\n`pytube` also works in python...\n\n```python\nsandbox ↪ main v3.8.11 ipython\n❯ from pytube import YouTube\n\nsandbox ↪ main v3.8.11 ipython\n❯ [x for x in YouTube(\"https://www.youtube.com/watch?v=LDU_Txk06tM\").streams]\n\n[\n    \u003cStream: itag=\"17\" mime_type=\"video/3gpp\" res=\"144p\" fps=\"8fps\" vcodec=\"mp4v.20.3\" acodec=\"mp4a.40.2\" progressive=\"True\" type=\"video\"\u003e,\n    \u003cStream: itag=\"18\" mime_type=\"video/mp4\" res=\"360p\" fps=\"30fps\" vcodec=\"avc1.42001E\" acodec=\"mp4a.40.2\" progressive=\"True\" type=\"video\"\u003e,\n    \u003cStream: itag=\"22\" mime_type=\"video/mp4\" res=\"720p\" fps=\"30fps\" vcodec=\"avc1.64001F\" acodec=\"mp4a.40.2\" progressive=\"True\" type=\"video\"\u003e,\n    \u003cStream: itag=\"313\" mime_type=\"video/webm\" res=\"2160p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"401\" mime_type=\"video/mp4\" res=\"2160p\" fps=\"30fps\" vcodec=\"av01.0.12M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"271\" mime_type=\"video/webm\" res=\"1440p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"400\" mime_type=\"video/mp4\" res=\"1440p\" fps=\"30fps\" vcodec=\"av01.0.12M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"137\" mime_type=\"video/mp4\" res=\"1080p\" fps=\"30fps\" vcodec=\"avc1.640028\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"248\" mime_type=\"video/webm\" res=\"1080p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"399\" mime_type=\"video/mp4\" res=\"1080p\" fps=\"30fps\" vcodec=\"av01.0.08M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"136\" mime_type=\"video/mp4\" res=\"720p\" fps=\"30fps\" vcodec=\"avc1.4d401f\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"247\" mime_type=\"video/webm\" res=\"720p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"398\" mime_type=\"video/mp4\" res=\"720p\" fps=\"30fps\" vcodec=\"av01.0.05M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"135\" mime_type=\"video/mp4\" res=\"480p\" fps=\"30fps\" vcodec=\"avc1.4d401f\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"244\" mime_type=\"video/webm\" res=\"480p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"397\" mime_type=\"video/mp4\" res=\"480p\" fps=\"30fps\" vcodec=\"av01.0.04M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"134\" mime_type=\"video/mp4\" res=\"360p\" fps=\"30fps\" vcodec=\"avc1.4d401e\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"243\" mime_type=\"video/webm\" res=\"360p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"396\" mime_type=\"video/mp4\" res=\"360p\" fps=\"30fps\" vcodec=\"av01.0.01M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"133\" mime_type=\"video/mp4\" res=\"240p\" fps=\"30fps\" vcodec=\"avc1.4d4015\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"242\" mime_type=\"video/webm\" res=\"240p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"395\" mime_type=\"video/mp4\" res=\"240p\" fps=\"30fps\" vcodec=\"av01.0.00M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"160\" mime_type=\"video/mp4\" res=\"144p\" fps=\"30fps\" vcodec=\"avc1.4d400c\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"278\" mime_type=\"video/webm\" res=\"144p\" fps=\"30fps\" vcodec=\"vp9\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"394\" mime_type=\"video/mp4\" res=\"144p\" fps=\"30fps\" vcodec=\"av01.0.00M.08\" progressive=\"False\" type=\"video\"\u003e,\n    \u003cStream: itag=\"139\" mime_type=\"audio/mp4\" abr=\"48kbps\" acodec=\"mp4a.40.5\" progressive=\"False\" type=\"audio\"\u003e,\n    \u003cStream: itag=\"140\" mime_type=\"audio/mp4\" abr=\"128kbps\" acodec=\"mp4a.40.2\" progressive=\"False\" type=\"audio\"\u003e,\n    \u003cStream: itag=\"249\" mime_type=\"audio/webm\" abr=\"50kbps\" acodec=\"opus\" progressive=\"False\" type=\"audio\"\u003e,\n    \u003cStream: itag=\"250\" mime_type=\"audio/webm\" abr=\"70kbps\" acodec=\"opus\" progressive=\"False\" type=\"audio\"\u003e,\n    \u003cStream: itag=\"251\" mime_type=\"audio/webm\" abr=\"160kbps\" acodec=\"opus\" progressive=\"False\" type=\"audio\"\u003e\n]\n\n\n```\n\n## YouTube Frontends\n\nThere's 2 really good options for self-hosting a YT front-end...\n\n[Tube Archivist](https://github.com/bbilly1/tubearchivist)\n\n[YouTubeDL-Material](https://github.com/Tzahi12345/YoutubeDL-Material)\n\nThey have their pros and cons.\nYou can also build yourself with the above utilities and use Plex or Jellyfin to serve up videos...\n\n__Your self-hosting journey is up to you!__\n\n\n## YouTube\n\nDownloading YouTube videos is a bit of a sore topic... Mainly you don't to hurt creators who rely on YT ad revenue for their livlihood.\n\nThen again, maybe you're a vigilante who knows that YT also monetizes videos for their _own_ gain and that the creators don't see that money either!\n\nThe solution is pretty easy and is 2-fold...\n\n1. Download YT videos\n2. Personally support the content creators you follow via paypall, patreon, or whatever else they might have set-up.... even a buck or two a month is more than they'd get from your ad revenue explicitly plus it all goes to them!\n",
      "summary": "Self-hosting 1 or several media servers is another common homelab use-case. Getting content for your media servers is up to you, but I'll show a few ways...",
      "date_published": "2022-03-24T00:00:00Z",
      "date_modified": "2022-03-24T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "homelab",
        "tech",
        "series-homelab"
      ]
    },
    {
      "id": "https://pype.dev/skimpy/",
      "url": "https://pype.dev/skimpy/",
      "title": "Skimpy",
      "content_html": "\u003ch2 id=\"eda\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eEDA\u003c/span\u003e \u003ca href=\"#eda\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI work with data a lot, but the nature of my job isn\u0026rsquo;t to dive super deep into a small amount of datasets,\nI\u0026rsquo;m often jumping between several projects every day and need to just get a super quick glance at some tables to get a high level view.\u003c/p\u003e\n\u003cp\u003eWhen I\u0026rsquo;m doing more interactive exploration I\u0026rsquo;ve graduated from Jupyter cells with \u003ccode\u003edf_N.head()\u003c/code\u003e to using an amazing tool called \u003ca href=\"https://www.visidata.org/\"\u003evisidata\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eHowever, Visidata is a terminal based application and I\u0026rsquo;m often in an iPython console\u0026hellip; so is there a way to move even faster for my super quick summary views?\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eyes!\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"skimpy\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSkimpy\u003c/span\u003e \u003ca href=\"#skimpy\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eFirst thing to do is \u003ccode\u003epip install skimpy\u003c/code\u003e and then it\u0026rsquo;s as easy to get some summary stats with \u003ccode\u003eskimpy \u0026lt;data\u0026gt;\u003c/code\u003e\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-zsh.png\" alt=\"Skimpy ZSH\" title=\"A fancy data summary in the shell\" /\u003e\n\u003cp\u003eThis is super nice for seeing missing values in particular as well as the distribution shape of the data.\u003c/p\u003e\n\u003ch2 id=\"ipython\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eiPython\u003c/span\u003e \u003ca href=\"#ipython\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eBut wait\u0026hellip; I just said I\u0026rsquo;m normally in an iPython session but that was called from zsh.. If I\u0026rsquo;m hoping back into zsh I might as well use visidata to have more powerful exploration at my fingertips.\nSo\u0026hellip; can I see this table quickly without breaking my iPython workflow?\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eOf course you can with magic!\u003c/strong\u003e\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-ipython.png\" alt=\"Skimpy iPython\" title=\"A fancy data summary in iPython\" /\u003e\n\u003cp\u003eThe above assumes you\u0026rsquo;re looking at a file, like you would in the terminal.\n\u003ccode\u003eskimpy\u003c/code\u003e works even better in iPython with \u003ccode\u003efrom skimpy import skim\u003c/code\u003e then pass any DataFrame to \u003ccode\u003eskim\u003c/code\u003e!\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-ipython2.png\" alt=\"Skimpy iPython2\" title=\"A fancy data summary in iPython\" /\u003e\n",
      "content_text": "\n## EDA\n\nI work with data a lot, but the nature of my job isn't to dive super deep into a small amount of datasets,\nI'm often jumping between several projects every day and need to just get a super quick glance at some tables to get a high level view.\n\nWhen I'm doing more interactive exploration I've graduated from Jupyter cells with `df_N.head()` to using an amazing tool called [visidata](https://www.visidata.org/)\n\nHowever, Visidata is a terminal based application and I'm often in an iPython console... so is there a way to move even faster for my super quick summary views?\n\n__yes!__ \n\n## Skimpy\n\nFirst thing to do is `pip install skimpy` and then it's as easy to get some summary stats with `skimpy \u003cdata\u003e`\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-zsh.png\" alt=\"Skimpy ZSH\" title=\"A fancy data summary in the shell\" /\u003e\n\nThis is super nice for seeing missing values in particular as well as the distribution shape of the data.\n\n## iPython\n\nBut wait... I just said I'm normally in an iPython session but that was called from zsh.. If I'm hoping back into zsh I might as well use visidata to have more powerful exploration at my fingertips.\nSo... can I see this table quickly without breaking my iPython workflow?\n\n__Of course you can with magic!__\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-ipython.png\" alt=\"Skimpy iPython\" title=\"A fancy data summary in iPython\" /\u003e\n\n\nThe above assumes you're looking at a file, like you would in the terminal. \n`skimpy` works even better in iPython with `from skimpy import skim` then pass any DataFrame to `skim`!\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/skimpy-ipython2.png\" alt=\"Skimpy iPython2\" title=\"A fancy data summary in iPython\" /\u003e\n",
      "summary": "I work with data a lot, but the nature of my job isn't to dive super deep into a small amount of datasets, I'm often jumping between several projects every...",
      "date_published": "2022-03-23T00:00:00Z",
      "date_modified": "2022-03-23T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "homepage",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/truenas-and-wireguard/",
      "url": "https://pype.dev/truenas-and-wireguard/",
      "title": "Truenas-And-Wireguard",
      "content_html": "\u003ch2 id=\"nas\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eNAS\u003c/span\u003e \u003ca href=\"#nas\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eOne of the most common use cases for self-hosting anything is a file share system.\nI have been a fan of \u003ca href=\"https://www.truenas.com/\"\u003eTrueNAS\u003c/a\u003e for a while.\nI currently use TrueNAS Core at home, and plan to consider transitioning to TrueNAS Scale soon.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eBlog post forthcoming on that!\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"vpn\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eVPN\u003c/span\u003e \u003ca href=\"#vpn\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI don\u0026rsquo;t write a ton about homelabbing yet but one of the first things to set up whether you have a massive homelab or a little raspberry pi would be a self-hosted VPN.\nI have notes on wireguard \u003ca href=\"%22/wireguard%22\"\u003ehere\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eI finally have a need to put my TrueNAS box on my wireguard network in order to transfer files to other devices that are outside my LAN.\u003c/p\u003e\n\u003cp\u003eThere is a handy tutorial on setting this up via the GUI \u003ca href=\"https://www.truenas.com/docs/core/network/wireguard/\"\u003ehere\u003c/a\u003e.\nThey walk you through setting up 2 tunables wireguard. One to enable the the connection and one to setup the network interface.\nNext you create a \u003ccode\u003ePost Init\u003c/code\u003e script which will check that the right directories exist and will copy the wireguard config that hasn\u0026rsquo;t been made yet to the proper location and finally starts wireguard.\u003c/p\u003e\n\u003cp\u003eThe above is just copy/paste from the tutorial but the final step, although not super tricky, isn\u0026rsquo;t the same for everyone as it depends on your wireguard config and network setup.\u003c/p\u003e\n\u003cp\u003eThe final step is for you create the relevant wireguard config (see my post but I just use \u003ccode\u003epivpn -a\u003c/code\u003e) and send that config over to your TrueNAS box!\u003c/p\u003e\n\u003cp\u003eFor me this final work flow looked like this:\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\"\u003essh user@vpn-server\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\"\u003epivpn -a\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\u0026lt;follow prompts\u0026gt;\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\"\u003escp ~/configs/truenas.conf root@\u0026lt;truenas ip\u0026gt;:/root/wg0.conf\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\u003ch2 id=\"bug\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBug?\u003c/span\u003e \u003ca href=\"#bug\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe script in the tutorial for starting the wireguard service is straight forward enough however my TrueNAS box didn\u0026rsquo;t get the wireguard interface up and running on reboot.\u003c/p\u003e\n\u003cp\u003eEasy enough solution:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003essh root@\u0026lt;truenas ip\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e/usr/local/etc/rc.d/wireguard start\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWe can check that the interface is now working with \u003ccode\u003eifconfig\u003c/code\u003e and should see something like the following:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ewg0: \u003cspan class=\"nv\"\u003eflags\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e8051\u0026lt;UP,POINTOPOINT,RUNNING,MULTICAST\u0026gt; metric \u003cspan class=\"m\"\u003e0\u003c/span\u003e mtu \u003cspan class=\"m\"\u003e1420\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"nv\"\u003eoptions\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e80000\u0026lt;LINKSTATE\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        inet x.x.x.x --\u0026gt; x.x.x.x netmask 0xffffff00\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        groups: tun\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        nd6 \u003cspan class=\"nv\"\u003eoptions\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e101\u0026lt;PERFORMNUD,NO_DAD\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        Opened by PID \u003cspan class=\"m\"\u003e1325\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"gotcha\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGotcha!\u003c/span\u003e \u003ca href=\"#gotcha\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s another thing I had to navigate when setting this up.\u003c/p\u003e\n\u003cp\u003eMy \u003ccode\u003epivpn\u003c/code\u003e configuration sets the endpoint for my wireguard clients to \u003ccode\u003epaynepride.com:\u0026lt;port forwarded to wireguard server\u0026gt;\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eWhat this means is that when I check the wireguard config for TrueNAS which is on my home network it resolves \u003ccode\u003epaynepride.com\u003c/code\u003e to the server I have running my reverse proxy.\nHowever! My reverse proxy is not responsible for my vpn traffic and so the traffic was just getting dropped - instead I needed to change the wireguard config just for my truenas box to piont to the local address of my vpn server.\nThis really threw me for a loop today but is just another reminder that if you have network problems it\u0026rsquo;s probably DNS\u0026hellip;\u003c/p\u003e\n\u003cp\u003eGive her the \u0026lsquo;ol reboot and now if I check \u003ccode\u003ewg\u003c/code\u003e I should see some traffic on my wireguard tunnel!\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/truenas-wireguard.png\" alt=\"TrueNAS Wireguard\" title=\"TrueNAS and Wireguard\" /\u003e\n",
      "content_text": "\n## NAS\n\nOne of the most common use cases for self-hosting anything is a file share system. \nI have been a fan of [TrueNAS](https://www.truenas.com/) for a while. \nI currently use TrueNAS Core at home, and plan to consider transitioning to TrueNAS Scale soon.\n\n__Blog post forthcoming on that!__ \n\n\n## VPN \n\nI don't write a ton about homelabbing yet but one of the first things to set up whether you have a massive homelab or a little raspberry pi would be a self-hosted VPN.\nI have notes on wireguard [here](\"/wireguard\").\n\nI finally have a need to put my TrueNAS box on my wireguard network in order to transfer files to other devices that are outside my LAN.\n\nThere is a handy tutorial on setting this up via the GUI [here](https://www.truenas.com/docs/core/network/wireguard/).\nThey walk you through setting up 2 tunables wireguard. One to enable the the connection and one to setup the network interface.\nNext you create a `Post Init` script which will check that the right directories exist and will copy the wireguard config that hasn't been made yet to the proper location and finally starts wireguard.\n\nThe above is just copy/paste from the tutorial but the final step, although not super tricky, isn't the same for everyone as it depends on your wireguard config and network setup.\n\nThe final step is for you create the relevant wireguard config (see my post but I just use `pivpn -a`) and send that config over to your TrueNAS box!\n\nFor me this final work flow looked like this:\n\n```bash\n\nssh user@vpn-server\n\npivpn -a\n\n\u003cfollow prompts\u003e\n\nscp ~/configs/truenas.conf root@\u003ctruenas ip\u003e:/root/wg0.conf\n\n```\n\n## Bug?\n\nThe script in the tutorial for starting the wireguard service is straight forward enough however my TrueNAS box didn't get the wireguard interface up and running on reboot.\n\nEasy enough solution:\n\n```bash\nssh root@\u003ctruenas ip\u003e\n/usr/local/etc/rc.d/wireguard start\n```\n\nWe can check that the interface is now working with `ifconfig` and should see something like the following:\n\n```bash \nwg0: flags=8051\u003cUP,POINTOPOINT,RUNNING,MULTICAST\u003e metric 0 mtu 1420\n        options=80000\u003cLINKSTATE\u003e\n        inet x.x.x.x --\u003e x.x.x.x netmask 0xffffff00\n        groups: tun\n        nd6 options=101\u003cPERFORMNUD,NO_DAD\u003e\n        Opened by PID 1325\n```\n\n\n## Gotcha!\n\nHere's another thing I had to navigate when setting this up.\n\nMy `pivpn` configuration sets the endpoint for my wireguard clients to `paynepride.com:\u003cport forwarded to wireguard server\u003e`\n\nWhat this means is that when I check the wireguard config for TrueNAS which is on my home network it resolves `paynepride.com` to the server I have running my reverse proxy.\nHowever! My reverse proxy is not responsible for my vpn traffic and so the traffic was just getting dropped - instead I needed to change the wireguard config just for my truenas box to piont to the local address of my vpn server.\nThis really threw me for a loop today but is just another reminder that if you have network problems it's probably DNS...\n\nGive her the 'ol reboot and now if I check `wg` I should see some traffic on my wireguard tunnel!\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/truenas-wireguard.png\" alt=\"TrueNAS Wireguard\" title=\"TrueNAS and Wireguard\" /\u003e\n",
      "summary": "One of the most common use cases for self-hosting anything is a file share system. I have been a fan of TrueNAS for a while. I currently use TrueNAS Core at...",
      "date_published": "2022-03-23T00:00:00Z",
      "date_modified": "2022-03-23T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/pyclean/",
      "url": "https://pype.dev/pyclean/",
      "title": "Pyclean",
      "content_html": "\u003cp\u003eI like to keep my workspace clean and one thing that I don\u0026rsquo;t personally love looking at is the \u003ccode\u003e__pycache__\u003c/code\u003e directory that pops up after running some code.\nThe \u003ccode\u003e*.pyc\u003c/code\u003e files that show up there are python bytecode and they are cached to make subsequent runs a tad faster.\nMy stuff never really needs this bonus speed boost and so I came across a neat tool called \u003ccode\u003epyclean\u003c/code\u003e!\u003c/p\u003e\n\u003ch2 id=\"pyclean\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003ePyclean\u003c/span\u003e \u003ca href=\"#pyclean\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe easiest way (\u003cstrong\u003ein my opinion\u003c/strong\u003e) to run \u003ccode\u003epyclean\u003c/code\u003e is to just use \u003ccode\u003epipx run\u003c/code\u003e.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esandbox/src  🌱 main 🗑️  ×3🛤️  ×2via 🐍 v3.8.11 \u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e  took 9s\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eabcmeta.py  __pycache__  python-print-align.py  system-monitor-psutils.py\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\"\u003esandbox/src  🌱 main 🗑️  ×3🛤️  ×2via 🐍 v3.8.11 \u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ pipx run pyclean .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e⚠️  pyclean is already on your PATH and installed at /usr/bin/pyclean. Downloading and running anyway.\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eCleaning directory .\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTotal \u003cspan class=\"m\"\u003e1\u003c/span\u003e files, \u003cspan class=\"m\"\u003e1\u003c/span\u003e directories removed.\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\"\u003esandbox/src  🌱 main 🗑️  ×3🛤️  ×2via 🐍 v3.8.11 \u003cspan class=\"o\"\u003e(\u003c/span\u003esandbox\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ ls\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eabcmeta.py  python-print-align.py  system-monitor-psutils.py\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\u003ch2 id=\"why-not-bash\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWhy not bash?\u003c/span\u003e \u003ca href=\"#why-not-bash\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eYou could accomplish something similar with \u003ccode\u003erm **/*.pyc\u003c/code\u003e or \u003ccode\u003efind -n '*.py?' -delete\u003c/code\u003e but there\u0026rsquo;s a chance you\u0026rsquo;ll find something you don\u0026rsquo;t love gone.\nAlso this won\u0026rsquo;t help our poor Windows friends out there!\n\u003ccode\u003epyclean\u003c/code\u003e is fully python so it\u0026rsquo;s OS independent.\u003c/p\u003e\n\u003ch2 id=\"credits\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eCredits!\u003c/span\u003e \u003ca href=\"#credits\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/bittner/pyclean\"\u003erepo\u003c/a\u003e\u003c/p\u003e\n",
      "content_text": "\nI like to keep my workspace clean and one thing that I don't personally love looking at is the `__pycache__` directory that pops up after running some code.\nThe `*.pyc` files that show up there are python bytecode and they are cached to make subsequent runs a tad faster. \nMy stuff never really needs this bonus speed boost and so I came across a neat tool called `pyclean`!\n\n## Pyclean\n\nThe easiest way (**in my opinion**) to run `pyclean` is to just use `pipx run`.\n\n```bash\nsandbox/src  🌱 main 🗑️  ×3🛤️  ×2via 🐍 v3.8.11 (sandbox)  took 9s\n❯ ls\nabcmeta.py  __pycache__  python-print-align.py  system-monitor-psutils.py\n\nsandbox/src  🌱 main 🗑️  ×3🛤️  ×2via 🐍 v3.8.11 (sandbox)\n❯ pipx run pyclean .\n⚠️  pyclean is already on your PATH and installed at /usr/bin/pyclean. Downloading and running anyway.\nCleaning directory .\nTotal 1 files, 1 directories removed.\n\nsandbox/src  🌱 main 🗑️  ×3🛤️  ×2via 🐍 v3.8.11 (sandbox)\n❯ ls\nabcmeta.py  python-print-align.py  system-monitor-psutils.py\n\n```\n\n## Why not bash?\n\nYou could accomplish something similar with `rm **/*.pyc` or `find -n '*.py?' -delete` but there's a chance you'll find something you don't love gone.\nAlso this won't help our poor Windows friends out there!\n`pyclean` is fully python so it's OS independent.\n\n## Credits!\n\n[repo](https://github.com/bittner/pyclean)\n",
      "summary": "I like to keep my workspace clean and one thing that I don't personally love looking at is the directory that pops up after running some code. The files that...",
      "date_published": "2022-03-22T00:00:00Z",
      "date_modified": "2022-03-22T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/psutils-01/",
      "url": "https://pype.dev/psutils-01/",
      "title": "Psutil-01",
      "content_html": "\u003cp\u003e\u003ca href=\"https://twitter.com/driscollis\"\u003eMike Driscoll\u003c/a\u003e has been posting some awesome posts about \u003ccode\u003epsutil\u003c/code\u003e lately.\nI\u0026rsquo;m interested in making my own system monitoring dashboard now using this library.\nI don\u0026rsquo;t expect it to compete with Netdata or Glances but it\u0026rsquo;ll just be for fun to see how Python can solve this problem!\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eRepo coming soon\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"example-code\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eExample code:\u003c/span\u003e \u003ca href=\"#example-code\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHere\u0026rsquo;s a short snippit to get used/available/total RAM and disk space (on partitions that you probably care about)\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=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003epsutil\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\"\u003esocket\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\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"sa\"\u003ef\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;System Memory used: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evirtual_memory\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eused\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e GB\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=\"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;System Memory available: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evirtual_memory\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eavailable\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e GB\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=\"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;System Memory total: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003evirtual_memory\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etotal\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e GB\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=\"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;Hostname: \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003esocket\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003egethostname\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\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\u003cspan class=\"n\"\u003epartitions\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edisk_partitions\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\u003cspan class=\"k\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003epart\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003epartitions\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\"\u003emnt\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epart\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emountpoint\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=\"s2\"\u003e\u0026#34;snap\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003emnt\u003c/span\u003e \u003cspan class=\"ow\"\u003eor\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;boot\u0026#34;\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003emnt\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\"\u003econtinue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003edisk\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epsutil\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edisk_usage\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emnt\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;Usage at \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003emnt\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e on \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epart\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edevice\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\"\u003edisk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eused\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e GB\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=\"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;Free at \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003emnt\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e on \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epart\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edevice\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\"\u003edisk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003efree\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003eGB\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=\"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;Total at \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003emnt\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003e on \u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003epart\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edevice\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\"\u003edisk\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003etotal\u003c/span\u003e \u003cspan class=\"o\"\u003e//\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1024\u003c/span\u003e \u003cspan class=\"o\"\u003e**\u003c/span\u003e \u003cspan class=\"mi\"\u003e3\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\u003cspan class=\"si\"\u003e}\u003c/span\u003e\u003cspan class=\"s2\"\u003eGB\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\u003cblockquote\u003e\n\u003cp\u003eBonus Ipython tip! Save this to a script called my_script.py and in Ipython you can %run -m my_script to run it!\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eproject ↪ main v3.8.11 ipython\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ %run -m system-monitor-psutils\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSystem Memory used: \u003cspan class=\"m\"\u003e25\u003c/span\u003e GB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSystem Memory available: \u003cspan class=\"m\"\u003e5\u003c/span\u003e GB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eSystem Memory total: \u003cspan class=\"m\"\u003e31\u003c/span\u003e GB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eHostname: ryzen-3600x\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eUsage at / on /dev/nvme1n1p2: \u003cspan class=\"m\"\u003e81\u003c/span\u003e GB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eFree at / on /dev/nvme1n1p2: \u003cspan class=\"m\"\u003e351\u003c/span\u003e GB\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eTotal at / on /dev/nvme1n1p2: \u003cspan class=\"m\"\u003e456\u003c/span\u003e GB\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\n[Mike Driscoll](https://twitter.com/driscollis) has been posting some awesome posts about `psutil` lately.\nI'm interested in making my own system monitoring dashboard now using this library.\nI don't expect it to compete with Netdata or Glances but it'll just be for fun to see how Python can solve this problem!\n\n__Repo coming soon__\n\n## Example code:\nHere's a short snippit to get used/available/total RAM and disk space (on partitions that you probably care about)\n```python\n\nimport psutil\nimport socket\n\nprint(f\"System Memory used: {psutil.virtual_memory().used // (1024 ** 3)} GB\")\nprint(f\"System Memory available: {psutil.virtual_memory().available // (1024 ** 3)} GB\")\nprint(f\"System Memory total: {psutil.virtual_memory().total // (1024 ** 3)} GB\")\n\n\nprint(f\"Hostname: {socket.gethostname()}\")\n\npartitions = psutil.disk_partitions()\n\nfor part in partitions:\n    mnt = part.mountpoint\n    if \"snap\" in mnt or \"boot\" in mnt:\n        continue\n    disk = psutil.disk_usage(mnt)\n    print(f\"Usage at {mnt} on {part.device}: {disk.used // (1024 ** 3)} GB\")\n    print(f\"Free at {mnt} on {part.device}: {disk.free // (1024 ** 3)}GB\")\n    print(f\"Total at {mnt} on {part.device}: {disk.total // (1024 ** 3)}GB\")\n```\n\n\u003e Bonus Ipython tip! Save this to a script called my_script.py and in Ipython you can %run -m my_script to run it!\n\n```bash\nproject ↪ main v3.8.11 ipython\n❯ %run -m system-monitor-psutils\nSystem Memory used: 25 GB\nSystem Memory available: 5 GB\nSystem Memory total: 31 GB\nHostname: ryzen-3600x\nUsage at / on /dev/nvme1n1p2: 81 GB\nFree at / on /dev/nvme1n1p2: 351 GB\nTotal at / on /dev/nvme1n1p2: 456 GB\n```\n",
      "summary": "Mike Driscoll has been posting some awesome posts about lately. I'm interested in making my own system monitoring dashboard now using this library. I don't...",
      "date_published": "2022-03-16T00:00:00Z",
      "date_modified": "2022-03-16T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/mu/",
      "url": "https://pype.dev/mu/",
      "title": "Mu",
      "content_html": "\u003cp\u003eIf you work with a template for several projects then you might sometimes need to do the same action across all repos.\nA good example of this is updating a package in \u003ccode\u003erequirements.txt\u003c/code\u003e in every project, or refactoring a common module.\nIf you have several repos to do this across then it can be time consuming\u0026hellip; enter \u003ccode\u003emu-repo\u003c/code\u003e\u003c/p\u003e\n\u003ch2 id=\"mu\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eMu\u003c/span\u003e \u003ca href=\"#mu\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://fabioz.github.io/mu-repo/\"\u003emu-repo\u003c/a\u003e is an awesome cli tool for working with multiple git repositories at the same time.\nThere are several things you can do:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003emu status\u003c/code\u003e will give you the \u003ccode\u003egit status\u003c/code\u003e of every registered repo (see below)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emu sh\u003c/code\u003e will let you execute system level commands in every repo\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003emu stash\u003c/code\u003e will stash all changes across all registered repos\u003c/li\u003e\n\u003cli\u003eThere\u0026rsquo;s literally a ton more but these are some handy ones\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"registration\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eRegistration\u003c/span\u003e \u003ca href=\"#registration\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ccode\u003emu\u003c/code\u003e tracks its own \u003ccode\u003egroups\u003c/code\u003e, and there is a default group when no particular one is active.\nIt\u0026rsquo;s as simple as \u003ccode\u003emu register proj1 prog2 ...\u003c/code\u003e to get repos registered\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❯ mu register proj1 proj2\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRepository: proj1 registered\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eRepository: proj2 registered\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❯ mu status\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  proj1 : git status\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    On branch main\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    No commits yet\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    Untracked files:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e(\u003c/span\u003euse \u003cspan class=\"s2\"\u003e\u0026#34;git add \u0026lt;file\u0026gt;...\u0026#34;\u003c/span\u003e to include in what will be committed\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    requirements.txt\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    nothing added to commit but untracked files present \u003cspan class=\"o\"\u003e(\u003c/span\u003euse \u003cspan class=\"s2\"\u003e\u0026#34;git add\u0026#34;\u003c/span\u003e to track\u003cspan class=\"o\"\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  proj2 : git status\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    On branch main\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    No commits yet\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    Changes to be committed:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e(\u003c/span\u003euse \u003cspan class=\"s2\"\u003e\u0026#34;git rm --cached \u0026lt;file\u0026gt;...\u0026#34;\u003c/span\u003e to unstage\u003cspan class=\"o\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    new file:   requirements.txt\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\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"working-with-mu\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWorking with mu\u003c/span\u003e \u003ca href=\"#working-with-mu\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eAs you can see above I have two projects each with a \u003ccode\u003erequirements.txt\u003c/code\u003e added but not committed yet.\nUsing \u003ccode\u003emu\u003c/code\u003e I can stage this change across both repos at once.\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❯ mu add requirements.txt\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  proj1 : git add requirements.txt\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  proj2 : git add requirements.txt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eThen as you might imagine, I can make the commit in each repo\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❯ mu commit -m \u003cspan class=\"s2\"\u003e\u0026#34;Add requirements.txts\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  proj1 : git commit -m Add requirements.txts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e[\u003c/span\u003emain \u003cspan class=\"o\"\u003e(\u003c/span\u003eroot-commit\u003cspan class=\"o\"\u003e)\u003c/span\u003e 18376d7\u003cspan class=\"o\"\u003e]\u003c/span\u003e Add requirements.txts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"m\"\u003e1\u003c/span\u003e file changed, \u003cspan class=\"m\"\u003e1\u003c/span\u003e insertion\u003cspan class=\"o\"\u003e(\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    create mode \u003cspan class=\"m\"\u003e100644\u003c/span\u003e requirements.txt\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  proj2 : git commit -m Add requirements.txts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e[\u003c/span\u003emain \u003cspan class=\"o\"\u003e(\u003c/span\u003eroot-commit\u003cspan class=\"o\"\u003e)\u003c/span\u003e 18376d7\u003cspan class=\"o\"\u003e]\u003c/span\u003e Add requirements.txts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"m\"\u003e1\u003c/span\u003e file changed, \u003cspan class=\"m\"\u003e1\u003c/span\u003e insertion\u003cspan class=\"o\"\u003e(\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    create mode \u003cspan class=\"m\"\u003e100644\u003c/span\u003e requirements.txt\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch2 id=\"mu-groups\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003emu groups\u003c/span\u003e \u003ca href=\"#mu-groups\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eThe other thing I got a lot of use out of recently was \u003ccode\u003emu\u003c/code\u003e\u0026rsquo;s groups.\nAt work I have about 40 repos cloned that are all based on the same kedro pipeline template.\nSome of these projects have been deprecated.\nI also have several more repos that are not kedro template - custom libraries or something.\n\u003ccode\u003egroup\u003c/code\u003e let me utilize \u003ccode\u003emu\u003c/code\u003e across different groups of repos.\u003c/p\u003e\n\u003cp\u003eSay \u003ccode\u003eproj2\u003c/code\u003e is a deprecated project that I don\u0026rsquo;t need to worry about making changes to anymore.\nI don\u0026rsquo;t just have to unregister it, instead I can make a group called \u0026ldquo;active\u0026rdquo; and register \u003ccode\u003eproj1\u003c/code\u003e in that group\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❯ mu group add active --empty\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~/personal\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ mu group add deprecated --empty\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~/personal\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e❯ mu group\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  active\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e* deprecated\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\u003cp\u003eThe \u003ccode\u003e*\u003c/code\u003e tells me which group is active.\nThe \u003ccode\u003e--empty\u003c/code\u003e flag tells \u003ccode\u003emu\u003c/code\u003e to not add all registered repos to that group.\nIf I don\u0026rsquo;t want to use any groups then \u003ccode\u003emu group reset\u003c/code\u003e will go back to the default group with all registered repos.\u003c/p\u003e\n\u003cp\u003eWith groups I can register only the repos that I want to be working across in their own group and not worry about affecting other repos with my batch changes!\u003c/p\u003e\n",
      "content_text": "\nIf you work with a template for several projects then you might sometimes need to do the same action across all repos.\nA good example of this is updating a package in `requirements.txt` in every project, or refactoring a common module.\nIf you have several repos to do this across then it can be time consuming... enter `mu-repo`\n\n\n## Mu\n\n[mu-repo](https://fabioz.github.io/mu-repo/) is an awesome cli tool for working with multiple git repositories at the same time. \nThere are several things you can do:\n\n1. `mu status` will give you the `git status` of every registered repo (see below)\n2. `mu sh` will let you execute system level commands in every repo\n3. `mu stash` will stash all changes across all registered repos\n4. There's literally a ton more but these are some handy ones\n\n\n## Registration\n\n`mu` tracks its own `groups`, and there is a default group when no particular one is active.\nIt's as simple as `mu register proj1 prog2 ...` to get repos registered\n\n```bash \n\n❯ mu register proj1 proj2\nRepository: proj1 registered\nRepository: proj2 registered\n\n❯ mu status\n\n  proj1 : git status\n    On branch main\n\n    No commits yet\n\n    Untracked files:\n    (use \"git add \u003cfile\u003e...\" to include in what will be committed)\n    requirements.txt\n\n    nothing added to commit but untracked files present (use \"git add\" to track)\n\n  proj2 : git status\n    On branch main\n\n    No commits yet\n\n    Changes to be committed:\n    (use \"git rm --cached \u003cfile\u003e...\" to unstage)\n    new file:   requirements.txt\n\n\n```\n\n## Working with mu\n\nAs you can see above I have two projects each with a `requirements.txt` added but not committed yet.\nUsing `mu` I can stage this change across both repos at once.\n\n```bash  \n\n❯ mu add requirements.txt\n\n  proj1 : git add requirements.txt\n\n  proj2 : git add requirements.txt\n```\n\nThen as you might imagine, I can make the commit in each repo\n\n\n```bash\n\n❯ mu commit -m \"Add requirements.txts\"\n\n  proj1 : git commit -m Add requirements.txts\n    [main (root-commit) 18376d7] Add requirements.txts\n    1 file changed, 1 insertion(+)\n    create mode 100644 requirements.txt\n\n  proj2 : git commit -m Add requirements.txts\n    [main (root-commit) 18376d7] Add requirements.txts\n    1 file changed, 1 insertion(+)\n    create mode 100644 requirements.txt\n```\n\n## mu groups\n\nThe other thing I got a lot of use out of recently was `mu`'s groups.\nAt work I have about 40 repos cloned that are all based on the same kedro pipeline template.\nSome of these projects have been deprecated.\nI also have several more repos that are not kedro template - custom libraries or something.\n`group` let me utilize `mu` across different groups of repos.\n\nSay `proj2` is a deprecated project that I don't need to worry about making changes to anymore.\nI don't just have to unregister it, instead I can make a group called \"active\" and register `proj1` in that group\n\n```bash\n\n❯ mu group add active --empty\n\n~/personal\n❯ mu group add deprecated --empty\n\n~/personal\n❯ mu group\n  active\n* deprecated\n\n```\n\n\nThe `*` tells me which group is active. \nThe `--empty` flag tells `mu` to not add all registered repos to that group.\nIf I don't want to use any groups then `mu group reset` will go back to the default group with all registered repos.\n\nWith groups I can register only the repos that I want to be working across in their own group and not worry about affecting other repos with my batch changes!\n",
      "summary": "If you work with a template for several projects then you might sometimes need to do the same action across all repos. A good example of this is updating a...",
      "date_published": "2022-03-15T00:00:00Z",
      "date_modified": "2022-03-15T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "git",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/wireguard/",
      "url": "https://pype.dev/wireguard/",
      "title": "Wireguard",
      "content_html": "\u003ch2 id=\"vpn\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eVPN\u003c/span\u003e \u003ca href=\"#vpn\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eVirtual Private Networks are a big deal, and this shouldn\u0026rsquo;t be considered anything even close to a guide on using them.\nHere are just my notes and some setup for how I use \u003ca href=\"https://www.wireguard.com/\"\u003ewireguard\u003c/a\u003e at home.\u003c/p\u003e\n\u003ch2 id=\"wireguard\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWireguard\u003c/span\u003e \u003ca href=\"#wireguard\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWireguard is an awesome peer-to-peer VPN tunnel that makes it really easy for me to get into my home network when I\u0026rsquo;m out and about.\nMy main reasons for this are 1. I don\u0026rsquo;t trust public wi-fi and 2. I want to use pi-hole for ad blocking when I\u0026rsquo;m not at home\u003c/p\u003e\n\u003cp\u003eWireguard can be configured as a \u0026ldquo;peer-to-site\u0026rdquo; VPN tunnel as well.\nMy vpn setup let\u0026rsquo;s me jump to various machines on my network from anywhere!\u003c/p\u003e\n\u003cp\u003eI use \u003ca href=\"https://pivpn.io/\"\u003epivpn\u003c/a\u003e in a VM that\u0026rsquo;s already running \u003ccode\u003epi-hole\u003c/code\u003e to host my wireguard server.\nIt\u0026rsquo;s super easy to setup just by following the instructions on the pivpn site.\u003c/p\u003e\n\u003cp\u003eThe reason I like it is that I have a nice \u003ccode\u003ecli\u003c/code\u003e for managing wireguard configs.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003edumbledore@pihole-vpn:~$ pivpn\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e::: Control all PiVPN specific functions!\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::: Usage: pivpn \u0026lt;command\u0026gt; \u003cspan class=\"o\"\u003e[\u003c/span\u003eoption\u003cspan class=\"o\"\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::: Commands:\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -a, add              Create a client conf profile\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -c, clients          List any connected clients to the server\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -d, debug            Start a debugging session \u003cspan class=\"k\"\u003eif\u003c/span\u003e having trouble\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -l, list             List all clients\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::   -qr, qrcode           Show the qrcode of a client \u003cspan class=\"k\"\u003efor\u003c/span\u003e use with the mobile app\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -r, remove           Remove a client\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::  -off, off              Disable a user\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::   -on, on               Enable a user\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -h, \u003cspan class=\"nb\"\u003ehelp\u003c/span\u003e             Show this \u003cspan class=\"nb\"\u003ehelp\u003c/span\u003e dialog\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::    -u, uninstall        Uninstall pivpn from your system!\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::   -up, update           Updates PiVPN Scripts\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e:::   -bk, backup           Backup VPN configs and user profiles\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eWhen I\u0026rsquo;m ready to add a new client to my \u003ccode\u003ewg\u003c/code\u003e network, it\u0026rsquo;s as easy as \u003ccode\u003epivpn add\u003c/code\u003e and follow the instructions.\nThe easiest part here is that you\u0026rsquo;ll be given a QR code in the terminal that you can just scan with the client (like a smart phone) and you\u0026rsquo;ll have your wireguard config handled by the app (oh right, download the wireguard app) in no time!\u003c/p\u003e\n",
      "content_text": "\n## VPN\n\nVirtual Private Networks are a big deal, and this shouldn't be considered anything even close to a guide on using them.\nHere are just my notes and some setup for how I use [wireguard](https://www.wireguard.com/) at home.\n\n## Wireguard\n\nWireguard is an awesome peer-to-peer VPN tunnel that makes it really easy for me to get into my home network when I'm out and about.\nMy main reasons for this are 1. I don't trust public wi-fi and 2. I want to use pi-hole for ad blocking when I'm not at home\n\nWireguard can be configured as a \"peer-to-site\" VPN tunnel as well.\nMy vpn setup let's me jump to various machines on my network from anywhere!\n\nI use [pivpn](https://pivpn.io/) in a VM that's already running `pi-hole` to host my wireguard server.\nIt's super easy to setup just by following the instructions on the pivpn site.\n\nThe reason I like it is that I have a nice `cli` for managing wireguard configs.\n\n```bash\ndumbledore@pihole-vpn:~$ pivpn\n::: Control all PiVPN specific functions!\n:::\n::: Usage: pivpn \u003ccommand\u003e [option]\n:::\n::: Commands:\n:::    -a, add              Create a client conf profile\n:::    -c, clients          List any connected clients to the server\n:::    -d, debug            Start a debugging session if having trouble\n:::    -l, list             List all clients\n:::   -qr, qrcode           Show the qrcode of a client for use with the mobile app\n:::    -r, remove           Remove a client\n:::  -off, off              Disable a user\n:::   -on, on               Enable a user\n:::    -h, help             Show this help dialog\n:::    -u, uninstall        Uninstall pivpn from your system!\n:::   -up, update           Updates PiVPN Scripts\n:::   -bk, backup           Backup VPN configs and user profiles\n```\n\n\nWhen I'm ready to add a new client to my `wg` network, it's as easy as `pivpn add` and follow the instructions.\nThe easiest part here is that you'll be given a QR code in the terminal that you can just scan with the client (like a smart phone) and you'll have your wireguard config handled by the app (oh right, download the wireguard app) in no time!\n",
      "summary": "Virtual Private Networks are a big deal, and this shouldn't be considered anything even close to a guide on using them. Here are just my notes and some setup...",
      "date_published": "2022-03-12T00:00:00Z",
      "date_modified": "2022-03-12T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "homepage",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/git-worktrees-01/",
      "url": "https://pype.dev/git-worktrees-01/",
      "title": "Git-Worktrees-01",
      "content_html": "\u003ch2 id=\"git\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eGit\u003c/span\u003e \u003ca href=\"#git\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eHopefully if you write code you are using git, if not go learn the basics of \u003ccode\u003ecommit\u003c/code\u003e, \u003ccode\u003epull\u003c/code\u003e, \u003ccode\u003epush\u003c/code\u003e, and \u003ccode\u003epull request\u003c/code\u003e/\u003ccode\u003emerge request\u003c/code\u003e like\u0026hellip; right now.\u003c/p\u003e\n\u003cp\u003eAssuming you are at least familiar with git then you probably work the same way I have since I\u0026rsquo;ve been using it.\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eclone or initialize a repo\u003c/li\u003e\n\u003cli\u003echeckout a branch, \u003ccode\u003egit checkout -b my-feature\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003ework on \u003ccode\u003emy-feature\u003c/code\u003e and when ready open a PR into \u003ccode\u003emain\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003egit pull main\u003c/code\u003e then \u003ccode\u003egit checkout -b another-feature\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eetc\u0026hellip;\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eWhat if you need to switch between branches for some reason? Often I\u0026rsquo;m jumping into projects with my co-workers left and right, and I\u0026rsquo;ll have changes that I\u0026rsquo;m either working on or exploring for them.\nWhen it\u0026rsquo;s time to switch branches I think there\u0026rsquo;s more elegant ways than this but I\u0026rsquo;ve always done this:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003ccode\u003estash\u003c/code\u003e the current changes\u003c/li\u003e\n\u003cli\u003echeckout out the relevant branch\u003c/li\u003e\n\u003cli\u003ehelped out\u003c/li\u003e\n\u003cli\u003ere-checkout my original branch\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003epop\u003c/code\u003e the \u003ccode\u003estash\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eNow, that\u0026rsquo;s not awful but I think \u003ccode\u003eworktrees\u003c/code\u003e will make this nicer for a few reasons!\u003c/p\u003e\n\u003ch2 id=\"worktrees\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWorktrees\u003c/span\u003e \u003ca href=\"#worktrees\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eWorktrees are linked branches that have their own directories somewhere on your computer.\nTo checkout a branch you don\u0026rsquo;t have to worry about stashing any changes, you just \u003ccode\u003ecd\u003c/code\u003e into the directory of that branch.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe branch can be literally anywhere - it doesn\u0026rsquo;t have to be in the repo folder\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"use-case\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eUse Case\u003c/span\u003e \u003ca href=\"#use-case\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI\u0026rsquo;ve seen ThePrimeagean argue for worktrees for several reasons, see a YT video \u003ca href=\"https://www.youtube.com/watch?v=2uEqYw-N8uE\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eI\u0026rsquo;m entirely in Python at the moment, or working with projects that dont\u0026rsquo; have that kind of requirement (ie. this website).\nMy reason for wanting worktrees is 3 fold.\u003c/p\u003e\n\u003ch3 id=\"files-that-could-have-been-gitignored-but-ain-t\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFiles that could have been gitignored but ain’t\u003c/span\u003e \u003ca href=\"#files-that-could-have-been-gitignored-but-ain-t\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eI have a \u003ccode\u003e.envrc\u003c/code\u003e I put in every project, but it\u0026rsquo;s not gitignored for reasons that aren\u0026rsquo;t relevant right now\u0026hellip;\nIf I switch branches I\u0026rsquo;ll stash everything I have at the time, including my .envrc, but then if I forget to pop the stash and I move on and come back then my environment isn\u0026rsquo;t active and I have to go find the stash, pop it, cd out, and then back in and honestly\u0026hellip;. that sucks.\nWorktrees will let me have the .envrc in every branch, and if I checkout or switch to a new one, my personal branch is unaffected.\u003c/p\u003e\n\u003ch3 id=\"symlinks\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eSymlinks\u003c/span\u003e \u003ca href=\"#symlinks\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eIn my team\u0026rsquo;s \u003ca href=\"https://kedro.readthedocs.io/en/stable/01_introduction/01_introduction.html\"\u003eKedro\u003c/a\u003e workflow we keep a specific directory, the \u003ccode\u003econf\u003c/code\u003e directory at a different spot than the Kedro team has in their templates (the why is outside the scope here).\nThe way I preserve every kedro utility for my own benefit is to symlink our \u003ccode\u003econf\u003c/code\u003e to where the Kedro template expects it to be.\nBut then everytime I stash changes I lose that symlink so I either just don\u0026rsquo;t have it for the time being or I recreate it which is a hassle\nWorktrees will let me have that present and persistent on all my branches at once.\u003c/p\u003e\n\u003ch3 id=\"foo\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eFoo\u003c/span\u003e \u003ca href=\"#foo\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h3\u003e\n\u003cp\u003eBecause why not!? This workflow feels future-proof, and if my toolset changes down the line then having this worktree centric workflow might be helpful and I\u0026rsquo;m just prepping for that possibility!\u003c/p\u003e\n",
      "content_text": "\n## Git \n\nHopefully if you write code you are using git, if not go learn the basics of `commit`, `pull`, `push`, and `pull request`/`merge request` like... right now.\n\nAssuming you are at least familiar with git then you probably work the same way I have since I've been using it.\n\n1. clone or initialize a repo\n2. checkout a branch, `git checkout -b my-feature`\n3. work on `my-feature` and when ready open a PR into `main`\n4. `git pull main` then `git checkout -b another-feature`\n5. etc...\n\n\nWhat if you need to switch between branches for some reason? Often I'm jumping into projects with my co-workers left and right, and I'll have changes that I'm either working on or exploring for them.\nWhen it's time to switch branches I think there's more elegant ways than this but I've always done this:\n\n1. `stash` the current changes\n2. checkout out the relevant branch \n3. helped out \n4. re-checkout my original branch\n5. `pop` the `stash`\n\nNow, that's not awful but I think `worktrees` will make this nicer for a few reasons!\n\n## Worktrees\n\nWorktrees are linked branches that have their own directories somewhere on your computer.\nTo checkout a branch you don't have to worry about stashing any changes, you just `cd` into the directory of that branch.\n\n\u003e The branch can be literally anywhere - it doesn't have to be in the repo folder\n\n\n## Use Case\n\nI've seen ThePrimeagean argue for worktrees for several reasons, see a YT video [here](https://www.youtube.com/watch?v=2uEqYw-N8uE)\n\nI'm entirely in Python at the moment, or working with projects that dont' have that kind of requirement (ie. this website).\nMy reason for wanting worktrees is 3 fold.\n\n### Files that could have been gitignored but ain't\n\nI have a `.envrc` I put in every project, but it's not gitignored for reasons that aren't relevant right now...\nIf I switch branches I'll stash everything I have at the time, including my .envrc, but then if I forget to pop the stash and I move on and come back then my environment isn't active and I have to go find the stash, pop it, cd out, and then back in and honestly.... that sucks.\nWorktrees will let me have the .envrc in every branch, and if I checkout or switch to a new one, my personal branch is unaffected.\n\n\n### Symlinks\n\nIn my team's [Kedro](https://kedro.readthedocs.io/en/stable/01_introduction/01_introduction.html) workflow we keep a specific directory, the `conf` directory at a different spot than the Kedro team has in their templates (the why is outside the scope here).\nThe way I preserve every kedro utility for my own benefit is to symlink our `conf` to where the Kedro template expects it to be. \nBut then everytime I stash changes I lose that symlink so I either just don't have it for the time being or I recreate it which is a hassle\nWorktrees will let me have that present and persistent on all my branches at once.\n\n### Foo\n\nBecause why not!? This workflow feels future-proof, and if my toolset changes down the line then having this worktree centric workflow might be helpful and I'm just prepping for that possibility!\n",
      "summary": "Hopefully if you write code you are using git, if not go learn the basics of , , , and / like... right now.",
      "date_published": "2022-03-11T00:00:00Z",
      "date_modified": "2022-03-11T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "git",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/abstract-base-class/",
      "url": "https://pype.dev/abstract-base-class/",
      "title": "Abstract-Base-Class",
      "content_html": "\u003ch2 id=\"abcmeta\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eABCMeta\u003c/span\u003e \u003ca href=\"#abcmeta\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI don\u0026rsquo;t do a lot of OOP currently, but I have been on a few heavy OOP projects and this \u003ccode\u003eABCMeta\u003c/code\u003e and \u003ccode\u003eabstractmethod\u003c/code\u003e from \u003ccode\u003eabc\u003c/code\u003e would\u0026rsquo;ve been super nice to know about!\u003c/p\u003e\n\u003cp\u003eIf you are creating a library with classes that you expect your users to extend, but you want to ensure that any extension has explicit methods defined then this is for you!.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003efrom\u003c/span\u003e \u003cspan class=\"nn\"\u003eabc\u003c/span\u003e \u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"n\"\u003eABCMeta\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eabstractmethod\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eFamily\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emetaclass\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"n\"\u003eABCMeta\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=\"nd\"\u003e@abstractmethod\u003c/span\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\"\u003eget_dad\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\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;\u0026#34;\u0026#34;Any extension of the Family class must implement a `get_dad` method\u0026#34;\u0026#34;\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\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMyFamily\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eFamily\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\"\u003epass\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\u003cp\u003eIf I try to instantiate \u003ccode\u003eMyFamily\u003c/code\u003e I will not be allowed:\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=\"err\"\u003e❯\u003c/span\u003e \u003cspan class=\"n\"\u003emy_fam\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eMyFamily\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=\"err\"\u003e╭───────────────────────────────\u003c/span\u003e \u003cspan class=\"n\"\u003eTraceback\u003c/span\u003e \u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003emost\u003c/span\u003e \u003cspan class=\"n\"\u003erecent\u003c/span\u003e \u003cspan class=\"n\"\u003ecall\u003c/span\u003e \u003cspan class=\"n\"\u003elast\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e \u003cspan class=\"err\"\u003e────────────────────────────────╮\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e│\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003eipython\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"nb\"\u003einput\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"mi\"\u003e8\u003c/span\u003e\u003cspan class=\"o\"\u003e-\u003c/span\u003e\u003cspan class=\"n\"\u003eecb8e21ce815\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003emodule\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e                                                     \u003cspan class=\"err\"\u003e│\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"err\"\u003e╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"ne\"\u003eTypeError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eCan\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;t instantiate abstract class MyFamily with abstract methods get_dad\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\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/py-abc-meta.png\" alt=\"abcmetadata\" title=\"ABC Metadata\" /\u003e\n\u003cp\u003eIn order for me to extend \u003ccode\u003eFamily\u003c/code\u003e I have to implement the method \u003ccode\u003eget_dad\u003c/code\u003e\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eMyFamily\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eFamily\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\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eget_dad\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"bp\"\u003eself\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=\"s2\"\u003e\u0026#34;Me\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eNow everything works as expected and I can sleep well knowing no one can extend my base class without creating methods I know they need.\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=\"n\"\u003emy_fam\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eMyFamily\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\u003cspan class=\"n\"\u003emy_fam\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eget_dad\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=\"s1\"\u003e\u0026#39;Me\u0026#39;\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": "\n## ABCMeta\n\nI don't do a lot of OOP currently, but I have been on a few heavy OOP projects and this `ABCMeta` and `abstractmethod` from `abc` would've been super nice to know about!\n\nIf you are creating a library with classes that you expect your users to extend, but you want to ensure that any extension has explicit methods defined then this is for you!.\n\n```python\nfrom abc import ABCMeta, abstractmethod\nclass Family(metaclass=ABCMeta):\n    @abstractmethod\n    def get_dad(self):\n        \"\"\"Any extension of the Family class must implement a `get_dad` method\"\"\"\n\nclass MyFamily(Family):\n    pass\n\n```\n\nIf I try to instantiate `MyFamily` I will not be allowed:\n```python\n\n❯ my_fam = MyFamily()\n╭─────────────────────────────── Traceback (most recent call last) ────────────────────────────────╮\n│ \u003cipython-input-8-ecb8e21ce815\u003e:1 in \u003cmodule\u003e                                                     │\n╰──────────────────────────────────────────────────────────────────────────────────────────────────╯\nTypeError: Can't instantiate abstract class MyFamily with abstract methods get_dad\n\n```\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/py-abc-meta.png\" alt=\"abcmetadata\" title=\"ABC Metadata\" /\u003e\n\nIn order for me to extend `Family` I have to implement the method `get_dad`\n\n```python\nclass MyFamily(Family):\n    def get_dad(self):\n        return \"Me\"\n```\n\nNow everything works as expected and I can sleep well knowing no one can extend my base class without creating methods I know they need.\n\n\n```python\n\nmy_fam = MyFamily()\n\nmy_fam.get_dad()\n'Me'\n\n```\n",
      "summary": "I don't do a lot of OOP currently, but I have been on a few heavy OOP projects and this and from would've been super nice to know about!",
      "date_published": "2022-03-09T00:00:00Z",
      "date_modified": "2022-03-09T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/python-f-string-align/",
      "url": "https://pype.dev/python-f-string-align/",
      "title": "Python-F-String-Align",
      "content_html": "\u003cp\u003eI am personally trying to use \u003ccode\u003elogger\u003c/code\u003e instead of \u003ccode\u003eprint\u003c/code\u003e in all of my code,\nhowever I learned from [@Python-Hub] that you can align printouts using \u003ccode\u003eprint\u003c/code\u003e with \u003ccode\u003ef\u003c/code\u003e-strings!.\u003c/p\u003e\n\u003cp\u003eThis little python script shows how options in the \u003ccode\u003ef\u003c/code\u003e-string can format the printout.\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=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003erandom\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\"\u003evariables\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;Foo Bar Baz Bing\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esplit\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\"\u003escores\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003erandom\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esample\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"nb\"\u003erange\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mi\"\u003e1\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"mi\"\u003e11\u003c/span\u003e\u003cspan class=\"p\"\u003e),\u003c/span\u003e \u003cspan class=\"nb\"\u003elen\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evariables\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\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\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=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\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\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;With \u0026#39;varable\u0026#39; left aligned\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\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003evarable\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escore\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003ezip\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evariables\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escores\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;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003evarable\u003c/span\u003e\u003cspan class=\"si\"\u003e:\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026lt;10\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\"\u003escore\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\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\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\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=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\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\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;With \u0026#39;varable\u0026#39; right aligned\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\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003evarable\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escore\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003ezip\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evariables\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escores\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;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003evarable\u003c/span\u003e\u003cspan class=\"si\"\u003e:\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026gt;15\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\"\u003escore\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\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\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*\u0026#34;\u003c/span\u003e \u003cspan class=\"o\"\u003e*\u003c/span\u003e \u003cspan class=\"mi\"\u003e30\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=\"s2\"\u003e\u0026#34;\u003c/span\u003e\u003cspan class=\"se\"\u003e\\n\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\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003eprint\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;With \u0026#39;varable\u0026#39; center aligned\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\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003evarable\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escore\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"nb\"\u003ezip\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003evariables\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003escores\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;\u003c/span\u003e\u003cspan class=\"si\"\u003e{\u003c/span\u003e\u003cspan class=\"n\"\u003evarable\u003c/span\u003e\u003cspan class=\"si\"\u003e:\u003c/span\u003e\u003cspan class=\"s2\"\u003e^5\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\"\u003escore\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\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/py-print-align.png\" alt=\"pyprintalign\" title=\"Align f-string printing\" /\u003e\n",
      "content_text": "\n\nI am personally trying to use `logger` instead of `print` in all of my code, \nhowever I learned from [@Python-Hub] that you can align printouts using `print` with `f`-strings!.\n\nThis little python script shows how options in the `f`-string can format the printout.\n\n```python\n\nimport random\n\nvariables = \"Foo Bar Baz Bing\".split()\nscores = random.sample(range(1, 11), len(variables))\n\nprint(\"*\" * 30)\nprint(\"\\n\")\nprint(\"With 'varable' left aligned\")\nfor varable, score in zip(variables, scores):\n    print(f\"{varable:\u003c10} | {score}\")\n\nprint(\"*\" * 30)\nprint(\"\\n\")\nprint(\"With 'varable' right aligned\")\nfor varable, score in zip(variables, scores):\n    print(f\"{varable:\u003e15} | {score}\")\n\nprint(\"*\" * 30)\nprint(\"\\n\")\nprint(\"With 'varable' center aligned\")\nfor varable, score in zip(variables, scores):\n    print(f\"{varable:^5} | {score}\")\n\n```\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/py-print-align.png\" alt=\"pyprintalign\" title=\"Align f-string printing\" /\u003e\n",
      "summary": "I am personally trying to use instead of in all of my code, however I learned from [@Python-Hub] that you can align printouts using with -strings!.",
      "date_published": "2022-03-08T00:00:00Z",
      "date_modified": "2022-03-08T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/python-builtin-calendar/",
      "url": "https://pype.dev/python-builtin-calendar/",
      "title": "Python-Builtin-Calendar",
      "content_html": "\u003ch2 id=\"being-lazy\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eBeing lazy\u003c/span\u003e \u003ca href=\"#being-lazy\" class=\"heading-anchor\"\u003e#\u003c/a\u003e\u003c/h2\u003e\n\u003cp\u003eI almost exclusively use Python for my job and have been eye-balls deep in it for almost 5 years but I really lack in-depth knowledge of builtins.\nI recently learned of an awesome builtin called \u003ccode\u003ecalendar\u003c/code\u003e  that has way more than I know about for sure but I\u0026rsquo;m glad I know it\u0026rsquo;s here now!\u003c/p\u003e\n\u003cp\u003eI only needed it because I was too lazy to hard code the 7 weekdays into my module but it turns out there\u0026rsquo;s a lot of useful things like \u003ccode\u003ecalendar.isleap()\u003c/code\u003e!\u003c/p\u003e\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/builtin-calendar.png\" alt=\"builtin\" title=\"Python's builtin calendar\" /\u003e\n## Future use\n\u003cp\u003eI\u0026rsquo;m not exactly sure what will come my way where \u003ccode\u003ecalendar\u003c/code\u003e will be super relevant but like anything, I\u0026rsquo;m just glad to know it exists for when the time arises!\u003c/p\u003e\n",
      "content_text": "\n## Being lazy\n\nI almost exclusively use Python for my job and have been eye-balls deep in it for almost 5 years but I really lack in-depth knowledge of builtins.\nI recently learned of an awesome builtin called `calendar`  that has way more than I know about for sure but I'm glad I know it's here now!\n\nI only needed it because I was too lazy to hard code the 7 weekdays into my module but it turns out there's a lot of useful things like `calendar.isleap()`!\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/builtin-calendar.png\" alt=\"builtin\" title=\"Python's builtin calendar\" /\u003e\n## Future use\n\nI'm not exactly sure what will come my way where `calendar` will be super relevant but like anything, I'm just glad to know it exists for when the time arises!\n",
      "summary": "I almost exclusively use Python for my job and have been eye-balls deep in it for almost 5 years but I really lack in-depth knowledge of builtins. I recently...",
      "date_published": "2022-03-08T00:00:00Z",
      "date_modified": "2022-03-08T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/dataframe-memory-usage/",
      "url": "https://pype.dev/dataframe-memory-usage/",
      "title": "Dataframe-Memory-Usage",
      "content_html": "\u003cp\u003eI have often wanted to dive into memory usage for pandas DataFrames when it comes to cloud deployment.\nIf I have a python process running on a server at home I can use \u003ccode\u003eglances\u003c/code\u003e or a number of other tools to diagnose a memory issue\u0026hellip;\nHowever at work I normally deploy dockerized processes on AWS Batch and it\u0026rsquo;s much more challenging to get info on the dockerized process without more AWS integration that my team isn\u0026rsquo;t quite ready for.\nSo TIL that I can get some of the info I want from pandas directly!\u003c/p\u003e\n\u003ch1 id=\"dataframe-info\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDataFrame.info()\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI didn\u0026rsquo;t realize that \u003ccode\u003edf.info()\u003c/code\u003e was able to give me more info than just dtypes and some summary stats\u0026hellip;\nThere is a kwarg \u003ccode\u003ememory_usage\u003c/code\u003e that can configure what you need to get back, so \u003ccode\u003edf.memory_usage=\u0026quot;deep\u0026quot;\u003c/code\u003e will give you how much RAM any given DataFrame is using!\nAmazing tool for finding issues with joins or renegade source data files.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread_csv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;cars.csv\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\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003einfo\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ememory_usage\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;deep\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\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/df-memory-usage.png\" alt=\"DataFrame Memory Usage\" title=\"DataFrame Memory Usage\" /\u003e\n",
      "content_text": "\n\nI have often wanted to dive into memory usage for pandas DataFrames when it comes to cloud deployment.\nIf I have a python process running on a server at home I can use `glances` or a number of other tools to diagnose a memory issue...\nHowever at work I normally deploy dockerized processes on AWS Batch and it's much more challenging to get info on the dockerized process without more AWS integration that my team isn't quite ready for.\nSo TIL that I can get some of the info I want from pandas directly!\n\n# DataFrame.info()\n\nI didn't realize that `df.info()` was able to give me more info than just dtypes and some summary stats...\nThere is a kwarg `memory_usage` that can configure what you need to get back, so `df.memory_usage=\"deep\"` will give you how much RAM any given DataFrame is using!\nAmazing tool for finding issues with joins or renegade source data files.\n\n```python\ndf = pd.read_csv(\"cars.csv\")\n\ndf.info(memory_usage=\"deep\")\n```\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/blog-media/df-memory-usage.png\" alt=\"DataFrame Memory Usage\" title=\"DataFrame Memory Usage\" /\u003e\n",
      "summary": "I have often wanted to dive into memory usage for pandas DataFrames when it comes to cloud deployment. If I have a python process running on a server at home...",
      "date_published": "2022-03-07T00:00:00Z",
      "date_modified": "2022-03-07T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/adblock-coverage/",
      "url": "https://pype.dev/adblock-coverage/",
      "title": "Adblock-Coverage",
      "content_html": "\u003cp\u003eI run pi-hole at home for ad blocking and some internal DNS/DHCP handling.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003epi hole posts on the way\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eOne thing I\u0026rsquo;ve never put too much thought in is asking \u0026ldquo;how well am I doing at blocking?\u0026rdquo;\nThere\u0026rsquo;s lots of ways to measure that depending on what you care about but I just learned of \u003ca href=\"https://d3ward.github.io/toolz/adblock\"\u003eadblock tester\u003c/a\u003e.\nIt\u0026rsquo;s awesome and gave me a quick glimpse into how my pi-hole is performing on keeping my webpages clean and my DNS history private!\u003c/p\u003e\n\u003cp\u003eCredits to \u003ca href=\"https://github.com/d3ward/toolz\"\u003ed3ward\u003c/a\u003e for the awesome tool!\u003c/p\u003e\n",
      "content_text": "\n\nI run pi-hole at home for ad blocking and some internal DNS/DHCP handling.\n\n__pi hole posts on the way__\n\nOne thing I've never put too much thought in is asking \"how well am I doing at blocking?\"\nThere's lots of ways to measure that depending on what you care about but I just learned of [adblock tester](https://d3ward.github.io/toolz/adblock).\nIt's awesome and gave me a quick glimpse into how my pi-hole is performing on keeping my webpages clean and my DNS history private!\n\nCredits to [d3ward](https://github.com/d3ward/toolz) for the awesome tool!\n",
      "summary": "I run pi-hole at home for ad blocking and some internal DNS/DHCP handling.",
      "date_published": "2022-03-07T00:00:00Z",
      "date_modified": "2022-03-07T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "til",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/webservers-and-indexes/",
      "url": "https://pype.dev/webservers-and-indexes/",
      "title": "Webservers-And-Indexes",
      "content_html": "\u003cp\u003eI host a lot of services in my homelab, but they\u0026rsquo;re mostly dockerized applications so I have never had to care much about how content gets served up.\nToday I had several little concepts click into place regarding webservers, and it was a similar experience to when I started homelabing and didn\u0026rsquo;t know what a \u0026ldquo;server\u0026rdquo; was in the first place.\u003c/p\u003e\n\u003ch1 id=\"servers\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eServers\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eA \u0026ldquo;server\u0026rdquo; can have a lot of different meanings but specifically in my world it was a physical server, like my PowerEdge R610 which acts as my main \u0026ldquo;home server\u0026rdquo;.\nBut then on my server, I have other servers\u0026hellip; Jellyfin is my main media server - but that\u0026rsquo;s obviously not a hardware thing, that\u0026rsquo;s software.\nThis is certainly not a groundbreaking thing but it was a tiny piece to the puzzle that I was missing\u0026hellip; that \u0026ldquo;server\u0026rdquo; is highly contextual.\u003c/p\u003e\n\u003ch1 id=\"webservers\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eWebservers\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eSomething that confused the heck out of me when I first started down the road of having a server was what a webserver even was\u0026hellip;\nI always thought the \u0026ldquo;webserver\u0026rdquo; was just \u0026ldquo;a server that hosts a website\u0026rdquo;\u0026hellip; and yes, that\u0026rsquo;s true, but also it wasn\u0026rsquo;t true in how I understood \u0026ldquo;server\u0026rdquo;.\nIt turns out that across my 40-odd dockerized services I have at home that I must have about 40-odd web servers running, each docker container is spinning up its own!\u003c/p\u003e\n\u003cp\u003eSo something I have wanted to do for a long time is put my theology notes online for my small group to access whenever they might want\u0026hellip; it doesn\u0026rsquo;t need to be fancy or anything.\nMy issue was not knowing what to even Google. I tried \u0026ldquo;How to serve up static html\u0026rdquo; but that kind of search is for people who know what a \u0026ldquo;static\u0026rdquo; site is - I am not one of those people.\nI kept running across nginx and apache things, wordpress and other website building tools, etc.\nIn fact I only recently learned that JavaScript assets cann still be considered static so I am a complete baby in the web-dev space.\u003c/p\u003e\n\u003cp\u003eWhat I really wanted was just a simple landing page with a link to each of my \u0026ldquo;posts\u0026rdquo; which are in the form of a single html file each that I can easily export from my tiddlywiki (I have a post about tiddlywiki \u003ca href=\"/tiddly-wiki\"\u003ehere\u003c/a\u003e)\u003c/p\u003e\n\u003cp\u003eThe first win \u003ccode\u003epython -m http.server\u003c/code\u003e right in the directory I kept my html files in and that got me what I wanted functionally.\nBut then I wanted just a hair more organization\u0026hellip;\nI started looking for a way to dynamically generate an index for a directory of html files but again the verbiage of that Google search just wasn\u0026rsquo;t helping me - I didn\u0026rsquo;t want anything complicated and I knew that what I wanted had to be easy\u0026hellip;\u003c/p\u003e\n\u003ch1 id=\"the-index\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eThe Index\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eLuckily I randomly came across a SO that mentioned a Linux utility called \u003ccode\u003etree\u003c/code\u003e which does exactly what I wanted!\u003c/p\u003e\n\u003cp\u003eSee my TIL on \u003ccode\u003etree\u003c/code\u003e \u003ca href=\"'/tree'\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eSo now it goes like this:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eTake notes on X in my tiddlywiki\u003c/li\u003e\n\u003cli\u003eExport that tiddler to a html file\u003c/li\u003e\n\u003cli\u003ePut that html file into a \u003ccode\u003enotes\u003c/code\u003e folder in my github repo for small group notes\u003c/li\u003e\n\u003cli\u003eUse \u003ccode\u003etree\u003c/code\u003e to generate an \u003ccode\u003eindex.html\u003c/code\u003e of each of those files in the \u003ccode\u003enotes\u003c/code\u003e directory\u003c/li\u003e\n\u003cli\u003eUse \u003ccode\u003epython -m http.server\u003c/code\u003e to start a web server that lands me at the \u003ccode\u003eindex.html\u003c/code\u003e and now I can click through to any post!\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eIt\u0026rsquo;s not fancy but it\u0026rsquo;s functional\u0026hellip;\nThis site/blog is built with markdown and \u003ca href=\"https://www.markata.dev\"\u003emarkata\u003c/a\u003e and I wanted way more functionality in my tech notes.\nBut for this simple use case I learned a ton about \u003cem\u003ehow\u003c/em\u003e content gets served up on a webpage and my small group benefits from the easy access as well!\u003c/p\u003e\n",
      "content_text": "\nI host a lot of services in my homelab, but they're mostly dockerized applications so I have never had to care much about how content gets served up.\nToday I had several little concepts click into place regarding webservers, and it was a similar experience to when I started homelabing and didn't know what a \"server\" was in the first place.\n\n# Servers\n\nA \"server\" can have a lot of different meanings but specifically in my world it was a physical server, like my PowerEdge R610 which acts as my main \"home server\".\nBut then on my server, I have other servers... Jellyfin is my main media server - but that's obviously not a hardware thing, that's software. \nThis is certainly not a groundbreaking thing but it was a tiny piece to the puzzle that I was missing... that \"server\" is highly contextual.\n\n# Webservers \n\nSomething that confused the heck out of me when I first started down the road of having a server was what a webserver even was...\nI always thought the \"webserver\" was just \"a server that hosts a website\"... and yes, that's true, but also it wasn't true in how I understood \"server\".\nIt turns out that across my 40-odd dockerized services I have at home that I must have about 40-odd web servers running, each docker container is spinning up its own!\n\nSo something I have wanted to do for a long time is put my theology notes online for my small group to access whenever they might want... it doesn't need to be fancy or anything.\nMy issue was not knowing what to even Google. I tried \"How to serve up static html\" but that kind of search is for people who know what a \"static\" site is - I am not one of those people.\nI kept running across nginx and apache things, wordpress and other website building tools, etc.\nIn fact I only recently learned that JavaScript assets cann still be considered static so I am a complete baby in the web-dev space.\n\nWhat I really wanted was just a simple landing page with a link to each of my \"posts\" which are in the form of a single html file each that I can easily export from my tiddlywiki (I have a post about tiddlywiki [here](/tiddly-wiki))\n\nThe first win `python -m http.server` right in the directory I kept my html files in and that got me what I wanted functionally. \nBut then I wanted just a hair more organization...\nI started looking for a way to dynamically generate an index for a directory of html files but again the verbiage of that Google search just wasn't helping me - I didn't want anything complicated and I knew that what I wanted had to be easy...\n\n# The Index \n\nLuckily I randomly came across a SO that mentioned a Linux utility called `tree` which does exactly what I wanted!\n\nSee my TIL on `tree` [here]('/tree')\n\nSo now it goes like this:\n\n* Take notes on X in my tiddlywiki\n* Export that tiddler to a html file \n* Put that html file into a `notes` folder in my github repo for small group notes \n* Use `tree` to generate an `index.html` of each of those files in the `notes` directory\n* Use `python -m http.server` to start a web server that lands me at the `index.html` and now I can click through to any post!\n\nIt's not fancy but it's functional... \nThis site/blog is built with markdown and [markata](https://www.markata.dev) and I wanted way more functionality in my tech notes.\nBut for this simple use case I learned a ton about _how_ content gets served up on a webpage and my small group benefits from the easy access as well!\n",
      "summary": "I host a lot of services in my homelab, but they're mostly dockerized applications so I have never had to care much about how content gets served up. Today I...",
      "date_published": "2022-03-06T00:00:00Z",
      "date_modified": "2022-03-06T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/tree/",
      "url": "https://pype.dev/tree/",
      "title": "Tree",
      "content_html": "\u003cp\u003eI wanted a quick way to generate an \u003ccode\u003eindex.html\u003c/code\u003e for a directory of html files that grows by 1 or 2 files a week.\nI don\u0026rsquo;t know any html (the files are exports from my \u003ca href=\"/tiddly-wiki\"\u003etiddlywiki\u003c/a\u003e)\u0026hellip;\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etree\u003c/code\u003e is just the answer.\u003c/p\u003e\n\u003cp\u003eSay I have a file structure like this:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e./html-files\n├── file1.html\n└── file2.html\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eTo generate a barebones simple \u003ccode\u003eindex.html\u003c/code\u003e we can use tree as follows:\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003etree ./html-files -H \u0026quot;.\u0026quot; -L 1 -P \u0026quot;*.html\u0026quot;\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eand get the following:\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=\"cp\"\u003e\u0026lt;!DOCTYPE html\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehtml\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003ehttp-equiv\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Content-Type\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003econtent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;text/html; charset=UTF-8\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Author\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003econtent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;Made by \u0026#39;tree\u0026#39;\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003emeta\u003c/span\u003e \u003cspan class=\"na\"\u003ename\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;GENERATOR\u0026#34;\u003c/span\u003e \u003cspan class=\"na\"\u003econtent\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;$Version: $ tree v1.8.0 (c) 1996 - 2018 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro $\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eDirectory Tree\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003etitle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003estyle\u003c/span\u003e \u003cspan class=\"na\"\u003etype\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;text/css\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e\u0026lt;!\u003c/span\u003e\u003cspan class=\"nt\"\u003e--\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"nt\"\u003eBODY\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003efont-family\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eariel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003emonospace\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003esans-serif\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=\"nt\"\u003eP\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003efont-weight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enormal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003efont-family\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003eariel\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003emonospace\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003esans-serif\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eblack\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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=\"nt\"\u003eB\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003efont-weight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enormal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eblack\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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=\"nt\"\u003eA\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"nd\"\u003evisited\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003efont-weight\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enormal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003etext-decoration\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enone\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003emargin\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003epadding\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edisplay\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003einline\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=\"nt\"\u003eA\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"nd\"\u003elink\u003c/span\u003e    \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003efont-weight\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enormal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003etext-decoration\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enone\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003emargin\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003epadding\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edisplay\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003einline\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=\"nt\"\u003eA\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"nd\"\u003ehover\u003c/span\u003e   \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mh\"\u003e#000000\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003efont-weight\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enormal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003etext-decoration\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eunderline\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eyellow\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003emargin\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003epadding\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edisplay\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003einline\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=\"nt\"\u003eA\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"nd\"\u003eactive\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mh\"\u003e#000000\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003efont-weight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003enormal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003emargin\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003epadding\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\u003c/span\u003e\u003cspan class=\"kt\"\u003epx\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003edisplay\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003einline\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=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eVERSION\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003efont-size\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003esmall\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003efont-family\u003c/span\u003e \u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"n\"\u003earial\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"kc\"\u003esans-serif\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=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003eNORM\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eblack\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e  \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eFIFO\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003epurple\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eCHAR\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eyellow\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eDIR\u003c/span\u003e   \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eblue\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e   \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eBLOCK\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eyellow\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eLINK\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eaqua\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e   \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eSOCK\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003efuchsia\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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\u003cspan class=\"nc\"\u003eEXEC\u003c/span\u003e  \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"k\"\u003ecolor\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003egreen\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e  \u003cspan class=\"k\"\u003ebackground-color\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003etransparent\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=\"nt\"\u003e--\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003estyle\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ehead\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003eh1\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003eDirectory Tree\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003eh1\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ea\u003c/span\u003e \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;.\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e.\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        ├── \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ea\u003c/span\u003e \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;./file1.html\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003efile1.html\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        └── \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ea\u003c/span\u003e \u003cspan class=\"na\"\u003ehref\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;./file2.html\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003efile2.html\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ea\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\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\"\u003e0 directories, 2 files\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ehr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e \u003cspan class=\"na\"\u003eclass\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s\"\u003e\u0026#34;VERSION\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 tree v1.8.0 © 1996 - 2018 by Steve Baker and Thomas Moore \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 HTML output hacked and copyleft © 1998 by Francesc Rocher \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 JSON output hacked and copyleft © 2014 by Florian Sesser \u003cspan class=\"p\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"nt\"\u003ebr\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e                 Charsets / OS/2 support © 2001 by Kyosuke Tokoro\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ep\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ebody\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e\u0026lt;/\u003c/span\u003e\u003cspan class=\"nt\"\u003ehtml\u003c/span\u003e\u003cspan class=\"p\"\u003e\u0026gt;\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\u003cp\u003ewhich \u003ca href=\"/tree-index-example.html\"\u003elooks like this\u003c/a\u003e when you serve it up with \u003ccode\u003epython -m http.server\u003c/code\u003e\u003c/p\u003e\n",
      "content_text": "\nI wanted a quick way to generate an `index.html` for a directory of html files that grows by 1 or 2 files a week.\nI don't know any html (the files are exports from my [tiddlywiki](/tiddly-wiki))...\n\n`tree` is just the answer.\n\nSay I have a file structure like this:\n\n```\n./html-files\n├── file1.html\n└── file2.html\n```\n\nTo generate a barebones simple `index.html` we can use tree as follows:\n\n`tree ./html-files -H \".\" -L 1 -P \"*.html\"`\n\nand get the following:\n\n```html\n\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n\u003chead\u003e\n \u003cmeta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\"\u003e\n \u003cmeta name=\"Author\" content=\"Made by 'tree'\"\u003e\n \u003cmeta name=\"GENERATOR\" content=\"$Version: $ tree v1.8.0 (c) 1996 - 2018 by Steve Baker, Thomas Moore, Francesc Rocher, Florian Sesser, Kyosuke Tokoro $\"\u003e\n \u003ctitle\u003eDirectory Tree\u003c/title\u003e\n \u003cstyle type=\"text/css\"\u003e\n  \u003c!--\n  BODY { font-family : ariel, monospace, sans-serif; }\n  P { font-weight: normal; font-family : ariel, monospace, sans-serif; color: black; background-color: transparent;}\n  B { font-weight: normal; color: black; background-color: transparent;}\n  A:visited { font-weight : normal; text-decoration : none; background-color : transparent; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }\n  A:link    { font-weight : normal; text-decoration : none; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }\n  A:hover   { color : #000000; font-weight : normal; text-decoration : underline; background-color : yellow; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }\n  A:active  { color : #000000; font-weight: normal; background-color : transparent; margin : 0px 0px 0px 0px; padding : 0px 0px 0px 0px; display: inline; }\n  .VERSION { font-size: small; font-family : arial, sans-serif; }\n  .NORM  { color: black;  background-color: transparent;}\n  .FIFO  { color: purple; background-color: transparent;}\n  .CHAR  { color: yellow; background-color: transparent;}\n  .DIR   { color: blue;   background-color: transparent;}\n  .BLOCK { color: yellow; background-color: transparent;}\n  .LINK  { color: aqua;   background-color: transparent;}\n  .SOCK  { color: fuchsia;background-color: transparent;}\n  .EXEC  { color: green;  background-color: transparent;}\n  --\u003e\n \u003c/style\u003e\n\u003c/head\u003e\n\u003cbody\u003e\n        \u003ch1\u003eDirectory Tree\u003c/h1\u003e\u003cp\u003e\n        \u003ca href=\".\"\u003e.\u003c/a\u003e\u003cbr\u003e\n        ├── \u003ca href=\"./file1.html\"\u003efile1.html\u003c/a\u003e\u003cbr\u003e\n        └── \u003ca href=\"./file2.html\"\u003efile2.html\u003c/a\u003e\u003cbr\u003e\n        \u003cbr\u003e\u003cbr\u003e\n        \u003c/p\u003e\n        \u003cp\u003e\n\n0 directories, 2 files\n        \u003cbr\u003e\u003cbr\u003e\n        \u003c/p\u003e\n        \u003chr\u003e\n        \u003cp class=\"VERSION\"\u003e\n                 tree v1.8.0 © 1996 - 2018 by Steve Baker and Thomas Moore \u003cbr\u003e\n                 HTML output hacked and copyleft © 1998 by Francesc Rocher \u003cbr\u003e\n                 JSON output hacked and copyleft © 2014 by Florian Sesser \u003cbr\u003e\n                 Charsets / OS/2 support © 2001 by Kyosuke Tokoro\n        \u003c/p\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n\n```\n\n\nwhich [looks like this](/tree-index-example.html) when you serve it up with `python -m http.server`\n",
      "summary": "I wanted a quick way to generate an for a directory of html files that grows by 1 or 2 files a week. I don't know any html (the files are exports from my...",
      "date_published": "2022-03-06T00:00:00Z",
      "date_modified": "2022-03-06T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/traefik/",
      "url": "https://pype.dev/traefik/",
      "title": "Traefik",
      "content_html": "\u003ch1 id=\"traefik\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eTraefik\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eIf you don\u0026rsquo;t know about \u003ca href=\"https://doc.traefik.io/traefik/\"\u003etraefik\u003c/a\u003e and you need a reverse-proxy then you might want to check it out.\nI used to use nginx for my reverse proxy but the config was over my head, and once it was working I was afraid to touch it.\nTraefik brings a lot to the table, my main uses are reverse proxy and ip whitelisting, but it\u0026rsquo;s doing even more under the hood that I don\u0026rsquo;t have a full-grasp of yet.\u003c/p\u003e\n\u003cp\u003eI like Traefik a lot because once I get some basic config up it\u0026rsquo;s incredibly easy to add services into my homelab whether they run on my primary server or not.\nThis will not be exhaustive but I\u0026rsquo;ll outline my simple setup process of traefik and how I add services whether they are in docker or not.\u003c/p\u003e\n\u003ch1 id=\"docker\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDocker\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eIn 2022 I\u0026rsquo;m still a docker fan-boy and I run my traefik instance in a docker container.\nThis isn\u0026rsquo;t necessary but I love the portability since my homelab is very dynamic at the moment.\nAnd even if it wasn\u0026rsquo;t I\u0026rsquo;d still want to keep traefik in docker because deployment and updating are just so flipping easy\u003c/p\u003e\n\u003cp\u003eA simple docker-compose file for traefik might look like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003etraefik\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=\"nt\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;traefik:v2.4\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=\"nt\"\u003enetwork_mode\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003ehost\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=\"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=\"s2\"\u003e\u0026#34;docker-data/traefik/traefik.toml:/etc/traefik/traefik.toml:ro\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=\"s2\"\u003e\u0026#34;docker-data/traefik/config.yml:/etc/traefik/config.yml:ro\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=\"s2\"\u003e\u0026#34;docker-data/traefik/letsencrypt:/letsencrypt:rw\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=\"s2\"\u003e\u0026#34;/var/run/docker.sock:/var/run/docker.sock:ro\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# for auto-discovery\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=\"nt\"\u003eenv\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ traefik_environment_variables }}\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=\"nt\"\u003erestart_policy\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=\"nt\"\u003ememory\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;1g\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\u003ch1 id=\"ansible-deployment\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eAnsible deployment\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003e\u003cstrong\u003eI plan to have more on my homelab and Ansible on this site eventually\u0026hellip;\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eI use Ansible to deploy most of my services at home, including traefik. My main homelab repo is \u003ca href=\"https://github.com/nicpayne713/ansible-nas\"\u003ehere\u003c/a\u003e which is a fork of \u003ca href=\"https://github.com/davestephens/ansible-nas\"\u003eAnsible NAS\u003c/a\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIf you want my stuff then be sure to go to the \u003ccode\u003euser/nic\u003c/code\u003e branch on my fork\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eYou can see the ansible stuff for traefik \u003ca href=\"https://github.com/davestephens/ansible-nas/tree/master/roles/traefik\"\u003ehere\u003c/a\u003e\u003c/p\u003e\n\u003ch1 id=\"config\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eConfig\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eI use a \u003ccode\u003etraefik.toml\u003c/code\u003e as the main config and it looks something like this.\nWith ansible a lot of this is done through template variables but this is the general idea.\nThis config tells traefik what ports to listen and forward on, and gives the names to be referenced by docker labels (down below).\u003c/p\u003e\n\u003cp\u003eTraefik also has a handy web ui that with this config you can find on port \u003ccode\u003e8080\u003c/code\u003e.\nThere is a \u003ccode\u003eproviders\u003c/code\u003e section - which is one of the biggest selling points of traefik for me.\nI have a docker provider configured  and a static file.\u003c/p\u003e\n\u003cp\u003eThe docker provider lets traefik auto-discover new services that I deploy and automatically handle the routing!\nThe static file lets me easily add non-dockerized service routing, or routing to dockerized services on another host (I think traefik has an easier way to do this automatically but I don\u0026rsquo;t do it often enough to need that kind of automation).\nThen at the bottom is the SSL cert stuff.\nUsing Let\u0026rsquo;s Encrypt is pretty easy and I use Cloudflare as my DNS provider\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoints\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\u003cspan class=\"nx\"\u003eentryPoints\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eweb\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=\"nx\"\u003eaddress\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;:80\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoints\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eweb\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eredirections\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoint\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=\"nx\"\u003eto\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;websecure\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoints\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewebsecure\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=\"nx\"\u003eaddress\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;:443\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoints\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewebsecure\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etls\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=\"nx\"\u003ecertResolver\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;letsencrypt\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoints\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ewebsecure\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ehttp\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etls\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edomains\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=\"nx\"\u003emain\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;example.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003esans\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;*.example.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\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eentryPoints\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003etraefik\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=\"nx\"\u003eaddress\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;:8080\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eproviders\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=\"nx\"\u003eprovidersThrottleDuration\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;1s\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\u003cspan class=\"nx\"\u003eproviders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003edocker\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=\"nx\"\u003eexposedbydefault\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003efalse\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\u003cspan class=\"nx\"\u003eproviders\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003efile\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=\"nx\"\u003efilename\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/etc/traefik/config.yml\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eapi\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=\"nx\"\u003einsecure\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003edashboard\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003etrue\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003elog\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=\"nx\"\u003elevel\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;INFO\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003eping\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=\"nx\"\u003eterminatingStatusCode\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"mi\"\u003e0\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003ecertificatesResolvers\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\u003cspan class=\"nx\"\u003ecertificatesResolvers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eletsencrypt\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\u003cspan class=\"nx\"\u003ecertificatesResolvers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eletsencrypt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eacme\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=\"nx\"\u003eemail\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;my_email@example.com\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003estorage\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;/letsencrypt/acme.json\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nx\"\u003ecaserver\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;https://acme-staging-v02.api.letsencrypt.org/directory\u0026#34;\u003c/span\u003e  \u003cspan class=\"c\"\u003e# le staging, not prod\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=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"nx\"\u003ecertificatesResolvers\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eletsencrypt\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003eacme\u003c/span\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nx\"\u003ednsChallenge\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=\"nx\"\u003eprovider\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;cloudflare\u0026#34;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch1 id=\"providers-file\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eProviders.file\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eTo my knowledge there isn\u0026rsquo;t much to configure on the docker provider side of things until you deploy a service.\nBut the provider config file should get a little screen time here.\u003c/p\u003e\n\u003cp\u003eThe file defines a traefik http router for each service you define, in this case just \u003ccode\u003epihole\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eHere I am adding my pihole instance which is not run inside docker but is inside a VM on another host.\nI want the \u003ccode\u003eentryPoints\u003c/code\u003e to be set to \u003ccode\u003ewebsecure\u003c/code\u003e which is configured above in the http redirects.\nI want some middlewares, \u003ccode\u003eaddprefix-pihole\u003c/code\u003e and \u003ccode\u003edefault-headers\u003c/code\u003e, which I\u0026rsquo;ll explain below.\nI set letsencrypt as the cert certResolver.\nFinally I name the service \u003ccode\u003epihole\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThen in the \u003ccode\u003eservices\u003c/code\u003e section I configure where pihole is located by just giving the internal IP for traefik to route to.\nFinally I define my middlewares.\nTo get to the pihole homepage you need to use the route \u003ccode\u003e/admin\u003c/code\u003e so I want that added automatically when I go to \u003ccode\u003epihole.example.com\u003c/code\u003e so I come to \u003ccode\u003epihole.example.com/admin\u003c/code\u003e.\nAnd I wanted to restrict access to just my internal network and my wireguard network - this is done with the \u003ccode\u003edefault-whitelist\u003c/code\u003e.\nThe last thing is to configure a chain of middlewares that I called \u003ccode\u003esecured\u003c/code\u003e which is just easier for the docker labels later on.\u003c/p\u003e\n\u003cp\u003eWith this config in play though, traefik will know about the route \u003ccode\u003epihole.example.com\u003c/code\u003e and handle the ip whitelisting and load balancing for me.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003ehttp\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#region routers \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\"\u003erouters\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\"\u003epihole\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\"\u003eentryPoints\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;websecure\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\"\u003erule\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Host(`pihole.example.com`)\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\"\u003emiddlewares\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# - default-headers\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\"\u003eaddprefix-pihole\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\"\u003edefault-whitelist\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\"\u003etls\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\"\u003ecertResolver\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eletsencrypt\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\"\u003eservice\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003epihole\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#region services\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\"\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\"\u003epihole\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\"\u003eloadBalancer\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\"\u003eservers\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\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;http://192.168.1.3:80\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\"\u003epassHostHeader\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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#endregion\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\"\u003emiddlewares\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\"\u003eaddprefix-pihole\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\"\u003eaddPrefix\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\"\u003eprefix\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;/admin\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\"\u003ehttps-redirect\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\"\u003eredirectScheme\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\"\u003escheme\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003ehttps\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\"\u003edefault-headers\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\"\u003eheaders\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\"\u003eframeDeny\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003esslRedirect\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003ebrowserXssFilter\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003econtentTypeNosniff\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003eforceSTSHeader\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003estsIncludeSubdomains\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003estsPreload\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"kc\"\u003etrue\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\"\u003estsSeconds\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e15552000\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\"\u003ecustomFrameOptionsValue\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003eSAMEORIGIN\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\"\u003edefault-whitelist\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\"\u003eipWhiteList\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\"\u003esourceRange\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;10.6.0.0/24\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# wg\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;192.168.1.0/24\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# lan\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;172.17.0.0/16\u0026#34;\u003c/span\u003e\u003cspan class=\"w\"\u003e  \u003c/span\u003e\u003cspan class=\"c\"\u003e# docker\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\"\u003esecured\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\"\u003echain\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\"\u003emiddlewares\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\"\u003edefault-whitelist\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\"\u003edefault-headers\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003ch1 id=\"docker-labels\"\u003e\u003cspan class=\"heading-wear-glyph\"\u003eDocker labels\u003c/span\u003e\u003c/h1\u003e\n\u003cp\u003eNow the real magic is with Docker.\nHere is an example docker-compose file for spinning up a \u003ca href=\"https://jellyfin.org/\"\u003ejellyfin\u003c/a\u003e server that you want to expose to the world, or at least access at home with \u003ccode\u003ejellyfin.example.com\u003c/code\u003e instead of \u003ccode\u003ehttp://192.168.1.N:8096\u003c/code\u003e\u0026hellip;\u003c/p\u003e\n\u003cp\u003eI left some of the ansible variable stuff in here, but the main part to be concerned with is the \u003ccode\u003elabels\u003c/code\u003e section\u0026hellip;\u003c/p\u003e\n\u003cp\u003eWe define just a few labels to throw onto this docker container which let\u0026rsquo;s traefik discover it automatically and apply any settings necessary (like my \u003ccode\u003eipWhiteList\u003c/code\u003e).\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ccode\u003etraefik.enable\u003c/code\u003e is either True or False.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003etraefik.http.router.jellyfin.rule\u003c/code\u003e defines an http router called jellyfin and sets the url to \u003ccode\u003ejellyfin.example.com\u003c/code\u003e (if example.com was my \u003ccode\u003eansible_nas_domain\u003c/code\u003e)\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003etraefik.http.routers.jellyfin.tls.certresolver\u003c/code\u003e is set to letsencrypt since I use LE for my wildcard certs.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003etraefik.http.routers.jellyfin.tls.domains[0].main\u003c/code\u003e will just be \u003ccode\u003eexample.com\u003c/code\u003e -\u0026gt; and this should remind you of the toml file above\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003etraefik.http.routers.jellyfin.tls.domains[0].sans\u003c/code\u003e is set to \u003ccode\u003e*.example.com\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003etraefik.http.services.jellyfin.loadbalancer.server.port\u003c/code\u003e is set to jellyfin\u0026rsquo;s default http port of 8096, which tells traefik which port to point to for this service.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nt\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003ejellyfin\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=\"nt\"\u003eimage\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003elinuxserver/jellyfin\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=\"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=\"s2\"\u003e\u0026#34;{{ jellyfin_config_directory }}:/config:rw\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=\"s2\"\u003e\u0026#34;{{ jellyfin_movies_directory }}:/movies:{{ jellyfin_movies_permissions }}\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=\"s2\"\u003e\u0026#34;{{ jellyfin_music_directory }}:/music:{{ jellyfin_music_permissions }}\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=\"s2\"\u003e\u0026#34;{{ jellyfin_photos_directory }}:/photos:{{ jellyfin_photos_permissions }}\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=\"s2\"\u003e\u0026#34;{{ jellyfin_tv_directory }}:/tv:{{ jellyfin_tv_permissions }}\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=\"s2\"\u003e\u0026#34;{{ jellyfin_books_directory }}:/books:{{ jellyfin_books_permissions }}\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=\"s2\"\u003e\u0026#34;{{ jellyfin_audiobooks_directory }}:/audiobooks:{{ jellyfin_audiobooks_permissions }}\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=\"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;{{ jellyfin_port_http }}:8096\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=\"s2\"\u003e\u0026#34;{{ jellyfin_port_https }}:8920\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=\"nt\"\u003eenv\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\"\u003eTZ\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ ansible_nas_timezone }}\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\"\u003ePUID\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ jellyfin_user_id }}\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\"\u003ePGID\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ jellyfin_group_id }}\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=\"nt\"\u003erestart_policy\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=\"nt\"\u003ememory\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003e1g\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=\"nt\"\u003elabels\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\"\u003etraefik.enable\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ jellyfin_available_externally }}\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\"\u003etraefik.http.routers.jellyfin.rule\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;Host(`jellyfin.{{ ansible_nas_domain }}`)\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\"\u003etraefik.http.routers.jellyfin.tls.certresolver\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;letsencrypt\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\"\u003etraefik.http.routers.jellyfin.tls.domains[0].main\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;{{ ansible_nas_domain }}\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\"\u003etraefik.http.routers.jellyfin.tls.domains[0].sans\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;*.{{ ansible_nas_domain }}\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\"\u003etraefik.http.services.jellyfin.loadbalancer.server.port\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;8096\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\u003eAnd just like that traefik will automagically find your jellyfin container and route \u003ccode\u003ejellyfin.example.com\u003c/code\u003e to it!\u003c/p\u003e\n",
      "content_text": "\n# Traefik\n\nIf you don't know about [traefik](https://doc.traefik.io/traefik/) and you need a reverse-proxy then you might want to check it out.\nI used to use nginx for my reverse proxy but the config was over my head, and once it was working I was afraid to touch it.\nTraefik brings a lot to the table, my main uses are reverse proxy and ip whitelisting, but it's doing even more under the hood that I don't have a full-grasp of yet.\n\nI like Traefik a lot because once I get some basic config up it's incredibly easy to add services into my homelab whether they run on my primary server or not.\nThis will not be exhaustive but I'll outline my simple setup process of traefik and how I add services whether they are in docker or not.\n\n# Docker\n\nIn 2022 I'm still a docker fan-boy and I run my traefik instance in a docker container. \nThis isn't necessary but I love the portability since my homelab is very dynamic at the moment.\nAnd even if it wasn't I'd still want to keep traefik in docker because deployment and updating are just so flipping easy\n\nA simple docker-compose file for traefik might look like this:\n\n```yaml\nname: traefik\nimage: \"traefik:v2.4\"\nnetwork_mode: host\nvolumes:\n  - \"docker-data/traefik/traefik.toml:/etc/traefik/traefik.toml:ro\"\n  - \"docker-data/traefik/config.yml:/etc/traefik/config.yml:ro\"\n  - \"docker-data/traefik/letsencrypt:/letsencrypt:rw\"\n  - \"/var/run/docker.sock:/var/run/docker.sock:ro\"  # for auto-discovery\nenv: \"{{ traefik_environment_variables }}\"\nrestart_policy: unless-stopped\nmemory: \"1g\"\n```\n\n# Ansible deployment\n\n__I plan to have more on my homelab and Ansible on this site eventually...__\n\nI use Ansible to deploy most of my services at home, including traefik. My main homelab repo is [here](https://github.com/nicpayne713/ansible-nas) which is a fork of [Ansible NAS](https://github.com/davestephens/ansible-nas).\n\n\u003e If you want my stuff then be sure to go to the `user/nic` branch on my fork\n\nYou can see the ansible stuff for traefik [here](https://github.com/davestephens/ansible-nas/tree/master/roles/traefik)\n\n# Config\n\nI use a `traefik.toml` as the main config and it looks something like this.\nWith ansible a lot of this is done through template variables but this is the general idea.\nThis config tells traefik what ports to listen and forward on, and gives the names to be referenced by docker labels (down below). \n\nTraefik also has a handy web ui that with this config you can find on port `8080`.\nThere is a `providers` section - which is one of the biggest selling points of traefik for me.\nI have a docker provider configured  and a static file. \n\nThe docker provider lets traefik auto-discover new services that I deploy and automatically handle the routing!\nThe static file lets me easily add non-dockerized service routing, or routing to dockerized services on another host (I think traefik has an easier way to do this automatically but I don't do it often enough to need that kind of automation).\nThen at the bottom is the SSL cert stuff. \nUsing Let's Encrypt is pretty easy and I use Cloudflare as my DNS provider\n\n```toml\n\n[entryPoints]\n[entryPoints.web]\naddress = \":80\"\n\n[entryPoints.web.http.redirections.entryPoint]\nto = \"websecure\"\n\n[entryPoints.websecure]\naddress = \":443\"\n\n[entryPoints.websecure.http.tls]\ncertResolver = \"letsencrypt\"\n\n[entryPoints.websecure.http.tls.domains]\nmain = \"example.com\"\nsans = [\n\"*.example.com\"\n]\n\n[entryPoints.traefik]\naddress = \":8080\"\n\n[providers]\nprovidersThrottleDuration = \"1s\"\n[providers.docker]\nexposedbydefault = false\n[providers.file]\nfilename = \"/etc/traefik/config.yml\"\n\n[api]\ninsecure = true\ndashboard = true\n\n[log]\nlevel = \"INFO\"\n\n[ping]\nterminatingStatusCode = 0\n\n[certificatesResolvers]\n[certificatesResolvers.letsencrypt]\n[certificatesResolvers.letsencrypt.acme]\nemail = \"my_email@example.com\"\nstorage = \"/letsencrypt/acme.json\"\ncaserver = \"https://acme-staging-v02.api.letsencrypt.org/directory\"  # le staging, not prod\n\n[certificatesResolvers.letsencrypt.acme.dnsChallenge]\nprovider = \"cloudflare\"\n```\n\n# Providers.file\n\nTo my knowledge there isn't much to configure on the docker provider side of things until you deploy a service.\nBut the provider config file should get a little screen time here.\n\nThe file defines a traefik http router for each service you define, in this case just `pihole`. \n\nHere I am adding my pihole instance which is not run inside docker but is inside a VM on another host.\nI want the `entryPoints` to be set to `websecure` which is configured above in the http redirects.\nI want some middlewares, `addprefix-pihole` and `default-headers`, which I'll explain below.\nI set letsencrypt as the cert certResolver.\nFinally I name the service `pihole`.\n\nThen in the `services` section I configure where pihole is located by just giving the internal IP for traefik to route to.\nFinally I define my middlewares. \nTo get to the pihole homepage you need to use the route `/admin` so I want that added automatically when I go to `pihole.example.com` so I come to `pihole.example.com/admin`.\nAnd I wanted to restrict access to just my internal network and my wireguard network - this is done with the `default-whitelist`. \nThe last thing is to configure a chain of middlewares that I called `secured` which is just easier for the docker labels later on.\n\nWith this config in play though, traefik will know about the route `pihole.example.com` and handle the ip whitelisting and load balancing for me.\n\n```yaml\nhttp:\n #region routers \n  routers:\n    pihole:\n      entryPoints:\n        - \"websecure\"\n      rule: \"Host(`pihole.example.com`)\"\n      middlewares:\n        # - default-headers\n        - addprefix-pihole\n        - default-whitelist\n      tls: \n        certResolver: letsencrypt\n      service: pihole\n  #region services\n  services:\n    pihole:\n      loadBalancer:\n        servers:\n          - url: \"http://192.168.1.3:80\"\n        passHostHeader: true\n  #endregion\n  middlewares:\n    addprefix-pihole:\n      addPrefix:\n        prefix: \"/admin\"\n    https-redirect:\n      redirectScheme:\n        scheme: https\n\n    default-headers:\n      headers:\n        frameDeny: true\n        sslRedirect: true\n        browserXssFilter: true\n        contentTypeNosniff: true\n        forceSTSHeader: true\n        stsIncludeSubdomains: true\n        stsPreload: true\n        stsSeconds: 15552000\n        customFrameOptionsValue: SAMEORIGIN\n\n    default-whitelist:\n      ipWhiteList:\n        sourceRange:\n        - \"10.6.0.0/24\"  # wg\n        - \"192.168.1.0/24\"  # lan\n        - \"172.17.0.0/16\"  # docker\n\n    secured:\n      chain:\n        middlewares:\n        - default-whitelist\n        - default-headers\n```\n\n\n# Docker labels\n\nNow the real magic is with Docker.\nHere is an example docker-compose file for spinning up a [jellyfin](https://jellyfin.org/) server that you want to expose to the world, or at least access at home with `jellyfin.example.com` instead of `http://192.168.1.N:8096`...\n\nI left some of the ansible variable stuff in here, but the main part to be concerned with is the `labels` section...\n\nWe define just a few labels to throw onto this docker container which let's traefik discover it automatically and apply any settings necessary (like my `ipWhiteList`).\n\n* `traefik.enable` is either True or False. \n* `traefik.http.router.jellyfin.rule` defines an http router called jellyfin and sets the url to `jellyfin.example.com` (if example.com was my `ansible_nas_domain`)\n* `traefik.http.routers.jellyfin.tls.certresolver` is set to letsencrypt since I use LE for my wildcard certs.\n* `traefik.http.routers.jellyfin.tls.domains[0].main` will just be `example.com` -\u003e and this should remind you of the toml file above\n* `traefik.http.routers.jellyfin.tls.domains[0].sans` is set to `*.example.com`\n* `traefik.http.services.jellyfin.loadbalancer.server.port` is set to jellyfin's default http port of 8096, which tells traefik which port to point to for this service.\n\n```yaml\nname: jellyfin\nimage: linuxserver/jellyfin\nvolumes:\n  - \"{{ jellyfin_config_directory }}:/config:rw\"\n  - \"{{ jellyfin_movies_directory }}:/movies:{{ jellyfin_movies_permissions }}\"\n  - \"{{ jellyfin_music_directory }}:/music:{{ jellyfin_music_permissions }}\"\n  - \"{{ jellyfin_photos_directory }}:/photos:{{ jellyfin_photos_permissions }}\"\n  - \"{{ jellyfin_tv_directory }}:/tv:{{ jellyfin_tv_permissions }}\"\n  - \"{{ jellyfin_books_directory }}:/books:{{ jellyfin_books_permissions }}\"\n  - \"{{ jellyfin_audiobooks_directory }}:/audiobooks:{{ jellyfin_audiobooks_permissions }}\"\nports:\n  - \"{{ jellyfin_port_http }}:8096\"\n  - \"{{ jellyfin_port_https }}:8920\"\nenv:\n  TZ: \"{{ ansible_nas_timezone }}\"\n  PUID: \"{{ jellyfin_user_id }}\"\n  PGID: \"{{ jellyfin_group_id }}\"\nrestart_policy: unless-stopped\nmemory: 1g\nlabels:\n  traefik.enable: \"{{ jellyfin_available_externally }}\"\n  traefik.http.routers.jellyfin.rule: \"Host(`jellyfin.{{ ansible_nas_domain }}`)\"\n  traefik.http.routers.jellyfin.tls.certresolver: \"letsencrypt\"\n  traefik.http.routers.jellyfin.tls.domains[0].main: \"{{ ansible_nas_domain }}\"\n  traefik.http.routers.jellyfin.tls.domains[0].sans: \"*.{{ ansible_nas_domain }}\"\n  traefik.http.services.jellyfin.loadbalancer.server.port: \"8096\"\n```\n\n\nAnd just like that traefik will automagically find your jellyfin container and route `jellyfin.example.com` to it!\n",
      "summary": "If you don't know about traefik and you need a reverse-proxy then you might want to check it out. I used to use nginx for my reverse proxy but the config was...",
      "date_published": "2022-03-06T00:00:00Z",
      "date_modified": "2022-03-06T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "homelab",
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/pandas-select-dtypes/",
      "url": "https://pype.dev/pandas-select-dtypes/",
      "title": "Pandas-Select-Dtypes",
      "content_html": "\u003cp\u003eOn my team we often have to change data types of columns in a \u003ccode\u003epandas.DataFrame\u003c/code\u003e for a variety of reasons.\nThe main one is it tends to be an artifact of EDA whereby a file is read in via \u003ccode\u003epandas\u003c/code\u003e but the data types are somewhat wonky (ie. dates show up as strings, or a column that \u003cem\u003eshould\u003c/em\u003e be a integer comes in as float, etc.).\nThe best solution I think is to leverage the \u003ccode\u003edtypes\u003c/code\u003e keyword argument in which \u003ccode\u003epd.read_X\u003c/code\u003e method is used.\nHowever there is another way which is to coerce the data types at runtime instead of loadtime.\u003c/p\u003e\n\u003cp\u003eA handy way to do this is by using \u003ccode\u003epandas.DataFrame.select_dtypes\u003c/code\u003e\u0026hellip;\u003c/p\u003e\n\u003cp\u003eHere is an example of finding columns read in as \u003ccode\u003edatetime64\u003c/code\u003e and the developer would prefer to use pandas datetimes.\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread_csv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;./file-with-confusing-dtypes.csv\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\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ecolumns\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\"\u003edf\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003edtype\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;datetime64\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\"\u003edf\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_datetime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ec\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\u003cp\u003eHere is the difference in code flow between \u003ccode\u003eselect_dtypes\u003c/code\u003e and manually finding the \u003ccode\u003edatetype64\u003c/code\u003e columns:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eread_csv\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;./file-with-confusing-dtypes.csv\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\"\u003efor\u003c/span\u003e \u003cspan class=\"n\"\u003ec\u003c/span\u003e \u003cspan class=\"ow\"\u003ein\u003c/span\u003e \u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eselect_dtypes\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;datetime64\u0026#39;\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\"\u003edf\u003c/span\u003e\u003cspan class=\"p\"\u003e[\u003c/span\u003e\u003cspan class=\"n\"\u003ec\u003c/span\u003e\u003cspan class=\"p\"\u003e]\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003epd\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eto_datetime\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003edf\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003ec\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\u003cp\u003eThe difference isn\u0026rsquo;t huge but it\u0026rsquo;s the little steps in leveling up that turn script-kitty scripts into clean looking functions.\u003c/p\u003e\n",
      "content_text": "\nOn my team we often have to change data types of columns in a `pandas.DataFrame` for a variety of reasons.\nThe main one is it tends to be an artifact of EDA whereby a file is read in via `pandas` but the data types are somewhat wonky (ie. dates show up as strings, or a column that *should* be a integer comes in as float, etc.).\nThe best solution I think is to leverage the `dtypes` keyword argument in which `pd.read_X` method is used. \nHowever there is another way which is to coerce the data types at runtime instead of loadtime.\n\nA handy way to do this is by using `pandas.DataFrame.select_dtypes`...\n\nHere is an example of finding columns read in as `datetime64` and the developer would prefer to use pandas datetimes.\n\n```python\ndf = pd.read_csv(\"./file-with-confusing-dtypes.csv\")\nfor c in df.columns:\n    if df[c].dtype == \"datetime64\":\n        df[c] = pd.to_datetime(df.c)\n\n```\n\nHere is the difference in code flow between `select_dtypes` and manually finding the `datetype64` columns:\n\n```python\ndf = pd.read_csv(\"./file-with-confusing-dtypes.csv\")\nfor c in df.select_dtypes('datetime64'):\n    df[c] = pd.to_datetime(df.c)\n\n```\n\n\nThe difference isn't huge but it's the little steps in leveling up that turn script-kitty scripts into clean looking functions.\n",
      "summary": "On my team we often have to change data types of columns in a for a variety of reasons. The main one is it tends to be an artifact of EDA whereby a file is...",
      "date_published": "2022-03-05T00:00:00Z",
      "date_modified": "2022-03-05T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "python",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/tiddly-wiki/",
      "url": "https://pype.dev/tiddly-wiki/",
      "title": "Tiddly-Wiki",
      "content_html": "\u003cp\u003e\u003ca href=\"https://tiddlywiki.com/\"\u003eTiddly Wiki\u003c/a\u003e is a great note taking utility for organizing non-linear notes.\nI used it to replace my OneNote workflow and my only complaint is I don\u0026rsquo;t have an easy way to access and edit my tiddlers (posts) if I\u0026rsquo;m not at home.\u003c/p\u003e\n\u003cp\u003eThe tiddlywiki is just an \u003ccode\u003ehtml\u003c/code\u003e file with a ton of stuff above my head baked in.\nI have a barebones repo with some notes and a nice starter tiddly wiki init on \u003ca href=\"https://github.com/nicpayne713/tiddlywiki-tutorial\"\u003emy github\u003c/a\u003e.\nUsage is pretty basic\u0026hellip; Just grab the \u003ccode\u003enotebook/template.htlm\u003c/code\u003e and save it to anywhere convenient on your computer.\nI put mine on my NAS to have the security of backups since I don\u0026rsquo;t keep my tidldlywiki in a git repo (I don\u0026rsquo;t really want to look at the diff).\u003c/p\u003e\n\u003cp\u003eTaking notes in the tidlywiki is nice because it supports a format similar to Markdown although it is specific to tidlywiki.\nTiddlers (each post in the wiki) can be tagged and linked together and it\u0026rsquo;s really easy to send notes to someone by just exporting an html file and emailing it since it\u0026rsquo;ll open up by default in a broswer with all the nice formatting already apart of it.\nI was using it primarily for taking notes for a small group I lead and sending those notes each week.\nThe group benefited from nicely formatted notes and I benefited from a centralized place to keep them all that Microsoft didn\u0026rsquo;t own!\u003c/p\u003e\n\u003cp\u003eHere\u0026rsquo;s an example of the body of a tiddler with some tiddlywiki specific formatting:\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e! Static IPs on Linux\n\n//Ubuntu 20//\n\nSetting static IP on Ubuntu 20.04\n\n# Navigate to /etc/netplan\n# Open the yaml file (the name seems to be kind of random but it seems to starts with 00 or 05)\n# Change the file as below with your desired settings\n# Run `sudo netplan apply` to have changes reflected\n\n    ```yaml\n    network:\n      version: 2\n      ethernets:\n        enp0s4:\n          addresses: [192.168.1.{Static IP}/24]\n          gateway4: 192.168.1.1\n          nameservers:\n            addresses: [8.8.4.4, 8.8.8.8]\n    ```\n\n\n\n\u0026lt;img src=\u0026quot;https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/tiddlywiki-example.png\u0026quot; alt=\u0026quot;tiddlywiki\u0026quot; title=\u0026quot;A TiddlyWiki Example\u0026quot; /\u0026gt;\n\n\u003c/code\u003e\u003c/pre\u003e\n\u003cp\u003eThe \u003ccode\u003e#\u003c/code\u003e create a numbered list, \u003ccode\u003e//\u003c/code\u003e creates an italicized heading, and \u003ccode\u003e!\u003c/code\u003e creates headings similar to Markdown\u0026rsquo;s \u003ccode\u003e#\u003c/code\u003e. The differences aren\u0026rsquo;t too bad to keep in mind and what renders out is totally depenent on the tidlywiki itself.\nMy template has a nice nord feel to it, feel free to download from my github and try it out!\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI have moved away from my tiddlywiki workflow in favor of sites like this since I can git commit markdown files and build with \u003ca href=\"https://markata.dev/\"\u003emarkata\u003c/a\u003e pretty easily (credit \u003ca href=\"www.waylonwalker.com\"\u003ewaylon walker\u003c/a\u003e)\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cblockquote\u003e\n\u003cp\u003eI still use tiddlywiki for tracking some todo items and questions \u0026mdash; I may have another solution in the future\u003c/p\u003e\n\u003c/blockquote\u003e\n",
      "content_text": "\n[Tiddly Wiki](https://tiddlywiki.com/) is a great note taking utility for organizing non-linear notes.\nI used it to replace my OneNote workflow and my only complaint is I don't have an easy way to access and edit my tiddlers (posts) if I'm not at home.\n\nThe tiddlywiki is just an `html` file with a ton of stuff above my head baked in. \nI have a barebones repo with some notes and a nice starter tiddly wiki init on [my github](https://github.com/nicpayne713/tiddlywiki-tutorial).\nUsage is pretty basic... Just grab the `notebook/template.htlm` and save it to anywhere convenient on your computer.\nI put mine on my NAS to have the security of backups since I don't keep my tidldlywiki in a git repo (I don't really want to look at the diff).\n\nTaking notes in the tidlywiki is nice because it supports a format similar to Markdown although it is specific to tidlywiki. \nTiddlers (each post in the wiki) can be tagged and linked together and it's really easy to send notes to someone by just exporting an html file and emailing it since it'll open up by default in a broswer with all the nice formatting already apart of it.\nI was using it primarily for taking notes for a small group I lead and sending those notes each week.\nThe group benefited from nicely formatted notes and I benefited from a centralized place to keep them all that Microsoft didn't own!\n\nHere's an example of the body of a tiddler with some tiddlywiki specific formatting:\n\n```\n! Static IPs on Linux\n\n//Ubuntu 20//\n\nSetting static IP on Ubuntu 20.04\n\n# Navigate to /etc/netplan\n# Open the yaml file (the name seems to be kind of random but it seems to starts with 00 or 05)\n# Change the file as below with your desired settings\n# Run `sudo netplan apply` to have changes reflected\n\n    ```yaml\n    network:\n      version: 2\n      ethernets:\n        enp0s4:\n          addresses: [192.168.1.{Static IP}/24]\n          gateway4: 192.168.1.1\n          nameservers:\n            addresses: [8.8.4.4, 8.8.8.8]\n    ```\n\n\n\n\u003cimg src=\"https://cdn.statically.io/gh/pypeaday/images.pype.dev/main/tiddlywiki-example.png\" alt=\"tiddlywiki\" title=\"A TiddlyWiki Example\" /\u003e\n\n```\nThe `#` create a numbered list, `//` creates an italicized heading, and `!` creates headings similar to Markdown's `#`. The differences aren't too bad to keep in mind and what renders out is totally depenent on the tidlywiki itself. \nMy template has a nice nord feel to it, feel free to download from my github and try it out!\n\n\u003e I have moved away from my tiddlywiki workflow in favor of sites like this since I can git commit markdown files and build with [markata](https://markata.dev/) pretty easily (credit [waylon walker](www.waylonwalker.com))\n\n\u003e I still use tiddlywiki for tracking some todo items and questions --- I may have another solution in the future\n",
      "summary": "Tiddly Wiki is a great note taking utility for organizing non-linear notes. I used it to replace my OneNote workflow and my only complaint is I don't have an...",
      "date_published": "2022-03-05T00:00:00Z",
      "date_modified": "2022-03-05T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "tech"
      ]
    },
    {
      "id": "https://pype.dev/vim-auto-space/",
      "url": "https://pype.dev/vim-auto-space/",
      "title": "Vim-Auto-Space",
      "content_html": "\u003cp\u003eI ran into an issue where I had some copy-pasta markdown tables in a docstring but the generator I used to make the table gave me tabs instead of spaces in odd places which caused \u003ccode\u003eblack\u003c/code\u003e to throw a fit.\nInstead of manually changing all tabs to spaes, or trying some goofy \u003ccode\u003e:%s/\u0026lt;magic tab character\u0026gt;/\u0026lt;%20 maybe?\u0026gt;/g\u003c/code\u003e I learned that Vim has my back\u0026hellip;\u003c/p\u003e\n\u003cpre\u003e\u003ccode\u003e:retab\n\u003c/code\u003e\u003c/pre\u003e\n",
      "content_text": "\nI ran into an issue where I had some copy-pasta markdown tables in a docstring but the generator I used to make the table gave me tabs instead of spaces in odd places which caused `black` to throw a fit.\nInstead of manually changing all tabs to spaes, or trying some goofy `:%s/\u003cmagic tab character\u003e/\u003c%20 maybe?\u003e/g` I learned that Vim has my back...\n\n```\n:retab\n```\n",
      "summary": "I ran into an issue where I had some copy-pasta markdown tables in a docstring but the generator I used to make the table gave me tabs instead of spaces in...",
      "date_published": "2022-03-04T00:00:00Z",
      "date_modified": "2022-03-04T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "vim",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/stow/",
      "url": "https://pype.dev/stow/",
      "title": "Stow",
      "content_html": "\u003cp\u003eStow is a great tool for managing dotfiles. My usage looks like cloning my dotfiles to my home directory, setting some environment variables via a script, then stowing relevant packages and boom my config is good to go\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e ~\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit clone \u0026lt;my dotfiles repo\u0026gt;\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"nb\"\u003ecd\u003c/span\u003e dotfiles\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# env variable stuff ignored here\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estow zsh  \u003cspan class=\"c1\"\u003e# This will symlink my .zshrc file which is in ~/dotfiles/zsh to ~/.zshrc\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eBy default stow will stow packages up one directory from the root directory.\nIn this example the root directory is \u003ccode\u003e~/dotfiles\u003c/code\u003e and the package is \u003ccode\u003ezsh\u003c/code\u003e.\nSo the files in the \u003ccode\u003ezsh\u003c/code\u003e package will symlinked into \u003ccode\u003e~/\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003estow\u003c/code\u003e makes it easy to share dotfiles across machines, or safely experiment with config changes while always being protected by \u003ccode\u003egit\u003c/code\u003e since your dotfiles are in a git repo!\n\u0026hellip;They are in a git repo\u0026hellip; right?\u003c/p\u003e\n",
      "content_text": "\nStow is a great tool for managing dotfiles. My usage looks like cloning my dotfiles to my home directory, setting some environment variables via a script, then stowing relevant packages and boom my config is good to go...\n\n```bash\ncd ~\ngit clone \u003cmy dotfiles repo\u003e\ncd dotfiles\n# env variable stuff ignored here\nstow zsh  # This will symlink my .zshrc file which is in ~/dotfiles/zsh to ~/.zshrc\n```\nBy default stow will stow packages up one directory from the root directory. \nIn this example the root directory is `~/dotfiles` and the package is `zsh`.\nSo the files in the `zsh` package will symlinked into `~/`.\n\n`stow` makes it easy to share dotfiles across machines, or safely experiment with config changes while always being protected by `git` since your dotfiles are in a git repo!\n...They are in a git repo... right?\n",
      "summary": "Stow is a great tool for managing dotfiles. My usage looks like cloning my dotfiles to my home directory, setting some environment variables via a script,...",
      "date_published": "2022-03-04T00:00:00Z",
      "date_modified": "2022-03-04T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "bash",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/stow-target/",
      "url": "https://pype.dev/stow-target/",
      "title": "Stow-Target",
      "content_html": "\u003cp\u003eCheck out \u003ca href=\"/stow\"\u003estow\u003c/a\u003e for a brief introduction to \u003ccode\u003estow\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003eWhat if I want to stow a package somewhere else?\nBoom, that\u0026rsquo;s where \u003ccode\u003e-t\u003c/code\u003e comes in\u0026hellip;\u003c/p\u003e\n\u003cp\u003eMaybe I don\u0026rsquo;t like having my \u003ccode\u003edotfiles\u003c/code\u003e repo at \u003ccode\u003e$HOME\u003c/code\u003e and instead I want it in \u003ccode\u003e~/git\u003c/code\u003e or \u003ccode\u003e~/personal\u003c/code\u003e just to stay organized\u0026hellip;\nWell then I could have the same workflow except the \u003ccode\u003estow\u003c/code\u003e command looks like this:\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estow zsh -t ~/\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e#or\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003estow zsh -t \u003cspan class=\"nv\"\u003e$HOME\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nCheck out [stow](/stow) for a brief introduction to `stow`\n\nWhat if I want to stow a package somewhere else?\nBoom, that's where `-t` comes in...\n\nMaybe I don't like having my `dotfiles` repo at `$HOME` and instead I want it in `~/git` or `~/personal` just to stay organized...\nWell then I could have the same workflow except the `stow` command looks like this:\n\n```bash\nstow zsh -t ~/\n#or\nstow zsh -t $HOME\n```\n",
      "summary": "Check out stow for a brief introduction to",
      "date_published": "2022-03-04T00:00:00Z",
      "date_modified": "2022-03-04T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "bash",
        "linux",
        "tech",
        "til"
      ]
    },
    {
      "id": "https://pype.dev/git-ammend-no-edit/",
      "url": "https://pype.dev/git-ammend-no-edit/",
      "title": "Git ammend to a commit",
      "content_html": "\u003cp\u003eAfter carefully staging only lines related to a specific change and comitting I suddenly realized I missed one\u0026hellip; darn, what do I do?\u003c/p\u003e\n\u003cp\u003eOld me would have soft reset my branch to the previous commit and redone all my careful staging\u0026hellip; what a PIA\u0026hellip;\u003c/p\u003e\n\u003cp\u003eNew me (credit: ThePrimeagen)\u0026hellip;\u003c/p\u003e\n\u003cpre class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c1\"\u003e# stage other changes I missed\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003egit commit --amend --no-edit\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e",
      "content_text": "\nAfter carefully staging only lines related to a specific change and comitting I suddenly realized I missed one... darn, what do I do?\n\nOld me would have soft reset my branch to the previous commit and redone all my careful staging... what a PIA...\n\nNew me (credit: ThePrimeagen)...\n\n```bash\n# stage other changes I missed\ngit commit --amend --no-edit\n```\n",
      "summary": "After carefully staging only lines related to a specific change and comitting I suddenly realized I missed one... darn, what do I do?",
      "date_published": "2022-03-04T00:00:00Z",
      "date_modified": "2022-03-04T00:00:00Z",
      "authors": [
        {
          "name": "Nic Payne",
          "url": "https://pype.dev"
        }
      ],
      "tags": [
        "git",
        "tech",
        "til"
      ]
    }
  ]
}