SavewebHQ is a single-site PostgreSQL job coordinator for Saveweb archive workers. HQ owns projects, job attempts, leases, outcomes, and bounded artifact receipts. It does not receive artifact contents, operate volunteer queue shards, or track delivery to downstream sinks.
source -> HQ/PostgreSQL -> worker -> external Artifact Receiver -> sink adapters
^ |
+---- artifact receipt ---+
- HQ decides whether a job is
todo,wip,done,failed, orreset_exhausted. - The external Artifact Receiver validates and durably accepts artifacts, then issues signed artifact receipts.
- A receipt proves that the Artifact Receiver accepted responsibility for an artifact. It does not prove delivery to Internet Archive or another final sink.
go2internetarchiveremains an IA client library used outside HQ, not an HQ component or artifact receiver.
The production image contains only:
tracker: migrations, GitHub-authenticated web administration, machine API, and source import;hqctl: remote administration CLI with bounded batch and packed-source enqueue commands;source: tools for producingjobs-jsonl-zstd-v1source files.
PostgreSQL is the only HQ state store. There is no shard daemon, queue relay, R2 credential, checkpoint protocol, owner generation, or route assignment in the production service.
Prerequisites are Go 1.25 or newer, uv 0.9 or newer, and Docker for the
PostgreSQL integration test.
make test
make check
make test-postgresThe included Compose file starts PostgreSQL and HQ. PostgreSQL data is stored
under ./data/postgres. Before the first start, create a GitHub OAuth App with
this callback URL:
https://hq.saveweb.org/auth/github/callback
Set its client ID in .env, and put its client secret in the ignored secrets
directory. Replace the public URL and team when deploying under another host
or organization.
cp .env.example .env
install -d -m 0700 secrets
printf '%s\n' 'GITHUB_OAUTH_CLIENT_SECRET' > secrets/github-client.secret
chmod 0600 secrets/github-client.secret
docker compose build
docker compose up -d
docker compose psHQ runs migrations before serving and listens on 127.0.0.74:8080 for a host
HTTPS reverse proxy. PostgreSQL is not published to the host.
Create a private machine-token file, then create a worker and a project:
tracker bootstrap-user \
--database-url "$HQ_DATABASE_URL" \
--user-id worker-1 \
--roles worker \
--machine-token-file ./worker.token
tracker put-project \
--database-url "$HQ_DATABASE_URL" \
--project-id sinavideo \
--identity-mode external_id \
--dispatch-qps 250 \
--worker-claim-qps 0.2 \
--max-jobs-per-claim 64 \
--recommended-lease-seconds 300 \
--client-versions sinavideo/2.4.0Pack URLs and import them directly into the project queue:
source pack --input urls.txt --output sinavideo.jobs.jsonl.zst
tracker enqueue-source \
--database-url "$HQ_DATABASE_URL" \
--project-id sinavideo \
--input sinavideo.jobs.jsonl.zstEvery job receives a compact internal PostgreSQL bigint ID used by workers.
Project identity mode controls enqueue deduplication:
noneaccepts repeated values and requires no externalid;external_idrequiresidand makes it unique within the project;unique_valuerequires noidand makesvalueunique within the project.
An identity mode is fixed when the project is created. Reimporting an identical
job into external_id or unique_value is idempotent; reusing the identity with
different immutable job data returns identity_conflict.
Each project also has a live claim_order setting. fifo is the default and
orders jobs by creation time; random uses a stored signed 32-bit random key.
HQ generates the key during enqueue unless the administrator supplies
random_key. Equal keys are resolved by internal job ID, so custom keys do not
need to be unique. Switching the setting affects later claims and leaves WIP
attempts unchanged. For deduplicated jobs, a retry never replaces the stored
key.
Projects may also set a tracker-enforced dispatch_qps, an SDK-enforced
per-worker worker_claim_qps, max_jobs_per_claim (1-256), and max_resets
(0-1000, default 3). recommended_lease_seconds (1-3600, default 300) controls
the default lease and Go SDK renewal cadence. Omit either QPS
to leave that limit disabled; this preserves the unlimited fast path. At high
dispatch rates the tracker permits at most 100 ms of accumulated tokens, while
lower rates permit no burst larger than one job.
Active members of the configured GitHub organization team can sign in at /
and manage projects, statuses, and bounded job batches. The callback verifies
team membership on every login; GitHub access tokens are not persisted. Other
GitHub users are registered as pending workers and receive no administration
session. After an administrator activates the account, the worker signs in
again and generates its own machine token at /worker.
An active administrator machine token can perform the same operations through
/api/v1/admin/projects. Project responses include queue counts for all job
states. Browser sessions are not accepted by machine API routes.
Build hqctl, then store an active administrator's machine token in a private
file. HQ_URL defaults to https://hq.saveweb.org, and
HQ_MACHINE_TOKEN_FILE can replace the corresponding flags.
install -d ./bin
go build -o ./bin/hqctl ./cmd/hqctl
chmod 0600 ./admin.tokenBatch enqueue reads one exact value per non-empty line. It fetches the project
first, generates stable IDs for external_id projects, and sends sequential
requests using the selected batch size (default 1000). There is no job-count
cap per request; the server's 8 MiB JSON body limit is the effective boundary.
The final JSON reports submitted and newly inserted totals; progress is written
to stderr every 100 batches by default.
jq -r '.vid' /home/yzqzss/git/sinavideo/records.jsonl |
./bin/hqctl enqueue \
--machine-token-file ./admin.token \
--project-id sina_bilivideo \
--batch-size 5000Use --format jsonl for one complete enqueue job per line. It may include a
signed 32-bit random_key; HQ generates one when omitted. A missing id is filled
for external_id projects; id is rejected locally for unique_value and
none projects.
./bin/hqctl enqueue \
--machine-token-file ./admin.token \
--project-id sina_bilivideo \
--format jsonl \
--input jobs.jsonl \
--batch-size 128For very large imports, pack once and stream the compressed source through one request:
./bin/hqctl enqueue-source \
--machine-token-file ./admin.token \
--project-id sina_bilivideo \
--input sina_bilivideo.jobs.jsonl.zstBatch enqueue is intentionally sequential. If a later batch fails, earlier
batches remain committed and the error reports how many jobs were submitted.
Rerunning is idempotent for external_id and unique_value; a none project
will insert another copy.
The administration API and Web Dashboard also import packed source files, manage user creation, deletion, and machine-token rotation, inspect current job state, requeue terminal failures, and delete non-WIP jobs or projects without WIP work. New machine tokens are displayed only in the rotation response or the Web Dashboard. Active workers can view, generate, rotate, or revoke their own machine token from the OAuth-authenticated worker page.
All requests use Authorization: Bearer <machine-token>. Workers call:
GET /api/v1/projects/{project_id}
GET /api/v1/whoami
POST /api/v1/projects/{project_id}/jobs/claim
POST /api/v1/projects/{project_id}/jobs/complete
POST /api/v1/projects/{project_id}/jobs/fail
POST /api/v1/projects/{project_id}/jobs/extend-lease
Claims and mutations are bounded batches. A claim returns the internal numeric
job_id and a unique attempt_id; late or replayed outcomes cannot finalize a
newer attempt. An optional source id is metadata and is never used to finish
an attempt.
The Go SDK exposes worker.OpenProjectQueue; the Python SDK exposes
open_project_queue. Both refresh project policy, use the project's recommended
lease by default, clamp claim batches, pace each worker's claims, and retry
explicit retryable 429 responses. The Go SDK additionally returns lifecycle
aware job handles, renews all held attempts every quarter lease in batches, and
cancels a job context when its lease is lost. See the
Go SDK guide and
Python SDK guide.
Worker configuration includes a required client version. HQ returns HTTP 426
client_upgrade_required unless that exact version is allowed by the project.
See design.md for state semantics and operations.md for the minimal runbook.