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
/simple
/repo
vendor
conf.json
conf.ini
*.pprof

View File

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

View File

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

View File

@ -6,3 +6,5 @@ import:
- openpgp
- package: github.com/blang/semver
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"
)
func uploadHandler(config Conf) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
func rescanHandler(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" {
http.Error(w, "method not supported", http.StatusMethodNotAllowed)
return
}
archType := r.URL.Query().Get("arch")
if archType == "" {
@ -31,7 +50,7 @@ func uploadHandler(config Conf) http.Handler {
key := r.URL.Query().Get("key")
if key == "" || key != config.Key {
if key == "" || key != conf.Http.Key {
http.Error(w, "unauthorized", 403)
return
}
@ -85,7 +104,7 @@ func uploadHandler(config Conf) http.Handler {
if part.FileName() == "" {
continue
}
newPath := config.PoolPackagePath(distroName, archType, part.FileName())
newPath := conf.PoolPackagePath(distroName, archType, part.FileName())
if _, err := os.Stat(newPath); err != nil {
if os.IsNotExist(err) {
@ -143,24 +162,22 @@ func uploadHandler(config Conf) http.Handler {
// 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)
return
}
err = createRelease(config, distroName, archType)
err = createRelease(conf, distroName, archType)
if err != nil {
httpErrorf(w, "error creating package: %s", err)
return
}
w.WriteHeader(http.StatusOK)
})
w.WriteHeader(http.StatusCreated)
}
func deleteHandler(config Conf) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
func deleteHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "DELETE" {
http.Error(w, "method not supported", http.StatusMethodNotAllowed)
return
@ -192,33 +209,34 @@ func deleteHandler(config Conf) http.Handler {
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 {
httpErrorf(w, "failed to delete: %s", err)
return
}
key := r.URL.Query().Get("key")
if key == "" || key != config.Key {
http.Error(w, "unauthorized", http.StatusForbidden)
return
}
mutex.Lock()
defer mutex.Unlock()
log.Println("got lock, updating package list...")
if err := createPackagesCached(config, req.DistroName, req.Arch, packages); err != nil {
if err := createPackagesCached(conf, req.DistroName, req.Arch, packages); err != nil {
httpErrorf(w, "failed to delete package: %s", err)
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)
return
}
})
w.WriteHeader(http.StatusOK)
}
func httpErrorf(w http.ResponseWriter, format string, a ...interface{}) {

View File

@ -16,6 +16,7 @@ import (
"crypto/sha1"
"crypto/sha256"
"github.com/blang/semver"
"encoding/json"
)
var (
@ -23,20 +24,20 @@ var (
)
type PackageFile struct {
Path string
Name string
Size int64
MD5Hash string
SHA1Hash string
SHA256Hash string
ControlData string
Info *Package
Path string `json:"path"`
Name string `json:"name"`
Size int64 `json:"size"`
MD5Hash string `json:"md5"`
SHA1Hash string `json:"sha1"`
SHA256Hash string `json:"sha256"`
ControlData string `json:"control"`
Info *Package `json:"info"`
}
type Package struct {
Package string
Version string
Architecture string
Package string `json:"package"`
Version string `json:"version"`
Architecture string `json:"architecture"`
}
func parsePackageData(ctlData string) *Package {
@ -57,14 +58,50 @@ func parsePackageData(ctlData string) *Package {
}
type Distro struct {
Name string
Architectures map[string]map[string]*PackageFile
Name string `json:"name"`
Architectures map[string]map[string]*PackageFile `json:"architectures"`
}
var (
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) {
m := make(map[string]*PackageFile)

View File

@ -1,53 +1,22 @@
package simple
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"strings"
"golang.org/x/crypto/openpgp"
"runtime"
"errors"
"github.com/go-ini/ini"
)
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 {
if index := strings.Index(name, "_"); index != -1 {
name = name[:index]
@ -63,9 +32,9 @@ type DeleteObj struct {
var (
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")
parsedConfig = Conf{}
conf = Conf{}
pgpEntity *openpgp.Entity
)
@ -83,45 +52,53 @@ func Start() {
log.Fatalln("unable to read config file, exiting...")
}
if err := json.Unmarshal(file, &parsedConfig); err != nil {
log.Fatalln("unable to marshal config file, exiting...")
if err := ini.MapTo(&conf, file); err != nil {
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.Fatalln("error creating directory structure, exiting")
}
if err := setupPgp(parsedConfig); err != nil {
if err := setupPgp(conf); err != nil {
log.Println(err)
log.Fatalln("error loading pgp key, exiting")
}
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)}
go scanInitialPackages(parsedConfig, distro)
go scanInitialPackages(conf, distro)
distros[dist] = distro
}
}
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir(parsedConfig.RootRepoPath))))
http.Handle("/upload", uploadHandler(parsedConfig))
http.Handle("/delete", deleteHandler(parsedConfig))
mux := http.NewServeMux()
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.Fatalln(http.ListenAndServeTLS(":"+parsedConfig.ListenPort, parsedConfig.SSLCert, parsedConfig.SSLKey, nil))
log.Fatalln(http.ListenAndServeTLS(bind, conf.Http.SSLCert, conf.Http.SSLKey, mux))
} else {
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) {
for _, arch := range config.SupportArch {
for _, arch := range config.Repo.ArchitectureNames() {
files, err := buildPackageList(config, dist.Name, arch)
if err != nil {
@ -134,14 +111,16 @@ func scanInitialPackages(config Conf, dist *Distro) {
createPackagesCached(config, dist.Name, arch, files)
createRelease(config, dist.Name, arch)
}
saveCache(dist)
}
func setupPgp(config Conf) error {
if config.PGPSecretKey == "" {
if config.PGP.Key == "" {
return nil
}
secretKey, err := os.Open(config.PGPSecretKey)
secretKey, err := os.Open(config.PGP.Key)
if err != nil {
return fmt.Errorf("failed to open private key ring file: %s", err)
}
@ -159,7 +138,7 @@ func setupPgp(config Conf) error {
pgpEntity = entitylist[0]
passphrase := []byte(config.PGPPassphrase)
passphrase := []byte(config.PGP.Passphrase)
if err := pgpEntity.PrivateKey.Decrypt(passphrase); err != nil {
return err
@ -177,8 +156,8 @@ func setupPgp(config Conf) error {
}
func createDirs(config Conf) error {
for _, distro := range config.DistroNames {
for _, arch := range config.SupportArch {
for _, distro := range config.Repo.DistroNames() {
for _, arch := range config.Repo.ArchitectureNames() {
if _, err := os.Stat(config.ArchPath(distro, arch)); err != nil {
if os.IsNotExist(err) {
log.Printf("Directory for %s (%s) does not exist, creating", distro, arch)