From aeb0ac9a329a253baf516be6ecfbdc4a750c6df6 Mon Sep 17 00:00:00 2001 From: Ophestra Date: Sat, 3 Oct 2026 15:51:56 +0900 Subject: internal/workflows: cache installed packages This avoids re-downloading packages on every run. Signed-off-by: Ophestra --- .gitea/workflows/test.yml | 2 +- internal/workflows/step.go | 34 +++++++++++++-- internal/workflows/test.go | 13 +----- test/flake.nix | 80 ------------------------------------ test/interactive/configuration.nix | 68 ------------------------------ test/interactive/hakurei.nix | 47 --------------------- test/interactive/raceattr.nix | 30 -------------- test/interactive/trace.nix | 35 ---------------- test/interactive/vm.nix | 56 ------------------------- test/internal/testsuite/testsuite.go | 11 +++++ test/sharefs/main.go | 6 +-- 11 files changed, 46 insertions(+), 336 deletions(-) delete mode 100644 test/interactive/configuration.nix delete mode 100644 test/interactive/hakurei.nix delete mode 100644 test/interactive/raceattr.nix delete mode 100644 test/interactive/trace.nix delete mode 100644 test/interactive/vm.nix diff --git a/.gitea/workflows/test.yml b/.gitea/workflows/test.yml index b42ba205..3cb08c5b 100644 --- a/.gitea/workflows/test.yml +++ b/.gitea/workflows/test.yml @@ -1 +1 @@ -{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-vm-output","path":"result/*","retention-days":1}}]},"sandbox-race":{"name":"Sandbox (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox-race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-race-vm-output","path":"result/*","retention-days":1}}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"install fuse and fs_mark","run":"apt-get update \u0026\u0026 apt-get install -y fuse3 fsmark"},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf \"result/hakurei-$HAKUREI_VERSION-amd64.tar.gz\" \u0026\u0026 \"./hakurei-$HAKUREI_VERSION-amd64/install.sh\" \u0026\u0026 sudo -u ubuntu hakurei version"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs ubuntu"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race","sandbox","sandbox-race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} +{"name":"Test","on":["push"],"jobs":{"hakurei":{"name":"Hakurei","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.hakurei"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-vm-output","path":"result/*","retention-days":1}}]},"race":{"name":"Hakurei (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-race-vm-output","path":"result/*","retention-days":1}}]},"sandbox":{"name":"Sandbox","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-vm-output","path":"result/*","retention-days":1}}]},"sandbox-race":{"name":"Sandbox (race detector)","runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run NixOS test","run":"nix build --out-link result --print-out-paths --print-build-logs ./test#checks.x86_64-linux.sandbox-race"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"sandbox-race-vm-output","path":"result/*","retention-days":1}}]},"sharefs":{"name":"ShareFS","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Set up Go toolchain","uses":"actions/setup-go@v6","with":{"go-version-file":"go.mod"}},{"name":"Install packages","uses":"awalsh128/cache-apt-pkgs-action@v1","with":{"packages":"fuse3 fsmark","version":0,"execute_install_scripts":true}},{"name":"Request distribution","id":"dist","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Install hakurei","run":"HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" \u0026\u0026 tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz \u0026\u0026 ./hakurei-$HAKUREI_VERSION*-amd64/install.sh \u0026\u0026 sudo -u ubuntu hakurei version \u0026\u0026 echo 'Defaults closefrom_override' \u003e /etc/sudoers.d/closefrom_override \u0026\u0026 mkdir /var/empty"},{"name":"Mount sharefs","run":"useradd -ru 1023 -md /var/lib/sdcard -k /var/empty -s /sbin/nologin media_rw \u0026\u0026 install -dm0 /sdcard \u0026\u0026 sharefs -o rw,noexec,nosuid,nodev,noatime,allow_other,mkdir,source=/var/lib/sdcard,setuid=1023,setgid=1023 /sdcard"},{"name":"Compile and run test suite","run":"sharefs -V \u0026\u0026 rm -rf result \u0026\u0026 go run -tags=testsuite ./test/sharefs"},{"name":"Upload test output","uses":"actions/upload-artifact@v3","with":{"name":"fs_mark","path":"result/*","retention-days":1}}]},"check":{"name":"Flake checks","needs":["hakurei","race","sandbox","sandbox-race"],"runs-on":"nix","steps":[{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Run checks","run":"nix --print-build-logs --experimental-features 'nix-command flakes' flake check ./test"}]},"dist":{"name":"Create distribution","runs-on":"rosa","steps":[{"name":"Fix container filesystem","run":"rm /var/run \u0026\u0026 ln -sf ../run /var"},{"name":"Checkout","uses":"actions/checkout@v4"},{"name":"Request distribution","id":"dist-test","run":"HAKUREI_REV=\"$(git rev-parse --short HEAD)\" \u0026\u0026 /rosa/bin/mbf ci dist -o result . \"$(cat cmd/dist/VERSION)-$HAKUREI_REV\" \u0026\u0026 echo \"rev=$HAKUREI_REV\" \u003e\u003e \"$GITHUB_OUTPUT\""},{"name":"Upload distribution","uses":"actions/upload-artifact@v3","with":{"name":"hakurei-${{ steps.dist-test.outputs.rev }}","path":"result/*","retention-days":1}}]}}} diff --git a/internal/workflows/step.go b/internal/workflows/step.go index 219ed78b..83d9c5f3 100644 --- a/internal/workflows/step.go +++ b/internal/workflows/step.go @@ -2,6 +2,8 @@ package main +import "strings" + // fixup is a step to work around a Podman regression introduced by a bad // container escape mitigation. var fixup = Step{ @@ -55,9 +57,35 @@ func newCIRequest(display, name, id string) Step { var install = Step{ Name: "Install hakurei", Run: "HAKUREI_VERSION=\"$(cat cmd/dist/VERSION)-${{ steps.dist.outputs.rev }}\" && " + - "tar xf \"result/hakurei-$HAKUREI_VERSION-amd64.tar.gz\" && " + - "\"./hakurei-$HAKUREI_VERSION-amd64/install.sh\" && " + - "sudo -u ubuntu hakurei version", + "tar xf result/hakurei-$HAKUREI_VERSION*-amd64.tar.gz && " + + "./hakurei-$HAKUREI_VERSION*-amd64/install.sh && " + + "sudo -u ubuntu hakurei version && " + + "echo 'Defaults closefrom_override' > " + + "/etc/sudoers.d/closefrom_override && " + + "mkdir /var/empty", +} + +// newTestsuite returns a step for running an integration test suite. +func newTestsuite(name, prefix string) Step { + return Step{ + Name: "Compile and run test suite", + Run: prefix + "rm -rf result && " + + "go run -tags=testsuite ./test/" + name, + } +} + +// newPackages returns a job for installing the specified packages with +// best-effort caching. Package names must not contain spaces. +func newPackages(rev int, packages ...string) Step { + return Step{ + Name: "Install packages", + Uses: "awalsh128/cache-apt-pkgs-action@v1", + With: []KV[any]{ + {"packages", strings.Join(packages, " ")}, + {"version", rev}, + {"execute_install_scripts", true}, + }, + } } // newNixOSTest returns a step for running the named NixOS test. diff --git a/internal/workflows/test.go b/internal/workflows/test.go index d3290f16..40e1a076 100644 --- a/internal/workflows/test.go +++ b/internal/workflows/test.go @@ -59,11 +59,7 @@ var _ = (&Workflow{ fixup, checkout, toolchain, - - { - Name: "install fuse and fs_mark", - Run: "apt-get update && apt-get install -y fuse3 fsmark", - }, + newPackages(0, "fuse3", "fsmark"), newCIRequest("distribution", "dist -o result", "dist"), install, @@ -82,12 +78,7 @@ var _ = (&Workflow{ "setuid=1023,setgid=1023 /sdcard", }, - { - Name: "Compile and run test suite", - Run: "sharefs -V && rm -rf result && " + - "go run -tags=testsuite ./test/sharefs ubuntu", - }, - + newTestsuite("sharefs", "sharefs -V && "), newUploadArtifact("test output", "fs_mark"), }, }}, diff --git a/test/flake.nix b/test/flake.nix index b106ec7a..a73ab03b 100644 --- a/test/flake.nix +++ b/test/flake.nix @@ -38,8 +38,6 @@ runCommandLocal callPackage nixfmt - deadnix - statix ; in { @@ -54,35 +52,6 @@ inherit self; withRace = true; }; - - formatting = runCommandLocal "check-formatting" { nativeBuildInputs = [ nixfmt ]; } '' - cd ${./.} - - echo "running nixfmt..." - nixfmt --width=256 --check . - - touch $out - ''; - - lint = - runCommandLocal "check-lint" - { - nativeBuildInputs = [ - deadnix - statix - ]; - } - '' - cd ${./.} - - echo "running deadnix..." - deadnix --fail - - echo "running statix..." - statix check . - - touch $out - ''; } ); @@ -155,55 +124,6 @@ }; withPackage = pkgs.mkShell { buildInputs = [ hakurei ] ++ hakurei.targetPkgs; }; - vm = - let - nixos = nixpkgs.lib.nixosSystem { - inherit system; - modules = [ - { - environment = { - systemPackages = [ - (pkgs.buildFHSEnv { - pname = "hakurei-fhs"; - inherit (hakurei) version; - targetPkgs = _: hakurei.targetPkgs; - extraOutputsToInstall = [ "dev" ]; - profile = '' - export PKG_CONFIG_PATH="/usr/share/pkgconfig:$PKG_CONFIG_PATH" - ''; - }) - ]; - - hakurei = - let - # this is used for interactive vm testing during development, where tests might be broken - package = self.packages.${pkgs.stdenv.hostPlatform.system}.hakurei.override { - buildGo127Module = previousArgs: pkgs.pkgsStatic.buildGo127Module (previousArgs // { doCheck = false; }); - }; - in - { - inherit package; - hsuPackage = self.packages.${pkgs.stdenv.hostPlatform.system}.hsu.override { hakurei = package; }; - }; - }; - } - - ./interactive/configuration.nix - ./interactive/vm.nix - ./interactive/hakurei.nix - ./interactive/trace.nix - ./interactive/raceattr.nix - - self.nixosModules.hakurei - home-manager.nixosModules.home-manager - ]; - }; - in - pkgs.mkShell { - buildInputs = [ nixos.config.system.build.vm ]; - shellHook = "exec run-nixos-vm $@"; - }; - generateDoc = let inherit (pkgs) lib; diff --git a/test/interactive/configuration.nix b/test/interactive/configuration.nix deleted file mode 100644 index 9d1bddd8..00000000 --- a/test/interactive/configuration.nix +++ /dev/null @@ -1,68 +0,0 @@ -{ pkgs, ... }: -{ - system.stateVersion = "23.05"; - - users.users = { - alice = { - isNormalUser = true; - description = "Alice Foobar"; - password = "foobar"; - uid = 1000; - extraGroups = [ "wheel" ]; - }; - untrusted = { - isNormalUser = true; - description = "Untrusted user"; - password = "foobar"; - uid = 1001; - }; - }; - - home-manager.users.alice.home.stateVersion = "24.11"; - - security = { - sudo.wheelNeedsPassword = false; - rtkit.enable = true; - pam.loginLimits = [ - { - domain = "@users"; - item = "rtprio"; - type = "-"; - value = 1; - } - ]; - }; - - services = { - getty.autologinUser = "alice"; - pipewire = { - enable = true; - alsa.enable = true; - alsa.support32Bit = true; - pulse.enable = true; - jack.enable = true; - }; - }; - - environment.variables = { - SWAYSOCK = "/tmp/sway-ipc.sock"; - WLR_RENDERER = "pixman"; - }; - - programs = { - sway.enable = true; - - bash.loginShellInit = '' - if [ "$(tty)" = "/dev/tty1" ]; then - set -e - - mkdir -p ~/.config/sway - (sed s/Mod4/Mod1/ /etc/sway/config && - echo 'output * bg ${pkgs.nixos-artwork.wallpapers.simple-light-gray.gnomeFilePath} fill') > ~/.config/sway/config - - sway --validate - systemd-cat --identifier=session sway && touch /tmp/sway-exit-ok - fi - ''; - }; -} diff --git a/test/interactive/hakurei.nix b/test/interactive/hakurei.nix deleted file mode 100644 index ef5d0ada..00000000 --- a/test/interactive/hakurei.nix +++ /dev/null @@ -1,47 +0,0 @@ -{ pkgs, ... }: -{ - environment.hakurei = rec { - enable = true; - stateDir = "/var/lib/hakurei"; - sharefs.source = "${stateDir}/sdcard"; - users.alice = 0; - apps = { - "cat.gensokyo.extern.foot.noEnablements" = { - name = "ne-foot"; - identity = 1; - shareUid = true; - verbose = true; - share = pkgs.foot; - packages = [ pkgs.foot ]; - command = "foot"; - enablements = { - dbus = false; - pipewire = false; - }; - }; - - "cat.gensokyo.extern.foot.badDaemon" = { - name = "bd-foot"; - identity = 1; - shareUid = true; - verbose = true; - share = pkgs.foot; - packages = [ pkgs.foot ]; - command = "foot"; - enablements = { - dbus = false; - }; - extraPaths = [ - { - type = "daemon"; - dst = "/proc/nonexistent"; - path = "/usr/bin/env"; - args = [ "false" ]; - } - ]; - }; - }; - - extraHomeConfig.home.stateVersion = "23.05"; - }; -} diff --git a/test/interactive/raceattr.nix b/test/interactive/raceattr.nix deleted file mode 100644 index de54f4cf..00000000 --- a/test/interactive/raceattr.nix +++ /dev/null @@ -1,30 +0,0 @@ -{ lib, pkgs, ... }: -{ - security.wrappers.raceattr = - let - inherit (pkgs) buildGoModule; - in - { - setuid = true; - owner = "root"; - group = "root"; - source = "${ - (buildGoModule rec { - name = "raceattr"; - pname = name; - tags = [ "raceattr" ]; - - src = builtins.path { - name = "${pname}-src"; - path = lib.cleanSource ../sharefs; - filter = path: type: (type == "directory") || (type == "regular" && lib.hasSuffix ".go" path); - }; - vendorHash = null; - - preBuild = '' - go mod init hakurei.app/raceattr >& /dev/null - ''; - }) - }/bin/raceattr"; - }; -} diff --git a/test/interactive/trace.nix b/test/interactive/trace.nix deleted file mode 100644 index b9ef872c..00000000 --- a/test/interactive/trace.nix +++ /dev/null @@ -1,35 +0,0 @@ -{ lib, pkgs, ... }: -let - tracing = name: "\"/sys/kernel/debug/tracing/${name}\""; -in -{ - environment.systemPackages = [ - (pkgs.writeShellScriptBin "hakurei-set-up-tracing" '' - set -e - echo "$1" > ${tracing "set_graph_function"} - echo function_graph > ${tracing "current_tracer"} - echo funcgraph-tail > ${tracing "trace_options"} - echo funcgraph-retval > ${tracing "trace_options"} - echo nofuncgraph-cpu > ${tracing "trace_options"} - echo nofuncgraph-overhead > ${tracing "trace_options"} - echo nofuncgraph-duration > ${tracing "trace_options"} - '') - (pkgs.writeShellScriptBin "hakurei-print-trace" "exec cat ${tracing "trace"}") - (pkgs.writeShellScriptBin "hakurei-consume-trace" "exec cat ${tracing "trace_pipe"}") - ]; - - boot.kernelPatches = [ - { - name = "funcgraph-retval"; - patch = null; - structuredExtraConfig = with lib.kernel; { - FUNCTION_GRAPH_RETVAL = yes; - - RUST = lib.mkForce unset; - DRM_NOVA = lib.mkForce unset; - DRM_PANIC_SCREEN_QR_CODE = lib.mkForce unset; - NOVA_CORE = lib.mkForce unset; - }; - } - ]; -} diff --git a/test/interactive/vm.nix b/test/interactive/vm.nix deleted file mode 100644 index 522878e9..00000000 --- a/test/interactive/vm.nix +++ /dev/null @@ -1,56 +0,0 @@ -{ - virtualisation.vmVariant.virtualisation = { - memorySize = 4096; - qemu.options = [ - "-vga none -device virtio-gpu-pci" - "-smp 8" - ]; - - mountHostNixStore = true; - writableStore = true; - writableStoreUseTmpfs = false; - - sharedDirectories = { - cwd = { - target = "/mnt/.ro-cwd"; - source = ''"$OLDPWD"''; - securityModel = "none"; - }; - }; - - fileSystems = { - "/mnt/.ro-cwd".options = [ - "ro" - "noatime" - ]; - "/mnt/cwd".overlay = { - lowerdir = [ "/mnt/.ro-cwd" ]; - upperdir = "/tmp/.cwd/upper"; - workdir = "/tmp/.cwd/work"; - }; - - "/mnt/src".overlay = { - lowerdir = [ ../.. ]; - upperdir = "/tmp/.src/upper"; - workdir = "/tmp/.src/work"; - }; - }; - }; - - systemd.services = { - logrotate-checkconf.enable = false; - hakurei-src-fix-ownership = { - wantedBy = [ "multi-user.target" ]; - wants = [ "mnt-src.mount" ]; - after = [ "mnt-src.mount" ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - }; - script = '' - chown -R alice:users /mnt/src/ - chmod -R +w /mnt/src/ - ''; - }; - }; -} diff --git a/test/internal/testsuite/testsuite.go b/test/internal/testsuite/testsuite.go index 4b70da34..9233818d 100644 --- a/test/internal/testsuite/testsuite.go +++ b/test/internal/testsuite/testsuite.go @@ -6,6 +6,7 @@ import ( "os" "os/exec" "os/signal" + "os/user" "syscall" ) @@ -25,6 +26,16 @@ func ReceiveSignals() { log.Fatalf("terminating on signal %s", <-s) } +// GetUser returns the [user.User] structure describing the user with uid 1000. +func GetUser() *user.User { + if u, err := user.LookupId("1000"); err != nil { + log.Fatal(err) + return nil + } else { + return u + } +} + // MustRun runs command and terminates the testsuite on error. func MustRun(command ...string) { cmd := exec.Command(command[0], command[1:]...) diff --git a/test/sharefs/main.go b/test/sharefs/main.go index 536a61b3..c2b6cd41 100644 --- a/test/sharefs/main.go +++ b/test/sharefs/main.go @@ -51,16 +51,12 @@ func checkBadOpts(username, opts, want string) { func main() { go testsuite.ReceiveSignals() + username := testsuite.GetUser().Username if err := os.Mkdir("result", 0755); err != nil { log.Fatal(err) } - if len(os.Args) != 2 { - log.Fatal("expecting 1 argument") - } - username := os.Args[1] - done := make(chan struct{}) go func() { defer close(done) -- cgit v1.3.1