├── .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 |
2 |
3 | # ram-dl
4 |
5 | A tool to download more RAM (yes, seriously!)
6 |
7 |
8 |
9 | [](https://github.com/pojntfx/ram-dl/actions/workflows/hydrun.yaml)
10 | [](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 |
--------------------------------------------------------------------------------