@colony2/gitvend 0.0.0-placeholder.0 → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/c2j-Apache-2.0.txt +201 -0
- package/README.md +386 -0
- package/THIRD_PARTY_NOTICES.md +7 -0
- package/bin/cli.js +41 -0
- package/package.json +46 -8
- package/scripts/postinstall.js +166 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for reasonable and customary use in describing the
|
|
141
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
167
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
168
|
+
or other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright [yyyy] [name of copyright owner]
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# gitvend
|
|
2
|
+
|
|
3
|
+
A Go Git/HTTPS gateway with permissions carried in signed JWTs. Agents use ordinary Git and one gateway credential across their assigned repositories. The gateway holds a shared upstream token and enforces repository, ref-discovery, branch-write, tag and repository-creation permissions before forwarding requests.
|
|
4
|
+
|
|
5
|
+
Implemented for GitHub and GitHub Enterprise-compatible APIs. Client and upstream Git connections use HTTPS; fetch/ref lookup requires protocol v2, while pushes use Git's normal receive-pack format. The service does not require a GitHub App.
|
|
6
|
+
|
|
7
|
+
This guide takes an operator from configuring a gateway to giving an agent a credential, cloning a repository, and pushing an allowed branch.
|
|
8
|
+
|
|
9
|
+
[Install](#install) · [Set up the gateway](#set-up-the-gateway) · [Issue a credential](#issue-an-agent-credential) · [Agent usage](#use-gitvend-as-an-agent) · [Permissions](#permission-strings) · [Operations](#provisioning-and-audit) · [Troubleshooting](#troubleshooting) · [CLI reference](#cli-reference)
|
|
10
|
+
|
|
11
|
+
## How credentials and permissions fit together
|
|
12
|
+
|
|
13
|
+
```text
|
|
14
|
+
Agent running Git -- HTTPS + agent JWT --> gitvend -- HTTPS + upstream token --> GitHub
|
|
15
|
+
^
|
|
16
|
+
trusts the issuer's public key
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Credential | Who holds it | Purpose |
|
|
20
|
+
| --- | --- | --- |
|
|
21
|
+
| Upstream GitHub token | Gateway operator; available to the gateway process | Lets gitvend access repositories and, when enabled, create them. Shared across agents. |
|
|
22
|
+
| Ed25519 private signing key | Trusted orchestrator or operator | Signs each agent's JWT with its identity, expiry and permissions. |
|
|
23
|
+
| Ed25519 public signing key | Gateway | Verifies JWTs without contacting the issuer. |
|
|
24
|
+
| Agent JWT | The assigned agent | Used as its Git HTTPS password. Contains the signed permission strings. |
|
|
25
|
+
|
|
26
|
+
An operation must pass all configured checks: the provider's allowed owners, the signing key's allowed owners, the JWT's grants, gateway-wide deny rules, and the upstream account's permissions. A JWT cannot grant access outside these limits. Give agents their JWT; retain the upstream token on the gateway and the private signing key with the trusted issuer.
|
|
27
|
+
|
|
28
|
+
Read permissions hide ungranted ref names; they do **not** isolate Git objects by branch. An agent with any read grant for a repository may fetch objects by known hash, subject to what the upstream serves. Use separate repositories when you need object confidentiality.
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
Download the archive for your Linux/macOS system and amd64/arm64 architecture from [GitHub Releases](https://github.com/colony-2/gitvend/releases). Releases include SHA-256 checksums. Extract the archive and place its `gitvend` executable on `PATH`; keep the included documentation and example configuration for setup.
|
|
33
|
+
|
|
34
|
+
Once the first release is published, install through Go or npm:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
go install github.com/colony-2/gitvend/cmd/gitvend@latest
|
|
38
|
+
# Or use the npm wrapper, which downloads and verifies the matching binary:
|
|
39
|
+
npm install -g @colony2/gitvend
|
|
40
|
+
gitvend version
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For a Go installation, add `$(go env GOPATH)/bin` to your `PATH` if needed. The npm wrapper requires Node 22+ and `tar`. Install scripts must be enabled so it can download the binary from GitHub Releases. Release builds report their release version; local builds report their Git revision when available.
|
|
44
|
+
|
|
45
|
+
## Build and verify
|
|
46
|
+
|
|
47
|
+
Requires Go 1.25+ and Git for the integration tests. Supported server platforms are Linux and macOS. Run these commands from the source checkout's root:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
make build
|
|
51
|
+
export PATH="$PWD/bin:$PATH"
|
|
52
|
+
gitvend version
|
|
53
|
+
make check
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`make check` runs race tests and vet. `make fuzz` exercises the permission, packet and JSON parsers. The default test suite uses real Git clients and a real `git-http-backend`, with a local GitHub API fixture; it needs neither a GitHub token nor external repository access.
|
|
57
|
+
|
|
58
|
+
## Set up the gateway
|
|
59
|
+
|
|
60
|
+
The walkthrough uses organization `fooorg`, an existing repository `project` with a `main` branch, and gateway address `git-gateway.example:8443`. Replace these with your own values. Commands assume `gitvend` is on `PATH`. Agents need Git with protocol v2 support; the gateway serves HTTPS directly in this example.
|
|
61
|
+
|
|
62
|
+
### 1. Provide the upstream token
|
|
63
|
+
|
|
64
|
+
Have your process supervisor or secret manager set `GITHUB_TOKEN` in the **gateway process** environment. The token's account must have access to the repositories the agents will use. For automatic creation, it must also be able to create private repositories in the organization and access them afterward. Gitvend does not generate or renew this upstream token.
|
|
65
|
+
|
|
66
|
+
GitHub's [token guide](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) describes account and organization restrictions. For private organization repository creation, GitHub documents `repo` scope for classic tokens or repository Administration write permission for fine-grained tokens; creation permissions alone do not establish Git read/write access. Check repository selection, organization approval and workflow-file access for your account. See [GitHub's create-repository requirements](https://docs.github.com/en/rest/repos/repos#create-an-organization-repository).
|
|
67
|
+
|
|
68
|
+
If you prefer a mounted token file, use this credential object inside the provider entry:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"credential": {
|
|
73
|
+
"kind": "token",
|
|
74
|
+
"secret_ref": "file:/run/secrets/github-token"
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The same credential is used for Git and REST API calls. An optional provider-level `api_credential` object with the same shape selects a separate REST token. This release supports token credentials; it does not use SSH keys or discover credentials from `gh auth` or Git credential helpers.
|
|
80
|
+
|
|
81
|
+
### 2. Generate and install the issuer key
|
|
82
|
+
|
|
83
|
+
On the trusted orchestrator or operator machine:
|
|
84
|
+
|
|
85
|
+
```sh
|
|
86
|
+
mkdir -p var
|
|
87
|
+
gitvend keygen -private var/signing.key -public var/signing.pub
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Keep `var/signing.key` on that machine. Copy **only** `signing.pub` to `var/signing.pub` in the gateway's working directory. If the operator and gateway run on one machine, restrict the private key to the issuer's account. Each configured key has an ID and an issuer name; the token-signing command must use those exact values.
|
|
91
|
+
|
|
92
|
+
### 3. Configure and start the server
|
|
93
|
+
|
|
94
|
+
On the gateway, create its writable state directory and copy the [configuration template](examples/gitvend.json) from the source checkout or release archive. If installed through Go or npm, obtain that template separately from this repository:
|
|
95
|
+
|
|
96
|
+
```sh
|
|
97
|
+
mkdir -p var
|
|
98
|
+
cp examples/gitvend.json gitvend.json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Edit `gitvend.json` using this checklist:
|
|
102
|
+
|
|
103
|
+
| Field | Set it to |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| `listen` | `0.0.0.0:8443` to accept connections on the gateway's network interfaces. The template binds to `127.0.0.1:8443` for local access. |
|
|
106
|
+
| `tls_cert`, `tls_key` | Readable certificate/key files for `git-gateway.example`, trusted by the agents. |
|
|
107
|
+
| `state_file` | A persistent, writable database path; default `var/gitvend.db`. |
|
|
108
|
+
| `keys[].public_key_file` | The public key installed in step 2. |
|
|
109
|
+
| `keys[].owners` and `providers[].allowed_owners` | Your lowercase organization names in both places. |
|
|
110
|
+
| `providers[].credential.secret_ref` | `env:GITHUB_TOKEN` or the mounted token file reference. |
|
|
111
|
+
| `providers[].allow_creation` | `true` for the scratch-repository example below; `false` to disable creation. |
|
|
112
|
+
| `denies` | Replace `fooorg` in the example deny rule too. It blocks updates/deletion of `main` and `master`. |
|
|
113
|
+
|
|
114
|
+
Configuration is JSON. Unknown fields and duplicate keys are rejected. Relative file paths are resolved from the server's **working directory**, not the configuration file's directory; use absolute paths when running under a supervisor.
|
|
115
|
+
|
|
116
|
+
```sh
|
|
117
|
+
gitvend serve -config gitvend.json
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The process runs in the foreground and writes operational logs to stderr. From another terminal:
|
|
121
|
+
|
|
122
|
+
```sh
|
|
123
|
+
curl --fail https://git-gateway.example:8443/healthz
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Expected response: `ok`. This is a liveness check; it does not verify a JWT or contact GitHub. The agent's `ls-remote` command below verifies the Git path.
|
|
127
|
+
|
|
128
|
+
The remote URL has this structure:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
https://GATEWAY/PROVIDER_ALIAS/OWNER/REPOSITORY.git
|
|
132
|
+
https://git-gateway.example:8443/github/fooorg/project.git
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The `github` segment is the configured provider alias. Permission strings use the configured upstream authority (`github.com`), not that alias or the gateway hostname. Repository permission patterns omit the `.git` suffix. Add more provider entries for distinct GitHub-compatible endpoints or owner/credential mappings; overlapping host/owner routes are rejected. Other forge APIs require an adapter.
|
|
136
|
+
|
|
137
|
+
### Running behind a TLS terminator
|
|
138
|
+
|
|
139
|
+
Set `allow_http: true` only for the protected HTTP listener behind the terminator, and bind it to the intended internal interface. This option makes the listener serve HTTP even if TLS files are present. Agents still use the public HTTPS address and its exact host/port in their credential helper.
|
|
140
|
+
|
|
141
|
+
Preserve the URL, `Authorization` and `Git-Protocol` headers, allow streaming request/response bodies, and align ingress body, header and timeout limits with gitvend. `allow_http_upstream` is a separate local-test option; upstream GitHub connections remain HTTPS. Neither option disables certificate verification for HTTPS.
|
|
142
|
+
|
|
143
|
+
## Issue an agent credential
|
|
144
|
+
|
|
145
|
+
On the trusted orchestrator, issue a short-lived JWT carrying the task's permissions:
|
|
146
|
+
|
|
147
|
+
```sh
|
|
148
|
+
gitvend sign \
|
|
149
|
+
-key var/signing.key \
|
|
150
|
+
-kid orchestrator-1 \
|
|
151
|
+
-issuer orchestrator \
|
|
152
|
+
-subject agent-47 \
|
|
153
|
+
-audience gitvend \
|
|
154
|
+
-ttl 15m \
|
|
155
|
+
-permission 'github.com/fooorg/project#main:r' \
|
|
156
|
+
-permission 'github.com/fooorg/project#agents/47/*:rw' \
|
|
157
|
+
-permission 'github.com/fooorg/agent-47-*:c' \
|
|
158
|
+
-permission 'github.com/fooorg/agent-47-*#agents/47/*:rw' \
|
|
159
|
+
-output var/agent-47.jwt
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Alternatively, pass `-grant examples/grant.json`. The file contains `v`, `permissions`, and optional `bindings` mapping canonical `host/owner/repository` names to string-valued provider repository IDs. `-output` replaces the token file atomically with permissions `0600`; without it, `sign` writes the token to stdout. `keygen` refuses to overwrite existing key files.
|
|
163
|
+
|
|
164
|
+
JWTs use Ed25519 signatures (`alg=EdDSA`, `typ=gitvend+jwt`). Verification requires the configured issuer, key ID and audience plus subject, token ID, issued-at, not-before, expiry and grant version. Unknown fields, duplicate JSON keys, unsupported algorithms and malformed rules are rejected. The gateway needs no issued-token database and never contacts the issuer on a Git request.
|
|
165
|
+
|
|
166
|
+
The sample grant allows reading `main`, reading/writing `agents/47/*` branches, and creating private scratch repositories named `agent-47-*` with writable branches under the same prefix. It does not allow pushing `main`, deleting branches or creating tags. The sample server deny rule protects `main` and `master` even if a later JWT grants them writes.
|
|
167
|
+
|
|
168
|
+
Deliver `var/agent-47.jwt` to the agent through your orchestrator's secret mechanism, for example as `/run/secrets/gitvend.jwt`. The file must be readable by that agent. Gitvend does not deliver tokens to agents itself.
|
|
169
|
+
|
|
170
|
+
Refresh the injected file before expiry. Re-running the signing command with `-output` atomically replaces its output file; arrange the same replacement at the agent's token path. The credential helper reads the current file for each credential request, so Git remotes need no change. The default maximum lifetime is 15 minutes; a longer `-ttl` also requires a matching server-side `max_token_lifetime_seconds` setting.
|
|
171
|
+
|
|
172
|
+
A task's existing token remains usable until expiry plus configured clock tolerance even if the issuer stops renewing it. There is no immediate per-agent revocation list. Removing a signing key or adding a gateway-wide deny rule affects new requests after reload; an admitted transfer may finish within its deadline.
|
|
173
|
+
|
|
174
|
+
## Use gitvend as an agent
|
|
175
|
+
|
|
176
|
+
### Configure Git authentication
|
|
177
|
+
|
|
178
|
+
Run these commands inside the agent's own OS account or sandbox, with `gitvend` on `PATH`:
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
git config --global protocol.version 2
|
|
182
|
+
git config --global credential.https://git-gateway.example:8443.helper ''
|
|
183
|
+
git config --global --add credential.https://git-gateway.example:8443.helper \
|
|
184
|
+
'!gitvend credential -host git-gateway.example:8443 -token-file /run/secrets/gitvend.jwt'
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
The empty entry resets inherited helpers for this host. The helper supplies username `git` and the JWT as the HTTPS Basic password only for the exact configured host and port. Use `git-gateway.example` in both settings if serving on standard HTTPS port 443 with no explicit port in the URL. The shell-style `!` helper invokes `gitvend` from `PATH`; Git appends its `get`, `store` or `erase` operation automatically. Store/erase are ignored because the orchestrator owns token replacement.
|
|
188
|
+
|
|
189
|
+
`-token-env VARIABLE_NAME` can replace `-token-file` if you inject the JWT into the agent's environment. Do not put the JWT in a clone URL. A custom Git client can use HTTP Basic directly, with the JWT as its password, and must request protocol v2 for fetch/discovery.
|
|
190
|
+
|
|
191
|
+
### Clone and push an assigned branch
|
|
192
|
+
|
|
193
|
+
```sh
|
|
194
|
+
# Confirm that the credential, route and upstream repository work.
|
|
195
|
+
git ls-remote --heads https://git-gateway.example:8443/github/fooorg/project.git
|
|
196
|
+
|
|
197
|
+
# Select main explicitly, even if the repository's default branch is hidden.
|
|
198
|
+
git clone --branch main https://git-gateway.example:8443/github/fooorg/project.git
|
|
199
|
+
git -C project switch -c agents/47/my-task
|
|
200
|
+
|
|
201
|
+
# After making and committing your changes, push an explicit destination.
|
|
202
|
+
git -C project push -u origin HEAD:refs/heads/agents/47/my-task
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
With the sample grant, discovery shows `main` and any existing `agents/47/*` branches. The push can create the assigned branch or update it. A push to `refs/heads/main` is denied. If one push contains any unauthorized ref update, gitvend rejects the whole request before forwarding its push commands or pack to GitHub. GitHub may independently reject permitted updates through its branch rules; use `git push --atomic` when you require all permitted updates to succeed together and the upstream supports it.
|
|
206
|
+
|
|
207
|
+
For an existing checkout, route its remote through the gateway:
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
git -C project remote set-url origin https://git-gateway.example:8443/github/fooorg/project.git
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Changing a remote does not remove previously fetched objects or refs from that checkout. Use a fresh clone when you want its initial local refs to reflect the current discovery grant.
|
|
214
|
+
|
|
215
|
+
### Create a missing scratch repository on first push
|
|
216
|
+
|
|
217
|
+
The sample token also grants creation of `fooorg/agent-47-*`. Create a local repository with a branch the agent can write, then push it through the gateway:
|
|
218
|
+
|
|
219
|
+
```sh
|
|
220
|
+
git init -b agents/47/start agent-47-scratch
|
|
221
|
+
git -C agent-47-scratch config user.name 'Agent 47'
|
|
222
|
+
git -C agent-47-scratch config user.email 'agent-47@example.invalid'
|
|
223
|
+
git -C agent-47-scratch commit --allow-empty -m 'Initialize agent scratch repository'
|
|
224
|
+
git -C agent-47-scratch remote add origin \
|
|
225
|
+
https://git-gateway.example:8443/github/fooorg/agent-47-scratch.git
|
|
226
|
+
git -C agent-47-scratch push -u origin HEAD:refs/heads/agents/47/start
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Gitvend creates the missing private repository during push discovery, then forwards the authorized push. It creates no initial commit or `main` branch of its own. For a later clone, select `--branch agents/47/start` explicitly. A clone of a missing repository does not create it. Even `git push --dry-run` can create an empty repository; see [provisioning behavior](#provisioning-and-audit).
|
|
230
|
+
|
|
231
|
+
## Permission strings
|
|
232
|
+
|
|
233
|
+
```text
|
|
234
|
+
[!]host/namespace/repository[#ref-selector]:actions
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
| Action | Meaning |
|
|
238
|
+
| --- | --- |
|
|
239
|
+
| `r` | Read objects by hash and discover matching refs. |
|
|
240
|
+
| `w` | Create/update matching refs, including force updates. |
|
|
241
|
+
| `d` | Delete matching refs. |
|
|
242
|
+
| `c` | Create matching private repositories; use without a ref selector. |
|
|
243
|
+
|
|
244
|
+
```text
|
|
245
|
+
github.com/fooorg/*:r
|
|
246
|
+
github.com/fooorg/blue*:rw
|
|
247
|
+
github.com/*:r
|
|
248
|
+
github.com/fooorg/red#(main|master|link):rw
|
|
249
|
+
!github.com/fooorg/*#(main|master):wd
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
Each rule is a separate `-permission` argument or an element in a grant's `permissions` array; do not combine rules into a comma-separated string. Quote rules in shell commands so glob and `!` characters reach gitvend unchanged.
|
|
253
|
+
|
|
254
|
+
| Rule | Use |
|
|
255
|
+
| --- | --- |
|
|
256
|
+
| `github.com/fooorg/project#main:r` | Discover main and allow repository hash reads. |
|
|
257
|
+
| `github.com/fooorg/project#agents/47/*:rw` | Read/create/update this agent's branches. Add `d` (`:rwd`) to allow deletion. |
|
|
258
|
+
| `github.com/fooorg/project#refs/tags/agent-47-*:rw` | Read/create/update matching tags explicitly. |
|
|
259
|
+
| `github.com/fooorg/agent-47-*:c` | Permit matching repository creation; also grant `rw` on at least one branch. |
|
|
260
|
+
| `!github.com/fooorg/*#(main\|master):wd` | Deny main/master writes and deletion across the organization. |
|
|
261
|
+
|
|
262
|
+
Repository `*` stays within one path segment; branch `*` can span `/`. `(a|b)` selects alternatives, not arbitrary regular expressions. `github.com/*` is the whole-host shorthand, still constrained by configured owners.
|
|
263
|
+
|
|
264
|
+
Matching allows combine; any matching deny wins. No `#` covers branches and tags; `#*` covers branches only. A `w` or `d` operation also requires read/discovery permission. `r` scoped to a branch hides other refs but deliberately does not block known-hash reads from the same repository. A broad read grant is not narrowed by adding a more specific grant.
|
|
265
|
+
|
|
266
|
+
See [the complete syntax](docs/permission-syntax.md) for wildcard, alternative, escaping and deny semantics. Inspect a decision without making network calls:
|
|
267
|
+
|
|
268
|
+
```sh
|
|
269
|
+
gitvend explain \
|
|
270
|
+
-permission 'github.com/fooorg/*:r' \
|
|
271
|
+
-permission 'github.com/fooorg/blue*:w' \
|
|
272
|
+
-permission '!github.com/fooorg/*#main:wd' \
|
|
273
|
+
-repo github.com/fooorg/bluebird \
|
|
274
|
+
-ref refs/heads/main \
|
|
275
|
+
-action ref.update
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
The example returns `"allowed": false`. `explain` evaluates only the rules you pass; include applicable deny rules yourself. It does not load server configuration, verify a JWT, or check GitHub permissions. A valid explanation command exits successfully even for a denied decision; inspect `allowed`. A `repo.create` explanation evaluates the `c` permission only, not the additional branch-overlap, quota or upstream checks.
|
|
279
|
+
|
|
280
|
+
## Provisioning and audit
|
|
281
|
+
|
|
282
|
+
Repository creation is opt-in twice: the provider must set `allow_creation: true` and the JWT must grant `c` plus an overlapping readable/writable branch. The gateway proves that overlap with bounded automata, including denies. It never guesses a branch name to establish permission.
|
|
283
|
+
|
|
284
|
+
Creation occurs during receive-pack discovery. Consequently, `git push --dry-run`, abandoned pushes and later branch-policy rejections may leave empty repositories. Clone/fetch never creates repositories. Direct receive-pack POSTs cannot create missing repositories. Created repositories are private and uninitialized; the gateway never changes an existing repository's visibility, deletes it, or silently seeds `main`.
|
|
285
|
+
|
|
286
|
+
The local database records creation reservations, daily quotas, outcomes and stable repository IDs. Same-repository creation requests are serialized. Conflicts and ambiguous create responses are reconciled by lookup; unresolved attempts remain recorded for the next authorized discovery or operator investigation. Existing enrolled repository names cannot silently bind to a different provider ID after deletion/recreation. New JWT bindings can also explicitly pin an ID.
|
|
287
|
+
|
|
288
|
+
Use a Git response's `X-Request-ID` to locate its recorded request in the audit journal. Audit intent is durable before a push or create is forwarded. Push audit distinguishes success, rejection, partial success and unknown outcomes; HTTP 200 alone is not success. An interrupted push is never automatically retried. A crash can leave an `admitted` record, which must be treated as an unknown outcome.
|
|
289
|
+
|
|
290
|
+
To inspect the audit journal, stop the server and run the following. Output is one JSON event per line, including identity, repository, operation and outcome when available:
|
|
291
|
+
|
|
292
|
+
```sh
|
|
293
|
+
gitvend audit -state var/gitvend.db
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The first release uses bbolt with an exclusive process lock: **run one gateway instance per state file**. It handles concurrent agents inside that instance. Active-active replicas need a shared transactional state implementation; separate local state files would not share quotas, identity enrollment or provisioning coordination. Preserve and back up the state file. Audit retention/compaction and a metrics export endpoint are not implemented yet.
|
|
297
|
+
|
|
298
|
+
## Configuration and limits
|
|
299
|
+
|
|
300
|
+
Run the service under a supervisor with a consistent working directory and durable state volume. `SIGTERM` or Ctrl-C starts graceful shutdown with a 10-second drain period; transfers still running afterward are closed.
|
|
301
|
+
|
|
302
|
+
Send `SIGHUP` to atomically reload public keys, issuer ceilings, upstream token files, deny rules and limits. A failed reload preserves the last valid configuration. Changes to the listener, TLS paths or state-file path require restart. Replacing an environment variable requires restarting the process. Config reload does not rewrite credentials already in use by an admitted operation.
|
|
303
|
+
|
|
304
|
+
For signing-key rotation, add the new public key under a new `id`, reload, and issue tokens with that `-kid`. Keep the old key configured until its tokens have expired, including clock tolerance, if they should remain valid; then remove it and reload. To rotate an upstream file token, replace that file and reload; the gateway reads upstream credentials at startup/reload, unlike the agent helper, which reads its JWT for each credential request.
|
|
305
|
+
|
|
306
|
+
Important defaults:
|
|
307
|
+
|
|
308
|
+
| Setting | Default |
|
|
309
|
+
| --- | --- |
|
|
310
|
+
| `max_token_bytes` | 4,096 serialized JWT bytes; configurable up to 32,768 |
|
|
311
|
+
| `max_token_lifetime_seconds` | 900 |
|
|
312
|
+
| `clock_skew_seconds` | 30 |
|
|
313
|
+
| `max_control_bytes` | 4 MiB |
|
|
314
|
+
| `max_pack_bytes` | 1 GiB incoming encoded and decoded request body limits |
|
|
315
|
+
| `max_concurrent` | 64 active Git requests |
|
|
316
|
+
| `request_timeout_seconds` | 1,800 |
|
|
317
|
+
| `creates_per_subject_per_day` | 100, grouped by issuer and subject |
|
|
318
|
+
| `creates_per_owner_per_day` | 1,000 |
|
|
319
|
+
|
|
320
|
+
The HTTP server accepts up to 64 KiB of headers. Basic authentication expands a JWT by roughly one-third, so ingress limits must accommodate the configured token budget too. Use `sign -max-token-bytes` with the same budget as the gateway. Oversized grants are rejected, never truncated or broadened.
|
|
321
|
+
|
|
322
|
+
Creation quotas reserve capacity durably before calling the forge. Uncertain/failed creation attempts conservatively retain the reservation for that day; retrying the same recorded attempt does not reserve again. Existing repositories do not consume creation quota. Incoming packs are streamed after their command prefix passes authorization. Pack contents are not inspected for file/path rules or commit ancestry.
|
|
323
|
+
|
|
324
|
+
Authorized Git requests look up repository metadata through the forge API to verify repository identity. These calls share the upstream credential's API rate limit; this release does not cache metadata or coordinate rate limits across deployments.
|
|
325
|
+
|
|
326
|
+
## Troubleshooting
|
|
327
|
+
|
|
328
|
+
Start with `gitvend version`, `/healthz`, and `git ls-remote --heads` as shown above. Use the HTTP `X-Request-ID` for authenticated Git requests to correlate a failure with the gateway audit journal. Avoid sharing raw credentials or full authorization headers when collecting diagnostics.
|
|
329
|
+
|
|
330
|
+
| Symptom | What to check |
|
|
331
|
+
| --- | --- |
|
|
332
|
+
| Connection refused or TLS certificate error | Confirm the listener interface/port, DNS, certificate hostname and trust chain. The template binds loopback; external agents need a reachable listener or TLS terminator. |
|
|
333
|
+
| Git prompts for a password; helper returns no credential | Confirm `gitvend` is on the agent's `PATH`, the JWT file is readable, and the helper's host **and port** match the remote. Configure the helper in that agent's account. |
|
|
334
|
+
| HTTP 401 / invalid or expired JWT | Refresh the JWT; check issuer, `kid`, audience, clock, lifetime and byte limits against gateway configuration. Agents use their JWT, not the upstream GitHub token. |
|
|
335
|
+
| `Git protocol v2 required` | Set `protocol.version=2`; ensure an ingress proxy preserves the `Git-Protocol` header. Pushes still use standard receive-pack. |
|
|
336
|
+
| HTTP 403 / `push denied for ...` | Inspect the exact destination ref, matching `r` plus `w`/`d` grants, issuer/owner limits and gateway denies. The sample denies writes to main/master. |
|
|
337
|
+
| No refs shown; clone cannot find main or remote HEAD | The requested branch may be absent or hidden. Use an explicitly permitted branch with `--branch`; new scratch repos may have only `agents/47/start`. |
|
|
338
|
+
| HTTP 404 / unsupported route or missing repository | Use `/alias/owner/repo.git`, check allowed owners, and verify the upstream identity can see the repository. Only authorized push discovery can create a missing repo. |
|
|
339
|
+
| Missing repo is not created | Check `allow_creation`, `c`, overlapping branch `rw`, owner limits, quotas and upstream organization permissions. Check the audit journal for an unresolved attempt. |
|
|
340
|
+
| HTTP 429 / gateway busy | The concurrent-request limit is reached. Wait for active requests to finish and retry. |
|
|
341
|
+
| HTTP 409 / creation quota exhausted or repository identity changed | Check creation quotas, prior enrollment and whether the upstream repository was deleted/recreated. Existing repositories do not consume creation quota. |
|
|
342
|
+
| HTTP 502 / upstream request refused or verification failed | Check the upstream token, Git/API base URLs, forge availability/rate limits and repository identity. A gateway JWT cannot overcome upstream restrictions. |
|
|
343
|
+
| HTTP 503 / audit or provisioning state unavailable | Check the state directory, disk space, permissions and database lock. Stop the server before using `audit`. |
|
|
344
|
+
| HTTP 431 or large token rejected at ingress | Align ingress header limits, server `max_token_bytes` and signing `-max-token-bytes`; Basic authentication expands the token. |
|
|
345
|
+
| Push disconnects or audit outcome is `unknown` | Fetch or inspect the permitted remote ref before deciding whether to retry; some or all updates may already have reached GitHub. |
|
|
346
|
+
| npm-installed command reports a missing binary | Check whether install scripts were disabled and whether release downloads were reachable; after fixing that, run `npm rebuild @colony2/gitvend`. |
|
|
347
|
+
|
|
348
|
+
Deleting and recreating a GitHub repository under the same name changes its ID. Gitvend intentionally refuses to silently replace an enrolled ID. There is no enrollment-reset CLI in this release; investigate with the operator instead of deleting the entire state database, which also holds quotas and audit history.
|
|
349
|
+
|
|
350
|
+
## CLI reference
|
|
351
|
+
|
|
352
|
+
For commands that take flags, use `-h` to list them, for example `gitvend sign -h`. The CLI expects flags before any positional argument; for credential helpers, Git appends the final operation.
|
|
353
|
+
|
|
354
|
+
| Command | Purpose / useful flags |
|
|
355
|
+
| --- | --- |
|
|
356
|
+
| `gitvend version` | Show the release version or development build identity. |
|
|
357
|
+
| `gitvend serve -config gitvend.json` | Start the gateway. |
|
|
358
|
+
| `gitvend keygen -private signing.key -public signing.pub` | Generate a new issuer key pair without overwriting existing files. |
|
|
359
|
+
| `gitvend sign` | Issue a JWT; requires `-key`, `-kid`, `-issuer`, `-subject` and a grant. Use repeated `-permission` **or** `-grant`, not both; `-output` writes an atomic token file. |
|
|
360
|
+
| `gitvend credential` | Git helper; specify `-host` and exactly one of `-token-file` or `-token-env`, followed by `get`, `store` or `erase`. |
|
|
361
|
+
| `gitvend explain` | Explain explicit `-permission` rules for a `-repo`, optional `-ref`, and `-action`. Ref values are fully qualified, such as `refs/heads/main`. |
|
|
362
|
+
| `gitvend audit -state var/gitvend.db` | Print the stored audit journal while the gateway is stopped. |
|
|
363
|
+
|
|
364
|
+
`explain` actions are `repo.read`, `repo.create`, `ref.discover`, `ref.create`, `ref.update` and `ref.delete`. The default is `ref.update`.
|
|
365
|
+
|
|
366
|
+
## Compatibility and verification scope
|
|
367
|
+
|
|
368
|
+
The automated tests cover real Git v2 discovery/clone/fetch, shallow and partial clones, known-hash reads, standard pushes, ref filtering, mixed denied pushes, tags, deletion, force updates, atomic and partial push outcomes, creation races/recovery, JWT tampering/expiry/size, secret/key reloads, identity substitution and malformed/gzip requests. Parser fuzz targets and race checks are included.
|
|
369
|
+
|
|
370
|
+
Supported object format is SHA-1. SSH, Git LFS, Git protocol v0/v1 fetch, signed push certificates, push options, named shallow exclusions, ref-in-want, offloaded pack/bundle URLs and provider-managed ref namespaces are rejected or not advertised. Branch writes can include workflow files; CI execution and secrets remain governed by the forge's configuration. GitHub branch rules continue to apply to the shared upstream identity.
|
|
371
|
+
|
|
372
|
+
No live GitHub credentials were required for the local suite. Validate the chosen account/token, organization creation policy, workflow permissions and GitHub Enterprise version in a disposable organization before deployment; the local API fixture cannot establish those external permissions.
|
|
373
|
+
|
|
374
|
+
The original requirements and implementation tradeoffs are in [the design](docs/git-proxy-design.md). [c2r](https://github.com/colony-2/c2r) informed the repository-provisioning approach. This implementation uses a small direct GitHub adapter; no c2r source was copied.
|
|
375
|
+
|
|
376
|
+
## Release automation
|
|
377
|
+
|
|
378
|
+
The [release workflow](.github/workflows/release.yaml) follows [c2j's release pattern](https://github.com/colony-2/c2j/blob/main/.github/workflows/release.yaml). Successful pushes to `main` in the `colony-2` organization run the test suite, automatically bump and push a version tag (patch by default), build four binaries, and publish GitHub release archives and checksums. It then publishes `@colony2/gitvend` to npm after installing and smoke-testing the packed wrapper against the published assets. There is no container-image build or publication.
|
|
379
|
+
|
|
380
|
+
GitHub release/tag publication uses the workflow's `GITHUB_TOKEN`. Configure npm trusted publishing for `colony-2/gitvend`, workflow `release.yaml`, or provide an `NPM_TOKEN` with publishing access to `@colony2/gitvend`. The npm package must exist and its trusted publisher must be configured before token-free publication can work; bootstrap it with a token if needed.
|
|
381
|
+
|
|
382
|
+
macOS signing/notarization is optional. Set all five secrets to enable it: `MACOS_SIGN_P12`, `MACOS_SIGN_PASSWORD`, `APPLE_API_ISSUER`, `APPLE_API_KEY_ID`, and `APPLE_API_KEY`. No signing secrets skips this step; a partial configuration fails the build. Signing requires the matching Apple certificate and notarization credentials.
|
|
383
|
+
|
|
384
|
+
The reusable [test workflow](.github/workflows/test.yml) runs Go race tests, vet, parser fuzzing and npm installer tests. Run the installer tests locally with `npm test --prefix npm/gitvend`. Release archive smoke tests also check all checksums and run the native binary's `version` command before upload.
|
|
385
|
+
|
|
386
|
+
Copied release tooling retains its upstream Apache-2.0 license and attribution in [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md). This repository does not yet declare a project-wide license; npm metadata uses `UNLICENSED`.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
The release workflow, release-note configuration and npm launcher/installer are adapted from [colony-2/c2j](https://github.com/colony-2/c2j/tree/ef65f00019724f17069d318845b0bc75ddc0dc98). The original project distributes these files under the Apache License, Version 2.0; see [the license text](LICENSES/c2j-Apache-2.0.txt).
|
|
4
|
+
|
|
5
|
+
Changes include gitvend naming and metadata, Go module configuration, scoped workflow permissions, local verification, and installer robustness tests.
|
|
6
|
+
|
|
7
|
+
This notice preserves the upstream license for those files. No project-wide license has been selected for gitvend; npm metadata therefore uses `UNLICENSED`.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Adapted from colony-2/c2j; see THIRD_PARTY_NOTICES.md.
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const fs = require("fs");
|
|
7
|
+
const path = require("path");
|
|
8
|
+
const { spawn } = require("child_process");
|
|
9
|
+
|
|
10
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
11
|
+
const packageJson = require(path.join(packageRoot, "package.json"));
|
|
12
|
+
const packageName = packageJson.name.split("/").pop();
|
|
13
|
+
const binaryPath = path.join(packageRoot, "vendor", packageName);
|
|
14
|
+
|
|
15
|
+
if (!fs.existsSync(binaryPath)) {
|
|
16
|
+
console.error(
|
|
17
|
+
`${packageName} binary is missing. Reinstall the package or run npm rebuild ${packageJson.name}.`
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
23
|
+
stdio: "inherit",
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
// The gateway is a long-running process; forward shutdown and reload signals.
|
|
27
|
+
const handlers = new Map();
|
|
28
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
29
|
+
const handler = () => child.kill(signal);
|
|
30
|
+
handlers.set(signal, handler);
|
|
31
|
+
process.on(signal, handler);
|
|
32
|
+
}
|
|
33
|
+
child.on("error", (error) => {
|
|
34
|
+
console.error(error.message);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
child.on("exit", (status, signal) => {
|
|
38
|
+
for (const [name, handler] of handlers) process.removeListener(name, handler);
|
|
39
|
+
if (signal) process.kill(process.pid, signal);
|
|
40
|
+
else process.exit(status === null ? 1 : status);
|
|
41
|
+
});
|
package/package.json
CHANGED
|
@@ -1,13 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colony2/gitvend",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "",
|
|
5
|
-
"
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Git HTTPS gateway with signed repository and branch permissions",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"homepage": "https://github.com/colony-2/gitvend",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/colony-2/gitvend.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/colony-2/gitvend/issues"
|
|
13
|
+
},
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "Colony 2",
|
|
16
|
+
"email": "noreply@colony2.com"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"git",
|
|
20
|
+
"github",
|
|
21
|
+
"ai",
|
|
22
|
+
"agent",
|
|
23
|
+
"cli",
|
|
24
|
+
"security"
|
|
25
|
+
],
|
|
26
|
+
"bin": {
|
|
27
|
+
"gitvend": "bin/cli.js"
|
|
28
|
+
},
|
|
6
29
|
"scripts": {
|
|
7
|
-
"
|
|
30
|
+
"postinstall": "node scripts/postinstall.js",
|
|
31
|
+
"test": "node --test test/*.test.js"
|
|
32
|
+
},
|
|
33
|
+
"os": [
|
|
34
|
+
"darwin",
|
|
35
|
+
"linux"
|
|
36
|
+
],
|
|
37
|
+
"cpu": [
|
|
38
|
+
"x64",
|
|
39
|
+
"arm64"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.0.0"
|
|
8
43
|
},
|
|
9
|
-
"
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
44
|
+
"files": [
|
|
45
|
+
"bin/",
|
|
46
|
+
"scripts/",
|
|
47
|
+
"README.md",
|
|
48
|
+
"THIRD_PARTY_NOTICES.md",
|
|
49
|
+
"LICENSES/"
|
|
50
|
+
]
|
|
13
51
|
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Adapted from colony-2/c2j; see THIRD_PARTY_NOTICES.md.
|
|
4
|
+
"use strict";
|
|
5
|
+
|
|
6
|
+
const crypto = require("crypto");
|
|
7
|
+
const fs = require("fs");
|
|
8
|
+
const os = require("os");
|
|
9
|
+
const path = require("path");
|
|
10
|
+
const { execFileSync } = require("child_process");
|
|
11
|
+
const { get } = require("https");
|
|
12
|
+
|
|
13
|
+
const { pipeline } = require("stream/promises");
|
|
14
|
+
|
|
15
|
+
const platforms = {
|
|
16
|
+
"linux:x64": ["Linux", "x86_64"],
|
|
17
|
+
"linux:arm64": ["Linux", "arm64"],
|
|
18
|
+
"darwin:x64": ["Darwin", "x86_64"],
|
|
19
|
+
"darwin:arm64": ["Darwin", "arm64"],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function releaseAsset(pkg, platform, arch) {
|
|
23
|
+
const target = platforms[`${platform}:${arch}`];
|
|
24
|
+
if (!target) throw new Error(`Unsupported platform: ${platform}:${arch}`);
|
|
25
|
+
if (!/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/.test(pkg.version)) {
|
|
26
|
+
throw new Error(`Not a release version: ${pkg.version}`);
|
|
27
|
+
}
|
|
28
|
+
const name = pkg.name.split("/").pop();
|
|
29
|
+
if (name !== "gitvend") throw new Error("Expected gitvend package name");
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
assetName: `${name}_${pkg.version}_${target[0]}_${target[1]}.tar.gz`,
|
|
33
|
+
baseUrl: `https://github.com/${repositoryPath(pkg)}/releases/download/v${pkg.version}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function install({
|
|
38
|
+
packageRoot = path.resolve(__dirname, ".."),
|
|
39
|
+
platform = process.platform,
|
|
40
|
+
arch = process.arch,
|
|
41
|
+
downloadFile = downloadWithRetry,
|
|
42
|
+
} = {}) {
|
|
43
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
|
|
44
|
+
const { name, assetName, baseUrl } = releaseAsset(pkg, platform, arch);
|
|
45
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `${name}-`));
|
|
46
|
+
const vendorDir = path.join(packageRoot, "vendor");
|
|
47
|
+
let stagedBinary;
|
|
48
|
+
try {
|
|
49
|
+
const archivePath = path.join(tempDir, assetName);
|
|
50
|
+
const checksumsPath = path.join(tempDir, "checksums.txt");
|
|
51
|
+
const extractDir = path.join(tempDir, "extract");
|
|
52
|
+
fs.mkdirSync(extractDir);
|
|
53
|
+
await downloadFile(`${baseUrl}/${assetName}`, archivePath);
|
|
54
|
+
await downloadFile(`${baseUrl}/checksums.txt`, checksumsPath, { maxBytes: 1024 * 1024 });
|
|
55
|
+
verifyChecksum(archivePath, checksumsPath, assetName);
|
|
56
|
+
// Extract only the executable, not documentation or other archive entries.
|
|
57
|
+
execFileSync("tar", ["-xzf", archivePath, "-C", extractDir, name], { stdio: "pipe" });
|
|
58
|
+
const binary = path.join(extractDir, name);
|
|
59
|
+
if (!fs.lstatSync(binary).isFile()) throw new Error("Release binary must be a regular file");
|
|
60
|
+
fs.mkdirSync(vendorDir, { recursive: true });
|
|
61
|
+
stagedBinary = path.join(vendorDir, `.gitvend-${crypto.randomBytes(8).toString("hex")}`);
|
|
62
|
+
fs.copyFileSync(binary, stagedBinary, fs.constants.COPYFILE_EXCL);
|
|
63
|
+
fs.chmodSync(stagedBinary, 0o755);
|
|
64
|
+
fs.renameSync(stagedBinary, path.join(vendorDir, name));
|
|
65
|
+
} finally {
|
|
66
|
+
if (stagedBinary) fs.rmSync(stagedBinary, { force: true });
|
|
67
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function repositoryPath(pkg) {
|
|
72
|
+
const raw =
|
|
73
|
+
typeof pkg.repository === "string" ? pkg.repository : pkg.repository && pkg.repository.url;
|
|
74
|
+
|
|
75
|
+
if (!raw) {
|
|
76
|
+
throw new Error("package.json repository is required to locate release assets");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const match = raw.match(/^(?:git\+)?https:\/\/github\.com\/([A-Za-z0-9_-]+\/[A-Za-z0-9_.-]+?)(?:\.git)?$/);
|
|
80
|
+
if (!match) {
|
|
81
|
+
throw new Error(`Unsupported repository URL for release assets: ${raw}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return match[1];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function downloadWithRetry(url, destination, options = {}) {
|
|
88
|
+
let lastError;
|
|
89
|
+
|
|
90
|
+
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
91
|
+
try {
|
|
92
|
+
await download(url, destination, options);
|
|
93
|
+
return;
|
|
94
|
+
} catch (error) {
|
|
95
|
+
lastError = error;
|
|
96
|
+
if (attempt === 5) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
await delay(attempt * 1000);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw lastError;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function download(url, destination, { request = get, redirects = 5, timeout = 30000, maxBytes = 256 * 1024 * 1024 } = {}) {
|
|
107
|
+
return new Promise((resolve, reject) => {
|
|
108
|
+
const parsed = new URL(url);
|
|
109
|
+
if (parsed.protocol !== "https:") throw new Error("Release downloads require HTTPS");
|
|
110
|
+
const req = request(parsed, (response) => {
|
|
111
|
+
if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
|
|
112
|
+
response.resume();
|
|
113
|
+
if (redirects === 0) return reject(new Error("Too many release download redirects"));
|
|
114
|
+
let location;
|
|
115
|
+
try { location = new URL(response.headers.location, parsed).href; }
|
|
116
|
+
catch (error) { reject(error); return; }
|
|
117
|
+
download(location, destination, { request, redirects: redirects - 1, timeout, maxBytes }).then(resolve, reject);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (response.statusCode !== 200) {
|
|
121
|
+
response.resume();
|
|
122
|
+
reject(new Error(`Failed to download ${url}: HTTP ${response.statusCode}`));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
let size = 0;
|
|
126
|
+
response.on("data", (chunk) => {
|
|
127
|
+
size += chunk.length;
|
|
128
|
+
if (size > maxBytes) response.destroy(new Error("Release download exceeds size limit"));
|
|
129
|
+
});
|
|
130
|
+
const file = fs.createWriteStream(destination, { mode: 0o600 });
|
|
131
|
+
pipeline(response, file).then(resolve, reject);
|
|
132
|
+
});
|
|
133
|
+
req.setTimeout(timeout, () => req.destroy(new Error("Release download timed out")));
|
|
134
|
+
req.on("error", reject);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function delay(ms) {
|
|
139
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function verifyChecksum(archive, checksums, assetName) {
|
|
143
|
+
const matches = fs
|
|
144
|
+
.readFileSync(checksums, "utf8")
|
|
145
|
+
.split(/\r?\n/)
|
|
146
|
+
.map((line) => line.trim().split(/\s+/))
|
|
147
|
+
.filter((parts) => parts[1] === assetName);
|
|
148
|
+
const expected = matches[0];
|
|
149
|
+
|
|
150
|
+
if (matches.length !== 1 || !/^[a-f0-9]{64}$/.test(expected[0])) {
|
|
151
|
+
throw new Error(`Missing, duplicate or invalid checksum for ${assetName}`);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const actual = crypto.createHash("sha256").update(fs.readFileSync(archive)).digest("hex");
|
|
155
|
+
if (actual !== expected[0]) {
|
|
156
|
+
throw new Error(`Checksum mismatch for ${assetName}`);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { install, releaseAsset, repositoryPath, download, verifyChecksum };
|
|
161
|
+
if (require.main === module) {
|
|
162
|
+
install().catch((error) => {
|
|
163
|
+
console.error(error.message || error);
|
|
164
|
+
process.exitCode = 1;
|
|
165
|
+
});
|
|
166
|
+
}
|