OXYGENHUB
# 8b3c9b2ba502 oxygen / docs / strata.md
9.3 KiB · Markdown · last changed Spec §13.7: signatures and index entries in a journal and entry lists, read once per open 16 days ago renderedsource blame history raw
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
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.
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`)
147
  and lists of them (`pack-<id>.entries`) that are read once per open
148
  (spec §13.7). After every client that writes to the bucket runs this
149
  version of oxy, run `oxy gc` once. From then on, looking up a
150
  signature or a seal sends no LIST request. A client still on oxy
151
  0.4.6 or older writes signatures that the others miss until the next
152
  `oxy gc`, so upgrade every client first.
153
154
A repository kept on a store holds:
155
156
```
157
PREFIX/
158
  pack-<id>.pack, .idx, .dict   packs, their indexes and dictionaries
159
  pack-retired                  what repacks and folds replaced, deleted an hour later
160
  pack-entries                  journal: signatures and index entries since the last fold
161
  pack-<id>.entries             every signature and index entry, as the last gc found them
162
  pack-<id>.entries-part        journal lines folded out once there were more than 1000
163
  pack-legacy-folded            the older one-list keys have been folded
164
  loose/  nocompress/           objects not yet packed; chunks kept raw
165
  sig/  seal/  receipt/  approval/  cert/  accepted/
166
                                one key per signature or index entry
167
  ops/head                      the operation head
168
```
169
170
## What a store holds
171
172
```
173
PREFIX/
174
  oxygen.toml                     format version and repository id, written once
175
  bundles/00000001-<hash>.bundle  one per push: changes, signatures, seals, objects
176
  bundles/00000002-<hash>.bundle
177
  refs                            bookmarks with heads and attestations, tags, policy
178
  gc                              bundles a gc superseded, with when
179
```
180
181
Each client keeps a mirror of the store under `.oxy/remotes/NAME/strata/`,
182
brought level before every use, and runs the same checks on a push that a
183
hub runs. The mirror is a cache: delete it and the next use rebuilds it.
184
185
## Troubleshooting
186
187
Every error says what to do next. The common ones:
188
189
- **access denied** — the identity cannot do this on the bucket: check
190
  its policy covers `ListBucket` on the bucket and `GetObject`,
191
  `PutObject` and `DeleteObject` under the prefix (or the equivalents on
192
  GCS and Azure).
193
- **no usable credentials** — nothing in the chain above produced any:
194
  set a profile, sign in with the provider's tool, or set the variables.
195
- **profile "x" is not in ~/.aws/config or ~/.aws/credentials** — the
196
  name on the URL is wrong, or the files live elsewhere
197
  (`AWS_CONFIG_FILE`, `AWS_SHARED_CREDENTIALS_FILE`).
198
- **no region was given and S3 did not say** — add `--region`, set
199
  `AWS_REGION`, or put `region` in the profile.
200
- **holds a different repository** — the prefix is taken: use another.
201
- **a push may be in progress, so try again** — a bundle another push
202
  uploaded is not yet named in the record; a moment later it is. If it
203
  never is, the store is missing changes: `oxy remote check` says what
204
  it holds.
205
- **the remote kept moving while pushing** — several pushes landed while
206
  yours was retried; `oxy pull`, then push again.
207
208
`oxy remote check NAME` is the first thing to run when something is
209
wrong: it separates reachability, permissions and behaviour.