GoCheese is Python private package repository and caching proxy.
-See gocheese.{info,texi} and INSTALL for more documentation.
+See doc/gocheese.info and INSTALL for more documentation.
umask 022
mkdir -p packages
exec setuidgid gocheese tcpserver -DRH -l 0 ::0 8080 \
- gocheese -ucspi -passwd passwd
+ gocheese -ucspi -passwd passwd /path/to/packages
EOF
# cat > log/run <<EOF
}
func mkdirForPkg(w http.ResponseWriter, r *http.Request, pkgName string) bool {
- path := filepath.Join(*Root, pkgName)
+ path := filepath.Join(Root, pkgName)
if _, err := os.Stat(path); os.IsNotExist(err) {
if err = os.Mkdir(path, os.FileMode(0777)); err != nil {
log.Println("error", r.RemoteAddr, "mkdir", pkgName, err)
HRRootTmpl = template.Must(template.New("hr-root").Parse(`<!DOCTYPE html>
<html>
<head>
- <title>{{.Version}}: human readable listing</title>
+ <title>{{.Version}}: packages</title>
</head>
<body>
<ul>{{range .Packages}}
)
func serveHRRoot(w http.ResponseWriter, r *http.Request) {
- files, err := ioutil.ReadDir(*Root)
+ files, err := ioutil.ReadDir(Root)
if err != nil {
log.Println("error", r.RemoteAddr, "hr-root", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func goodIntegrity() bool {
- dirs, err := ioutil.ReadDir(*Root)
+ dirs, err := ioutil.ReadDir(Root)
if err != nil {
log.Fatal(err)
}
isGood := true
var pkgName string
for _, dir := range dirs {
- files, err := ioutil.ReadDir(filepath.Join(*Root, dir.Name()))
+ files, err := ioutil.ReadDir(filepath.Join(Root, dir.Name()))
if err != nil {
log.Fatal(err)
}
pkgName = strings.TrimSuffix(file.Name(), "."+HashAlgoSHA256)
if !checkFile(
pkgName,
- filepath.Join(*Root, dir.Name(), pkgName),
- filepath.Join(*Root, dir.Name(), file.Name()),
+ filepath.Join(Root, dir.Name(), pkgName),
+ filepath.Join(Root, dir.Name(), file.Name()),
"SHA256", hasherSHA256, digestSHA256,
) {
isGood = false
pkgName = strings.TrimSuffix(file.Name(), "."+HashAlgoBLAKE2b256)
if !checkFile(
pkgName,
- filepath.Join(*Root, dir.Name(), pkgName),
- filepath.Join(*Root, dir.Name(), file.Name()),
+ filepath.Join(Root, dir.Name(), pkgName),
+ filepath.Join(Root, dir.Name(), file.Name()),
"BLAKE2b-256", hasherBLAKE2b256, digestBLAKE2b256,
) {
isGood = false
if err != nil {
return nil, nil, err
}
- metadata, err := ioutil.ReadFile(filepath.Join(*Root, pkgName, MetadataFile))
+ metadata, err := ioutil.ReadFile(filepath.Join(Root, pkgName, MetadataFile))
if err != nil {
if !os.IsNotExist(err) {
return nil, nil, err
)
func listRoot(w http.ResponseWriter, r *http.Request) {
- files, err := ioutil.ReadDir(*Root)
+ files, err := ioutil.ReadDir(Root)
if err != nil {
log.Println("error", r.RemoteAddr, "root", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func listDir(pkgName string, doSize bool) (int, []*PkgReleaseInfo, error) {
- dirPath := filepath.Join(*Root, pkgName)
+ dirPath := filepath.Join(Root, pkgName)
entries, err := os.ReadDir(dirPath)
if err != nil {
return 0, nil, err
pkgName string,
autorefresh, gpgUpdate bool,
) {
- dirPath := filepath.Join(*Root, pkgName)
+ dirPath := filepath.Join(Root, pkgName)
if autorefresh {
if !refreshDir(w, r, pkgName, "", gpgUpdate) {
return
const (
Version = "3.0.0"
UserAgent = "GoCheese/" + Version
-
- Warranty = `This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, version 3 of the License.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program. If not, see <http://www.gnu.org/licenses/>.`
)
var (
- Root = flag.String("root", "./packages", "Path to packages directory")
- Bind = flag.String("bind", "[::]:8080", "Address to bind to")
- MaxClients = flag.Int("maxclients", 128, "Maximal amount of simultaneous clients")
- DoUCSPI = flag.Bool("ucspi", false, "Work as UCSPI-TCP service")
+ Root string
+ Bind = flag.String("bind", DefaultBind, "")
+ MaxClients = flag.Int("maxclients", DefaultMaxClients, "")
+ DoUCSPI = flag.Bool("ucspi", false, "")
- TLSCert = flag.String("tls-cert", "", "Path to TLS X.509 certificate")
- TLSKey = flag.String("tls-key", "", "Path to TLS X.509 private key")
+ TLSCert = flag.String("tls-cert", "", "")
+ TLSKey = flag.String("tls-key", "", "")
- NoRefreshURLPath = flag.String("norefresh", "/norefresh/", "Non-refreshing URL path")
- RefreshURLPath = flag.String("refresh", "/simple/", "Auto-refreshing URL path")
- GPGUpdateURLPath = flag.String("gpgupdate", "/gpgupdate/", "GPG forceful refreshing URL path")
- JSONURLPath = flag.String("json", "/pypi/", "JSON API URL path")
+ NoRefreshURLPath = flag.String("norefresh", DefaultNoRefreshURLPath, "")
+ RefreshURLPath = flag.String("refresh", DefaultRefreshURLPath, "")
+ GPGUpdateURLPath = flag.String("gpgupdate", DefaultGPGUpdateURLPath, "")
+ JSONURLPath = flag.String("json", DefaultJSONURLPath, "")
- PyPIURL = flag.String("pypi", "https://pypi.org/simple/", "Upstream (PyPI) URL")
- PyPICertHash = flag.String("pypi-cert-hash", "", "Authenticate upstream by its X.509 certificate's SPKI SHA256 hash")
- JSONURL = flag.String("pypi-json", "https://pypi.org/pypi/", "Enable and use specified JSON API upstream URL")
+ PyPIURL = flag.String("pypi", DefaultPyPIURL, "")
+ JSONURL = flag.String("pypi-json", DefaultJSONURL, "")
+ PyPICertHash = flag.String("pypi-cert-hash", "", "")
- PasswdPath = flag.String("passwd", "", "Path to FIFO for upload authentication")
- PasswdListPath = flag.String("passwd-list", "", "Path to FIFO for login listing")
- PasswdCheck = flag.Bool("passwd-check", false, "Run password checker")
+ PasswdPath = flag.String("passwd", "", "")
+ PasswdListPath = flag.String("passwd-list", "", "")
+ PasswdCheck = flag.Bool("passwd-check", false, "")
- LogTimestamped = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages")
- FSCK = flag.Bool("fsck", false, "Check integrity of all packages (errors are in stderr)")
- DoVersion = flag.Bool("version", false, "Print version information")
- DoWarranty = flag.Bool("warranty", false, "Print warranty information")
+ LogTimestamped = flag.Bool("log-timestamped", false, "")
+ FSCK = flag.Bool("fsck", false, "")
+ DoVersion = flag.Bool("version", false, "")
+ DoWarranty = flag.Bool("warranty", false, "")
Killed bool
)
func servePkg(w http.ResponseWriter, r *http.Request, pkgName, filename string) {
log.Println(r.RemoteAddr, "get", filename)
- path := filepath.Join(*Root, pkgName, filename)
+ path := filepath.Join(Root, pkgName, filename)
if _, err := os.Stat(path); os.IsNotExist(err) {
if !refreshDir(w, r, pkgName, filename, false) {
return
}
func main() {
+ flag.Usage = usage
flag.Parse()
if *DoWarranty {
fmt.Println(Warranty)
log.SetOutput(os.Stdout)
}
+ if len(flag.Args()) != 1 {
+ usage()
+ os.Exit(1)
+ }
+ Root = flag.Args()[0]
+ if _, err := os.Stat(Root); err != nil {
+ log.Fatalln(err)
+ }
+
if *FSCK {
if !goodIntegrity() {
os.Exit(1)
log.Println(
UserAgent, "ready:",
- "root:", *Root,
+ "root:", Root,
"bind:", *Bind,
"pypi:", *PyPIURL,
- "json:", *JSONURL,
- "hr: /",
)
if *TLSCert == "" {
err = server.Serve(ln)
pkgName, filenameGet string,
gpgUpdate bool,
) bool {
- if _, err := os.Stat(filepath.Join(*Root, pkgName, InternalFlag)); err == nil {
+ if _, err := os.Stat(filepath.Join(Root, pkgName, InternalFlag)); err == nil {
return true
}
c := http.Client{Transport: &PyPIHTTPTransport}
- dirPath := filepath.Join(*Root, pkgName)
+ dirPath := filepath.Join(Root, pkgName)
now := time.Now()
var allReleases map[string][]*PkgReleaseInfo
return
}
pkgName := strings.ToLower(NormalizationRe.ReplaceAllString(pkgNames[0], "-"))
- dirPath := filepath.Join(*Root, pkgName)
+ dirPath := filepath.Join(Root, pkgName)
gpgSigsExpected := make(map[string]struct{})
now := time.Now().UTC()
--- /dev/null
+/*
+GoCheese -- Python private package repository and caching proxy
+Copyright (C) 2019-2021 Sergey Matveev <stargrave@stargrave.org>
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+ "fmt"
+ "os"
+)
+
+const (
+ DefaultBind = "[::]:8080"
+ DefaultMaxClients = 128
+ DefaultNoRefreshURLPath = "/norefresh/"
+ DefaultRefreshURLPath = "/simple/"
+ DefaultGPGUpdateURLPath = "/gpgupdate/"
+ DefaultJSONURLPath = "/pypi/"
+ DefaultPyPIURL = "https://pypi.org/simple/"
+ DefaultJSONURL = "https://pypi.org/pypi/"
+
+ Warranty = `This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, version 3 of the License.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.`
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage: gocheese [OPTIONS ...] /path/to/packages
+
+Network transport options:
+ -ucspi -- Work as UCSPI-TCP service instead of listening
+ -bind HOST:PORT -- TCP address to bind to (default: %s)
+ -maxclients N -- Maximal amount of simultaneous clients (default: %d)
+
+TLS enabling options:
+ -tls-cert PEM -- Path to TLS X.509 certificate
+ -tls-key PEM -- Path to TLS X.509 private key
+
+HTTP endpoints:
+ -norefresh URLPATH -- Non-refreshing Simple API path (default: %s)
+ -refresh URLPATH -- Auto-refreshing Simple API path (default: %s)
+ -gpgupdate URLPATH -- GPG forceful refreshing path (default: %s)
+ -json URLPATH -- JSON API path (default: %s)
+
+Upstream PyPI:
+ -pypi URL -- Upstream Simple API (default: %s)
+ -pypi-json URL -- Enable and use specified JSON API (default: %s)
+ Disabled if empty.
+ -pypi-cert-hash HEX(SHA256(SPKI)) -- Authenticate upstream by its
+ X.509 certificate's hash
+Password management:
+ -passwd PATH -- Path to readable FIFO for loading passwords
+ -passwd-list PATH -- Path to writeable FIFO for listing logins
+ -passwd-check -- Verify passwords format from stdin, then exit
+
+Other options:
+ -log-timestamped -- Prepend timestamp to log messages
+ -fsck -- Run integrity check of all packages
+ -version -- Print version information
+ -warranty -- Print warranty information
+
+GOCHEESE_NO_SYNC=1 environment variable disable filesystem fsyncs.
+`,
+ DefaultBind,
+ DefaultMaxClients,
+ DefaultNoRefreshURLPath,
+ DefaultRefreshURLPath,
+ DefaultGPGUpdateURLPath,
+ DefaultJSONURLPath,
+ DefaultPyPIURL,
+ DefaultJSONURLPath,
+ )
+}