Initial commit: Null DRM Official

Capture, decrypt, and restream toolkit with compiled-in app modules
(RTE, TG4, BBC), on-device MITM proxy, streamd control plane, and www.
BBC module.yaml is published (clear streams); other module values stay local.
This commit is contained in:
404errordeveloper 2026-10-06 00:25:35 +02:00
commit 2fa8f2435f
121 changed files with 17802 additions and 0 deletions

View file

@ -0,0 +1,334 @@
package queue
import (
"database/sql"
"fmt"
"os"
"path/filepath"
"time"
_ "modernc.org/sqlite"
)
type Status string
const (
StatusQueued Status = "queued"
StatusRunning Status = "running"
StatusOK Status = "ok"
StatusFailed Status = "failed"
StatusCancel Status = "cancelled"
)
type Job struct {
ID int64
StreamID int64
StreamName string
App string
Channel string
Reason string
Status Status
Priority int
Attempts int
MaxAttempts int
DeviceSerial string
LastError string
NotBefore time.Time
CreatedAt time.Time
StartedAt time.Time
FinishedAt time.Time
}
type Store struct {
DB *sql.DB
}
func Open(dataDir string) (*Store, error) {
if err := os.MkdirAll(dataDir, 0o755); err != nil {
return nil, err
}
path := filepath.Join(dataDir, "agent.db")
dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)", filepath.ToSlash(path))
db, err := sql.Open("sqlite", dsn)
if err != nil {
return nil, err
}
db.SetMaxOpenConns(1)
s := &Store{DB: db}
if err := s.migrate(); err != nil {
_ = db.Close()
return nil, err
}
return s, nil
}
func (s *Store) Close() error { return s.DB.Close() }
func (s *Store) migrate() error {
_, err := s.DB.Exec(`
CREATE TABLE IF NOT EXISTS jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
stream_id INTEGER NOT NULL,
stream_name TEXT NOT NULL DEFAULT '',
app TEXT NOT NULL DEFAULT '',
channel TEXT NOT NULL DEFAULT '',
reason TEXT NOT NULL DEFAULT '',
status TEXT NOT NULL DEFAULT 'queued',
priority INTEGER NOT NULL DEFAULT 100,
attempts INTEGER NOT NULL DEFAULT 0,
max_attempts INTEGER NOT NULL DEFAULT 5,
device_serial TEXT NOT NULL DEFAULT '',
last_error TEXT NOT NULL DEFAULT '',
not_before TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
started_at TEXT NOT NULL DEFAULT '',
finished_at TEXT NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status, priority, id);
CREATE INDEX IF NOT EXISTS idx_jobs_stream ON jobs(stream_id, status);
`)
return err
}
func now() string { return time.Now().UTC().Format(time.RFC3339) }
func parseTime(s string) time.Time {
if s == "" {
return time.Time{}
}
t, _ := time.Parse(time.RFC3339, s)
return t
}
// EnqueueIfIdle inserts a job unless one is already queued/running for the stream.
func (s *Store) EnqueueIfIdle(streamID int64, name, app, channel, reason string, maxAttempts int) (Job, bool, error) {
var existing int
err := s.DB.QueryRow(`
SELECT COUNT(1) FROM jobs WHERE stream_id=? AND status IN ('queued','running')`, streamID).Scan(&existing)
if err != nil {
return Job{}, false, err
}
if existing > 0 {
return Job{}, false, nil
}
if maxAttempts <= 0 {
maxAttempts = 5
}
res, err := s.DB.Exec(`
INSERT INTO jobs(stream_id,stream_name,app,channel,reason,status,priority,attempts,max_attempts,created_at,not_before)
VALUES(?,?,?,?,?,'queued',100,0,?,?,?)`,
streamID, name, app, channel, reason, maxAttempts, now(), now())
if err != nil {
return Job{}, false, err
}
id, _ := res.LastInsertId()
j, err := s.Get(id)
return j, true, err
}
func (s *Store) Get(id int64) (Job, error) {
row := s.DB.QueryRow(`
SELECT id,stream_id,stream_name,app,channel,reason,status,priority,attempts,max_attempts,
device_serial,last_error,not_before,created_at,started_at,finished_at
FROM jobs WHERE id=?`, id)
return scanJob(row)
}
type scannable interface {
Scan(dest ...any) error
}
func scanJob(row scannable) (Job, error) {
var j Job
var status, nb, ca, sa, fa string
err := row.Scan(
&j.ID, &j.StreamID, &j.StreamName, &j.App, &j.Channel, &j.Reason, &status,
&j.Priority, &j.Attempts, &j.MaxAttempts, &j.DeviceSerial, &j.LastError,
&nb, &ca, &sa, &fa,
)
if err != nil {
return j, err
}
j.Status = Status(status)
j.NotBefore = parseTime(nb)
j.CreatedAt = parseTime(ca)
j.StartedAt = parseTime(sa)
j.FinishedAt = parseTime(fa)
return j, nil
}
// ClaimNext marks the next ready queued job as running. Returns false if none.
func (s *Store) ClaimNext() (Job, bool, error) {
tx, err := s.DB.Begin()
if err != nil {
return Job{}, false, err
}
defer func() { _ = tx.Rollback() }()
nowStr := now()
row := tx.QueryRow(`
SELECT id FROM jobs
WHERE status='queued' AND (not_before='' OR not_before<=?)
ORDER BY priority ASC, id ASC
LIMIT 1`, nowStr)
var id int64
if err := row.Scan(&id); err != nil {
if err == sql.ErrNoRows {
return Job{}, false, nil
}
return Job{}, false, err
}
_, err = tx.Exec(`UPDATE jobs SET status='running', started_at=?, last_error='' WHERE id=? AND status='queued'`, nowStr, id)
if err != nil {
return Job{}, false, err
}
if err := tx.Commit(); err != nil {
return Job{}, false, err
}
j, err := s.Get(id)
return j, true, err
}
func (s *Store) SetDevice(id int64, serial string) error {
_, err := s.DB.Exec(`UPDATE jobs SET device_serial=? WHERE id=?`, serial, id)
return err
}
func (s *Store) MarkOK(id int64) error {
_, err := s.DB.Exec(`UPDATE jobs SET status='ok', finished_at=?, last_error='' WHERE id=?`, now(), id)
return err
}
// MarkFailed increments attempts. Re-queues with backoff unless maxed out.
// waitingDevice=true keeps status queued without burning an attempt.
func (s *Store) MarkFailed(id int64, msg string, waitingDevice bool) error {
j, err := s.Get(id)
if err != nil {
return err
}
if waitingDevice {
_, err = s.DB.Exec(`
UPDATE jobs SET status='queued', device_serial='', last_error=?, started_at='' WHERE id=?`,
msg, id)
return err
}
attempts := j.Attempts + 1
if attempts >= j.MaxAttempts {
_, err = s.DB.Exec(`
UPDATE jobs SET status='failed', attempts=?, last_error=?, finished_at=? WHERE id=?`,
attempts, msg, now(), id)
return err
}
backoff := time.Duration(1<<uint(min(attempts, 4))) * time.Minute
if backoff > 15*time.Minute {
backoff = 15 * time.Minute
}
nb := time.Now().UTC().Add(backoff).Format(time.RFC3339)
_, err = s.DB.Exec(`
UPDATE jobs SET status='queued', attempts=?, last_error=?, not_before=?, device_serial='', started_at='' WHERE id=?`,
attempts, msg, nb, id)
return err
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func (s *Store) Cancel(id int64) error {
_, err := s.DB.Exec(`
UPDATE jobs SET status='cancelled', finished_at=? WHERE id=? AND status IN ('queued','running')`, now(), id)
return err
}
func (s *Store) ListRecent(limit int) ([]Job, error) {
if limit <= 0 {
limit = 30
}
rows, err := s.DB.Query(`
SELECT id,stream_id,stream_name,app,channel,reason,status,priority,attempts,max_attempts,
device_serial,last_error,not_before,created_at,started_at,finished_at
FROM jobs ORDER BY id DESC LIMIT ?`, limit)
if err != nil {
return nil, err
}
defer rows.Close()
var out []Job
for rows.Next() {
j, err := scanJob(rows)
if err != nil {
return nil, err
}
out = append(out, j)
}
return out, rows.Err()
}
func (s *Store) ActiveForStream(streamID int64) (bool, error) {
var n int
err := s.DB.QueryRow(`SELECT COUNT(1) FROM jobs WHERE stream_id=? AND status IN ('queued','running')`, streamID).Scan(&n)
return n > 0, err
}
// RecoverStaleRunning re-queues jobs left in status=running after a crash/kill
// so a down stream is not blocked forever by EnqueueIfIdle.
func (s *Store) RecoverStaleRunning() (int, error) {
nowStr := now()
res, err := s.DB.Exec(`
UPDATE jobs SET status='queued', device_serial='', started_at='',
last_error='recovered after agent restart', not_before=?
WHERE status='running'`, nowStr)
if err != nil {
return 0, err
}
n, _ := res.RowsAffected()
return int(n), nil
}
// NextDelayed returns the soonest queued job that is still waiting on not_before.
func (s *Store) NextDelayed() (Job, time.Duration, bool, error) {
nowStr := now()
row := s.DB.QueryRow(`
SELECT id,stream_id,stream_name,app,channel,reason,status,priority,attempts,max_attempts,
device_serial,last_error,not_before,created_at,started_at,finished_at
FROM jobs
WHERE status='queued' AND not_before!='' AND not_before>?
ORDER BY not_before ASC LIMIT 1`, nowStr)
j, err := scanJob(row)
if err == sql.ErrNoRows {
return Job{}, 0, false, nil
}
if err != nil {
return Job{}, 0, false, err
}
until := time.Until(j.NotBefore)
if until < 0 {
until = 0
}
return j, until, true, nil
}
// ClearBackoff makes a queued job runnable immediately (e.g. after fixing auth).
func (s *Store) ClearBackoff(id int64) error {
_, err := s.DB.Exec(`UPDATE jobs SET not_before=? WHERE id=? AND status='queued'`, now(), id)
return err
}
// ActiveJobForStream returns the queued/running job for a stream, if any.
func (s *Store) ActiveJobForStream(streamID int64) (Job, bool, error) {
row := s.DB.QueryRow(`
SELECT id,stream_id,stream_name,app,channel,reason,status,priority,attempts,max_attempts,
device_serial,last_error,not_before,created_at,started_at,finished_at
FROM jobs WHERE stream_id=? AND status IN ('queued','running')
ORDER BY id DESC LIMIT 1`, streamID)
j, err := scanJob(row)
if err == sql.ErrNoRows {
return Job{}, false, nil
}
if err != nil {
return Job{}, false, err
}
return j, true, nil
}