├── .github └── workflows │ └── hydrun.yaml ├── .gitignore ├── Hydrunfile ├── LICENSE ├── Makefile ├── README.md ├── cmd ├── ram-dl │ └── main.go └── ram-ul │ └── main.go ├── docs └── icon.svg ├── go.mod └── go.sum /.github/workflows/hydrun.yaml: -------------------------------------------------------------------------------- 1 | name: hydrun CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: "0 0 * * 0" 8 | 9 | jobs: 10 | build-linux: 11 | runs-on: ${{ matrix.target.runner }} 12 | permissions: 13 | contents: read 14 | strategy: 15 | matrix: 16 | target: 17 | # Tests 18 | - id: test 19 | src: . 20 | os: golang:bookworm 21 | flags: -e '-v /tmp/ccache:/root/.cache/go-build' 22 | cmd: GOFLAGS="-short" ./Hydrunfile test 23 | dst: out/nonexistent 24 | runner: ubuntu-latest 25 | 26 | # Binaries 27 | - id: go.ram-dl 28 | src: . 29 | os: golang:bookworm 30 | flags: -e '-v /tmp/ccache:/root/.cache/go-build' 31 | cmd: ./Hydrunfile go ram-dl 32 | dst: out/* 33 | runner: ubuntu-latest 34 | - id: go.ram-ul 35 | src: . 36 | os: golang:bookworm 37 | flags: -e '-v /tmp/ccache:/root/.cache/go-build' 38 | cmd: ./Hydrunfile go ram-ul 39 | dst: out/* 40 | runner: ubuntu-latest 41 | 42 | steps: 43 | - name: Checkout 44 | uses: actions/checkout@v4 45 | - name: Restore ccache 46 | uses: actions/cache/restore@v4 47 | with: 48 | path: | 49 | /tmp/ccache 50 | key: cache-ccache-${{ matrix.target.id }} 51 | - name: Set up QEMU 52 | uses: docker/setup-qemu-action@v3 53 | - name: Set up Docker Buildx 54 | uses: docker/setup-buildx-action@v3 55 | - name: Set up hydrun 56 | run: | 57 | curl -L -o /tmp/hydrun "https://github.com/pojntfx/hydrun/releases/latest/download/hydrun.linux-$(uname -m)" 58 | sudo install /tmp/hydrun /usr/local/bin 59 | - name: Build with hydrun 60 | working-directory: ${{ matrix.target.src }} 61 | run: hydrun -o ${{ matrix.target.os }} ${{ matrix.target.flags }} "${{ matrix.target.cmd }}" 62 | - name: Fix permissions for output 63 | run: sudo chown -R $USER . 64 | - name: Save ccache 65 | uses: actions/cache/save@v4 66 | with: 67 | path: | 68 | /tmp/ccache 69 | key: cache-ccache-${{ matrix.target.id }} 70 | - name: Upload output 71 | uses: actions/upload-artifact@v4 72 | with: 73 | name: ${{ matrix.target.id }} 74 | path: ${{ matrix.target.dst }} 75 | 76 | publish-linux: 77 | runs-on: ubuntu-latest 78 | permissions: 79 | contents: write 80 | needs: build-linux 81 | 82 | steps: 83 | - name: Checkout 84 | uses: actions/checkout@v4 85 | - name: Download output 86 | uses: actions/download-artifact@v4 87 | with: 88 | path: /tmp/out 89 | - name: Extract branch name 90 | id: extract_branch 91 | run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})" 92 | - name: Publish pre-release to GitHub releases 93 | if: ${{ github.ref == 'refs/heads/main' }} 94 | uses: softprops/action-gh-release@v2 95 | with: 96 | tag_name: release-${{ steps.extract_branch.outputs.branch }} 97 | prerelease: true 98 | files: | 99 | /tmp/out/*/* 100 | - name: Publish release to GitHub releases 101 | if: startsWith(github.ref, 'refs/tags/v') 102 | uses: softprops/action-gh-release@v2 103 | with: 104 | prerelease: false 105 | files: | 106 | /tmp/out/*/* 107 | 108 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | -------------------------------------------------------------------------------- /Hydrunfile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # Test 6 | if [ "$1" = "test" ]; then 7 | # Configure Git 8 | git config --global --add safe.directory '*' 9 | 10 | # Generate dependencies 11 | make depend 12 | 13 | # Run tests 14 | make test 15 | 16 | exit 0 17 | fi 18 | 19 | # Go 20 | if [ "$1" = "go" ]; then 21 | # Install native dependencies 22 | apt update 23 | apt install -y curl make 24 | 25 | # Install bagop 26 | curl -L -o /tmp/bagop "https://github.com/pojntfx/bagop/releases/latest/download/bagop.linux-$(uname -m)" 27 | install /tmp/bagop /usr/local/bin 28 | 29 | # Configure Git 30 | git config --global --add safe.directory '*' 31 | 32 | # Generate dependencies 33 | make depend 34 | 35 | # Build 36 | CGO_ENABLED=0 bagop -j "$(nproc)" -b "$2" -x '(aix/*|android/*|darwin/*|dragonfly/*|freebsd/*|illumos/*|ios/*|js/*|linux/386|linux/arm|linux/arm64|linux/loong64|linux/mips|linux/mips64|linux/mips64le|linux/mipsle|linux/ppc64|linux/ppc64le|linux/riscv64|linux/s390x|netbsd/*|openbsd/*|plan9/*|solaris/*|windows/*|wasip1/wasm)' -p "make build/$2 DST=\$DST" -d out 37 | 38 | exit 0 39 | fi 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Public variables 2 | DESTDIR ?= 3 | PREFIX ?= /usr/local 4 | OUTPUT_DIR ?= out 5 | DST ?= 6 | 7 | # Private variables 8 | obj = ram-dl ram-ul 9 | all: $(addprefix build/,$(obj)) 10 | 11 | # Build 12 | build: $(addprefix build/,$(obj)) 13 | $(addprefix build/,$(obj)): 14 | ifdef DST 15 | go build -o $(DST) ./cmd/$(subst build/,,$@) 16 | else 17 | go build -o $(OUTPUT_DIR)/$(subst build/,,$@) ./cmd/$(subst build/,,$@) 18 | endif 19 | 20 | # Install 21 | install: $(addprefix install/,$(obj)) 22 | $(addprefix install/,$(obj)): 23 | install -D -m 0755 $(OUTPUT_DIR)/$(subst install/,,$@) $(DESTDIR)$(PREFIX)/bin/$(subst install/,,$@) 24 | 25 | # Uninstall 26 | uninstall: $(addprefix uninstall/,$(obj)) 27 | $(addprefix uninstall/,$(obj)): 28 | rm $(DESTDIR)$(PREFIX)/bin/$(subst uninstall/,,$@) 29 | 30 | # Run 31 | $(addprefix run/,$(obj)): 32 | $(subst run/,,$@) $(ARGS) 33 | 34 | # Test 35 | test: 36 | go test -timeout 3600s -parallel $(shell nproc) ./... 37 | 38 | # Benchmark 39 | benchmark: 40 | go test -timeout 3600s -bench=./... ./... 41 | 42 | # Clean 43 | clean: 44 | rm -rf out 45 | 46 | # Dependencies 47 | depend: 48 | true -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Project icon 2 | 3 | # ram-dl 4 | 5 | A tool to download more RAM (yes, seriously!) 6 | 7 |
8 | 9 | [![hydrun CI](https://github.com/pojntfx/ram-dl/actions/workflows/hydrun.yaml/badge.svg)](https://github.com/pojntfx/ram-dl/actions/workflows/hydrun.yaml) 10 | [![Matrix](https://img.shields.io/matrix/go-nbd:matrix.org)](https://matrix.to/#/#ram-dl:matrix.org?via=matrix.org) 11 | 12 | ## Overview 13 | 14 | `ram-dl` is a tool that allows you to **upload** and **download** RAM to/from your system. Well, sort of :P 15 | 16 | It is mostly intended as a tech demo for [r3map](https://github.com/pojntfx/r3map), a library for efficient distributed `mmap` with mounting and migration support, and [go-nbd](https://github.com/pojntfx/go-nbd), a pure Go NBD server and client library. Despite this it can however also be of use for a few experimental use cases, such as: 17 | 18 | - **Extending a system's memory capacity over the network**: By leveraging the fast [fRPC](https://frpc.io/) RPC framework, you can use `ram-dl` and `ram-ul` to expand your local system's RAM (effectively **"downloading RAM"**) without having to use local disk space or memory. 19 | - **Mounting a remote system's memory**: By allocating a memory region with `ram-ul` (effectively **"uploading RAM"**), it is possible to mount a remote system's RAM to your local system with minimal latency. 20 | - **Getting insights into memory usage and contents**: By inspecting the "downloaded"/mounted memory with `ram-ul`, you can get insights into the contents of a remote system's memory. 21 | 22 | `ram-dl` achieves this by essentially doing the following: 23 | 24 | 1. Starting a `go-nbd` server _and_ client locally with r3map's [Direct Mount API](https://pkg.go.dev/github.com/pojntfx/r3map@main/pkg/mount) 25 | 2. Connecting the NBD _server_ to a remote fRPC backend provided by `ram-ul` 26 | 3. Calling `mkswap`, `swapon` and `swapoff`, which enables paging out to the block device provided by the NBD client and thus to the connected remote fRPC backend. 27 | 28 | For most real-world use cases, using [r3map](https://github.com/pojntfx/r3map)'s [Managed Mount API](https://pkg.go.dev/github.com/pojntfx/r3map@main/pkg/mount) or [Migration API](https://pkg.go.dev/github.com/pojntfx/r3map@main/pkg/migration) directly is the better (and much faster) option, but if you just want to see the [Download More RAM](https://knowyourmeme.com/memes/download-more-ram) meme implemented in real life, you've come to the right place! 29 | 30 | ## Installation 31 | 32 | Static binaries are available on [GitHub releases](https://github.com/pojntfx/ram-dl/releases). 33 | 34 | On Linux, you can install them like so: 35 | 36 | ```shell 37 | $ curl -L -o /tmp/ram-dl "https://github.com/pojntfx/ram-dl/releases/latest/download/ram-dl.linux-$(uname -m)" 38 | $ curl -L -o /tmp/ram-ul "https://github.com/pojntfx/ram-dl/releases/latest/download/ram-ul.linux-$(uname -m)" 39 | $ sudo install /tmp/ram-dl /usr/local/bin 40 | $ sudo install /tmp/ram-ul /usr/local/bin 41 | ``` 42 | 43 | You can find binaries for more architectures on [GitHub releases](https://github.com/pojntfx/ram-dl/releases). 44 | 45 | ## Tutorial 46 | 47 | > TL;DR: "Upload" RAM with `ram-ul`, "download" the RAM with `ram-dl`, done! 48 | 49 | ### 1. Upload RAM 50 | 51 | On a remote (or local) system, first start `ram-ul`. This component exposes a memory region, file or directory as a fRPC server: 52 | 53 | ```shell 54 | $ ram-ul --size 4294967296 55 | 2023/06/30 14:52:12 Listening on :1337 56 | ``` 57 | 58 | ### 2. Download RAM 59 | 60 | On your local system, start `ram-dl`. This will mount the remote system's exposed memory region, file or directory using fRPC and r3map as swap space, and umount it as soon as you interrupt the app: 61 | 62 | ```shell 63 | $ sudo modprobe nbd 64 | $ sudo ram-dl --raddr localhost:1337 65 | 2023/06/30 14:54:22 Connected to localhost:1337 66 | 2023/06/30 14:54:22 Ready on /dev/nbd0 67 | ``` 68 | 69 | This should give you an extra 4GB of local memory/swap space, without using up significant local memory (or disk space): 70 | 71 | ```shell 72 | # Before 73 | $ free -h 74 | total used free shared buff/cache available 75 | Mem: 30Gi 7.9Gi 6.5Gi 721Mi 16Gi 21Gi 76 | Swap: 8.0Gi 0B 8.0Gi 77 | 78 | # After 79 | $ free -h 80 | total used free shared buff/cache available 81 | Mem: 30Gi 7.9Gi 6.5Gi 717Mi 16Gi 21Gi 82 | Swap: 11Gi 0B 11Gi 83 | ``` 84 | 85 | 🚀 **That's it!** We hope you have fun using `ram-dl`, and if you're interested in more like this, be sure to check out [r3map](https://github.com/pojntfx/r3map)! 86 | 87 | ## Reference 88 | 89 | ### ram-dl 90 | 91 | ```shell 92 | $ ram-dl --help 93 | Usage of ram-dl: 94 | -chunk-size int 95 | Chunk size to use (default 4096) 96 | -chunking 97 | Whether the backend requires to be interfaced with in fixed chunks (default true) 98 | -raddr string 99 | Remote address for the fRPC r3map backend server (default "localhost:1337") 100 | -size int 101 | Size of the memory region or file to allocate (default 4294967296) 102 | -verbose 103 | Whether to enable verbose logging 104 | ``` 105 | 106 | ### ram-ul 107 | 108 | ```shell 109 | $ ram-ul --help 110 | Usage of ram-ul: 111 | -addr string 112 | Listen address (default ":1337") 113 | -backend string 114 | Backend to use (one of [file memory directory]) (default "file") 115 | -chunk-size int 116 | Chunk size to use (default 4096) 117 | -chunking 118 | Whether the backend requires to be interfaced with in fixed chunks in tests (default true) 119 | -location string 120 | Backend's directory (for directory backend) or file (for file backend) (default "/tmp/ram-ul") 121 | -size int 122 | Size of the memory region or file to allocate (default 4294967296) 123 | -verbose 124 | Whether to enable verbose logging 125 | ``` 126 | 127 | ## Acknowledgements 128 | 129 | - [pojntfx/go-bd](https://github.com/pojntfx/go-nbd) provides the Go NBD client and server. 130 | - [pojntfx/r3map](https://github.com/pojntfx/r3map) provides the device abstraction layer. 131 | - ["We ACTUALLY downloaded more RAM" by LTT](https://www.youtube.com/watch?v=minxwFqinpw) provided the original idea for this demo. 132 | 133 | ## Contributing 134 | 135 | To contribute, please use the [GitHub flow](https://guides.github.com/introduction/flow/) and follow our [Code of Conduct](./CODE_OF_CONDUCT.md). 136 | 137 | To build and start a development version of `ram-dl` locally, run the following: 138 | 139 | ```shell 140 | $ git clone https://github.com/pojntfx/ram-dl.git 141 | $ cd ram-dl 142 | $ make depend 143 | $ make && sudo make install 144 | $ sudo modprobe nbd 145 | $ ram-ul 146 | # In another terminal 147 | $ sudo ram-dl 148 | ``` 149 | 150 | Have any questions or need help? Chat with us [on Matrix](https://matrix.to/#/#ram-dl:matrix.org?via=matrix.org)! 151 | 152 | ## License 153 | 154 | ram-dl (c) 2024 Felicitas Pojtinger and contributors 155 | 156 | SPDX-License-Identifier: AGPL-3.0 157 | -------------------------------------------------------------------------------- /cmd/ram-dl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "os/signal" 10 | "sync" 11 | "syscall" 12 | 13 | "github.com/pojntfx/go-nbd/pkg/backend" 14 | "github.com/pojntfx/go-nbd/pkg/server" 15 | v1frpc "github.com/pojntfx/r3map/pkg/api/frpc/mount/v1" 16 | lbackend "github.com/pojntfx/r3map/pkg/backend" 17 | "github.com/pojntfx/r3map/pkg/chunks" 18 | "github.com/pojntfx/r3map/pkg/mount" 19 | "github.com/pojntfx/r3map/pkg/services" 20 | "github.com/pojntfx/r3map/pkg/utils" 21 | ) 22 | 23 | func main() { 24 | raddr := flag.String("raddr", "localhost:1337", "Remote address for the fRPC r3map backend server") 25 | 26 | size := flag.Int64("size", 4096*1024*1024, "Size of the memory region or file to allocate") 27 | chunkSize := flag.Int64("chunk-size", 4096, "Chunk size to use") 28 | 29 | chunking := flag.Bool("chunking", true, "Whether the backend requires to be interfaced with in fixed chunks") 30 | 31 | verbose := flag.Bool("verbose", false, "Whether to enable verbose logging") 32 | 33 | flag.Parse() 34 | 35 | ctx, cancel := context.WithCancel(context.Background()) 36 | defer cancel() 37 | 38 | client, err := v1frpc.NewClient(nil, nil) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | if err := client.Connect(*raddr); err != nil { 44 | panic(err) 45 | } 46 | defer client.Close() 47 | 48 | log.Println("Connected to", *raddr) 49 | 50 | devPath, err := utils.FindUnusedNBDDevice() 51 | if err != nil { 52 | panic(err) 53 | } 54 | 55 | devFile, err := os.Open(devPath) 56 | if err != nil { 57 | panic(err) 58 | } 59 | defer devFile.Close() 60 | 61 | var b backend.Backend 62 | b = lbackend.NewRPCBackend( 63 | ctx, 64 | &services.BackendRemote{ 65 | ReadAt: func(ctx context.Context, length int, off int64) (r services.ReadAtResponse, err error) { 66 | res, err := client.Backend.ReadAt(ctx, &v1frpc.ComPojtingerFelicitasR3MapMountV1ReadAtArgs{ 67 | Length: int32(length), 68 | Off: off, 69 | }) 70 | if err != nil { 71 | return services.ReadAtResponse{}, err 72 | } 73 | 74 | return services.ReadAtResponse{ 75 | N: int(res.N), 76 | P: res.P, 77 | }, err 78 | }, 79 | WriteAt: func(context context.Context, p []byte, off int64) (n int, err error) { 80 | res, err := client.Backend.WriteAt(ctx, &v1frpc.ComPojtingerFelicitasR3MapMountV1WriteAtArgs{ 81 | Off: off, 82 | P: p, 83 | }) 84 | if err != nil { 85 | return 0, err 86 | } 87 | 88 | return int(res.Length), nil 89 | }, 90 | Sync: func(context context.Context) error { 91 | if _, err := client.Backend.Sync(ctx, &v1frpc.ComPojtingerFelicitasR3MapMountV1SyncArgs{}); err != nil { 92 | return err 93 | } 94 | 95 | return nil 96 | }, 97 | }, 98 | *size, 99 | *verbose, 100 | ) 101 | 102 | if *chunking { 103 | b = lbackend.NewReaderAtBackend( 104 | chunks.NewArbitraryReadWriterAt( 105 | chunks.NewChunkedReadWriterAt( 106 | b, *chunkSize, *size / *chunkSize), 107 | *chunkSize, 108 | ), 109 | (b).Size, 110 | (b).Sync, 111 | false, 112 | ) 113 | } 114 | 115 | dev := mount.NewDirectPathMount( 116 | b, 117 | devFile, 118 | 119 | &server.Options{ 120 | MinimumBlockSize: uint32(*chunkSize), 121 | PreferredBlockSize: uint32(*chunkSize), 122 | MaximumBlockSize: uint32(*chunkSize), 123 | }, 124 | nil, 125 | ) 126 | 127 | var ( 128 | errs = make(chan error) 129 | wg sync.WaitGroup 130 | ) 131 | defer wg.Wait() 132 | 133 | wg.Add(1) 134 | go func() { 135 | defer wg.Done() 136 | 137 | for err := range errs { 138 | if err != nil { 139 | panic(err) 140 | } 141 | } 142 | }() 143 | 144 | go func() { 145 | if err := dev.Wait(); err != nil { 146 | errs <- err 147 | 148 | return 149 | } 150 | 151 | close(errs) 152 | }() 153 | 154 | defer dev.Close() 155 | if err := dev.Open(); err != nil { 156 | panic(err) 157 | } 158 | 159 | if output, err := exec.Command("mkswap", devPath).CombinedOutput(); err != nil { 160 | log.Printf("Could not create swap partition: %s", output) 161 | 162 | panic(err) 163 | } 164 | 165 | if output, err := exec.Command("swapon", devPath).CombinedOutput(); err != nil { 166 | log.Printf("Could not enable partition for swap: %s", output) 167 | 168 | panic(err) 169 | } 170 | 171 | defer func() { 172 | syscall.Sync() 173 | 174 | if output, err := exec.Command("swapoff", devPath).CombinedOutput(); err != nil { 175 | log.Printf("Could not enable partition for swap: %s", output) 176 | 177 | panic(err) 178 | } 179 | }() 180 | 181 | log.Println("Ready on", devPath) 182 | 183 | ch := make(chan os.Signal, 1) 184 | signal.Notify(ch, os.Interrupt, syscall.SIGTERM) 185 | 186 | <-ch 187 | 188 | if *verbose { 189 | log.Println("Gracefully shutting down") 190 | } 191 | 192 | go func() { 193 | <-ch 194 | 195 | log.Println("Forcefully exiting") 196 | 197 | os.Exit(1) 198 | }() 199 | } 200 | -------------------------------------------------------------------------------- /cmd/ram-ul/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "os" 9 | "path/filepath" 10 | 11 | "github.com/pojntfx/go-nbd/pkg/backend" 12 | v1frpc "github.com/pojntfx/r3map/pkg/api/frpc/mount/v1" 13 | lbackend "github.com/pojntfx/r3map/pkg/backend" 14 | "github.com/pojntfx/r3map/pkg/chunks" 15 | "github.com/pojntfx/r3map/pkg/services" 16 | "github.com/pojntfx/r3map/pkg/utils" 17 | ) 18 | 19 | const ( 20 | backendTypeFile = "file" 21 | backendTypeMemory = "memory" 22 | backendTypeDirectory = "directory" 23 | ) 24 | 25 | var ( 26 | knownBackendTypes = []string{backendTypeFile, backendTypeMemory, backendTypeDirectory} 27 | 28 | errUnknownBackend = errors.New("unknown backend") 29 | ) 30 | 31 | func main() { 32 | laddr := flag.String("addr", ":1337", "Listen address") 33 | 34 | size := flag.Int64("size", 4096*1024*1024, "Size of the memory region or file to allocate") 35 | chunkSize := flag.Int64("chunk-size", 4096, "Chunk size to use") 36 | 37 | bck := flag.String( 38 | "backend", 39 | backendTypeFile, 40 | fmt.Sprintf( 41 | "Backend to use (one of %v)", 42 | knownBackendTypes, 43 | ), 44 | ) 45 | location := flag.String("location", filepath.Join(os.TempDir(), "ram-ul"), "Backend's directory (for directory backend) or file (for file backend)") 46 | chunking := flag.Bool("chunking", true, "Whether the backend requires to be interfaced with in fixed chunks in tests") 47 | 48 | verbose := flag.Bool("verbose", false, "Whether to enable verbose logging") 49 | 50 | flag.Parse() 51 | 52 | var b backend.Backend 53 | switch *bck { 54 | case backendTypeMemory: 55 | b = backend.NewMemoryBackend(make([]byte, *size)) 56 | 57 | case backendTypeFile: 58 | if err := os.MkdirAll(filepath.Dir(*location), os.ModePerm); err != nil { 59 | panic(err) 60 | } 61 | 62 | if err := os.RemoveAll(*location); err != nil { 63 | panic(err) 64 | } 65 | 66 | file, err := os.Create(*location) 67 | if err != nil { 68 | panic(err) 69 | } 70 | defer os.RemoveAll(file.Name()) 71 | 72 | if err := file.Truncate(*size); err != nil { 73 | panic(err) 74 | } 75 | 76 | b = backend.NewFileBackend(file) 77 | 78 | case backendTypeDirectory: 79 | if err := os.RemoveAll(*location); err != nil { 80 | panic(err) 81 | } 82 | 83 | if err := os.MkdirAll(*location, os.ModePerm); err != nil { 84 | panic(err) 85 | } 86 | 87 | b = lbackend.NewDirectoryBackend(*location, *size, *chunkSize, 512, false) 88 | default: 89 | panic(errUnknownBackend) 90 | } 91 | 92 | if *chunking { 93 | b = lbackend.NewReaderAtBackend( 94 | chunks.NewArbitraryReadWriterAt( 95 | chunks.NewChunkedReadWriterAt( 96 | b, *chunkSize, *size / *chunkSize), 97 | *chunkSize, 98 | ), 99 | (b).Size, 100 | (b).Sync, 101 | false, 102 | ) 103 | } 104 | 105 | errs := make(chan error) 106 | server, err := v1frpc.NewServer(services.NewBackendServiceFrpc(services.NewBackend(b, *verbose, *chunkSize)), nil, nil) 107 | if err != nil { 108 | panic(err) 109 | } 110 | 111 | log.Println("Listening on", *laddr) 112 | 113 | go func() { 114 | if err := server.Start(*laddr); err != nil { 115 | if !utils.IsClosedErr(err) { 116 | errs <- err 117 | } 118 | 119 | return 120 | } 121 | }() 122 | 123 | for err := range errs { 124 | if err != nil { 125 | panic(err) 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/pojntfx/ram-dl 2 | 3 | go 1.21.4 4 | 5 | toolchain go1.22.5 6 | 7 | require ( 8 | github.com/pojntfx/go-nbd v0.3.2 9 | github.com/pojntfx/r3map v0.1.1 10 | ) 11 | 12 | require ( 13 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 14 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 15 | github.com/dustin/go-humanize v1.0.1 // indirect 16 | github.com/edsrzf/mmap-go v1.1.0 // indirect 17 | github.com/go-ini/ini v1.67.0 // indirect 18 | github.com/goccy/go-json v0.10.3 // indirect 19 | github.com/gocql/gocql v1.6.0 // indirect 20 | github.com/golang/snappy v0.0.4 // indirect 21 | github.com/google/uuid v1.6.0 // indirect 22 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed // indirect 23 | github.com/klauspost/compress v1.17.9 // indirect 24 | github.com/klauspost/cpuid/v2 v2.2.8 // indirect 25 | github.com/loopholelabs/common v0.4.9 // indirect 26 | github.com/loopholelabs/frisbee-go v0.8.1 // indirect 27 | github.com/loopholelabs/polyglot/v2 v2.0.1 // indirect 28 | github.com/mattn/go-colorable v0.1.13 // indirect 29 | github.com/mattn/go-isatty v0.0.20 // indirect 30 | github.com/minio/md5-simd v1.1.2 // indirect 31 | github.com/minio/minio-go/v7 v7.0.74 // indirect 32 | github.com/pilebones/go-udev v0.9.0 // indirect 33 | github.com/redis/go-redis/v9 v9.6.1 // indirect 34 | github.com/rs/xid v1.5.0 // indirect 35 | github.com/rs/zerolog v1.33.0 // indirect 36 | go.uber.org/atomic v1.11.0 // indirect 37 | golang.org/x/crypto v0.25.0 // indirect 38 | golang.org/x/net v0.27.0 // indirect 39 | golang.org/x/sys v0.22.0 // indirect 40 | golang.org/x/text v0.16.0 // indirect 41 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f // indirect 42 | google.golang.org/grpc v1.65.0 // indirect 43 | google.golang.org/protobuf v1.34.2 // indirect 44 | gopkg.in/inf.v0 v0.9.1 // indirect 45 | ) 46 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932 h1:mXoPYz/Ul5HYEDvkta6I8/rnYM5gSdSV2tJ6XbZuEtY= 2 | github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= 3 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY= 4 | github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= 5 | github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= 6 | github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= 7 | github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= 8 | github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= 9 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 10 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 11 | github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 14 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= 16 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= 17 | github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= 18 | github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= 19 | github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= 20 | github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= 21 | github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= 22 | github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= 23 | github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= 24 | github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 25 | github.com/gocql/gocql v1.6.0 h1:IdFdOTbnpbd0pDhl4REKQDM+Q0SzKXQ1Yh+YZZ8T/qU= 26 | github.com/gocql/gocql v1.6.0/go.mod h1:3gM2c4D3AnkISwBxGnMMsS8Oy4y2lhbPRsH4xnJrHG8= 27 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 28 | github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 29 | github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= 30 | github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 31 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 32 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 33 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 34 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 35 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8= 36 | github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= 37 | github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= 38 | github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= 39 | github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 40 | github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= 41 | github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= 42 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 43 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 44 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 45 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 46 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 47 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 48 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 49 | github.com/loopholelabs/common v0.4.9 h1:9MPUYlZZ/qx3Kt8LXgXxcSXthrM91od8026c4DlGpAU= 50 | github.com/loopholelabs/common v0.4.9/go.mod h1:Wop5srN1wYT+mdQ9gZ+kn2I9qKAyVd0FB48pThwIa9M= 51 | github.com/loopholelabs/frisbee-go v0.8.1 h1:muH8PhvyYz2jd0+T7c48L+FhYUGblkAhlbJNFKh3jLg= 52 | github.com/loopholelabs/frisbee-go v0.8.1/go.mod h1:A2sNAaojm71/ZqA/VzJvRksReOt4tAfY1/bh6XKdGzQ= 53 | github.com/loopholelabs/polyglot/v2 v2.0.1 h1:JVSrXwx3jzr6/IrUe7Y1f7R3I6bFaVjLlPNLjJlmQaU= 54 | github.com/loopholelabs/polyglot/v2 v2.0.1/go.mod h1:kFoSKvnKAWmV0ICfbaCHDv/+cz5LSuA+xXG4WtYV/z4= 55 | github.com/loopholelabs/testing v0.2.3 h1:4nVuK5ctaE6ua5Z0dYk2l7xTFmcpCYLUeGjRBp8keOA= 56 | github.com/loopholelabs/testing v0.2.3/go.mod h1:gqtGY91soYD1fQoKQt/6kP14OYpS7gcbcIgq5mc9m8Q= 57 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 58 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 59 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 60 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 61 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 62 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 63 | github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= 64 | github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= 65 | github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0= 66 | github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= 67 | github.com/pilebones/go-udev v0.9.0 h1:N1uEO/SxUwtIctc0WLU0t69JeBxIYEYnj8lT/Nabl9Q= 68 | github.com/pilebones/go-udev v0.9.0/go.mod h1:T2eI2tUSK0hA2WS5QLjXJUfQkluZQu+18Cqvem3CaXI= 69 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 70 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 71 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 72 | github.com/pojntfx/go-nbd v0.3.2 h1:qI6S4qsHD87V9fTH6jiS4DIqq/rWmI0El0xSToMUDeg= 73 | github.com/pojntfx/go-nbd v0.3.2/go.mod h1:SehHnbi2e8NiSAKby42Itm8SIoS7b+wAprsfPH3qgYk= 74 | github.com/pojntfx/r3map v0.1.1 h1:TVQq8wLSyMeAG26dBn7gCfrWQ7QFmdTrpzB2T3ey588= 75 | github.com/pojntfx/r3map v0.1.1/go.mod h1:s5igwpEUo9MrukFp1mpcqg3j/jpJL+/CI0Sr8miiVLc= 76 | github.com/redis/go-redis/v9 v9.6.1 h1:HHDteefn6ZkTtY5fGUE8tj8uy85AHk6zP7CpzIAM0y4= 77 | github.com/redis/go-redis/v9 v9.6.1/go.mod h1:0C0c6ycQsdpVNQpxb1njEQIqkx5UcsM8FJCQLgE9+RA= 78 | github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= 79 | github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= 80 | github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= 81 | github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= 82 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 83 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 84 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 85 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 86 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 87 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 88 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 89 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 90 | golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 91 | golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= 92 | golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= 93 | golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= 94 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 95 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 96 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 97 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 98 | golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= 99 | golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 100 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= 101 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= 102 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f h1:RARaIm8pxYuxyNPbBQf5igT7XdOyCNtat1qAT2ZxjU4= 103 | google.golang.org/genproto/googleapis/rpc v0.0.0-20240725223205-93522f1f2a9f/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY= 104 | google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= 105 | google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= 106 | google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= 107 | google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= 108 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 109 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 110 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 111 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 112 | --------------------------------------------------------------------------------