HashAlgoMD5,
}
- root = flag.String("root", "./packages", "Path to packages directory")
- bind = flag.String("bind", "[::]:8080", "Address to bind to")
- tlsCert = flag.String("tls-cert", "", "Path to TLS X.509 certificate")
- tlsKey = flag.String("tls-key", "", "Path to TLS X.509 private key")
+ 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")
+
+ tlsCert = flag.String("tls-cert", "", "Path to TLS X.509 certificate")
+ tlsKey = flag.String("tls-key", "", "Path to TLS X.509 private 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")
- 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")
- logTimestamped = flag.Bool("log-timestamped", false, "Prepend timestmap to log messages")
- passwdPath = flag.String("passwd", "", "Path to FIFO for upload authentication")
- passwdCheck = flag.Bool("passwd-check", false, "Run password checker")
- fsck = flag.Bool("fsck", false, "Check integrity of all packages (errors are in stderr)")
- maxClients = flag.Int("maxclients", 128, "Maximal amount of simultaneous clients")
- version = flag.Bool("version", false, "Print version information")
- warranty = flag.Bool("warranty", false, "Print warranty information")
+
+ 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")
+
+ passwdPath = flag.String("passwd", "", "Path to FIFO for upload authentication")
+ passwdCheck = flag.Bool("passwd-check", false, "Run password checker")
+
+ 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)")
+ version = flag.Bool("version", false, "Print version information")
+ warranty = flag.Bool("warranty", false, "Print warranty information")
killed bool
pypiURLParsed *url.URL
} else {
log.SetFlags(log.Lshortfile)
}
- log.SetOutput(os.Stdout)
+ if !*doUCSPI {
+ log.SetOutput(os.Stdout)
+ }
if *fsck {
if !goodIntegrity() {
}
}
- ln, err := net.Listen("tcp", *bind)
- if err != nil {
- log.Fatal(err)
- }
- ln = netutil.LimitListener(ln, *maxClients)
server := &http.Server{
ReadTimeout: time.Minute,
WriteTimeout: time.Minute,
http.HandleFunc(*gpgUpdateURLPath, handler)
}
+ if *doUCSPI {
+ server.SetKeepAlivesEnabled(false)
+ ln := &UCSPI{}
+ server.ConnState = connStater
+ err := server.Serve(ln)
+ if _, ok := err.(UCSPIAlreadyAccepted); !ok {
+ log.Fatalln(err)
+ }
+ UCSPIJob.Wait()
+ return
+ }
+
+ ln, err := net.Listen("tcp", *bind)
+ if err != nil {
+ log.Fatal(err)
+ }
+ ln = netutil.LimitListener(ln, *maxClients)
+
needsShutdown := make(chan os.Signal, 0)
exitErr := make(chan error, 0)
signal.Notify(needsShutdown, syscall.SIGTERM, syscall.SIGINT)
--- /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 (
+ "io"
+ "net"
+ "net/http"
+ "os"
+ "sync"
+ "time"
+)
+
+var (
+ aLongTimeAgo = time.Unix(1, 0)
+ UCSPIJob sync.WaitGroup
+)
+
+type UCSPIAddr struct {
+ ip string
+ port string
+}
+
+func (addr *UCSPIAddr) Network() string { return "tcp" }
+
+func (addr *UCSPIAddr) String() string { return addr.ip + ":" + addr.port }
+
+type UCSPIConn struct {
+ eof chan struct{}
+}
+
+type ReadResult struct {
+ n int
+ err error
+}
+
+func (conn *UCSPIConn) Read(b []byte) (int, error) {
+ c := make(chan ReadResult)
+ go func() {
+ n, err := os.Stdin.Read(b)
+ c <- ReadResult{n, err}
+ }()
+ select {
+ case res := <-c:
+ return res.n, res.err
+ case <-conn.eof:
+ return 0, io.EOF
+ }
+}
+
+func (conn *UCSPIConn) Write(b []byte) (int, error) { return os.Stdout.Write(b) }
+
+func (conn *UCSPIConn) Close() error {
+ if err := os.Stdin.Close(); err != nil {
+ return err
+ }
+ return os.Stdout.Close()
+}
+
+func (conn *UCSPIConn) LocalAddr() net.Addr {
+ return &UCSPIAddr{ip: os.Getenv("TCPLOCALIP"), port: os.Getenv("TCPLOCALPORT")}
+}
+
+func (conn *UCSPIConn) RemoteAddr() net.Addr {
+ return &UCSPIAddr{ip: os.Getenv("TCPREMOTEIP"), port: os.Getenv("TCPREMOTEPORT")}
+}
+
+func (conn *UCSPIConn) SetDeadline(t time.Time) error {
+ if err := os.Stdin.SetReadDeadline(t); err != nil {
+ return err
+ }
+ return os.Stdout.SetWriteDeadline(t)
+}
+
+func (conn *UCSPIConn) SetReadDeadline(t time.Time) error {
+ // An ugly hack to forcefully terminate pending read.
+ // net/http calls SetReadDeadline(aLongTimeAgo), but file
+ // descriptors are not capable to exit immediately that way.
+ if t.Equal(aLongTimeAgo) {
+ conn.eof <- struct{}{}
+ }
+ return os.Stdin.SetReadDeadline(t)
+}
+
+func (conn *UCSPIConn) SetWriteDeadline(t time.Time) error {
+ return os.Stdout.SetWriteDeadline(t)
+}
+
+type UCSPI struct{ accepted bool }
+
+type UCSPIAlreadyAccepted struct{}
+
+func (u UCSPIAlreadyAccepted) Error() string {
+ return "already accepted"
+}
+
+func (ucspi *UCSPI) Accept() (net.Conn, error) {
+ if ucspi.accepted {
+ return nil, UCSPIAlreadyAccepted{}
+ }
+ ucspi.accepted = true
+ conn := UCSPIConn{eof: make(chan struct{}, 1)}
+ return &conn, nil
+}
+
+func (ucspi *UCSPI) Close() error {
+ return nil
+}
+
+func (ucspi *UCSPI) Addr() net.Addr {
+ return &UCSPIAddr{ip: os.Getenv("TCPLOCALIP"), port: os.Getenv("TCPLOCALPORT")}
+}
+
+func connStater(conn net.Conn, connState http.ConnState) {
+ switch connState {
+ case http.StateNew:
+ UCSPIJob.Add(1)
+ case http.StateClosed:
+ UCSPIJob.Done()
+ }
+}