Caching of packages, new config format

This commit is contained in:
Tyler 2017-06-11 20:41:49 -04:00
parent 1b7951fb75
commit bed2ffbec7
8 changed files with 360 additions and 265 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
/deb-simple /deb-simple
/simple
/repo /repo
vendor vendor
conf.json conf.ini
*.pprof *.pprof

View File

@ -1,12 +1,13 @@
{ [http]
"listenPort" : "9090", port = 9090
"rootRepoPath" : "/opt/deb-simple/repo", key = "abcdefg"
"supportedArch" : ["all","i386","amd64"], ssl = false
"distroNames":["stable"],
"pgpSecretKey": "secring.gpg", [repo]
"pgpPassphrase" : "", root = "repo/"
"enableSSL" : false, distros = "stable"
"SSLcert" : "server.crt", architectures = "all,i386,amd64"
"SSLkey" : "server.key",
"key" : "abcdefg" [pgp]
} key = ""
passphrase = ""

View File

@ -1 +1,56 @@
package simple package simple
import (
"path/filepath"
"strings"
)
type Conf struct {
Http HttpConf `ini:"http"`
Repo RepoConf `ini:"repo"`
PGP PGPConf `ini:"pgp"`
}
type HttpConf struct {
Port int `ini:"port"`
Key string `ini:"key"`
SSL bool `ini:"ssl"`
SSLCert string `ini:"cert"`
SSLKey string `ini:"key"`
}
type RepoConf struct {
Root string `ini:"root"`
Distros string `ini:"distros"`
Architectures string `ini:"architectures"`
}
type PGPConf struct {
Key string `ini:"key"`
Passphrase string `ini:"passphrase"`
}
func (c Conf) DistPath(distro string) string {
return filepath.Join(c.Repo.Root, "dists", distro)
}
func (c Conf) ArchPath(distro, arch string) string {
return filepath.Join(c.Repo.Root, "dists", distro, "main/binary-"+arch)
}
func (c Conf) PoolPath(distro, arch string) string {
return filepath.Join(c.Repo.Root, "pool/main", distro, arch)
}
func (c Conf) PoolPackagePath(distro, arch, name string) string {
name = packageName(name)
return filepath.Join(c.Repo.Root, "pool/main", distro, arch, name[0:1], name)
}
func (c RepoConf) DistroNames() []string {
return strings.Split(c.Distros, ",")
}
func (c RepoConf) ArchitectureNames() []string {
return strings.Split(c.Architectures, ",")
}

View File

@ -1,10 +1,12 @@
hash: 79d0f0e99e99ce9318bb0e8c6ab398657a20679dbe3c421c6fc588db7a738131 hash: 33760adb1090508f6d56dfc1d600a664790a280e5fc4d84cb26a7e11ad0f1f35
updated: 2017-06-11T03:04:15.102617-04:00 updated: 2017-06-11T20:13:27.6668377-04:00
imports: imports:
- name: github.com/blakesmith/ar - name: github.com/blakesmith/ar
version: 8bd4349a67f2533b078dbc524689d15dba0f4659 version: 8bd4349a67f2533b078dbc524689d15dba0f4659
- name: github.com/blang/semver - name: github.com/blang/semver
version: b38d23b8782a487059e8fc8773e9a5b228a77cb6 version: b38d23b8782a487059e8fc8773e9a5b228a77cb6
- name: github.com/go-ini/ini
version: d3de07a94d22b4a0972deb4b96d790c2c0ce8333
- name: golang.org/x/crypto - name: golang.org/x/crypto
version: 7e9105388ebff089b3f99f0ef676ea55a6da3a7e version: 7e9105388ebff089b3f99f0ef676ea55a6da3a7e
subpackages: subpackages:

View File

@ -6,3 +6,5 @@ import:
- openpgp - openpgp
- package: github.com/blang/semver - package: github.com/blang/semver
version: ^3.5.0 version: ^3.5.0
- package: github.com/go-ini/ini
version: ^1.28.0

View File

