<p>A runner asks the hub which build it should run, fetches it, checks it as a release, finishes what it holds and swaps itself in; a build that does not reach the hub goes back to the one before. <strong>Off unless chosen.</strong> The site, an owner, a test group and a runner each may set any of the settings below, and the nearest setting wins: a runner's own, then its test groups', then its owner's, then the site's. A runner's <code>flow.toml</code> can still refuse every update (<code>[runner.update] allow = false</code>).</p>
18
<pclass="small mute">A rollout: turn updates on for a test group (two chosen runners, or 10% of them), watch it, then widen it to 50%, then turn updates on for everyone. A percentage is taken by a stable hash of each runner's id, so it is the same machines every time, and 10% sits inside 50%.</p>
<pclass="small mute">Each release offers one file per machine, <code>flow-linux-x86_64</code>, <code>flow-linux-aarch64</code>, <code>flow-linux-riscv64</code>, <code>flow-windows-x86_64.exe</code>. <em>stable</em> is its newest release, <em>canary</em> its newest including pre-releases; a version (<code>0.3.4</code>) pins it, going back if need be.</p>
<pclass="small mute">A named set of runners: those you choose, or a percentage of the site's runners (or one owner's). Give a group its own settings to try a build on it before the rest.</p>
64
{% if groups.is_empty() %}<pclass="mute small">No test groups yet.</p>{% endif %}
65
{% for gv in groups %}{% let g = gv.g %}
66
<divclass="card wide update-group">
67
<h3>{{ g.name }} <smallclass="mute">{{ gv.reach }} · {% if let Some(p) = g.percent %}{{ p }}%{% else %}chosen{% endif %} · {{ gv.members.len()|plural("runner", "runners") }} now</small></h3>
68
<pclass="small">{% if gv.members.is_empty() %}<spanclass="mute">No runners in it.</span>{% else %}{% for m in gv.members %}<spanclass="lbl">{{ m }}</span> {% endfor %}{% endif %}</p>
<labelclass="pol"><inputtype="radio"name="mode"value="percent"{% ifg.percent.is_some() %} checked{% endif %}> a percentage of {{ gv.reach }}: <inputtype="number"name="percent"min="1"max="100"size="4"value="{% if let Some(p) = g.percent %}{{ p }}{% endif %}">%</label>
77
<labelclass="pol"><inputtype="radio"name="mode"value="chosen"{% ifg.percent.is_none() %} checked{% endif %}> these runners:</label>
78
<divclass="pol-row">{% for rv in runners %}{% if g.owner.is_empty() || g.owner == rv.r.scope_owner %}<labelclass="pol"><inputtype="checkbox"name="runner"value="{{ rv.r.id }}"{% ifgv.chosen.contains(rv.r.id) %} checked{% endif %}> {{ rv.r.name }}</label>{% endif %}{% endfor %}</div>
79
<buttontype="submit">Save members</button>
80
</form>
81
<formclass="tiny"method="post"action="/admin/runners/updates/groups/{{ g.id }}/delete"data-confirm="Remove the test group {{ g.name }}? Its runners follow the levels above it."><inputtype="hidden"name="_csrf"value="{{ shell.csrf }}"><buttonclass="danger-btn">remove group</button></form>
<labelclass="pol">Of <inputtype="text"name="owner"size="12"placeholder="the site's runners"title="Blank for the site's runners, or an owner for theirs"></label>
91
</div>
92
<labelclass="pol"><inputtype="radio"name="mode"value="percent"> a percentage: <inputtype="number"name="percent"min="1"max="100"size="4">%</label>
93
<labelclass="pol"><inputtype="radio"name="mode"value="chosen"checked> these runners:</label>
94
<divclass="pol-row">{% for rv in runners %}<labelclass="pol"><inputtype="checkbox"name="runner"value="{{ rv.r.id }}"> {{ rv.r.name }}</label>{% endfor %}</div>
95
<buttontype="submit">Make group</button>
96
</form>
97
</section>
98
<sectionclass="panel"id="runners">
99
<h2>Runners</h2>
100
{% if runners.is_empty() %}<pclass="mute">No runners yet.</p>{% else %}
101
<divclass="table"><table>
102
<tr><th>Runner</th><th>Runs</th><th>Groups</th><th>Its policy</th><th>Now</th><th>Last said</th><th>Its own settings</th></tr>