Compare commits

...

3 Commits

Author SHA1 Message Date
Grendgi
5eb8e21eda Add monitoring TG CI hygiene guard
All checks were successful
CI / hygiene (push) Successful in 2s
Build and Deploy / build-and-deploy (push) Successful in 27s
CI / go (push) Successful in 26s
CI / python (push) Successful in 2s
2026-06-12 16:42:35 +03:00
Grendgi
778b48cc12 Protect monitoring TG API with internal key 2026-06-12 16:32:10 +03:00
Grendgi
1f1354e72b Retry monitoring TG database connection on startup 2026-06-12 16:28:02 +03:00
6 changed files with 109 additions and 2 deletions

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
fail=0
while IFS= read -r -d '' path; do
base="$(basename "$path")"
case "$base" in
.DS_Store|.env)
echo "::error file=$path::tracked local-only file is forbidden"
fail=1
;;
esac
case "$path" in
*node_modules/*|node_modules/*)
echo "::error file=$path::tracked node_modules content is forbidden"
fail=1
;;
*.tmp|*.temp|*.bak|*.orig|*.rej|*.zip|*.tar|*.tar.gz|*.tgz|*.rar|*.7z)
echo "::error file=$path::tracked temporary/archive artifact is forbidden"
fail=1
;;
esac
if [ -f "$path" ]; then
size="$(wc -c < "$path" | tr -d ' ')"
if [ "${size:-0}" -gt 52428800 ]; then
echo "::error file=$path::tracked file is larger than 50 MiB"
fail=1
fi
fi
done < <(git ls-files -z)
exit "$fail"

View File

@@ -5,8 +5,15 @@ on:
pull_request:
jobs:
hygiene:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: bash .gitea/scripts/hygiene-check.sh
go:
runs-on: ubuntu-latest
needs: hygiene
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
@@ -22,6 +29,7 @@ jobs:
python:
runs-on: ubuntu-latest
needs: hygiene
steps:
- uses: actions/checkout@v4
- run: python3 -m compileall src alembic

View File

@@ -15,6 +15,7 @@ import (
"time"
"monitoring-tg/internal/aiservice"
"monitoring-tg/internal/dbretry"
"github.com/jackc/pgx/v5/pgxpool"
)
@@ -69,7 +70,7 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
pool, err := pgxpool.New(ctx, cfg.databaseURL())
pool, err := dbretry.Connect(ctx, cfg.databaseURL(), 2*time.Minute)
if err != nil {
slog.Error("db_connect_failed", "error", err)
os.Exit(1)

View File

@@ -21,6 +21,7 @@ import (
"time"
"monitoring-tg/internal/aiservice"
"monitoring-tg/internal/dbretry"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
@@ -49,6 +50,7 @@ type config struct {
LLMTimeout time.Duration
AIServiceURL string
AIServiceToken string
InternalAPIKey string
MinioEndpoint string
MinioAccessKey string
MinioSecretKey string
@@ -134,7 +136,7 @@ func main() {
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
pool, err := pgxpool.New(ctx, cfg.databaseURL())
pool, err := dbretry.Connect(ctx, cfg.databaseURL(), 2*time.Minute)
if err != nil {
slog.Error("db_connect_failed", "error", err)
os.Exit(1)
@@ -190,6 +192,9 @@ func (a *app) serveHTTP(w http.ResponseWriter, r *http.Request) {
writeError(w, http.StatusNotFound, "not found")
return
}
if !a.checkInternalAuth(w, r) {
return
}
ctx := r.Context()
switch {
@@ -226,6 +231,18 @@ func (a *app) serveHTTP(w http.ResponseWriter, r *http.Request) {
}
}
func (a *app) checkInternalAuth(w http.ResponseWriter, r *http.Request) bool {
want := strings.TrimSpace(a.cfg.InternalAPIKey)
if want == "" {
return true
}
if r.Header.Get("X-Internal-Key") != want {
writeError(w, http.StatusUnauthorized, "unauthorized")
return false
}
return true
}
func (a *app) apiPath(path string) string {
base := strings.TrimRight(a.cfg.PublicBasePath, "/")
if base != "" && strings.HasPrefix(path, base+"/") {
@@ -1872,6 +1889,7 @@ func loadConfig() config {
LLMTimeout: time.Duration(envInt("LLM_TIMEOUT_SECONDS", 120)) * time.Second,
AIServiceURL: env("AI_SERVICE_URL", ""),
AIServiceToken: env("AI_SERVICE_TOKEN", ""),
InternalAPIKey: env("INTERNAL_API_KEY", env("PORTAL_INTERNAL_API_KEY", "")),
MinioEndpoint: env("MINIO_ENDPOINT", ""),
MinioAccessKey: env("MINIO_ACCESS_KEY", ""),
MinioSecretKey: env("MINIO_SECRET_KEY", ""),

View File

@@ -0,0 +1,44 @@
package dbretry
import (
"context"
"fmt"
"time"
"github.com/jackc/pgx/v5/pgxpool"
)
func Connect(ctx context.Context, databaseURL string, maxWait time.Duration) (*pgxpool.Pool, error) {
deadline := time.Now().Add(maxWait)
var lastErr error
for attempt := 1; ; attempt++ {
pool, err := pgxpool.New(ctx, databaseURL)
if err == nil {
if pingErr := pool.Ping(ctx); pingErr == nil {
return pool, nil
} else {
err = fmt.Errorf("ping postgres: %w", pingErr)
pool.Close()
}
} else {
err = fmt.Errorf("connect postgres: %w", err)
}
lastErr = err
if time.Now().After(deadline) {
return nil, fmt.Errorf("connect postgres after retry: %w", lastErr)
}
sleep := time.Duration(attempt) * time.Second
if sleep > 5*time.Second {
sleep = 5 * time.Second
}
timer := time.NewTimer(sleep)
select {
case <-ctx.Done():
timer.Stop()
return nil, fmt.Errorf("connect postgres cancelled: %w", ctx.Err())
case <-timer.C:
}
}
}

View File

@@ -10,6 +10,7 @@ stringData:
TG_PHONE: "+971524994695"
TG_SESSION_STRING: ""
POSTGRES_PASSWORD: "parser"
INTERNAL_API_KEY: "36fe89ed40c01fdc54d3cf4e3fcacc8751dc456a4a1acd394e9fed48257c5734"
AI_SERVICE_TOKEN: "d18bcacf9e02bae1806ee6b6eeda62b95be6a915c0a22936d9a700128b275442"
MINIO_ACCESS_KEY: "admjn"
MINIO_SECRET_KEY: "TropicalMacaw9Fantasize"