Skip to main content

Activity for angrydutchman.eurosky.social

Active on:

Loading activity...

*tap tap tappity* is this on?

Just the usual obligatory first post where I totally could've just pasted some Lorem Ipsum but didn't because, well, I can Lorem my own Ipsum just fine.

June 28, 20261 min read

Published a repository on ATCR

blavatar

blavatar A caching proxy for Bluesky avatars with real-time cache invalidation. Overview blavatar is an HTTP service that fetches, caches, and serves user avatars from the Bluesky social network. It maintains cache freshness by subscribing to Bluesky's Jetstream for profile update events, automatically invalidating cached avatars when users change their profiles. Currently it observes only the Bluesky profile since there is no single profile lexicon (yet). This may change in the future. Features - File-based avatar caching with automatic invalidation - Real-time updates via Bluesky Jetstream subscription - Supports both DIDs and handles as identifiers - Generates default placeholder avatars for users without avatars - Scales all avatars to 128x128 JPEG format - Graceful shutdown handling Building bash make build Compile binary make install Build and install make clean Remove build artifacts For Docker: bash make release-dev Build and push dev image make release Build and push release image Configuration Configuration is done via CLI flags or environment variables: | Flag | Environment Variable | Default | Description | |------|---------------------|---------|-------------| | --jetstream-url | BLAVATARJETSTREAMURL | wss://jetstream2.us-west.bsky.network/subscribe | Jetstream WebSocket URL | | --store-path | BLAVATARSTOREPATH | ./avatars | Directory for cached avatars | | --listen | BLAVATARLISTEN | :8080 | HTTP server listen address | Usage Start the server: bash blavatar Or with custom configuration: bash blavatar --store-path /var/cache/avatars --listen :3000 Using environment variables: bash export BLAVATARSTOREPATH=/var/cache/avatars export BLAVATARLISTEN=:3000 blavatar API GET /{identifier}.jpg Retrieve an avatar by DID or handle. Examples: bash By handle curl http://localhost:8080/alice.bsky.social.jpg -o avatar.jpg By DID curl http://localhost:8080/did:plc:abcdef123456.jpg -o avatar.jpg Response Headers: - Content-Type: image/jpeg - Cache-Control: public, max-age=7200 (for real avatars) - Cache-Control: no-cache (for default placeholder avatars) Response Codes: - 200 OK - Avatar returned (real or default) - 400 Bad Request - Invalid identifier format - 500 Internal Server Error - Fetch or processing failure How It Works 1. Request: Client requests an avatar via /{identifier}.jpg 2. Resolution: Handle is resolved to DID if needed 3. Cache Check: Local file cache is checked 4. Fetch: On cache miss, avatar is fetched from the user's PDS 5. Processing: Image is scaled to 128x128 and encoded as JPEG 6. Caching: Result is stored in the file cache 7. Invalidation: Jetstream consumer listens for profile updates and removes stale cache entries License See LICENSE file for details.

ATCR5mo ago
Repository created: blavatar

Bluesky avatar proxy thing

Tangled5mo ago

An AI skeptic developer's journey with AI

