mirror of
https://github.com/pterodactyl/wings.git
synced 2025-12-10 14:18:11 -06:00
Compare commits
96 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae6c62905b | ||
|
|
e4f139a656 | ||
|
|
b75ab17fe2 | ||
|
|
149b796849 | ||
|
|
503b87c240 | ||
|
|
60cc3ad30c | ||
|
|
daefc0de79 | ||
|
|
d9b1675a59 | ||
|
|
ee922588e0 | ||
|
|
7bb7696307 | ||
|
|
695b2eaf86 | ||
|
|
e76dc74ddb | ||
|
|
0d9b0863f9 | ||
|
|
326db212e9 | ||
|
|
798cbe567d | ||
|
|
8b65328390 | ||
|
|
746c7afbfb | ||
|
|
c93a1da141 | ||
|
|
06a3167754 | ||
|
|
ccae513224 | ||
|
|
56bb2ce298 | ||
|
|
d1f2c4e641 | ||
|
|
b6f07bc959 | ||
|
|
b7d442ecc0 | ||
|
|
a94d4ec5e5 | ||
|
|
35d2e0f040 | ||
|
|
d2b4e983f5 | ||
|
|
25aaf127f6 | ||
|
|
cb391bcbb8 | ||
|
|
2a94eb0af5 | ||
|
|
ae1192f760 | ||
|
|
3855eecb60 | ||
|
|
3ea9e6991a | ||
|
|
9533f4f04a | ||
|
|
0bc3df9306 | ||
|
|
a4e16748b0 | ||
|
|
3d90ac5909 | ||
|
|
74fd19c86f | ||
|
|
cecdc8c612 | ||
|
|
489af48eb9 | ||
|
|
104fe1aace | ||
|
|
81e8c9a8c4 | ||
|
|
9ff918bf0b | ||
|
|
d5097e5f59 | ||
|
|
e311206d6b | ||
|
|
f18726c874 | ||
|
|
49b04fe9bb | ||
|
|
9535aae52e | ||
|
|
4b4e8f8fa0 | ||
|
|
91e016249c | ||
|
|
59905a6b69 | ||
|
|
d49607de0e | ||
|
|
91ed7f25e1 | ||
|
|
a4ff433d95 | ||
|
|
7f63162d1d | ||
|
|
d0e7332881 | ||
|
|
5e9a1c7139 | ||
|
|
407b783aa5 | ||
|
|
25966e7838 | ||
|
|
77153ffbb1 | ||
|
|
c6c235dbc0 | ||
|
|
a55277da47 | ||
|
|
76a9f6dc5a | ||
|
|
0e96ef3edf | ||
|
|
1dee350268 | ||
|
|
ec14db6142 | ||
|
|
c83875ffc0 | ||
|
|
b4dc19fca1 | ||
|
|
a2be26574c | ||
|
|
8045318e44 | ||
|
|
024fe548ed | ||
|
|
82ae64b4c6 | ||
|
|
f221cde754 | ||
|
|
63c4c1ce57 | ||
|
|
b4a9a1c5de | ||
|
|
7daaaaac18 | ||
|
|
708cdd0ba8 | ||
|
|
adb2b26ae0 | ||
|
|
6c9d3670c8 | ||
|
|
18306badaf | ||
|
|
34bd2b54e5 | ||
|
|
d023c97334 | ||
|
|
8744e64f1d | ||
|
|
3b88bbc7aa | ||
|
|
d739948989 | ||
|
|
ac260bd5ee | ||
|
|
2f4a0d7262 | ||
|
|
1d8b383682 | ||
|
|
934bf2493d | ||
|
|
29e4425e21 | ||
|
|
5a15612754 | ||
|
|
ad1ae862a9 | ||
|
|
3114a3b82e | ||
|
|
500f217514 | ||
|
|
9ffbcdcdb1 | ||
|
|
9b341db2db |
21
.editorconfig
Normal file
21
.editorconfig
Normal file
@ -0,0 +1,21 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
tab_width = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.go]
|
||||
max_line_length = 100
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{md,nix,yaml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
tab_width = 2
|
||||
9
.envrc
9
.envrc
@ -1 +1,8 @@
|
||||
use flake
|
||||
#!/usr/bin/env sh
|
||||
|
||||
# Load the flake's `devShells.${currentSystem}.default`.
|
||||
if ! use flake .; then
|
||||
echo 'The development shell was unable to be built.' >&2
|
||||
echo 'The development environment was not loaded.' >&2
|
||||
echo 'Please make the necessary changes in flake.nix to fix any issues and hit enter to try again.' >&2
|
||||
fi
|
||||
|
||||
10
.github/dependabot.yaml
vendored
Normal file
10
.github/dependabot.yaml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: github-actions
|
||||
directory: /
|
||||
schedule:
|
||||
interval: monthly
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
schedule:
|
||||
interval: weekly
|
||||
28
.github/workflows/codeql.yaml
vendored
28
.github/workflows/codeql.yaml
vendored
@ -13,30 +13,26 @@ on:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: go
|
||||
build-mode: autobuild
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language:
|
||||
- go
|
||||
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee # v3.29.5
|
||||
|
||||
25
.github/workflows/docker.yaml
vendored
25
.github/workflows/docker.yaml
vendored
@ -11,18 +11,21 @@ on:
|
||||
jobs:
|
||||
build-and-push:
|
||||
name: Build and Push
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
# Always run against a tag, even if the commit into the tag has [docker skip] within the commit message.
|
||||
if: "!contains(github.ref, 'develop') || (!contains(github.event.head_commit.message, 'skip docker') && !contains(github.event.head_commit.message, 'docker skip'))"
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Docker metadata
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f # v5.8.0
|
||||
with:
|
||||
images: ghcr.io/pterodactyl/wings
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
flavor: |
|
||||
latest=false
|
||||
tags: |
|
||||
@ -31,17 +34,17 @@ jobs:
|
||||
type=ref,event=branch
|
||||
|
||||
- name: Setup QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
|
||||
|
||||
- name: Setup Docker buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Get Build Information
|
||||
id: build_info
|
||||
@ -50,7 +53,7 @@ jobs:
|
||||
echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and Push (tag)
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
if: "github.event_name == 'release' && github.event.action == 'published'"
|
||||
with:
|
||||
context: .
|
||||
@ -63,7 +66,7 @@ jobs:
|
||||
tags: ${{ steps.docker_meta.outputs.tags }}
|
||||
|
||||
- name: Build and Push (develop)
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||
if: "github.event_name == 'push' && contains(github.ref, 'develop')"
|
||||
with:
|
||||
context: .
|
||||
|
||||
27
.github/workflows/push.yaml
vendored
27
.github/workflows/push.yaml
vendored
@ -15,20 +15,21 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-22.04]
|
||||
go: ["1.21.9", "1.22.2"]
|
||||
os: [ubuntu-24.04]
|
||||
go: ["1.23.7", "1.24.1"]
|
||||
goos: [linux]
|
||||
goarch: [amd64, arm64]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: go mod download
|
||||
env:
|
||||
CGO_ENABLED: 0
|
||||
@ -42,8 +43,8 @@ jobs:
|
||||
CGO_ENABLED: 0
|
||||
SRC_PATH: github.com/pterodactyl/wings
|
||||
run: |
|
||||
go build -v -trimpath -ldflags="-s -w -X ${SRC_PATH}/system.Version=dev-${GIT_COMMIT:0:7}" -o dist/wings ${SRC_PATH}
|
||||
go build -v -trimpath -ldflags="-X ${SRC_PATH}/system.Version=dev-${GIT_COMMIT:0:7}" -o dist/wings_debug ${SRC_PATH}
|
||||
go build -v -trimpath -ldflags="-s -w -X ${SRC_PATH}/system.Version=dev-${GITHUB_SHA:0:7}" -o dist/wings ${SRC_PATH}
|
||||
go build -v -trimpath -ldflags="-X ${SRC_PATH}/system.Version=dev-${GITHUB_SHA:0:7}" -o dist/wings_debug ${SRC_PATH}
|
||||
chmod 755 dist/*
|
||||
|
||||
- name: go test
|
||||
@ -61,15 +62,15 @@ jobs:
|
||||
go test -race $(go list ./...)
|
||||
|
||||
- name: Upload Release Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.21.8' }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.23.7' }}
|
||||
with:
|
||||
name: wings_linux_${{ matrix.goarch }}
|
||||
path: dist/wings
|
||||
|
||||
- name: Upload Debug Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.21.8' }}
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
if: ${{ (github.ref == 'refs/heads/develop' || github.event_name == 'pull_request') && matrix.go == '1.23.7' }}
|
||||
with:
|
||||
name: wings_linux_${{ matrix.goarch }}_debug
|
||||
path: dist/wings_debug
|
||||
|
||||
48
.github/workflows/release.yaml
vendored
48
.github/workflows/release.yaml
vendored
@ -8,16 +8,18 @@ on:
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
permissions:
|
||||
contents: write # write is required to create releases and push.
|
||||
|
||||
steps:
|
||||
- name: Code Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Code checkout
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@44694675825211faa026b3c33043df3e48a5fa00 # v6.0.0
|
||||
with:
|
||||
go-version: "1.21.9"
|
||||
go-version: "1.23.7"
|
||||
|
||||
- name: Build release binaries
|
||||
env:
|
||||
@ -57,41 +59,13 @@ jobs:
|
||||
git push
|
||||
|
||||
- name: Create release
|
||||
id: create_release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@6da8fa9354ddfdc4aeace5fc48d7f679b5214090 # v2.4.1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ contains(github.ref, 'rc') || contains(github.ref, 'beta') || contains(github.ref, 'alpha') }}
|
||||
body_path: ./RELEASE_CHANGELOG
|
||||
|
||||
- name: Upload amd64 binary
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: dist/wings_linux_amd64
|
||||
asset_name: wings_linux_amd64
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload arm64 binary
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: dist/wings_linux_arm64
|
||||
asset_name: wings_linux_arm64
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload checksum
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
||||
asset_path: ./checksums.txt
|
||||
asset_name: checksums.txt
|
||||
asset_content_type: text/plain
|
||||
files: |
|
||||
dist/*
|
||||
checksums.txt
|
||||
|
||||
10
CHANGELOG.md
10
CHANGELOG.md
@ -1,5 +1,15 @@
|
||||
# Changelog
|
||||
|
||||
## v1.11.14
|
||||
|
||||
### Added
|
||||
|
||||
* Support relative file paths for the Wings config ([#180](https://github.com/pterodactyl/wings/pull/180))
|
||||
|
||||
### Fixed
|
||||
|
||||
* Folders not being sorted before files properly ([#5078](https://github.com/pterodactyl/panel/issues/5078)
|
||||
|
||||
## v1.11.13
|
||||
|
||||
### Fixed
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@ -1,8 +1,8 @@
|
||||
# Stage 1 (Build)
|
||||
FROM golang:1.21.9-alpine AS builder
|
||||
FROM golang:1.23.7-alpine AS builder
|
||||
|
||||
ARG VERSION
|
||||
RUN apk add --update --no-cache git make
|
||||
RUN apk add --update --no-cache git make mailcap
|
||||
WORKDIR /app/
|
||||
COPY go.mod go.sum /app/
|
||||
RUN go mod download
|
||||
@ -18,8 +18,11 @@ RUN echo "ID=\"distroless\"" > /etc/os-release
|
||||
# Stage 2 (Final)
|
||||
FROM gcr.io/distroless/static:latest
|
||||
COPY --from=builder /etc/os-release /etc/os-release
|
||||
COPY --from=builder /etc/mime.types /etc/mime.types
|
||||
|
||||
COPY --from=builder /app/wings /usr/bin/
|
||||
CMD [ "/usr/bin/wings", "--config", "/etc/pterodactyl/config.yml" ]
|
||||
|
||||
EXPOSE 8080
|
||||
ENTRYPOINT ["/usr/bin/wings"]
|
||||
CMD ["--config", "/etc/pterodactyl/config.yml"]
|
||||
|
||||
EXPOSE 8080 2022
|
||||
|
||||
16
README.md
16
README.md
@ -18,14 +18,14 @@ dependencies, and allowing users to authenticate with the same credentials they
|
||||
I would like to extend my sincere thanks to the following sponsors for helping fund Pterodactyl's development.
|
||||
[Interested in becoming a sponsor?](https://github.com/sponsors/matthewpi)
|
||||
|
||||
| Company | About |
|
||||
|--------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||
| [**HostEZ**](https://hostez.io) | US & EU Rust & Minecraft Hosting. DDoS Protected bare metal, VPS and colocation with low latency, high uptime and maximum availability. EZ! |
|
||||
| [**Blueprint**](https://blueprint.zip/?pterodactyl=true) | Create and install Pterodactyl addons and themes with the growing Blueprint framework - the package-manager for Pterodactyl. Use multiple modifications at once without worrying about conflicts and make use of the large extension ecosystem. |
|
||||
| [**indifferent broccoli**](https://indifferentbroccoli.com/) | indifferent broccoli is a game server hosting and rental company. With us, you get top-notch computer power for your gaming sessions. We destroy lag, latency, and complexity--letting you focus on the fun stuff. |
|
||||
| Company | About |
|
||||
|-----------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| [**Aussie Server Hosts**](https://aussieserverhosts.com/) | No frills Australian Owned and operated High Performance Server hosting for some of the most demanding games serving Australia and New Zealand. |
|
||||
| [**BisectHosting**](https://www.bisecthosting.com/) | BisectHosting provides Minecraft, Valheim and other server hosting services with the highest reliability and lightning fast support since 2012. |
|
||||
| [**MineStrator**](https://minestrator.com/) | Looking for the most highend French hosting company for your minecraft server? More than 24,000 members on our discord trust us. Give us a try! |
|
||||
| [**HostEZ**](https://hostez.io) | US & EU Rust & Minecraft Hosting. DDoS Protected bare metal, VPS and colocation with low latency, high uptime and maximum availability. EZ! |
|
||||
| [**Blueprint**](https://blueprint.zip/?utm_source=pterodactyl&utm_medium=sponsor) | Create and install Pterodactyl addons and themes with the growing Blueprint framework - the package-manager for Pterodactyl. Use multiple modifications at once without worrying about conflicts and make use of the large extension ecosystem. |
|
||||
| [**indifferent broccoli**](https://indifferentbroccoli.com/) | indifferent broccoli is a game server hosting and rental company. With us, you get top-notch computer power for your gaming sessions. We destroy lag, latency, and complexity--letting you focus on the fun stuff. |
|
||||
|
||||
## Documentation
|
||||
|
||||
|
||||
@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -13,7 +14,6 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
|
||||
@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -17,9 +18,9 @@ import (
|
||||
"github.com/AlecAivazis/survey/v2/terminal"
|
||||
"github.com/apex/log"
|
||||
"github.com/docker/docker/api/types"
|
||||
dockersystem "github.com/docker/docker/api/types/system"
|
||||
"github.com/docker/docker/pkg/parsers/kernel"
|
||||
"github.com/docker/docker/pkg/parsers/operatingsystem"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
@ -206,18 +207,18 @@ func diagnosticsCmdRun(*cobra.Command, []string) {
|
||||
}
|
||||
}
|
||||
|
||||
func getDockerInfo() (types.Version, types.Info, error) {
|
||||
func getDockerInfo() (types.Version, dockersystem.Info, error) {
|
||||
client, err := environment.Docker()
|
||||
if err != nil {
|
||||
return types.Version{}, types.Info{}, err
|
||||
return types.Version{}, dockersystem.Info{}, err
|
||||
}
|
||||
dockerVersion, err := client.ServerVersion(context.Background())
|
||||
if err != nil {
|
||||
return types.Version{}, types.Info{}, err
|
||||
return types.Version{}, dockersystem.Info{}, err
|
||||
}
|
||||
dockerInfo, err := client.Info(context.Background())
|
||||
if err != nil {
|
||||
return types.Version{}, types.Info{}, err
|
||||
return types.Version{}, dockersystem.Info{}, err
|
||||
}
|
||||
return dockerVersion, dockerInfo, nil
|
||||
}
|
||||
|
||||
35
cmd/root.go
35
cmd/root.go
@ -13,7 +13,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/NYTimes/logrotate"
|
||||
@ -104,6 +104,7 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
|
||||
if err := config.ConfigureTimezone(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to detect system timezone or use supplied configuration value")
|
||||
return
|
||||
}
|
||||
log.WithField("timezone", config.Get().System.Timezone).Info("configured wings with system timezone")
|
||||
if err := config.ConfigureDirectories(); err != nil {
|
||||
@ -112,6 +113,11 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
}
|
||||
if err := config.EnsurePterodactylUser(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to create pterodactyl system user")
|
||||
return
|
||||
}
|
||||
if err := config.ConfigurePasswd(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to configure container passwd file")
|
||||
return
|
||||
}
|
||||
log.WithFields(log.Fields{
|
||||
"username": config.Get().System.Username,
|
||||
@ -123,9 +129,10 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
return
|
||||
}
|
||||
|
||||
t := config.Get().Token
|
||||
pclient := remote.New(
|
||||
config.Get().PanelLocation,
|
||||
remote.WithCredentials(config.Get().AuthenticationTokenId, config.Get().AuthenticationToken),
|
||||
remote.WithCredentials(t.ID, t.Token),
|
||||
remote.WithHttpClient(&http.Client{
|
||||
Timeout: time.Second * time.Duration(config.Get().RemoteQuery.Timeout),
|
||||
}),
|
||||
@ -133,19 +140,26 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
|
||||
if err := database.Initialize(); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to initialize database")
|
||||
return
|
||||
}
|
||||
|
||||
manager, err := server.NewManager(cmd.Context(), pclient)
|
||||
if err != nil {
|
||||
log.WithField("error", err).Fatal("failed to load server configurations")
|
||||
return
|
||||
}
|
||||
|
||||
if err := environment.ConfigureDocker(cmd.Context()); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to configure docker environment")
|
||||
return
|
||||
}
|
||||
|
||||
if err := config.WriteToDisk(config.Get()); err != nil {
|
||||
log.WithField("error", err).Fatal("failed to write configuration to disk")
|
||||
if !errors.Is(err, syscall.EROFS) {
|
||||
log.WithField("error", err).Error("failed to write configuration to disk")
|
||||
} else {
|
||||
log.WithField("error", err).Debug("failed to write configuration to disk")
|
||||
}
|
||||
}
|
||||
|
||||
// Just for some nice log output.
|
||||
@ -379,13 +393,14 @@ func rootCmdRun(cmd *cobra.Command, _ []string) {
|
||||
// Reads the configuration from the disk and then sets up the global singleton
|
||||
// with all the configuration values.
|
||||
func initConfig() {
|
||||
if !strings.HasPrefix(configPath, "/") {
|
||||
d, err := os.Getwd()
|
||||
if !filepath.IsAbs(configPath) {
|
||||
d, err := filepath.Abs(configPath)
|
||||
if err != nil {
|
||||
log2.Fatalf("cmd/root: could not determine directory: %s", err)
|
||||
log2.Fatalf("cmd/root: failed to get path to config file: %s", err)
|
||||
}
|
||||
configPath = path.Clean(path.Join(d, configPath))
|
||||
configPath = d
|
||||
}
|
||||
|
||||
err := config.FromFile(configPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
@ -440,18 +455,18 @@ in all copies or substantial portions of the Software.%s`), system.Version, time
|
||||
}
|
||||
|
||||
func exitWithConfigurationNotice() {
|
||||
fmt.Print(colorstring.Color(`
|
||||
fmt.Printf(colorstring.Color(`
|
||||
[_red_][white][bold]Error: Configuration File Not Found[reset]
|
||||
|
||||
Wings was not able to locate your configuration file, and therefore is not
|
||||
able to complete its boot process. Please ensure you have copied your instance
|
||||
configuration file into the default location below.
|
||||
|
||||
Default Location: /etc/pterodactyl/config.yml
|
||||
Default Location: %s
|
||||
|
||||
[yellow]This is not a bug with this software. Please do not make a bug report
|
||||
for this issue, it will be closed.[reset]
|
||||
|
||||
`))
|
||||
`), config.DefaultLocation)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
133
config/config.go
133
config/config.go
@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
@ -172,6 +173,25 @@ type SystemConfiguration struct {
|
||||
Gid int `yaml:"gid"`
|
||||
} `yaml:"user"`
|
||||
|
||||
// Passwd controls the mounting of a generated passwd files into containers started by Wings.
|
||||
Passwd struct {
|
||||
// Enable controls whether generated passwd files should be mounted into containers.
|
||||
//
|
||||
// By default this option is disabled and Wings will not mount any additional passwd
|
||||
// files into containers.
|
||||
Enable bool `yaml:"enabled" default:"false"`
|
||||
|
||||
// Directory is the directory on disk where the generated files will be stored.
|
||||
// This directory may be temporary as it will be re-created whenever Wings is started.
|
||||
//
|
||||
// This path **WILL** be both written to by Wings and mounted into containers created by
|
||||
// Wings. If you are running Wings itself in a container, this path will need to be mounted
|
||||
// into the Wings container as the exact path on the host, which should match the value
|
||||
// specified here. If you are using SELinux, you will need to make sure this file has the
|
||||
// correct SELinux context in order for containers to use it.
|
||||
Directory string `yaml:"directory" default:"/run/wings/etc"`
|
||||
} `yaml:"passwd"`
|
||||
|
||||
// The amount of time in seconds that can elapse before a server's disk space calculation is
|
||||
// considered stale and a re-check should occur. DANGER: setting this value too low can seriously
|
||||
// impact system performance and cause massive I/O bottlenecks and high CPU usage for the Wings
|
||||
@ -275,7 +295,14 @@ type ConsoleThrottles struct {
|
||||
Period uint64 `json:"line_reset_interval" yaml:"line_reset_interval" default:"100"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
ID string
|
||||
Token string
|
||||
}
|
||||
|
||||
type Configuration struct {
|
||||
Token Token `json:"-" yaml:"-"`
|
||||
|
||||
// The location from which this configuration instance was instantiated.
|
||||
path string
|
||||
|
||||
@ -348,11 +375,16 @@ func NewAtPath(path string) (*Configuration, error) {
|
||||
// will be paused until it is complete.
|
||||
func Set(c *Configuration) {
|
||||
mu.Lock()
|
||||
if _config == nil || _config.AuthenticationToken != c.AuthenticationToken {
|
||||
_jwtAlgo = jwt.NewHS256([]byte(c.AuthenticationToken))
|
||||
defer mu.Unlock()
|
||||
token := c.Token.Token
|
||||
if token == "" {
|
||||
c.Token.Token = c.AuthenticationToken
|
||||
token = c.Token.Token
|
||||
}
|
||||
if _config == nil || _config.Token.Token != token {
|
||||
_jwtAlgo = jwt.NewHS256([]byte(token))
|
||||
}
|
||||
_config = c
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// SetDebugViaFlag tracks if the application is running in debug mode because of
|
||||
@ -360,9 +392,9 @@ func Set(c *Configuration) {
|
||||
// change to the disk.
|
||||
func SetDebugViaFlag(d bool) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
_config.Debug = d
|
||||
_debugViaFlag = d
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// Get returns the global configuration instance. This is a thread-safe operation
|
||||
@ -387,8 +419,8 @@ func Get() *Configuration {
|
||||
// the global configuration.
|
||||
func Update(callback func(c *Configuration)) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
callback(_config)
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// GetJwtAlgorithm returns the in-memory JWT algorithm.
|
||||
@ -497,6 +529,37 @@ func EnsurePterodactylUser() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigurePasswd generates required passwd files for use with containers started by Wings.
|
||||
func ConfigurePasswd() error {
|
||||
passwd := _config.System.Passwd
|
||||
if !passwd.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := []byte(fmt.Sprintf(
|
||||
`root:x:0:
|
||||
container:x:%d:
|
||||
nogroup:x:65534:`,
|
||||
_config.System.User.Gid,
|
||||
))
|
||||
if err := os.WriteFile(filepath.Join(passwd.Directory, "group"), v, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write file to %s/group: %v", passwd.Directory, err)
|
||||
}
|
||||
|
||||
v = []byte(fmt.Sprintf(
|
||||
`root:x:0:0::/root:/bin/sh
|
||||
container:x:%d:%d::/home/container:/bin/sh
|
||||
nobody:x:65534:65534::/var/empty:/bin/sh
|
||||
`,
|
||||
_config.System.User.Uid,
|
||||
_config.System.User.Gid,
|
||||
))
|
||||
if err := os.WriteFile(filepath.Join(passwd.Directory, "passwd"), v, 0o644); err != nil {
|
||||
return fmt.Errorf("failed to write file to %s/passwd: %v", passwd.Directory, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromFile reads the configuration from the provided file and stores it in the
|
||||
// global singleton for this instance.
|
||||
func FromFile(path string) error {
|
||||
@ -513,6 +576,26 @@ func FromFile(path string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Token = Token{
|
||||
ID: os.Getenv("WINGS_TOKEN_ID"),
|
||||
Token: os.Getenv("WINGS_TOKEN"),
|
||||
}
|
||||
if c.Token.ID == "" {
|
||||
c.Token.ID = c.AuthenticationTokenId
|
||||
}
|
||||
if c.Token.Token == "" {
|
||||
c.Token.Token = c.AuthenticationToken
|
||||
}
|
||||
|
||||
c.Token.ID, err = Expand(c.Token.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Token.Token, err = Expand(c.Token.Token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Store this configuration in the global state.
|
||||
Set(c)
|
||||
return nil
|
||||
@ -561,6 +644,13 @@ func ConfigureDirectories() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if _config.System.Passwd.Enable {
|
||||
log.WithField("path", _config.System.Passwd.Directory).Debug("ensuring passwd directory exists")
|
||||
if err := os.MkdirAll(_config.System.Passwd.Directory, 0o755); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -708,3 +798,36 @@ func UseOpenat2() bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Expand expands an input string by calling [os.ExpandEnv] to expand all
|
||||
// environment variables, then checks if the value is prefixed with `file://`
|
||||
// to support reading the value from a file.
|
||||
//
|
||||
// NOTE: the order of expanding environment variables first then checking if
|
||||
// the value references a file is important. This behaviour allows a user to
|
||||
// pass a value like `file://${CREDENTIALS_DIRECTORY}/token` to allow us to
|
||||
// work with credentials loaded by systemd's `LoadCredential` (or `LoadCredentialEncrypted`)
|
||||
// options without the user needing to assume the path of `CREDENTIALS_DIRECTORY`
|
||||
// or use a preStart script to read the files for us.
|
||||
func Expand(v string) (string, error) {
|
||||
// Expand environment variables within the string.
|
||||
//
|
||||
// NOTE: this may cause issues if the string contains `$` and doesn't intend
|
||||
// on getting expanded, however we are using this for our tokens which are
|
||||
// all alphanumeric characters only.
|
||||
v = os.ExpandEnv(v)
|
||||
|
||||
// Handle files.
|
||||
const filePrefix = "file://"
|
||||
if strings.HasPrefix(v, filePrefix) {
|
||||
p := v[len(filePrefix):]
|
||||
|
||||
b, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
v = string(bytes.TrimRight(bytes.TrimRight(b, "\r"), "\n"))
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
@ -2,11 +2,11 @@ package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type dockerNetworkInterfaces struct {
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
@ -39,7 +38,7 @@ func ConfigureDocker(ctx context.Context) error {
|
||||
}
|
||||
|
||||
nw := config.Get().Docker.Network
|
||||
resource, err := cli.NetworkInspect(ctx, nw.Name, types.NetworkInspectOptions{})
|
||||
resource, err := cli.NetworkInspect(ctx, nw.Name, network.InspectOptions{})
|
||||
if err != nil {
|
||||
if !client.IsErrNotFound(err) {
|
||||
return err
|
||||
@ -72,9 +71,10 @@ func ConfigureDocker(ctx context.Context) error {
|
||||
// Creates a new network on the machine if one does not exist already.
|
||||
func createDockerNetwork(ctx context.Context, cli *client.Client) error {
|
||||
nw := config.Get().Docker.Network
|
||||
_, err := cli.NetworkCreate(ctx, nw.Name, types.NetworkCreate{
|
||||
enableIPv6 := true
|
||||
_, err := cli.NetworkCreate(ctx, nw.Name, network.CreateOptions{
|
||||
Driver: nw.Driver,
|
||||
EnableIPv6: true,
|
||||
EnableIPv6: &enableIPv6,
|
||||
Internal: nw.IsInternal,
|
||||
IPAM: &network.IPAM{
|
||||
Config: []network.IPAMConfig{{
|
||||
|
||||
@ -2,6 +2,7 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"reflect"
|
||||
@ -13,7 +14,6 @@ import (
|
||||
"github.com/docker/docker/api/types/versions"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
)
|
||||
|
||||
@ -12,9 +12,10 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
@ -49,7 +50,7 @@ func (e *Environment) Attach(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
opts := types.ContainerAttachOptions{
|
||||
opts := container.AttachOptions{
|
||||
Stdin: true,
|
||||
Stdout: true,
|
||||
Stderr: true,
|
||||
@ -103,7 +104,7 @@ func (e *Environment) Attach(ctx context.Context) error {
|
||||
// container. This allows memory, cpu, and IO limitations to be adjusted on the
|
||||
// fly for individual instances.
|
||||
func (e *Environment) InSituUpdate() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if _, err := e.ContainerInspect(ctx); err != nil {
|
||||
@ -199,14 +200,15 @@ func (e *Environment) Create() error {
|
||||
networkName := "ip-" + strings.ReplaceAll(strings.ReplaceAll(a.DefaultMapping.Ip, ".", "-"), ":", "-")
|
||||
networkMode = container.NetworkMode(networkName)
|
||||
|
||||
if _, err := e.client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{}); err != nil {
|
||||
if _, err := e.client.NetworkInspect(ctx, networkName, network.InspectOptions{}); err != nil {
|
||||
if !client.IsErrNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := e.client.NetworkCreate(ctx, networkName, types.NetworkCreate{
|
||||
enableIPv6 := false
|
||||
if _, err := e.client.NetworkCreate(ctx, networkName, network.CreateOptions{
|
||||
Driver: "bridge",
|
||||
EnableIPv6: false,
|
||||
EnableIPv6: &enableIPv6,
|
||||
Internal: false,
|
||||
Attachable: false,
|
||||
Ingress: false,
|
||||
@ -270,7 +272,7 @@ func (e *Environment) Destroy() error {
|
||||
// We set it to stopping than offline to prevent crash detection from being triggered.
|
||||
e.SetState(environment.ProcessStoppingState)
|
||||
|
||||
err := e.client.ContainerRemove(context.Background(), e.Id, types.ContainerRemoveOptions{
|
||||
err := e.client.ContainerRemove(context.Background(), e.Id, container.RemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
RemoveLinks: false,
|
||||
Force: true,
|
||||
@ -316,7 +318,7 @@ func (e *Environment) SendCommand(c string) error {
|
||||
// is running or not, it will simply try to read the last X bytes of the file
|
||||
// and return them.
|
||||
func (e *Environment) Readlog(lines int) ([]string, error) {
|
||||
r, err := e.client.ContainerLogs(context.Background(), e.Id, types.ContainerLogsOptions{
|
||||
r, err := e.client.ContainerLogs(context.Background(), e.Id, container.LogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Tail: strconv.Itoa(lines),
|
||||
@ -343,25 +345,25 @@ func (e *Environment) Readlog(lines int) ([]string, error) {
|
||||
// late, and we don't need to block all the servers from booting just because
|
||||
// of that. I'd imagine in a lot of cases an outage shouldn't affect users too
|
||||
// badly. It'll at least keep existing servers working correctly if anything.
|
||||
func (e *Environment) ensureImageExists(image string) error {
|
||||
func (e *Environment) ensureImageExists(img string) error {
|
||||
e.Events().Publish(environment.DockerImagePullStarted, "")
|
||||
defer e.Events().Publish(environment.DockerImagePullCompleted, "")
|
||||
|
||||
// Images prefixed with a ~ are local images that we do not need to try and pull.
|
||||
if strings.HasPrefix(image, "~") {
|
||||
if strings.HasPrefix(img, "~") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Give it up to 15 minutes to pull the image. I think this should cover 99.8% of cases where an
|
||||
// image pull might fail. I can't imagine it will ever take more than 15 minutes to fully pull
|
||||
// an image. Let me know when I am inevitably wrong here...
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Minute*15)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Get a registry auth configuration from the config.
|
||||
var registryAuth *config.RegistryConfiguration
|
||||
for registry, c := range config.Get().Docker.Registries {
|
||||
if !strings.HasPrefix(image, registry) {
|
||||
if !strings.HasPrefix(img, registry) {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -371,7 +373,7 @@ func (e *Environment) ensureImageExists(image string) error {
|
||||
}
|
||||
|
||||
// Get the ImagePullOptions.
|
||||
imagePullOptions := types.ImagePullOptions{All: false}
|
||||
imagePullOptions := image.PullOptions{All: false}
|
||||
if registryAuth != nil {
|
||||
b64, err := registryAuth.Base64()
|
||||
if err != nil {
|
||||
@ -382,23 +384,23 @@ func (e *Environment) ensureImageExists(image string) error {
|
||||
imagePullOptions.RegistryAuth = b64
|
||||
}
|
||||
|
||||
out, err := e.client.ImagePull(ctx, image, imagePullOptions)
|
||||
out, err := e.client.ImagePull(ctx, img, imagePullOptions)
|
||||
if err != nil {
|
||||
images, ierr := e.client.ImageList(ctx, types.ImageListOptions{})
|
||||
images, ierr := e.client.ImageList(ctx, image.ListOptions{})
|
||||
if ierr != nil {
|
||||
// Well damn, something has gone really wrong here, just go ahead and abort there
|
||||
// isn't much anything we can do to try and self-recover from this.
|
||||
return errors.Wrap(ierr, "environment/docker: failed to list images")
|
||||
}
|
||||
|
||||
for _, img := range images {
|
||||
for _, t := range img.RepoTags {
|
||||
if t != image {
|
||||
for _, img2 := range images {
|
||||
for _, t := range img2.RepoTags {
|
||||
if t != img {
|
||||
continue
|
||||
}
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"image": image,
|
||||
"image": img,
|
||||
"container_id": e.Id,
|
||||
"err": err.Error(),
|
||||
}).Warn("unable to pull requested image from remote source, however the image exists locally")
|
||||
@ -409,11 +411,11 @@ func (e *Environment) ensureImageExists(image string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return errors.Wrapf(err, "environment/docker: failed to pull \"%s\" image for server", image)
|
||||
return errors.Wrapf(err, "environment/docker: failed to pull \"%s\" image for server", img)
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
log.WithField("image", image).Debug("pulling docker image... this could take a bit of time")
|
||||
log.WithField("image", img).Debug("pulling docker image... this could take a bit of time")
|
||||
|
||||
// I'm not sure what the best approach here is, but this will block execution until the image
|
||||
// is done being pulled, which is what we need.
|
||||
@ -431,22 +433,21 @@ func (e *Environment) ensureImageExists(image string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
log.WithField("image", image).Debug("completed docker image pull")
|
||||
log.WithField("image", img).Debug("completed docker image pull")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *Environment) convertMounts() []mount.Mount {
|
||||
var out []mount.Mount
|
||||
|
||||
for _, m := range e.Configuration.Mounts() {
|
||||
out = append(out, mount.Mount{
|
||||
mounts := e.Configuration.Mounts()
|
||||
out := make([]mount.Mount, len(mounts))
|
||||
for i, m := range mounts {
|
||||
out[i] = mount.Mount{
|
||||
Type: mount.TypeBind,
|
||||
Source: m.Source,
|
||||
Target: m.Target,
|
||||
ReadOnly: m.ReadOnly,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
@ -4,12 +4,10 @@ import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
@ -27,7 +25,7 @@ import (
|
||||
// is running does not result in the server becoming un-bootable.
|
||||
func (e *Environment) OnBeforeStart(ctx context.Context) error {
|
||||
// Always destroy and re-create the server container to ensure that synced data from the Panel is used.
|
||||
if err := e.client.ContainerRemove(ctx, e.Id, types.ContainerRemoveOptions{RemoveVolumes: true}); err != nil {
|
||||
if err := e.client.ContainerRemove(ctx, e.Id, container.RemoveOptions{RemoveVolumes: true}); err != nil {
|
||||
if !client.IsErrNotFound(err) {
|
||||
return errors.WrapIf(err, "environment/docker: failed to remove container during pre-boot")
|
||||
}
|
||||
@ -122,7 +120,7 @@ func (e *Environment) Start(ctx context.Context) error {
|
||||
return errors.WrapIf(err, "environment/docker: failed to attach to container")
|
||||
}
|
||||
|
||||
if err := e.client.ContainerStart(actx, e.Id, types.ContainerStartOptions{}); err != nil {
|
||||
if err := e.client.ContainerStart(actx, e.Id, container.StartOptions{}); err != nil {
|
||||
return errors.WrapIf(err, "environment/docker: failed to start container")
|
||||
}
|
||||
|
||||
@ -143,42 +141,49 @@ func (e *Environment) Stop(ctx context.Context) error {
|
||||
s := e.meta.Stop
|
||||
e.mu.RUnlock()
|
||||
|
||||
// A native "stop" as the Type field value will just skip over all of this
|
||||
// logic and end up only executing the container stop command (which may or
|
||||
// may not work as expected).
|
||||
if s.Type == "" || s.Type == remote.ProcessStopSignal {
|
||||
if s.Type == "" {
|
||||
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using termination procedure")
|
||||
}
|
||||
|
||||
signal := os.Kill
|
||||
// Handle a few common cases, otherwise just fall through and just pass along
|
||||
// the os.Kill signal to the process.
|
||||
switch strings.ToUpper(s.Value) {
|
||||
case "SIGABRT":
|
||||
signal = syscall.SIGABRT
|
||||
case "SIGINT":
|
||||
signal = syscall.SIGINT
|
||||
case "SIGTERM":
|
||||
signal = syscall.SIGTERM
|
||||
}
|
||||
return e.Terminate(ctx, signal)
|
||||
}
|
||||
|
||||
// If the process is already offline don't switch it back to stopping. Just leave it how
|
||||
// it is and continue through to the stop handling for the process.
|
||||
if e.st.Load() != environment.ProcessOfflineState {
|
||||
e.SetState(environment.ProcessStoppingState)
|
||||
}
|
||||
|
||||
// Handle signal based actions
|
||||
if s.Type == remote.ProcessStopSignal {
|
||||
log.WithField("signal_value", s.Value).Debug("stopping server using signal")
|
||||
|
||||
// Handle some common signals - Default to SIGKILL
|
||||
signal := "SIGKILL"
|
||||
switch strings.ToUpper(s.Value) {
|
||||
case "SIGABRT":
|
||||
signal = "SIGABRT"
|
||||
case "SIGINT", "C":
|
||||
signal = "SIGINT"
|
||||
case "SIGTERM":
|
||||
signal = "SIGTERM"
|
||||
case "SIGKILL":
|
||||
signal = "SIGKILL"
|
||||
default:
|
||||
log.Info("Unrecognised signal requested, defaulting to SIGKILL")
|
||||
}
|
||||
|
||||
return e.SignalContainer(ctx, signal)
|
||||
|
||||
}
|
||||
|
||||
// Handle command based stops
|
||||
// Only attempt to send the stop command to the instance if we are actually attached to
|
||||
// the instance. If we are not for some reason, just send the container stop event.
|
||||
if e.IsAttached() && s.Type == remote.ProcessStopCommand {
|
||||
return e.SendCommand(s.Value)
|
||||
}
|
||||
|
||||
// Allow the stop action to run for however long it takes, similar to executing a command
|
||||
// and using a different logic pathway to wait for the container to stop successfully.
|
||||
if s.Type == "" {
|
||||
log.WithField("container_id", e.Id).Warn("no stop configuration detected for environment, using native docker stop")
|
||||
}
|
||||
|
||||
// Fallback to a native docker stop. As we aren't passing a signal to ContainerStop docker will
|
||||
// attempt to stop the container using the default stop signal, SIGTERM, unless
|
||||
// another signal was specified in the Dockerfile
|
||||
//
|
||||
// Using a negative timeout here will allow the container to stop gracefully,
|
||||
// rather than forcefully terminating it. Value is in seconds, but -1 is
|
||||
@ -224,7 +229,7 @@ func (e *Environment) WaitForStop(ctx context.Context, duration time.Duration, t
|
||||
|
||||
doTermination := func(s string) error {
|
||||
e.log().WithField("step", s).WithField("duration", duration).Warn("container stop did not complete in time, terminating process...")
|
||||
return e.Terminate(ctx, os.Kill)
|
||||
return e.Terminate(ctx, "SIGKILL")
|
||||
}
|
||||
|
||||
// We pass through the timed context for this stop action so that if one of the
|
||||
@ -268,8 +273,8 @@ func (e *Environment) WaitForStop(ctx context.Context, duration time.Duration, t
|
||||
return nil
|
||||
}
|
||||
|
||||
// Terminate forcefully terminates the container using the signal provided.
|
||||
func (e *Environment) Terminate(ctx context.Context, signal os.Signal) error {
|
||||
// Sends the specified signal to the container in an attempt to stop it.
|
||||
func (e *Environment) SignalContainer(ctx context.Context, signal string) error {
|
||||
c, err := e.ContainerInspect(ctx)
|
||||
if err != nil {
|
||||
// Treat missing containers as an okay error state, means it is obviously
|
||||
@ -294,10 +299,23 @@ func (e *Environment) Terminate(ctx context.Context, signal os.Signal) error {
|
||||
|
||||
// We set it to stopping than offline to prevent crash detection from being triggered.
|
||||
e.SetState(environment.ProcessStoppingState)
|
||||
sig := strings.TrimSuffix(strings.TrimPrefix(signal.String(), "signal "), "ed")
|
||||
if err := e.client.ContainerKill(ctx, e.Id, sig); err != nil && !client.IsErrNotFound(err) {
|
||||
if err := e.client.ContainerKill(ctx, e.Id, signal); err != nil && !client.IsErrNotFound(err) {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Terminate forcefully terminates the container using the signal provided.
|
||||
// then sets its state to stopped.
|
||||
func (e *Environment) Terminate(ctx context.Context, signal string) error {
|
||||
// Send the signal to the container to kill it
|
||||
if err := e.SignalContainer(ctx, signal); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
// We expect Terminate to instantly kill the container
|
||||
// so go ahead and mark it as dead and clean up
|
||||
e.SetState(environment.ProcessOfflineState)
|
||||
|
||||
return nil
|
||||
|
||||
@ -2,13 +2,13 @@ package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
)
|
||||
@ -57,7 +57,7 @@ func (e *Environment) pollResources(ctx context.Context) error {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
var v types.StatsJSON
|
||||
var v container.StatsResponse
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
if err != io.EOF && !errors.Is(err, context.Canceled) {
|
||||
e.log().WithField("error", err).Warn("error while processing Docker stats output for container")
|
||||
@ -95,7 +95,7 @@ func (e *Environment) pollResources(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// The "docker stats" CLI call does not return the same value as the types.MemoryStats.Usage
|
||||
// The "docker stats" CLI call does not return the same value as the [container.MemoryStats].Usage
|
||||
// value which can be rather confusing to people trying to compare panel usage to
|
||||
// their stats output.
|
||||
//
|
||||
@ -103,7 +103,7 @@ func (e *Environment) pollResources(ctx context.Context) error {
|
||||
// bothering me about it. It should also reflect a slightly more correct memory value anyways.
|
||||
//
|
||||
// @see https://github.com/docker/cli/blob/96e1d1d6/cli/command/container/stats_helpers.go#L227-L249
|
||||
func calculateDockerMemory(stats types.MemoryStats) uint64 {
|
||||
func calculateDockerMemory(stats container.MemoryStats) uint64 {
|
||||
if v, ok := stats.Stats["total_inactive_file"]; ok && v < stats.Usage {
|
||||
return stats.Usage - v
|
||||
}
|
||||
@ -119,7 +119,7 @@ func calculateDockerMemory(stats types.MemoryStats) uint64 {
|
||||
// by the defined CPU limits on the container.
|
||||
//
|
||||
// @see https://github.com/docker/cli/blob/aa097cf1aa19099da70930460250797c8920b709/cli/command/container/stats_helpers.go#L166
|
||||
func calculateDockerAbsoluteCpu(pStats types.CPUStats, stats types.CPUStats) float64 {
|
||||
func calculateDockerAbsoluteCpu(pStats container.CPUStats, stats container.CPUStats) float64 {
|
||||
// Calculate the change in CPU usage between the current and previous reading.
|
||||
cpuDelta := float64(stats.CPUUsage.TotalUsage) - float64(pStats.CPUUsage.TotalUsage)
|
||||
|
||||
|
||||
@ -2,7 +2,6 @@ package environment
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pterodactyl/wings/events"
|
||||
@ -72,7 +71,7 @@ type ProcessEnvironment interface {
|
||||
|
||||
// Terminate stops a running server instance using the provided signal. This function
|
||||
// is a no-op if the server is already stopped.
|
||||
Terminate(ctx context.Context, signal os.Signal) error
|
||||
Terminate(ctx context.Context, signal string) error
|
||||
|
||||
// Destroys the environment removing any containers that were created (in Docker
|
||||
// environments at least).
|
||||
|
||||
@ -34,7 +34,7 @@ type Mount struct {
|
||||
// Limits is the build settings for a given server that impact docker container
|
||||
// creation and resource limits for a server instance.
|
||||
type Limits struct {
|
||||
// The total amount of memory in megabytes that this server is allowed to
|
||||
// The total amount of memory in mebibytes that this server is allowed to
|
||||
// use on the host system.
|
||||
MemoryLimit int64 `json:"memory_limit"`
|
||||
|
||||
@ -79,7 +79,7 @@ func (l Limits) MemoryOverheadMultiplier() float64 {
|
||||
}
|
||||
|
||||
func (l Limits) BoundedMemoryLimit() int64 {
|
||||
return int64(math.Round(float64(l.MemoryLimit) * l.MemoryOverheadMultiplier() * 1_000_000))
|
||||
return int64(math.Round(float64(l.MemoryLimit) * l.MemoryOverheadMultiplier() * 1024 * 1024))
|
||||
}
|
||||
|
||||
// ConvertedSwap returns the amount of swap available as a total in bytes. This
|
||||
@ -90,7 +90,7 @@ func (l Limits) ConvertedSwap() int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
return (l.Swap * 1_000_000) + l.BoundedMemoryLimit()
|
||||
return (l.Swap * 1024 * 1024) + l.BoundedMemoryLimit()
|
||||
}
|
||||
|
||||
// ProcessLimit returns the process limit for a container. This is currently
|
||||
@ -105,7 +105,7 @@ func (l Limits) AsContainerResources() container.Resources {
|
||||
pids := l.ProcessLimit()
|
||||
resources := container.Resources{
|
||||
Memory: l.BoundedMemoryLimit(),
|
||||
MemoryReservation: l.MemoryLimit * 1_000_000,
|
||||
MemoryReservation: l.MemoryLimit * 1024 * 1024,
|
||||
MemorySwap: l.ConvertedSwap(),
|
||||
BlkioWeight: l.IoWeight,
|
||||
OomKillDisable: &l.OOMDisabled,
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/system"
|
||||
)
|
||||
|
||||
35
flake.lock
generated
35
flake.lock
generated
@ -5,11 +5,11 @@
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1706830856,
|
||||
"narHash": "sha256-a0NYyp+h9hlb7ddVz4LUn1vT/PLwqfrWYcHMvFB1xYg=",
|
||||
"lastModified": 1741352980,
|
||||
"narHash": "sha256-+u2UunDA4Cl5Fci3m7S643HzKmIDAe+fiXrLqYsR2fs=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "b253292d9c0a5ead9bc98c4e9a26c6312e27d69f",
|
||||
"rev": "f4330d22f1c5d2ba72d3d22df5597d123fdb60a9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -20,11 +20,11 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1707956935,
|
||||
"narHash": "sha256-ZL2TrjVsiFNKOYwYQozpbvQSwvtV/3Me7Zwhmdsfyu4=",
|
||||
"lastModified": 1741379970,
|
||||
"narHash": "sha256-Wh7esNh7G24qYleLvgOSY/7HlDUzWaL/n4qzlBePpiw=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a4d4fe8c5002202493e87ec8dbc91335ff55552c",
|
||||
"rev": "36fd87baa9083f34f7f5027900b62ee6d09b1f2f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -36,19 +36,16 @@
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1706550542,
|
||||
"narHash": "sha256-UcsnCG6wx++23yeER4Hg18CXWbgNpqNXcHIo5/1Y+hc=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "97b17f32362e475016f942bbdfda4a4a72a8a652",
|
||||
"lastModified": 1740877520,
|
||||
"narHash": "sha256-oiwv/ZK/2FhGxrCkQkB83i7GnWXPPLzoqFHpDD3uYpk=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "147dee35aab2193b174e4c0868bd80ead5ce755c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"dir": "lib",
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
@ -66,11 +63,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1707300477,
|
||||
"narHash": "sha256-qQF0fEkHlnxHcrKIMRzOETnRBksUK048MXkX0SOmxvA=",
|
||||
"lastModified": 1739829690,
|
||||
"narHash": "sha256-mL1szCeIsjh6Khn3nH2cYtwO5YXG6gBiTw1A30iGeDU=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "ac599dab59a66304eb511af07b3883114f061b9d",
|
||||
"rev": "3d0579f5cc93436052d94b73925b48973a104204",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
outputs = {...} @ inputs:
|
||||
inputs.flake-parts.lib.mkFlake {inherit inputs;} {
|
||||
systems = ["aarch64-darwin" "aarch64-linux" "x86_64-darwin" "x86_64-linux"];
|
||||
systems = inputs.nixpkgs.lib.systems.flakeExposed;
|
||||
|
||||
imports = [
|
||||
inputs.treefmt-nix.flakeModule
|
||||
@ -24,10 +24,10 @@
|
||||
in {
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
go_1_22
|
||||
go_1_24
|
||||
gofumpt
|
||||
golangci-lint
|
||||
gotools
|
||||
gotools
|
||||
];
|
||||
};
|
||||
|
||||
|
||||
123
go.mod
123
go.mod
@ -1,6 +1,8 @@
|
||||
module github.com/pterodactyl/wings
|
||||
|
||||
go 1.21
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.24.1
|
||||
|
||||
require (
|
||||
emperror.dev/errors v0.8.1
|
||||
@ -10,73 +12,75 @@ require (
|
||||
github.com/acobaugh/osrelease v0.1.0
|
||||
github.com/apex/log v1.9.0
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/beevik/etree v1.3.0
|
||||
github.com/beevik/etree v1.5.0
|
||||
github.com/buger/jsonparser v1.1.1
|
||||
github.com/cenkalti/backoff/v4 v4.3.0
|
||||
github.com/creasty/defaults v1.7.0
|
||||
github.com/docker/docker v25.0.4+incompatible
|
||||
github.com/creasty/defaults v1.8.0
|
||||
github.com/docker/docker v28.3.3+incompatible
|
||||
github.com/docker/go-connections v0.5.0
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf
|
||||
github.com/gabriel-vasile/mimetype v1.4.3
|
||||
github.com/gabriel-vasile/mimetype v1.4.8
|
||||
github.com/gammazero/workerpool v1.1.3
|
||||
github.com/gbrlsnchs/jwt/v3 v3.0.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/glebarez/sqlite v1.11.0
|
||||
github.com/go-co-op/gocron v1.37.0
|
||||
github.com/goccy/go-json v0.10.2
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/iancoleman/strcase v0.3.0
|
||||
github.com/icza/dyno v0.0.0-20230330125955-09f820a8d9c0
|
||||
github.com/juju/ratelimit v1.0.2
|
||||
github.com/klauspost/compress v1.17.8
|
||||
github.com/klauspost/compress v1.18.0
|
||||
github.com/klauspost/pgzip v1.2.6
|
||||
github.com/magiconair/properties v1.8.7
|
||||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8
|
||||
github.com/magiconair/properties v1.8.9
|
||||
github.com/mattn/go-colorable v0.1.14
|
||||
github.com/mholt/archives v0.1.3
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pkg/sftp v1.13.6
|
||||
github.com/pkg/sftp v1.13.9
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sys v0.19.0
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/crypto v0.41.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/sys v0.35.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
gorm.io/gorm v1.25.9
|
||||
gorm.io/gorm v1.26.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
||||
github.com/Microsoft/hcsshim v0.12.2 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/Microsoft/hcsshim v0.12.9 // indirect
|
||||
github.com/STARRY-S/zip v0.2.2 // indirect
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 // indirect
|
||||
github.com/bodgit/plumbing v1.3.0 // indirect
|
||||
github.com/bodgit/sevenzip v1.5.1 // indirect
|
||||
github.com/bodgit/sevenzip v1.6.0 // indirect
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.11.3 // indirect
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
|
||||
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
||||
github.com/bytedance/sonic v1.13.1 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.4 // indirect
|
||||
github.com/cloudwego/base64x v0.1.5 // indirect
|
||||
github.com/containerd/errdefs v0.3.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/gammazero/deque v0.2.1 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gammazero/deque v1.0.0 // indirect
|
||||
github.com/gin-contrib/sse v1.0.0 // indirect
|
||||
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.19.0 // indirect
|
||||
github.com/go-playground/validator/v10 v10.25.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
@ -85,53 +89,56 @@ require (
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
|
||||
github.com/kr/fs v0.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/minlz v1.0.0 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 // indirect
|
||||
github.com/nwaples/rardecode/v2 v2.1.1 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.22 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/therootcompany/xz v1.0.1 // indirect
|
||||
github.com/sorairolake/lzip-go v0.3.5 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
github.com/ulikunitz/xz v0.5.12 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 // indirect
|
||||
go.opentelemetry.io/otel v1.25.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.14 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.25.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/mod v0.17.0 // indirect
|
||||
golang.org/x/net v0.24.0 // indirect
|
||||
golang.org/x/term v0.19.0 // indirect
|
||||
golang.org/x/text v0.14.0 // indirect
|
||||
golang.org/x/arch v0.15.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/term v0.34.0 // indirect
|
||||
golang.org/x/text v0.28.0 // indirect
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af // indirect
|
||||
golang.org/x/tools v0.20.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||
google.golang.org/protobuf v1.33.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
gotest.tools/v3 v3.0.2 // indirect
|
||||
modernc.org/libc v1.49.3 // indirect
|
||||
modernc.org/mathutil v1.6.0 // indirect
|
||||
modernc.org/memory v1.8.0 // indirect
|
||||
modernc.org/sqlite v1.29.6 // indirect
|
||||
modernc.org/libc v1.61.13 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
modernc.org/sqlite v1.36.0 // indirect
|
||||
)
|
||||
|
||||
348
go.sum
348
go.sum
@ -19,24 +19,27 @@ emperror.dev/errors v0.8.1 h1:UavXZ5cSX/4u9iyvH6aDcuGkVjeexUGJ7Ij7G4VfQT0=
|
||||
emperror.dev/errors v0.8.1/go.mod h1:YcRvLPh626Ubn2xqtoprejnA5nFha+TJ+2vew48kWuE=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Jeffail/gabs/v2 v2.7.0 h1:Y2edYaTcE8ZpRsR2AtmPu5xQdFDIthFG0jYhu5PY8kg=
|
||||
github.com/Jeffail/gabs/v2 v2.7.0/go.mod h1:dp5ocw1FvBBQYssgHsG7I1WYsiLRtkUaB1FEtSwvNUw=
|
||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
||||
github.com/Microsoft/hcsshim v0.12.2 h1:AcXy+yfRvrx20g9v7qYaJv5Rh+8GaHOS6b8G6Wx/nKs=
|
||||
github.com/Microsoft/hcsshim v0.12.2/go.mod h1:RZV12pcHCXQ42XnlQ3pz6FZfmrC1C+R4gaOHhRNML1g=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg=
|
||||
github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y=
|
||||
github.com/NYTimes/logrotate v1.0.0 h1:6jFGbon6jOtpy3t3kwZZKS4Gdmf1C/Wv5J4ll4Xn5yk=
|
||||
github.com/NYTimes/logrotate v1.0.0/go.mod h1:GxNz1cSw1c6t99PXoZlw+nm90H6cyQyrH66pjVv7x88=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||
github.com/STARRY-S/zip v0.2.2 h1:8QeCbIi1Z9U5MgoDARJR1ClbBo9RD46SmVy+dl0woCk=
|
||||
github.com/STARRY-S/zip v0.2.2/go.mod h1:lqJ9JdeRipyOQJrYSOtpNAiaesFO6zVDsE8GIGFaoSk=
|
||||
github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE=
|
||||
github.com/acobaugh/osrelease v0.1.0/go.mod h1:4bFEs0MtgHNHBrmHCt67gNisnabCRAlzdVasCEGHTWY=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3 h1:8PmGpDEZl9yDpcdEr6Odf23feCxK3LNUNMxjXg41pZQ=
|
||||
github.com/andybalholm/brotli v1.1.2-0.20250424173009-453214e765f3/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||
github.com/apex/log v1.9.0 h1:FHtw/xuaM8AgmvDDTI9fiwoAL25Sq2cxojnZICUU8l0=
|
||||
github.com/apex/log v1.9.0/go.mod h1:m82fZlWIuiWzWP04XCTXmnX0xRkYYbCdYn8jbJeLBEA=
|
||||
github.com/apex/logs v1.0.0/go.mod h1:XzxuLZ5myVHDy9SAmYpamKKRNApGj54PfYLcFrXqDwo=
|
||||
@ -46,81 +49,82 @@ github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3d
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
|
||||
github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I=
|
||||
github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU=
|
||||
github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
|
||||
github.com/beevik/etree v1.5.0 h1:iaQZFSDS+3kYZiGoc9uKeOkUY3nYMXOKLl6KIJxiJWs=
|
||||
github.com/beevik/etree v1.5.0/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
|
||||
github.com/bodgit/plumbing v1.3.0 h1:pf9Itz1JOQgn7vEOE7v7nlEfBykYqvUYioC61TwWCFU=
|
||||
github.com/bodgit/plumbing v1.3.0/go.mod h1:JOTb4XiRu5xfnmdnDJo6GmSbSbtSyufrsyZFByMtKEs=
|
||||
github.com/bodgit/sevenzip v1.5.1 h1:rVj0baZsooZFy64DJN0zQogPzhPrT8BQ8TTRd1H4WHw=
|
||||
github.com/bodgit/sevenzip v1.5.1/go.mod h1:Q3YMySuVWq6pyGEolyIE98828lOfEoeWg5zeH6x22rc=
|
||||
github.com/bodgit/sevenzip v1.6.0 h1:a4R0Wu6/P1o1pP/3VV++aEOcyeBxeO/xE2Y9NSTrr6A=
|
||||
github.com/bodgit/sevenzip v1.6.0/go.mod h1:zOBh9nJUof7tcrlqJFv1koWRrhz3LbDbUNngkuZxLMc=
|
||||
github.com/bodgit/windows v1.0.1 h1:tF7K6KOluPYygXa3Z2594zxlkbKPAOvqr97etrGNIz4=
|
||||
github.com/bodgit/windows v1.0.1/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM=
|
||||
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
||||
github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA=
|
||||
github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
||||
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
|
||||
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
|
||||
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
|
||||
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
|
||||
github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chenzhuoyu/iasm v0.9.1 h1:tUHQJXo3NhBqw6s33wkGn9SP3bvrWLdlVIJ3hQBL7P0=
|
||||
github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
|
||||
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
|
||||
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.17 h1:QeVUsEDNrLBW4tMgZHvxy18sKtr6VI492kBhUfhDJNI=
|
||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
|
||||
github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/creasty/defaults v1.8.0 h1:z27FJxCAa0JKt3utc0sCImAEb+spPucmKoOdLHvHYKk=
|
||||
github.com/creasty/defaults v1.8.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/docker/docker v25.0.4+incompatible h1:XITZTrq+52tZyZxUOtFIahUf3aH367FLxJzt9vZeAF8=
|
||||
github.com/docker/docker v25.0.4+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 h1:iFaUwBSo5Svw6L7HYpRu/0lE3e0BaElwnNO1qkNQxBY=
|
||||
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 h1:2tV76y6Q9BB+NEBasnqvs7e49aEBFI8ejC89PSnWH+4=
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707/go.mod h1:qssHWj60/X5sZFNxpG4HBPDHVqxNm4DfnCKgrbZOT+s=
|
||||
github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf h1:NrF81UtW8gG2LBGkXFQFqlfNnvMt9WdB46sfdJY4oqc=
|
||||
github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0=
|
||||
github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
|
||||
github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34=
|
||||
github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo=
|
||||
github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q=
|
||||
github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc=
|
||||
github.com/gbrlsnchs/jwt/v3 v3.0.1 h1:lbUmgAKpxnClrKloyIwpxm4OuWeDl5wLk52G91ODPw4=
|
||||
github.com/gbrlsnchs/jwt/v3 v3.0.1/go.mod h1:AncDcjXz18xetI3A6STfXq2w+LuTx8pQ8bGEwRN8zVM=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
|
||||
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
|
||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
|
||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||
@ -131,8 +135,8 @@ github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
@ -141,10 +145,10 @@ github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/o
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.19.0 h1:ol+5Fu+cSq9JD7SoSqe04GMI92cbn0+wvQ3bZ8b/AU4=
|
||||
github.com/go-playground/validator/v10 v10.19.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8=
|
||||
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
|
||||
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@ -159,10 +163,6 @@ github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
@ -170,15 +170,16 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
||||
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
@ -186,8 +187,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
@ -226,12 +227,12 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
|
||||
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
@ -241,8 +242,9 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
@ -252,24 +254,33 @@ github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjS
|
||||
github.com/magefile/mage v1.9.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
|
||||
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM=
|
||||
github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A=
|
||||
github.com/mholt/archives v0.1.3 h1:aEAaOtNra78G+TvV5ohmXrJOAzf++dIlYeDW3N9q458=
|
||||
github.com/mholt/archives v0.1.3/go.mod h1:LUCGp++/IbV/I0Xq4SzcIR6uwgeh2yjnQWamjRQfLTU=
|
||||
github.com/mikelolasagasti/xz v1.0.1 h1:Q2F2jX0RYJUG3+WsM+FJknv+6eVjsjXNDV0KJXZzkD0=
|
||||
github.com/mikelolasagasti/xz v1.0.1/go.mod h1:muAirjiOUxPRXwm9HdDtB3uoRPrGnL85XHtokL9Hcgc=
|
||||
github.com/minio/minlz v1.0.0 h1:Kj7aJZ1//LlTP1DM8Jm7lNKvvJS2m74gyyXXn3+uJWQ=
|
||||
github.com/minio/minlz v1.0.0/go.mod h1:qT0aEB35q79LLornSzeDH75LBf3aH1MV+jB5w9Wasec=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI=
|
||||
github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@ -281,26 +292,26 @@ github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2 h1:e3mzJFJs4k83GXBEiTaQ5HgSc/kOK8q0rDaRO0MPaOk=
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.2/go.mod h1:yntwv/HfMc/Hbvtq9I19D1n58te3h6KsqCf3GxyfBGY=
|
||||
github.com/nwaples/rardecode/v2 v2.1.1 h1:OJaYalXdliBUXPmC8CZGQ7oZDxzX1/5mQmgn0/GASew=
|
||||
github.com/nwaples/rardecode/v2 v2.1.1/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pierrec/lz4/v4 v4.1.22 h1:cKFw6uJDK+/gfw5BcDL0JL5aBsAFdsIT18eRtLj7VIU=
|
||||
github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
|
||||
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
|
||||
github.com/pkg/sftp v1.13.9 h1:4NGkvGudBL7GteO3m6qnaQ4pC0Kvf0onSVc9gR3EWBw=
|
||||
github.com/pkg/sftp v1.13.9/go.mod h1:OBN7bVXdstkFFN/gdnHPUb5TE8eb8G1Rp9wCItqjkkA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@ -311,8 +322,9 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG
|
||||
github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
|
||||
github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
|
||||
@ -323,11 +335,13 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
|
||||
github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM=
|
||||
github.com/smartystreets/gunit v1.0.0/go.mod h1:qwPWnhz6pn0NnRBP++URONOVyNkPyr4SauJk4cUOwJs=
|
||||
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||
github.com/sorairolake/lzip-go v0.3.5 h1:ms5Xri9o1JBIWvOFAorYtUNik6HI3HgBTkISiqu0Cwg=
|
||||
github.com/sorairolake/lzip-go v0.3.5/go.mod h1:N0KYq5iWrMXI0ZEXKXaS9hCyOjZUQdBDEIbXfoUwbdk=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
@ -341,10 +355,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw=
|
||||
github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tj/assert v0.0.0-20171129193455-018094318fb0/go.mod h1:mZ9/Rh9oLWpLLDRpvE+3b7gP/C2YyLFYxNmcLnPTMe0=
|
||||
github.com/tj/assert v0.0.3 h1:Df/BlaZ20mq6kuai7f5z2TvPFiwC3xaWJSDQNiIS3Rk=
|
||||
github.com/tj/assert v0.0.3/go.mod h1:Ne6X72Q+TB1AteidzQncjw9PabbMp4PBMZ1k+vd1Pvk=
|
||||
@ -357,8 +369,10 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc=
|
||||
github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.14 h1:uv/0Bq533iFdnMHZdRBTOlaNMdb1+ZxXIlHDZHIHcvg=
|
||||
github.com/ulikunitz/xz v0.5.14/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
@ -366,20 +380,24 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0 h1:cEPbyTSEHlQR89XVlyo78gqluF8Y3oMeBkXGWzQsfXY=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.50.0/go.mod h1:DKdbWcT4GH1D0Y3Sqt/PFXt2naRKDWtU+eE6oLdFNA8=
|
||||
go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k=
|
||||
go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
||||
go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA=
|
||||
go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
|
||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
||||
go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM=
|
||||
go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I=
|
||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
|
||||
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
@ -393,9 +411,8 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 h1:nifaUDeh+rPaBCMPMQHZmvJf+QdpLFnuQPwx+LxVmtc=
|
||||
go4.org v0.0.0-20230225012048-214862532bf5/go.mod h1:F57wTi5Lrj6WLyswp5EYV1ncrEbFGHD4hhz6S1ZYeaU=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
||||
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
|
||||
golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
@ -404,9 +421,12 @@ golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@ -415,6 +435,8 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE
|
||||
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@ -434,8 +456,12 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
golang.org/x/mod v0.26.0/go.mod h1:/j6NAhSk8iQ723BGAUyoAcn7SlD7s15Dp9Nd/SfeaFQ=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@ -454,10 +480,14 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@ -471,8 +501,13 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -494,18 +529,26 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@ -514,8 +557,13 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20220922220347-f3bd1da661af h1:Yx9k8YCG3dvF87UAn2tu2HQLf2dt/eR1bXxpLMWeH+Y=
|
||||
@ -549,14 +597,17 @@ golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapK
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.20.0 h1:hz/CVckiOxybQvFw6h7b/q80NTr9IUQb4s1IIzW7KNY=
|
||||
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
|
||||
@ -586,8 +637,8 @@ google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfG
|
||||
google.golang.org/genproto v0.0.0-20240123012728-ef4313101c80 h1:KAeGQVN3M9nD0/bQXnr/ClcEMJ968gUXJQ9pwfSynuQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917 h1:rcS6EyEaoCO52hQDupoSfrxI3R6C2Tq741is7X8OvnM=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240102182953-50ed04b92917/go.mod h1:CmlNWB9lSezaYELKS5Ym1r44VrrbPUa7JTvw+6MbpJ0=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 h1:AjyfHzEPEFp/NpvfN5g+KDla3EMojjhRVZc1i7cj+oM=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80/go.mod h1:PAREbraiVEVGVdTZsVWjSbbTtSyGbAgIIvni8a8CD5s=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
@ -595,10 +646,10 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
||||
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.62.0 h1:HQKZ/fa1bXkX1oFOvSjmZEUL8wLSaZTjCcLAlmZRtdk=
|
||||
google.golang.org/grpc v1.62.0/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw=
|
||||
google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@ -617,8 +668,8 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
|
||||
gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/gorm v1.25.9 h1:wct0gxZIELDk8+ZqF/MVnHLkA1rvYlBWUMv2EdsK1g8=
|
||||
gorm.io/gorm v1.25.9/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
|
||||
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
|
||||
gotest.tools/v3 v3.0.2 h1:kG1BFyqVHuQoVQiR1bWGnfz/fmHvvuiSPIV7rvl360E=
|
||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
@ -626,32 +677,31 @@ honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWh
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
|
||||
modernc.org/cc/v4 v4.20.0 h1:45Or8mQfbUqJOG9WaxvlFYOAQO0lQ5RvqBcFCXngjxk=
|
||||
modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
||||
modernc.org/ccgo/v4 v4.16.0 h1:ofwORa6vx2FMm0916/CkZjpFPSR70VwTjUCe2Eg5BnA=
|
||||
modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI=
|
||||
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo=
|
||||
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
||||
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
||||
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
||||
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
||||
modernc.org/libc v1.49.3 h1:j2MRCRdwJI2ls/sGbeSk0t2bypOG/uvPZUsGQFDulqg=
|
||||
modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
||||
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
||||
modernc.org/sqlite v1.29.6 h1:0lOXGrycJPptfHDuohfYgNqoe4hu+gYuN/pKgY5XjS4=
|
||||
modernc.org/sqlite v1.29.6/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw=
|
||||
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8=
|
||||
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI=
|
||||
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU=
|
||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||
modernc.org/sqlite v1.36.0 h1:EQXNRn4nIS+gfsKeUTymHIz1waxuv5BzU7558dHSfH8=
|
||||
modernc.org/sqlite v1.36.0/go.mod h1:7MPwH7Z6bREicF9ZVUR78P1IKuxfZ8mRIDHD0iD+8TU=
|
||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
|
||||
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
|
||||
|
||||
@ -56,6 +56,7 @@ func (ac *activityCron) Run(ctx context.Context) error {
|
||||
activities = append(activities, v)
|
||||
}
|
||||
|
||||
// Delete any invalid activies
|
||||
if len(ids) > 0 {
|
||||
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
|
||||
if tx.Error != nil {
|
||||
@ -71,16 +72,28 @@ func (ac *activityCron) Run(ctx context.Context) error {
|
||||
return errors.WrapIf(err, "cron: failed to send activity events to Panel")
|
||||
}
|
||||
|
||||
// Add all the successful activities to the list of IDs to delete.
|
||||
ids = make([]int, len(activities))
|
||||
for i, v := range activities {
|
||||
ids[i] = v.ID
|
||||
}
|
||||
|
||||
// Delete all the activities that were sent to the Panel (or that were invalid).
|
||||
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids).Delete(&models.Activity{})
|
||||
if tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
// SQLite has a limitation of how many parameters we can specify in a single
|
||||
// query, so we need to delete the activies in chunks of 32,000 instead of
|
||||
// all at once.
|
||||
i := 0
|
||||
idsLen := len(ids)
|
||||
for i < idsLen {
|
||||
start := i
|
||||
end := min(i+32000, idsLen)
|
||||
batchSize := end - start
|
||||
|
||||
tx = database.Instance().WithContext(ctx).Where("id IN ?", ids[start:end]).Delete(&models.Activity{})
|
||||
if tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
}
|
||||
|
||||
i += batchSize
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"reflect"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"github.com/pterodactyl/wings/internal/database"
|
||||
"github.com/pterodactyl/wings/internal/models"
|
||||
@ -83,9 +84,26 @@ func (sc *sftpCron) Run(ctx context.Context) error {
|
||||
if err := sc.manager.Client().SendActivityLogs(ctx, events.Elements()); err != nil {
|
||||
return errors.Wrap(err, "failed to send sftp activity logs to Panel")
|
||||
}
|
||||
if tx := database.Instance().Where("id IN ?", events.ids).Delete(&models.Activity{}); tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
|
||||
// SQLite has a limitation of how many parameters we can specify in a single
|
||||
// query, so we need to delete the activies in chunks of 32,000 instead of
|
||||
// all at once.
|
||||
i := 0
|
||||
idsLen := len(events.ids)
|
||||
var tx *gorm.DB
|
||||
for i < idsLen {
|
||||
start := i
|
||||
end := min(i+32000, idsLen)
|
||||
batchSize := end - start
|
||||
|
||||
tx = database.Instance().WithContext(ctx).Where("id IN ?", events.ids[start:end]).Delete(&models.Activity{})
|
||||
if tx.Error != nil {
|
||||
return errors.WithStack(tx.Error)
|
||||
}
|
||||
|
||||
i += batchSize
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -2,9 +2,9 @@ package models
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
type JsonNullString struct {
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
# Filesystem
|
||||
|
||||
Coming Soon™
|
||||
|
||||
> TODO
|
||||
|
||||
## Licensing
|
||||
|
||||
Most code in this package is licensed under `MIT` with some exceptions.
|
||||
@ -11,7 +15,7 @@ verbatim or derived from [Go](https://go.dev)'s source code.
|
||||
- [`mkdir_unix.go`](./mkdir_unix.go)
|
||||
- [`path_unix.go`](./path_unix.go)
|
||||
- [`removeall_unix.go`](./removeall_unix.go)
|
||||
- [`stat_unix.go`](./stat_unix.go)
|
||||
- [`stat_unix.go`](./stat_unix.go)
|
||||
- [`walk.go`](./walk.go)
|
||||
|
||||
These changes are not associated with nor endorsed by The Go Authors.
|
||||
|
||||
@ -7,6 +7,7 @@ import (
|
||||
"errors"
|
||||
iofs "io/fs"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
@ -48,9 +49,9 @@ type PathError = iofs.PathError
|
||||
// SyscallError records an error from a specific system call.
|
||||
type SyscallError = os.SyscallError
|
||||
|
||||
// NewSyscallError returns, as an error, a new SyscallError
|
||||
// with the given system call name and error details.
|
||||
// As a convenience, if err is nil, NewSyscallError returns nil.
|
||||
// NewSyscallError returns, as an error, a new [*os.SyscallError] with the
|
||||
// given system call name and error details. As a convenience, if err is nil,
|
||||
// [NewSyscallError] returns nil.
|
||||
func NewSyscallError(syscall string, err error) error {
|
||||
return os.NewSyscallError(syscall, err)
|
||||
}
|
||||
@ -61,60 +62,122 @@ func convertErrorType(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pErr *PathError
|
||||
switch {
|
||||
case errors.As(err, &pErr):
|
||||
switch {
|
||||
// File exists
|
||||
case errors.Is(pErr.Err, unix.EEXIST):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrExist,
|
||||
}
|
||||
// Is a directory
|
||||
case errors.Is(pErr.Err, unix.EISDIR):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrIsDirectory,
|
||||
}
|
||||
// Not a directory
|
||||
case errors.Is(pErr.Err, unix.ENOTDIR):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrNotDirectory,
|
||||
}
|
||||
// No such file or directory
|
||||
case errors.Is(pErr.Err, unix.ENOENT):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrNotExist,
|
||||
}
|
||||
// Operation not permitted
|
||||
case errors.Is(pErr.Err, unix.EPERM):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrPermission,
|
||||
}
|
||||
// Invalid cross-device link
|
||||
case errors.Is(pErr.Err, unix.EXDEV):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrBadPathResolution,
|
||||
}
|
||||
// Too many levels of symbolic links
|
||||
case errors.Is(pErr.Err, unix.ELOOP):
|
||||
return &PathError{
|
||||
Op: pErr.Op,
|
||||
Path: pErr.Path,
|
||||
Err: ErrBadPathResolution,
|
||||
}
|
||||
if errors.As(err, &pErr) {
|
||||
if errno, ok := pErr.Err.(syscall.Errno); ok {
|
||||
return errnoToPathError(errno, pErr.Op, pErr.Path)
|
||||
}
|
||||
return pErr
|
||||
}
|
||||
|
||||
// If the error wasn't already a path error and is a errno, wrap it with
|
||||
// details that we can use to know there is something wrong with our
|
||||
// error wrapping somewhere.
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) {
|
||||
return &PathError{
|
||||
Op: "!(UNKNOWN)",
|
||||
Path: "!(UNKNOWN)",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// ensurePathError ensures that err is a PathError. The op and path arguments
|
||||
// are only used of the error isn't already a PathError.
|
||||
func ensurePathError(err error, op, path string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the error is already a PathError.
|
||||
var pErr *PathError
|
||||
if errors.As(err, &pErr) {
|
||||
// If underlying error is a errno, convert it.
|
||||
//
|
||||
// DO NOT USE `errors.As` or whatever here, the error will either be
|
||||
// an errno, or it will be wrapped already.
|
||||
if errno, ok := pErr.Err.(syscall.Errno); ok {
|
||||
return errnoToPathError(errno, pErr.Op, pErr.Path)
|
||||
}
|
||||
// Return the PathError as-is without modification.
|
||||
return pErr
|
||||
}
|
||||
|
||||
// If the error is directly an errno, convert it to a PathError.
|
||||
var errno syscall.Errno
|
||||
if errors.As(err, &errno) {
|
||||
return errnoToPathError(errno, op, path)
|
||||
}
|
||||
|
||||
// Otherwise just wrap it as a PathError without any additional changes.
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
// errnoToPathError converts an errno into a proper path error.
|
||||
func errnoToPathError(err syscall.Errno, op, path string) error {
|
||||
switch err {
|
||||
// File exists
|
||||
case unix.EEXIST:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrExist,
|
||||
}
|
||||
// Is a directory
|
||||
case unix.EISDIR:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrIsDirectory,
|
||||
}
|
||||
// Not a directory
|
||||
case unix.ENOTDIR:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrNotDirectory,
|
||||
}
|
||||
// No such file or directory
|
||||
case unix.ENOENT:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrNotExist,
|
||||
}
|
||||
// Operation not permitted
|
||||
case unix.EPERM:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrPermission,
|
||||
}
|
||||
// Invalid cross-device link
|
||||
case unix.EXDEV:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrBadPathResolution,
|
||||
}
|
||||
// Too many levels of symbolic links
|
||||
case unix.ELOOP:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: ErrBadPathResolution,
|
||||
}
|
||||
default:
|
||||
return &PathError{
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,6 +146,7 @@ const (
|
||||
ModePerm = iofs.ModePerm
|
||||
)
|
||||
|
||||
// Re-using the same names as Go's official `unix` and `os` package do.
|
||||
const (
|
||||
// O_RDONLY opens the file read-only.
|
||||
O_RDONLY = unix.O_RDONLY
|
||||
|
||||
@ -7,6 +7,12 @@ import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// Quota is a wrapper around [*UnixFS] that provides the ability to limit the
|
||||
// disk usage of the filesystem.
|
||||
//
|
||||
// NOTE: this is not a full complete quota filesystem, it provides utilities for
|
||||
// tracking and checking the usage of the filesystem. The only operation that is
|
||||
// automatically accounted against the quota are file deletions.
|
||||
type Quota struct {
|
||||
// fs is the underlying filesystem that runs the actual I/O operations.
|
||||
*UnixFS
|
||||
@ -26,6 +32,7 @@ type Quota struct {
|
||||
usage atomic.Int64
|
||||
}
|
||||
|
||||
// NewQuota creates a new Quota filesystem using an existing UnixFS and a limit.
|
||||
func NewQuota(fs *UnixFS, limit int64) *Quota {
|
||||
qfs := Quota{UnixFS: fs}
|
||||
qfs.limit.Store(limit)
|
||||
@ -101,6 +108,9 @@ func (fs *Quota) CanFit(size int64) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory.
|
||||
//
|
||||
// If there is an error, it will be of type [*PathError].
|
||||
func (fs *Quota) Remove(name string) error {
|
||||
// For information on why this interface is used here, check its
|
||||
// documentation.
|
||||
@ -125,7 +135,7 @@ func (fs *Quota) Remove(name string) error {
|
||||
// it encounters. If the path does not exist, RemoveAll
|
||||
// returns nil (no error).
|
||||
//
|
||||
// If there is an error, it will be of type *PathError.
|
||||
// If there is an error, it will be of type [*PathError].
|
||||
func (fs *Quota) RemoveAll(name string) error {
|
||||
name, err := fs.unsafePath(name)
|
||||
if err != nil {
|
||||
|
||||
@ -12,7 +12,6 @@ import (
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
@ -26,10 +25,6 @@ type UnixFS struct {
|
||||
// basePath is the base path for file operations to take place in.
|
||||
basePath string
|
||||
|
||||
// dirfd holds the file descriptor of BasePath and is used to ensure
|
||||
// operations are restricted into descendants of BasePath.
|
||||
dirfd atomic.Int64
|
||||
|
||||
// useOpenat2 controls whether the `openat2` syscall is used instead of the
|
||||
// older `openat` syscall.
|
||||
useOpenat2 bool
|
||||
@ -41,18 +36,10 @@ type UnixFS struct {
|
||||
// checked and prevented from enabling an escape in a non-raceable manor.
|
||||
func NewUnixFS(basePath string, useOpenat2 bool) (*UnixFS, error) {
|
||||
basePath = strings.TrimSuffix(basePath, "/")
|
||||
// We don't need Openat2, if we are given a basePath that is already unsafe
|
||||
// I give up on trying to sandbox it.
|
||||
dirfd, err := unix.Openat(AT_EMPTY_PATH, basePath, O_DIRECTORY|O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return nil, convertErrorType(err)
|
||||
}
|
||||
|
||||
fs := &UnixFS{
|
||||
basePath: basePath,
|
||||
useOpenat2: useOpenat2,
|
||||
}
|
||||
fs.dirfd.Store(int64(dirfd))
|
||||
return fs, nil
|
||||
}
|
||||
|
||||
@ -66,12 +53,7 @@ func (fs *UnixFS) BasePath() string {
|
||||
// Close releases the file descriptor used to sandbox operations within the
|
||||
// base path of the filesystem.
|
||||
func (fs *UnixFS) Close() error {
|
||||
// Once closed, change dirfd to something invalid to detect when it has been
|
||||
// closed.
|
||||
defer func() {
|
||||
fs.dirfd.Store(-1)
|
||||
}()
|
||||
return unix.Close(int(fs.dirfd.Load()))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Chmod changes the mode of the named file to mode.
|
||||
@ -99,7 +81,16 @@ func (fs *UnixFS) Chmod(name string, mode FileMode) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return convertErrorType(unix.Fchmodat(dirfd, name, uint32(mode), 0))
|
||||
return fs.fchmodat("chmod", dirfd, name, mode)
|
||||
}
|
||||
|
||||
// Chmodat is like Chmod but it takes a dirfd and name instead of a full path.
|
||||
func (fs *UnixFS) Chmodat(dirfd int, name string, mode FileMode) error {
|
||||
return fs.fchmodat("chmodat", dirfd, name, mode)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) fchmodat(op string, dirfd int, name string, mode FileMode) error {
|
||||
return ensurePathError(unix.Fchmodat(dirfd, name, uint32(mode), 0), op, name)
|
||||
}
|
||||
|
||||
// Chown changes the numeric uid and gid of the named file.
|
||||
@ -111,7 +102,7 @@ func (fs *UnixFS) Chmod(name string, mode FileMode) error {
|
||||
// On Windows or Plan 9, Chown always returns the syscall.EWINDOWS or
|
||||
// EPLAN9 error, wrapped in *PathError.
|
||||
func (fs *UnixFS) Chown(name string, uid, gid int) error {
|
||||
return fs.fchown(name, uid, gid, 0)
|
||||
return ensurePathError(fs.fchown(name, uid, gid, 0), "chown", name)
|
||||
}
|
||||
|
||||
// Lchown changes the numeric uid and gid of the named file.
|
||||
@ -124,7 +115,7 @@ func (fs *UnixFS) Chown(name string, uid, gid int) error {
|
||||
func (fs *UnixFS) Lchown(name string, uid, gid int) error {
|
||||
// With AT_SYMLINK_NOFOLLOW, Fchownat acts like Lchown but allows us to
|
||||
// pass a dirfd.
|
||||
return fs.fchown(name, uid, gid, AT_SYMLINK_NOFOLLOW)
|
||||
return ensurePathError(fs.fchown(name, uid, gid, AT_SYMLINK_NOFOLLOW), "lchown", name)
|
||||
}
|
||||
|
||||
// fchown is a re-usable Fchownat syscall used by Chown and Lchown.
|
||||
@ -134,19 +125,19 @@ func (fs *UnixFS) fchown(name string, uid, gid, flags int) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return convertErrorType(unix.Fchownat(dirfd, name, uid, gid, flags))
|
||||
return unix.Fchownat(dirfd, name, uid, gid, flags)
|
||||
}
|
||||
|
||||
// Chownat is like Chown but allows passing an existing directory file
|
||||
// descriptor rather than needing to resolve one.
|
||||
func (fs *UnixFS) Chownat(dirfd int, name string, uid, gid int) error {
|
||||
return convertErrorType(unix.Fchownat(dirfd, name, uid, gid, 0))
|
||||
return ensurePathError(unix.Fchownat(dirfd, name, uid, gid, 0), "chownat", name)
|
||||
}
|
||||
|
||||
// Lchownat is like Lchown but allows passing an existing directory file
|
||||
// descriptor rather than needing to resolve one.
|
||||
func (fs *UnixFS) Lchownat(dirfd int, name string, uid, gid int) error {
|
||||
return convertErrorType(unix.Fchownat(dirfd, name, uid, gid, AT_SYMLINK_NOFOLLOW))
|
||||
return ensurePathError(unix.Fchownat(dirfd, name, uid, gid, AT_SYMLINK_NOFOLLOW), "lchownat", name)
|
||||
}
|
||||
|
||||
// Chtimes changes the access and modification times of the named
|
||||
@ -178,11 +169,9 @@ func (fs *UnixFS) Chtimesat(dirfd int, name string, atime, mtime time.Time) erro
|
||||
}
|
||||
set(0, atime)
|
||||
set(1, mtime)
|
||||
|
||||
// This does support `AT_SYMLINK_NOFOLLOW` as well if needed.
|
||||
if err := unix.UtimesNanoAt(dirfd, name, utimes[0:], 0); err != nil {
|
||||
return convertErrorType(&PathError{Op: "chtimes", Path: name, Err: err})
|
||||
}
|
||||
return nil
|
||||
return ensurePathError(unix.UtimesNanoAt(dirfd, name, utimes[0:], 0), "chtimes", name)
|
||||
}
|
||||
|
||||
// Create creates or truncates the named file. If the file already exists,
|
||||
@ -206,11 +195,15 @@ func (fs *UnixFS) Mkdir(name string, mode FileMode) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.Mkdirat(dirfd, name, mode)
|
||||
return fs.mkdirat("mkdir", dirfd, name, mode)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) Mkdirat(dirfd int, name string, mode FileMode) error {
|
||||
return convertErrorType(unix.Mkdirat(dirfd, name, uint32(mode)))
|
||||
return fs.mkdirat("mkdirat", dirfd, name, mode)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) mkdirat(op string, dirfd int, name string, mode FileMode) error {
|
||||
return ensurePathError(unix.Mkdirat(dirfd, name, uint32(mode)), op, name)
|
||||
}
|
||||
|
||||
// MkdirAll creates a directory named path, along with any necessary
|
||||
@ -294,8 +287,10 @@ func (fs *UnixFS) ReadDir(path string) ([]DirEntry, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
return fs.readDir(fd, name, nil)
|
||||
defer func() {
|
||||
_ = unix.Close(fd)
|
||||
}()
|
||||
return fs.readDir(fd, name, ".", nil)
|
||||
}
|
||||
|
||||
// RemoveStat is a combination of Stat and Remove, it is used to more
|
||||
@ -329,7 +324,7 @@ func (fs *UnixFS) RemoveStat(name string) (FileInfo, error) {
|
||||
err = fs.unlinkat(dirfd, name, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return s, convertErrorType(&PathError{Op: "remove", Path: name, Err: err})
|
||||
return s, ensurePathError(err, "rename", name)
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
@ -378,7 +373,7 @@ func (fs *UnixFS) Remove(name string) error {
|
||||
if err1 != unix.ENOTDIR {
|
||||
err = err1
|
||||
}
|
||||
return convertErrorType(&PathError{Op: "remove", Path: name, Err: err})
|
||||
return ensurePathError(err, "remove", name)
|
||||
}
|
||||
|
||||
// RemoveAll removes path and any children it contains.
|
||||
@ -393,6 +388,7 @@ func (fs *UnixFS) RemoveAll(name string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// While removeAll internally checks this, I want to make sure we check it
|
||||
// and return the proper error so our tests can ensure that this will never
|
||||
// be a possibility.
|
||||
@ -403,9 +399,29 @@ func (fs *UnixFS) RemoveAll(name string) error {
|
||||
Err: ErrBadPathResolution,
|
||||
}
|
||||
}
|
||||
|
||||
return fs.removeAll(name)
|
||||
}
|
||||
|
||||
// RemoveContents recursively removes the contents of name.
|
||||
//
|
||||
// It removes everything it can but returns the first error
|
||||
// it encounters. If the path does not exist, RemoveContents
|
||||
// returns nil (no error).
|
||||
//
|
||||
// If there is an error, it will be of type [*PathError].
|
||||
func (fs *UnixFS) RemoveContents(name string) error {
|
||||
name, err := fs.unsafePath(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Unlike RemoveAll, we don't remove `name` itself, only it's contents.
|
||||
// So there is no need to check for a name of `.` here.
|
||||
|
||||
return fs.removeContents(name)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) unlinkat(dirfd int, name string, flags int) error {
|
||||
return ignoringEINTR(func() error {
|
||||
return unix.Unlinkat(dirfd, name, flags)
|
||||
@ -434,11 +450,11 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
|
||||
// While unix.Renameat ends up throwing a "device or resource busy" error,
|
||||
// that doesn't mean we are protecting the system properly.
|
||||
if oldname == "." {
|
||||
return convertErrorType(&PathError{
|
||||
return &PathError{
|
||||
Op: "rename",
|
||||
Path: oldname,
|
||||
Err: ErrBadPathResolution,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Stat the old target to return proper errors.
|
||||
if _, err := fs.Lstatat(olddirfd, oldname); err != nil {
|
||||
@ -449,11 +465,11 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
|
||||
if err != nil {
|
||||
closeFd2()
|
||||
if !errors.Is(err, ErrNotExist) {
|
||||
return convertErrorType(err)
|
||||
return err
|
||||
}
|
||||
var pathErr *PathError
|
||||
if !errors.As(err, &pathErr) {
|
||||
return convertErrorType(err)
|
||||
return err
|
||||
}
|
||||
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
|
||||
return err
|
||||
@ -471,38 +487,41 @@ func (fs *UnixFS) Rename(oldpath, newpath string) error {
|
||||
// While unix.Renameat ends up throwing a "device or resource busy" error,
|
||||
// that doesn't mean we are protecting the system properly.
|
||||
if newname == "." {
|
||||
return convertErrorType(&PathError{
|
||||
return &PathError{
|
||||
Op: "rename",
|
||||
Path: newname,
|
||||
Err: ErrBadPathResolution,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Stat the new target to return proper errors.
|
||||
_, err = fs.Lstatat(newdirfd, newname)
|
||||
switch {
|
||||
case err == nil:
|
||||
return convertErrorType(&PathError{
|
||||
return &PathError{
|
||||
Op: "rename",
|
||||
Path: newname,
|
||||
Err: ErrExist,
|
||||
})
|
||||
}
|
||||
case !errors.Is(err, ErrNotExist):
|
||||
return err
|
||||
}
|
||||
return unix.Renameat(olddirfd, oldname, newdirfd, newname)
|
||||
if err := unix.Renameat(olddirfd, oldname, newdirfd, newname); err != nil {
|
||||
return &LinkError{Op: "rename", Old: oldpath, New: newpath, Err: err}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the named file.
|
||||
//
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func (fs *UnixFS) Stat(name string) (FileInfo, error) {
|
||||
return fs.fstat(name, 0)
|
||||
return fs._fstat("stat", name, 0)
|
||||
}
|
||||
|
||||
// Statat is like Stat but allows passing an existing directory file
|
||||
// descriptor rather than needing to resolve one.
|
||||
func (fs *UnixFS) Statat(dirfd int, name string) (FileInfo, error) {
|
||||
return fs.fstatat(dirfd, name, 0)
|
||||
return fs._fstatat("statat", dirfd, name, 0)
|
||||
}
|
||||
|
||||
// Lstat returns a FileInfo describing the named file.
|
||||
@ -512,30 +531,38 @@ func (fs *UnixFS) Statat(dirfd int, name string) (FileInfo, error) {
|
||||
//
|
||||
// If there is an error, it will be of type *PathError.
|
||||
func (fs *UnixFS) Lstat(name string) (FileInfo, error) {
|
||||
return fs.fstat(name, AT_SYMLINK_NOFOLLOW)
|
||||
return fs._fstat("lstat", name, AT_SYMLINK_NOFOLLOW)
|
||||
}
|
||||
|
||||
// Lstatat is like Lstat but allows passing an existing directory file
|
||||
// descriptor rather than needing to resolve one.
|
||||
func (fs *UnixFS) Lstatat(dirfd int, name string) (FileInfo, error) {
|
||||
return fs.fstatat(dirfd, name, AT_SYMLINK_NOFOLLOW)
|
||||
return fs._fstatat("lstatat", dirfd, name, AT_SYMLINK_NOFOLLOW)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) fstat(name string, flags int) (FileInfo, error) {
|
||||
return fs._fstat("fstat", name, flags)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) _fstat(op string, name string, flags int) (FileInfo, error) {
|
||||
dirfd, name, closeFd, err := fs.safePath(name)
|
||||
defer closeFd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.fstatat(dirfd, name, flags)
|
||||
return fs._fstatat(op, dirfd, name, flags)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) fstatat(dirfd int, name string, flags int) (FileInfo, error) {
|
||||
return fs._fstatat("fstatat", dirfd, name, flags)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) _fstatat(op string, dirfd int, name string, flags int) (FileInfo, error) {
|
||||
var s fileStat
|
||||
if err := ignoringEINTR(func() error {
|
||||
return unix.Fstatat(dirfd, name, &s.sys, flags)
|
||||
}); err != nil {
|
||||
return nil, &PathError{Op: "stat", Path: name, Err: err}
|
||||
return nil, ensurePathError(err, op, name)
|
||||
}
|
||||
fillFileStatFromSys(&s, name)
|
||||
return &s, nil
|
||||
@ -571,23 +598,42 @@ func (fs *UnixFS) Touch(path string, flag int, mode FileMode) (File, error) {
|
||||
if flag&O_CREATE == 0 {
|
||||
flag |= O_CREATE
|
||||
}
|
||||
dirfd, name, closeFd, err := fs.safePath(path)
|
||||
dirfd, name, closeFd, err, _ := fs.TouchPath(path)
|
||||
defer closeFd()
|
||||
if err == nil {
|
||||
return fs.OpenFileat(dirfd, name, flag, mode)
|
||||
}
|
||||
if !errors.Is(err, ErrNotExist) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fs.OpenFileat(dirfd, name, flag, mode)
|
||||
}
|
||||
|
||||
// TouchPath is like SafePath except that it will create any missing directories
|
||||
// in the path. Unlike SafePath, TouchPath returns an additional boolean which
|
||||
// indicates whether the parent directories already existed, this is intended to
|
||||
// be used as a way to know if the final destination could already exist.
|
||||
func (fs *UnixFS) TouchPath(path string) (int, string, func(), error, bool) {
|
||||
dirfd, name, closeFd, err := fs.safePath(path)
|
||||
switch {
|
||||
case err == nil:
|
||||
return dirfd, name, closeFd, nil, true
|
||||
case !errors.Is(err, ErrNotExist):
|
||||
return dirfd, name, closeFd, err, false
|
||||
}
|
||||
|
||||
var pathErr *PathError
|
||||
if !errors.As(err, &pathErr) {
|
||||
return nil, err
|
||||
return dirfd, name, closeFd, err, false
|
||||
}
|
||||
if err := fs.MkdirAll(pathErr.Path, 0o755); err != nil {
|
||||
return nil, err
|
||||
return dirfd, name, closeFd, err, false
|
||||
}
|
||||
// Try to open the file one more time after creating its parent directories.
|
||||
return fs.OpenFile(path, flag, mode)
|
||||
|
||||
// Close the previous file descriptor since we are going to be opening
|
||||
// a new one.
|
||||
closeFd()
|
||||
|
||||
// Run safe path again now that the parent directories have been created.
|
||||
dirfd, name, closeFd, err = fs.safePath(path)
|
||||
return dirfd, name, closeFd, err, false
|
||||
}
|
||||
|
||||
// WalkDir walks the file tree rooted at root, calling fn for each file or
|
||||
@ -629,43 +675,54 @@ func (fs *UnixFS) openat(dirfd int, name string, flag int, mode FileMode) (int,
|
||||
if err == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return 0, convertErrorType(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// If we are using openat2, we don't need the additional security checks.
|
||||
if fs.useOpenat2 {
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
// If we are not using openat2, do additional path checking. This assumes
|
||||
// that openat2 is using `RESOLVE_BENEATH` to avoid the same security
|
||||
// issue.
|
||||
if !fs.useOpenat2 {
|
||||
var finalPath string
|
||||
finalPath, err := filepath.EvalSymlinks(filepath.Join("/proc/self/fd/", strconv.Itoa(dirfd)))
|
||||
if err != nil {
|
||||
return fd, convertErrorType(err)
|
||||
}
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrNotExist) {
|
||||
return fd, fmt.Errorf("failed to evaluate symlink: %w", convertErrorType(err))
|
||||
}
|
||||
|
||||
// The target of one of the symlinks (EvalSymlinks is recursive)
|
||||
// does not exist. So get the path that does not exist and use
|
||||
// that for further validation instead.
|
||||
var pErr *PathError
|
||||
if ok := errors.As(err, &pErr); !ok {
|
||||
return fd, fmt.Errorf("failed to evaluate symlink: %w", convertErrorType(err))
|
||||
}
|
||||
finalPath = pErr.Path
|
||||
var finalPath string
|
||||
finalPath, err := filepath.EvalSymlinks(filepath.Join("/proc/self/fd/", strconv.Itoa(fd)))
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrNotExist) {
|
||||
return fd, fmt.Errorf("failed to evaluate symlink: %w", convertErrorType(err))
|
||||
}
|
||||
|
||||
// Check if the path is within our root.
|
||||
if !fs.unsafeIsPathInsideOfBase(finalPath) {
|
||||
return fd, convertErrorType(&PathError{
|
||||
Op: "openat",
|
||||
Path: name,
|
||||
Err: ErrBadPathResolution,
|
||||
})
|
||||
// The target of one of the symlinks (EvalSymlinks is recursive)
|
||||
// does not exist. So get the path that does not exist and use
|
||||
// that for further validation instead.
|
||||
var pErr *PathError
|
||||
if !errors.As(err, &pErr) {
|
||||
return fd, fmt.Errorf("failed to evaluate symlink: %w", convertErrorType(err))
|
||||
}
|
||||
|
||||
// Update the final path to whatever directory or path didn't exist while
|
||||
// recursing any symlinks.
|
||||
finalPath = pErr.Path
|
||||
// Ensure the error is wrapped correctly.
|
||||
err = convertErrorType(err)
|
||||
}
|
||||
|
||||
// Check if the path is within our root.
|
||||
if !fs.unsafeIsPathInsideOfBase(finalPath) {
|
||||
op := "openat"
|
||||
if fs.useOpenat2 {
|
||||
op = "openat2"
|
||||
}
|
||||
return fd, &PathError{
|
||||
Op: op,
|
||||
Path: name,
|
||||
Err: ErrBadPathResolution,
|
||||
}
|
||||
}
|
||||
return fd, nil
|
||||
|
||||
// Return the file descriptor and any potential error.
|
||||
return fd, err
|
||||
}
|
||||
|
||||
// _openat is a wrapper around unix.Openat. This method should never be directly
|
||||
@ -683,11 +740,11 @@ func (fs *UnixFS) _openat(dirfd int, name string, flag int, mode uint32) (int, e
|
||||
case err == nil:
|
||||
return fd, nil
|
||||
case err == unix.EINTR:
|
||||
return 0, err
|
||||
return fd, err
|
||||
case err == unix.EAGAIN:
|
||||
return 0, err
|
||||
return fd, err
|
||||
default:
|
||||
return 0, &PathError{Op: "openat", Path: name, Err: err}
|
||||
return fd, ensurePathError(err, "openat", name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -697,7 +754,7 @@ func (fs *UnixFS) _openat(dirfd int, name string, flag int, mode uint32) (int, e
|
||||
// present in Kernel 5.6 and above.
|
||||
//
|
||||
// This method should never be directly called, use `openat` instead.
|
||||
func (fs *UnixFS) _openat2(dirfd int, name string, flag uint64, mode uint64) (int, error) {
|
||||
func (fs *UnixFS) _openat2(dirfd int, name string, flag, mode uint64) (int, error) {
|
||||
// Ensure the O_CLOEXEC flag is set.
|
||||
// Go sets this when using the os package, but since we are directly using
|
||||
// the unix package we need to set it ourselves.
|
||||
@ -722,11 +779,11 @@ func (fs *UnixFS) _openat2(dirfd int, name string, flag uint64, mode uint64) (in
|
||||
case err == nil:
|
||||
return fd, nil
|
||||
case err == unix.EINTR:
|
||||
return 0, err
|
||||
return fd, err
|
||||
case err == unix.EAGAIN:
|
||||
return 0, err
|
||||
return fd, err
|
||||
default:
|
||||
return 0, &PathError{Op: "openat2", Path: name, Err: err}
|
||||
return fd, ensurePathError(err, "openat2", name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -745,11 +802,11 @@ func (fs *UnixFS) safePath(path string) (dirfd int, file string, closeFd func(),
|
||||
return
|
||||
}
|
||||
|
||||
// Check if dirfd was closed, this will happen if (*UnixFS).Close()
|
||||
// was called.
|
||||
fsDirfd := int(fs.dirfd.Load())
|
||||
if fsDirfd == -1 {
|
||||
err = ErrClosed
|
||||
// Open the base path. We use this as the sandbox root for any further
|
||||
// operations.
|
||||
var fsDirfd int
|
||||
fsDirfd, err = fs._openat(AT_EMPTY_PATH, fs.basePath, O_DIRECTORY|O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
@ -759,9 +816,8 @@ func (fs *UnixFS) safePath(path string) (dirfd int, file string, closeFd func(),
|
||||
dir, file = filepath.Split(name)
|
||||
// If dir is empty then name is not nested.
|
||||
if dir == "" {
|
||||
// We don't need to set closeFd here as it will default to a NO-OP and
|
||||
// `fs.dirfd` is re-used until the filesystem is no-longer needed.
|
||||
dirfd = fsDirfd
|
||||
closeFd = func() { _ = unix.Close(dirfd) }
|
||||
|
||||
// Return dirfd, name, an empty closeFd func, and no error
|
||||
return
|
||||
@ -771,26 +827,36 @@ func (fs *UnixFS) safePath(path string) (dirfd int, file string, closeFd func(),
|
||||
// trim slashes.
|
||||
dir = strings.TrimSuffix(dir, "/")
|
||||
dirfd, err = fs.openat(fsDirfd, dir, O_DIRECTORY|O_RDONLY, 0)
|
||||
if dirfd != 0 {
|
||||
if err != nil {
|
||||
// An error occurred while opening the directory, but we already opened
|
||||
// the filesystem root, so we still need to ensure it gets closed.
|
||||
closeFd = func() { _ = unix.Close(fsDirfd) }
|
||||
} else {
|
||||
// Set closeFd to close the newly opened directory file descriptor.
|
||||
closeFd = func() { _ = unix.Close(dirfd) }
|
||||
closeFd = func() {
|
||||
_ = unix.Close(dirfd)
|
||||
_ = unix.Close(fsDirfd)
|
||||
}
|
||||
}
|
||||
|
||||
// Return dirfd, name, the closeFd func, and err
|
||||
return
|
||||
}
|
||||
|
||||
// unsafePath prefixes the given path and prefixes it with the filesystem's
|
||||
// base path, cleaning the result. The path returned by this function may not
|
||||
// be inside the filesystem's base path, additional checks are required to
|
||||
// safely use paths returned by this function.
|
||||
// unsafePath strips and joins the given path with the filesystem's base path,
|
||||
// cleaning the result. The cleaned path is then checked if it starts with the
|
||||
// filesystem's base path to obvious any obvious path traversal escapes. The
|
||||
// fully resolved path (if symlinks are followed) may not be within the
|
||||
// filesystem's base path, additional checks are required to safely use paths
|
||||
// returned by this function.
|
||||
func (fs *UnixFS) unsafePath(path string) (string, error) {
|
||||
// Calling filepath.Clean on the joined directory will resolve it to the
|
||||
// absolute path, removing any ../ type of resolution arguments, and leaving
|
||||
// us with a direct path link.
|
||||
// Calling filepath.Clean on the path will resolve it to it's absolute path,
|
||||
// removing any path traversal arguments (such as ..), leaving us with an
|
||||
// absolute path we can then use.
|
||||
//
|
||||
// This will also trim the existing root path off the beginning of the path
|
||||
// passed to the function since that can get a bit messy.
|
||||
// This will also trim the filesystem's base path from the given path and
|
||||
// join the base path back on to ensure the path starts with the base path
|
||||
// without appending it twice.
|
||||
r := filepath.Clean(filepath.Join(fs.basePath, strings.TrimPrefix(path, fs.basePath)))
|
||||
|
||||
if fs.unsafeIsPathInsideOfBase(r) {
|
||||
@ -817,6 +883,10 @@ func (fs *UnixFS) unsafePath(path string) (string, error) {
|
||||
|
||||
// unsafeIsPathInsideOfBase checks if the given path is inside the filesystem's
|
||||
// base path.
|
||||
//
|
||||
// NOTE: this method doesn't clean the given path or attempt to join the
|
||||
// filesystem's base path. This is purely a basic prefix check against the
|
||||
// given path.
|
||||
func (fs *UnixFS) unsafeIsPathInsideOfBase(path string) bool {
|
||||
return strings.HasPrefix(
|
||||
strings.TrimSuffix(path, "/")+"/",
|
||||
|
||||
@ -9,6 +9,9 @@ import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/pterodactyl/wings/internal/ufs"
|
||||
@ -35,8 +38,8 @@ func newTestUnixFS() (*testUnixFS, error) {
|
||||
if err := os.Mkdir(root, 0o755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO: test both disabled and enabled.
|
||||
fs, err := ufs.NewUnixFS(root, false)
|
||||
// fmt.Println(tmpDir)
|
||||
fs, err := ufs.NewUnixFS(root, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -48,6 +51,323 @@ func newTestUnixFS() (*testUnixFS, error) {
|
||||
return tfs, nil
|
||||
}
|
||||
|
||||
func TestUnixFS(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// Test creating a file within the root.
|
||||
_, _, closeFd, err := fs.SafePath("/")
|
||||
closeFd()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := fs.Touch("directory/file", ufs.O_RDWR, 0o644)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
// Test creating a file within the root.
|
||||
f, err = fs.Create("test")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
// Test stating a file within the root.
|
||||
if _, err := fs.Stat("test"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test creating a directory within the root.
|
||||
if err := fs.Mkdir("ima_directory", 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test creating a nested directory within the root.
|
||||
if err := fs.Mkdir("ima_directory/ima_nother_directory", 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test creating a file inside a directory within the root.
|
||||
f, err = fs.Create("ima_directory/ima_file")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
// Test listing directory entries.
|
||||
if _, err := fs.ReadDir("ima_directory"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test symlink pointing outside the root.
|
||||
if err := os.Symlink(fs.TmpDir, filepath.Join(fs.Root, "ima_bad_link")); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
f, err = fs.Create("ima_bad_link/ima_bad_file")
|
||||
if err == nil {
|
||||
_ = f.Close()
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
if err := fs.Mkdir("ima_bad_link/ima_bad_directory", 0o755); err == nil {
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
|
||||
// Test symlink pointing outside the root inside a parent directory.
|
||||
if err := fs.Symlink(fs.TmpDir, filepath.Join(fs.Root, "ima_directory/ima_bad_link")); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err := fs.Mkdir("ima_directory/ima_bad_link/ima_bad_directory", 0o755); err == nil {
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
|
||||
// Test symlink pointing outside the root with a child directory.
|
||||
if err := os.Mkdir(filepath.Join(fs.TmpDir, "ima_directory"), 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
f, err = fs.Create("ima_bad_link/ima_directory/ima_bad_file")
|
||||
if err == nil {
|
||||
_ = f.Close()
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
if err := fs.Mkdir("ima_bad_link/ima_directory/ima_bad_directory", 0o755); err == nil {
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := fs.ReadDir("ima_bad_link/ima_directory"); err == nil {
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
|
||||
// Create multiple nested directories.
|
||||
if err := fs.MkdirAll("ima_directory/ima_directory/ima_directory/ima_directory", 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if _, err := fs.ReadDir("ima_directory/ima_directory"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test creating a directory under a symlink with a pre-existing directory.
|
||||
if err := fs.MkdirAll("ima_bad_link/ima_directory/ima_bad_directory/ima_bad_directory", 0o755); err == nil {
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
|
||||
// Test deletion
|
||||
if err := fs.Remove("test"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err := fs.Remove("ima_bad_link"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test recursive deletion
|
||||
if err := fs.RemoveAll("ima_directory"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Test recursive deletion underneath a bad symlink
|
||||
if err := fs.Mkdir("ima_directory", 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err := fs.Symlink(fs.TmpDir, filepath.Join(fs.Root, "ima_directory/ima_bad_link")); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
if err := fs.RemoveAll("ima_directory/ima_bad_link/ima_bad_file"); err == nil {
|
||||
t.Error("expected an error")
|
||||
return
|
||||
}
|
||||
|
||||
// This should delete the symlink itself.
|
||||
if err := fs.RemoveAll("ima_directory/ima_bad_link"); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
//for i := 0; i < 5; i++ {
|
||||
// dirName := "dir" + strconv.Itoa(i)
|
||||
// if err := fs.Mkdir(dirName, 0o755); err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
// }
|
||||
// for j := 0; j < 5; j++ {
|
||||
// f, err := fs.Create(filepath.Join(dirName, "file"+strconv.Itoa(j)))
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
// }
|
||||
// _ = f.Close()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//if err := fs.WalkDir2("", func(fd int, path string, info filesystem.DirEntry, err error) error {
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// fmt.Println(path)
|
||||
// return nil
|
||||
//}); err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
//}
|
||||
}
|
||||
|
||||
func TestUnixFS_Chmod(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Chown(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Lchown(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Chtimes(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Create(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Mkdir(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_MkdirAll(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
if err := fs.MkdirAll("/a/bunch/of/directories", 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: stat sanity check
|
||||
}
|
||||
|
||||
func TestUnixFS_Open(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_OpenFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_ReadDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Remove(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
@ -154,12 +474,12 @@ func TestUnixFS_Rename(t *testing.T) {
|
||||
|
||||
t.Run("file rename", func(t *testing.T) {
|
||||
// Create a directory to rename to something else.
|
||||
if f, err := fs.Create("test_file"); err != nil {
|
||||
f, err := fs.Create("test_file")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
} else {
|
||||
_ = f.Close()
|
||||
}
|
||||
_ = f.Close()
|
||||
|
||||
// Try to rename "test_file" to "file".
|
||||
if err := fs.Rename("test_file", "file"); err != nil {
|
||||
@ -175,6 +495,42 @@ func TestUnixFS_Rename(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnixFS_Stat(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Lstat(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Symlink(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
// TODO: implement
|
||||
}
|
||||
|
||||
func TestUnixFS_Touch(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
@ -253,3 +609,160 @@ func TestUnixFS_Touch(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnixFS_WalkDir(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
//for i := 0; i < 5; i++ {
|
||||
// dirName := "dir" + strconv.Itoa(i)
|
||||
// if err := fs.Mkdir(dirName, 0o755); err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
// }
|
||||
// for j := 0; j < 5; j++ {
|
||||
// f, err := fs.Create(filepath.Join(dirName, "file"+strconv.Itoa(j)))
|
||||
// if err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
// }
|
||||
// _ = f.Close()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//if err := fs.WalkDir(".", func(path string, info ufs.DirEntry, err error) error {
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// t.Log(path)
|
||||
// return nil
|
||||
//}); err != nil {
|
||||
// t.Error(err)
|
||||
// return
|
||||
//}
|
||||
}
|
||||
|
||||
func TestUnixFS_WalkDirat(t *testing.T) {
|
||||
t.Parallel()
|
||||
fs, err := newTestUnixFS()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
}
|
||||
defer fs.Cleanup()
|
||||
|
||||
for i := 0; i < 2; i++ {
|
||||
dirName := "base" + strconv.Itoa(i)
|
||||
if err := fs.Mkdir(dirName, 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
for j := 0; j < 1; j++ {
|
||||
f, err := fs.Create(filepath.Join(dirName, "file"+strconv.Itoa(j)))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
if err := fs.Mkdir(filepath.Join(dirName, "dir"+strconv.Itoa(j)), 0o755); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
f, err = fs.Create(filepath.Join(dirName, "dir"+strconv.Itoa(j), "file"+strconv.Itoa(j)))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
_ = f.Close()
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("walk starting at the filesystem root", func(t *testing.T) {
|
||||
pathsTraversed, err := fs.testWalkDirAt("")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
expect := []Path{
|
||||
{Name: ".", Relative: "."},
|
||||
{Name: "base0", Relative: "base0"},
|
||||
{Name: "dir0", Relative: "base0/dir0"},
|
||||
{Name: "file0", Relative: "base0/dir0/file0"},
|
||||
{Name: "file0", Relative: "base0/file0"},
|
||||
{Name: "base1", Relative: "base1"},
|
||||
{Name: "dir0", Relative: "base1/dir0"},
|
||||
{Name: "file0", Relative: "base1/dir0/file0"},
|
||||
{Name: "file0", Relative: "base1/file0"},
|
||||
}
|
||||
if !reflect.DeepEqual(pathsTraversed, expect) {
|
||||
t.Log(pathsTraversed)
|
||||
t.Log(expect)
|
||||
t.Error("walk doesn't match")
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("walk starting in a directory", func(t *testing.T) {
|
||||
pathsTraversed, err := fs.testWalkDirAt("base0")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
}
|
||||
expect := []Path{
|
||||
// TODO: what should relative actually be here?
|
||||
// The behaviour differs from walking the directory root vs a sub
|
||||
// directory. When walking from the root, dirfd is the directory we
|
||||
// are walking from and both name and relative are `.`. However,
|
||||
// when walking from a subdirectory, fd is the parent of the
|
||||
// subdirectory, and name is the subdirectory.
|
||||
{Name: "base0", Relative: "."},
|
||||
{Name: "dir0", Relative: "dir0"},
|
||||
{Name: "file0", Relative: "dir0/file0"},
|
||||
{Name: "file0", Relative: "file0"},
|
||||
}
|
||||
if !reflect.DeepEqual(pathsTraversed, expect) {
|
||||
t.Log(pathsTraversed)
|
||||
t.Log(expect)
|
||||
t.Error("walk doesn't match")
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type Path struct {
|
||||
Name string
|
||||
Relative string
|
||||
}
|
||||
|
||||
func (fs *testUnixFS) testWalkDirAt(path string) ([]Path, error) {
|
||||
dirfd, name, closeFd, err := fs.SafePath(path)
|
||||
defer closeFd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pathsTraversed []Path
|
||||
if err := fs.WalkDirat(dirfd, name, func(_ int, name, relative string, _ ufs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
pathsTraversed = append(pathsTraversed, Path{Name: name, Relative: relative})
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
slices.SortStableFunc(pathsTraversed, func(a, b Path) int {
|
||||
if a.Relative > b.Relative {
|
||||
return 1
|
||||
}
|
||||
if a.Relative < b.Relative {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
return pathsTraversed, nil
|
||||
}
|
||||
|
||||
@ -10,10 +10,6 @@
|
||||
|
||||
package ufs
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// mkdirAll is a recursive Mkdir implementation that properly handles symlinks.
|
||||
func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
|
||||
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
|
||||
@ -30,7 +26,7 @@ func (fs *UnixFS) mkdirAll(name string, mode FileMode) error {
|
||||
if dir.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return convertErrorType(&PathError{Op: "mkdir", Path: name, Err: unix.ENOTDIR})
|
||||
return &PathError{Op: "mkdir", Path: name, Err: ErrNotDirectory}
|
||||
}
|
||||
|
||||
// Slow path: make sure parent exists and then call Mkdir for path.
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
package ufs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"sync/atomic"
|
||||
)
|
||||
@ -33,7 +32,7 @@ func (w *CountedWriter) BytesWritten() int64 {
|
||||
// Error returns the error from the writer if any. If the error is an EOF, nil
|
||||
// will be returned.
|
||||
func (w *CountedWriter) Error() error {
|
||||
if errors.Is(w.err, io.EOF) {
|
||||
if w.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return w.err
|
||||
@ -54,9 +53,8 @@ func (w *CountedWriter) Write(p []byte) (int, error) {
|
||||
// TODO: is this how we actually want to handle errors with this?
|
||||
if err == io.EOF {
|
||||
return n, io.EOF
|
||||
} else {
|
||||
return n, nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (w *CountedWriter) ReadFrom(r io.Reader) (n int64, err error) {
|
||||
@ -92,7 +90,7 @@ func (r *CountedReader) BytesRead() int64 {
|
||||
// Error returns the error from the reader if any. If the error is an EOF, nil
|
||||
// will be returned.
|
||||
func (r *CountedReader) Error() error {
|
||||
if errors.Is(r.err, io.EOF) {
|
||||
if r.err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
return r.err
|
||||
@ -109,9 +107,9 @@ func (r *CountedReader) Read(p []byte) (int, error) {
|
||||
r.counter.Add(int64(n))
|
||||
r.err = err
|
||||
|
||||
// TODO: is this how we actually want to handle errors with this?
|
||||
if err == io.EOF {
|
||||
return n, io.EOF
|
||||
} else {
|
||||
return n, nil
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@ -52,60 +52,69 @@ func removeAll(fs unixFS, path string) error {
|
||||
parentDir, base := splitPath(path)
|
||||
|
||||
parent, err := fs.Open(parentDir)
|
||||
if errors.Is(err, ErrNotExist) {
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
// If parent does not exist, base cannot exist. Fail silently
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
if err := removeAllFrom(fs, parent, base); err != nil {
|
||||
if pathErr, ok := err.(*PathError); ok {
|
||||
pathErr.Path = parentDir + string(os.PathSeparator) + pathErr.Path
|
||||
err = pathErr
|
||||
err = convertErrorType(pathErr)
|
||||
} else {
|
||||
err = ensurePathError(err, "removeallfrom", base)
|
||||
}
|
||||
return convertErrorType(err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAllFrom(fs unixFS, parent File, base string) error {
|
||||
parentFd := int(parent.Fd())
|
||||
// Simple case: if Unlink (aka remove) works, we're done.
|
||||
err := fs.unlinkat(parentFd, base, 0)
|
||||
if err == nil || errors.Is(err, ErrNotExist) {
|
||||
func (fs *UnixFS) removeContents(path string) error {
|
||||
return removeContents(fs, path)
|
||||
}
|
||||
|
||||
func removeContents(fs unixFS, path string) error {
|
||||
if path == "" {
|
||||
// fail silently to retain compatibility with previous behavior
|
||||
// of RemoveAll. See issue https://go.dev/issue/28830.
|
||||
return nil
|
||||
}
|
||||
|
||||
// EISDIR means that we have a directory, and we need to
|
||||
// remove its contents.
|
||||
// EPERM or EACCES means that we don't have write permission on
|
||||
// the parent directory, but this entry might still be a directory
|
||||
// whose contents need to be removed.
|
||||
// Otherwise, just return the error.
|
||||
if err != unix.EISDIR && err != unix.EPERM && err != unix.EACCES {
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||
}
|
||||
// RemoveAll recurses by deleting the path base from
|
||||
// its parent directory
|
||||
parentDir, base := splitPath(path)
|
||||
|
||||
// Is this a directory we need to recurse into?
|
||||
var statInfo unix.Stat_t
|
||||
statErr := ignoringEINTR(func() error {
|
||||
return unix.Fstatat(parentFd, base, &statInfo, AT_SYMLINK_NOFOLLOW)
|
||||
})
|
||||
if statErr != nil {
|
||||
if errors.Is(statErr, ErrNotExist) {
|
||||
return nil
|
||||
parent, err := fs.Open(parentDir)
|
||||
if err != nil {
|
||||
if !errors.Is(err, ErrNotExist) {
|
||||
return err
|
||||
}
|
||||
return &PathError{Op: "fstatat", Path: base, Err: statErr}
|
||||
}
|
||||
if statInfo.Mode&unix.S_IFMT != unix.S_IFDIR {
|
||||
// Not a directory; return the error from the unix.Unlinkat.
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||
// If parent does not exist, base cannot exist. Fail silently
|
||||
return nil
|
||||
}
|
||||
defer parent.Close()
|
||||
|
||||
if err := removeContentsFrom(fs, parent, base); err != nil {
|
||||
if pathErr, ok := err.(*PathError); ok {
|
||||
pathErr.Path = parentDir + string(os.PathSeparator) + pathErr.Path
|
||||
err = convertErrorType(pathErr)
|
||||
} else {
|
||||
err = ensurePathError(err, "removecontentsfrom", base)
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeContentsFrom recursively removes all descendants of parent without
|
||||
// removing parent itself. Parent must be a directory.
|
||||
func removeContentsFrom(fs unixFS, parent File, base string) error {
|
||||
parentFd := int(parent.Fd())
|
||||
|
||||
// Remove the directory's entries.
|
||||
var recurseErr error
|
||||
for {
|
||||
const reqSize = 1024
|
||||
@ -168,6 +177,50 @@ func removeAllFrom(fs unixFS, parent File, base string) error {
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeAllFrom(fs unixFS, parent File, base string) error {
|
||||
parentFd := int(parent.Fd())
|
||||
|
||||
// Simple case: if Unlink (aka remove) works, we're done.
|
||||
err := fs.unlinkat(parentFd, base, 0)
|
||||
if err == nil || errors.Is(err, ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// EISDIR means that we have a directory, and we need to
|
||||
// remove its contents.
|
||||
// EPERM or EACCES means that we don't have write permission on
|
||||
// the parent directory, but this entry might still be a directory
|
||||
// whose contents need to be removed.
|
||||
// Otherwise, just return the error.
|
||||
if err != unix.EISDIR && err != unix.EPERM && err != unix.EACCES {
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||
}
|
||||
|
||||
// Is this a directory we need to recurse into?
|
||||
var statInfo unix.Stat_t
|
||||
statErr := ignoringEINTR(func() error {
|
||||
return unix.Fstatat(parentFd, base, &statInfo, AT_SYMLINK_NOFOLLOW)
|
||||
})
|
||||
if statErr != nil {
|
||||
if errors.Is(statErr, ErrNotExist) {
|
||||
return nil
|
||||
}
|
||||
return &PathError{Op: "fstatat", Path: base, Err: statErr}
|
||||
}
|
||||
if statInfo.Mode&unix.S_IFMT != unix.S_IFDIR {
|
||||
// Not a directory; return the error from the unix.Unlinkat.
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: err}
|
||||
}
|
||||
|
||||
// Remove all contents will remove the contents of the directory.
|
||||
//
|
||||
// It was split out of this function to allow the deletion of the
|
||||
// contents of a directory, without deleting the directory itself.
|
||||
recurseErr := removeContentsFrom(fs, parent, base)
|
||||
|
||||
// Remove the directory itself.
|
||||
unlinkErr := fs.unlinkat(parentFd, base, AT_REMOVEDIR)
|
||||
if unlinkErr == nil || errors.Is(unlinkErr, ErrNotExist) {
|
||||
@ -177,7 +230,8 @@ func removeAllFrom(fs unixFS, parent File, base string) error {
|
||||
if recurseErr != nil {
|
||||
return recurseErr
|
||||
}
|
||||
return &PathError{Op: "unlinkat", Path: base, Err: unlinkErr}
|
||||
|
||||
return ensurePathError(err, "unlinkat", base)
|
||||
}
|
||||
|
||||
// openFdAt opens path relative to the directory in fd.
|
||||
|
||||
@ -119,5 +119,6 @@ func walkDir(fs Filesystem, name string, d DirEntry, walkDirFn WalkDirFunc) erro
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -8,10 +8,10 @@ package ufs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
iofs "io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"unsafe"
|
||||
|
||||
@ -21,16 +21,12 @@ import (
|
||||
type WalkDiratFunc func(dirfd int, name, relative string, d DirEntry, err error) error
|
||||
|
||||
func (fs *UnixFS) WalkDirat(dirfd int, name string, fn WalkDiratFunc) error {
|
||||
if dirfd == 0 {
|
||||
// TODO: proper validation, ideally a dedicated function.
|
||||
dirfd = int(fs.dirfd.Load())
|
||||
}
|
||||
info, err := fs.Lstatat(dirfd, name)
|
||||
if err != nil {
|
||||
err = fn(dirfd, name, name, nil, err)
|
||||
err = fn(dirfd, name, ".", nil, err)
|
||||
} else {
|
||||
b := newScratchBuffer()
|
||||
err = fs.walkDir(b, dirfd, name, name, iofs.FileInfoToDirEntry(info), fn)
|
||||
err = fs.walkDir(b, dirfd, name, ".", iofs.FileInfoToDirEntry(info), fn)
|
||||
}
|
||||
if err == SkipDir || err == SkipAll {
|
||||
return nil
|
||||
@ -48,12 +44,14 @@ func (fs *UnixFS) walkDir(b []byte, parentfd int, name, relative string, d DirEn
|
||||
}
|
||||
|
||||
dirfd, err := fs.openat(parentfd, name, O_DIRECTORY|O_RDONLY, 0)
|
||||
if dirfd != 0 {
|
||||
defer unix.Close(dirfd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer unix.Close(dirfd)
|
||||
|
||||
dirs, err := fs.readDir(dirfd, name, b)
|
||||
dirs, err := fs.readDir(dirfd, name, relative, b)
|
||||
if err != nil {
|
||||
// Second call, to report ReadDir error.
|
||||
err = walkDirFn(dirfd, name, relative, d, err)
|
||||
@ -66,20 +64,28 @@ func (fs *UnixFS) walkDir(b []byte, parentfd int, name, relative string, d DirEn
|
||||
}
|
||||
|
||||
for _, d1 := range dirs {
|
||||
// TODO: the path.Join on this line may actually be partially incorrect.
|
||||
// If we are not walking starting at the root, relative will contain the
|
||||
// name of the directory we are starting the walk from, which will be
|
||||
// relative to the root of the filesystem instead of from where the walk
|
||||
// was initiated from.
|
||||
name := d1.Name()
|
||||
// This fancy logic ensures that if we start walking from a subdirectory
|
||||
// that we don't make the path relative to the root of the filesystem.
|
||||
//
|
||||
// ref; https://github.com/pterodactyl/panel/issues/5030
|
||||
if err := fs.walkDir(b, dirfd, d1.Name(), path.Join(relative, d1.Name()), d1, walkDirFn); err != nil {
|
||||
// For example, if we walk from the root of a filesystem, relative would
|
||||
// be "." and path.Join would end up just returning name. But if relative
|
||||
// was a subdirectory, relative could be "dir" and path.Join would make
|
||||
// it "dir/child" even though we are walking starting at dir.
|
||||
var rel string
|
||||
if relative == "." {
|
||||
rel = name
|
||||
} else {
|
||||
rel = path.Join(relative, name)
|
||||
}
|
||||
if err := fs.walkDir(b, dirfd, name, rel, d1, walkDirFn); err != nil {
|
||||
if err == SkipDir {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -97,7 +103,7 @@ func ReadDirMap[T any](fs *UnixFS, path string, fn func(DirEntry) (T, error)) ([
|
||||
}
|
||||
defer unix.Close(fd)
|
||||
|
||||
entries, err := fs.readDir(fd, ".", nil)
|
||||
entries, err := fs.readDir(fd, ".", path, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -112,6 +118,7 @@ func ReadDirMap[T any](fs *UnixFS, path string, fn func(DirEntry) (T, error)) ([
|
||||
}
|
||||
out[idx] = v
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@ -157,7 +164,7 @@ func nameFromDirent(de *unix.Dirent) (name []byte) {
|
||||
//
|
||||
// When the syscall constant is not recognized, this function falls back to a
|
||||
// Stat on the file system.
|
||||
func (fs *UnixFS) modeTypeFromDirent(fd int, de *unix.Dirent, osDirname, osBasename string) (FileMode, error) {
|
||||
func (fs *UnixFS) modeTypeFromDirent(de *unix.Dirent, fd int, name string) (FileMode, error) {
|
||||
switch de.Type {
|
||||
case unix.DT_REG:
|
||||
return 0, nil
|
||||
@ -176,7 +183,7 @@ func (fs *UnixFS) modeTypeFromDirent(fd int, de *unix.Dirent, osDirname, osBasen
|
||||
default:
|
||||
// If syscall returned unknown type (e.g., DT_UNKNOWN, DT_WHT), then
|
||||
// resolve actual mode by reading file information.
|
||||
return fs.modeType(fd, filepath.Join(osDirname, osBasename))
|
||||
return fs.modeType(fd, name)
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,12 +196,12 @@ func (fs *UnixFS) modeTypeFromDirent(fd int, de *unix.Dirent, osDirname, osBasen
|
||||
// from syscall or stat call. Therefore, mask out the additional file mode bits
|
||||
// that are provided by stat but not by the syscall, so users can rely on their
|
||||
// values.
|
||||
func (fs *UnixFS) modeType(dirfd int, name string) (os.FileMode, error) {
|
||||
func (fs *UnixFS) modeType(dirfd int, name string) (FileMode, error) {
|
||||
fi, err := fs.Lstatat(dirfd, name)
|
||||
if err == nil {
|
||||
return fi.Mode() & ModeType, nil
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("ufs: error finding mode type for %s during readDir: %w", name, err)
|
||||
}
|
||||
return 0, err
|
||||
return fi.Mode() & ModeType, nil
|
||||
}
|
||||
|
||||
var minimumScratchBufferSize = os.Getpagesize()
|
||||
@ -203,7 +210,7 @@ func newScratchBuffer() []byte {
|
||||
return make([]byte, minimumScratchBufferSize)
|
||||
}
|
||||
|
||||
func (fs *UnixFS) readDir(fd int, name string, b []byte) ([]DirEntry, error) {
|
||||
func (fs *UnixFS) readDir(fd int, name, relative string, b []byte) ([]DirEntry, error) {
|
||||
scratchBuffer := b
|
||||
if scratchBuffer == nil || len(scratchBuffer) < minimumScratchBufferSize {
|
||||
scratchBuffer = newScratchBuffer()
|
||||
@ -220,7 +227,7 @@ func (fs *UnixFS) readDir(fd int, name string, b []byte) ([]DirEntry, error) {
|
||||
if err == unix.EINTR {
|
||||
continue
|
||||
}
|
||||
return nil, convertErrorType(err)
|
||||
return nil, ensurePathError(err, "getdents", name)
|
||||
}
|
||||
if n <= 0 {
|
||||
// end of directory: normal exit
|
||||
@ -245,23 +252,29 @@ func (fs *UnixFS) readDir(fd int, name string, b []byte) ([]DirEntry, error) {
|
||||
}
|
||||
|
||||
childName := string(nameSlice)
|
||||
mt, err := fs.modeTypeFromDirent(fd, &sde, name, childName)
|
||||
mt, err := fs.modeTypeFromDirent(&sde, fd, childName)
|
||||
if err != nil {
|
||||
return nil, convertErrorType(err)
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, &dirent{name: childName, path: name, modeType: mt, dirfd: fd, fs: fs})
|
||||
var rel string
|
||||
if relative == "." {
|
||||
rel = name
|
||||
} else {
|
||||
rel = path.Join(relative, childName)
|
||||
}
|
||||
entries = append(entries, &dirent{dirfd: fd, name: childName, path: rel, modeType: mt, fs: fs})
|
||||
}
|
||||
}
|
||||
|
||||
// dirent stores the name and file system mode type of discovered file system
|
||||
// entries.
|
||||
type dirent struct {
|
||||
dirfd int
|
||||
name string
|
||||
path string
|
||||
modeType FileMode
|
||||
|
||||
dirfd int
|
||||
fs *UnixFS
|
||||
fs *UnixFS
|
||||
}
|
||||
|
||||
func (de dirent) Name() string {
|
||||
@ -281,6 +294,7 @@ func (de dirent) Info() (FileInfo, error) {
|
||||
return nil, nil
|
||||
}
|
||||
return de.fs.Lstatat(de.dirfd, de.name)
|
||||
// return de.fs.Lstat(de.path)
|
||||
}
|
||||
|
||||
func (de dirent) Open() (File, error) {
|
||||
@ -288,6 +302,7 @@ func (de dirent) Open() (File, error) {
|
||||
return nil, nil
|
||||
}
|
||||
return de.fs.OpenFileat(de.dirfd, de.name, O_RDONLY, 0)
|
||||
// return de.fs.OpenFile(de.path, O_RDONLY, 0)
|
||||
}
|
||||
|
||||
// reset releases memory held by entry err and name, and resets mode type to 0.
|
||||
@ -295,4 +310,5 @@ func (de *dirent) reset() {
|
||||
de.name = ""
|
||||
de.path = ""
|
||||
de.modeType = 0
|
||||
de.dirfd = 0
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package parser
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -11,7 +12,6 @@ import (
|
||||
"github.com/apex/log"
|
||||
"github.com/beevik/etree"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/icza/dyno"
|
||||
"github.com/magiconair/properties"
|
||||
"gopkg.in/ini.v1"
|
||||
@ -191,7 +191,7 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
||||
// Parse parses a given configuration file and updates all the values within
|
||||
// as defined in the API response from the Panel.
|
||||
func (f *ConfigurationFile) Parse(file ufs.File) error {
|
||||
//log.WithField("path", path).WithField("parser", f.Parser.String()).Debug("parsing server configuration file")
|
||||
// log.WithField("path", path).WithField("parser", f.Parser.String()).Debug("parsing server configuration file")
|
||||
|
||||
// What the fuck is going on here?
|
||||
if mb, err := json.Marshal(config.Get()); err != nil {
|
||||
|
||||
@ -3,6 +3,7 @@ package remote
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@ -15,7 +16,6 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/system"
|
||||
)
|
||||
|
||||
@ -2,11 +2,11 @@ package remote
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/apex/log"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/parser"
|
||||
)
|
||||
|
||||
@ -2,6 +2,7 @@ package downloader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
@ -14,7 +15,6 @@ import (
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/pterodactyl/wings/server"
|
||||
|
||||
@ -168,7 +168,6 @@ func RequireAuthorization() gin.HandlerFunc {
|
||||
// We don't put this value outside this function since the node's authentication
|
||||
// token can be changed on the fly and the config.Get() call returns a copy, so
|
||||
// if it is rotated this value will never properly get updated.
|
||||
token := config.Get().AuthenticationToken
|
||||
auth := strings.SplitN(c.GetHeader("Authorization"), " ", 2)
|
||||
if len(auth) != 2 || auth[0] != "Bearer" {
|
||||
c.Header("WWW-Authenticate", "Bearer")
|
||||
@ -179,7 +178,7 @@ func RequireAuthorization() gin.HandlerFunc {
|
||||
// All requests to Wings must be authorized with the authentication token present in
|
||||
// the Wings configuration file. Remeber, all requests to Wings come from the Panel
|
||||
// backend, or using a signed JWT for temporary authentication.
|
||||
if subtle.ConstantTimeCompare([]byte(auth[1]), []byte(token)) != 1 {
|
||||
if subtle.ConstantTimeCompare([]byte(auth[1]), []byte(config.Get().Token.Token)) != 1 {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "You are not authorized to access this endpoint."})
|
||||
return
|
||||
}
|
||||
|
||||
@ -2,10 +2,10 @@ package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-json"
|
||||
ws "github.com/gorilla/websocket"
|
||||
|
||||
"github.com/pterodactyl/wings/router/middleware"
|
||||
|
||||
@ -2,11 +2,11 @@ package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/events"
|
||||
"github.com/pterodactyl/wings/system"
|
||||
|
||||
@ -2,6 +2,7 @@ package websocket
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
@ -14,7 +15,6 @@ import (
|
||||
"github.com/apex/log"
|
||||
"github.com/gbrlsnchs/jwt/v3"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/goccy/go-json"
|
||||
"github.com/google/uuid"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import (
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/mholt/archives"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
@ -19,9 +19,10 @@ import (
|
||||
"github.com/pterodactyl/wings/server/filesystem"
|
||||
)
|
||||
|
||||
var format = archiver.CompressedArchive{
|
||||
Compression: archiver.Gz{},
|
||||
Archival: archiver.Tar{},
|
||||
var format = archives.CompressedArchive{
|
||||
Compression: archives.Gz{},
|
||||
Archival: archives.Tar{},
|
||||
Extraction: archives.Tar{},
|
||||
}
|
||||
|
||||
type AdapterType string
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/mholt/archives"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/remote"
|
||||
@ -93,7 +93,7 @@ func (b *LocalBackup) Restore(ctx context.Context, _ io.Reader, callback Restore
|
||||
if writeLimit := int64(config.Get().System.Backups.WriteLimit * 1024 * 1024); writeLimit > 0 {
|
||||
reader = ratelimit.Reader(f, ratelimit.NewBucketWithRate(float64(writeLimit), writeLimit))
|
||||
}
|
||||
if err := format.Extract(ctx, reader, nil, func(ctx context.Context, f archiver.File) error {
|
||||
if err := format.Extract(ctx, reader, func(ctx context.Context, f archives.FileInfo) error {
|
||||
r, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -12,7 +12,7 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/cenkalti/backoff/v4"
|
||||
"github.com/juju/ratelimit"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/mholt/archives"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/remote"
|
||||
@ -93,7 +93,7 @@ func (s *S3Backup) Restore(ctx context.Context, r io.Reader, callback RestoreCal
|
||||
if writeLimit := int64(config.Get().System.Backups.WriteLimit * 1024 * 1024); writeLimit > 0 {
|
||||
reader = ratelimit.Reader(r, ratelimit.NewBucketWithRate(float64(writeLimit), writeLimit))
|
||||
}
|
||||
if err := format.Extract(ctx, reader, nil, func(ctx context.Context, f archiver.File) error {
|
||||
if err := format.Extract(ctx, reader, func(ctx context.Context, f archives.FileInfo) error {
|
||||
r, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -231,7 +231,6 @@ func (fu *s3FileUploader) uploadPart(ctx context.Context, part string, size int6
|
||||
|
||||
return nil
|
||||
}, fu.backoff(ctx))
|
||||
|
||||
if err != nil {
|
||||
if v, ok := err.(*backoff.PermanentError); ok {
|
||||
return "", v.Unwrap()
|
||||
|
||||
@ -10,7 +10,7 @@ import (
|
||||
"testing"
|
||||
|
||||
. "github.com/franela/goblin"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/mholt/archives"
|
||||
)
|
||||
|
||||
func TestArchive_Stream(t *testing.T) {
|
||||
@ -60,11 +60,11 @@ func TestArchive_Stream(t *testing.T) {
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Open the archive.
|
||||
genericFs, err := archiver.FileSystem(context.Background(), archivePath)
|
||||
genericFs, err := archives.FileSystem(context.Background(), archivePath, nil)
|
||||
g.Assert(err).IsNil()
|
||||
|
||||
// Assert that we are opening an archive.
|
||||
afs, ok := genericFs.(archiver.ArchiveFS)
|
||||
afs, ok := genericFs.(iofs.ReadDirFS)
|
||||
g.Assert(ok).IsTrue()
|
||||
|
||||
// Get the names of the files recursively from the archive.
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"io"
|
||||
"io/fs"
|
||||
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/mholt/archives"
|
||||
)
|
||||
|
||||
// FileFS allows accessing a file on disk using a consistent file system interface.
|
||||
@ -29,7 +29,7 @@ type FileFS struct {
|
||||
|
||||
// If file is compressed, setting this field will
|
||||
// transparently decompress reads.
|
||||
Compression archiver.Decompressor
|
||||
Compression archives.Decompressor
|
||||
}
|
||||
|
||||
// Open opens the named file, which must be the file used to create the file system.
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/klauspost/compress/zip"
|
||||
"github.com/mholt/archiver/v4"
|
||||
"github.com/mholt/archives"
|
||||
|
||||
"github.com/pterodactyl/wings/internal/ufs"
|
||||
"github.com/pterodactyl/wings/server/filesystem/archiverext"
|
||||
@ -58,8 +58,8 @@ func (fs *Filesystem) archiverFileSystem(ctx context.Context, p string) (iofs.FS
|
||||
}
|
||||
// Do not use defer to close `f`, it will likely be used later.
|
||||
|
||||
format, _, err := archiver.Identify(filepath.Base(p), f)
|
||||
if err != nil && !errors.Is(err, archiver.ErrNoMatch) {
|
||||
format, _, err := archives.Identify(ctx, filepath.Base(p), f)
|
||||
if err != nil && !errors.Is(err, archives.NoMatch) {
|
||||
_ = f.Close()
|
||||
return nil, err
|
||||
}
|
||||
@ -78,20 +78,20 @@ func (fs *Filesystem) archiverFileSystem(ctx context.Context, p string) (iofs.FS
|
||||
|
||||
if format != nil {
|
||||
switch ff := format.(type) {
|
||||
case archiver.Zip:
|
||||
case archives.Zip:
|
||||
// zip.Reader is more performant than ArchiveFS, because zip.Reader caches content information
|
||||
// and zip.Reader can open several content files concurrently because of io.ReaderAt requirement
|
||||
// while ArchiveFS can't.
|
||||
// zip.Reader doesn't suffer from issue #330 and #310 according to local test (but they should be fixed anyway)
|
||||
return zip.NewReader(f, info.Size())
|
||||
case archiver.Archival:
|
||||
return archiver.ArchiveFS{Stream: io.NewSectionReader(f, 0, info.Size()), Format: ff, Context: ctx}, nil
|
||||
case archiver.Compression:
|
||||
case archives.Extraction:
|
||||
return &archives.ArchiveFS{Stream: io.NewSectionReader(f, 0, info.Size()), Format: ff, Context: ctx}, nil
|
||||
case archives.Compression:
|
||||
return archiverext.FileFS{File: f, Compression: ff}, nil
|
||||
}
|
||||
}
|
||||
_ = f.Close()
|
||||
return nil, archiver.ErrNoMatch
|
||||
return nil, archives.NoMatch
|
||||
}
|
||||
|
||||
// SpaceAvailableForDecompression looks through a given archive and determines
|
||||
@ -105,7 +105,7 @@ func (fs *Filesystem) SpaceAvailableForDecompression(ctx context.Context, dir st
|
||||
|
||||
fsys, err := fs.archiverFileSystem(ctx, filepath.Join(dir, file))
|
||||
if err != nil {
|
||||
if errors.Is(err, archiver.ErrNoMatch) {
|
||||
if errors.Is(err, archives.NoMatch) {
|
||||
return newFilesystemError(ErrCodeUnknownArchive, err)
|
||||
}
|
||||
return err
|
||||
@ -147,9 +147,9 @@ func (fs *Filesystem) DecompressFile(ctx context.Context, dir string, file strin
|
||||
defer f.Close()
|
||||
|
||||
// Identify the type of archive we are dealing with.
|
||||
format, input, err := archiver.Identify(filepath.Base(file), f)
|
||||
format, input, err := archives.Identify(ctx, filepath.Base(file), f)
|
||||
if err != nil {
|
||||
if errors.Is(err, archiver.ErrNoMatch) {
|
||||
if errors.Is(err, archives.NoMatch) {
|
||||
return newFilesystemError(ErrCodeUnknownArchive, err)
|
||||
}
|
||||
return err
|
||||
@ -165,9 +165,9 @@ func (fs *Filesystem) DecompressFile(ctx context.Context, dir string, file strin
|
||||
|
||||
// ExtractStreamUnsafe .
|
||||
func (fs *Filesystem) ExtractStreamUnsafe(ctx context.Context, dir string, r io.Reader) error {
|
||||
format, input, err := archiver.Identify("archive.tar.gz", r)
|
||||
format, input, err := archives.Identify(ctx, "archive.tar.gz", r)
|
||||
if err != nil {
|
||||
if errors.Is(err, archiver.ErrNoMatch) {
|
||||
if errors.Is(err, archives.NoMatch) {
|
||||
return newFilesystemError(ErrCodeUnknownArchive, err)
|
||||
}
|
||||
return err
|
||||
@ -185,26 +185,24 @@ type extractStreamOptions struct {
|
||||
// File name of the archive.
|
||||
FileName string
|
||||
// Format of the archive.
|
||||
Format archiver.Format
|
||||
Format archives.Format
|
||||
// Reader for the archive.
|
||||
Reader io.Reader
|
||||
}
|
||||
|
||||
func (fs *Filesystem) extractStream(ctx context.Context, opts extractStreamOptions) error {
|
||||
|
||||
// See if it's a compressed archive, such as TAR or a ZIP
|
||||
ex, ok := opts.Format.(archiver.Extractor)
|
||||
ex, ok := opts.Format.(archives.Extractor)
|
||||
if !ok {
|
||||
|
||||
// If not, check if it's a single-file compression, such as
|
||||
// .log.gz, .sql.gz, and so on
|
||||
de, ok := opts.Format.(archiver.Decompressor)
|
||||
de, ok := opts.Format.(archives.Decompressor)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Strip the compression suffix
|
||||
p := filepath.Join(opts.Directory, strings.TrimSuffix(opts.FileName, opts.Format.Name()))
|
||||
p := filepath.Join(opts.Directory, strings.TrimSuffix(opts.FileName, opts.Format.Extension()))
|
||||
|
||||
// Make sure it's not ignored
|
||||
if err := fs.IsIgnored(p); err != nil {
|
||||
@ -259,7 +257,7 @@ func (fs *Filesystem) extractStream(ctx context.Context, opts extractStreamOptio
|
||||
}
|
||||
|
||||
// Decompress and extract archive
|
||||
return ex.Extract(ctx, opts.Reader, nil, func(ctx context.Context, f archiver.File) error {
|
||||
return ex.Extract(ctx, opts.Reader, func(ctx context.Context, f archives.FileInfo) error {
|
||||
if f.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/unix"
|
||||
"slices"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
@ -164,6 +166,8 @@ func (fs *Filesystem) DirectorySize(root string) (int64, error) {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var hardLinks []uint64
|
||||
|
||||
var size atomic.Int64
|
||||
err = fs.unixFS.WalkDirat(dirfd, name, func(dirfd int, name, _ string, d ufs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
@ -180,8 +184,16 @@ func (fs *Filesystem) DirectorySize(root string) (int64, error) {
|
||||
return errors.Wrap(err, "lstatat err")
|
||||
}
|
||||
|
||||
// TODO: detect if info is a hard-link and de-duplicate it.
|
||||
// ref; https://github.com/pterodactyl/wings/pull/181/files
|
||||
var sysFileInfo = info.Sys().(*unix.Stat_t)
|
||||
if sysFileInfo.Nlink > 1 {
|
||||
// Hard links have the same inode number
|
||||
if slices.Contains(hardLinks, sysFileInfo.Ino) {
|
||||
// Don't add hard links size twice
|
||||
return nil
|
||||
} else {
|
||||
hardLinks = append(hardLinks, sysFileInfo.Ino)
|
||||
}
|
||||
}
|
||||
|
||||
size.Add(info.Size())
|
||||
return nil
|
||||
|
||||
@ -480,9 +480,9 @@ func (fs *Filesystem) ListDirectory(p string) ([]Stat, error) {
|
||||
case a.IsDir() && b.IsDir():
|
||||
return 0
|
||||
case a.IsDir():
|
||||
return 1
|
||||
default:
|
||||
return -1
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@ -38,7 +38,7 @@ func (s *Stat) MarshalJSON() ([]byte, error) {
|
||||
Size: s.Size(),
|
||||
Directory: s.IsDir(),
|
||||
File: !s.IsDir(),
|
||||
Symlink: s.Mode().Perm()&ufs.ModeSymlink != 0,
|
||||
Symlink: s.Mode().Type()&ufs.ModeSymlink != 0,
|
||||
Mime: s.Mimetype,
|
||||
})
|
||||
}
|
||||
|
||||
@ -13,8 +13,8 @@ import (
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/mount"
|
||||
"github.com/docker/docker/client"
|
||||
|
||||
@ -161,7 +161,7 @@ func (s *Server) SetRestoring(state bool) {
|
||||
|
||||
// RemoveContainer removes the installation container for the server.
|
||||
func (ip *InstallationProcess) RemoveContainer() error {
|
||||
err := ip.client.ContainerRemove(ip.Server.Context(), ip.Server.ID()+"_installer", types.ContainerRemoveOptions{
|
||||
err := ip.client.ContainerRemove(ip.Server.Context(), ip.Server.ID()+"_installer", container.RemoveOptions{
|
||||
RemoveVolumes: true,
|
||||
Force: true,
|
||||
})
|
||||
@ -247,7 +247,7 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
||||
}
|
||||
|
||||
// Get the ImagePullOptions.
|
||||
imagePullOptions := types.ImagePullOptions{All: false}
|
||||
imagePullOptions := image.PullOptions{All: false}
|
||||
if registryAuth != nil {
|
||||
b64, err := registryAuth.Base64()
|
||||
if err != nil {
|
||||
@ -260,7 +260,7 @@ func (ip *InstallationProcess) pullInstallationImage() error {
|
||||
|
||||
r, err := ip.client.ImagePull(ip.Server.Context(), ip.Script.ContainerImage, imagePullOptions)
|
||||
if err != nil {
|
||||
images, ierr := ip.client.ImageList(ip.Server.Context(), types.ImageListOptions{})
|
||||
images, ierr := ip.client.ImageList(ip.Server.Context(), image.ListOptions{})
|
||||
if ierr != nil {
|
||||
// Well damn, something has gone really wrong here, just go ahead and abort there
|
||||
// isn't much anything we can do to try and self-recover from this.
|
||||
@ -332,7 +332,7 @@ func (ip *InstallationProcess) AfterExecute(containerId string) error {
|
||||
defer ip.RemoveContainer()
|
||||
|
||||
ip.Server.Log().WithField("container_id", containerId).Debug("pulling installation logs for server")
|
||||
reader, err := ip.client.ContainerLogs(ip.Server.Context(), containerId, types.ContainerLogsOptions{
|
||||
reader, err := ip.client.ContainerLogs(ip.Server.Context(), containerId, container.LogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Follow: false,
|
||||
@ -463,7 +463,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
||||
}
|
||||
|
||||
ip.Server.Log().WithField("container_id", r.ID).Info("running installation script for server in container")
|
||||
if err := ip.client.ContainerStart(ctx, r.ID, types.ContainerStartOptions{}); err != nil {
|
||||
if err := ip.client.ContainerStart(ctx, r.ID, container.StartOptions{}); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@ -498,7 +498,7 @@ func (ip *InstallationProcess) Execute() (string, error) {
|
||||
// the server configuration directory, as well as to a websocket listener so
|
||||
// that the process can be viewed in the panel by administrators.
|
||||
func (ip *InstallationProcess) StreamOutput(ctx context.Context, id string) error {
|
||||
opts := types.ContainerLogsOptions{ShowStdout: true, ShowStderr: true, Follow: true}
|
||||
opts := container.LogsOptions{ShowStdout: true, ShowStderr: true, Follow: true}
|
||||
reader, err := ip.client.ContainerLogs(ctx, id, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
@ -13,7 +14,6 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/gammazero/workerpool"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
|
||||
@ -29,6 +29,21 @@ func (s *Server) Mounts() []environment.Mount {
|
||||
},
|
||||
}
|
||||
|
||||
// Handle mounting a generated `/etc/passwd` if the feature is enabled.
|
||||
if passwd := config.Get().System.Passwd; passwd.Enable {
|
||||
s.Log().WithFields(log.Fields{"source_path": passwd.Directory}).Info("mouting generated /etc/{group,passwd} to workaround UID/GID issues")
|
||||
m = append(m, environment.Mount{
|
||||
Source: filepath.Join(passwd.Directory, "group"),
|
||||
Target: "/etc/group",
|
||||
ReadOnly: true,
|
||||
})
|
||||
m = append(m, environment.Mount{
|
||||
Source: filepath.Join(passwd.Directory, "passwd"),
|
||||
Target: "/etc/passwd",
|
||||
ReadOnly: true,
|
||||
})
|
||||
}
|
||||
|
||||
// Also include any of this server's custom mounts when returning them.
|
||||
return append(m, s.customMounts()...)
|
||||
}
|
||||
@ -56,14 +71,12 @@ func (s *Server) customMounts() []environment.Mount {
|
||||
if !strings.HasPrefix(source, filepath.Clean(allowed)) {
|
||||
continue
|
||||
}
|
||||
|
||||
mounted = true
|
||||
mounts = append(mounts, environment.Mount{
|
||||
Source: source,
|
||||
Target: target,
|
||||
ReadOnly: m.ReadOnly,
|
||||
})
|
||||
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"emperror.dev/errors"
|
||||
@ -161,7 +160,7 @@ func (s *Server) HandlePowerAction(action PowerAction, waitSeconds ...int) error
|
||||
|
||||
return s.Environment.Start(s.Context())
|
||||
case PowerActionTerminate:
|
||||
return s.Environment.Terminate(s.Context(), os.Kill)
|
||||
return s.Environment.Terminate(s.Context(), "SIGKILL")
|
||||
}
|
||||
|
||||
return errors.New("attempting to handle unknown power action")
|
||||
|
||||
@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@ -11,7 +12,6 @@ import (
|
||||
"emperror.dev/errors"
|
||||
"github.com/apex/log"
|
||||
"github.com/creasty/defaults"
|
||||
"github.com/goccy/go-json"
|
||||
|
||||
"github.com/pterodactyl/wings/config"
|
||||
"github.com/pterodactyl/wings/environment"
|
||||
|
||||
@ -28,6 +28,7 @@ func (s *Server) SyncWithEnvironment() {
|
||||
Mounts: s.Mounts(),
|
||||
Allocations: cfg.Allocations,
|
||||
Limits: cfg.Build,
|
||||
Labels: cfg.Labels,
|
||||
})
|
||||
|
||||
// For Docker specific environments we also want to update the configured image
|
||||
|
||||
@ -31,8 +31,8 @@ func NewSinkPool() *SinkPool {
|
||||
// On adds a channel to the sink pool instance.
|
||||
func (p *SinkPool) On(c chan []byte) {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
p.sinks = append(p.sinks, c)
|
||||
p.mu.Unlock()
|
||||
}
|
||||
|
||||
// Off removes a given channel from the sink pool. If no matching sink is found
|
||||
@ -69,13 +69,11 @@ func (p *SinkPool) Off(c chan []byte) {
|
||||
func (p *SinkPool) Destroy() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
for _, c := range p.sinks {
|
||||
if c != nil {
|
||||
close(c)
|
||||
}
|
||||
}
|
||||
|
||||
p.sinks = nil
|
||||
}
|
||||
|
||||
@ -98,6 +96,7 @@ func (p *SinkPool) Destroy() {
|
||||
func (p *SinkPool) Push(data []byte) {
|
||||
p.mu.RLock()
|
||||
defer p.mu.RUnlock()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(p.sinks))
|
||||
for _, c := range p.sinks {
|
||||
@ -105,15 +104,22 @@ func (p *SinkPool) Push(data []byte) {
|
||||
defer wg.Done()
|
||||
select {
|
||||
case c <- data:
|
||||
case <-time.After(time.Millisecond * 10):
|
||||
// If there is nothing in the channel to read, but we also cannot write
|
||||
// to the channel, just skip over sending data. If we don't do this you'll
|
||||
// end up blocking the application on the channel read below.
|
||||
if len(c) == 0 {
|
||||
break
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
// If we cannot send the message to the channel within 10ms,
|
||||
// then try to drop the oldest message from the channel, then
|
||||
// send our message.
|
||||
select {
|
||||
case <-c:
|
||||
// Only attempt to send the message if we were able to make
|
||||
// space for it on the channel.
|
||||
select {
|
||||
case c <- data:
|
||||
default:
|
||||
}
|
||||
default:
|
||||
// Do nothing, this is a fallthrough if there is nothing to
|
||||
// read from c.
|
||||
}
|
||||
<-c
|
||||
c <- data
|
||||
}
|
||||
}(c)
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ package system
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
@ -11,20 +10,11 @@ import (
|
||||
)
|
||||
|
||||
func MutexLocked(m *sync.RWMutex) bool {
|
||||
v := reflect.ValueOf(m).Elem()
|
||||
|
||||
state := v.FieldByName("w").FieldByName("state")
|
||||
|
||||
readerCountField := v.FieldByName("readerCount")
|
||||
// go1.20 changed readerCount to an atomic
|
||||
// ref; https://github.com/golang/go/commit/e509452727b469d89a3fc4a7d1cbf9d3f110efee
|
||||
var readerCount int64
|
||||
if readerCountField.Kind() == reflect.Struct {
|
||||
readerCount = readerCountField.FieldByName("v").Int()
|
||||
} else {
|
||||
readerCount = readerCountField.Int()
|
||||
unlocked := m.TryLock()
|
||||
if unlocked {
|
||||
m.Unlock()
|
||||
}
|
||||
return state.Int()&1 == 1 || readerCount > 0
|
||||
return !unlocked
|
||||
}
|
||||
|
||||
func TestSink(t *testing.T) {
|
||||
|
||||
@ -3,13 +3,13 @@ package system
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"emperror.dev/errors"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"sync"
|
||||
@ -8,7 +9,6 @@ import (
|
||||
"time"
|
||||
|
||||
. "github.com/franela/goblin"
|
||||
"github.com/goccy/go-json"
|
||||
)
|
||||
|
||||
func Test_Utils(t *testing.T) {
|
||||
|
||||
BIN
wings-api.paw
BIN
wings-api.paw
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user