OXYGENHUB
# 2bbdc0268c23 oxygenhub / templates / backups.html
6.2 KiB · HTML · last changed Backups to S3, keylessly 22 days ago source blame history raw
d7d18246 Andy McCall ↶ 1
{% extends "base.html" %}
2
{% import "settings_tabs.html" as t %}
3
{% block title %}Backups{% if kind == "org" %} · {{ owner }}{% endif %}{% endblock %}
4
{% block tabs %}{% if kind == "user" %}{% call t::tabs("backups") %}{% endif %}{% endblock %}
5
{% block content %}
6
<div class="head"><h1>Backups <small>{% if kind == "org" %}<a href="/orgs/{{ owner }}/settings">{{ owner }}</a>'s repositories{% else %}your repositories{% endif %}, copied to a place you control</small></h1></div>
7
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
8
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
9
<p class="small mute backup-what">Each run copies every repository {% if kind == "org" %}the organisation{% else %}you{% endif %} own, with its wiki, byte for byte (signatures and all), its issues, proposals, releases and settings (never a secret value){% if kind == "org" %}, and the organisation's members and teams{% endif %}. Only what changed since the last run travels, and any kept run can be restored with <code>oxygenhub admin restore</code>.</p>
10
11
<form class="card wide backup-form" method="post" action="{{ base }}">
12
  <input type="hidden" name="_csrf" value="{{ shell.csrf }}">
13
  <h2>Where and how often</h2>
14
  <fieldset class="choices">
15
    <legend class="small mute">How often</legend>
16
    {% for (v, label) in every() %}<label class="choice"><input type="radio" name="every" value="{{ v }}"{% if setting.every == *v %} checked{% endif %}> {{ label }}</label>{% endfor %}
17
  </fieldset>
18
  <fieldset class="choices">
19
    <legend class="small mute">Where</legend>
20
    {% if !dirs.is_empty() %}<label class="choice"><input type="radio" name="destination" value="dir"{% if setting.destination == "dir" %} checked{% endif %}> A directory on this hub's host</label>{% endif %}
7b9f4570 Andy McCall ↶ 21
    <label class="choice"><input type="radio" name="destination" value="gcs"{% if setting.destination == "gcs" || (dirs.is_empty() && setting.destination != "s3") %} checked{% endif %}> A Google Cloud Storage bucket, with no key</label>
22
    <label class="choice"><input type="radio" name="destination" value="s3"{% if setting.destination == "s3" %} checked{% endif %}> An Amazon S3 bucket, with no key</label>
d7d18246 Andy McCall ↶ 23
  </fieldset>
24
  {% if !dirs.is_empty() %}
25
  <label for="dir">Directory</label>
26
  <select id="dir" name="dir">{% for d in dirs %}<option value="{{ d }}"{% if setting.destination == "dir" && setting.target == *d %} selected{% endif %}>{{ d }}/{{ owner }}</option>{% endfor %}</select>
27
  {% endif %}
28
  <label for="bucket">GCS bucket</label>
29
  <input type="text" id="bucket" name="bucket" placeholder="{{ owner }}-oxygen-backup" value="{% if setting.destination == "gcs" %}{{ setting.target }}{% endif %}">
30
  <label for="provider">Workload identity provider</label>
7b9f4570 Andy McCall ↶ 31
  <input type="text" id="provider" name="provider" placeholder="//iam.googleapis.com/projects/NUMBER/locations/global/workloadIdentityPools/oxygenhub/providers/oxygenhub-io" value="{% if setting.destination == "gcs" %}{{ setting.provider }}{% endif %}">
d7d18246 Andy McCall ↶ 32
  <p class="small mute">The hub signs in as <code>backup:{{ owner }}</code> with a token it issues itself; Google exchanges it for a few minutes' access to this bucket. No key file exists. One-time setup: <a href="/help#backups">Help › Backups</a> (docs/backups.md §6).</p>
7b9f4570 Andy McCall ↶ 33
  <label for="s3_bucket">S3 bucket</label>
34
  <input type="text" id="s3_bucket" name="s3_bucket" placeholder="{{ owner }}-oxygen-backup" value="{% if setting.destination == "s3" %}{{ setting.target }}{% endif %}">
35
  <label for="s3_region">S3 region</label>
36
  <input type="text" id="s3_region" name="s3_region" placeholder="eu-west-2" value="{{ setting.region }}">
37
  <label for="s3_role">IAM role</label>
38
  <input type="text" id="s3_role" name="s3_role" placeholder="arn:aws:iam::ACCOUNT:role/oxygenhub-backup-{{ owner }}" value="{% if setting.destination == "s3" %}{{ setting.provider }}{% endif %}">
39
  <p class="small mute">The hub signs in to AWS as <code>backup:{{ owner }}</code> with a token it issues itself (audience <code>sts.amazonaws.com</code>); AWS gives it an hour's credentials of this role. No access key exists. One-time setup: docs/backups.md §6.</p>
d7d18246 Andy McCall ↶ 40
  <label for="prefix">Prefix (optional)</label>
41
  <input type="text" id="prefix" name="prefix" value="{{ setting.prefix }}" placeholder="oxygenhub">
42
  <label for="keep_days">Keep runs for</label>
43
  <select id="keep_days" name="keep_days">{% for k in keeps() %}<option value="{{ k }}"{% if setting.keep_days == *k %} selected{% endif %}>{{ k }} days</option>{% endfor %}</select>
44
  <button type="submit">SAVE</button>
45
</form>
46
47
<section class="card-box" id="runs">
48
  <div class="card-head"><h2>Runs</h2><span class="count-chip">{{ runs.len() }}</span>
35fea423 Andy McCall ↶ 49
    <form class="inline more" method="post" action="{{ base }}/test"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" class="small"{% if setting.updated_ms == 0 %} disabled title="Save where backups go first"{% endif %} title="Write, read and remove a probe at the destination">TEST</button></form>
50
    <form class="inline" method="post" action="{{ base }}/run"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><button type="submit" class="small primary-btn"{% if setting.updated_ms == 0 %} disabled title="Save where backups go first"{% endif %}>Back up now</button></form>
d7d18246 Andy McCall ↶ 51
  </div>
52
  {% if runs.is_empty() %}<p class="empty small">No runs yet.{% if setting.every == "off" %} Pick how often and where, then save.{% endif %}</p>
53
  {% else %}
54
  <div class="table"><table class="backup-runs">
55
    <tr><th>Started</th><th>Took</th><th>Outcome</th><th>Repositories</th><th>Files sent</th><th>Size sent</th><th></th></tr>
56
    {% for r in runs %}
57
    <tr>
58
      <td><time title="{{ r.started_ms|when_i }}">{{ r.started_ms|ago_i }}</time>{% if r.trigger == "now" %} <span class="mute small">by hand</span>{% endif %}</td>
59
      <td>{% if let Some(f) = r.finished_ms %}{{ ((f - r.started_ms) / 1000) }} s{% else %}<span class="mute">running</span>{% endif %}</td>
60
      <td><span class="backup-status st-{{ r.status }}">{{ r.status }}</span></td>
61
      <td>{{ r.repos }}</td>
62
      <td>{{ r.sent }} <span class="mute small">of {{ r.files }}</span></td>
63
      <td>{{ (r.bytes / 1024) }} KiB</td>
64
      <td class="small mute">{{ r.detail }}</td>
65
    </tr>
66
    {% endfor %}
67
  </table></div>
68
  {% endif %}
69
</section>
70
{% endblock %}