@ -11,12 +11,31 @@ import (
"github.com/blang/semver" "github.com/blang/semver"
) )
func uploadHandler(config Conf) http.Handler { func rescanHandler(w http.ResponseWriter, r *http.Request) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { distroName := r.URL.Query().Get("distro")
if distroName == "" {
distroName = "stable"
}
if _, exists := distros[distroName]; !exists {
httpErrorf(w, "Unable to find distro %s", distroName)
return
}
distro := &Distro{Name: distroName, Architectures: make(map[string]map[string]*PackageFile)}
scanInitialPackages(conf, distro)
distros[distroName] = distro
}
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" { if r.Method != "POST" {
http.Error(w, "method not supported", http.StatusMethodNotAllowed) http.Error(w, "method not supported", http.StatusMethodNotAllowed)
return return
} }
archType := r.URL.Query().Get("arch") archType := r.URL.Query().Get("arch")
if archType == "" { if archType == "" {
@ -31,7 +50,7 @@ func uploadHandler(config Conf) http.Handler {
key := r.URL.Query().Get("key") key := r.URL.Query().Get("key")
if key == "" || key != config.Key { if key == "" || key != conf.Http.Key {
http.Error(w, "unauthorized", 403) http.Error(w, "unauthorized", 403)
return return
} }
@ -85,7 +104,7 @@ func uploadHandler(config Conf) http.Handler {
if part.FileName() == "" { if part.FileName() == "" {
continue continue
} }
newPath := config.PoolPackagePath(distroName, archType, part.FileName()) newPath := conf.PoolPackagePath(distroName, archType, part.FileName())
if _, err := os.Stat(newPath); err != nil { if _, err := os.Stat(newPath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -143,24 +162,22 @@ func uploadHandler(config Conf) http.Handler {
// Recreate the package index and release file. // Recreate the package index and release file.
if err := createPackagesCached(config, distroName, archType, packages); err != nil { if err := createPackagesCached(conf, distroName, archType, packages); err != nil {
httpErrorf(w, "error creating package: %s", err) httpErrorf(w, "error creating package: %s", err)
return return
} }
err = createRelease(config, distroName, archType) err = createRelease(conf, distroName, archType)
if err != nil { if err != nil {
httpErrorf(w, "error creating package: %s", err) httpErrorf(w, "error creating package: %s", err)
return return
} }
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusCreated)
})
} }
func deleteHandler(config Conf) http.Handler { func deleteHandler(w http.ResponseWriter, r *http.Request) {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" { if r.Method != "DELETE" {
http.Error(w, "method not supported", http.StatusMethodNotAllowed) http.Error(w, "method not supported", http.StatusMethodNotAllowed)
return return
@ -192,33 +209,34 @@ func deleteHandler(config Conf) http.Handler {
mutex.RUnlock() mutex.RUnlock()
debPath := filepath.Join(config.ArchPath(req.DistroName, req.Arch), req.Filename) key := r.URL.Query().Get("key")
if key == "" || key != conf.Http.Key {
http.Error(w, "unauthorized", http.StatusForbidden)
return
}
debPath := filepath.Join(conf.ArchPath(req.DistroName, req.Arch), req.Filename)
if err := os.Remove(debPath); err != nil { if err := os.Remove(debPath); err != nil {
httpErrorf(w, "failed to delete: %s", err) httpErrorf(w, "failed to delete: %s", err)
return return
} }
key := r.URL.Query().Get("key")
if key == "" || key != config.Key {
http.Error(w, "unauthorized", http.StatusForbidden)
return
}
mutex.Lock() mutex.Lock()
defer mutex.Unlock() defer mutex.Unlock()
log.Println("got lock, updating package list...") if err := createPackagesCached(conf, req.DistroName, req.Arch, packages); err != nil {
if err := createPackagesCached(config, req.DistroName, req.Arch, packages); err != nil {
httpErrorf(w, "failed to delete package: %s", err) httpErrorf(w, "failed to delete package: %s", err)
return return
} }
if err := createRelease(config, req.DistroName, req.Arch); err != nil { if err := createRelease(conf, req.DistroName, req.Arch); err != nil {
httpErrorf(w, "failed to delete package: %s", err) httpErrorf(w, "failed to delete package: %s", err)
return return
} }
})
w.WriteHeader(http.StatusOK)
} }
func httpErrorf(w http.ResponseWriter, format string, a ...interface{}) { func httpErrorf(w http.ResponseWriter, format string, a ...interface{}) {

View File

@ -16,6 +16,7 @@ import (
"crypto/sha1" "crypto/sha1"
"crypto/sha256" "crypto/sha256"
"github.com/blang/semver" "github.com/blang/semver"
"encoding/json"
) )
var ( var (
@ -23,20 +24,20 @@ var (
) )
type PackageFile struct { type PackageFile struct {
Path string Path string `json:"path"`
Name string Name string `json:"name"`
Size int64 Size int64 `json:"size"`
MD5Hash string MD5Hash string `json:"md5"`
SHA1Hash string SHA1Hash string `json:"sha1"`
SHA256Hash string SHA256Hash string `json:"sha256"`
ControlData string ControlData string `json:"control"`
Info *Package Info *Package `json:"info"`
} }
type Package struct { type Package struct {
Package string Package string `json:"package"`
Version string Version string `json:"version"`
Architecture string Architecture string `json:"architecture"`
} }
func parsePackageData(ctlData string) *Package { func parsePackageData(ctlData string) *Package {
@ -57,14 +58,50 @@ func parsePackageData(ctlData string) *Package {
} }
type Distro struct { type Distro struct {
Name string Name string `json:"name"`
Architectures map[string]map[string]*PackageFile Architectures map[string]map[string]*PackageFile `json:"architectures"`
} }
var ( var (
distros map[string]*Distro = make(map[string]*Distro) distros map[string]*Distro = make(map[string]*Distro)
) )
func loadCache(dist string) error {
f, err := os.Open(filepath.Join(conf.DistPath(dist), "dist.json"))
if err != nil {
return err
}
defer f.Close()
var distro Distro
if err := json.NewDecoder(f).Decode(&distro); err != nil {
return err
}
distros[dist] = &distro
return nil
}
func saveCache(dist *Distro) error {
f, err := os.Create(filepath.Join(conf.DistPath(dist.Name), "dist.json"))
if err != nil {
return err
}
defer f.Close()
if err := json.NewEncoder(f).Encode(dist); err != nil {
return err
}
return nil
}
func buildPackageList(config Conf, distro, arch string) (map[string]*PackageFile, error) { func buildPackageList(config Conf, distro, arch string) (map[string]*PackageFile, error) {
m := make(map[string]*PackageFile) m := make(map[string]*PackageFile)

View File

@ -1,53 +1,22 @@
package simple package simple
import ( import (
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
"path/filepath"
"sync" "sync"
"strings" "strings"
"golang.org/x/crypto/openpgp" "golang.org/x/crypto/openpgp"
"runtime" "runtime"
"errors" "errors"
"github.com/go-ini/ini"
) )
var VERSION string = "1.1.0" var VERSION string = "1.1.0"
type Conf struct {
ListenPort string `json:"listenPort"`
RootRepoPath string `json:"rootRepoPath"`
SupportArch []string `json:"supportedArch"`
DistroNames []string `json:"distroNames"`
PGPSecretKey string `json:"pgpSecretKey"`
PGPPassphrase string `json:"pgpPassphrase"`
EnableSSL bool `json:"enableSSL"`
SSLCert string `json:"SSLcert"`
SSLKey string `json:"SSLkey"`
Key string `json:"key"`
}
func (c Conf) DistPath(distro string) string {
return filepath.Join(c.RootRepoPath, "dists", distro)
}
func (c Conf) ArchPath(distro, arch string) string {
return filepath.Join(c.RootRepoPath, "dists", distro, "main/binary-"+arch)
}
func (c Conf) PoolPath(distro, arch string) string {
return filepath.Join(c.RootRepoPath, "pool/main", distro, arch)
}
func (c Conf) PoolPackagePath(distro, arch, name string) string {
name = packageName(name)
return filepath.Join(c.RootRepoPath, "pool/main", distro, arch, name[0:1], name)
}
func packageName(name string) string { func packageName(name string) string {
if index := strings.Index(name, "_"); index != -1 { if index := strings.Index(name, "_"); index != -1 {
name = name[:index] name = name[:index]
@ -63,9 +32,9 @@ type DeleteObj struct {
var ( var (
mutex sync.RWMutex mutex sync.RWMutex
configFile = flag.String("c", "conf.json", "config file location") configFile = flag.String("c", "conf.ini", "config file location")
flagShowVersion = flag.Bool("version", false, "Show dnsconfig version") flagShowVersion = flag.Bool("version", false, "Show dnsconfig version")
parsedConfig = Conf{} conf = Conf{}
pgpEntity *openpgp.Entity pgpEntity *openpgp.Entity
) )
@ -83,45 +52,53 @@ func Start() {
log.Fatalln("unable to read config file, exiting...") log.Fatalln("unable to read config file, exiting...")
} }
if err := json.Unmarshal(file, &parsedConfig); err != nil { if err := ini.MapTo(&conf, file); err != nil {
log.Fatalln("unable to marshal config file, exiting...") log.Fatalln("unable to marshal config file, exiting...", err)
} }
if err := createDirs(parsedConfig); err != nil { if err := createDirs(conf); err != nil {
log.Println(err) log.Println(err)
log.Fatalln("error creating directory structure, exiting") log.Fatalln("error creating directory structure, exiting")
} }
if err := setupPgp(parsedConfig); err != nil { if err := setupPgp(conf); err != nil {
log.Println(err) log.Println(err)
log.Fatalln("error loading pgp key, exiting") log.Fatalln("error loading pgp key, exiting")
} }
log.Println("Indexing packages...") log.Println("Indexing packages...")
for _, dist := range parsedConfig.DistroNames { for _, dist := range conf.Repo.DistroNames() {
if err := loadCache(dist); err != nil {
log.Println("Unable to load cached data for", dist, "- reindexing")
distro := &Distro{Name: dist, Architectures: make(map[string]map[string]*PackageFile)} distro := &Distro{Name: dist, Architectures: make(map[string]map[string]*PackageFile)}
go scanInitialPackages(parsedConfig, distro) go scanInitialPackages(conf, distro)
distros[dist] = distro distros[dist] = distro
} }
}
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(parsedConfig.RootRepoPath)))) mux := http.NewServeMux()
http.Handle("/upload", uploadHandler(parsedConfig))
http.Handle("/delete", deleteHandler(parsedConfig))
if parsedConfig.EnableSSL { mux.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(conf.Repo.Root))))
mux.HandleFunc("/rescan", rescanHandler)
mux.HandleFunc("/upload", uploadHandler)
mux.HandleFunc("/delete", deleteHandler)
bind := fmt.Sprintf(":%d", conf.Http.Port)
if conf.Http.SSL {
log.Println("running with SSL enabled") log.Println("running with SSL enabled")
log.Fatalln(http.ListenAndServeTLS(":"+parsedConfig.ListenPort, parsedConfig.SSLCert, parsedConfig.SSLKey, nil)) log.Fatalln(http.ListenAndServeTLS(bind, conf.Http.SSLCert, conf.Http.SSLKey, mux))
} else { } else {
log.Println("running without SSL enabled") log.Println("running without SSL enabled")
log.Fatalln(http.ListenAndServe(":"+parsedConfig.ListenPort, nil)) log.Fatalln(http.ListenAndServe(bind, mux))
} }
} }
func scanInitialPackages(config Conf, dist *Distro) { func scanInitialPackages(config Conf, dist *Distro) {
for _, arch := range config.SupportArch { for _, arch := range config.Repo.ArchitectureNames() {
files, err := buildPackageList(config, dist.Name, arch) files, err := buildPackageList(config, dist.Name, arch)
if err != nil { if err != nil {
@ -134,14 +111,16 @@ func scanInitialPackages(config Conf, dist *Distro) {
createPackagesCached(config, dist.Name, arch, files) createPackagesCached(config, dist.Name, arch, files)
createRelease(config, dist.Name, arch) createRelease(config, dist.Name, arch)
} }
saveCache(dist)
} }
func setupPgp(config Conf) error { func setupPgp(config Conf) error {
if config.PGPSecretKey == "" { if config.PGP.Key == "" {
return nil return nil
} }
secretKey, err := os.Open(config.PGPSecretKey) secretKey, err := os.Open(config.PGP.Key)
if err != nil { if err != nil {
return fmt.Errorf("failed to open private key ring file: %s", err) return fmt.Errorf("failed to open private key ring file: %s", err)
} }
@ -159,7 +138,7 @@ func setupPgp(config Conf) error {
pgpEntity = entitylist[0] pgpEntity = entitylist[0]
passphrase := []byte(config.PGPPassphrase) passphrase := []byte(config.PGP.Passphrase)
if err := pgpEntity.PrivateKey.Decrypt(passphrase); err != nil { if err := pgpEntity.PrivateKey.Decrypt(passphrase); err != nil {
return err return err
@ -177,8 +156,8 @@ func setupPgp(config Conf) error {
} }
func createDirs(config Conf) error { func createDirs(config Conf) error {
for _, distro := range config.DistroNames { for _, distro := range config.Repo.DistroNames() {
for _, arch := range config.SupportArch { for _, arch := range config.Repo.ArchitectureNames() {
if _, err := os.Stat(config.ArchPath(distro, arch)); err != nil { if _, err := os.Stat(config.ArchPath(distro, arch)); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
log.Printf("Directory for %s (%s) does not exist, creating", distro, arch) log.Printf("Directory for %s (%s) does not exist, creating", distro, arch)