NixOS-based container for running GitHub actions
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

trick github into running our programs

+285 -29
+103 -17
build.zig
··· 3 3 4 4 const std = @import("std"); 5 5 6 + pub fn find(b: *std.Build, name: []const u8) ![]const u8 { 7 + const path = b.graph.environ_map.get("PATH") orelse return error.NoPath; 8 + var it = std.mem.splitScalar(u8, path, ':'); 9 + while (it.next()) |entry| { 10 + if (entry.len == 0 or entry[0] != '/') continue; 11 + var dir = std.Io.Dir.openDirAbsolute(b.graph.io, entry, .{}) catch |err| switch (err) { 12 + error.FileNotFound => continue, 13 + else => |e| return e, 14 + }; 15 + defer dir.close(b.graph.io); 16 + const stat = dir.statFile(b.graph.io, name, .{ .follow_symlinks = false }) catch |err| switch (err) { 17 + error.FileNotFound => continue, 18 + error.NotDir => continue, 19 + else => |e| return e, 20 + }; 21 + switch (stat.kind) { 22 + .file => return try std.fs.path.join(b.allocator, &.{ entry, name }), 23 + .sym_link => { 24 + var link: []const u8 = try std.fs.path.resolve(b.allocator, &.{ entry, name }); 25 + while (true) { 26 + var buf: [std.fs.max_path_bytes]u8 = undefined; 27 + const len = try dir.readLink(b.graph.io, link, &buf); 28 + link = try std.fs.path.resolve(b.allocator, &.{ std.fs.path.dirname(link) orelse link, buf[0..len] }); 29 + const new_stat = dir.statFile(b.graph.io, link, .{ .follow_symlinks = false }) catch |err| switch (err) { 30 + error.FileNotFound => continue, 31 + error.NotDir => continue, 32 + else => |e| return e, 33 + }; 34 + switch (new_stat.kind) { 35 + .file => return link, 36 + .sym_link => continue, 37 + else => return error.NotAFile, 38 + } 39 + } 40 + }, 41 + else => return error.NotAFile, 42 + } 43 + } 44 + return error.FileNotFound; 45 + } 46 + 6 47 pub fn build(b: *std.Build) !void { 7 48 const target = b.standardTargetOptions(.{}); 8 49 const optimize = b.standardOptimizeOption(.{}); 9 50 10 - const uid = b.option(u32, "uid", "uid to run as") orelse return error.MissingOption; 51 + const test_step = b.step("test", "Run tests"); 52 + 53 + const uid = b.option(u32, "uid", "uid to run as") orelse 1001; 54 + const username = b.option([]const u8, "username", "username to run as") orelse "github"; 55 + const tail = tail: { 56 + const tail = b.option([]const u8, "tail", "real tail binary") orelse try find(b, "tail"); 57 + if (!std.mem.eql(u8, std.fs.path.basename(tail), "coreutils")) break :tail tail; 58 + const dir = std.fs.path.dirname(tail) orelse break :tail tail; 59 + break :tail try std.fs.path.join(b.allocator, &.{ dir, "tail" }); 60 + }; 61 + const nix = b.option([]const u8, "nix", "real nix binary") orelse try find(b, "nix"); 62 + const bash = b.option([]const u8, "bash", "real bash binary") orelse try find(b, "bash"); 11 63 12 64 const options = b.addOptions(); 13 65 options.addOption(u32, "uid", uid); 66 + options.addOption([]const u8, "username", username); 67 + options.addOption([]const u8, "tail", tail); 68 + options.addOption([]const u8, "nix", nix); 69 + options.addOption([]const u8, "bash", bash); 14 70 15 - const exe = b.addExecutable(.{ 71 + const execas_exe = b.addExecutable(.{ 16 72 .name = b.fmt("execas-{d}", .{uid}), 17 73 .root_module = b.createModule(.{ 18 - .root_source_file = b.path("src/main.zig"), 74 + .root_source_file = b.path("src/execas.zig"), 19 75 .target = target, 20 76 .optimize = optimize, 21 77 }), 78 + .use_llvm = false, 79 + .use_lld = false, 22 80 }); 23 - exe.root_module.addOptions("options", options); 81 + execas_exe.root_module.addOptions("options", options); 82 + b.installArtifact(execas_exe); 24 83 25 - b.installArtifact(exe); 26 - const run_step = b.step("run", "Run the app"); 84 + const execas_tests = b.addTest(.{ 85 + .root_module = execas_exe.root_module, 86 + }); 87 + const run_execas_tests = b.addRunArtifact(execas_tests); 88 + test_step.dependOn(&run_execas_tests.step); 27 89 28 - const run_cmd = b.addRunArtifact(exe); 29 - run_step.dependOn(&run_cmd.step); 90 + const tail_exe = b.addExecutable(.{ 91 + .name = "tail", 92 + .root_module = b.createModule(.{ 93 + .root_source_file = b.path("src/tail.zig"), 94 + .target = target, 95 + .optimize = optimize, 96 + }), 97 + .use_llvm = false, 98 + .use_lld = false, 99 + }); 100 + tail_exe.root_module.addOptions("options", options); 30 101 31 - run_cmd.step.dependOn(b.getInstallStep()); 102 + b.installArtifact(tail_exe); 32 103 33 - if (b.args) |args| { 34 - run_cmd.addArgs(args); 35 - } 104 + const tail_tests = b.addTest(.{ 105 + .root_module = tail_exe.root_module, 106 + }); 107 + 108 + const run_tail_tests = b.addRunArtifact(tail_tests); 109 + test_step.dependOn(&run_tail_tests.step); 36 110 37 - const exe_tests = b.addTest(.{ 38 - .root_module = exe.root_module, 111 + const bash_exe = b.addExecutable(.{ 112 + .name = "bash", 113 + .root_module = b.createModule(.{ 114 + .root_source_file = b.path("src/bash.zig"), 115 + .target = target, 116 + .optimize = optimize, 117 + }), 118 + .use_llvm = false, 119 + .use_lld = false, 39 120 }); 121 + bash_exe.root_module.addOptions("options", options); 40 122 41 - const run_exe_tests = b.addRunArtifact(exe_tests); 123 + b.installArtifact(bash_exe); 124 + 125 + const bash_tests = b.addTest(.{ 126 + .root_module = tail_exe.root_module, 127 + }); 42 128 43 - const test_step = b.step("test", "Run tests"); 44 - test_step.dependOn(&run_exe_tests.step); 129 + const run_bash_tests = b.addRunArtifact(bash_tests); 130 + test_step.dependOn(&run_bash_tests.step); 45 131 }
+8 -12
flake.nix
··· 485 485 chown -R 1001:1001 github 486 486 ''; 487 487 config = 488 - # let 489 - # execas-github = pkgs.callPackage ./package.nix { 490 - # uid = 1001; 491 - # zig = zig.packages.${pkgs.stdenv.hostPlatform.system}.master; 492 - # }; 493 - # entrypoint = pkgs.writeShellScriptBin "setup" '' 494 - # ${lib.getExe pkgs.nix} daemon --trusted >/dev/null 2>&1 & 495 - 496 - # exec ${lib.getExe execas-github} "$@" 497 - # ''; 498 - # in 488 + let 489 + execas-github = pkgs.callPackage ./package.nix { 490 + uid = 1001; 491 + zig = zig.packages.${pkgs.stdenv.hostPlatform.system}.master; 492 + }; 493 + in 499 494 { 500 - Cmd = [ "${pkgs.bashInteractive}/bin/bash" ]; 495 + Cmd = [ "${lib.getExe' execas-github "bash"}" ]; 501 496 User = "0:0"; 502 497 # WorkingDir = "/github/home"; 503 498 # Entrypoint = [ "${lib.getExe entrypoint}" ]; ··· 505 500 "USER=root" 506 501 "PATH=${ 507 502 lib.concatStringsSep ":" [ 503 + "${lib.getBin execas-github}/bin" 508 504 "/root/.nix-profile/bin" 509 505 "/nix/var/nix/profiles/default/bin" 510 506 "/nix/var/nix/profiles/default/sbin"
+6
package.nix
··· 6 6 stdenv, 7 7 zig, 8 8 uid, 9 + coreutils, 10 + bashInteractive, 11 + nix, 9 12 ... 10 13 }: 11 14 stdenv.mkDerivation (finalAttrs: { ··· 16 19 ]; 17 20 zigBuildFlags = [ 18 21 "-Duid=${toString uid}" 22 + "-Dtail=${lib.getExe' coreutils "tail"}" 23 + "-Dnix=${lib.getExe' nix "nix"}" 24 + "-Dbash=${lib.getExe' bashInteractive "bash"}" 19 25 ]; 20 26 meta = { 21 27 mainProgram = "execas-${toString uid}";
+37
src/bash.zig
··· 1 + // SPDX-FileCopyrightText: © 2023 Jeffrey C. Ollie 2 + // SPDX-License-Identifier: MIT 3 + 4 + const std = @import("std"); 5 + const options = @import("options"); 6 + 7 + const lib = @import("lib.zig"); 8 + 9 + pub fn main(init: std.process.Init) !void { 10 + const arena: std.mem.Allocator = init.arena.allocator(); 11 + const io = init.io; 12 + 13 + var environ_map = try lib.fixupEnvironMap(arena, init.environ_map); 14 + defer environ_map.deinit(); 15 + 16 + var argv: std.ArrayList([]const u8) = .empty; 17 + defer argv.deinit(arena); 18 + 19 + try argv.append(arena, options.bash); 20 + 21 + var it = try init.minimal.args.iterateAllocator(arena); 22 + defer it.deinit(); 23 + 24 + _ = it.next(); 25 + while (it.next()) |arg| { 26 + try argv.append(arena, arg); 27 + } 28 + 29 + try lib.setUID(); 30 + 31 + const err = std.process.replace(io, .{ 32 + .argv = argv.items, 33 + .environ_map = &environ_map, 34 + }); 35 + 36 + std.debug.print("unable to execute: {t}\n", .{err}); 37 + }
+12
src/lib.zig
··· 1 + // SPDX-FileCopyrightText: © 2023 Jeffrey C. Ollie 2 + // SPDX-License-Identifier: MIT 3 + 4 + const std = @import("std"); 5 + 6 + pub const fixupEnvironMap = @import("lib/env.zig").fixupEnvironMap; 7 + pub const setUID = @import("lib/setuid.zig").setUID; 8 + 9 + test { 10 + _ = @import("lib/env.zig"); 11 + _ = @import("lib/setuid.zig"); 12 + }
+34
src/lib/env.zig
··· 1 + // SPDX-FileCopyrightText: © 2023 Jeffrey C. Ollie 2 + // SPDX-License-Identifier: MIT 3 + 4 + const std = @import("std"); 5 + const options = @import("options"); 6 + 7 + pub fn fixupEnvironMap(alloc: std.mem.Allocator, old: *const std.process.Environ.Map) (std.mem.Allocator.Error || std.Io.Writer.Error)!std.process.Environ.Map { 8 + var new = try old.clone(alloc); 9 + errdefer new.deinit(); 10 + 11 + path: { 12 + const path = new.get("PATH") orelse break :path; 13 + 14 + var it = std.mem.splitScalar(u8, path, ':'); 15 + 16 + _ = it.next(); 17 + 18 + var index: usize = 0; 19 + 20 + var writer: std.Io.Writer.Allocating = .init(alloc); 21 + defer writer.deinit(); 22 + 23 + while (it.next()) |entry| : (index += 1) { 24 + if (index != 0) try writer.writer.writeByte(std.fs.path.sep); 25 + try writer.writer.writeAll(entry); 26 + } 27 + 28 + try new.put("PATH", writer.written()); 29 + } 30 + 31 + try new.put("USER", options.username); 32 + 33 + return new; 34 + }
+17
src/lib/setuid.zig
··· 1 + // SPDX-FileCopyrightText: © 2023 Jeffrey C. Ollie 2 + // SPDX-License-Identifier: MIT 3 + 4 + const std = @import("std"); 5 + const options = @import("options"); 6 + 7 + pub fn setUID() !void { 8 + const rc = std.os.linux.setuid(options.uid); 9 + switch (std.os.linux.errno(rc)) { 10 + .SUCCESS => return, 11 + .PERM => return error.NoPermission, 12 + else => |err| { 13 + std.debug.print("unexpected error: {t}\n", .{err}); 14 + return error.UnexpectedError; 15 + }, 16 + } 17 + }
src/main.zig src/execas.zig
+68
src/tail.zig
··· 1 + // SPDX-FileCopyrightText: © 2023 Jeffrey C. Ollie 2 + // SPDX-License-Identifier: MIT 3 + 4 + const std = @import("std"); 5 + const options = @import("options"); 6 + 7 + const lib = @import("lib.zig"); 8 + 9 + pub fn main(init: std.process.Init) !void { 10 + const arena: std.mem.Allocator = init.arena.allocator(); 11 + const io = init.io; 12 + 13 + var environ_map = try lib.fixupEnvironMap(arena, init.environ_map); 14 + defer environ_map.deinit(); 15 + 16 + nix: { 17 + if (!std.mem.eql(u8, init.environ_map.get("CI") orelse break :nix, "true")) break :nix; 18 + if (!std.mem.eql(u8, init.environ_map.get("GITHUB_ACTIONS") orelse break :nix, "true")) break :nix; 19 + 20 + var it = try init.minimal.args.iterateAllocator(arena); 21 + defer it.deinit(); 22 + 23 + if (!std.mem.eql(u8, it.next() orelse break :nix, "tail")) break :nix; 24 + if (!std.mem.eql(u8, it.next() orelse break :nix, "-f")) break :nix; 25 + if (!std.mem.eql(u8, it.next() orelse break :nix, "/dev/null")) break :nix; 26 + if (it.next() != null) break :nix; 27 + 28 + cwd: { 29 + var dir = std.Io.Dir.openDirAbsolute(io, "/", .{}) catch break :cwd; 30 + defer dir.close(io); 31 + std.process.setCurrentDir(io, dir) catch break :cwd; 32 + } 33 + 34 + const err = std.process.replace(io, .{ 35 + .argv = &.{ 36 + options.nix, 37 + "daemon", 38 + "--trusted", 39 + }, 40 + .environ_map = &environ_map, 41 + }); 42 + 43 + std.debug.print("unable to execute: {t}\n", .{err}); 44 + return; 45 + } 46 + 47 + var argv: std.ArrayList([]const u8) = .empty; 48 + defer argv.deinit(arena); 49 + 50 + try argv.append(arena, options.tail); 51 + 52 + var it = try init.minimal.args.iterateAllocator(arena); 53 + defer it.deinit(); 54 + 55 + _ = it.next(); 56 + while (it.next()) |arg| { 57 + try argv.append(arena, arg); 58 + } 59 + 60 + try lib.setUID(); 61 + 62 + const err = std.process.replace(io, .{ 63 + .argv = argv.items, 64 + .environ_map = &environ_map, 65 + }); 66 + 67 + std.debug.print("unable to execute: {t}\n", .{err}); 68 + }