diff --git a/alr-updater.example.toml b/alr-updater.example.toml index d9c1401..4aafd08 100644 --- a/alr-updater.example.toml +++ b/alr-updater.example.toml @@ -43,4 +43,12 @@ reposBaseDir = "/var/cache/alr-updater" log_file = "/var/log/alr-updater.log" # Максимальный размер файла логов в байтах (по умолчанию 100MB) # При достижении этого размера файл будет ротирован - max_size = 104857600 \ No newline at end of file + max_size = 104857600 + +[github] + # GitHub Personal Access Token для увеличения лимита API запросов + # Без токена: 60 запросов/час + # С токеном: 5000 запросов/час + # Создать токен: https://github.com/settings/tokens + # Требуемые права: public_repo (или repo для приватных репозиториев) + token = "CHANGE ME" \ No newline at end of file diff --git a/internal/builtins/http.go b/internal/builtins/http.go index 82bab2e..4e9176d 100644 --- a/internal/builtins/http.go +++ b/internal/builtins/http.go @@ -44,30 +44,88 @@ var ( ErrIncorrectPassword = errors.New("incorrect password") ) -var httpModule = &starlarkstruct.Module{ - Name: "http", - Members: starlark.StringDict{ - "get": starlark.NewBuiltin("http.get", httpGet), - "post": starlark.NewBuiltin("http.post", httpPost), - "put": starlark.NewBuiltin("http.put", httpPut), - "head": starlark.NewBuiltin("http.head", httpHead), - }, +func newHTTPModule(cfg *config.Config) *starlarkstruct.Module { + return &starlarkstruct.Module{ + Name: "http", + Members: starlark.StringDict{ + "get": httpGetWithConfig(cfg), + "post": httpPostWithConfig(cfg), + "put": httpPutWithConfig(cfg), + "head": httpHeadWithConfig(cfg), + }, + } } -func httpGet(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - return makeRequest("http.get", http.MethodGet, args, kwargs, thread) +func httpGetWithConfig(cfg *config.Config) *starlark.Builtin { + return starlark.NewBuiltin("http.get", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + return makeRequestWithConfig("http.get", http.MethodGet, args, kwargs, thread, cfg) + }) } -func httpPost(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - return makeRequest("http.post", http.MethodPost, args, kwargs, thread) +func httpPostWithConfig(cfg *config.Config) *starlark.Builtin { + return starlark.NewBuiltin("http.post", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + return makeRequestWithConfig("http.post", http.MethodPost, args, kwargs, thread, cfg) + }) } -func httpPut(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - return makeRequest("http.put", http.MethodPut, args, kwargs, thread) +func httpPutWithConfig(cfg *config.Config) *starlark.Builtin { + return starlark.NewBuiltin("http.put", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + return makeRequestWithConfig("http.put", http.MethodPut, args, kwargs, thread, cfg) + }) } -func httpHead(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { - return makeRequest("http.head", http.MethodHead, args, kwargs, thread) +func httpHeadWithConfig(cfg *config.Config) *starlark.Builtin { + return starlark.NewBuiltin("http.head", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { + return makeRequestWithConfig("http.head", http.MethodHead, args, kwargs, thread, cfg) + }) +} + +func makeRequestWithConfig(name, method string, args starlark.Tuple, kwargs []starlark.Tuple, thread *starlark.Thread, cfg *config.Config) (starlark.Value, error) { + var ( + url string + redirect = true + headers = &starlarkHeaders{} + body = newBodyReader() + ) + err := starlark.UnpackArgs(name, args, kwargs, "url", &url, "redirect??", &redirect, "headers??", headers, "body??", body) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(method, url, body) + if err != nil { + return nil, err + } + + // Устанавливаем заголовки из аргументов + if headers.Header != nil { + req.Header = headers.Header + } + + // Добавляем GitHub токен для запросов к api.github.com + if cfg.GitHub.Token != "" && strings.Contains(url, "api.github.com") { + req.Header.Set("Authorization", "token "+cfg.GitHub.Token) + } + + client := http.DefaultClient + if !redirect { + client = &http.Client{ + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return http.ErrUseLastResponse + }, + } + } + + log.Debug("Making HTTP request").Str("url", url).Str("method", req.Method).Bool("redirect", redirect).Stringer("pos", thread.CallFrame(1).Pos).Send() + + res, err := client.Do(req) + if err != nil { + return nil, err + } + + log.Debug("Got HTTP response").Str("host", res.Request.URL.Host).Int("code", res.StatusCode).Stringer("pos", thread.CallFrame(1).Pos).Send() + + return starlarkResponse(res), nil } type starlarkBodyReader struct { diff --git a/internal/builtins/register.go b/internal/builtins/register.go index 08099ba..e08dc4e 100644 --- a/internal/builtins/register.go +++ b/internal/builtins/register.go @@ -38,7 +38,7 @@ type Options struct { func Register(sd starlark.StringDict, opts *Options) { sd["run_every"] = runEveryModule sd["sleep"] = starlark.NewBuiltin("sleep", sleep) - sd["http"] = httpModule + sd["http"] = newHTTPModule(opts.Config) sd["regex"] = regexModule sd["store"] = storeModule(opts.DB, opts.Name) sd["updater"] = updaterModule(opts.Config) diff --git a/internal/builtins/run_every.go b/internal/builtins/run_every.go index d6eb168..4472db0 100644 --- a/internal/builtins/run_every.go +++ b/internal/builtins/run_every.go @@ -178,6 +178,15 @@ func runScheduled(thread *starlark.Thread, fn *starlark.Function, duration strin tickerMtx.Unlock() log.Debug("Created new scheduled ticker").Int("handle", handle).Str("duration", duration).Stringer("pos", thread.CallFrame(1).Pos).Send() + // Запускаем функцию немедленно при первой регистрации + go func() { + log.Info("Running plugin function immediately on startup").Str("plugin", thread.Name).Str("function", fn.Name()).Send() + _, err := starlark.Call(thread, fn, nil, nil) + if err != nil { + log.Warn("Error while executing initial plugin function").Str("plugin", thread.Name).Str("function", fn.Name()).Err(err).Send() + } + }() + go func() { for range t.C { log.Debug("Calling scheduled function").Str("name", fn.Name()).Stringer("pos", fn.Position()).Send() diff --git a/internal/builtins/updater.go b/internal/builtins/updater.go index fdc938c..92fdd1b 100644 --- a/internal/builtins/updater.go +++ b/internal/builtins/updater.go @@ -87,11 +87,34 @@ func updaterPull(cfg *config.Config) *starlark.Builtin { return nil, err } + // Исправляем права доступа после git pull + err = fixRepoPermissions(repoDir) + if err != nil { + log.Warn("Failed to fix repository permissions after pull").Str("repo", repoName).Err(err).Send() + } + _ = repoConfig // Избегаем неиспользованной переменной return starlark.None, nil }) } +// fixRepoPermissions рекурсивно устанавливает права 775 для директорий и 664 для файлов +func fixRepoPermissions(path string) error { + return filepath.Walk(path, func(filePath string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + // Устанавливаем права 2775 для директорий (setgid) + return os.Chmod(filePath, 0o2775) + } else { + // Устанавливаем права 664 для файлов + return os.Chmod(filePath, 0o664) + } + }) +} + func updaterPushChanges(cfg *config.Config) *starlark.Builtin { return starlark.NewBuiltin("updater.push_changes", func(thread *starlark.Thread, b *starlark.Builtin, args starlark.Tuple, kwargs []starlark.Tuple) (starlark.Value, error) { var repoName, msg string diff --git a/internal/config/config.go b/internal/config/config.go index 6465df8..ae3b7a9 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -23,6 +23,7 @@ type Config struct { Repositories map[string]GitRepo `toml:"repositories"` Webhook Webhook `toml:"webhook" envPrefix:"WEBHOOK_"` Logging Logging `toml:"logging" envPrefix:"LOGGING_"` + GitHub GitHub `toml:"github" envPrefix:"GITHUB_"` } type GitRepo struct { @@ -50,3 +51,7 @@ type Logging struct { MaxSize int64 `toml:"max_size" env:"MAX_SIZE"` EnableFile bool `toml:"enable_file" env:"ENABLE_FILE"` } + +type GitHub struct { + Token string `toml:"token" env:"TOKEN"` +} diff --git a/main.go b/main.go index 450290a..0de52e8 100644 --- a/main.go +++ b/main.go @@ -21,7 +21,6 @@ package main import ( "bufio" "fmt" - "io" "net/http" "os" "path/filepath" @@ -244,7 +243,7 @@ func main() { log.Fatal("Error creating repository directory").Str("repo", repoName).Err(err).Send() } - _, err := git.PlainClone(repoDir, false, &git.CloneOptions{ + repo, err := git.PlainClone(repoDir, false, &git.CloneOptions{ URL: repoConfig.RepoURL, Progress: os.Stderr, }) @@ -252,6 +251,16 @@ func main() { log.Fatal("Error cloning repository").Str("repo", repoName).Err(err).Send() } + // Настраиваем Git для корректной работы с правами доступа + gitConfig, err := repo.Config() + if err == nil { + gitConfig.Raw.Section("core").SetOption("sharedRepository", "group") + err = repo.SetConfig(gitConfig) + if err != nil { + log.Warn("Failed to set Git sharedRepository config").Str("repo", repoName).Err(err).Send() + } + } + // Исправляем права доступа после клонирования if err := fixRepoPermissions(repoDir); err != nil { log.Error("Error fixing repository permissions").Str("repo", repoName).Err(err).Send() @@ -262,6 +271,19 @@ func main() { log.Fatal("Cannot stat repository directory").Str("repo", repoName).Err(err).Send() } else { log.Info("Repository already exists").Str("name", repoName).Send() + + // Настраиваем Git конфигурацию для существующих репозиториев + repo, err := git.PlainOpen(repoDir) + if err == nil { + gitConfig, err := repo.Config() + if err == nil { + gitConfig.Raw.Section("core").SetOption("sharedRepository", "group") + err = repo.SetConfig(gitConfig) + if err != nil { + log.Warn("Failed to set Git sharedRepository config").Str("repo", repoName).Err(err).Send() + } + } + } } } diff --git a/scripts/install.sh b/scripts/install.sh index 3397991..42e764f 100644 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -25,6 +25,7 @@ CONFIG_DIR="/etc/alr-updater" DATA_DIR="/var/lib/alr-updater" CACHE_DIR="/var/cache/alr-updater" PLUGIN_DIR="${CONFIG_DIR}/plugins" +LOG_FILE="/var/log/alr-updater.log" # Создание пользователя и добавление в группу wheel echo -e "${YELLOW}Creating user and adding to wheel group...${NC}" @@ -44,6 +45,12 @@ mkdir -p ${DATA_DIR} mkdir -p ${CACHE_DIR} mkdir -p ${PLUGIN_DIR} +# Создание файла лога +echo -e "${YELLOW}Creating log file...${NC}" +touch ${LOG_FILE} +chown ${SERVICE_USER}:${SERVICE_GROUP} ${LOG_FILE} +chmod 664 ${LOG_FILE} + # Установка прав доступа с setgid битом echo -e "${YELLOW}Setting permissions with setgid...${NC}" chown -R root:${SERVICE_GROUP} ${DATA_DIR} @@ -99,7 +106,7 @@ NoNewPrivileges=true PrivateTmp=true ProtectSystem=strict ProtectHome=true -ReadWritePaths=${DATA_DIR} ${CACHE_DIR} +ReadWritePaths=${DATA_DIR} ${CACHE_DIR} ${LOG_FILE} ReadOnlyPaths=${CONFIG_DIR} [Install]