--- /dev/null
+prepro -- prepare GOPROXY and go-import friendly website
+
+Go modules (https://go.dev/ref/mod), that are not located on hard-coded
+well-known hosting platforms like GitHub, are discovered by go-get using
+the go-import-protocol: it requests special URL, based on module's name,
+to get HTML with <meta name="go-import"> tag. That tag will redirect
+go-get further either to VCS, or to GOPROXY-capable URL.
+
+If VCS is in use, then go-get will invoke external utilities to checkout
+the code from VCS. After that, it will create ZIP-archive with the
+snapshot of the code and store it locally in cache directory.
+
+GOPROXY protocol can be treated just like a HTTP-transport to that cache
+directory. Only several prepared static files per each module's version
+is enough.
+
+* VCS solution is usually quicker to setup, because development VCS
+ repositories are publicly available in most cases too
+* VCS requires corresponding external tools to be installed. Hardly
+ either Fossil or GNU Bazaar are often installed on many systems
+* VCS checkout requires more disk space: for the VCS itself and for the
+ cached module's archive
+* VCS is slower to check and download in general, because it requires
+ several round-trips during the handshake between server and client
+* GOPROXY is easier to scale on multiple hosts, because you have to
+ replicate only several static files
+
+Another advantage of GOPROXY is actually the reason why "prepro" was
+born: current go-get can not handle SHA256-driven Git repositories. It
+git-init-s empty repository first and then tries to git-fetch it, that
+won't work if git-init was not aware of SHA256-based format. GOPROXY
+gives easy ability to make that module's repository publicly fetchable.
--- /dev/null
+mk-mod utility creates GOPROXY-compatible version of the module in the
+current directory:
+ mk-mod VCS-URL VCS-PATH MODULE-NAME VERSION.
+Only Git VCS is supported. Git tag's name must be equal to VERSION.
+
+mk-list utility collects all known versions in current GOPROXY directory
+and creates corresponding "list" files.
+
+mk-goget creates go-import-compatible static files:
+ mk-goget VCS-WEB-URL MODULE-NAME.
+
+For example:
+ mkdir proxy
+ cd proxy
+ mk-mod https://git.cypherpunks.ru/balloon.git ~w/balloon go.cypherpunks.ru/balloon v1.1.1
+ mk-mod https://git.cypherpunks.ru/balloon.git ~w/balloon go.cypherpunks.ru/balloon/v2 v2.0.0
+ mk-list
+ cd ..
+
+ mkdir goget
+ cd goget
+ mk-goget "http://www.git.cypherpunks.ru/?p=balloon.git" go.cypherpunks.ru/balloon
+ mk-goget "http://www.git.cypherpunks.ru/?p=balloon.git" go.cypherpunks.ru/balloon/v2
--- /dev/null
+module go.cypherpunks.ru/prepro
+
+go 1.21.0
+
+require golang.org/x/mod v0.12.0
--- /dev/null
+golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
--- /dev/null
+package main
+
+import (
+ "flag"
+ "log"
+ "os"
+
+ "golang.org/x/mod/module"
+ "golang.org/x/mod/zip"
+)
+
+func main() {
+ modName := flag.String("mod", "", "Module name")
+ version := flag.String("version", "", "Version")
+ root := flag.String("vcs", "", "Path to VCS root")
+ sub := flag.String("sub", "", "Optional subdirectory path")
+ flag.Parse()
+ err := zip.CreateFromVCS(
+ os.Stdout,
+ module.Version{Path: *modName, Version: *version},
+ *root, *version, *sub,
+ )
+ if err != nil {
+ log.Fatal(err)
+ }
+}
--- /dev/null
+#!/usr/bin/env zsh
+
+set -e
+
+vcsurl=$1
+modname=$2
+
+cols=(${(s#/#)modname})
+[[ ${cols[-1]} =~ v[0-9]+ ]] || cols=($cols v1)
+moddir=${(j:/:)cols[1,-2]}
+v=${cols[-1]}
+[[ -s $moddir/$v ]] && exit
+mkdir -p $moddir
+cat > $moddir/$v <<EOF
+<!DOCTYPE html>
+<html>
+<head>
+<meta name="go-import" content="$modname mod https://proxy.${cols[1]}">
+<head>
+<body>
+<a href="$vcsurl">$modname</a>
+</body>
+</html>
+EOF
--- /dev/null
+#!/usr/bin/env zsh
+
+set -e
+setopt EXTENDED_GLOB
+for d (**/*(/)) {
+ list=()
+ for f ($d/*.info(onN)) list=($list $f:t:r)
+ [[ ${#list} != 0 ]] || continue
+ print ${(F)list} > $d/list
+ touch -r $d/${list[-1]}.info $d/list
+}
--- /dev/null
+#!/usr/bin/env zsh
+
+set -e
+
+vcsurl=$1
+vcspath=$2
+modname=$3
+version=$4
+
+dst=$modname/@v
+mkdir -p $dst
+cd $dst
+[[ -s $version.zip ]] && exit
+
+prepro=$0:h:a/prepro
+zmodload -F zsh/datetime b:strftime
+git -C $vcspath cat-file -p $version | while read line ; do
+ [[ $line =~ ^object ]] && hsh=${${=line}[-1]}
+ [[ $line =~ ^tagger ]] && {
+ strftime -s sec -r "%s %z" "${${=line}[-2,-1]}"
+ break
+ }
+done
+[[ -n $hsh ]]
+[[ -n $sec ]]
+strftime -s when %Y-%m-%dT%H:%M:%SZ $sec
+
+$prepro -vcs $vcspath -mod $modname -version $version > $version.zip
+unzip -p $version.zip ${modname}@${version}/go.mod > $version.mod
+printf '{"Version":"%s","Time":"%s","Origin":{"VCS":"git","URL":"%s","Ref":"refs/tags/%s","Hash":"%s"}}' \
+ $version $when $vcsurl $version $hsh > $version.info
+touch -d $when $version.info $version.mod $version.zip