|
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 |
+ |
## What a store holds |
|
124 |
+ |
|
|
125 |
+ |
``` |
|
126 |
+ |
PREFIX/ |
|
127 |
+ |
oxygen.toml format version and repository id, written once |
|
128 |
+ |
bundles/00000001-<hash>.bundle one per push: changes, signatures, seals, objects |
|
129 |
+ |
bundles/00000002-<hash>.bundle |
|
130 |
+ |
refs bookmarks with heads and attestations, tags, policy |
|
131 |
+ |
gc bundles a gc superseded, with when |
|
132 |
+ |
``` |
|
133 |
+ |
|
|
134 |
+ |
Each client keeps a mirror of the store under `.oxy/remotes/NAME/strata/`, |
|
135 |
+ |
brought level before every use, and runs the same checks on a push that a |
|
136 |
+ |
hub runs. The mirror is a cache: delete it and the next use rebuilds it. |
|
137 |
+ |
|
|
138 |
+ |
## Troubleshooting |
|
139 |
+ |
|
|
140 |
+ |
Every error says what to do next. The common ones: |
|
141 |
+ |
|
|
142 |
+ |
- **access denied** — the identity cannot do this on the bucket: check |
|
143 |
+ |
its policy covers `ListBucket` on the bucket and `GetObject`, |
|
144 |
+ |
`PutObject` and `DeleteObject` under the prefix (or the equivalents on |
|
145 |
+ |
GCS and Azure). |
|
146 |
+ |
- **no usable credentials** — nothing in the chain above produced any: |
|
147 |
+ |
set a profile, sign in with the provider's tool, or set the variables. |
|
148 |
+ |
- **profile "x" is not in ~/.aws/config or ~/.aws/credentials** — the |
|
149 |
+ |
name on the URL is wrong, or the files live elsewhere |
|
150 |
+ |
(`AWS_CONFIG_FILE`, `AWS_SHARED_CREDENTIALS_FILE`). |
|
151 |
+ |
- **no region was given and S3 did not say** — add `--region`, set |
|
152 |
+ |
`AWS_REGION`, or put `region` in the profile. |
|
153 |
+ |
- **holds a different repository** — the prefix is taken: use another. |
|
154 |
+ |
- **a push may be in progress, so try again** — a bundle another push |
|
155 |
+ |
uploaded is not yet named in the record; a moment later it is. If it |
|
156 |
+ |
never is, the store is missing changes: `oxy remote check` says what |
|
157 |
+ |
it holds. |
|
158 |
+ |
- **the remote kept moving while pushing** — several pushes landed while |
|
159 |
+ |
yours was retried; `oxy pull`, then push again. |
|
160 |
+ |
|
|
161 |
+ |
`oxy remote check NAME` is the first thing to run when something is |
|
162 |
+ |
wrong: it separates reachability, permissions and behaviour. |