Shopping list again. We've been through this a few times so we should all know the words by now.

Lists are our aggregation roots. Lists have a name, a unique id, a list of users with access, and a list of items. Items have a name and a status. Items are distinguished by name (ignoring case).

Commands are CreateList, RenameList, DeleteList, AddItem, RenameItem, SetItemStatus, DeleteItem, ShareList, AcceptList.

Clients handle commands from the ui locally and pass events to the backend to be fanned out to other clients. The backend stores all events and sends new events to a client when it connects.

Backend stores events and a projection of the current state of lists that's built at app start time and updated as events arrive. Database is a single table of list id, timestamp, event blob.


I've spun up a new VM to host the osric.uk stuff (so pulling it off wepiu in the cloud). I still haven't brought myself to renting a Hezner machine - they offer a really good deal for what you get, but it's still £30/month.

(I guess I haven't got used to £30/month being affordable. Hubbies new pc was about 2.5k£, or just under 7 years hosting at that price).

If i got a new machine (and it is a machine, not a VM), I'd probably set up Wireguard so it's connected to wepiu (and the house machines, just to make life easier) and keep DNS pointing at wepiu (Mythic have a much better rep than Hetzner).

So this month, I'm going to move the osric.uk stuff to a VM on one of the home machines to prove the theory, and if it works I'll buy the Hetzner machine at the start of February and copy stuff over.

(I'll probably leave the home VM setup as a staging/test server, I've got a spare domain i can use for it, and it might be fun to set up automated ui tests).

It's a shame I can't take advantage of Hetzner's unlimited bandwidth, but too many services are still ipv4 only, and I don't think I'm anywhere close to the limit at mythic.


In the spirit of "got to start somewhere":

Sheep wander the world eating. Sometimes, for no obvious reason, they will panic and run around for a while, and then calm down and start eating again. Sheep that are eating mostly stay still, but will absently move forwards now and then. Sheep that are panicking move much faster, and occasionally change direction. Sheep will try to avoid obstacles, and will stop moving (even if they are panicking) if they don't have anywhere to go.

Sheep can perceve other nearby sheep, and will tend to panic if a neighbour is panicking (with the chance increasing as the panicking sheep is closer). However, once a sheep starts to panic, it pays much less attention to it's neighbours.


That's shopping thrown up on a server (on ''anat", the new hosting VM). Not even half finished, but going well. Will try to get a bunch more done tomorrow (before work eats up my energy).


Looks like I can use podman secrets to stash the NuGet config file and inject it into the build containers. (That's a far cleaner plan that the current "copy it into the right folder and hope the gitignore for is up to date).

Runtime secrets are already managed by storing them in a file and mounting the path (which is what this new command does anyway).


I want to script the container build somehow - especially if/as I'm going to need --secret arguments now. Since I'm Windows based now (weird, huh?), I should use poweshell, I just have to be careful not to accidentally write make by mistake.

(make doesn't do what I actually want, which is too run a program to get the date of a target. e.g., my source folder is dated last Tuesday, what's the date on the latest image? make wants me to touch image as part of the build but that's not source of truth (although I don't know how to get the build date of an image anyway))

But since poweshell runs under Linux anyway, a two line ppdman build, podman push script isn't the end of the world. If I'm careful, I only need one copy and it can pick up the tag from the source folder.

(Ok, now I want a 'build all' version that I can run after I've updated the shared project to bump version numbers in dependent projects, build, push, and restart them.)

(That's not so "out there", right? Also, I can add "increase project version on build" code, and maybe even "bump minor" and "bump major" options)


I've dumped self hosted elastic on favour of Grafana Cloud. Elastic is just to heavy, and I can't be bothered to set up the Grafana stack at home (especially as the logs and traces both need weird databases and/or S3 compatible storage).

On the plus side, I've written a few poweshell functions for building and publishing container images, for updating .net dependencies, and for automatically bumping .net project versions. This means I could(and hopefully will) be able to tweak something in the shared web project, and automatically update and publish dependant projects.


Turns out podman secrets don't work the way I expected, in that there seems to be a different namespace for build secrets compared to runtime secrets. This means that injecting nuget.conf isn't as easy as I'd hoped.

However, it looks like copying the file into the podman machine should solve the problem (and i can probably wrap that in a script, why not?)

(Putting my powershell module into my one drive was a good idea, now I just need to do something similar for my bashrc in Linux)


NuGet protocol works with basic auth, and my auth service is OIDC, so there's a conflict there.

I don't want to give apps other than auth access to the auth db, so NuGet needs to make some kind of call to auth.


"Scope" is a red herring, at least for my current use case.

Scope is the client asking the server for access to a type of resource.

Roles are bundles of permissions that the client should interpret in a way that the server expects (e.g., permission to "read files" shouldn't be used to access telescope controls)

How complex do I want to make this? I've got a picture in my head of individual endpoints each with their own set of permissions, but on the other hand it's me, husband, and a couple of friends, and the last three just want t sleeve where they can share files.

That gives three roles - me, hubs, and the other two.

[A short time passes]

I've thought of a couple more roles: an explicit "anyone including not logged in", and read only NuGet downloader role (and hopefully a read only container downloader role once I've figured out how to hook in that subsystem).

Maybe I'm taking about policies here rather than roles? I get an "all access" policy, hubs (and probably the Dr Who Boyz) get file share access, and the policy for automated downloaders. Then all I'm left with is how to map an account to a policy (maybe that's what roles are?)


Yay! I've got the new authorisation policy setup working!

As you may already know (can't remember if I've actually said), I'm on a mission to split up osric.uk into a bunch of smaller sites/packages, to make it easier for me to mess with part of the site without impacting the rest of the site.

On the critical path is getting authentication and authorisation working the way I want. I've setup an OIDC server (Thanks Ory Hydra!) and that's working well for authentication ("Who are you?" checks), but I want to be able to give out accounts without giving away the homeworld, and that's what authorisation is for.

In theory it's easy, asp core has policy authorisation built in. In practice, because I'd made a couple of mistakes, it's taken much longer than expected.

For the record, the mistakes were:

  • Not including the Razor page model in the Razor page, so the framework couldn't pick up the attribute that set the policy
  • Using "Is in role 'User'" as a proxy for "Is the user logged in", when the user role doesn't exist

I've even got "forwarding basic auth" working, where an app can forward the Authenticae header to the auth app, so only the auth app needs to access the auth database.

All together pretty chuffed!


Hello babe. I've started taking the Metformin and got past the initial side effects (bad stomach ache), although it might be messing with my sleep.

I do feel better this week (five days in, maybe?), nothing I can put my finger on, but definitely better. (Still all fatigued though).

Hubs has given me the Fitbit Charge 6 they bought themselves a couple of months back. I've had it on for less than a day so far, no initial thoughts further than it's easy to wear.

$WORK is being stupid, we're coming to the end of a project and there's not enough work for the team. This wouldn't be a problem, except I don't feel right getting on with $PROJECT stuff on work time.

Overall, doing ok.



Comparing C# and JavaScript Syntax

Simple statements

The syntax of simple statements are identical.

for ([initializer]; [test]; [increment]) {
  ...
}

[initializer]
while ([test]) {
  ...
  [increment]
}

if ([test]) {
  ...
} else if ([test]) {
  ...
} else {
  ...
}

Variables

JavaScript is dynamic typed, meaning that any given variable can hold any type of value. C# is static typed, so variables get a type at creation time and can only accept values of the correct type.

Variables in JavaScript are declared with one of three keywords: var, let, const. C# also uses var to declare variables, but they have different meanings.

var in C# tells the compiler to infer the type of the variable from context, e.g. in the statement var x = "string";, x can only be a string type.

var in JavaScript creates a variable that can be used anywhere in a function (the variable definition is hoisted to the start of the enclosing function).

Comparison

Comparison of values is one of the places where JavaScript and C# diverge due to the static/dynamic natures of the languages.

Because JavaScript can only know the type of a value at runtime it relies on type coercion to force values to be the same type. It is therefore safe in JavaScript to compare e.g. a string and a number, because the runtime will coerce the number to a string. However, this leads to unexpected results, so modern JavaScript provides an === operator that does not coerce the values, and so different typed values will not match (e.g., "1" == 1 is true, but "1" === 1 is false).

Functions

Calling a function is mostly the same in both languages: result = function(arg1, arg2). Of course, in C# result must be the correct type (or the code won't compile).

C# won't let you call a function with the wrong number of arguments, or arguments of the wrong type. JavaScript will pass along to the functions any arguments you provide, filling in missing arguments with 'undefined' and adding any extra arguments onto the end of the formal parameters. (You can access extra arguments through the arguments array).


I don't think c# is the right language for an activity pub server. There's to much "fuzzy" - properties that could be a list or a string, or one of a bunch of broadly similar types that aren't really sub-types.

If I had a JavaScript engine I was happy with, that could work, but I don't.

F# feels like it's probably a good fit, and this is probably the right size of project to start with, but I'm not sure. F# feels like a "write only" language (like Scala) in that it's very dense and the compiler picks up much of the slack, so getting a hook into understanding code is tickets.

I'm tempted by C. I can write a basic JSON parser (which knows that it's not going to have to deal with extreme cases), but even so it's the same old C problem (that there's just so much more to type)


Of course, even in c# I don't need to parse JSON into a type right away, although I do still need to cope with the "can be an array or string" (and "object or reference") cases.

I could dereference links at parse time, although I'd need to be careful of depth (and cache timeouts).

I still can't believe that the type tree has two roots (Object and Link).


I need to keep the wire format and the database schema separate in my head so I don't keep running into the "link or object"/"list or string" stuff.

The database can store normalised data, and then separately we can stringify and parse as needed, maybe using intermediate types of it makes life easier.

That should push much more of the complexity to the edge, where it belongs.


New thought: Since the protocol is based on "Actions", do I want to do this as an event sourced thing? Keep current state in memory, write events to disk ("Alice Followed Bob", "Bob Posted a thing", only using ids instead of names) and rebuild state at start time?

Doesn't really solve the "What types do I need" problem, but it makes the database schema easier: an event table with (id, verb, id) triples, and an object table with (id, bob) pairs.

Remote objects (anything I'm not hosting) need to have cache properties along with everything else: last updated, last validated, valid until. Management of that can be wrapped in the Dereference call that can pull from the cache or the network (and might want to do something clever like queue requests for an item while a fetch is in progress).


public readonly struct Entity { public long Id { get; init; } }

public interface ISystem { public void Update(); }

public interface IComponent{ public long Id { get; set; } }

public Dictionary<IComponent, IList> EntityComponents


Linux Kconfig

The point is to give Values to Symbols.

Symbols have:

  • name (string)
  • type (bool, trivalue, string, int, hex, range)
  • default (expression)
  • value (trivalue, string, null)
  • dependencies (expression)
  • reverse dependencies (expression)
  • weak reverse dependencies (expression)
  • visible (expression)
  • help text (multi line string)

Advanced sleep tracker

A page of buttons for activities

A page that shows the data in the approved format, along with a print option (and print css)

Run only on phone, ideally no network after first download


Stone

The stone is old and weathered, still standing proud in the shade of the forest. It pays you no attention as you approach, making no move to attack or retreat.

You reach your hand towards the stone and it remains unflinching, steady as you touch it. For a moment you see time as the stone sees time, the Sun a dull yellow band across a grey sky, the forest reduced to a faint brown and green mist as trees grow, die, and are born again around the stone.

You let your hand drop back to your side and look at the rock. It stands apparently unchanged by the experience. You wonder what it saw in you?


That's me switched from Traefik to Caddy. Not entirely sure why, but then I was never entirely happy with Traefik.

Caddy's config language is less wordy than Traefik (and not yaml!), and while that might mean I've lost some options, they weren't options I was using.

Caddy also installs from an apt repo, rather than as a naked download, so I should get updates in good time.


How to Avoid Tax as a Megacorp

Let's pretend that we own a company that makes widgets. Our widgets sell for £1 each, it costs £0.80 to make a widget, so we're making £0.20 profit per widget.

Clearly, as an honest company, we pay tax on that £0.20.

Our widgets are very high quality, but we're new to the market and don't have name recognition. BigWidget are the brand leaders, and they agree that we can use their name (and follow their quality standards) for, oh, £0.20 per widget.

We agree to the deal, and add the £0.20 licencing cost to our expenses, meaning that we don't make a profit and we stop paying tax.

Why would we agree to that? Well, we're 100% owned by BigWidget and they're based in a jurisdiction that has a zero tax rate.


Havok died today at 20:00.

She hadn't been eating much, sleeping more, and moving less than normal for a few weeks. If you knew Havok, then you'd know that being off her food was a bad sign, so we took her to the vets about a week after we first noticed. The vet examined her, couldn't find any specific problems, and said that Havok was probably stressed from us rearranging the furniture in the lounge.

We took her back to the vet yesterday morning. They examined her and recommended that they keep her for observation and for IV fluids (to help rehydrate her). Early evening the vets called us and recommended moving Havok to the local animal hospital to continue the IV and observation overnight, and to perform an ultrasound in the morning.

We followed their advice and drove Havok to the hospital yesterday evening. This morning the hospital called and told us that Havok had a septic abdomen, and recommended surgery to get more information.

Havok went into surgery and never woke up. She stopped breathing during the procedure and was put on a ventilator.

Cats normally wake up within half an hour of surgery finishing, so the hospital calling at the three and a half hour mark to say she was still asleep and still not breathing on her own made it fairly clear that she was dying.

We asked them to give us/her another hour and a half for something to change. The vets had said that keeping her on ventilation for more than seven hours would start doing more damage, and we wanted to give her as much time as we could without hurting her any more.

(Since she was in a coma, I'm choosing to believe that she was at peace and in no pain. Effectively, to me at least, she died in surgery, and anything after that was an exercise in human pain management.)

The call came and her condition hadn't changed. We drove up to the hospital to say goodbye. We were taken through to her room where she was laying on a table under a blanket with her head and one leg poking out.

She looked fine, other than being unnaturally still. Husband and I stroked her head, called her name, told her it was time for food, but she didn't react to any of it. She didn't flick her ear to stop us from touching it, she didn't push into my hand when I scratched her cheek glands. This wasn't my cat, this was just an empty house that she used to live in. Time to switch it off and stop wasting resources.

The vets assistant disconnected the sensors and removed the tubes that were giving her fluid. The vet gave her a massive dose of anesthetic, and the assistant listened to Havok's heart and confirmed that it had stopped beating. Nothing changed. Havok was just as still as before. I kissed her little head and said goodbye.

Husband and I were both crying. It's not fair! Havok was a fantastic cat and we are good cat parents. We do everything right by our cats, they are fed, they run around outside during the day and sleep safe at night. Havok was a loving, tactile cat. She made the effort to come upstairs and snuggle me a few times in her final weeks (and Gods, if I'd known I would have payed more attention).

Insurance will take care of the bill (something like £6k+ apparently, don't care), and we need to tell the hospital what to do with the body (individual cremation, then probably scatter some ashes in the back yard here and maybe at the coast, but I've got a whole thing about her spirit being attached to the ashes that's causing me worry), and then life goes on.

I hope that, if Havok has a spirt, or a soul, or whatever kind of cosmic energy, she is happy wherever she is now. She liked being stoked. She liked snuggling between husband and me on the sofa. She liked to sleep on the cat tree in the study while I was at work, or sleeping in there. She liked being out in the garden on a summer's day with the rest of the colony playing hunt the sister in the tall grass. She tended to stretch out when she slept, clearly trusting us and her environment. She liked playing with Carnage and she wanted to play more with Storm. She stood up to the dogs who walk past the front garden. She was a good cat, and she will be missed.


To remember your current position in the blog, this page must store some data in this browser.

Are you OK with that?