## Semi-disclaimer So this will be one of those fun posts because the whole subject is polarizing and kind of spicy as all hell, so let me just say that when it comes to AI, I still believe that for things like drawing, painting, music, and the "true" creative pursuits people have, AI has no place there. However... ## What I do in daily life My current job is one where I do a fair share of developing, not the user-facing stuff, but all the things you never see. And I develop tools for *other* developers at work to do their thing. So generally speaking I'm that creepy guy behind the curtain pulling all sorts of levers, and nobody's any the wiser. Also because nobody ever comes to visit me behind the curtain... anyway moving on... So that's what I do. I've bounced between being an actual "pure" developer, system administrator, IT security consultant, and software architect over the past 30 years or so. (Yes, I'm old-ish :P). ## Why I became an AI skeptic Because I just am skeptic by nature. I refused to believe that AI would be of any benefit, considering the early versions of various models generally didn't generate code any better than a random intern pulled off the street. And a part of it required a bit of self-reflection: it scared the shit out of me. I'm at an age now where "keeping up" with developments is becoming harder, and while I can still write code, I'm not as fast at it as I used to be. Learning new things is also becoming harder, and that does add a fair bit of existential dread. And it could all just be between my ears, but hey, it was between *my* ears so... ## The great experiment My boss suggested I give it a shot, a while back, and I refused with my usual "I'm a skeptic and ..." bit. So then he dangled a free subscription to Claude with a budget for any additional usage every month. Free reign on what I want to use it for, and if I still don't like it after month 1, then fair enough. I mean, I'm Dutch, we love free stuff, so I took him up on the offer, got my account sorted, subscription sorted, Claude Code installed... and then what? ## The first steps I write a lot of code in Perl. Yes, I'm an absolute dinosaur. I'm a fair hand at Python as well, and if pushed, I will admit to knowing enough Java to be dangerous. But I don't know any Go, for example. I mean, I can read Go code and infer it's meaning, I'm just not "up" on commonly used constructs and language idioms. Anyway. So I figured I'd tell Claude to go look at a project I'd done in Perl. Nicely asked it to please summarize what it thought it was doing into a file that I could read later. Perl, on a good day, is reasonably readable. This particular project? It pulls some of the more filthy tricks for efficiency, and convenience. Like run-time class modifications, a fair bit of monkeypatching, and a lot of asynchronous work. It took 5 minutes. 5 minutes in which I was sitting there like "alright, it'll fail, heck, it's Perl code. Only perl can parse Perl". Imagine my surprise when out came a perfect summary of what it does, where functionality was contained, along with a note that it had found 2 potential bugs that I hadn't seen yet. Oh, well... shit. ### The next step I pondered a bit because I'd been toying with the idea of learning Go, and had started already, except I'd given in to my frustrations that Go doesn't let me do certain things that I'm used to being able to do, and goroutines and channels got confusing real damn fast for a bit. So I figured you know what, let me ask Claude. Could this project be ported to Go? Yes. Okay... let's go out on a limb because fuck it, I've got tokens for days, and I just outright told Claude to port my project from Perl to Go. I then went and did my shopping while Claude sat there spinning it's little wheels. What it produced? Pretty clean. Pretty well organized. Functional parity with the Perl version. Except smaller. And with a quick benchmark also a fuckton faster at doing the job. Not surprising because Perl isn't exactly known to be a speed demon. I spent the next week or so reading all the Go code, which got me pretty current with common patterns and the language in and of itself; made much easier because I could compare the Perl version (of which I know all the ins and outs) with the Go version. Hum... so I guess AI can be a tool that helps you learn a new language. Because you can do stuff like this and can do a side-by-side comparison. ### Further steps I pulled up another Perl project. At work, we send all logs from all services, all servers, and every other thing not nailed down into a RabbitMQ cluster. The project? A processor that takes all these raw entries, and shoves them through configurable pipelines that munge data, look at data, and finally route it to a different topic based exchange in RabbitMQ for all our *other* tools to have easy access to only log entries they want, need, or have access to. This means that not only are there pipeline definitions in JSON, there are comparators, operators, mutators, lookups, and of course a fair bit of glue to put this all together. The Perl version works very well, but has a realistic throughput of about 300-500 events per second. Considering we ingest, on average, 5000-6000 events per second with peaks up to 20.000 per second you can see why this particular thing burns up a fair chunk of budget, because it's just not fast/efficient enough. So I did the same thing. Had Claude summarize it. Then told it to port the whole thing to Go. Took about 20 minutes, and I took a few days to see what it had done. And again, functional parity, no issues. I ran that first version alongside the Perl version in production, and it handled about 1000-1500 events per second. The biggest bottleneck was doing an external HTTP lookup that we had in place for legacy reasons, but this is where things really sort of started "working"... ### The Eureka moment Any developer should be familiar with the Rubber Ducky thing. The rubber ducky is, well, a rubber ducky. You talk to your rubber ducky about what you're doing, and by doing so, it should bring some more "aha" moments to your thoughts. The problem is, the rubber ducky doesn't talk back. Co-workers do, but that's got it's own issues when you work fully remote. So... hm... you know Claude talks, in a way... maybe it can be my rubber ducky. I actually have a global config setting where I explain that it's job is to be exactly that. My rubber ducky. Minus the absolute creepy sycophant suck-up language it uses from time to time. Oh, and a rubber ducky that is allowed to write code. Anyway. So having done that, I asked it to suggest ways to increase efficiency, considering it came from a single-thread synchronous Perl application. It suggested a few things. Again asked it to write it to a file so I could look at what it wanted to do. And it made perfect sense. So I went ahead and implemented it myself. Then I asked Claude to look at it, and it decided there was nothing more it could do. Yay. I did a good! Deployed it, and now we're up to 2000 requests/sec handled. But rubber ducky... so I started playing "what if I do X instead of Y". "How about dropping that external lookup and doing Z instead". Over the course of a day, I now had a clear idea of what was required to get things working even better. And then I did something a lot of you will consider filthy. I asked Claude to handle the implementation. It spent another 20 minutes (and a fair chunk of my boss' money) doing it. This time I took a few hours to review what it had done, and signed off on it. It's been running in production now for a few weeks. Instead of the 8-12 instances we needed to run of the Perl version (to handle peaks, because you don't want to wake up to a backlogged-by-2-million-events queue) we're now running 2 of the Go version. Then I retired a couple of nodes in our workloads cluster because we didn't need them any longer. ## So what came next? More of the same, really. I've been letting Claude look at old Perl code, and porting it to Go. I also came to realize that, yeah, okay, I actually like *thinking* and *designing* things way more than I do writing code. I've always considered the code a tool that lets me get something done, and the something was always more important to me. And this works surprisingly well, with AI assistance. I've been working on a side project for a while that I wrote an entire design document for. Then let Claude go over that, and played 20 questions-and-suggestions with it. I now have a document that defines something I've wanted to build for ages, and in some places surpasses my original idea on how it should be done. I can hear people in the back screaming about vibe coding now... And yes. Maybe it is vibe coding. I'm going to let Claude scaffold out the entire project, and if I see a part of it that I want to work on myself I will, but chances are I'll use Claude to generate a bunch of the code. Does it go into production right away? Well, no. Of course not, that'd be insanity. As with previous projects, I feed Claude a description of the most common security vulnerabilities in web applications (path traversals, XSS, CSRF, SQL injections) and security practices (never, ever, ever trust a user to enter proper data, validate all the things, fail early and loudly, etc.) and ask it to follow all of that. And then I do an audit myself (I used to be an ISO27001 lead auditor) and so far? So far it's been actually doing a good job. But you do need to tell it to do that... ## A few takeaways Claude makes a good rubber duck. It also makes a good tool to make writing code (and boring things like documentation) easy. It does, however, need to be very explicitly told what it can and can't do - such as the security related things mentioned above. It also must be told explicitly how you are planning to scale a project (from the deployment end of things), how it's going to be deployed, and what other services you have available. For instance in a previous for-work project it decided that it was going to implement it's own encrypted-at-rest key and certificate storage. Until I told it that, at work, we use HashiCorp Vault for secrets management. It then decided to implement it's own encrypted-at-rest key and certificate storage using Vault's transit encryption. It required a few back-and-forths to get it to just use Vault's key/value store (which is encrypted at rest) to do what it needed to do. So I guess one takeaway is that you *need to know what you're doing* if you're going to have happy fun time with AI. I guess that can be summarized as "garbage in, garbage out". There is also the problem, with Claude at least, that it seems to "forget" what has been done before, and it will end up hallucinating something, and it will absolutely double down on it. Or cases where you just end up having a vicious circle with it. I recall having it make a change that instead of reading a file it could get it from Consul KV, and it implemented that in a wacky way , that I vetoed, and it went right back to "oh I'll just read it from file"; where you tell it no, you want to get it from Consul KV and it implements it the exact same way. Rinse, repeat, until you tell it to just stub it out and you end up doing your own implementation. ## Final thoughts Am I still a skeptic? Kind of. I've tried Claude, and while I think Claude does a pretty good job at taking away the more boring parts (writing code, documentation), and being a good rubber ducky, it's incredibly easy to fall into the trap where you trust it with everything. I'm paranoid, so my global prompt is about 2 pages long and consists mostly of things it shouldn't do at all, or should only do with explicit permission given. I guess I'm no longer truly skeptical about AI's capabilities, I've now migrated my skepticism into the people side of things, where I'm skeptical that people can actualyl use AI in a way where it's not the AI driving, and you're just being a passenger. You need to be in control. The AI is not the driver, you are. The AI is perhaps at worst a backseat driver, at best a passenger. ## Final final thoughts. Written at 6am after not enough sleep and not enough coffee, so any typo's or grammar mistakes I blame on that. Also these are just my own experiences and thoughts, so take it with a grain of salt maybe. And last but not least, I'm not going to engage in discourse about going to the dark side, or about how AI is the devil, or the question as to whether or not I'm vibe coding or making AI slop; after 30 years of writing code in a professional capacity I think I'm able to review things properly ;)

