OXYGENHUB
◆ v0.5.0 oxygen / docs / strata.md
9.4 KiB · Markdown · last changed Entries stay fresh in a long-lived handle: re-read after the pack listing's 30 s 16 days ago renderedsource blame history raw
8e119486 Andy McCall ↶ 1
# Oxygen Strata: keeping a repository on your own storage
2
3
A repository can be pushed to, cloned from and pulled from a bucket in
4
your own cloud account, or a directory on a shared drive, with no server
5
in between. The bucket is just another remote:
6
7
```
8
oxy remote add cloud s3://my-bucket/repos/project --profile work
9
oxy remote check cloud
10
oxy push cloud
11
oxy clone s3://my-bucket/repos/project?profile=work
12
oxy pull
13
```
14
15
It works because Oxygen's objects never change and are named by their
16
hash, so any store that can keep write-once objects and update one small
17
record by compare-and-swap can hold a complete repository. Nothing in the
18
store is trusted: every object is re-hashed as it is read, changes are
19
signed, and a bookmark's attestation is verified under your policy, as
20
for any remote. The data stays in your account, in your region, under
21
your keys; an operator of a hub need never see it.
22
23
Supported stores:
24
25
| URL | Store |
26
|---|---|
27
| `s3://BUCKET/PREFIX` | Amazon S3, and S3-compatible services with `--endpoint` |
28
| `gs://BUCKET/PREFIX` | Google Cloud Storage |
29
| `az://ACCOUNT/CONTAINER/PREFIX` | Azure Blob Storage |
30
| `dir://PATH` | a directory: a USB stick, a network share, or for trying things out |
31
32
A store holds one repository per prefix. The first push binds the prefix
33
to the repository; a push of another repository to the same prefix is
34
refused. Use a prefix per repository, as `repos/project` above.
35
36
## Setting up a bucket
37
38
### Amazon S3
39
40
1. Create a bucket in the region you want the data kept in (for example
41
   `eu-west-2`, London), with public access blocked.
42
2. Give the identity oxy will use these permissions, and no more:
43
44
   ```json
45
   {
46
     "Version": "2012-10-17",
47
     "Statement": [
48
       { "Effect": "Allow", "Action": "s3:ListBucket",
49
         "Resource": "arn:aws:s3:::my-bucket" },
50
       { "Effect": "Allow",
51
         "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"],
52
         "Resource": "arn:aws:s3:::my-bucket/repos/*" }
53
     ]
54
   }
55
   ```
56
57
   A read-only identity (`s3:ListBucket` and `s3:GetObject`) gives a
58
   working clone and pull.
59
3. Credentials come from the places the AWS tools use, in this order:
60
   `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` in the environment; the
61
   profile named by `--profile` (or `AWS_PROFILE`, else `default`) in
62
   `~/.aws/credentials`; what the AWS CLI can produce for that profile
63
   (`aws sso login`, `aws login`, assumed roles, credential processes), so
64
   anything that works for `aws` works for `oxy`; and the instance's or
65
   container's role.
66
4. The region is taken from `--region`, `AWS_REGION`, the profile, or
67
   asked of S3. `--endpoint http://…` points at an S3-compatible service.
68
69
### Google Cloud Storage
70
71
1. Create a bucket in the region you want (for example `europe-west2`,
72
   London), with uniform access and public access prevention.
73
2. Create a service account and grant it **Storage Object Admin** on that
74
   bucket only (**Storage Object Viewer** for a read-only identity), or
75
   use your own account.
76
3. Credentials: a service account key file named by `--credentials` or
77
   `GOOGLE_APPLICATION_CREDENTIALS`; else the application default
78
   credentials that `gcloud auth application-default login` writes; else
79
   the instance's service account.
80
81
### Azure Blob Storage
82
83
1. Create a storage account in the region you want (for example
84
   `UK South`), with public blob access off, and a container in it.
85
2. Create a service principal (`az ad sp create-for-rbac`) with
86
   **Storage Blob Data Contributor** scoped to that container (**Storage
87
   Blob Data Reader** for read-only), or use your own sign-in.
88
3. Credentials: `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET` and
89
   `AZURE_TENANT_ID` for a service principal; `AZURE_STORAGE_ACCOUNT_KEY`
90
   or `AZURE_STORAGE_SAS_TOKEN`; else what `az login` left behind; else
91
   the machine's managed identity.
