

I’ll finally be able to switch from docker compose to podman with quadlets on my server running alpine
openpgp4fpr:2a420f2982e589326ca49d1b0644b87ed144c988


I’ll finally be able to switch from docker compose to podman with quadlets on my server running alpine
GitLab LFS storage is mad expensive. I regret buying into it… We have an unreal engine project and the assets cross over 20GB, not raw assets mind you. I’ve self hosted perforce before and should have stuck with that


Forgejo has auto mirroring built in, it’ll periodically sync the repos you add. The disadvantage is you have to add them manually. Initially I wanted a list of my started github repos synced to my forgejo instance and just added them one by one. A simple cron job might be enough to do that, but last time I checked github didn’t have an API for fetching started repositories.


Isn’t VST pretty much a wild west anyway? I wonder how much is this really gonna help to standardize the tools/plugins


Mainly use it as a documentation search for APIs I’m not familiar with, or when I’m not sure what options there are to approach a problem. I work with unreal engine a lot, so I’d get a few pointers from an LLM first, then go read the source code of those APIs and inplement the rest myself.


I guess it can go both ways, but I’d probably put nixos under minimal and experienced branch
That’s a great analogy, I’m going to try to use it at my place, next time I need to explain tech debt


If the image metadata contains tags, they will be imported into immich as well. At least all my Google Photos tags got imported no problem.
ScratchMark sounds great. For an icon, why not an actual scratch mark across a few pages? You know, the typical 3 ripped scratches, maybe with a sneaky paw print in the corner of the pages.


Rider for Unreal Engine at work. Neovim at work/home for literally everything else (web, golang, python, zig). I have vscodium as well, a glorified config file editor basically.


It pretty much does, at least for all the apps I want to use I have no need to use fdroid. If not, maintainers should probably publish apks and have reproducible builds


