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 }