all: use make to build

This overhauls the build system.
We used to use a just-in-time clever build system
so that 'go run' and 'go install' Just Worked.

This was really nice, except that it make it
all but impossible to ship a single binary.
It also required our uses to install npm,
which some folks have an understandably negative reaction to.

This migrates to a makefile for building.
The core typescript building logic is mostly still in Go,
and untouched (boy did I learn that lesson the hard way).

The output is a single file that includes the webui, innie, and outie.

(There are still very mild shenanigans in which we write outie
out to a temp file and then 'docker cp' it into the docker container.
But this is pretty manageable.)

There are some significant follow-ups left after this commit:

- convert the nightly release builds to use the makefile
- lots of dead code removal
- maybe add -race support using a dockerfile for the cgo compilation
- maybe use 'docker cp' stdin reading with tar to avoid the temp outtie file
- all the rest of the "better release" todos (brew install, etc.)
diff --git a/webui/esbuild.go b/webui/esbuild.go
index d310f95..1588f06 100644
--- a/webui/esbuild.go
+++ b/webui/esbuild.go
@@ -358,19 +358,7 @@
 		return nil, fmt.Errorf("failed to compress .js/.js.map/.css files: %w", err)
 	}
 
-	// Everything succeeded, so we write tmpHashDir to hashZip
-	buf := new(bytes.Buffer)
-	w := zip.NewWriter(buf)
-	if err := w.AddFS(os.DirFS(tmpHashDir)); err != nil {
-		return nil, err
-	}
-	if err := w.Close(); err != nil {
-		return nil, err
-	}
-	if err := os.WriteFile(hashZip, buf.Bytes(), 0o666); err != nil {
-		return nil, err
-	}
-	return zip.NewReader(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
+	return os.DirFS(tmpHashDir), nil
 }
 
 func esbuildBundle(outDir, src, metafilePath string) error {