Changes
7 changed files (+569/-13)
-
-
@@ -69,6 +69,7 @@(final: prev: { my-theme = prev.callPackage ./programs/theme { }; my-waybar-text = prev.callPackage ./programs/waybar-text { }; my-workerbee = prev.callPackage ./programs/workerbee { }; plac = plac.packages."${system}".default; }) ];
-
@@ -152,21 +153,25 @@homeConfigurations = { dev-linux = mkHomeConfiguration { system = "x86_64-linux"; module = { features.wayland-de.enable = true; features.gui.enable = true; module = { pkgs, ... }: { home.packages = with pkgs; [ pkgs.my-workerbee ]; nixGL.packages = nixgl.packages; nixGL.defaultWrapper = "mesa"; nixGL.installScripts = [ "mesa" ]; features.wayland-de.enable = true; features.gui.enable = true; features.wayland-de.niri.outputs = [ { name = "HDMI-A-1"; scale = 1.4; } ]; }; nixGL.packages = nixgl.packages; nixGL.defaultWrapper = "mesa"; nixGL.installScripts = [ "mesa" ]; features.wayland-de.niri.outputs = [ { name = "HDMI-A-1"; scale = 1.4; } ]; }; }; pixelbook = mkHomeConfiguration {
-
-
-
@@ -0,0 +1,45 @@// Copyright 2025 Shota FUJI <pockawoooh@gmail.com> // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const exe = b.addExecutable(.{ .name = ",workerbee", .root_module = b.createModule(.{ .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }), }); b.installArtifact(exe); // zig build run { const step = b.step("run", "Compile and run program"); const run = b.addRunArtifact(exe); if (b.args) |args| { run.addArgs(args); } step.dependOn(&run.step); } }
-
-
-
@@ -0,0 +1,86 @@# Copyright 2025 Shota FUJI <pockawoooh@gmail.com> # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # SPDX-License-Identifier: Apache-2.0 { stdenvNoCC, lib, installShellFiles, zig, }: stdenvNoCC.mkDerivation rec { pname = "my-workerbee"; version = "1.0.0"; nativeBuildInputs = [ zig.hook installShellFiles ]; src = with lib.fileset; toSource { root = ./.; fileset = unions [ ./src ./build.zig ]; }; meta = { mainProgram = ",workerbee"; }; dontConfigure = true; postInstall = let name = meta.mainProgram; in '' installShellCompletion --fish --cmd ${name} <(cat << "EOF" set -l commands incus set -l incus_commands config complete -c ${name} --no-files complete -c ${name} --long help -d "Prints usage to stdout and exits" complete -c ${name} \ --condition "not __fish_seen_subcommand_from $commands" \ -a "$commands" complete -c ${name} \ --condition "__fish_seen_subcommand_from incus; and not __fish_seen_subcommand_from $incus_commands" \ -a "$incus_commands" complete -c ${name} \ --condition "__fish_seen_subcommand_from incus; and __fish_seen_subcommand_from $incus_commands" \ --long disable-nesting \ -d "Disable container nesting" complete -c ${name} \ --condition "__fish_seen_subcommand_from incus; and __fish_seen_subcommand_from $incus_commands" \ --long wayland \ -d "Proxy Wayland socket" complete -c ${name} \ --condition "__fish_seen_subcommand_from incus; and __fish_seen_subcommand_from $incus_commands" \ --short p \ --long project \ --require-parameter \ -d "Project directory to bind mount" EOF) ''; }
-
-
-
@@ -0,0 +1,28 @@// Copyright 2025 Shota FUJI <pockawoooh@gmail.com> // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 pub const Code = enum(u8) { ok = 0, unhandled_error = 1, incorrect_usage = 2, out_of_memory = 3, stdout_write_error = 4, filename_error = 5, pub fn to_u8(self: @This()) u8 { return @intFromEnum(self); } };
-
-
-
@@ -0,0 +1,66 @@// Copyright 2025 Shota FUJI <pockawoooh@gmail.com> // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const exit = @import("./exit.zig"); const incus_config = @import("./incus_config.zig"); const help = \\,workerbee incus - Manage Incus containers for workerbee \\ \\[USAGE] \\,workerbee incus <COMMAND> \\ \\[COMMANDS] \\config Print Incus instance configuration to stdout. \\ \\[OPTIONS] \\--help Print this message to stdout and exits. \\ ; pub fn run(allocator: std.mem.Allocator, args: *std.process.ArgIterator) !exit.Code { while (args.next()) |arg| { if (std.mem.eql(u8, arg, "--help")) { var buf: [1024]u8 = undefined; var stdout = std.fs.File.stdout().writer(&buf); const writer = &stdout.interface; writer.writeAll(help) catch return .stdout_write_error; writer.flush() catch return .stdout_write_error; return .ok; } if (std.mem.eql(u8, arg, "config")) { return incus_config.run(allocator, args); } std.log.err("Unknown subcommand: {s}", .{arg}); return .incorrect_usage; } { var buf: [1024]u8 = undefined; var stderr = std.fs.File.stderr().writer(&buf); const writer = &stderr.interface; writer.writeAll(help) catch {}; writer.flush() catch {}; } return .incorrect_usage; }
-
-
-
@@ -0,0 +1,240 @@// Copyright 2025 Shota FUJI <pockawoooh@gmail.com> // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const exit = @import("./exit.zig"); const incus = struct { const ProxyDevice = struct { type: []const u8 = "proxy", bind: enum { host, instance } = .instance, connect: []const u8, gid: []const u8, listen: []const u8, mode: []const u8, @"security.gid": []const u8, @"security.uid": []const u8, uid: []const u8, }; const DiskDevice = struct { type: []const u8 = "disk", shift: bool = true, source: []const u8, path: []const u8, }; }; const WaylandSocket = struct { host_location: []const u8, uid: std.posix.uid_t, gid: std.posix.gid_t, }; const Options = struct { /// Project directory to bind mount. project_dir: ?[]const u8 = null, /// Enable container nesting (only container). nesting: bool = true, /// Wayland socket to bind. wayland_socket: ?WaylandSocket = null, const WriteError = std.Io.Writer.Error || std.mem.Allocator.Error; fn writeJson(self: *const Options, allocator: std.mem.Allocator, writer: *std.Io.Writer) WriteError!void { var json: std.json.Stringify = .{ .writer = writer }; try json.beginObject(); try self.writeInstanceConfig(&json); try self.writeDevices(allocator, &json); try json.endObject(); } fn writeInstanceConfig(self: *const Options, json: *std.json.Stringify) WriteError!void { try json.objectField("config"); try json.beginObject(); try json.objectField("security.idmap.isolated"); try json.write(true); if (self.nesting) { try json.objectField("security.nesting"); try json.write(true); } try json.endObject(); } fn writeDevices(self: *const Options, allocator: std.mem.Allocator, json: *std.json.Stringify) WriteError!void { try json.objectField("devices"); try json.beginObject(); if (self.wayland_socket) |wayland_socket| { try json.objectField("wayland-socket"); const host_address = try std.fmt.allocPrint(allocator, "unix:{s}", .{wayland_socket.host_location}); try json.write(incus.ProxyDevice{ .connect = host_address, .gid = try std.fmt.allocPrint(allocator, "{d}", .{wayland_socket.gid}), .listen = "unix:/mnt/wayland-0", .mode = "0700", .@"security.gid" = "1000", .@"security.uid" = "1000", .uid = try std.fmt.allocPrint(allocator, "{d}", .{wayland_socket.uid}), }); } if (self.project_dir) |project_dir| { try json.objectField("project"); try json.write(incus.DiskDevice{ .source = project_dir, .path = "/home/workerbee/project", }); } try json.endObject(); } }; const help = \\,workerbee incus config - Print Incus instance configuration to stdout \\ \\[USAGE] \\,workerbee incus config <OPTIONS> \\ \\[OPTIONS] \\--help Print this message to stdout and exits. \\ \\--disable-nesting Disable container features inside the container. \\ Non-Incus sandbox technologies such as Nix and podman \\ won't work when disabled. \\ \\--wayland Proxy host's Wayland socket to the container, \\ at "/mnt/wayland-0". As this command Wayland socket \\ path and UID of local machine (where the command runs \\ on,) host and local machine MUST be the same. \\ \\--project <PATH>, -p <PATH> \\ Project directory to bind mount. To use this option, \\ host and local machine MUST be the same. \\ ; pub fn run(allocator: std.mem.Allocator, args: *std.process.ArgIterator) !exit.Code { var options: Options = .{}; while (args.next()) |arg| { if (std.mem.eql(u8, arg, "--help")) { var buf: [1024]u8 = undefined; var stdout = std.fs.File.stdout().writer(&buf); const writer = &stdout.interface; writer.writeAll(help) catch return .stdout_write_error; writer.flush() catch return .stdout_write_error; return .ok; } if (std.mem.eql(u8, arg, "--disable-nesting")) { options.nesting = false; continue; } if (std.mem.eql(u8, arg, "--wayland")) { // The current Zig stdlib misses `std.posix.getgid`. const gid = std.os.linux.getgid(); const uid = std.posix.getuid(); const wayland_display = std.posix.getenv("WAYLAND_DISPLAY") orelse { std.log.err("$WAYLAND_DISPLAY is not set", .{}); return .incorrect_usage; }; if (std.fs.path.isAbsolute(wayland_display)) { options.wayland_socket = .{ .host_location = wayland_display, .uid = uid, .gid = gid, }; continue; } const xdg_runtime_dir = std.posix.getenv("XDG_RUNTIME_DIR") orelse { std.log.err("$WAYLAND_DISPLAY set to non-absolute path, but $XDG_RUNTIME_DIR is not set", .{}); return .incorrect_usage; }; const resolved_path = std.fs.path.join(allocator, &.{ xdg_runtime_dir, wayland_display }) catch { return .out_of_memory; }; options.wayland_socket = .{ .host_location = resolved_path, .uid = uid, .gid = gid, }; continue; } if (std.mem.eql(u8, arg, "--project") or std.mem.eql(u8, arg, "-p")) { const path = args.next() orelse { std.log.err("{s} option requires a value", .{arg}); return .incorrect_usage; }; const realpath = std.fs.cwd().realpathAlloc(allocator, path) catch |err| switch (err) { std.mem.Allocator.Error.OutOfMemory => return .out_of_memory, else => { std.log.err("Failed to resolve path \"{s}\": {t}", .{ path, err }); return .filename_error; }, }; options.project_dir = realpath; continue; } std.log.err("Unknown option value: {s}", .{arg}); return .incorrect_usage; } var output_buffer: [1024]u8 = undefined; var output_writer = std.fs.File.stdout().writer(&output_buffer); const writer = &output_writer.interface; options.writeJson(allocator, writer) catch |err| { std.log.err("Failed to write to stdout: {t}", .{err}); return .stdout_write_error; }; writer.writeByte('\n') catch |err| { std.log.err("Failed to write to stdout: {t}", .{err}); return .stdout_write_error; }; writer.flush() catch |err| { std.log.err("Failed to flush write buffer: {t}", .{err}); return .stdout_write_error; }; return .ok; }
-
-
-
@@ -0,0 +1,86 @@// Copyright 2025 Shota FUJI <pockawoooh@gmail.com> // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // SPDX-License-Identifier: Apache-2.0 const std = @import("std"); const exit = @import("./exit.zig"); const incus = @import("./incus.zig"); pub fn main() !u8 { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); return run(allocator).to_u8(); } const help = \\,workerbee - Helper program for workerbee development environment management \\ \\[USAGE] \\,workerbee <COMMAND> \\ \\[COMMANDS] \\incus Container management helpers. \\ \\[OPTIONS] \\--help Print this message to stdout and exits. \\ ; fn run(allocator: std.mem.Allocator) exit.Code { var iter = std.process.ArgIterator.initWithAllocator(allocator) catch { return .out_of_memory; }; defer iter.deinit(); // Skip program name. _ = iter.next(); while (iter.next()) |arg| { if (std.mem.eql(u8, arg, "--help")) { var buf: [1024]u8 = undefined; var stdout = std.fs.File.stdout().writer(&buf); const writer = &stdout.interface; writer.writeAll(help) catch return .stdout_write_error; writer.flush() catch return .stdout_write_error; return .ok; } if (std.mem.eql(u8, arg, "incus")) { return incus.run(allocator, &iter) catch |err| { std.log.err("Unhandled error at \"incus\" command: {t}", .{err}); return .unhandled_error; }; } std.log.err("Unknown command \"{s}\"", .{arg}); return .incorrect_usage; } { var buf: [1024]u8 = undefined; var stderr = std.fs.File.stderr().writer(&buf); const writer = &stderr.interface; writer.writeAll(help) catch {}; writer.flush() catch {}; } return .incorrect_usage; }
-