├── .gitignore ├── LICENSE.md ├── Makefile ├── README.md ├── bridge ├── config.go ├── db.go ├── migrations.go └── repo.go ├── cmd ├── git-nostr-bridge │ ├── main.go │ ├── repo.go │ └── sshkey.go ├── git-nostr-cli │ ├── config.go │ ├── main.go │ ├── repo.go │ └── sshkey.go └── git-nostr-ssh │ └── main.go ├── git-nostr.png ├── go.mod ├── go.sum ├── license.go ├── path.go ├── protocol ├── kind.go ├── repository.go └── repositorypermission.go └── pubkey.go /.gitignore: -------------------------------------------------------------------------------- 1 | bin -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | The gitnostr project is licensed under the MIT License but uses components under other licenses listed below 3 | 4 | MIT License 5 | 6 | Copyright (c) 2023 Steven Pearson 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | 26 | # Component Licenses 27 | 28 | ## https://github.com/SaveTheRbtz/generic-sync-map-go 29 | 30 | MIT License 31 | 32 | Copyright (c) 2023 Alexey Ivanov 33 | 34 | Permission is hereby granted, free of charge, to any person obtaining a copy 35 | of this software and associated documentation files (the "Software"), to deal 36 | in the Software without restriction, including without limitation the rights 37 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 38 | copies of the Software, and to permit persons to whom the Software is 39 | furnished to do so, subject to the following conditions: 40 | 41 | The above copyright notice and this permission notice shall be included in all 42 | copies or substantial portions of the Software. 43 | 44 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 45 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 46 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 47 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 48 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 49 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 50 | SOFTWARE. 51 | 52 | ## https://gitlab.com/cznic/sqlite 53 | 54 | BSD 3-Clause "New" or "Revised" License 55 | 56 | Copyright (c) 2017 The Sqlite Authors. All rights reserved. 57 | 58 | Redistribution and use in source and binary forms, with or without 59 | modification, are permitted provided that the following conditions are met: 60 | 61 | 1. Redistributions of source code must retain the above copyright notice, this 62 | list of conditions and the following disclaimer. 63 | 64 | 2. Redistributions in binary form must reproduce the above copyright notice, 65 | this list of conditions and the following disclaimer in the documentation 66 | and/or other materials provided with the distribution. 67 | 68 | 3. Neither the name of the copyright holder nor the names of its contributors 69 | may be used to endorse or promote products derived from this software without 70 | specific prior written permission. 71 | 72 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 73 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 74 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 75 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 76 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 77 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 78 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 79 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 80 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 81 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 82 | 83 | ## https://github.com/btcsuite/btcd 84 | 85 | ISC License 86 | 87 | Copyright (c) 2013-2022 The btcsuite developers 88 | Copyright (c) 2015-2016 The Decred developers 89 | 90 | Permission to use, copy, modify, and distribute this software for any 91 | purpose with or without fee is hereby granted, provided that the above 92 | copyright notice and this permission notice appear in all copies. 93 | 94 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 95 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 96 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 97 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 98 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 99 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 100 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 101 | 102 | ## https://github.com/decred/dcrd 103 | 104 | ISC License 105 | 106 | Copyright (c) 2013-2017 The btcsuite developers 107 | Copyright (c) 2015-2020 The Decred developers 108 | Copyright (c) 2017 The Lightning Network Developers 109 | 110 | Permission to use, copy, modify, and distribute this software for any 111 | purpose with or without fee is hereby granted, provided that the above 112 | copyright notice and this permission notice appear in all copies. 113 | 114 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 115 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 116 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 117 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 118 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 119 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 120 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 121 | 122 | ## https://github.com/gorilla/websocket 123 | 124 | BSD 2-Clause "Simplified" License 125 | 126 | Copyright (c) 2013 The Gorilla WebSocket Authors. All rights reserved. 127 | 128 | Redistribution and use in source and binary forms, with or without 129 | modification, are permitted provided that the following conditions are met: 130 | 131 | Redistributions of source code must retain the above copyright notice, this 132 | list of conditions and the following disclaimer. 133 | 134 | Redistributions in binary form must reproduce the above copyright notice, 135 | this list of conditions and the following disclaimer in the documentation 136 | and/or other materials provided with the distribution. 137 | 138 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 139 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 140 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 141 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 142 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 143 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 144 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 145 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 146 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 147 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 148 | 149 | ## https://github.com/valyala/fastjson 150 | 151 | The MIT License (MIT) 152 | 153 | Copyright (c) 2018 Aliaksandr Valialkin 154 | 155 | Permission is hereby granted, free of charge, to any person obtaining a copy 156 | of this software and associated documentation files (the "Software"), to deal 157 | in the Software without restriction, including without limitation the rights 158 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 159 | copies of the Software, and to permit persons to whom the Software is 160 | furnished to do so, subject to the following conditions: 161 | 162 | The above copyright notice and this permission notice shall be included in all 163 | copies or substantial portions of the Software. 164 | 165 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 166 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 167 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 168 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 169 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 170 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 171 | SOFTWARE. 172 | 173 | ## https://cs.opensource.google/go 174 | 175 | Copyright (c) 2009 The Go Authors. All rights reserved. 176 | 177 | Redistribution and use in source and binary forms, with or without 178 | modification, are permitted provided that the following conditions are 179 | met: 180 | 181 | * Redistributions of source code must retain the above copyright 182 | notice, this list of conditions and the following disclaimer. 183 | * Redistributions in binary form must reproduce the above 184 | copyright notice, this list of conditions and the following disclaimer 185 | in the documentation and/or other materials provided with the 186 | distribution. 187 | * Neither the name of Google Inc. nor the names of its 188 | contributors may be used to endorse or promote products derived from 189 | this software without specific prior written permission. 190 | 191 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 192 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 193 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 194 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 195 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 196 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 197 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 198 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 199 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 200 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 201 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 202 | 203 | ## https://github.com/google/uuid 204 | 205 | BSD 3-Clause "New" or "Revised" License 206 | 207 | Copyright (c) 2009,2014 Google Inc. All rights reserved. 208 | 209 | Redistribution and use in source and binary forms, with or without 210 | modification, are permitted provided that the following conditions are 211 | met: 212 | 213 | * Redistributions of source code must retain the above copyright 214 | notice, this list of conditions and the following disclaimer. 215 | * Redistributions in binary form must reproduce the above 216 | copyright notice, this list of conditions and the following disclaimer 217 | in the documentation and/or other materials provided with the 218 | distribution. 219 | * Neither the name of Google Inc. nor the names of its 220 | contributors may be used to endorse or promote products derived from 221 | this software without specific prior written permission. 222 | 223 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 224 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 225 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 226 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 227 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 228 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 229 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 230 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 231 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 232 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 233 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 234 | 235 | ## https://github.com/kballard/go-shellquote 236 | 237 | MIT License 238 | 239 | Copyright (C) 2014 Kevin Ballard 240 | 241 | Permission is hereby granted, free of charge, to any person obtaining 242 | a copy of this software and associated documentation files (the "Software"), 243 | to deal in the Software without restriction, including without limitation 244 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 245 | and/or sell copies of the Software, and to permit persons to whom the 246 | Software is furnished to do so, subject to the following conditions: 247 | 248 | The above copyright notice and this permission notice shall be included 249 | in all copies or substantial portions of the Software. 250 | 251 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 252 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 253 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 254 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 255 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 256 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 257 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 258 | 259 | ## https://github.com/mattn/go-isatty 260 | 261 | Copyright (c) Yasuhiro MATSUMOTO 262 | 263 | MIT License (Expat) 264 | 265 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 266 | 267 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 268 | 269 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 270 | 271 | ## https://github.com/nbd-wtf 272 | 273 | MIT License 274 | 275 | Copyright (c) 2022 nbd 276 | 277 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 278 | 279 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 280 | 281 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 282 | 283 | ## https://github.com/remyoudompheng/bigfft 284 | 285 | BSD 3-Clause "New" or "Revised" License 286 | 287 | Copyright (c) 2012 The Go Authors. All rights reserved. 288 | 289 | Redistribution and use in source and binary forms, with or without 290 | modification, are permitted provided that the following conditions are 291 | met: 292 | 293 | * Redistributions of source code must retain the above copyright 294 | notice, this list of conditions and the following disclaimer. 295 | * Redistributions in binary form must reproduce the above 296 | copyright notice, this list of conditions and the following disclaimer 297 | in the documentation and/or other materials provided with the 298 | distribution. 299 | * Neither the name of Google Inc. nor the names of its 300 | contributors may be used to endorse or promote products derived from 301 | this software without specific prior written permission. 302 | 303 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 304 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 305 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 306 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 307 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 308 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 309 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 310 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 311 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 312 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 313 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 314 | 315 | ## https://github.com/lukechampine/uint128 316 | 317 | The MIT License (MIT) 318 | 319 | Copyright (c) 2019 Luke Champine 320 | 321 | Permission is hereby granted, free of charge, to any person obtaining a copy 322 | of this software and associated documentation files (the "Software"), to deal 323 | in the Software without restriction, including without limitation the rights 324 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 325 | copies of the Software, and to permit persons to whom the Software is 326 | furnished to do so, subject to the following conditions: 327 | 328 | The above copyright notice and this permission notice shall be included in 329 | all copies or substantial portions of the Software. 330 | 331 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 332 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 333 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 334 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 335 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 336 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 337 | THE SOFTWARE. 338 | 339 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CGO_ENABLED=0 2 | 3 | .PHONY: git-nostr-bridge 4 | git-nostr-bridge: 5 | go build -tags netgo -ldflags="-s -w" -trimpath -o ./bin/git-nostr-bridge ./cmd/git-nostr-bridge 6 | go build -tags netgo -ldflags="-s -w" -trimpath -o ./bin/git-nostr-ssh ./cmd/git-nostr-ssh 7 | 8 | .PHONY: git-nostr-cli 9 | git-nostr-cli: 10 | go build -tags netgo -ldflags="-s -w" -trimpath -o ./bin/gn ./cmd/git-nostr-cli 11 | 12 | .PHONY: all 13 | all: git-nostr-bridge git-nostr-cli -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nostr Git 2 | 3 | A proof of concept integration of git and nostr providing 4 | 5 | - repository management 6 | - ssh-key management 7 | - repository permission management 8 | 9 | This will hopefully form part of a solution for creating a decentralized version of the github/gitlab experience. 10 | 11 | I chose to build on top of the existing git tooling to allow the client side dev tools to remain largely unchanged for daily work (standard git commands work including push and pull) 12 | 13 | By storing the config on Nostr your repository configuration can be easily regenerated a new host if your current git provider decides to censor you. 14 | 15 | See a demo video here: https://www.youtube.com/watch?v=G-WzlC8XfW4 16 | 17 | There is much more to a decentralized github/gitlab experience than just a repository. It would also be advantageous to move pull requests and issues to the Nostr protocol. These should however be treated as separate projects that will hopefully be interopable with this project's approach to repository management. 18 | 19 | 20 | # How 21 | 22 | ![Architecture diagram](git-nostr.png) 23 | 24 | ## git-nostr-db 25 | 26 | An sqlite DB is used to cache the latest version of the data needed to perform access control checks to avoid development downtime in case of a relay or the git-nostr-bridge being offline. 27 | 28 | ### git-nostr-bridge 29 | 30 | Connects to a set of relays and: 31 | 1. subscribes to the events needed to keep the git-nostr-db up to date 32 | 2. creates git repositories as needed 33 | 3. updates the ssh authorized_keys file 34 | 35 | **DO NOT RUN THE BRIDGE AS YOUR OWN USER YOU WILL LOSE YOUR AUTHORIZED_KEYS FILE** 36 | 37 | ### git-nostr-ssh 38 | 39 | Configured as the command for a nostr users ssh-key in the authorized_keys file. 40 | Whenever a user tries to perform a git operation (push/pull) git-nostr-ssh will perform an access control check. 41 | 42 | ### git-nostr-hook 43 | 44 | TODO: not implemented yet. 45 | 46 | Will enable fine grain branch control e.g. prevent pushing to specific branches or force pushing to a branch. 47 | 48 | ### git-nostr-cli (gn) 49 | 50 | Command line tool with similar options to the github cli that will publish the relevant events using your private key to the configured relays 51 | 52 | git-nostr-bridge will then react to these events and update the DB and create any git repos needed. 53 | 54 | 55 | # Setup Instructions 56 | 57 | **Currently this project is Linux only** 58 | **Go version 1.20+ is required** 59 | **It is recommended to use a local private relay for testing. Testing was performed using https://github.com/scsibug/nostr-rs-relay** 60 | 61 | ## git-nostr-bridge 62 | 63 | **These instructions are needed if you intend to host git repositories. If another nostr user has configured a git-nostr-bridge for you then follwo the git-nostr-cli instructions below.** 64 | 65 | Create a new user to host the git repositories (This is needed as the bridge will overwrite the authroized_keys file) and switch to the new account 66 | 67 | **DO NOT RUN THE BRIDGE AS YOUR OWN USER YOU WILL LOSE YOUR AUTHORIZED_KEYS FILE** 68 | 69 | ```bash 70 | sudo useradd --create-home git-nostr 71 | sudo su - git-nostr 72 | ``` 73 | 74 | Clone the gitnostr repository and build the bridge components 75 | 76 | ```bash 77 | git clone https://github.com/spearson78/gitnostr 78 | cd gitnostr 79 | make git-nostr-bridge 80 | ``` 81 | 82 | Start the bridge once to create the empty config files. **DO NOT RUN THE BRIDGE AS YOUR OWN USER YOU WILL LOSE YOUR AUTHORIZED_KEYS FILE** 83 | 84 | ```bash 85 | ./bin/git-nostr-bridge 86 | ``` 87 | 88 | You should get the message `no relays connected` 89 | 90 | Edit the config file at `~/.config/git-nostr/git-nostr-bridge.json`. The default file should look like this 91 | 92 | ``` 93 | { 94 | "repositoryDir": "~/git-nostr-repositories", 95 | "DbFile": "~/.config/git-nostr/git-nostr-db.sqlite", 96 | "relays": [], 97 | "gitRepoOwners": [] 98 | } 99 | ``` 100 | 101 | Add your relay of relays to the list of relays. **You should use a local relay for testing until the implementation is finalized.** 102 | Add your public key to the list of gitRepoOwners. **It is recommended to generate a new nostr private/public key pair for testing** 103 | 104 | git-nostr-bridge will follow events published by gitRepoOwners and create git repositories for them. 105 | 106 | My local testing config looks like this 107 | 108 | ``` 109 | { 110 | "repositoryDir": "~/git-nostr-repositories", 111 | "DbFile": "~/.config/git-nostr/git-nostr-db.sqlite", 112 | "relays": ["ws://localhost:8080"], 113 | "gitRepoOwners": ["e0e7807d354ea7662412d99856335e1923b0b57b6668575bf320837f6b1816e3"] 114 | } 115 | ``` 116 | 117 | You can now start the bridge again. **DO NOT RUN THE BRIDGE AS YOUR OWN USER YOU WILL LOSE YOUR AUTHORIZED_KEYS FILE** 118 | 119 | ```bash 120 | ./bin/git-nostr-bridge 121 | ``` 122 | 123 | As no events have been published you should see no console output. 124 | 125 | Your git-nostr-bridge is now ready for use 126 | 127 | ## git-nostr-cli (gn) 128 | 129 | **Watch out for a conflict with the gn command from https://gn.googlesource.com ** 130 | 131 | Clone the gitnostr repository and build the cli components 132 | 133 | ```bash 134 | git clone https://github.com/spearson78/gitnostr 135 | cd gitnostr 136 | make git-nostr-cli 137 | ``` 138 | 139 | run the git-nostr-cli command once to create the default config file 140 | 141 | ```bash 142 | ./bin/gn 143 | ``` 144 | 145 | You should get the message `no relays connected` 146 | 147 | Edit the config file at `~/.config/git-nostr/git-nostr-cli.json`. The default file should look like this 148 | 149 | ``` 150 | { 151 | "relays": [], 152 | "privateKey": "", 153 | "gitSshBase": "" 154 | } 155 | ``` 156 | 157 | Add your relay of relays to the list of relays. **You should use a local relay for testing until the implementation is finalized.** 158 | Set your private key. **It is recommended to generate a new nostr private key for testing** 159 | Set gitSshBase to the ssh user@hostname where a git-nostr-bridge has been installed. 160 | 161 | My local testing config looks like this 162 | 163 | ``` 164 | { 165 | "relays": ["ws://localhost:8080"], 166 | "privateKey": "...", 167 | "gitSshBase": "git-str@localhost" 168 | } 169 | ``` 170 | 171 | Publish your ssh-key. you may need to replace id_rsa.pub with the correct public key file 172 | 173 | ```bash 174 | ./bin/gn ssh-key add ~/.ssh/id_rsa.pub 175 | ``` 176 | 177 | Create a test repository and clone it. replace with the hex represenation of your public key. If you are using a nip05 capable public key you can use the nip05 identifier instead. 178 | 179 | ```bash 180 | ./bin/gn repo create test 181 | ./bin/gn repo clone :test 182 | ``` 183 | 184 | You can set write permission for your repository with the following command. replace with the hex represenation of your public key. If you are using a nip05 capable public key you can use the nip05 identifier instead. 185 | 186 | ```bash 187 | ./bin/gn repo permissions test WRITE 188 | ``` 189 | -------------------------------------------------------------------------------- /bridge/config.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "io/fs" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/spearson78/gitnostr" 12 | ) 13 | 14 | type Config struct { 15 | ConfigDir string `json:"-"` 16 | RepositoryDir string `json:"repositoryDir"` 17 | DbFile string `json:"DbFile"` 18 | Relays []string `json:"relays"` 19 | GitRepoOwners []string `json:"gitRepoOwners"` 20 | } 21 | 22 | func getConfigFilePath(resolvedConfigDir string) string { 23 | return filepath.Join(resolvedConfigDir, "git-nostr-bridge.json") 24 | } 25 | 26 | func LoadConfig(configDir string) (Config, error) { 27 | 28 | resolvedConfigDir, err := gitnostr.ResolvePath(configDir) 29 | if err != nil { 30 | return Config{}, fmt.Errorf("load config : %w", err) 31 | } 32 | 33 | configPath := getConfigFilePath(resolvedConfigDir) 34 | 35 | configFile, err := os.Open(configPath) 36 | if err != nil { 37 | if errors.Is(err, fs.ErrNotExist) { 38 | cfg := Config{ 39 | ConfigDir: configDir, 40 | RepositoryDir: "~/git-nostr-repositories", 41 | DbFile: "~/.config/git-nostr/git-nostr-db.sqlite", 42 | Relays: []string{}, 43 | GitRepoOwners: []string{}, 44 | } 45 | err = SaveConfig(cfg) 46 | if err != nil { 47 | return Config{}, fmt.Errorf("load config initialize : %w", err) 48 | } 49 | return cfg, nil 50 | } else { 51 | return Config{}, err 52 | } 53 | } 54 | defer configFile.Close() 55 | 56 | cfg := Config{ 57 | ConfigDir: resolvedConfigDir, 58 | } 59 | err = json.NewDecoder(configFile).Decode(&cfg) 60 | 61 | return cfg, err 62 | } 63 | 64 | func SaveConfig(cfg Config) error { 65 | resolvedConfigDir, err := gitnostr.ResolvePath(cfg.ConfigDir) 66 | if err != nil { 67 | return fmt.Errorf("save config resolve: %w", err) 68 | } 69 | 70 | err = os.MkdirAll(resolvedConfigDir, 0700) 71 | if err != nil { 72 | if errors.Is(err, fs.ErrExist) { 73 | //Ignore 74 | } else { 75 | return fmt.Errorf("save config mkdir: %w", err) 76 | } 77 | } 78 | 79 | configPath := getConfigFilePath(resolvedConfigDir) 80 | 81 | configFile, err := os.OpenFile(configPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 82 | if err != nil { 83 | return fmt.Errorf("save config open : %w", err) 84 | } 85 | defer configFile.Close() 86 | 87 | enc := json.NewEncoder(configFile) 88 | enc.SetIndent("", " ") 89 | err = enc.Encode(cfg) 90 | if err != nil { 91 | return fmt.Errorf("save config write : %w", err) 92 | } 93 | 94 | return nil 95 | } 96 | -------------------------------------------------------------------------------- /bridge/db.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | 7 | "github.com/spearson78/gitnostr" 8 | _ "modernc.org/sqlite" 9 | ) 10 | 11 | func OpenDb(dbFilePath string) (*sql.DB, error) { 12 | 13 | resolvedDbFilePath, err := gitnostr.ResolvePath(dbFilePath) 14 | if err != nil { 15 | return nil, fmt.Errorf("open db resolve %v : %w", dbFilePath, err) 16 | } 17 | 18 | db, err := sql.Open("sqlite", resolvedDbFilePath) 19 | if err != nil { 20 | return nil, fmt.Errorf("open db %v : %w", resolvedDbFilePath, err) 21 | } 22 | 23 | _, err = db.Exec("PRAGMA busy_timeout = 500;") 24 | if err != nil { 25 | return nil, fmt.Errorf("open db set timeout %v : %w", resolvedDbFilePath, err) 26 | } 27 | 28 | err = applyMigrations(db) 29 | if err != nil { 30 | return nil, err 31 | } 32 | 33 | return db, nil 34 | } 35 | -------------------------------------------------------------------------------- /bridge/migrations.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/spearson78/fsql" 7 | "github.com/spearson78/migrate" 8 | ) 9 | 10 | var migrated = false 11 | 12 | func applyMigrations(db *sql.DB) (err error) { 13 | 14 | if migrated { 15 | return nil 16 | } 17 | migrated = true 18 | 19 | return migrate.Apply(db, []migrate.Migration{ 20 | {Id: "createRepositoryTable", Migration: createRepositoryTable}, 21 | {Id: "createAuthorizedKeysTable", Migration: createAuthorizedKeysTable}, 22 | {Id: "createRepositoryPermissionTable", Migration: createRepositoryPermissionTable}, 23 | {Id: "createSinceTable", Migration: createSinceTable}, 24 | }) 25 | } 26 | 27 | func createRepositoryTable(tx *sql.Tx) error { 28 | 29 | _, err := fsql.Exec(tx, "CREATE TABLE Repository (OwnerPubKey TEXT,RepositoryName TEXT,PublicRead INTEGER,PublicWrite INTEGER,UpdatedAt INTEGER, PRIMARY KEY (OwnerPubKey,RepositoryName))") 30 | return err 31 | } 32 | 33 | func createAuthorizedKeysTable(tx *sql.Tx) error { 34 | 35 | _, err := fsql.Exec(tx, "CREATE TABLE AuthorizedKeys (PubKey TEXT,SshKey TEXT,UpdatedAt INTEGER, PRIMARY KEY (PubKey))") 36 | return err 37 | } 38 | 39 | func createRepositoryPermissionTable(tx *sql.Tx) error { 40 | 41 | _, err := fsql.Exec(tx, "CREATE TABLE RepositoryPermission (OwnerPubKey TEXT,RepositoryName TEXT,TargetPubKey TEXT,Permission TEXT,UpdatedAt INTEGER, PRIMARY KEY (OwnerPubKey,RepositoryName,TargetPubKey))") 42 | return err 43 | } 44 | 45 | func createSinceTable(tx *sql.Tx) error { 46 | 47 | _, err := fsql.Exec(tx, "CREATE TABLE Since (Kind INTEGER,UpdatedAt INTEGER, PRIMARY KEY (Kind))") 48 | return err 49 | } 50 | -------------------------------------------------------------------------------- /bridge/repo.go: -------------------------------------------------------------------------------- 1 | package bridge 2 | 3 | import "strings" 4 | 5 | func IsValidRepoName(repoName string) bool { 6 | return len(repoName) > 0 && !strings.ContainsAny(repoName, " /.") 7 | } 8 | -------------------------------------------------------------------------------- /cmd/git-nostr-bridge/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "log" 7 | "os" 8 | "time" 9 | 10 | "github.com/nbd-wtf/go-nostr" 11 | "github.com/spearson78/gitnostr" 12 | "github.com/spearson78/gitnostr/bridge" 13 | "github.com/spearson78/gitnostr/protocol" 14 | ) 15 | 16 | func getSshKeyPubKeys(db *sql.DB) ([]string, error) { 17 | 18 | var sshKeyPubKeys []string 19 | rows, err := db.Query("SELECT DISTINCT(TargetPubKey) FROM RepositoryPermission") 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | for rows.Next() { 25 | var targetPubKey string 26 | err := rows.Scan(&targetPubKey) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | sshKeyPubKeys = append(sshKeyPubKeys, targetPubKey) 32 | } 33 | 34 | return sshKeyPubKeys, nil 35 | 36 | } 37 | 38 | func connectNostr(relays []string) (*nostr.RelayPool, error) { 39 | 40 | pool := nostr.NewRelayPool() 41 | 42 | for _, relay := range relays { 43 | cherr := pool.Add(relay, nostr.SimplePolicy{ 44 | Read: true, 45 | Write: false, 46 | }) 47 | err := <-cherr 48 | if err != nil { 49 | log.Printf("relay connect failed : %v\n", err) 50 | } 51 | } 52 | 53 | relayConnected := false 54 | pool.Relays.Range(func(key string, r *nostr.Relay) bool { 55 | relayConnected = true 56 | return false 57 | }) 58 | if !relayConnected { 59 | return nil, fmt.Errorf("no relays connected") 60 | } 61 | 62 | go func() { 63 | for notice := range pool.Notices { 64 | log.Printf("notice: %s '%s'\n", notice.Relay, notice.Message) 65 | } 66 | }() 67 | 68 | return pool, nil 69 | } 70 | 71 | func updateSince(kind int, updatedAt int64, db *sql.DB) error { 72 | _, err := db.Exec("INSERT INTO Since (Kind,UpdatedAt) VALUES (?,?) ON CONFLICT DO UPDATE SET UpdatedAt=? WHERE UpdatedAt 1 && os.Args[1] == "license" { 106 | fmt.Println(gitnostr.Licenses) 107 | os.Exit(0) 108 | } 109 | 110 | cfg, err := bridge.LoadConfig("~/.config/git-nostr") 111 | if err != nil { 112 | log.Fatal(err) 113 | } 114 | 115 | db, err := bridge.OpenDb(cfg.DbFile) 116 | if err != nil { 117 | log.Fatal(err) 118 | } 119 | defer db.Close() 120 | 121 | sshDir, err := gitnostr.ResolvePath("~/.ssh") 122 | if err != nil { 123 | log.Fatal(err) 124 | } 125 | os.MkdirAll(sshDir, 0700) 126 | 127 | err = updateAuthorizedKeys(db) 128 | if err != nil { 129 | log.Fatal(err) 130 | } 131 | 132 | sshKeyPubKeys, err := getSshKeyPubKeys(db) 133 | if err != nil { 134 | log.Fatal(err) 135 | } 136 | 137 | for { 138 | pool, err := connectNostr(cfg.Relays) 139 | if err != nil { 140 | log.Fatal(err) 141 | } 142 | 143 | since, err := getSince(db) 144 | if err != nil { 145 | log.Fatal(err) 146 | } 147 | 148 | _, gitNostrEvents := pool.Sub(nostr.Filters{ 149 | { 150 | Authors: cfg.GitRepoOwners, 151 | Kinds: []int{protocol.KindRepository, protocol.KindRepositoryPermission}, 152 | Since: since[protocol.KindRepository], 153 | }, 154 | { 155 | Authors: sshKeyPubKeys, 156 | Kinds: []int{protocol.KindSshKey}, 157 | Since: since[protocol.KindSshKey], 158 | }, 159 | }) 160 | 161 | exit: 162 | for event := range nostr.Unique(gitNostrEvents) { 163 | switch event.Kind { 164 | case protocol.KindRepository: 165 | err := handleRepositoryEvent(event, db, cfg) 166 | if err != nil { 167 | log.Println(err) 168 | continue 169 | } 170 | 171 | err = updateSince(protocol.KindRepository, event.CreatedAt.Unix(), db) 172 | if err != nil { 173 | log.Println(err) 174 | continue 175 | } 176 | 177 | case protocol.KindSshKey: 178 | err := handleSshKeyEvent(event, db, cfg) 179 | if err != nil { 180 | log.Println(err) 181 | continue 182 | } 183 | 184 | err = updateSince(protocol.KindSshKey, event.CreatedAt.Unix(), db) 185 | if err != nil { 186 | log.Println(err) 187 | continue 188 | } 189 | 190 | case protocol.KindRepositoryPermission: 191 | err := handleRepositorPermission(event, db, cfg) 192 | if err != nil { 193 | log.Println(err) 194 | continue 195 | } 196 | 197 | err = updateSince(protocol.KindRepository, event.CreatedAt.Unix(), db) //Permissions are queried in the same filter as KindRepository 198 | if err != nil { 199 | log.Println(err) 200 | continue 201 | } 202 | 203 | newSshKeyPubKeys, err := getSshKeyPubKeys(db) 204 | if err != nil { 205 | log.Println(err) 206 | continue 207 | } 208 | 209 | if len(newSshKeyPubKeys) != len(sshKeyPubKeys) { 210 | sshKeyPubKeys = newSshKeyPubKeys 211 | //There doesn't seem to be a function to cancel the subscription and resubscribe so I have to reconnect 212 | pool.Relays.Range(func(key string, value *nostr.Relay) bool { 213 | pool.Remove(key) 214 | value.Close() 215 | return true 216 | }) 217 | break exit 218 | } 219 | 220 | } 221 | } 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /cmd/git-nostr-bridge/repo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io/fs" 9 | "log" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | 14 | "github.com/nbd-wtf/go-nostr" 15 | "github.com/spearson78/gitnostr" 16 | "github.com/spearson78/gitnostr/bridge" 17 | "github.com/spearson78/gitnostr/protocol" 18 | ) 19 | 20 | func handleRepositoryEvent(event nostr.Event, db *sql.DB, cfg bridge.Config) error { 21 | 22 | var repo protocol.Repository 23 | err := json.Unmarshal([]byte(event.Content), &repo) 24 | if err != nil { 25 | return fmt.Errorf("malformed repository: %w : %v", err, event.Content) 26 | } 27 | 28 | if !bridge.IsValidRepoName(repo.RepositoryName) { 29 | return fmt.Errorf("invalid repository name: %v", repo.RepositoryName) 30 | } 31 | 32 | updatedAt := event.CreatedAt.Unix() 33 | res, err := db.Exec("INSERT INTO Repository (OwnerPubKey,RepositoryName,PublicRead,PublicWrite,UpdatedAt) VALUES (?,?,?,?,?) ON CONFLICT DO UPDATE SET PublicRead=?,PublicWrite=?,UpdatedAt=? WHERE UpdatedAt 1 && os.Args[1] == "license" { 67 | fmt.Println(gitnostr.Licenses) 68 | os.Exit(0) 69 | } 70 | 71 | cfg, err := LoadConfig("~/.config/git-nostr") 72 | if err != nil { 73 | log.Fatal(err) 74 | } 75 | 76 | pool, err := connectNostr(cfg.Relays) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | pool.SecretKey = &cfg.PrivateKey 81 | 82 | //Doesn't seem to be supported by any relays 83 | //advertiseRelays(pool, cfg.Relays) 84 | 85 | cmd := os.Args[1] 86 | switch cmd { 87 | case "repo": 88 | subcmd := os.Args[2] 89 | switch subcmd { 90 | case "create": 91 | repoCreate(cfg, pool) 92 | case "clone": 93 | repoClone(cfg, pool) 94 | case "permission": 95 | repoPermission(cfg, pool) 96 | default: 97 | log.Fatalf("unknown repo sub command %v", subcmd) 98 | } 99 | case "ssh-key": 100 | subcmd := os.Args[2] 101 | switch subcmd { 102 | case "add": 103 | sshKeyAdd(cfg, pool) 104 | default: 105 | log.Fatalf("unknown repo sub command %v", subcmd) 106 | } 107 | default: 108 | log.Fatalf("unknown command %v", cmd) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /cmd/git-nostr-cli/repo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "flag" 7 | "fmt" 8 | "log" 9 | "os" 10 | "os/exec" 11 | "strings" 12 | "time" 13 | 14 | "github.com/nbd-wtf/go-nostr" 15 | "github.com/spearson78/gitnostr" 16 | "github.com/spearson78/gitnostr/protocol" 17 | ) 18 | 19 | func repoCreate(cfg Config, pool *nostr.RelayPool) { 20 | flags := flag.NewFlagSet("repo create", flag.ContinueOnError) 21 | 22 | publicRead := flags.Bool("public-read", true, "repository will be readable by all users") 23 | publicWrite := flags.Bool("public-write", false, "repository will be writeable by all users") 24 | 25 | flags.Parse(os.Args[3:]) 26 | 27 | repoName := flags.Args()[0] 28 | 29 | log.Println("repo create --public-read=", *publicRead, " --public-write=", *publicWrite, " ", repoName) 30 | 31 | repoJson, err := json.Marshal(protocol.Repository{ 32 | RepositoryName: repoName, 33 | PublicRead: *publicRead, 34 | PublicWrite: *publicWrite, 35 | GitSshBase: cfg.GitSshBase, 36 | }) 37 | if err != nil { 38 | log.Fatal("repo marshal :", err) 39 | } 40 | 41 | var tags nostr.Tags 42 | _, statuses, err := pool.PublishEvent(&nostr.Event{ 43 | CreatedAt: time.Now(), 44 | Kind: protocol.KindRepository, 45 | Tags: tags, 46 | Content: string(repoJson), 47 | }) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 53 | defer cancel() 54 | 55 | publishSuccess := false 56 | 57 | for { 58 | select { 59 | case <-ctx.Done(): 60 | if !publishSuccess { 61 | fmt.Printf("repository was not published") 62 | os.Exit(1) 63 | } 64 | return 65 | case status := <-statuses: 66 | switch status.Status { 67 | case nostr.PublishStatusSent: 68 | publishSuccess = true 69 | fmt.Printf("published repository to '%s'.\n", status.Relay) 70 | case nostr.PublishStatusFailed: 71 | fmt.Printf("failed to publish repository to '%s'.\n", status.Relay) 72 | case nostr.PublishStatusSucceeded: 73 | publishSuccess = true 74 | fmt.Printf("published repository to '%s'.\n", status.Relay) 75 | } 76 | } 77 | } 78 | } 79 | 80 | func repoPermission(cfg Config, pool *nostr.RelayPool) { 81 | 82 | targetPubKey, err := gitnostr.ResolveHexPubKey(os.Args[4]) 83 | if err != nil { 84 | log.Fatal(err) 85 | } 86 | 87 | permJson, err := json.Marshal(protocol.RepositoryPermission{ 88 | RepositoryName: os.Args[3], 89 | TargetPubKey: targetPubKey, 90 | Permission: os.Args[5], 91 | }) 92 | 93 | if err != nil { 94 | log.Fatal("permission marshal :", err) 95 | } 96 | 97 | var tags nostr.Tags 98 | _, statuses, err := pool.PublishEvent(&nostr.Event{ 99 | CreatedAt: time.Now(), 100 | Kind: protocol.KindRepositoryPermission, 101 | Tags: tags, 102 | Content: string(permJson), 103 | }) 104 | if err != nil { 105 | log.Fatal(err) 106 | } 107 | 108 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 109 | defer cancel() 110 | 111 | publishSuccess := false 112 | 113 | for { 114 | select { 115 | case <-ctx.Done(): 116 | if !publishSuccess { 117 | fmt.Printf("permission was not published") 118 | os.Exit(1) 119 | } 120 | return 121 | case status := <-statuses: 122 | switch status.Status { 123 | case nostr.PublishStatusSent: 124 | publishSuccess = true 125 | fmt.Printf("published permission to '%s'.\n", status.Relay) 126 | case nostr.PublishStatusFailed: 127 | fmt.Printf("failed to publish permission to '%s'.\n", status.Relay) 128 | case nostr.PublishStatusSucceeded: 129 | publishSuccess = true 130 | fmt.Printf("published permission to '%s'.\n", status.Relay) 131 | } 132 | } 133 | } 134 | 135 | } 136 | 137 | func repoClone(cfg Config, pool *nostr.RelayPool) { 138 | 139 | repoParam := os.Args[3] 140 | // steve@localhost:public 141 | 142 | split := strings.SplitN(repoParam, ":", 2) 143 | 144 | name := split[0] 145 | repoName := split[1] 146 | 147 | identifier, err := gitnostr.ResolveHexPubKey(name) 148 | if err != nil { 149 | log.Fatal(err) 150 | } 151 | 152 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 153 | defer cancel() 154 | 155 | _, subchan := pool.Sub(nostr.Filters{{Kinds: []int{protocol.KindRepository}, Authors: []string{identifier}}}) 156 | 157 | var pubKey string 158 | var repository protocol.Repository 159 | 160 | for { 161 | select { 162 | case <-ctx.Done(): 163 | if pubKey != "" { 164 | log.Println("git", "clone", repository.GitSshBase+":"+pubKey+"/"+repoName) 165 | cmd := exec.Command("git", "clone", repository.GitSshBase+":"+pubKey+"/"+repoName) 166 | cmd.Stdout = os.Stdout 167 | cmd.Stdin = os.Stdin 168 | cmd.Stderr = os.Stderr 169 | err := cmd.Run() 170 | if err != nil { 171 | log.Fatal(err) 172 | } 173 | } else { 174 | log.Fatal("Repo not found") 175 | } 176 | 177 | return 178 | case event := <-subchan: 179 | var checkRepo protocol.Repository 180 | 181 | err := json.Unmarshal([]byte(event.Event.Content), &checkRepo) 182 | if err != nil { 183 | log.Println("Failed to parse repository.") 184 | } 185 | 186 | if checkRepo.RepositoryName == repoName { 187 | repository = checkRepo 188 | pubKey = event.Event.PubKey 189 | } 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /cmd/git-nostr-cli/sshkey.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "strings" 11 | "time" 12 | 13 | "github.com/nbd-wtf/go-nostr" 14 | "github.com/spearson78/gitnostr/protocol" 15 | ) 16 | 17 | func sshKeyAdd(cfg Config, pool *nostr.RelayPool) { 18 | 19 | flags := flag.NewFlagSet("ssh-key add", flag.ContinueOnError) 20 | 21 | title := flags.String("title", "", "override the title from the key file") 22 | 23 | flags.Parse(os.Args[3:]) 24 | 25 | keyFilePath := flags.Arg(0) 26 | 27 | keyData, err := ioutil.ReadFile(keyFilePath) 28 | if err != nil { 29 | log.Fatalf("read key file : %v", err) 30 | } 31 | 32 | split := strings.Split(string(keyData), " ") 33 | if len(split) != 3 { 34 | log.Fatal("key file parse error") 35 | } 36 | 37 | if *title != "" { 38 | split[2] = *title 39 | } 40 | 41 | var tags nostr.Tags 42 | _, statuses, err := pool.PublishEvent(&nostr.Event{ 43 | CreatedAt: time.Now(), 44 | Kind: protocol.KindSshKey, 45 | Tags: tags, 46 | Content: strings.Join(split, " "), 47 | }) 48 | if err != nil { 49 | log.Fatal(err) 50 | } 51 | 52 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 53 | defer cancel() 54 | 55 | publishSuccess := false 56 | 57 | for { 58 | select { 59 | case <-ctx.Done(): 60 | if !publishSuccess { 61 | fmt.Printf("ssh-key was not added") 62 | os.Exit(1) 63 | } 64 | return 65 | case status := <-statuses: 66 | switch status.Status { 67 | case nostr.PublishStatusSent: 68 | publishSuccess = true 69 | fmt.Printf("ssh-key added to '%s'.\n", status.Relay) 70 | case nostr.PublishStatusFailed: 71 | fmt.Printf("failed to add ssh-key to '%s'.\n", status.Relay) 72 | case nostr.PublishStatusSucceeded: 73 | publishSuccess = true 74 | fmt.Printf("ssh-key added to '%s'.\n", status.Relay) 75 | } 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /cmd/git-nostr-ssh/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/hex" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strings" 12 | 13 | "github.com/spearson78/gitnostr" 14 | "github.com/spearson78/gitnostr/bridge" 15 | ) 16 | 17 | func isReadAllowed(rights *string) bool { 18 | return rights != nil && (*rights == "ADMIN" || *rights == "READ" || *rights == "WRITE") 19 | } 20 | 21 | func isWriteAllowed(rights *string) bool { 22 | return rights != nil && (*rights == "ADMIN" || *rights == "WRITE") 23 | } 24 | 25 | func isAdminAllowed(rights *string) bool { 26 | return rights != nil && (*rights == "ADMIN") 27 | } 28 | 29 | func main() { 30 | if len(os.Args) > 1 && os.Args[1] == "license" { 31 | fmt.Println(gitnostr.Licenses) 32 | os.Exit(1) 33 | } 34 | 35 | if len(os.Args) != 2 { 36 | fmt.Fprintln(os.Stderr, "interactive login not allowed") 37 | os.Exit(1) 38 | } 39 | 40 | targetPubKey := os.Args[1] 41 | 42 | sshCommand := os.Getenv("SSH_ORIGINAL_COMMAND") 43 | if sshCommand == "" { 44 | fmt.Fprintln(os.Stderr, "interactive login not allowed") 45 | os.Exit(1) 46 | } 47 | 48 | cfg, err := bridge.LoadConfig("~/.config/git-nostr") 49 | if err != nil { 50 | fmt.Fprintln(os.Stderr, "config error :", err) 51 | os.Exit(1) 52 | } 53 | 54 | split := strings.SplitN(sshCommand, " ", 2) 55 | verb := split[0] 56 | repoParam := strings.Trim(split[1], "'") 57 | repoSplit := strings.SplitN(repoParam, "/", 2) 58 | if len(repoSplit) != 2 { 59 | fmt.Fprintf(os.Stderr, "repository name missing / : %v", repoParam) 60 | os.Exit(1) 61 | } 62 | 63 | ownerPubKey := repoSplit[0] 64 | _, err = hex.DecodeString(ownerPubKey) 65 | if err != nil { 66 | fmt.Fprintln(os.Stderr, "invalid repository pubkey", repoParam) 67 | os.Exit(1) 68 | } 69 | 70 | repoName := repoSplit[1] 71 | if !bridge.IsValidRepoName(repoName) { 72 | fmt.Fprintln(os.Stderr, "invalid repository name", repoName) 73 | os.Exit(1) 74 | } 75 | 76 | reposDir, err := gitnostr.ResolvePath(cfg.RepositoryDir) 77 | if err != nil { 78 | fmt.Fprintln(os.Stderr, "config error") 79 | os.Exit(1) 80 | } 81 | 82 | repoParentPath := filepath.Join(reposDir, ownerPubKey) 83 | 84 | repoPath := filepath.Join(repoParentPath, repoName+".git") 85 | _, err = os.Stat(repoPath) 86 | if err != nil { 87 | fmt.Fprintln(os.Stderr, "repository not found") 88 | os.Exit(1) 89 | } 90 | 91 | db, err := bridge.OpenDb(cfg.DbFile) 92 | if err != nil { 93 | fmt.Fprintln(os.Stderr, "config error db") 94 | os.Exit(1) 95 | } 96 | defer db.Close() 97 | 98 | row := db.QueryRow("SELECT Repository.PublicRead,Repository.PublicWrite,RepositoryPermission.Permission FROM Repository LEFT OUTER JOIN RepositoryPermission ON Repository.OwnerPubKey=RepositoryPermission.OwnerPubKey AND Repository.RepositoryName=RepositoryPermission.RepositoryName AND TargetPubKey=? WHERE Repository.OwnerPubKey=? AND Repository.RepositoryName=?", targetPubKey, ownerPubKey, repoName) 99 | 100 | var publicRead bool 101 | var publicWrite bool 102 | var permission *string 103 | err = row.Scan(&publicRead, &publicWrite, &permission) 104 | if err != nil { 105 | if errors.Is(err, sql.ErrNoRows) { 106 | //ignore 107 | } else { 108 | fmt.Fprintln(os.Stderr, "permission error") 109 | os.Exit(1) 110 | } 111 | } 112 | 113 | row = db.QueryRow("SELECT PublicRead,PublicWrite FROM RepositoryPermission WHERE OwnerPubKey=? AND RepositoryName=? AND TargetPubKey=?", ownerPubKey, repoName, targetPubKey) 114 | 115 | switch verb { 116 | case "git-upload-pack": 117 | if !publicRead && !isReadAllowed(permission) { 118 | fmt.Fprintln(os.Stderr, "permission denied") 119 | os.Exit(1) 120 | } 121 | case "git-receive-pack": 122 | if !publicWrite && !isWriteAllowed(permission) { 123 | fmt.Fprintln(os.Stderr, "permission denied") 124 | os.Exit(1) 125 | } 126 | default: 127 | if !isAdminAllowed(permission) { 128 | fmt.Fprintln(os.Stderr, "permission denied") 129 | os.Exit(1) 130 | } 131 | } 132 | 133 | c := exec.Command("git", "shell", "-c", verb+" '"+repoPath+"'") 134 | c.Stdout = os.Stdout 135 | c.Stdin = os.Stdin 136 | c.Stderr = os.Stderr 137 | 138 | err = c.Run() 139 | if err != nil { 140 | fmt.Fprintln(os.Stderr, "git error:", err) 141 | if e := (&exec.ExitError{}); errors.As(err, &e) { 142 | os.Exit(e.ExitCode()) 143 | } else { 144 | os.Exit(1) 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /git-nostr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spearson78/gitnostr/2cfca0e64c2270bf7f1086c66db810c453fea187/git-nostr.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/spearson78/gitnostr 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/spearson78/fsql v0.0.3 7 | github.com/spearson78/migrate v0.0.7 8 | modernc.org/sqlite v1.19.4 9 | ) 10 | 11 | require ( 12 | github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 // indirect 13 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 // indirect 14 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 // indirect 15 | github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect 16 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect 17 | github.com/gorilla/websocket v1.4.2 // indirect 18 | github.com/spearson78/fault v0.4.4-floc // indirect 19 | github.com/valyala/fastjson v1.6.3 // indirect 20 | golang.org/x/exp v0.0.0-20221106115401-f9659909a136 // indirect 21 | ) 22 | 23 | require ( 24 | github.com/google/uuid v1.3.0 // indirect 25 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect 26 | github.com/mattn/go-isatty v0.0.16 // indirect 27 | github.com/nbd-wtf/go-nostr v0.9.0 28 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect 29 | golang.org/x/mod v0.6.0 // indirect 30 | golang.org/x/sys v0.1.0 // indirect 31 | golang.org/x/tools v0.2.0 // indirect 32 | lukechampine.com/uint128 v1.2.0 // indirect 33 | modernc.org/cc/v3 v3.40.0 // indirect 34 | modernc.org/ccgo/v3 v3.16.13 // indirect 35 | modernc.org/libc v1.21.4 // indirect 36 | modernc.org/mathutil v1.5.0 // indirect 37 | modernc.org/memory v1.4.0 // indirect 38 | modernc.org/opt v0.1.3 // indirect 39 | modernc.org/strutil v1.1.3 // indirect 40 | modernc.org/token v1.0.1 // indirect 41 | ) 42 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8 h1:Xa6tp8DPDhdV+k23uiTC/GrAYOe4IdyJVKtob4KW3GA= 2 | github.com/SaveTheRbtz/generic-sync-map-go v0.0.0-20220414055132-a37292614db8/go.mod h1:ihkm1viTbO/LOsgdGoFPBSvzqvx7ibvkMzYp3CgtHik= 3 | github.com/btcsuite/btcd/btcec/v2 v2.2.0 h1:fzn1qaOt32TuLjFlkzYSsBC35Q3KUjT1SwPxiMSCF5k= 4 | github.com/btcsuite/btcd/btcec/v2 v2.2.0/go.mod h1:U7MHm051Al6XmscBQ0BoNydpOTsFAn707034b5nY8zU= 5 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= 6 | github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= 9 | github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= 10 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= 11 | github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs= 12 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 13 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 14 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 15 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 16 | github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= 17 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 18 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 19 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 20 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 21 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 22 | github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= 23 | github.com/nbd-wtf/go-nostr v0.9.0 h1:6wDsgKUXOtnzHwe/RW80yBebsVNg5jbRyBCTyxlDjiw= 24 | github.com/nbd-wtf/go-nostr v0.9.0/go.mod h1:qFFTIxh15H5GGN0WsBI/P73DteqsevnhSEW/yk8nEf4= 25 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 26 | github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 27 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= 28 | github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= 29 | github.com/spearson78/fault v0.4.4-floc h1:okWJwfrMbH5UglLDwQkkyeOESULEqxJVcDTizskU2LE= 30 | github.com/spearson78/fault v0.4.4-floc/go.mod h1:+jQardX+c7/re7zDsIgRWs3RGulfo4J+ztavTJpSdDo= 31 | github.com/spearson78/fsql v0.0.3 h1:jpfXAcbn95sT3gEhGUEFuqdo2AkgX/VtEfjqfhPq4Fw= 32 | github.com/spearson78/fsql v0.0.3/go.mod h1:/s8pwGCbtorYZDRueWtdVytnEdsKahZqHP2zT/Agu20= 33 | github.com/spearson78/migrate v0.0.7 h1:lByiqiFhTKAXu/TMTldU+TOQYRSjHcrRY16VXRII6ok= 34 | github.com/spearson78/migrate v0.0.7/go.mod h1:P8ZOra8nFve/pkEqVprVgdf1rxKGSdza0sQbnQZSVuk= 35 | github.com/valyala/fastjson v1.6.3 h1:tAKFnnwmeMGPbwJ7IwxcTPCNr3uIzoIj3/Fh90ra4xc= 36 | github.com/valyala/fastjson v1.6.3/go.mod h1:CLCAqky6SMuOcxStkYQvblddUtoRxhYMGLrsQns1aXY= 37 | golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw= 38 | golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= 39 | golang.org/x/mod v0.6.0 h1:b9gGHsz9/HhJ3HF5DHQytPpuwocVTChQJK3AvoLRD5I= 40 | golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= 41 | golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0= 42 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 43 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 44 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 45 | golang.org/x/tools v0.2.0 h1:G6AHpWxTMGY1KyEYoAQ5WTtIekUUvDNjan3ugu60JvE= 46 | golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= 47 | lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= 48 | lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= 49 | modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= 50 | modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= 51 | modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= 52 | modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= 53 | modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk= 54 | modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM= 55 | modernc.org/libc v1.21.4 h1:CzTlumWeIbPV5/HVIMzYHNPCRP8uiU/CWiN2gtd/Qu8= 56 | modernc.org/libc v1.21.4/go.mod h1:przBsL5RDOZajTVslkugzLBj1evTue36jEomFQOoYuI= 57 | modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= 58 | modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= 59 | modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= 60 | modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= 61 | modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= 62 | modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= 63 | modernc.org/sqlite v1.19.4 h1:nlPIDqumn6/mSvs7T5C8MNYEuN73sISzPdKtMdURpUI= 64 | modernc.org/sqlite v1.19.4/go.mod h1:x/yZNb3h5+I3zGQSlwIv4REL5eJhiRkUH5MReogAeIc= 65 | modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= 66 | modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= 67 | modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34= 68 | modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= 69 | modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= 70 | modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE= 71 | -------------------------------------------------------------------------------- /license.go: -------------------------------------------------------------------------------- 1 | package gitnostr 2 | 3 | import _ "embed" 4 | 5 | //go:embed LICENSE.md 6 | var Licenses string 7 | -------------------------------------------------------------------------------- /path.go: -------------------------------------------------------------------------------- 1 | package gitnostr 2 | 3 | import ( 4 | "fmt" 5 | "os/user" 6 | "path/filepath" 7 | "strings" 8 | ) 9 | 10 | func ResolvePath(path string) (string, error) { 11 | 12 | usr, err := user.Current() 13 | if err != nil { 14 | return path, fmt.Errorf("current user : %w", err) 15 | } 16 | homeDir := usr.HomeDir 17 | 18 | if path == "~" { 19 | return homeDir, nil 20 | } else if strings.HasPrefix(path, "~/") { 21 | return filepath.Join(homeDir, path[2:]), nil 22 | } else { 23 | return path, nil 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /protocol/kind.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | const ( 4 | KindRepositoryPermission int = 50 5 | KindRepository int = 51 6 | KindSshKey int = 52 7 | ) 8 | -------------------------------------------------------------------------------- /protocol/repository.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type Repository struct { 4 | RepositoryName string `json:"repositoryName"` 5 | PublicRead bool `json:"publicRead"` 6 | PublicWrite bool `json:"publicWrite"` 7 | GitSshBase string `json:"gitSshBase"` 8 | } 9 | -------------------------------------------------------------------------------- /protocol/repositorypermission.go: -------------------------------------------------------------------------------- 1 | package protocol 2 | 3 | type RepositoryPermission struct { 4 | RepositoryName string `json:"repositoryName"` 5 | TargetPubKey string `json:"targetPubKey"` 6 | Permission string `json:"permission"` 7 | } 8 | -------------------------------------------------------------------------------- /pubkey.go: -------------------------------------------------------------------------------- 1 | package gitnostr 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | "github.com/nbd-wtf/go-nostr/nip05" 10 | ) 11 | 12 | func resolveNip05(name string) string { 13 | if name == "steve@localhost" { 14 | return "e0e7807d354ea7662412d99856335e1923b0b57b6668575bf320837f6b1816e3" 15 | } 16 | 17 | identifier := nip05.QueryIdentifier(name) 18 | 19 | return identifier 20 | } 21 | 22 | func ResolveHexPubKey(pubKeyStr string) (string, error) { 23 | 24 | if strings.Contains(pubKeyStr, "@") { 25 | resolved := resolveNip05(pubKeyStr) 26 | if resolved == "" { 27 | return "", fmt.Errorf("couldnot resolve nip05 pub key %v", pubKeyStr) 28 | } else { 29 | log.Println(pubKeyStr, "->", resolved) 30 | return resolved, nil 31 | } 32 | } else { 33 | _, err := hex.DecodeString(pubKeyStr) 34 | return pubKeyStr, err 35 | } 36 | } 37 | --------------------------------------------------------------------------------