Obtanium is also a good alternative btw
Let’s encrypt brother
const std = @import("std");
const List = std.ArrayList;
const Map = std.AutoHashMap;
const tokenizeScalar = std.mem.tokenizeScalar;
const splitScalar = std.mem.splitScalar;
const parseInt = std.fmt.parseInt;
const print = std.debug.print;
const contains = std.mem.containsAtLeast;
const eql = std.mem.eql;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const Answer = struct {
middle_sum: i32,
reordered_sum: i32,
};
pub fn solve(input: []const u8) !Answer {
var rows = splitScalar(u8, input, '\n');
// key is a page number and value is a
// list of pages to be printed before it
var rules = Map(i32, List(i32)).init(alloc);
var pages = List([]i32).init(alloc);
defer {
var iter = rules.iterator();
while (iter.next()) |rule| {
rule.value_ptr.deinit();
}
rules.deinit();
pages.deinit();
}
var parse_rules = true;
while (rows.next()) |row| {
if (eql(u8, row, "")) {
parse_rules = false;
continue;
}
if (parse_rules) {
var rule_pair = tokenizeScalar(u8, row, '|');
const rule = try rules.getOrPut(try parseInt(i32, rule_pair.next().?, 10));
if (!rule.found_existing) {
rule.value_ptr.* = List(i32).init(alloc);
}
try rule.value_ptr.*.append(try parseInt(i32, rule_pair.next().?, 10));
} else {
var page = List(i32).init(alloc);
var page_list = tokenizeScalar(u8, row, ',');
while (page_list.next()) |list| {
try page.append(try parseInt(i32, list, 10));
}
try pages.append(try page.toOwnedSlice());
}
}
var middle_sum: i32 = 0;
var reordered_sum: i32 = 0;
var wrong_order = false;
for (pages.items) |page| {
var index: usize = page.len - 1;
while (index > 0) : (index -= 1) {
var page_rule = rules.get(page[index]) orelse continue;
// check the rest of the pages
var remaining: usize = 0;
while (remaining < page[0..index].len) {
if (contains(i32, page_rule.items, 1, &[_]i32{page[remaining]})) {
// re-order the wrong page
const element = page[remaining];
page[remaining] = page[index];
page[index] = element;
wrong_order = true;
if (rules.get(element)) |next_rule| {
page_rule = next_rule;
}
continue;
}
remaining += 1;
}
}
if (wrong_order) {
reordered_sum += page[(page.len - 1) / 2];
wrong_order = false;
} else {
// middle page number
middle_sum += page[(page.len - 1) / 2];
}
}
return Answer{ .middle_sum = middle_sum, .reordered_sum = reordered_sum };
}
pub fn main() !void {
const answer = try solve(@embedFile("input.txt"));
print("Part 1: {d}\n", .{answer.middle_sum});
print("Part 2: {d}\n", .{answer.reordered_sum});
}
test "test input" {
const answer = try solve(@embedFile("test.txt"));
try std.testing.expectEqual(143, answer.middle_sum);
try std.testing.expectEqual(123, answer.reordered_sum);
}
const std = @import("std");
const List = std.ArrayList;
const tokenizeScalar = std.mem.tokenizeScalar;
const parseInt = std.fmt.parseInt;
const print = std.debug.print;
const eql = std.mem.eql;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const Point = struct {
x: isize,
y: isize,
fn add(self: *const Point, point: *const Point) Point {
return Point{ .x = self.x + point.x, .y = self.y + point.y };
}
};
// note: i have no idea how to use this or if it's even possible
// const DirectionType = enum(u8) { Up, Down, Left, Right, UpLeft, UpRight, DownLeft, DownRight };
// const Direction = union(DirectionType) {
// up: Point = .{ .x = 0, .y = 0 },
// };
const AllDirections = [_]Point{
.{ .x = 0, .y = -1 }, // up
.{ .x = 0, .y = 1 }, // down
.{ .x = -1, .y = 0 }, // left
.{ .x = 1, .y = 0 }, // right
.{ .x = -1, .y = -1 }, // up left
.{ .x = 1, .y = -1 }, // up right
.{ .x = -1, .y = 1 }, // down left
.{ .x = 1, .y = 1 }, // down right
};
const Answer = struct {
xmas: u32,
mas: u32,
};
pub fn searchXmas(letters: List([]const u8), search_char: u8, position: Point, direction: Point) u32 {
const current_char = getChar(letters, position);
if (current_char == search_char) {
const next = position.add(&direction);
if (current_char == 'M') {
return searchXmas(letters, 'A', next, direction);
} else if (current_char == 'A') {
return searchXmas(letters, 'S', next, direction);
} else if (current_char == 'S') {
return 1; // found all letters
}
}
return 0;
}
pub fn countXmas(letters: List([]const u8), starts: List(Point)) u32 {
var counter: u32 = 0;
for (starts.items) |start| {
for (AllDirections) |direction| {
const next = start.add(&direction);
counter += searchXmas(letters, 'M', next, direction);
}
}
return counter;
}
pub fn countMas(letters: List([]const u8), starts: List(Point)) u32 {
var counter: u32 = 0;
for (starts.items) |start| {
const a_char = getChar(letters, start) orelse continue;
const top_left_char = getChar(letters, start.add(&AllDirections[4])) orelse continue;
const down_right_char = getChar(letters, start.add(&AllDirections[7])) orelse continue;
const top_right_char = getChar(letters, start.add(&AllDirections[5])) orelse continue;
const down_left_char = getChar(letters, start.add(&AllDirections[6])) orelse continue;
const tldr = [3]u8{ top_left_char, a_char, down_right_char };
const trdl = [3]u8{ top_right_char, a_char, down_left_char };
if ((eql(u8, &tldr, "MAS") or eql(u8, &tldr, "SAM")) and (eql(u8, &trdl, "MAS") or eql(u8, &trdl, "SAM"))) {
counter += 1;
}
}
return counter;
}
pub fn getChar(letters: List([]const u8), point: Point) ?u8 {
if (0 > point.x or point.x >= letters.items.len) {
return null;
}
const row = @as(usize, @intCast(point.x));
if (0 > point.y or point.y >= letters.items[row].len) {
return null;
}
const col = @as(usize, @intCast(point.y));
return letters.items[row][col];
}
pub fn solve(input: []const u8) !Answer {
var rows = tokenizeScalar(u8, input, '\n');
var letters = List([]const u8).init(alloc);
defer letters.deinit();
var x_starts = List(Point).init(alloc);
defer x_starts.deinit();
var a_starts = List(Point).init(alloc);
defer a_starts.deinit();
var x: usize = 0;
while (rows.next()) |row| {
try letters.append(row);
for (row, 0..) |letter, y| {
if (letter == 'X') {
try x_starts.append(.{ .x = @intCast(x), .y = @intCast(y) });
} else if (letter == 'A') {
try a_starts.append(.{ .x = @intCast(x), .y = @intCast(y) });
}
}
x += 1;
}
// PART 1
const xmas = countXmas(letters, x_starts);
// PART 2
const mas = countMas(letters, a_starts);
return Answer{ .xmas = xmas, .mas = mas };
}
pub fn main() !void {
const answer = try solve(@embedFile("input.txt"));
print("Part 1: {d}\n", .{answer.xmas});
print("Part 2: {d}\n", .{answer.mas});
}
test "test input" {
const answer = try solve(@embedFile("test.txt"));
try std.testing.expectEqual(18, answer.xmas);
}
#Zig
const std = @import("std");
const List = std.ArrayList;
const splitScalar = std.mem.splitScalar;
const parseInt = std.fmt.parseInt;
const print = std.debug.print;
const concat = std.mem.concat;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const Answer = struct {
safe: u32,
tolerated: u32,
};
pub fn isSafe(levels: []i32) bool {
if (levels.len == 0) {
return false;
}
// slide window in pairs, advancing by one
var it = std.mem.window(i32, levels, 2, 1);
const first = it.first();
const decreasing = first[0] - first[1] > 0;
it.reset(); // rewind the iterator
while (it.next()) |slice| {
const lhs: i32 = slice[0];
const rhs: i32 = slice[1];
if (decreasing) {
if (lhs <= rhs) return false;
if (lhs - rhs < 1 or lhs - rhs > 3) return false;
} else {
if (rhs <= lhs) return false;
if (rhs - lhs < 1 or rhs - lhs > 3) return false;
}
}
return true;
}
pub fn solve(input: []const u8) !Answer {
var rows = splitScalar(u8, input, '\n');
// PART 1
// determine how many reports are safe
var safe_reports: u32 = 0;
var tolerated_reports: u32 = 0;
var unsafe_reports = List([]i32).init(alloc);
defer unsafe_reports.deinit();
while (rows.next()) |row| {
var levels = splitScalar(u8, row, ' ');
var report = List(i32).init(alloc);
defer report.deinit();
while (levels.next()) |level| {
const value = parseInt(i32, level, 10) catch continue;
report.append(value) catch continue;
}
if (isSafe(report.items)) {
safe_reports += 1;
} else {
try unsafe_reports.append(try alloc.dupe(i32, report.items));
}
}
// PART 2
// determine how many unsafe reports can be tolerated
for (unsafe_reports.items) |report| {
var index: usize = 0;
while (index < report.len) : (index += 1) {
// mutate report by removing one level
const mutated_report = concat(
alloc,
i32,
&[_][]const i32{ report[0..index], report[index + 1 ..] },
) catch report;
defer alloc.free(mutated_report);
if (isSafe(mutated_report)) {
tolerated_reports += 1;
break;
}
}
}
return Answer{ .safe = safe_reports, .tolerated = safe_reports + tolerated_reports };
}
pub fn main() !void {
const answer = try solve(@embedFile("input.txt"));
print("Part 1: {d}\n", .{answer.safe});
print("Part 2: {d}\n", .{answer.tolerated});
}
test "test input" {
const answer = try solve(@embedFile("test.txt"));
try std.testing.expectEqual(2, answer.safe);
try std.testing.expectEqual(4, answer.tolerated);
const std = @import("std");
const List = std.ArrayList;
const Map = std.AutoHashMap;
const splitSeq = std.mem.splitSequence;
const splitScalar = std.mem.splitScalar;
const parseInt = std.fmt.parseInt;
const print = std.debug.print;
const sort = std.sort.block;
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const Answer = struct {
distance: u32,
similarity: u32,
};
fn lessThan(_: void, lhs: []const u8, rhs: []const u8) bool {
return std.mem.lessThan(u8, lhs, rhs);
}
pub fn solve(input: []const u8) !Answer {
var rows = splitScalar(u8, input, '\n');
var left_list = List([]const u8).init(alloc);
defer left_list.deinit();
var right_list = List([]const u8).init(alloc);
defer right_list.deinit();
// PART 1
// split the rows into two lists
while (rows.next()) |row| {
var sides = splitSeq(u8, row, " ");
try left_list.append(sides.next() orelse break);
try right_list.append(sides.next() orelse break);
}
_ = left_list.pop(); // last null
// sort both lists
sort([]const u8, left_list.items, {}, lessThan);
sort([]const u8, right_list.items, {}, lessThan);
var distance: u32 = 0;
for (left_list.items, right_list.items) |left, right| {
distance += @abs(try parseInt(i32, left, 10) - try parseInt(i32, right, 10));
}
// PART 2
var right_scores = Map(i32, u32).init(alloc);
defer right_scores.deinit();
// count number of item appearances in the right list
for (right_list.items) |item| {
const value = try parseInt(i32, item, 10);
const result = try right_scores.getOrPut(value);
if (!result.found_existing) {
result.value_ptr.* = 1;
} else {
result.value_ptr.* += 1;
}
}
// sum up similarity between items in left list and right list scores
var similarity: u32 = 0;
for (left_list.items) |item| {
const value = try parseInt(i32, item, 10);
const result = right_scores.get(value) orelse 0;
similarity += @as(u32, @intCast(value)) * result;
}
return Answer{ .distance = distance, .similarity = similarity };
}
pub fn main() !void {
const answer = try solve(@embedFile("input.txt"));
print("Part 1: {d}\n", .{answer.distance});
print("Part 2: {d}\n", .{answer.similarity});
}
test "test input" {
const answer = try solve(@embedFile("test.txt"));
try std.testing.expectEqual(answer.distance, 11);
try std.testing.expectEqual(answer.similarity, 31);
}


Been trying to pick up zig, so made sense to try the AoC with it as well.


If only it wasn’t paywalled
Currently just alpine with docker compose, but I initially wanted to use podman, this was before quadlets were a thing and I found it very cumbersome to configure the networking between containers, also recall having to use kustomize as an extra step, so I gave up and went with compose.
Now there are quadlets with podman, which seem to do everything I need but require systemd. I’d be happy to switch from openrc to systemd just for the sake of using podman.