{% extends "base.html" %} {% block title %}Runners · Administration{% endblock %} {% block mainclass %} class="wide"{% endblock %} {% block content %}

Runners {{ runners.len()|plural("runner", "runners") }} · Administration

runnersimagessettings
{% if let Some(n) = notice %}

{{ n }}

{% endif %} {% if let Some(e) = error %}

{{ e }}

{% endif %} {% if let Some(t) = new_token %}

Registration token

Use it once, within a day, on the machine that will run jobs. It is shown only now.

flow runner register {{ hub }} --token {{ t }} --name NAME [--ca hub-ca.pem] [--label LABEL]
flow runner

The runner sends its signing identity when it registers; this hub accepts receipts from it signed by that key only.

{% endif %}
{% if runners.is_empty() %}

No runners yet. Create a registration token below and run flow runner register on the runner.

{% else %}
{% for row in runners %}{% let r = row.r %} {% endfor %}
RunnerConnectivityStatusServesSigning keyMachineLabelsCapacityLast seen
{{ r.name }}{% if r.source == "config" %} hub.toml{% endif %} {{ r.connectivity() }} {{ row.doing() }} {{ row.scope }}
{% if r.key.is_empty() %}not yet{% else %}{{ r.key|short }}{% endif %} {% if r.arch.is_empty() %}unknown{% else %}{{ r.arch }} · {{ r.os }} · flow {{ r.version }}{% endif %}{% let f = r.facts_line() %}{% if !f.is_empty() %}
{{ f }}{% endif %}{% if !row.swarm.is_empty() %}
swarm {{ row.swarm }}{% endif %}
{% for l in r.label_list() %}{{ l }} {% endfor %} {{ r.max_jobs|plural("job", "jobs") }}{% if let Some(m) = r.memory_mib %} · {{ m }} MiB{% endif %}{% if let Some(c) = r.cpus %} · {{ c }} vCPU{% endif %} {% if let Some(t) = r.last_seen_ms %}{% else %}never{% endif %}
{% if r.state == "active" %} {% else %}{% endif %}
{% endif %}

Queue {% if filter.any() %}{{ queue.len() }} of {{ queue_total }} · all{% else %}{{ queue_total|plural("job", "jobs") }}{% endif %}

Charts ↗
{% if queue.is_empty() %}

{% if filter.any() %}Nothing matches.{% else %}Nothing running or waiting.{% endif %}

{% else %}
{% for q in queue %}{% endfor %}
RepositoryRunJobStatusPriorityStartedRunner
{{ q.owner }}/{{ q.name }}#{{ q.run_number }}{{ q.job }}{{ q.status }}{% if q.priority() == "normal" %}normal{% else %}{{ q.priority() }}{% endif %}{% if let Some(t) = q.started_ms %}{% endif %}{% if q.runner.is_empty() %}waiting{% let needs = q.needs() %}{% if !needs.is_empty() %} for a runner with {% for l in needs %}{{ l }} {% endfor %}{% endif %}{% if let Some(a) = q.arch() %} on {{ a }}{% endif %}{% else %}{{ q.runner }}{% endif %}

Queued jobs are in the order runners claim them: repositories take turns, a repository with jobs running waiting behind those without; a job waits for its dependencies and for a runner whose scope, tier and room fit it.

{% endif %}

Add a runner

A runner registers with a one-time token: it sends its signing identity, architecture and labels, and gets its own credential, kept in runner.toml beside its flow.toml.

The runner serves the whole site, one owner's repositories, or one repository; it never takes another scope's jobs.

{% if !pending.is_empty() %}

Unused tokens: {% for (by, made, expires) in pending %}by {{ by }} {{ made|ago_i }}, expires {{ expires|when_i }}{% if !loop.last %}; {% endif %}{% endfor %}

{% endif %}
{% endblock %}