app.greengale6mo ago
Repository created: tangled-core

Monorepo for Tangled

Tangled6mo ago
Repository created: gott

A go template renderer based on Perl's Template Toolkit

Tangled6mo ago
Repository created: plcbundle-rs

High-performance implementation of plcbundle written in Rust

Tangled6mo ago
Repository created: plcbundle

A Transparent and Verifiable Way to Sync the AT Protocol's PLC Directory

Tangled6mo ago

Commented on a post

All it needs is some HTMX and it'll be great! :D

Nooki8mo ago

Commented on a post

Community export. Just a button that lets anyone grab the community metadata; as long as all posts and replies are on users' PDSes then you can use the metadata to reconstruct things, and all you need to do then is do a backfill for all the old records. I mean you could do this already with pdsls.dev and some copy/paste but having it under a nice little button would be cool.

Nooki8mo ago

Commented on a post

I mean, I'll take shots at PHP any day of the week because reasons but if it works, it works. (I'm a Perl guy myself :P)

Nooki8mo ago
Repository created: carproto
Tangled8mo ago
Repository created: multiformats
Tangled9mo ago

I wish people would stop trying to reinvent the wheel except square this time...

Okay so this could've just been a thread on Bluesky but I hate making threads so y'all get a post instead. Because why not. Fight me. This post does call out a particular module (and then by extension it's author), but they kind of have it coming. ## Adventures in Markdown parsing So for a project I'm working on I decided that instead of typing HTML for all my text content I'd just type Markdown. Because, again, why not? It's convenient, portable, and I'm pretty sure someone in the Perl ecosystem has written a parser for it already. And for sure, MetaCPAN shows a fair number of Markdown related modules. Some of these are part of other larger modules though and chipping them out (and maybe getting them to work) seemed weird. But then... ### Enter `Markdown::Parser` So I saw this. Since most parsing modules follow this naming scheme (`HTML::Parser`, `XML::Parser`, need I go on?) I figured alright, cool we'll just use that. And then it blew my mind because it pulled in a few dependencies, by the same author. #### `Module::Generic`... what the fuck is this thing smoking? So this is the first of a few God-class type things. It's a module. A generic module. That has, itself, a stupid amount of dependencies because I've always wanted to be able to require a module that not only handles JSON serialization but also does terminal color output, HTTP, and a variety of other things because **of course** this makes perfect sense! God forbid you'd ever want to step away from using the `JSON` module and want to use `JSON::XS` (because faster) instead, nope, you get the god object. Which of course fails to build on the CI/CD pipeline because *color tests require a fucking terminal with color support and a pseudo-TTY doesn't have that*. #### `Promise::Me` also makes an apperance... There's a perfectly fine Promise module in my web framework of choice, so why do I need this? I don't know. But apparently it's a requirement for Module::Generic. Which is a requirement for a Markdown parser. Which in my mind makes no sense. Also it implements promises using fork. Yeah. ### But that's not even the worst of it So I have a file of markdown content. Let's say this particular blog post in it's raw format. It takes **over 6 seconds** to parse the Markdown and spit out HTML. I've actually used this on a privacy policy document (they're wordy AF), and that sat there for **20+ seconds** before it generated anything. And then, instead of just going "markdown goes in, HTML comes out", no, no of course the HTML needs to be properly wrapped with a `<html><head><body>` section because **obviously** I'm not going to use that Markdown somewhere else, noooo that'd be too simple. ### So then you look again and find `CommonMark` `CommonMark` is just a set of bindings on the C `libcmark` library. It has a lot of additional functionality if you really want to mess with parse trees and other such things, but the method I'm interested in is simply `markdown_to_html` - you feed it Markdown, and HTML comes out! Without any additional bits added. And it's fast. That privacy policy document took under a second to generate. So why didn't I use that right from the start? Well, the module naming wasn't entirely obvious, and it showed up low down in the search results. But that's something I can't change. ## The point of this rant So. A long story short: I fucking hate it when people reinvent a perfectly fine wheel, and then decide it needs to be square. I especially hate God-objects that will do everything including brewing coffee. And it annoys the everloving piss out of me that people still seem to think following `systemd`'s trend of "everything must be reinvented here" is a *brilliant* idea. And I guess the point is I just wanted to rant. Rant over. For now.

Whitewind1y ago

Nomadic cloudy adventures in PDS land

Or how I managed to get the PDS to run as a Nomad job. With a few detours. And some cursing. Actually a lot of cursing. ## A warning: don't do what I did, kids! To preface this post, I messed up and managed to delete my *actual running PDS*'s data directory by way of fat-fingering the delete button on the cloud volume I was using for it. This means if you have no *rotationKeys* set on your did:plc you are straight out of luck, since the keys required to sign the operation to point your old identity to a new PDS are... as you guessed it, in that data directory. So set up an additional *rotationKey* that you save in a safe place! ## Assumptions! I'm assuming you can figure out what Nomad, Consul, and Vault are. In case you have no idea what I'm talking about, feel free to read this post anyway but you may have to do a little on-a-tangent reading :) ## Nomad. and why the hell am I doing this? Okay, good question! The reason I wanted to do it is because on the one hand, why not, and on the other hand, it'd be convenient for a project I've been tinkering on (slowly) for a while now. The end goal is to be able to offer EU based hosting for people to spin up their own PDS with a few clicks and a few questions. Also to run an EU based PDS where people can migrate to if they don't want to be on Bluesky's PDS's anymore. Anyway. Managing all that by hand is not a viable solution, and since at $orkplace we use Nomad in a professional capacity, I figured what the heck, I'll just do that for my happy fun time hobby projects too because at least I can re-use a lot of the tooling I've built over the years to benefit from it. I'll add that it's not just Nomad, but Consul and Vault are also very much present. ## The Cluster(tm) The current cluster runs the Nomad servers, Consul servers, and Vault servers. Nomad is for container orchestration, Consul for service discovery and configuration key/value store, and Vault is for secrets management (i.e. things stored in there are encrypted, and it allows my Nomad jobs to "not know a goddamn thing"). For now there are 2 additional nodes that act as Nomad agents, where workloads are scheduled. One ingress node, and one node that runs workloads. This is supported by a bunch of S3 compatible object storage, cloud volumes, and a load balancer that does all the TLS termination before forwarding all traffic to the ingress node. I use Hetzner Cloud for all hosting, because really, AWS is just not something my wallet will support. And it's in the EU. And it's not an American company. And for many reasons that makes me happy. ## Ingress For ingress, I use Traefik - I know the PDS distribution as we get it from Bluesky uses Caddy, but Traefik is what I've been using for a while now and I know how to get it to do what I want. It also lets me do some fun shenanigans behind the scenes that I'm experimenting with (think "walled garden"). ## The PDS docker image When you download the PDS installer from Bluesky, it actually sets up 3 containers, and does some wonky things with systemd. In Nomad, i don't need 3 containers (pds, caddy, watchtower), I just need the pds container. Fixed easily enough, just look at the *compose.yml* and pull out the definition. One thing it lacks is a port mapping, so for those of you wondering, the PDS image listens on port 3000. ## Making it work Nomad operates with *jobs* - a job is comprised of some meta information, and 1..n task groups. Each task group can have 1..n tasks in it. All tasks in a task group are allocated to the same agent (physical server). Sounds complicated, but isn't. And in this case fully unnecessary to dive into that deeper since the PDS job is a 1-group-1-task type thing, so simple. For sheets and geegles I've included the job spec (with comments) below: ``` job "pds-peedee" { # there can be multiple datacenters in a region, this indicates I don't care where it ends up datacenters = ["*"] namespace = "default" type = "service" # this will ensure this job only runs on a Nomad agent of the specified class. If I had 324 of these, it'd pick the one that would satisfy # the schedulers' preference of where to put it. constraint { attribute = "${node.class}" value = "pds_4g_2c" } group "pds" { # this isn't really used when you have only 1 instance of a task group running, but # it does mean if I fat-finger the job spec into not working, it will revert back to a previous one. It also won't consider # the tasks in this group healthy unless they've been reported as such for at least 10 seconds, and they have 1 minute # to actually get there. update { max_parallel = 1 canary = 1 min_healthy_time = "10s" healthy_deadline = "1m" auto_revert = true auto_promote = true } # this is a Container Storage Interface sourced volume; this is basically a Hetzner cloud volume that I'm pulling in volume "storage" { type = "csi" source = "pds-peedee" attachment_mode = "file-system" access_mode = "single-node-writer" read_only = false } # network settings; bridge mode creates a little tiny slice of private network on the nomad agent. the DNS servers are pointing # to Docker's bridge IP so that I can use Consul's DNS (which I bound there). network { mode = "bridge" port "pds" { to = 3000 } dns { servers = [ "172.17.0.1" ] } } # here's where the fun begins; this is a service definition that gets # registered in Consul with a health check, and various tags. The tags are used # by Traefik to decide what requests to route where. service { port = "pds" check { name = "PDS port listening" type = "http" path = "/xrpc/_health" interval = "60s" timeout = "10s" } tags = [ "traefik.enable=true", "traefik.http.routers.pds-peedee.entrypoints=https", # the almighty routing rule: this lets me host another app or site on https://peedee.es - only pass # to the PDS the things that concern the PDS basically. Or render unto... you know the rest. Something # with Caesar. "traefik.http.routers.pds-peedee.rule=(Host(`peedee.es`) || HostRegexp(`^.+\\.peedee\\.es$`)) && (PathPrefix(`/xrpc/`) || PathPrefix(`/.well-known/`) || PathPrefix(`/oauth/`) || PathPrefix(`/@`))", ] } task "pds" { driver = "docker" # here we say we need to use the PDS port (which above was specified that the container listens on port 3000, and Nomad # will allocate a port on a chosen (private) network interface dynamically. # force_pull will ensure that any time we restart this we get the latest and greatest 0.4 version config { ports = [ "pds" ] image = "ghcr.io/bluesky-social/pds:0.4" force_pull = true } # this sets up a Workload Identity JWT that allows us to access our Vault vault {} # this actually maps the above included CSI volume into the container filesystem volume_mount { volume = "storage" destination = "/pds" read_only = false } # well, instead of an env file we get an env block. Some values maybe redacted. env { PDS_HOSTNAME="peedee.es" PDS_DATA_DIRECTORY="/pds" PDS_DID_PLC_URL="https://plc.directory" PDS_BSKY_APP_VIEW_URL="https://api.bsky.app" PDS_BSKY_APP_VIEW_DID="did:web:api.bsky.app" PDS_REPORT_SERVICE_URL="https://mod.bsky.app" PDS_REPORT_SERVICE_DID="did:plc:ar7c4by46qjdydhdevvrndac" PDS_CRAWLERS="https://bsky.network" LOG_ENABLED="true" PDS_CONTACT_EMAIL_ADDRESS="ping@peedee.es" PDS_EMAIL_FROM_ADDRESS="noreply@peedee.es" PDS_INVITE_REQUIRED="true" PDS_BLOB_UPLOAD_LIMIT="52428800" PDS_BLOBSTORE_S3_BUCKET="supersecretbucketname" PDS_BLOBSTORE_S3_REGION="fsn1" PDS_BLOBSTORE_S3_ENDPOINT="https://fsn1.your-objectstorage.com" PDS_BLOBSTORE_S3_FORCE_PATH_STYLE="true" PDS_PRIVACY_POLICY_URL="https://peedee.es/d/privacy/" PDS_TERMS_OF_SERVICE_URL="https://peedee.es/d/terms/" PDS_HOME_URL="https://peedee.es/" } # this is where all those juicy, juicy secrets come in from Vault. A template is rendered using values taken from # Vault's secure key/value store, and the "env=true" bit at the end means the rendered result is merged # with the env block above. template { data = <<EOH {{ with secret "secret/data/application/pds-peedee" }}{{with .Data}} PDS_JWT_SECRET={{ .data.jwt_secret }} PDS_ADMIN_PASSWORD={{ .data.admin_password }} PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX={{ .data.rotation_key_hex }} PDS_EMAIL_SMTP_URL={{ .data.smtp_url }} PDS_BLOBSTORE_S3_ACCESS_KEY_ID={{ .data.s3_access_key_id }} PDS_BLOBSTORE_S3_SECRET_ACCESS_KEY={{ .data.s3_secret_access_key }} {{ end }}{{ end }} EOH destination = "${NOMAD_SECRETS_DIR}/pds-secrets.env" env = true } # and here I tell it how much memory and CPU it can use. Memory is actually an enforced limit, # whereas CPU isn't. resources { cpu = 1024 memory = 1024 } } } } ``` ## Caveats and other such things This is of course hilariously over-complicated if you just want to run your own PDS for yourself. I also have the routing rule from hell in this job because I need to be able to share the same domain for another website, *and* the PDS. If it's dedicated to just your PDS the routing rule becomes much simpler. ## Closing remarks Since I wrote this at 4:30am I'll apologise if it doesn't seem to make much sense. But I wanted to write this anyway because hey why not words words words! Anyway. Mostly just put into a blog post to demonstrate how to run a PDS in a Nomad job. Might help if you ever (god have mercy on your soul) want to do it with Kubernetes. The configuration of the PDS is also still subject to various tweaks, so don't just copy/paste what's there. Actually don't do that anyway because you aren't me. I think.

Whitewind1y ago