OXYGENHUB
◆ carried-not-removed oxygen / docs / strata.md
8.0 KiB · Markdown · last changed A repository kept on a store: oxy init --store 1 month 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
145
## What a store holds
146
147
```
148
PREFIX/
149
  oxygen.toml                     format version and repository id, written once
150
  bundles/00000001-<hash>.bundle  one per push: changes, signatures, seals, objects
151
  bundles/00000002-<hash>.bundle
152
  refs                            bookmarks with heads and attestations, tags, policy
153
  gc                              bundles a gc superseded, with when
154
```
155
156
Each client keeps a mirror of the store under `.oxy/remotes/NAME/strata/`,
157
brought level before every use, and runs the same checks on a push that a
158
hub runs. The mirror is a cache: delete it and the next use rebuilds it.
159
160
## Troubleshooting
161
162
Every error says what to do next. The common ones:
163
164
- **access denied** — the identity cannot do this on the bucket: check
165
  its policy covers `ListBucket` on the bucket and `GetObject`,
166
  `PutObject` and `DeleteObject` under the prefix (or the equivalents on
167
  GCS and Azure).
168
- **no usable credentials** — nothing in the chain above produced any:
169
  set a profile, sign in with the provider's tool, or set the variables.
170
- **profile "x" is not in ~/.aws/config or ~/.aws/credentials** — the
171
  name on the URL is wrong, or the files live elsewhere
172
  (`AWS_CONFIG_FILE`, `AWS_SHARED_CREDENTIALS_FILE`).
173
- **no region was given and S3 did not say** — add `--region`, set
174
  `AWS_REGION`, or put `region` in the profile.
175
- **holds a different repository** — the prefix is taken: use another.
176
- **a push may be in progress, so try again** — a bundle another push
177
  uploaded is not yet named in the record; a moment later it is. If it
178
  never is, the store is missing changes: `oxy remote check` says what
179
  it holds.
180
- **the remote kept moving while pushing** — several pushes landed while
181
  yours was retried; `oxy pull`, then push again.
182
183
`oxy remote check NAME` is the first thing to run when something is
184
wrong: it separates reachability, permissions and behaviour.