Files
ALR-updater/main.go
Евгений (ХрамычЪ) Храмов 2ae44a466e feat: добавлен флаг --now для немедленного запуска всех проверок
- Добавлен флаг --now (-n) для запуска всех зарегистрированных проверок сразу при старте
- Программа завершается после выполнения всех проверок при использовании --now
- Исправлена ошибка в плагине binary-packages с store.get()
- Исправлена ошибка с атрибутом .text в плагине binary-packages
- Использован sync.WaitGroup для корректного ожидания завершения всех проверок
2025-08-15 22:50:34 +03:00

194 lines
6.1 KiB
Go

/*
* ALR Updater - Automated updater bot for ALR packages
* Copyright (C) 2025 The ALR Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package main
import (
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
"sync"
"github.com/caarlos0/env/v8"
"github.com/go-git/go-git/v5"
"github.com/pelletier/go-toml/v2"
"github.com/spf13/pflag"
"go.elara.ws/logger"
"go.elara.ws/logger/log"
"gitea.plemya-x.ru/Plemya-x/ALR-updater/internal/builtins"
"gitea.plemya-x.ru/Plemya-x/ALR-updater/internal/config"
"go.etcd.io/bbolt"
"go.starlark.net/starlark"
"golang.org/x/crypto/bcrypt"
"golang.org/x/term"
)
func init() {
log.Logger = logger.NewPretty(os.Stderr)
}
func main() {
configPath := pflag.StringP("config", "c", "/etc/alr-updater/config.toml", "Path to config file")
dbPath := pflag.StringP("database", "d", "/etc/alr-updater/db", "Path to database file")
pluginDir := pflag.StringP("plugin-dir", "p", "/etc/alr-updater/plugins", "Path to plugin directory")
serverAddr := pflag.StringP("address", "a", ":8080", "Webhook server address")
genHash := pflag.BoolP("gen-hash", "g", false, "Generate a password hash for webhooks")
useEnv := pflag.BoolP("use-env", "E", false, "Use environment variables for configuration")
debug := pflag.BoolP("debug", "D", false, "Enable debug logging")
runNow := pflag.BoolP("now", "n", false, "Run all plugin checks immediately on startup")
pflag.Parse()
if *debug {
log.Logger.SetLevel(logger.LogLevelDebug)
}
if *genHash {
fmt.Print("Password: ")
pwd, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
log.Fatal("Error reading password").Err(err).Send()
}
hash, err := bcrypt.GenerateFromPassword(pwd, bcrypt.DefaultCost)
if err != nil {
log.Fatal("Error hashing password").Err(err).Send()
}
fmt.Printf("\n%s\n", hash)
return
}
db, err := bbolt.Open(*dbPath, 0o644, nil)
if err != nil {
log.Fatal("Error opening database").Err(err).Send()
}
cfg := &config.Config{}
if *useEnv {
err = env.Parse(cfg)
if err != nil {
log.Fatal("Error parsing environment variables").Err(err).Send()
}
} else {
fl, err := os.Open(*configPath)
if err != nil {
log.Fatal("Error opening config file").Err(err).Send()
}
err = toml.NewDecoder(fl).Decode(cfg)
if err != nil {
log.Fatal("Error decoding config file").Err(err).Send()
}
err = fl.Close()
if err != nil {
log.Fatal("Error closing config file").Err(err).Send()
}
}
if _, err := os.Stat(cfg.Git.RepoDir); os.IsNotExist(err) {
err = os.MkdirAll(cfg.Git.RepoDir, 0o755)
if err != nil {
log.Fatal("Error creating repository directory").Err(err).Send()
}
_, err := git.PlainClone(cfg.Git.RepoDir, false, &git.CloneOptions{
URL: cfg.Git.RepoURL,
Progress: os.Stderr,
})
if err != nil {
log.Fatal("Error cloning repository").Err(err).Send()
}
} else if err != nil {
log.Fatal("Cannot stat configured repo directory").Err(err).Send()
}
starFiles, err := filepath.Glob(filepath.Join(*pluginDir, "*.star"))
if err != nil {
log.Fatal("Error finding plugin files").Err(err).Send()
}
if len(starFiles) == 0 {
log.Fatal("No plugins found. At least one plugin is required.").Send()
}
mux := http.NewServeMux()
for _, starFile := range starFiles {
pluginName := filepath.Base(strings.TrimSuffix(starFile, ".star"))
thread := &starlark.Thread{Name: pluginName}
predeclared := starlark.StringDict{}
builtins.Register(predeclared, &builtins.Options{
Name: pluginName,
Config: cfg,
DB: db,
Mux: mux,
RunNow: *runNow,
})
_, err = starlark.ExecFile(thread, starFile, nil, predeclared)
if err != nil {
log.Fatal("Error executing starlark file").Str("file", starFile).Err(err).Send()
}
log.Info("Initialized plugin").Str("name", pluginName).Send()
}
// Запускаем все зарегистрированные функции немедленно если установлен флаг --now
if *runNow {
// Получаем все функции, зарегистрированные через run_every
registeredFns := builtins.GetRegisteredFunctions()
if len(registeredFns) > 0 {
log.Info("Running all registered plugin checks immediately").Int("functions", len(registeredFns)).Send()
var wg sync.WaitGroup
for key, fn := range registeredFns {
parts := strings.Split(key, ":")
pluginName := parts[0]
log.Info("Executing registered function").Str("plugin", pluginName).Str("function", fn.Name()).Send()
wg.Add(1)
// Запускаем функцию в горутине для параллельного выполнения
go func(function *starlark.Function, plugin string) {
defer wg.Done()
thread := &starlark.Thread{Name: plugin}
_, err := starlark.Call(thread, function, nil, nil)
if err != nil {
log.Error("Error executing function").Str("plugin", plugin).Str("function", function.Name()).Err(err).Send()
} else {
log.Info("Function executed successfully").Str("plugin", plugin).Str("function", function.Name()).Send()
}
}(fn, pluginName)
}
// Ждём завершения всех функций
log.Info("Waiting for immediate checks to complete...").Send()
wg.Wait()
log.Info("All checks completed, exiting").Send()
return // Выходим после выполнения всех проверок
} else {
log.Warn("No functions registered with run_every, nothing to run immediately").Send()
return
}
}
log.Info("Starting HTTP server").Str("addr", *serverAddr).Send()
http.ListenAndServe(*serverAddr, mux)
}