Initial commit: LlamaLink Go rewrite

Complete rewrite from Python/FastAPI to Go/Gin:
- Go backend: auth (API keys + bcrypt), llama.cpp subprocess manager,
  hot-swap multi-model, rate limiting, quota system, webhooks
- Vue 3 SPA admin panel (src/) with Tailwind CSS
- Deployment: Docker multi-stage, docker-compose, nginx, systemd
- GORM/SQLite models: ApiKey, Model, UsageLog, Quota, Webhook
- REST API: /api/v1/admin/* (keys, models, chat, usage, health)
- Embedded frontend via go:embed (build output at web/dist/)

Removed legacy Python artifacts (app/, tests/, pyproject.toml, etc.)
This commit is contained in:
2026-07-30 10:58:55 -04:00
commit 4c9ed3c24b
52 changed files with 5105 additions and 0 deletions
+107
View File
@@ -0,0 +1,107 @@
package quota
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log/slog"
"net/http"
"time"
"github.com/google/uuid"
"gorm.io/gorm"
"github.com/llamalink/llamalink/internal/db"
)
type WebhookPayload struct {
Event string `json:"event"`
Timestamp time.Time `json:"timestamp"`
KeyID uuid.UUID `json:"key_id"`
KeyName string `json:"key_name"`
Message string `json:"message"`
Usage *struct {
Used int `json:"tokens_used"`
Limit int `json:"tokens_limit"`
} `json:"usage,omitempty"`
}
type WebhookService struct {
db *gorm.DB
client *http.Client
webhookSecret string
}
func NewWebhookService(db *gorm.DB) *WebhookService {
return &WebhookService{
db: db,
client: &http.Client{Timeout: 10 * time.Second},
}
}
func (s *WebhookService) Dispatch(event string, key *db.ApiKey, message string, usage *struct{ Used, Limit int }) {
var webhooks []db.Webhook
s.db.Where("api_key_id = ? AND event = ? AND is_active = ?", key.ID, event, true).Find(&webhooks)
for _, wh := range webhooks {
go s.send(wh, event, key, message, usage)
}
}
func (s *WebhookService) send(wh db.Webhook, event string, key *db.ApiKey, message string, usage *struct{ Used, Limit int }) {
payload := WebhookPayload{
Event: event,
Timestamp: time.Now().UTC(),
KeyID: key.ID,
KeyName: key.Name,
Message: message,
}
if usage != nil {
payload.Usage = &struct {
Used int `json:"tokens_used"`
Limit int `json:"tokens_limit"`
}{Used: usage.Used, Limit: usage.Limit}
}
body, err := json.Marshal(payload)
if err != nil {
slog.Error("failed to marshal webhook payload", "error", err)
return
}
req, err := http.NewRequest("POST", wh.URL, bytes.NewReader(body))
if err != nil {
slog.Error("failed to create webhook request", "error", err)
return
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-LlamaLink-Event", event)
req.Header.Set("X-LlamaLink-Timestamp", fmt.Sprintf("%d", time.Now().Unix()))
if wh.Secret != nil && *wh.Secret != "" {
sig := s.sign(body, *wh.Secret)
req.Header.Set("X-LlamaLink-Signature", sig)
}
resp, err := s.client.Do(req)
if err != nil {
slog.Error("webhook delivery failed", "url", wh.URL, "error", err)
return
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
slog.Warn("webhook returned error", "url", wh.URL, "status", resp.StatusCode)
}
}
func (s *WebhookService) sign(body []byte, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write(body)
return "sha256=" + hex.EncodeToString(h.Sum(nil))
}