92
93
### A directory
94
95
`dir:///media/usb/project` needs nothing. Several people on one machine,
96
or one person from several checkouts, can share it; a reference is
97
updated under a lock file beside it.
98
99
## Using a store remote
100
101
`oxy remote add NAME URL` stores the URL; `--profile`, `--region`,
102
`--endpoint` and `--credentials` are kept on it as query parameters, so a
103
clone can name them too: `oxy clone "gs://bucket/project?credentials=key.json"`.
104
105
`oxy remote check NAME` reaches the store with the credentials in hand
106
and says how long that took, what the prefix holds (this repository,
107
another one, or nothing yet), and then puts the store through the checks
108
a repository depends on: object round trips, create-only writes, listing,
109
deletion and compare-and-swap on a reference. It writes under a probe
110
prefix of its own and leaves nothing behind.
111
112
Then `push`, `pull`, `fetch` and `clone` work as for any remote. Every
113
push uploads one bundle holding what the store lacks, and moves the
114
record of bookmarks and tags from the version it read. Two people
115
pushing at once are safe: whoever lands second is applied over what
116
landed, or told to pull first when the same bookmark moved.
117
118
`oxy gc --remote NAME` folds the bundles into one, so a fresh clone
119
downloads one file. The superseded bundles are removed only seven days
120
later (`--now` removes them at once), so a clone that was listing them at
121
the time still completes.
122
d069701a Andy McCall ↶ 123
## A repository kept on a store
124
125
A store can also hold a repository itself rather than a copy of it.
126
Start one with `--store`:
127
128
```sh
129
oxy init myproject --store s3://my-bucket/repos/myproject?profile=work
130
```
131
132
- **Where things live:** the objects and the operation log are in the
133
  store. `.oxy/cache` keeps the objects this machine has used, up to
134
  `storage.cache` (2 GiB unless you set it, for example
135
  `oxy config storage.cache 10G`).
136
- **The cache:** you can delete `.oxy/cache` at any time. Objects come
137
  back from the store as they are needed.
138
- **One repository per prefix:** `oxy init --store` refuses a prefix
139
  that already holds a repository's operation log.
140
- **Speed:** each command that records something makes a few requests to
141
  the store, so a commit takes about half a second to a second.
142
- **Choose at init:** set `storage.url` only when the repository is
143
  created. Changing it later does not move the objects.
8b3c9b2b Andy McCall ↶ 144
- **Signatures and seals:** each one is stored under a key of its own,
145
  as in any repository. A bucket can only find those keys with a LIST
146
  request, so the store also keeps a journal of them (`pack-entries`)
771661aa Andy McCall ↶ 147
  and lists of them (`pack-<id>.entries`). These are read when the
148
  repository is opened and again every 30 seconds (spec §13.7). After
149
  every client that writes to the bucket runs this version of oxy, run
150
  `oxy gc` once. From then on, looking up a signature or a seal sends no
151
  LIST request, and another client's new signature is seen within 30
152
  seconds. A client still on oxy 0.4.6 or older writes signatures that
153
  the others miss until the next `oxy gc`, so upgrade every client
154
  first.
d069701a Andy McCall ↶ 155
8b3c9b2b Andy McCall ↶ 156
A repository kept on a store holds:
157
158
```
159
PREFIX/
160
  pack-<id>.pack, .idx, .dict   packs, their indexes and dictionaries
161
  pack-retired                  what repacks and folds replaced, deleted an hour later
162
  pack-entries                  journal: signatures and index entries since the last fold
163
  pack-<id>.entries             every signature and index entry, as the last gc found them
164
  pack-<id>.entries-part        journal lines folded out once there were more than 1000
165
  pack-legacy-folded            the older one-list keys have been folded
166
  loose/  nocompress/           objects not yet packed; chunks kept raw
167
  sig/  seal/  receipt/  approval/  cert/  accepted/
168
                                one key per signature or index entry
169
  ops/head                      the operation head
170
```
171
8e119486 Andy McCall ↶ 172
## What a store holds
173
174
```
175
PREFIX/
176
  oxygen.toml                     format version and repository id, written once
177
  bundles/00000001-<hash>.bundle  one per push: changes, signatures, seals, objects
178
  bundles/00000002-<hash>.bundle
179
  refs                            bookmarks with heads and attestations, tags, policy
180
  gc                              bundles a gc superseded, with when
181
```
182
183
Each client keeps a mirror of the store under `.oxy/remotes/NAME/strata/`,
184
brought level before every use, and runs the same checks on a push that a
185
hub runs. The mirror is a cache: delete it and the next use rebuilds it.
186
187
## Troubleshooting
188
189
Every error says what to do next. The common ones:
190
191
- **access denied** — the identity cannot do this on the bucket: check
192
  its policy covers `ListBucket` on the bucket and `GetObject`,
193
  `PutObject` and `DeleteObject` under the prefix (or the equivalents on
194
  GCS and Azure).
195
- **no usable credentials** — nothing in the chain above produced any:
196
  set a profile, sign in with the provider's tool, or set the variables.
197
- **profile "x" is not in ~/.aws/config or ~/.aws/credentials** — the
198
  name on the URL is wrong, or the files live elsewhere
199
  (`AWS_CONFIG_FILE`, `AWS_SHARED_CREDENTIALS_FILE`).
200
- **no region was given and S3 did not say** — add `--region`, set
201
  `AWS_REGION`, or put `region` in the profile.
202
- **holds a different repository** — the prefix is taken: use another.
203
- **a push may be in progress, so try again** — a bundle another push
204
  uploaded is not yet named in the record; a moment later it is. If it
205
  never is, the store is missing changes: `oxy remote check` says what
206
  it holds.
207
- **the remote kept moving while pushing** — several pushes landed while
208
  yours was retried; `oxy pull`, then push again.
209
210
`oxy remote check NAME` is the first thing to run when something is
211
wrong: it separates reachability, permissions and behaviour.