295 lines
6.6 KiB
Go
295 lines
6.6 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
|
"github.com/aws/aws-sdk-go/aws/session"
|
|
s3 "github.com/fclairamb/afero-s3"
|
|
"github.com/go-ini/ini"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/afero"
|
|
"golang.org/x/crypto/openpgp"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
var VERSION = "1.4.4"
|
|
|
|
func packageName(name string) string {
|
|
if index := strings.Index(name, "_"); index != -1 {
|
|
name = name[:index]
|
|
}
|
|
return name
|
|
}
|
|
|
|
type DeleteObj struct {
|
|
Filename string
|
|
DistroName string
|
|
Arch string
|
|
}
|
|
|
|
var (
|
|
mutex sync.RWMutex
|
|
configFile = flag.String("c", "conf.ini", "config file location")
|
|
flagShowVersion = flag.Bool("version", false, "Show deb-simple version")
|
|
flagDebug = flag.Bool("debug", false, "debug output")
|
|
conf = Conf{}
|
|
pgpEntity *openpgp.Entity
|
|
fs afero.Fs
|
|
tmpFs afero.Fs
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if *flagDebug {
|
|
log.SetLevel(log.DebugLevel)
|
|
}
|
|
|
|
if *flagShowVersion {
|
|
fmt.Printf("deb-simple %s (%s)\n", VERSION, runtime.Version())
|
|
os.Exit(0)
|
|
}
|
|
|
|
log.WithFields(log.Fields{
|
|
"version": VERSION,
|
|
"runtimeVersion": runtime.Version(),
|
|
}).Info("Starting deb-simple")
|
|
|
|
file, err := ioutil.ReadFile(*configFile)
|
|
|
|
if err != nil {
|
|
log.WithError(err).Fatalln("unable to read config file, exiting...")
|
|
}
|
|
|
|
if err := ini.MapTo(&conf, file); err != nil {
|
|
log.WithError(err).Fatalln("unable to marshal config file, exiting...", err)
|
|
}
|
|
|
|
osFs := afero.NewOsFs()
|
|
|
|
switch conf.Fs.Driver {
|
|
case "s3":
|
|
awsConfig := &aws.Config{
|
|
Region: aws.String(conf.Fs.S3.Region),
|
|
Credentials: credentials.NewStaticCredentials(conf.Fs.S3.ID, conf.Fs.S3.Secret, ""),
|
|
}
|
|
|
|
if conf.Fs.S3.Endpoint != "" {
|
|
awsConfig.Endpoint = aws.String(conf.Fs.S3.Endpoint)
|
|
}
|
|
|
|
sess, err := session.NewSession(awsConfig)
|
|
|
|
if err != nil {
|
|
log.Fatalln("Unable to create S3 session:", err)
|
|
}
|
|
|
|
fs = s3.NewFs(conf.Fs.S3.Bucket, sess)
|
|
case "local":
|
|
fallthrough
|
|
default:
|
|
fs = afero.NewBasePathFs(osFs, conf.Repo.Root)
|
|
}
|
|
|
|
switch conf.Fs.TmpDriver {
|
|
case "memory":
|
|
tmpFs = afero.NewMemMapFs()
|
|
case "local":
|
|
fallthrough
|
|
default:
|
|
baseTempDir := path.Join(os.TempDir(), "deb-simple")
|
|
|
|
if _, err := os.Stat(baseTempDir); err != nil {
|
|
if os.IsNotExist(err) {
|
|
if err := os.MkdirAll(baseTempDir, 0755); err != nil {
|
|
log.Fatalln("Unable to create temp dir:", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
tmpFs = afero.NewBasePathFs(osFs, baseTempDir)
|
|
}
|
|
|
|
if err := createDirs(conf); err != nil {
|
|
log.WithError(err).Fatalln("error creating directory structure, exiting")
|
|
}
|
|
|
|
if err := setupPgp(conf); err != nil {
|
|
log.WithError(err).Fatalln("error loading pgp key, exiting")
|
|
}
|
|
|
|
log.Info("Indexing packages...")
|
|
|
|
for _, dist := range conf.Repo.DistroNames() {
|
|
if err := loadCache(dist); err != nil {
|
|
log.Info("Unable to load cached data for", dist, "- reindexing")
|
|
distro := &Distro{Name: dist, Architectures: make(map[string]map[string]*PackageFile)}
|
|
|
|
go scanInitialPackages(conf, distro)
|
|
|
|
distros[dist] = distro
|
|
}
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
|
|
httpFs := afero.NewHttpFs(fs)
|
|
|
|
mux.Handle("/", http.StripPrefix("/", http.FileServer(httpFs)))
|
|
mux.HandleFunc("/rescan", requireAuth(rescanHandler))
|
|
mux.HandleFunc("/upload", requireAuth(uploadHandler))
|
|
mux.HandleFunc("/delete", requireAuth(deleteHandler))
|
|
|
|
bind := fmt.Sprintf(":%d", conf.Http.Port)
|
|
|
|
if conf.Http.SSL {
|
|
log.WithFields(log.Fields{
|
|
"bind": bind,
|
|
"certFile": conf.Http.SSLCert,
|
|
"keyFile": conf.Http.SSLKey,
|
|
}).Info("Starting with SSL enabled")
|
|
|
|
err = http.ListenAndServeTLS(bind, conf.Http.SSLCert, conf.Http.SSLKey, mux)
|
|
} else {
|
|
log.WithField("bind", bind).Info("Starting without SSL enabled")
|
|
|
|
err = http.ListenAndServe(bind, mux)
|
|
}
|
|
|
|
if err != nil {
|
|
log.WithError(err).Fatalln("Unable to start or keep running due to error")
|
|
}
|
|
}
|
|
|
|
func scanInitialPackages(config Conf, dist *Distro) {
|
|
for _, arch := range config.Repo.ArchitectureNames() {
|
|
files, err := buildPackageList(config, dist.Name, arch)
|
|
|
|
if err != nil {
|
|
log.Fatalln("Unable to load packages:", err)
|
|
}
|
|
|
|
dist.Architectures[arch] = files
|
|
|
|
log.Info("Generating packages file for", dist.Name, arch)
|
|
|
|
err = createPackagesCached(config, dist.Name, arch, files)
|
|
|
|
if err != nil {
|
|
log.WithError(err).Fatalln("Unable to generate package list")
|
|
}
|
|
}
|
|
|
|
log.Info("Generating Release for", dist.Name)
|
|
err := createRelease(config, dist.Name)
|
|
|
|
if err != nil {
|
|
log.WithError(err).Fatalln("Unable to generate release file")
|
|
}
|
|
|
|
err = saveCache(dist)
|
|
|
|
if err != nil {
|
|
log.WithError(err).Fatalln("Unable to save cache")
|
|
}
|
|
}
|
|
|
|
func setupPgp(config Conf) error {
|
|
if config.PGP.Key == "" {
|
|
return nil
|
|
}
|
|
|
|
secretKey, err := os.Open(config.PGP.Key)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to open private key ring file: %s", err)
|
|
}
|
|
defer secretKey.Close()
|
|
|
|
entitylist, err := openpgp.ReadKeyRing(secretKey)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read key ring: %s", err)
|
|
}
|
|
|
|
if len(entitylist) < 1 {
|
|
return errors.New("no keys in key ring")
|
|
}
|
|
|
|
pgpEntity = entitylist[0]
|
|
|
|
passphrase := []byte(config.PGP.Passphrase)
|
|
|
|
if err := pgpEntity.PrivateKey.Decrypt(passphrase); err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, subkey := range pgpEntity.Subkeys {
|
|
err := subkey.PrivateKey.Decrypt(passphrase)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func createDirs(config Conf) error {
|
|
for _, distro := range config.Repo.DistroNames() {
|
|
for _, arch := range config.Repo.ArchitectureNames() {
|
|
if _, err := fs.Stat(config.ArchPath(distro, arch)); err != nil {
|
|
if os.IsNotExist(err) {
|
|
log.Debugf("Directory for %s (%s) does not exist, creating", distro, arch)
|
|
if err := fs.MkdirAll(config.ArchPath(distro, arch), 0755); err != nil {
|
|
return fmt.Errorf("error creating directory for %s (%s): %s", distro, arch, err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("error inspecting %s (%s): %s", distro, arch, err)
|
|
}
|
|
}
|
|
if _, err := fs.Stat(config.PoolPath(distro, arch)); err != nil {
|
|
if os.IsNotExist(err) {
|
|
log.Debugf("Directory for %s (%s) does not exist, creating", distro, arch)
|
|
if err := fs.MkdirAll(config.PoolPath(distro, arch), 0755); err != nil {
|
|
return fmt.Errorf("error creating directory for %s (%s): %s", distro, arch, err)
|
|
}
|
|
} else {
|
|
return fmt.Errorf("error inspecting %s (%s): %s", distro, arch, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func copyFile(sourceFs afero.Fs, oldPath string, destFs afero.Fs, newPath string) error {
|
|
old, err := sourceFs.Open(oldPath)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer old.Close()
|
|
|
|
n, err := destFs.Create(newPath)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer n.Close()
|
|
|
|
_, err = io.Copy(n, old)
|
|
|
|
return err
|
|
} |