Initial Commit
This commit is contained in:
commit
e1c49febd0
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
users.json
|
||||
pikabldr.db
|
||||
config.json
|
||||
apicalls/
|
||||
brunel
|
5
README
Normal file
5
README
Normal file
@ -0,0 +1,5 @@
|
||||
Copy the example config and user json files and name them config.json and user.json
|
||||
Fill in what you need
|
||||
|
||||
go build -o brunel
|
||||
./brunel
|
126
auth/auth.go
Normal file
126
auth/auth.go
Normal file
@ -0,0 +1,126 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"brunel/config"
|
||||
"brunel/domain"
|
||||
"brunel/helpers"
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
type Params struct {
|
||||
Memory uint32
|
||||
Iterations uint32
|
||||
Parallelism uint8
|
||||
KeyLength uint32
|
||||
}
|
||||
|
||||
func NewPasswordHash(password string) string {
|
||||
params := &Params{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 2,
|
||||
KeyLength: 32,
|
||||
}
|
||||
salt := []byte(config.Configs.Salt)
|
||||
hash := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength)
|
||||
return fmt.Sprintf("$argon2id$v=19$m=%d,t=%d,p=%d$%s$%s", params.Memory, params.Iterations, params.Parallelism, base64.RawURLEncoding.EncodeToString(salt), base64.RawURLEncoding.EncodeToString(hash))
|
||||
}
|
||||
|
||||
func VerifyPassword(username, password string) (bool, error) {
|
||||
var user domain.User
|
||||
user, err := helpers.DBInst.GetUser(username)
|
||||
if err != nil {
|
||||
usr, ok := config.Users[username]
|
||||
if !ok {
|
||||
return false, errors.New("user not found")
|
||||
}
|
||||
user = usr
|
||||
}
|
||||
|
||||
encodedHash := user.PasswordHash
|
||||
_, _, _, _, salt, hash, err := decodeHash(encodedHash)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return false, err
|
||||
}
|
||||
params := &Params{
|
||||
Memory: 64 * 1024,
|
||||
Iterations: 3,
|
||||
Parallelism: 2,
|
||||
KeyLength: 32,
|
||||
}
|
||||
|
||||
newHash := argon2.IDKey([]byte(password), salt, params.Iterations, params.Memory, params.Parallelism, params.KeyLength)
|
||||
if subtle.ConstantTimeCompare(hash, newHash) == 1 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func GenerateAndStoreSessionToken(username string) (string, error) {
|
||||
token := make([]byte, 32)
|
||||
_, err := rand.Read(token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
endcodedToken := base64.RawURLEncoding.EncodeToString(token)
|
||||
helpers.DBInst.CreateSession(domain.Session{
|
||||
Token: endcodedToken,
|
||||
Username: username,
|
||||
Expiry: time.Now().Add(24 * time.Hour),
|
||||
})
|
||||
return endcodedToken, nil
|
||||
}
|
||||
|
||||
func CheckSessionToken(token string) (bool, string) {
|
||||
session, err := helpers.DBInst.GetSession(token)
|
||||
if err != nil {
|
||||
return false, ""
|
||||
}
|
||||
return true, session.Username
|
||||
}
|
||||
|
||||
func decodeHash(encodedHash string) (version int, memory, iterations, parallelism uint32, salt, hash []byte, err error) {
|
||||
parts := strings.Split(encodedHash, "$")
|
||||
if len(parts) != 6 {
|
||||
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid hash format")
|
||||
}
|
||||
|
||||
if parts[1] != "argon2id" {
|
||||
return 0, 0, 0, 0, nil, nil, fmt.Errorf("unsupported hash algorithm")
|
||||
}
|
||||
|
||||
var v int
|
||||
_, err = fmt.Sscanf(parts[2], "v=%d", &v)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid version: %v", err)
|
||||
}
|
||||
version = v
|
||||
|
||||
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &memory, &iterations, ¶llelism)
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid parameters: %v", err)
|
||||
}
|
||||
|
||||
salt, err = base64.RawURLEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid salt: %v", err)
|
||||
}
|
||||
|
||||
hash, err = base64.RawURLEncoding.DecodeString(parts[5])
|
||||
if err != nil {
|
||||
return 0, 0, 0, 0, nil, nil, fmt.Errorf("invalid hash: %v", err)
|
||||
}
|
||||
|
||||
return version, memory, iterations, parallelism, salt, hash, nil
|
||||
}
|
101
config/config.go
Normal file
101
config/config.go
Normal file
@ -0,0 +1,101 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"brunel/domain"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
var Users map[string]domain.User
|
||||
var Configs Config
|
||||
|
||||
// Struct representing an individual package file entries
|
||||
type PackageFile struct {
|
||||
Name string `json:"name"`
|
||||
Url string `json:"url"`
|
||||
Subrepos []string `json:"subrepos"`
|
||||
Priority int `json:"priority"`
|
||||
UseWhitelist bool `json:"usewhitelist"`
|
||||
Whitelist []string `json:"whitelist"`
|
||||
Blacklist []string `json:"blacklist"`
|
||||
Packagepath string `json:"packagepath"`
|
||||
Compression string `json:"compression"`
|
||||
}
|
||||
|
||||
// Struct for the overall configuration
|
||||
type Config struct {
|
||||
Hostname string `json:"hostname"`
|
||||
Port int `json:"port"`
|
||||
UpstreamFallback bool `json:"upstreamFallback"`
|
||||
LocalPackageFiles []PackageFile `json:"localPackageFiles"`
|
||||
ExternalPackageFiles []PackageFile `json:"externalPackageFiles"`
|
||||
LTOBlocklist []string `json:"ltoBlocklist"`
|
||||
DeboutputDir string `json:"deboutputDir"`
|
||||
Salt string `json:"salt"`
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
err := loadUsers()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = loadConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadUsers() error {
|
||||
jsonFile, err := os.Open("users.json")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
byteValue, _ := io.ReadAll(jsonFile)
|
||||
|
||||
var users []domain.User
|
||||
err = json.Unmarshal(byteValue, &users)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
var usersMap = make(map[string]domain.User, len(users))
|
||||
for _, user := range users {
|
||||
usersMap[user.Username] = user
|
||||
}
|
||||
|
||||
Users = usersMap
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadConfig() error {
|
||||
jsonFile, err := os.Open("config.json")
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
byteValue, _ := io.ReadAll(jsonFile)
|
||||
|
||||
var config Config
|
||||
err = json.Unmarshal(byteValue, &config)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
Configs = config
|
||||
|
||||
return nil
|
||||
}
|
42
db/db.go
Normal file
42
db/db.go
Normal file
@ -0,0 +1,42 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"brunel/domain"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
func New() (*gorm.DB, error) {
|
||||
newLogger := logger.New(
|
||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
||||
logger.Config{
|
||||
SlowThreshold: time.Second, // Slow SQL threshold
|
||||
LogLevel: logger.Silent, // Log level
|
||||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||
ParameterizedQueries: true, // Don't include params in the SQL log
|
||||
Colorful: true, // Disable color
|
||||
},
|
||||
)
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("pikabldr.db"), &gorm.Config{
|
||||
SkipDefaultTransaction: true,
|
||||
CreateBatchSize: 1999,
|
||||
Logger: newLogger,
|
||||
})
|
||||
if err != nil {
|
||||
panic("failed to connect database")
|
||||
}
|
||||
|
||||
db.AutoMigrate(&domain.User{})
|
||||
db.AutoMigrate(&domain.Session{})
|
||||
db.AutoMigrate(&domain.PackageInfo{})
|
||||
db.AutoMigrate(&domain.TimeContainer{})
|
||||
db.AutoMigrate(&domain.SourcePackageDTO{})
|
||||
|
||||
return db, nil
|
||||
}
|
141
db/repository.go
Normal file
141
db/repository.go
Normal file
@ -0,0 +1,141 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"brunel/domain"
|
||||
"brunel/fastmap"
|
||||
"time"
|
||||
|
||||
"github.com/jinzhu/now"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
const MaxBatchSize = 1999
|
||||
|
||||
type Repository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRepository(db *gorm.DB) *Repository {
|
||||
return &Repository{db: db}
|
||||
}
|
||||
|
||||
func (r *Repository) GetUser(username string) (domain.User, error) {
|
||||
var user domain.User
|
||||
tx := r.db.Where("username = ?", username).First(&user)
|
||||
return user, tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) CreateUser(user domain.User) error {
|
||||
tx := r.db.Create(&user)
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateUser(user domain.User) error {
|
||||
tx := r.db.Save(&user)
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) CreateSession(session domain.Session) error {
|
||||
tx := r.db.Create(&session)
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetSession(token string) (domain.Session, error) {
|
||||
now := now.BeginningOfMinute()
|
||||
var session domain.Session
|
||||
tx := r.db.Where("token = ? AND expiry > ?", token, now).First(&session)
|
||||
return session, tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteSession(token string) error {
|
||||
tx := r.db.Delete(&domain.Session{}, "token = ?", token)
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteExpiredSessions() error {
|
||||
now := now.BeginningOfMinute()
|
||||
tx := r.db.Where("expiry < ?", now).Delete(&domain.Session{})
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) UpdatePackage(pkg domain.SourcePackage) error {
|
||||
dto := sourcePackageToDto(pkg)
|
||||
tx := r.db.Save(&dto)
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetPackages() ([]domain.SourcePackage, error) {
|
||||
var packages []domain.SourcePackageDTO
|
||||
tx := r.db.Find(&packages)
|
||||
if tx.Error != nil {
|
||||
return nil, tx.Error
|
||||
}
|
||||
var output []domain.SourcePackage
|
||||
for _, v := range packages {
|
||||
output = append(output, sourcePackageDtoToDomain(v))
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetPackage(name string) (domain.SourcePackage, error) {
|
||||
var pkg domain.SourcePackageDTO
|
||||
tx := r.db.Where("name = ?", name).First(&pkg)
|
||||
if tx.Error != nil {
|
||||
return domain.SourcePackage{}, tx.Error
|
||||
}
|
||||
return sourcePackageDtoToDomain(pkg), nil
|
||||
}
|
||||
|
||||
func (r *Repository) SavePackages(pkgs *fastmap.Fastmap[string, domain.SourcePackage]) error {
|
||||
packs := make([]domain.SourcePackageDTO, 0)
|
||||
pkgs.Iter(func(k string, v domain.SourcePackage) bool {
|
||||
packs = append(packs, sourcePackageToDto(v))
|
||||
return true
|
||||
})
|
||||
|
||||
for i := 0; i < len(packs); i += MaxBatchSize {
|
||||
end := i + MaxBatchSize
|
||||
length := len(packs)
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
tx := r.db.Save(packs[i:end])
|
||||
if tx.Error != nil {
|
||||
return tx.Error
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) UpdateLastUpdateTime(time time.Time) error {
|
||||
val := &domain.TimeContainer{
|
||||
Time: time,
|
||||
ID: "lastupdatetime",
|
||||
}
|
||||
tx := r.db.Save(val)
|
||||
return tx.Error
|
||||
}
|
||||
|
||||
func sourcePackageToDto(pkg domain.SourcePackage) domain.SourcePackageDTO {
|
||||
dto := domain.SourcePackageDTO{
|
||||
Name: pkg.Name,
|
||||
Packages: make([]domain.PackageInfo, 0),
|
||||
}
|
||||
pkg.Packages.Iter(func(k string, v domain.PackageInfo) bool {
|
||||
dto.Packages = append(dto.Packages, v)
|
||||
return true
|
||||
})
|
||||
return dto
|
||||
}
|
||||
|
||||
func sourcePackageDtoToDomain(dto domain.SourcePackageDTO) domain.SourcePackage {
|
||||
pkg := domain.SourcePackage{
|
||||
Name: dto.Name,
|
||||
Packages: fastmap.New[string, domain.PackageInfo](),
|
||||
}
|
||||
for _, v := range dto.Packages {
|
||||
pkg.Packages.Set(v.PackageName, v)
|
||||
}
|
||||
return pkg
|
||||
}
|
307
deb/format.go
Normal file
307
deb/format.go
Normal file
@ -0,0 +1,307 @@
|
||||
package deb
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Stanza or paragraph of Debian control file
|
||||
type Stanza map[string]string
|
||||
|
||||
// MaxFieldSize is maximum stanza field size in bytes
|
||||
const MaxFieldSize = 2 * 1024 * 1024
|
||||
|
||||
// Canonical order of fields in stanza
|
||||
// Taken from: http://bazaar.launchpad.net/~ubuntu-branches/ubuntu/vivid/apt/vivid/view/head:/apt-pkg/tagfile.cc#L504
|
||||
var (
|
||||
canonicalOrderRelease = []string{
|
||||
"Origin",
|
||||
"Label",
|
||||
"Archive",
|
||||
"Suite",
|
||||
"Version",
|
||||
"Codename",
|
||||
"Date",
|
||||
"NotAutomatic",
|
||||
"ButAutomaticUpgrades",
|
||||
"Architectures",
|
||||
"Architecture",
|
||||
"Components",
|
||||
"Component",
|
||||
"Description",
|
||||
"MD5Sum",
|
||||
"SHA1",
|
||||
"SHA256",
|
||||
"SHA512",
|
||||
}
|
||||
|
||||
canonicalOrderBinary = []string{
|
||||
"Package",
|
||||
"Essential",
|
||||
"Status",
|
||||
"Priority",
|
||||
"Section",
|
||||
"Installed-Size",
|
||||
"Maintainer",
|
||||
"Original-Maintainer",
|
||||
"Architecture",
|
||||
"Source",
|
||||
"Version",
|
||||
"Replaces",
|
||||
"Provides",
|
||||
"Depends",
|
||||
"Pre-Depends",
|
||||
"Recommends",
|
||||
"Suggests",
|
||||
"Conflicts",
|
||||
"Breaks",
|
||||
"Conffiles",
|
||||
"Filename",
|
||||
"Size",
|
||||
"MD5Sum",
|
||||
"MD5sum",
|
||||
"SHA1",
|
||||
"SHA256",
|
||||
"SHA512",
|
||||
"Description",
|
||||
}
|
||||
|
||||
canonicalOrderSource = []string{
|
||||
"Package",
|
||||
"Source",
|
||||
"Binary",
|
||||
"Version",
|
||||
"Priority",
|
||||
"Section",
|
||||
"Maintainer",
|
||||
"Original-Maintainer",
|
||||
"Build-Depends",
|
||||
"Build-Depends-Indep",
|
||||
"Build-Conflicts",
|
||||
"Build-Conflicts-Indep",
|
||||
"Architecture",
|
||||
"Standards-Version",
|
||||
"Format",
|
||||
"Directory",
|
||||
"Files",
|
||||
}
|
||||
canonicalOrderInstaller = []string{
|
||||
"",
|
||||
}
|
||||
)
|
||||
|
||||
// Copy returns copy of Stanza
|
||||
func (s Stanza) Copy() (result Stanza) {
|
||||
result = make(Stanza, len(s))
|
||||
for k, v := range s {
|
||||
result[k] = v
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func isMultilineField(field string, isRelease bool) bool {
|
||||
switch field {
|
||||
// file without a section
|
||||
case "":
|
||||
return true
|
||||
case "Description":
|
||||
return true
|
||||
case "Files":
|
||||
return true
|
||||
case "Changes":
|
||||
return true
|
||||
case "Checksums-Sha1":
|
||||
return true
|
||||
case "Checksums-Sha256":
|
||||
return true
|
||||
case "Checksums-Sha512":
|
||||
return true
|
||||
case "Package-List":
|
||||
return true
|
||||
case "MD5Sum":
|
||||
return isRelease
|
||||
case "SHA1":
|
||||
return isRelease
|
||||
case "SHA256":
|
||||
return isRelease
|
||||
case "SHA512":
|
||||
return isRelease
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Write single field from Stanza to writer.
|
||||
//
|
||||
// nolint: interfacer
|
||||
func writeField(w *bufio.Writer, field, value string, isRelease bool) (err error) {
|
||||
if !isMultilineField(field, isRelease) {
|
||||
_, err = w.WriteString(field + ": " + value + "\n")
|
||||
} else {
|
||||
if field != "" && !strings.HasSuffix(value, "\n") {
|
||||
value = value + "\n"
|
||||
}
|
||||
|
||||
if field != "Description" && field != "" {
|
||||
value = "\n" + value
|
||||
}
|
||||
|
||||
if field != "" {
|
||||
_, err = w.WriteString(field + ":" + value)
|
||||
} else {
|
||||
_, err = w.WriteString(value)
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// WriteTo saves stanza back to stream, modifying itself on the fly
|
||||
func (s Stanza) WriteTo(w *bufio.Writer, isSource, isRelease, isInstaller bool) error {
|
||||
canonicalOrder := canonicalOrderBinary
|
||||
if isSource {
|
||||
canonicalOrder = canonicalOrderSource
|
||||
}
|
||||
if isRelease {
|
||||
canonicalOrder = canonicalOrderRelease
|
||||
}
|
||||
if isInstaller {
|
||||
canonicalOrder = canonicalOrderInstaller
|
||||
}
|
||||
|
||||
for _, field := range canonicalOrder {
|
||||
value, ok := s[field]
|
||||
if ok {
|
||||
delete(s, field)
|
||||
err := writeField(w, field, value, isRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// no extra fields in installer
|
||||
if !isInstaller {
|
||||
// Print extra fields in deterministic order (alphabetical)
|
||||
keys := make([]string, len(s))
|
||||
i := 0
|
||||
for field := range s {
|
||||
keys[i] = field
|
||||
i++
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, field := range keys {
|
||||
err := writeField(w, field, s[field], isRelease)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Parsing errors
|
||||
var (
|
||||
ErrMalformedStanza = errors.New("malformed stanza syntax")
|
||||
)
|
||||
|
||||
func canonicalCase(field string) string {
|
||||
upper := strings.ToUpper(field)
|
||||
switch upper {
|
||||
case "SHA1", "SHA256", "SHA512":
|
||||
return upper
|
||||
case "MD5SUM":
|
||||
return "MD5Sum"
|
||||
case "NOTAUTOMATIC":
|
||||
return "NotAutomatic"
|
||||
case "BUTAUTOMATICUPGRADES":
|
||||
return "ButAutomaticUpgrades"
|
||||
}
|
||||
|
||||
startOfWord := true
|
||||
|
||||
return strings.Map(func(r rune) rune {
|
||||
if startOfWord {
|
||||
startOfWord = false
|
||||
return unicode.ToUpper(r)
|
||||
}
|
||||
|
||||
if r == '-' {
|
||||
startOfWord = true
|
||||
}
|
||||
|
||||
return unicode.ToLower(r)
|
||||
}, field)
|
||||
}
|
||||
|
||||
// ControlFileReader implements reading of control files stanza by stanza
|
||||
type ControlFileReader struct {
|
||||
scanner *bufio.Scanner
|
||||
isRelease bool
|
||||
isInstaller bool
|
||||
}
|
||||
|
||||
// NewControlFileReader creates ControlFileReader, it wraps with buffering
|
||||
func NewControlFileReader(r io.Reader, isRelease, isInstaller bool) *ControlFileReader {
|
||||
scnr := bufio.NewScanner(bufio.NewReaderSize(r, 32768))
|
||||
scnr.Buffer(nil, MaxFieldSize)
|
||||
|
||||
return &ControlFileReader{
|
||||
scanner: scnr,
|
||||
isRelease: isRelease,
|
||||
isInstaller: isInstaller,
|
||||
}
|
||||
}
|
||||
|
||||
// ReadStanza reeads one stanza from control file
|
||||
func (c *ControlFileReader) ReadStanza() (Stanza, error) {
|
||||
stanza := make(Stanza, 32)
|
||||
lastField := ""
|
||||
lastFieldMultiline := c.isInstaller
|
||||
|
||||
for c.scanner.Scan() {
|
||||
line := c.scanner.Text()
|
||||
|
||||
// Current stanza ends with empty line
|
||||
if line == "" {
|
||||
if len(stanza) > 0 {
|
||||
return stanza, nil
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if line[0] == ' ' || line[0] == '\t' || c.isInstaller {
|
||||
if lastFieldMultiline {
|
||||
stanza[lastField] += line + "\n"
|
||||
} else {
|
||||
stanza[lastField] += " " + strings.TrimSpace(line)
|
||||
}
|
||||
} else {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil, ErrMalformedStanza
|
||||
}
|
||||
lastField = canonicalCase(parts[0])
|
||||
lastFieldMultiline = isMultilineField(lastField, c.isRelease)
|
||||
if lastFieldMultiline {
|
||||
stanza[lastField] = parts[1]
|
||||
if parts[1] != "" {
|
||||
stanza[lastField] += "\n"
|
||||
}
|
||||
} else {
|
||||
stanza[lastField] = strings.TrimSpace(parts[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := c.scanner.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(stanza) > 0 {
|
||||
return stanza, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
74
domain/packages.go
Normal file
74
domain/packages.go
Normal file
@ -0,0 +1,74 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"brunel/fastmap"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PackagesCount struct {
|
||||
Stale int `json:"stale"`
|
||||
Missing int `json:"missing"`
|
||||
Current int `json:"built"`
|
||||
Error int `json:"error"`
|
||||
Queued int `json:"queued"`
|
||||
Building int `json:"building"`
|
||||
}
|
||||
|
||||
type BuildQueue *fastmap.Fastmap[string, BuildQueueItem]
|
||||
|
||||
type BuildQueueItem struct {
|
||||
Source SourcePackage
|
||||
Status BuildStatus
|
||||
Patch bool
|
||||
LTO bool
|
||||
Rebuild bool
|
||||
BuildNumber int
|
||||
BuildVersion string
|
||||
I386 bool
|
||||
}
|
||||
|
||||
type SourcePackage struct {
|
||||
Name string `gorm:"primarykey"`
|
||||
Packages *fastmap.Fastmap[string, PackageInfo] `gorm:"foreignKey:PackageInfo;references:PackageName"`
|
||||
}
|
||||
|
||||
type SourcePackageDTO struct {
|
||||
Name string `gorm:"primarykey"`
|
||||
Packages []PackageInfo `gorm:"foreignKey:PackageName"`
|
||||
}
|
||||
|
||||
type PackageInfo struct {
|
||||
PackageName string `gorm:"primarykey"`
|
||||
Version string
|
||||
Source string
|
||||
Architecture string
|
||||
Description string
|
||||
Status PackageStatus
|
||||
NewVersion string
|
||||
LastBuildStatus PackageStatus
|
||||
}
|
||||
|
||||
type PackageStatus string
|
||||
type BuildStatus string
|
||||
|
||||
const (
|
||||
// Package is built
|
||||
Built PackageStatus = "Built"
|
||||
// Package is stale
|
||||
Stale PackageStatus = "Stale"
|
||||
// Package build errored out
|
||||
Error PackageStatus = "Error"
|
||||
// Package is being missing
|
||||
Missing PackageStatus = "Missing"
|
||||
// Package is upto date
|
||||
Current PackageStatus = "Current"
|
||||
// Package is queued for building
|
||||
Queued BuildStatus = "Queued"
|
||||
// Package is being built
|
||||
Building BuildStatus = "Building"
|
||||
)
|
||||
|
||||
type TimeContainer struct {
|
||||
ID string
|
||||
Time time.Time
|
||||
}
|
14
domain/user.go
Normal file
14
domain/user.go
Normal file
@ -0,0 +1,14 @@
|
||||
package domain
|
||||
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"passwordHash"`
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Token string `json:"token"`
|
||||
Username string `json:"user"`
|
||||
Expiry time.Time `json:"expiry"`
|
||||
}
|
77
example_config_json
Normal file
77
example_config_json
Normal file
@ -0,0 +1,77 @@
|
||||
{
|
||||
"hostname": "127.0.0.1",
|
||||
"port": 7555,
|
||||
"deboutputDir": "/srv/www/canary-incoming/",
|
||||
"upstreamFallback": true,
|
||||
"ltoBlocklist": [
|
||||
"biber",
|
||||
"publican",
|
||||
"xserver-xorg-video-intel",
|
||||
"debian-package-book-de",
|
||||
"debian-package-scripts",
|
||||
"libcoq-iris",
|
||||
"libproc-pid-file-perl"
|
||||
],
|
||||
"localPackageFiles": [
|
||||
{
|
||||
"name": "PikaOS",
|
||||
"url": "https://ppa.pika-os.com/dists/pika/",
|
||||
"subrepos": [
|
||||
"nest", "canary", "parrot", "pigeon", "raven", "cockatiel"
|
||||
],
|
||||
"priority": 500,
|
||||
"blacklist": [],
|
||||
"usewhitelist": false,
|
||||
"whitelist": [],
|
||||
"packagepath": "/binary-amd64/Packages",
|
||||
"compression": "bz2"
|
||||
}
|
||||
],
|
||||
"externalPackageFiles": [
|
||||
{
|
||||
"name": "Debian sid",
|
||||
"url": "http://ftp.debian.org/debian/dists/sid/",
|
||||
"subrepos": [
|
||||
"main", "contrib", "non-free", "non-free-firmware"
|
||||
],
|
||||
"priority": 350,
|
||||
"blacklist": ["acl2"],
|
||||
"usewhitelist": false,
|
||||
"whitelist": [],
|
||||
"packagepath": "/binary-amd64/Packages",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"name": "Debian experimental",
|
||||
"url": "http://ftp.debian.org/debian/dists/experimental/",
|
||||
"subrepos": [
|
||||
"main", "contrib", "non-free", "non-free-firmware"
|
||||
],
|
||||
"priority": 400,
|
||||
"usewhitelist": true,
|
||||
"whitelist": [
|
||||
"plasma-workspace-data",
|
||||
"plasma-workspace-dev"
|
||||
],
|
||||
"blacklist": [
|
||||
"libwebrtc-audio-processing", "libhsa-runtime64", "hipcc", "rocm"
|
||||
],
|
||||
"packagepath": "/binary-amd64/Packages",
|
||||
"compression": "xz"
|
||||
},
|
||||
{
|
||||
"name": "Debian multimedia",
|
||||
"url": "https://debian-mirrors.sdinet.de/deb-multimedia/dists/sid/",
|
||||
"subrepos": [
|
||||
"main", "non-free"
|
||||
],
|
||||
"usewhitelist": false,
|
||||
"whitelist": [],
|
||||
"priority": 450,
|
||||
"blacklist": [],
|
||||
"packagepath": "/binary-amd64/Packages",
|
||||
"compression": "xz"
|
||||
}
|
||||
],
|
||||
"salt": "sdfwefwfwfwefwef"
|
||||
}
|
6
example_users_json
Normal file
6
example_users_json
Normal file
@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"username": "testuser",
|
||||
"passwordHash": "$argon2id$v=19$m=65536,t=3,p=2$MHVsRnHuN8hUcTE5$lC3bWNpyjYAcBrhSNx31GW_k-P6gd4GcOM4WJIbYii4"
|
||||
}
|
||||
]
|
140
fastmap/fastmap.go
Normal file
140
fastmap/fastmap.go
Normal file
@ -0,0 +1,140 @@
|
||||
package fastmap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type Fastmap[K comparable, V any] struct {
|
||||
idx map[K]int
|
||||
store []fastmapValue[K, V]
|
||||
}
|
||||
|
||||
type fastmapValue[K comparable, V any] struct {
|
||||
Key K
|
||||
Value V
|
||||
}
|
||||
|
||||
func New[K comparable, V any]() *Fastmap[K, V] {
|
||||
return &Fastmap[K, V]{
|
||||
idx: make(map[K]int),
|
||||
store: make([]fastmapValue[K, V], 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Set(key K, value V) {
|
||||
if _, ok := m.idx[key]; ok {
|
||||
m.store[m.idx[key]].Value = value
|
||||
return
|
||||
}
|
||||
m.idx[key] = len(m.store)
|
||||
m.store = append(m.store, fastmapValue[K, V]{Key: key, Value: value})
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Get(key K) (value V, ok bool) {
|
||||
idx, ok := m.idx[key]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
return m.store[idx].Value, true
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Delete(key K) {
|
||||
idx, ok := m.idx[key]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
delete(m.idx, key)
|
||||
m.store[idx] = m.store[len(m.store)-1]
|
||||
m.store = m.store[:len(m.store)-1]
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Has(key K) bool {
|
||||
_, ok := m.idx[key]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Len() int {
|
||||
return len(m.idx)
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) GetPage(pageNum int, pageSize int) *Fastmap[K, V] {
|
||||
start := pageSize * pageNum
|
||||
end := start + pageSize
|
||||
if end > len(m.store) {
|
||||
end = len(m.store)
|
||||
}
|
||||
|
||||
returnVal := New[K, V]()
|
||||
for i := start; i < end; i++ {
|
||||
returnVal.Set(m.store[i].Key, m.store[i].Value)
|
||||
}
|
||||
return returnVal
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Clear() {
|
||||
m.idx = make(map[K]int)
|
||||
m.store = make([]fastmapValue[K, V], 0)
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) Iter(fn func(key K, value V) bool) {
|
||||
for _, v := range m.store {
|
||||
if !fn(v.Key, v.Value) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) MarshalText() ([]byte, error) {
|
||||
var builder strings.Builder
|
||||
for _, v := range m.store {
|
||||
builder.WriteString(fmt.Sprintf("%v:%v\n", v.Key, v.Value))
|
||||
}
|
||||
return []byte(builder.String()), nil
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) UnmarshalText(text []byte) error {
|
||||
m.Clear()
|
||||
lines := strings.Split(string(text), "\n")
|
||||
for _, line := range lines {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return fmt.Errorf("invalid format: %s", line)
|
||||
}
|
||||
var key K
|
||||
var value V
|
||||
if _, err := fmt.Sscan(parts[0], &key); err != nil {
|
||||
return fmt.Errorf("error parsing key: %v", err)
|
||||
}
|
||||
if _, err := fmt.Sscan(parts[1], &value); err != nil {
|
||||
return fmt.Errorf("error parsing value: %v", err)
|
||||
}
|
||||
m.Set(key, value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) MarshalJSON() ([]byte, error) {
|
||||
temp := make(map[K]V)
|
||||
for _, v := range m.store {
|
||||
temp[v.Key] = v.Value
|
||||
}
|
||||
return json.Marshal(temp)
|
||||
}
|
||||
|
||||
func (m *Fastmap[K, V]) UnmarshalJSON(data []byte) error {
|
||||
temp := make(map[K]V)
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Clear()
|
||||
for k, v := range temp {
|
||||
m.Set(k, v)
|
||||
}
|
||||
return nil
|
||||
}
|
34
go.mod
Normal file
34
go.mod
Normal file
@ -0,0 +1,34 @@
|
||||
module brunel
|
||||
|
||||
go 1.22.5
|
||||
|
||||
require (
|
||||
github.com/goccy/go-json v0.10.3
|
||||
github.com/gofiber/fiber/v2 v2.52.5
|
||||
github.com/jinzhu/now v1.1.5
|
||||
github.com/klauspost/compress v1.17.9
|
||||
github.com/samber/slog-fiber v1.16.0
|
||||
github.com/ulikunitz/xz v0.5.12
|
||||
golang.org/x/crypto v0.14.0
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
gorm.io/gorm v1.25.11
|
||||
pault.ag/go/debian v0.16.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasthttp v1.51.0 // indirect
|
||||
github.com/valyala/tcplisten v1.0.0 // indirect
|
||||
go.opentelemetry.io/otel v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.19.0 // indirect
|
||||
golang.org/x/sys v0.15.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
)
|
63
go.sum
Normal file
63
go.sum
Normal file
@ -0,0 +1,63 @@
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
||||
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/samber/slog-fiber v1.16.0 h1:7HUfFC1c4OhoU9pGhrUyIX+BK+4OVPJduuQiWM6fzzU=
|
||||
github.com/samber/slog-fiber v1.16.0/go.mod h1:RQr46XiBUwVNgWTiAizSGBxV9IbOpGbMMEEsth05iXg=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
|
||||
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
|
||||
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
|
||||
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
|
||||
go.opentelemetry.io/otel v1.19.0 h1:MuS/TNf4/j4IXsZuJegVzI1cwut7Qc00344rgH7p8bs=
|
||||
go.opentelemetry.io/otel v1.19.0/go.mod h1:i0QyjOq3UPoTzff0PJB2N66fb4S0+rSbSB15/oyH9fY=
|
||||
go.opentelemetry.io/otel/trace v1.19.0 h1:DFVQmlVbfVeOuBRrwdtaehRrWiL1JoVs9CPIQ1Dzxpg=
|
||||
go.opentelemetry.io/otel/trace v1.19.0/go.mod h1:mfaSyvGyEJEI0nyV2I4qhNQnbBOUUmYZpYojqMnX2vo=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
|
||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
pault.ag/go/debian v0.16.0 h1:fivXn/IO9rn2nzTGndflDhOkNU703Axs/StWihOeU2g=
|
||||
pault.ag/go/debian v0.16.0/go.mod h1:JFl0XWRCv9hWBrB5MDDZjA5GSEs1X3zcFK/9kCNIUmE=
|
43
handlers/auth/login.go
Normal file
43
handlers/auth/login.go
Normal file
@ -0,0 +1,43 @@
|
||||
package handlers_auth
|
||||
|
||||
import (
|
||||
"brunel/auth"
|
||||
"brunel/config"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Login(c *fiber.Ctx) error {
|
||||
|
||||
username := c.FormValue("username")
|
||||
password := c.FormValue("password")
|
||||
|
||||
ok, err := auth.VerifyPassword(username, password)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
|
||||
}
|
||||
|
||||
token, err := auth.GenerateAndStoreSessionToken(username)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
tokenCookie := fiber.Cookie{
|
||||
Name: "pt",
|
||||
Value: token + ":" + username,
|
||||
Domain: config.Configs.Hostname,
|
||||
Expires: time.Now().Add(24 * time.Hour),
|
||||
Secure: true,
|
||||
HTTPOnly: true,
|
||||
SameSite: "lax",
|
||||
}
|
||||
|
||||
c.Cookie(&tokenCookie)
|
||||
|
||||
return c.Status(fiber.StatusOK).SendString("Login")
|
||||
}
|
36
handlers/auth/register.go
Normal file
36
handlers/auth/register.go
Normal file
@ -0,0 +1,36 @@
|
||||
package handlers_auth
|
||||
|
||||
import (
|
||||
"brunel/auth"
|
||||
"brunel/domain"
|
||||
"brunel/helpers"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Register(c *fiber.Ctx) error {
|
||||
|
||||
username := c.FormValue("username")
|
||||
password := c.FormValue("password")
|
||||
passwordConfirm := c.FormValue("passwordConfirm")
|
||||
|
||||
_, err := helpers.DBInst.GetUser(username)
|
||||
if err == nil {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Username already taken")
|
||||
}
|
||||
|
||||
if password != passwordConfirm {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Passwords do not match")
|
||||
}
|
||||
|
||||
pw := auth.NewPasswordHash(password)
|
||||
|
||||
user := domain.User{
|
||||
Username: username,
|
||||
PasswordHash: pw,
|
||||
}
|
||||
|
||||
helpers.DBInst.CreateUser(user)
|
||||
|
||||
return c.Status(fiber.StatusOK).SendString("User created")
|
||||
}
|
35
handlers/auth/updatePassword.go
Normal file
35
handlers/auth/updatePassword.go
Normal file
@ -0,0 +1,35 @@
|
||||
package handlers_auth
|
||||
|
||||
import (
|
||||
"brunel/auth"
|
||||
"brunel/helpers"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func UpdatePassword(c *fiber.Ctx) error {
|
||||
|
||||
username := c.FormValue("username")
|
||||
password := c.FormValue("password")
|
||||
passwordConfirm := c.FormValue("passwordConfirm")
|
||||
|
||||
user, err := helpers.DBInst.GetUser(username)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
if password != passwordConfirm {
|
||||
return c.Status(fiber.StatusBadRequest).SendString("Passwords do not match")
|
||||
}
|
||||
|
||||
pw := auth.NewPasswordHash(password)
|
||||
|
||||
user.PasswordHash = pw
|
||||
|
||||
err = helpers.DBInst.UpdateUser(user)
|
||||
if err != nil {
|
||||
return c.Status(fiber.StatusInternalServerError).SendString("Internal Server Error")
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).SendString("Password updated")
|
||||
}
|
11
handlers/packages/counts.go
Normal file
11
handlers/packages/counts.go
Normal file
@ -0,0 +1,11 @@
|
||||
package handlers_packages
|
||||
|
||||
import (
|
||||
"brunel/packages"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Counts(c *fiber.Ctx) error {
|
||||
return c.Status(fiber.StatusOK).JSON(packages.GetPackagesCount())
|
||||
}
|
70
handlers/packages/packages.go
Normal file
70
handlers/packages/packages.go
Normal file
@ -0,0 +1,70 @@
|
||||
package handlers_packages
|
||||
|
||||
import (
|
||||
"brunel/domain"
|
||||
"brunel/fastmap"
|
||||
"brunel/packages"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func Packages(c *fiber.Ctx) error {
|
||||
pageNum := c.QueryInt("page")
|
||||
pageSize := c.QueryInt("pageSize")
|
||||
search := c.Query("search")
|
||||
filter := c.Query("filter")
|
||||
if pageNum == 0 {
|
||||
pageNum = 1
|
||||
}
|
||||
if pageSize == 0 {
|
||||
pageSize = 250
|
||||
}
|
||||
packs := packages.GetPackages()
|
||||
|
||||
if filter != "" {
|
||||
finalReturn := fastmap.New[string, domain.SourcePackage]()
|
||||
filteredPacks := fastmap.New[string, domain.SourcePackage]()
|
||||
packs.Iter(func(k string, source domain.SourcePackage) bool {
|
||||
source.Packages.Iter(func(key string, value domain.PackageInfo) bool {
|
||||
if value.Status == domain.PackageStatus(filter) {
|
||||
filteredPacks.Set(k, source)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
if search != "" {
|
||||
filteredPacks.Iter(func(k string, source domain.SourcePackage) bool {
|
||||
source.Packages.Iter(func(key string, value domain.PackageInfo) bool {
|
||||
if strings.Contains(key, search) {
|
||||
finalReturn.Set(k, source)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
} else {
|
||||
finalReturn = filteredPacks
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(finalReturn.GetPage(pageNum, pageSize))
|
||||
}
|
||||
if search != "" {
|
||||
finalReturn := fastmap.New[string, domain.SourcePackage]()
|
||||
packs.Iter(func(k string, source domain.SourcePackage) bool {
|
||||
source.Packages.Iter(func(key string, value domain.PackageInfo) bool {
|
||||
if value.Status == domain.PackageStatus(filter) {
|
||||
finalReturn.Set(k, source)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
})
|
||||
return c.Status(fiber.StatusOK).JSON(finalReturn.GetPage(pageNum, pageSize))
|
||||
}
|
||||
|
||||
return c.Status(fiber.StatusOK).JSON(packs.GetPage(pageNum, pageSize))
|
||||
}
|
5
helpers/db.go
Normal file
5
helpers/db.go
Normal file
@ -0,0 +1,5 @@
|
||||
package helpers
|
||||
|
||||
import "brunel/db"
|
||||
|
||||
var DBInst *db.Repository
|
47
main.go
Normal file
47
main.go
Normal file
@ -0,0 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"brunel/db"
|
||||
"brunel/helpers"
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
)
|
||||
|
||||
func main() {
|
||||
datab, err := db.New()
|
||||
if err != nil {
|
||||
panic("failed to connect database")
|
||||
}
|
||||
|
||||
repo := db.NewRepository(datab)
|
||||
helpers.DBInst = repo
|
||||
|
||||
// Run your server.
|
||||
ctx := context.Background()
|
||||
|
||||
// trap Ctrl+C and call cancel on the context
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
defer func() {
|
||||
signal.Stop(c)
|
||||
cancel()
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-c:
|
||||
cancel()
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
if err := runServer(ctx); err != nil {
|
||||
slog.Error("Failed to start server!", "details", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}()
|
||||
<-ctx.Done()
|
||||
}
|
34
middleware/auth.go
Normal file
34
middleware/auth.go
Normal file
@ -0,0 +1,34 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"brunel/auth"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func NewAuth() fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
tokenPlusUsername := c.Cookies("pt")
|
||||
fmt.Println("cookie", tokenPlusUsername)
|
||||
if tokenPlusUsername == "" {
|
||||
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
|
||||
}
|
||||
|
||||
split := strings.Split(tokenPlusUsername, ":")
|
||||
token := split[0]
|
||||
username := split[1]
|
||||
ok, suser := auth.CheckSessionToken(token)
|
||||
if !ok {
|
||||
fmt.Println("not ok")
|
||||
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
|
||||
}
|
||||
if suser != username {
|
||||
fmt.Println("not suser")
|
||||
return c.Status(fiber.StatusUnauthorized).SendString("Unauthorized")
|
||||
}
|
||||
|
||||
return c.Next()
|
||||
}
|
||||
}
|
430
packages/packages.go
Normal file
430
packages/packages.go
Normal file
@ -0,0 +1,430 @@
|
||||
package packages
|
||||
|
||||
import (
|
||||
"brunel/config"
|
||||
"brunel/deb"
|
||||
"brunel/domain"
|
||||
"brunel/fastmap"
|
||||
"brunel/helpers"
|
||||
"compress/bzip2"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"pault.ag/go/debian/version"
|
||||
|
||||
"github.com/klauspost/compress/gzip"
|
||||
"github.com/ulikunitz/xz"
|
||||
)
|
||||
|
||||
var LastUpdateTime time.Time
|
||||
var currentPackagesFastMap = fastmap.New[string, domain.SourcePackage]()
|
||||
var buildQueue domain.BuildQueue = fastmap.New[string, domain.BuildQueueItem]()
|
||||
|
||||
func ProcessPackages() error {
|
||||
var internalPackages = fastmap.New[string, domain.SourcePackage]()
|
||||
var externalPackages = fastmap.New[string, domain.SourcePackage]()
|
||||
err := LoadInternalPackages(internalPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = LoadExternalPackages(externalPackages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ProcessStalePackages(internalPackages, externalPackages)
|
||||
ProcessMissingPackages(internalPackages, externalPackages)
|
||||
|
||||
currentPackagesFastMap.Clear()
|
||||
internalPackages.Iter(func(k string, v domain.SourcePackage) bool {
|
||||
currentPackagesFastMap.Set(k, v)
|
||||
return true
|
||||
})
|
||||
|
||||
LastUpdateTime = time.Now()
|
||||
err = SaveToDb()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetBuildQueue() domain.BuildQueue {
|
||||
return buildQueue
|
||||
}
|
||||
|
||||
func GetPackages() *fastmap.Fastmap[string, domain.SourcePackage] {
|
||||
return currentPackagesFastMap
|
||||
}
|
||||
|
||||
func UpdatePackage(pkg domain.PackageInfo) error {
|
||||
curr, ok := currentPackagesFastMap.Get(pkg.Source)
|
||||
if !ok {
|
||||
return fmt.Errorf("package %s not found", pkg.Source)
|
||||
}
|
||||
curr.Packages.Set(pkg.PackageName, pkg)
|
||||
currentPackagesFastMap.Set(pkg.Source, curr)
|
||||
return saveSingleToDb(curr)
|
||||
}
|
||||
|
||||
func IsBuilt(pkg domain.PackageInfo) bool {
|
||||
curr, ok := currentPackagesFastMap.Get(pkg.Source)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
pk, ok := curr.Packages.Get(pkg.PackageName)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return pk.Status == domain.Built || pk.Status == domain.Current
|
||||
}
|
||||
|
||||
func saveSingleToDb(pkg domain.SourcePackage) error {
|
||||
err := helpers.DBInst.UpdatePackage(pkg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
LastUpdateTime = time.Now()
|
||||
err = helpers.DBInst.UpdateLastUpdateTime(LastUpdateTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SaveToDb() error {
|
||||
err := helpers.DBInst.SavePackages(currentPackagesFastMap)
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
LastUpdateTime = time.Now()
|
||||
return helpers.DBInst.UpdateLastUpdateTime(LastUpdateTime)
|
||||
}
|
||||
|
||||
func LoadFromDb() error {
|
||||
packages, err := helpers.DBInst.GetPackages()
|
||||
if err != nil {
|
||||
slog.Error(err.Error())
|
||||
return nil
|
||||
}
|
||||
slices.SortStableFunc(packages, func(a, b domain.SourcePackage) int {
|
||||
if a.Name == b.Name {
|
||||
return 0
|
||||
}
|
||||
if a.Name > b.Name {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
})
|
||||
currentPackagesFastMap.Clear()
|
||||
for _, pkg := range packages {
|
||||
currentPackagesFastMap.Set(pkg.Name, pkg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadInternalPackages(internalPackages *fastmap.Fastmap[string, domain.SourcePackage]) error {
|
||||
localPackageFile := config.Configs.LocalPackageFiles
|
||||
slices.SortStableFunc(localPackageFile, func(a, b config.PackageFile) int {
|
||||
if a.Priority == b.Priority {
|
||||
return 0
|
||||
}
|
||||
if a.Priority < b.Priority {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
})
|
||||
|
||||
for _, pkg := range config.Configs.LocalPackageFiles {
|
||||
for _, repo := range pkg.Subrepos {
|
||||
packages, err := fetchPackageFile(pkg, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packages.Iter(func(newKey string, newPkg domain.PackageInfo) bool {
|
||||
pk, ok := internalPackages.Get(newPkg.Source)
|
||||
if !ok {
|
||||
newMap := fastmap.New[string, domain.PackageInfo]()
|
||||
newMap.Set(newKey, newPkg)
|
||||
internalPackages.Set(newPkg.Source, domain.SourcePackage{
|
||||
Name: newPkg.Source,
|
||||
Packages: newMap,
|
||||
})
|
||||
return true
|
||||
}
|
||||
pkg, ok := pk.Packages.Get(newKey)
|
||||
if !ok {
|
||||
pk.Packages.Set(newKey, newPkg)
|
||||
return true
|
||||
}
|
||||
mVer, _ := version.Parse(pkg.Version)
|
||||
extVer, _ := version.Parse(newPkg.Version)
|
||||
cmpVal := version.Compare(extVer, mVer)
|
||||
if cmpVal >= 0 {
|
||||
pk.Packages.Set(newKey, newPkg)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func LoadExternalPackages(externalPackages *fastmap.Fastmap[string, domain.SourcePackage]) error {
|
||||
externalPackageFile := config.Configs.ExternalPackageFiles
|
||||
slices.SortStableFunc(externalPackageFile, func(a, b config.PackageFile) int {
|
||||
if a.Priority == b.Priority {
|
||||
return 0
|
||||
}
|
||||
if a.Priority < b.Priority {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
|
||||
for _, pkg := range config.Configs.ExternalPackageFiles {
|
||||
for _, repo := range pkg.Subrepos {
|
||||
packages, err := fetchPackageFile(pkg, repo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
packages.Iter(func(k string, v domain.PackageInfo) bool {
|
||||
pk, ok := externalPackages.Get(v.Source)
|
||||
if !ok {
|
||||
newMap := fastmap.New[string, domain.PackageInfo]()
|
||||
newMap.Set(k, v)
|
||||
externalPackages.Set(v.Source, domain.SourcePackage{
|
||||
Name: v.Source,
|
||||
Packages: newMap,
|
||||
})
|
||||
return true
|
||||
}
|
||||
pkg, ok := pk.Packages.Get(k)
|
||||
if !ok {
|
||||
pk.Packages.Set(k, v)
|
||||
return true
|
||||
}
|
||||
mVer, _ := version.Parse(pkg.Version)
|
||||
extVer, _ := version.Parse(v.Version)
|
||||
cmpVal := version.Compare(extVer, mVer)
|
||||
if cmpVal >= 0 {
|
||||
pk.Packages.Set(k, v)
|
||||
return true
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ProcessMissingPackages(internalPackages *fastmap.Fastmap[string, domain.SourcePackage], externalPackages *fastmap.Fastmap[string, domain.SourcePackage]) {
|
||||
externalPackages.Iter(func(k string, src domain.SourcePackage) bool {
|
||||
_, ok := internalPackages.Get(k)
|
||||
if !ok && src.Packages.Len() > 0 {
|
||||
newStatus := domain.Missing
|
||||
src.Packages.Iter(func(k string, v domain.PackageInfo) bool {
|
||||
v.Status = newStatus
|
||||
src.Packages.Set(k, v)
|
||||
return true
|
||||
})
|
||||
internalPackages.Set(k, src)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func ProcessStalePackages(internalPackages *fastmap.Fastmap[string, domain.SourcePackage], externalPackages *fastmap.Fastmap[string, domain.SourcePackage]) {
|
||||
externalPackages.Iter(func(newPackage string, newSource domain.SourcePackage) bool {
|
||||
matchedPackage, ok := internalPackages.Get(newPackage)
|
||||
if !ok || matchedPackage.Packages.Len() == 0 {
|
||||
return true
|
||||
}
|
||||
matchedPackage.Packages.Iter(func(currentKey string, currentPackage domain.PackageInfo) bool {
|
||||
if currentPackage.Status == domain.Missing {
|
||||
return true
|
||||
}
|
||||
newSource.Packages.Iter(func(newKey string, newPackage domain.PackageInfo) bool {
|
||||
if currentKey != newKey {
|
||||
return true
|
||||
}
|
||||
newVersion := strings.Split(newPackage.Version, "+b")[0]
|
||||
mVer, _ := version.Parse(currentPackage.Version)
|
||||
extVer, _ := version.Parse(newVersion)
|
||||
cmpVal := version.Compare(mVer, extVer)
|
||||
if cmpVal < 0 {
|
||||
currentPackage.Status = domain.Stale
|
||||
currentPackage.NewVersion = extVer.String()
|
||||
matchedPackage.Packages.Set(currentKey, currentPackage)
|
||||
}
|
||||
return false
|
||||
})
|
||||
return true
|
||||
})
|
||||
wasMissing := false
|
||||
newSource.Packages.Iter(func(newKey string, newPackage domain.PackageInfo) bool {
|
||||
found := false
|
||||
matchedPackage.Packages.Iter(func(currentKey string, currentPackage domain.PackageInfo) bool {
|
||||
if currentKey != newKey {
|
||||
return true
|
||||
}
|
||||
found = true
|
||||
return false
|
||||
})
|
||||
if !found {
|
||||
wasMissing = true
|
||||
newPackage.Status = domain.Missing
|
||||
newPackage.NewVersion = newPackage.Version
|
||||
matchedPackage.Packages.Set(newKey, newPackage)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if wasMissing {
|
||||
matchedPackage.Packages.Iter(func(k string, v domain.PackageInfo) bool {
|
||||
if v.Status == domain.Missing {
|
||||
return true
|
||||
}
|
||||
v.Status = domain.Missing
|
||||
return true
|
||||
})
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func fetchPackageFile(pkg config.PackageFile, selectedRepo string) (*fastmap.Fastmap[string, domain.PackageInfo], error) {
|
||||
resp, err := http.Get(pkg.Url + selectedRepo + "/" + pkg.Packagepath + "." + pkg.Compression)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
rdr := io.Reader(resp.Body)
|
||||
if pkg.Compression == "bz2" {
|
||||
r := bzip2.NewReader(resp.Body)
|
||||
rdr = r
|
||||
}
|
||||
if pkg.Compression == "xz" {
|
||||
r, err := xz.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rdr = r
|
||||
}
|
||||
if pkg.Compression == "gz" {
|
||||
r, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rdr = r
|
||||
}
|
||||
|
||||
packages := fastmap.New[string, domain.PackageInfo]()
|
||||
sreader := deb.NewControlFileReader(rdr, false, false)
|
||||
for {
|
||||
stanza, err := sreader.ReadStanza()
|
||||
if err != nil || stanza == nil {
|
||||
break
|
||||
}
|
||||
|
||||
if stanza["Section"] == "debian-installer" {
|
||||
continue
|
||||
}
|
||||
|
||||
name := stanza["Package"]
|
||||
|
||||
useWhitelist := pkg.UseWhitelist && len(pkg.Whitelist) > 0
|
||||
if useWhitelist {
|
||||
contained := nameContains(name, pkg.Whitelist)
|
||||
if !contained {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
broken := nameContains(name, pkg.Blacklist)
|
||||
if broken {
|
||||
continue
|
||||
}
|
||||
|
||||
ver, err := version.Parse(stanza["Version"])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pk, ok := packages.Get(name)
|
||||
if ok {
|
||||
matchedVer, _ := version.Parse(pk.Version)
|
||||
cmpVal := version.Compare(ver, matchedVer)
|
||||
if cmpVal < 0 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sourceSplit := strings.Split(stanza["Source"], " ")
|
||||
source := sourceSplit[0]
|
||||
if source == "" {
|
||||
source = name
|
||||
}
|
||||
|
||||
packages.Set(name, domain.PackageInfo{
|
||||
PackageName: name,
|
||||
Version: ver.String(),
|
||||
Source: source,
|
||||
Architecture: stanza["Architecture"],
|
||||
Description: stanza["Description"],
|
||||
Status: domain.Current,
|
||||
})
|
||||
}
|
||||
|
||||
return packages, nil
|
||||
}
|
||||
|
||||
func nameContains(name string, match []string) bool {
|
||||
for _, m := range match {
|
||||
if strings.Contains(name, m) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetPackagesCount() domain.PackagesCount {
|
||||
count := domain.PackagesCount{
|
||||
Stale: 0,
|
||||
Missing: 0,
|
||||
Current: 0,
|
||||
Error: 0,
|
||||
Queued: 0,
|
||||
Building: 0,
|
||||
}
|
||||
currentPackagesFastMap.Iter(func(k string, v domain.SourcePackage) bool {
|
||||
v.Packages.Iter(func(k string, pkg domain.PackageInfo) bool {
|
||||
switch pkg.Status {
|
||||
case domain.Stale:
|
||||
count.Stale++
|
||||
case domain.Missing:
|
||||
count.Missing++
|
||||
case domain.Built:
|
||||
count.Current++
|
||||
case domain.Current:
|
||||
count.Current++
|
||||
case domain.Error:
|
||||
count.Error++
|
||||
}
|
||||
if pkg.LastBuildStatus == domain.Error {
|
||||
count.Error++
|
||||
}
|
||||
return false
|
||||
})
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
93
server.go
Normal file
93
server.go
Normal file
@ -0,0 +1,93 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"brunel/config"
|
||||
handlers_auth "brunel/handlers/auth"
|
||||
handlers_packages "brunel/handlers/packages"
|
||||
"brunel/helpers"
|
||||
"brunel/middleware"
|
||||
"brunel/packages"
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/gofiber/fiber/v2/middleware/etag"
|
||||
"github.com/gofiber/fiber/v2/middleware/recover"
|
||||
slogfiber "github.com/samber/slog-fiber"
|
||||
)
|
||||
|
||||
// runServer runs a new HTTP server with the loaded environment variables.
|
||||
func runServer(ctx context.Context) error {
|
||||
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
|
||||
|
||||
err := config.Init()
|
||||
if err != nil {
|
||||
slog.Error("unable to load configuration: " + err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
err := helpers.DBInst.DeleteExpiredSessions()
|
||||
if err != nil {
|
||||
slog.Error("unable to delete expired sessions: " + err.Error())
|
||||
}
|
||||
time.Sleep(time.Minute)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
start := time.Now()
|
||||
err = packages.LoadFromDb()
|
||||
if err != nil {
|
||||
slog.Error("unable to load packages from db: " + err.Error())
|
||||
return err
|
||||
}
|
||||
slog.Info("packages loaded from db in " + time.Since(start).String())
|
||||
|
||||
start = time.Now()
|
||||
err = packages.ProcessPackages()
|
||||
if err != nil {
|
||||
slog.Error("unable to process packages: " + err.Error())
|
||||
return err
|
||||
}
|
||||
slog.Info("packages processed in " + time.Since(start).String())
|
||||
|
||||
cfg := fiber.Config{
|
||||
JSONEncoder: json.Marshal,
|
||||
JSONDecoder: json.Unmarshal,
|
||||
ReadTimeout: 5 * time.Second,
|
||||
WriteTimeout: 10 * time.Second,
|
||||
AppName: "brunel",
|
||||
Prefork: false,
|
||||
}
|
||||
|
||||
server := fiber.New(cfg)
|
||||
|
||||
server.Use(slogfiber.NewWithConfig(logger, slogfiber.Config{
|
||||
WithSpanID: true,
|
||||
WithTraceID: true,
|
||||
}))
|
||||
server.Use(recover.New())
|
||||
server.Use(etag.New())
|
||||
|
||||
adminRoutes := server.Group("api/admin")
|
||||
adminRoutes.Use(middleware.NewAuth())
|
||||
|
||||
server.Get("/api/counts", handlers_packages.Counts)
|
||||
server.Get("/api/packages", handlers_packages.Packages)
|
||||
|
||||
server.Post("/api/login", handlers_auth.Login)
|
||||
adminRoutes.Post("/register", handlers_auth.Register)
|
||||
adminRoutes.Post("/updatePassword", handlers_auth.UpdatePassword)
|
||||
|
||||
return server.Listen(fmt.Sprintf(":%d", config.Configs.Port))
|
||||
}
|
Loading…
Reference in New Issue
Block a user