diff --git a/AGENTS.md b/AGENTS.md index baa05bb..c9e864d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,4 +22,4 @@ Commits follow a Conventional Commits prefix (`fix:`, `refactor:`) in imperative Document expected locations for `yt-dlp`, cookies, and OPML files when altering defaults. If a change requires bgutil or other external services, provide startup commands and update the sample `config.json`. Ensure new options degrade gracefully for existing configs and call out migration steps in the PR description. ## HTTP API -Enable the intake server with `http_api.enable` (binds to `127.0.0.1:4416`). Set `auth_token` for bearer auth and `queue_file` to persist pending jobs. Example: `curl -H "Authorization: Bearer $TOKEN" -d '{"url":"https://youtu.be/ID","out_dir":"Channel"}' http://127.0.0.1:4416/v1/videos`. Inspect the queue with `curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:4416/status`. +Enable the intake server with `http_api.enable` (binds to `127.0.0.1:4416`). Set `auth_token` or point `auth_token_file` at a mounted secret, and use `queue_file` to persist pending jobs. Example: `curl -H "Authorization: Bearer $TOKEN" -d '{"url":"https://youtu.be/ID","out_dir":"Channel"}' http://127.0.0.1:4416/v1/videos`. Inspect the queue with `curl -H "Authorization: Bearer $TOKEN" http://127.0.0.1:4416/status`. diff --git a/README.md b/README.md index c707aec..4e8bdda 100644 --- a/README.md +++ b/README.md @@ -273,7 +273,7 @@ OPML export. "http_api": { "enable": true, "listen": "127.0.0.1:4416", - "auth_token": "super-secret", + "auth_token_file": "/run/secrets/subsyt-token", "queue_file": "./tmp/api-queue.json" } } @@ -283,7 +283,7 @@ Submit new downloads with bearer authentication: ``` curl \ - -H "Authorization: Bearer super-secret" \ + -H "Authorization: Bearer $(cat /run/secrets/subsyt-token)" \ -H "Content-Type: application/json" \ --data '{"url":"https://youtu.be/VIDEO","out_dir":"Channel"}' \ http://127.0.0.1:4416/v1/videos diff --git a/internal/config/config.go b/internal/config/config.go index 8ad4436..5929693 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type Http_api struct { Enable bool Listen string Auth_token string + Auth_token_file string Queue_file string } diff --git a/internal/server/server.go b/internal/server/server.go index 1e7de06..4db42e9 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -7,6 +7,7 @@ import ( "log" "net/http" "net/url" + "os" "path/filepath" "strings" "time" @@ -22,15 +23,28 @@ type Server struct { defaultOutDir string httpServer *http.Server shutdownSignal chan struct{} + authToken string } func NewServer(cfg config.Http_api, defaultOutDir string, queue *Queue) *Server { - return &Server{ + srv := &Server{ cfg: cfg, queue: queue, defaultOutDir: defaultOutDir, shutdownSignal: make(chan struct{}), } + + srv.authToken = strings.TrimSpace(cfg.Auth_token) + + if srv.authToken == "" && cfg.Auth_token_file != "" { + if token, err := os.ReadFile(cfg.Auth_token_file); err == nil { + srv.authToken = strings.TrimSpace(string(token)) + } else { + log.Printf("failed to read auth token file %s: %v", cfg.Auth_token_file, err) + } + } + + return srv } func (s *Server) Start(ctx context.Context) error { @@ -156,7 +170,7 @@ func (s *Server) handleStatus(w http.ResponseWriter, r *http.Request) { } func (s *Server) authorize(header string) bool { - required := strings.TrimSpace(s.cfg.Auth_token) + required := s.authToken if required == "" { return true } diff --git a/internal/server/server_test.go b/internal/server/server_test.go index d659061..e38e280 100644 --- a/internal/server/server_test.go +++ b/internal/server/server_test.go @@ -87,7 +87,13 @@ func TestHandleStatus(t *testing.T) { t.Fatalf("new queue: %v", err) } - s := NewServer(config.Http_api{Auth_token: "secret"}, "/videos", queue) + tmp := t.TempDir() + tokenPath := filepath.Join(tmp, "token.txt") + if err := os.WriteFile(tokenPath, []byte("secret"), 0o600); err != nil { + t.Fatalf("write token file: %v", err) + } + + s := NewServer(config.Http_api{Auth_token_file: tokenPath}, "/videos", queue) req := httptest.NewRequest(http.MethodGet, "/status", nil) rec := httptest.NewRecorder()