diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Controllers/ImageResizeController.cs b/extra/admin-api/Spacebar.Cdn.Worker/Controllers/ImageResizeController.cs
new file mode 100644
index 000000000..7ba269655
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Controllers/ImageResizeController.cs
@@ -0,0 +1,8 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace Spacebar.Cdn.Worker.Controllers;
+
+[ApiController]
+public class ImageResizeController : ControllerBase {
+
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/DefaultAvatarRenderer.cs b/extra/admin-api/Spacebar.Cdn.Worker/DefaultAvatarRenderer.cs
new file mode 100644
index 000000000..bf41d30b8
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/DefaultAvatarRenderer.cs
@@ -0,0 +1,47 @@
+using ArcaneLibs.Extensions;
+using ImageMagick;
+using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
+
+namespace Spacebar.AdminApi.TestClient.Services.Helpers;
+
+public class DefaultAvatarRenderer {
+ public static readonly (byte r, byte g, byte b)[] DefaultAvatarColors = [
+ (70, 73, 236),
+ (150, 150, 150),
+ (236, 52, 83),
+ (211, 52, 236),
+ (45, 202, 76),
+ (236, 103, 52)
+ ];
+
+ public static readonly (byte r, byte g, byte b) SpacebarLogoColor = (0x01, 0x85, 0xFF);
+
+ // Slower at runtime, but doesnt depend on filesystem
+ private static string GetDefaultAvatarSvg(byte r, byte g, byte b, byte rf = 0xff, byte gf = 0xff, byte bf = 0xff) {
+ return $"""
+
+ """;
+ }
+
+ public static async Task GetDefaultAvatarImage(byte r, byte g, byte b, byte rf = 0xff, byte gf = 0xff, byte bf = 0xff, int size = 4096) {
+ var img = new MagickImageCollection(GetDefaultAvatarSvg(r, g, b, rf, gf, bf).AsBytes().ToArray(), new MagickReadSettings() {
+ Width = (uint?)size,
+ Height = (uint?)size,
+ // Verbose = true,
+ // ColorSpace = ColorSpace.RGB
+ });
+ return img;
+ }
+
+ public static async Task GetDefaultAvatar(byte r, byte g, byte b, byte rf = 0xff, byte gf = 0xff, byte bf = 0xff, MagickFormat format = MagickFormat.Png,
+ int size = 4096) {
+ var img = await GetDefaultAvatarImage(r, g, b, rf, gf, bf, size);
+ var ms = new MemoryStream();
+ await img.WriteAsync(ms, format);
+ ms.Position = 0;
+ return ms;
+ }
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Mimes.cs b/extra/admin-api/Spacebar.Cdn.Worker/Mimes.cs
new file mode 100644
index 000000000..8337e81d9
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Mimes.cs
@@ -0,0 +1,50 @@
+using System.ComponentModel;
+using ImageMagick;
+
+namespace Spacebar.Cdn.Worker;
+
+// Keep up to date with CDN!
+public static class Mimes {
+ private static string PrintLogged(string msg, string mime) {
+ Console.WriteLine($"{msg}: {mime}");
+ return mime;
+ }
+
+ public static MagickFormat GetFormatForExtension(string extension) {
+ extension = extension.ToLower();
+ // ban some values...
+ // TODO: look for more
+ if (extension
+ // screen capture/write
+ is "screenshot"
+ or "win"
+ or "clipboard"
+ or "x" // read from/write to x11 server
+ or "xwd" // x11 window dump
+ or "dds" // MS DirectDraw surface
+ or "open" // display image on screen, OSX only
+ // printer stuff
+ or "print"
+ or "scan"
+ or "scanx"
+ // special
+ or "dmr" // MagicCache media library, let's not...
+ or "emf" // some microsoft meta format, windows only
+ or "mpr" // Magick Persistent Registry - basically a resident in-memory image
+ ) throw new AccessViolationException("Disallowed extension: " + extension);
+
+ var matchingFormat = Enum.GetNames().FirstOrDefault(f => f.ToLower() == extension);
+ if (string.IsNullOrWhiteSpace(matchingFormat)) throw new InvalidEnumArgumentException("Unknown format: " + extension);
+ return Enum.TryParse(matchingFormat, out MagickFormat fmt) ? fmt : throw new InvalidEnumArgumentException("Unknown format: " + extension);
+ }
+
+ public static string GetMime(MagickFormat fmt) => fmt switch {
+ MagickFormat.Png => "image/png",
+ MagickFormat.Jpeg => "image/jpeg",
+ MagickFormat.Gif => "image/gif",
+ MagickFormat.Bmp => "image/bmp",
+ MagickFormat.Tiff => "image/tiff",
+ MagickFormat.WebP => "image/webp",
+ _ => PrintLogged("Unknown mime for format " + fmt.ToString() + "!", "application/octet-stream")
+ };
+}
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Program.cs b/extra/admin-api/Spacebar.Cdn.Worker/Program.cs
new file mode 100644
index 000000000..fc3aaf932
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Program.cs
@@ -0,0 +1,88 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using ArcaneLibs;
+using ImageMagick;
+using Spacebar.AdminApi.TestClient.Services.Helpers;
+using Spacebar.Cdn.Worker;
+
+var builder = WebApplication.CreateBuilder(args);
+
+var sw = Stopwatch.StartNew();
+Console.WriteLine("Pre-initializing Magick.NET...");
+// OpenCL.IsEnabled = true;
+MagickNET.Initialize();
+// Console.WriteLine("==> Rendering default avatars...");
+// foreach (var (r, g, b) in DefaultAvatarRenderer.DefaultAvatarColors) {
+// var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, size: 4096);
+// Console.WriteLine($" ==> #{r:X2}{g:X2}{b:X2} => {res.Length} bytes");
+// res.Position = 0;
+// await using (var fs = File.OpenWrite($"default-{r:X2}{g:X2}{b:X2}.png")) {
+// await res.CopyToAsync(fs);
+// fs.Flush();
+// fs.Close();
+// }
+// }
+
+// byte skip = 8;
+// var re = new RainbowEnumerator(lengthFactor: 256, skip: skip);
+// var reFg = new RainbowEnumerator(lengthFactor: 512, skip: skip, offset: 128);
+// var magickCollection = new MagickImageCollection();
+// for (int i = 0; i < 255; i += skip) {
+// var sw2 = Stopwatch.StartNew();
+// var clr = re.Next();
+// var clrFg = reFg.Next();
+// var res = await DefaultAvatarRenderer.GetDefaultAvatarImage(clr.r, clr.g, clr.b, clrFg.r, clrFg.g, clrFg.b, size: 512);
+// Console.Write($" ==> #{clr.r:X2}{clr.g:X2}{clr.b:X2}/{clrFg.r:X2}{clrFg.g:X2}{clrFg.b:X2} ({i} => {magickCollection.Count + 1})... R");
+// res.Flatten();
+// Console.Write("F");
+// res.First().AnimationDelay = 4;
+// Console.Write("A");
+// magickCollection.Add(res.First());
+// Console.WriteLine(" => " + sw2.Elapsed);
+// }
+//
+// Console.WriteLine(" ==> Optimizing (1/2)...");
+// magickCollection.OptimizePlus();
+// Console.WriteLine(" ==> Optimizing (2/2)...");
+// magickCollection.OptimizeTransparency();
+// Console.WriteLine(" ==> Writing...");
+// await using (var fs = File.OpenWrite($"default-animated.gif")) {
+// await magickCollection.WriteAsync(fs, MagickFormat.Gif);
+// }
+//
+// Console.WriteLine(sw.Elapsed);
+// Environment.Exit(0);
+
+// builder.WebHost.ConfigureKestrel(opts => opts.ListenUnixSocket(Environment.GetEnvironmentVariable("SOCKET_PATH")!));
+
+builder.Services.AddControllers();
+var app = builder.Build();
+app.MapControllers();
+
+app.MapGet("/defaultAvatar/{idx:int}.{ext}", async (HttpContext ctx, int idx, string ext) => {
+ var (r, g, b) = DefaultAvatarRenderer.DefaultAvatarColors[idx % DefaultAvatarRenderer.DefaultAvatarColors.Length];
+ var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, size: ctx.Request.Query.ContainsKey("size") ? int.Parse(ctx.Request.Query["size"]!) : 4096,
+ format: Mimes.GetFormatForExtension(ext));
+ return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext)));
+});
+
+// small easter egg internal stuff, maybe used someday :)
+app.MapGet("/defaultAvatar/_{bg:length(6)}.{ext}", async (HttpContext ctx, string bg, string ext) => {
+ var (r, g, b) = (byte.Parse(bg[..2], NumberStyles.HexNumber), byte.Parse(bg[2..4], NumberStyles.HexNumber), byte.Parse(bg[4..6], NumberStyles.HexNumber));
+ var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, size: ctx.Request.Query.ContainsKey("size") ? int.Parse(ctx.Request.Query["size"]!) : 4096,
+ format: Mimes.GetFormatForExtension(ext));
+ return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext)));
+});
+
+app.MapGet("/defaultAvatar/_{bg:length(6)}_{fg:length(6)}.{ext}", async (HttpContext ctx, string bg, string fg, string ext) => {
+ var (r, g, b) = (byte.Parse(bg[..2], NumberStyles.HexNumber), byte.Parse(bg[2..4], NumberStyles.HexNumber), byte.Parse(bg[4..6], NumberStyles.HexNumber));
+ var (rf, gf, bf) = (byte.Parse(fg[..2], NumberStyles.HexNumber), byte.Parse(fg[2..4], NumberStyles.HexNumber), byte.Parse(fg[4..6], NumberStyles.HexNumber));
+ var res = await DefaultAvatarRenderer.GetDefaultAvatar(r, g, b, rf, gf, bf, size: ctx.Request.Query.ContainsKey("size") ? int.Parse(ctx.Request.Query["size"]!) : 4096,
+ format: Mimes.GetFormatForExtension(ext));
+ return Results.File(res, Mimes.GetMime(Mimes.GetFormatForExtension(ext)));
+});
+
+app.MapGet("/*", async (ctx) => { });
+
+app.Run();
\ No newline at end of file
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Properties/launchSettings.json b/extra/admin-api/Spacebar.Cdn.Worker/Properties/launchSettings.json
new file mode 100644
index 000000000..7d6ece9c6
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Properties/launchSettings.json
@@ -0,0 +1,14 @@
+{
+ "$schema": "https://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "dotnetRunMessages": true,
+ "launchBrowser": false,
+ "applicationUrl": "http://localhost:5280",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj
new file mode 100644
index 000000000..2c2b794b8
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj
new file mode 100644
index 000000000..16d278df8
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj
new file mode 100644
index 000000000..34f49b8e1
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj
new file mode 100644
index 000000000..48e671ed5
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj
new file mode 100644
index 000000000..781068a6d
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj
new file mode 100644
index 000000000..16a2d1157
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj
new file mode 100644
index 000000000..663e16c66
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj
new file mode 100644
index 000000000..326ed11e7
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj
new file mode 100644
index 000000000..1943e7b0b
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0
+ enable
+ enable
+ Spacebar.Cdn.Worker
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/appsettings.Development.json b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.Development.json
new file mode 100644
index 000000000..dc0a4c8cc
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.Development.json
@@ -0,0 +1,17 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore.Routing.EndpointMiddleware": "Information",
+ "Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware": "Information",
+ "Microsoft.AspNetCore": "Trace", //Warning
+ "Microsoft.AspNetCore.Mvc": "Warning", //Warning
+ "Microsoft.AspNetCore.HostFiltering": "Warning", //Warning
+ "Microsoft.AspNetCore.Cors": "Warning", //Warning
+ "Microsoft.AspNetCore.server.Kestrel": "Information",
+ "Microsoft.AspNetCore.Routing.Matching.DfaMatcher": "Information",
+ // "Microsoft.EntityFrameworkCore": "Warning"
+ "Microsoft.EntityFrameworkCore.Database.Command": "Debug"
+ }
+ }
+}
diff --git a/extra/admin-api/Spacebar.Cdn.Worker/appsettings.json b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.json
new file mode 100644
index 000000000..10f68b8c8
--- /dev/null
+++ b/extra/admin-api/Spacebar.Cdn.Worker/appsettings.json
@@ -0,0 +1,9 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*"
+}
diff --git a/extra/admin-api/SpacebarAdminAPI.slnx b/extra/admin-api/SpacebarAdminAPI.slnx
index 0578bc353..e2ec0b008 100644
--- a/extra/admin-api/SpacebarAdminAPI.slnx
+++ b/extra/admin-api/SpacebarAdminAPI.slnx
@@ -1,41 +1,42 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/extra/admin-api/update-deps.cs b/extra/admin-api/update-deps.cs
index 9aa1b0fe3..592bbf11f 100755
--- a/extra/admin-api/update-deps.cs
+++ b/extra/admin-api/update-deps.cs
@@ -7,6 +7,29 @@ using ArcaneLibs;
using ArcaneLibs.Extensions;
using System.Text.Json;
+// sync versions for CDN worker
+{
+ Console.WriteLine("==> Ensuring CDN worker dependencies are in sync...");
+ var origContent = await File.ReadAllTextAsync("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj");
+ var depToReplace = "Magick.NET-Q16-HDRI-OpenMP-x64";
+ (string Project, string Dependency)[] replaceTargets = [
+ // ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.x86_64.csproj", "Magick.NET-Q16-HDRI-OpenMP-x64"), // source
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.x86_64.csproj", "Magick.NET-Q16-OpenMP-x64"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.x86_64.csproj", "Magick.NET-Q8-OpenMP-x64"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.aarch64.csproj", "Magick.NET-Q16-HDRI-OpenMP-arm64"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.aarch64.csproj", "Magick.NET-Q16-OpenMP-arm64"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.aarch64.csproj", "Magick.NET-Q8-OpenMP-arm64"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16-HDRI.AnyCPU.csproj", "Magick.NET-Q16-HDRI-AnyCPU"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q16.AnyCPU.csproj", "Magick.NET-Q16-AnyCPU"),
+ ("Spacebar.Cdn.Worker/Spacebar.Cdn.Worker.Q8.AnyCPU.csproj", "Magick.NET-Q8-AnyCPU"),
+ ];
+
+ foreach (var target in replaceTargets) {
+ Console.WriteLine($" ==> {target.Project} -> {target.Dependency}");
+ await File.WriteAllTextAsync(target.Project, origContent.Replace(depToReplace, target.Dependency));
+ }
+}
+
Console.WriteLine("==> Getting outputs...");
var outs = JsonSerializer.Deserialize(Util.GetCommandOutputSync("nix", $"eval --json .#packages.x86_64-linux --apply builtins.attrNames", silent: true, stderr: false));
if (args.Length > 0) {