mirror of
https://github.com/hacdias/webdav.git
synced 2026-09-24 04:01:54 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cef2e3b673 | ||
|
|
4fe4703e02 | ||
|
|
882c2200eb | ||
|
|
4f4045dbca | ||
|
|
20606cd795 | ||
|
|
c9ca42d89a | ||
|
|
8217bc0816 | ||
|
|
98f8f93ebb | ||
|
|
66049d9c08 | ||
|
|
0406449b05 | ||
|
|
d7745cff34 | ||
|
|
d8558a77ff | ||
|
|
ab5cf175ad | ||
|
|
f225b0418f | ||
|
|
d9600ee82d | ||
|
|
6359115c2d | ||
|
|
8574b461a9 | ||
|
|
1196ad506d | ||
|
|
4ac9a1041c | ||
|
|
27aad9d403 | ||
|
|
e06f26b095 | ||
|
|
04070a8ae8 | ||
|
|
bd0667aac1 | ||
|
|
b620cd5c88 | ||
|
|
c4c58fe420 | ||
|
|
0ff71ca067 | ||
|
|
efdaa784b9 | ||
|
|
7662d629e3 | ||
|
|
010ca576fb | ||
|
|
7b8aa1c422 | ||
|
|
2f98af7d1e | ||
|
|
50e1828b60 | ||
|
|
e78d59e374 | ||
|
|
de0a102ed8 | ||
|
|
bdfa98d00d | ||
|
|
f0e7e23ea8 | ||
|
|
065991a195 | ||
|
|
bb85514c8f | ||
|
|
c0af549a1f | ||
|
|
685d89162a | ||
|
|
a5496c9516 | ||
|
|
24abd52e23 | ||
|
|
1af4c723f4 | ||
|
|
2406836223 | ||
|
|
7ca822bcaf | ||
|
|
a98d0c2616 | ||
|
|
98e92d0c13 | ||
|
|
53416e57da | ||
|
|
ff6637f396 | ||
|
|
15fbcd391b | ||
|
|
70b3630d08 | ||
|
|
aa014b092b | ||
|
|
b4553710eb | ||
|
|
e07e55d97a | ||
|
|
cfee0f9f0b | ||
|
|
79eb02aba4 | ||
|
|
7bbe36e4c1 | ||
|
|
520b7df04d | ||
|
|
8f3a3298a8 | ||
|
|
1753aa2945 | ||
|
|
4645f6f46f | ||
|
|
e998e2a838 | ||
|
|
3b3dedc4eb | ||
|
|
8706802dac | ||
|
|
5676a1c382 | ||
|
|
c938367dc0 | ||
|
|
758410ba64 | ||
|
|
1bbf52e475 | ||
|
|
2c2a325215 | ||
|
|
f7e006b449 | ||
|
|
49f61eab79 | ||
|
|
5d1a68c4f2 | ||
|
|
04cee682fb | ||
|
|
19d128cfbd | ||
|
|
118a0dda88 | ||
|
|
aad8e563a7 | ||
|
|
8446f9bdae | ||
|
|
5226853fca | ||
|
|
e350dc989a | ||
|
|
65de8e8578 |
@@ -12,10 +12,10 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.x"
|
go-version: "1.26.x"
|
||||||
- run: go build .
|
- run: go build .
|
||||||
env:
|
env:
|
||||||
CGO_ENABLED: '0'
|
CGO_ENABLED: '0'
|
||||||
|
|||||||
@@ -18,22 +18,22 @@ jobs:
|
|||||||
id-token: write
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- name: Check out the repo
|
- name: Check out the repo
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v4
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v4
|
||||||
|
|
||||||
- name: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
- name: Login to GitHub Container Registry
|
- name: Login to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v4
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.actor }}
|
username: ${{ github.actor }}
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Extract metadata (tags, labels) for Docker
|
- name: Extract metadata (tags, labels) for Docker
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v6
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
hacdias/webdav
|
hacdias/webdav
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.x"
|
go-version: "1.26.x"
|
||||||
- uses: golangci/golangci-lint-action@v6
|
- uses: golangci/golangci-lint-action@v9
|
||||||
with:
|
with:
|
||||||
version: "v1.64"
|
version: "latest"
|
||||||
|
|||||||
@@ -12,11 +12,13 @@ jobs:
|
|||||||
goreleaser:
|
goreleaser:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v6
|
||||||
- uses: goreleaser/goreleaser-action@v6
|
with:
|
||||||
|
go-version: "1.26.x"
|
||||||
|
- uses: goreleaser/goreleaser-action@v7
|
||||||
with:
|
with:
|
||||||
distribution: goreleaser
|
distribution: goreleaser
|
||||||
version: '~> v2'
|
version: '~> v2'
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ jobs:
|
|||||||
name: test
|
name: test
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v6
|
||||||
- uses: actions/setup-go@v5
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: "1.24.x"
|
go-version: "1.26.x"
|
||||||
- name: Run test with coverage
|
- name: Run test with coverage
|
||||||
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
dist/
|
dist/
|
||||||
webdav
|
webdav
|
||||||
|
|||||||
+7
-3
@@ -38,16 +38,20 @@ builds:
|
|||||||
- goos: openbsd
|
- goos: openbsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 6
|
goarm: 6
|
||||||
|
# Broken as of Go 1.24, deprecated as of Go 1.26
|
||||||
- goos: freebsd
|
- goos: freebsd
|
||||||
goarch: arm
|
goarch: arm
|
||||||
goarm: 6
|
- goos: windows
|
||||||
|
goarch: arm
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
- name_template: "{{.Os}}-{{.Arch}}{{if .Arm}}v{{.Arm}}{{end}}-{{ .ProjectName }}"
|
||||||
format: tar.gz
|
formats:
|
||||||
|
- tar.gz
|
||||||
format_overrides:
|
format_overrides:
|
||||||
- goos: windows
|
- goos: windows
|
||||||
format: zip
|
formats:
|
||||||
|
- zip
|
||||||
|
|
||||||
release:
|
release:
|
||||||
github:
|
github:
|
||||||
|
|||||||
+1
-4
@@ -1,9 +1,7 @@
|
|||||||
FROM golang:1.24-alpine3.20 AS build
|
FROM golang:1.26-alpine3.22 AS build
|
||||||
|
|
||||||
ARG VERSION="untracked"
|
ARG VERSION="untracked"
|
||||||
|
|
||||||
RUN apk --update add ca-certificates
|
|
||||||
|
|
||||||
WORKDIR /webdav/
|
WORKDIR /webdav/
|
||||||
|
|
||||||
COPY ./go.mod ./
|
COPY ./go.mod ./
|
||||||
@@ -15,7 +13,6 @@ RUN go build -o main -trimpath -ldflags="-s -w -X 'github.com/hacdias/webdav/v5/
|
|||||||
|
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
|
||||||
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
|
||||||
COPY --from=build /webdav/main /bin/webdav
|
COPY --from=build /webdav/main /bin/webdav
|
||||||
|
|
||||||
EXPOSE 6065
|
EXPOSE 6065
|
||||||
|
|||||||
@@ -14,6 +14,14 @@ For a manual install, please refer to the [releases](https://github.com/hacdias/
|
|||||||
go install github.com/hacdias/webdav/v5@latest
|
go install github.com/hacdias/webdav/v5@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Homebrew
|
||||||
|
|
||||||
|
If you have Homebrew available on your system, you can also install `webdav` using it:
|
||||||
|
|
||||||
|
```
|
||||||
|
brew install webdav
|
||||||
|
```
|
||||||
|
|
||||||
### Docker
|
### Docker
|
||||||
|
|
||||||
Docker images are provided on both [GitHub's registry](https://github.com/hacdias/webdav/pkgs/container/webdav) and [Docker Hub](https://hub.docker.com/r/hacdias/webdav). You can pull the images using one of the following two commands. Note that this commands pull the latest released version. You can use specific tags to pin specific versions, or use `main` for the development branch.
|
Docker images are provided on both [GitHub's registry](https://github.com/hacdias/webdav/pkgs/container/webdav) and [Docker Hub](https://hub.docker.com/r/hacdias/webdav). You can pull the images using one of the following two commands. Note that this commands pull the latest released version. You can use specific tags to pin specific versions, or use `main` for the development branch.
|
||||||
@@ -30,26 +38,23 @@ docker pull hacdias/webdav:latest
|
|||||||
|
|
||||||
For usage information regarding the CLI, run `webdav --help`.
|
For usage information regarding the CLI, run `webdav --help`.
|
||||||
|
|
||||||
### Docker
|
### Container
|
||||||
|
|
||||||
To use with Docker, you need to provide a configuration file and mount the data directories. For example, let's take the following configuration file that simply sets the port to `6060` and the directory to `/data`.
|
To run the container, you can refer to the `compose.yml` file which provides a minimal setup. Additionally, you also need to create a configuration file, as [explained below](#configuration).
|
||||||
|
|
||||||
```yaml
|
The equivalent Docker command to the aforementioned compose file would be as follows:
|
||||||
port: 6060
|
|
||||||
directory: /data
|
|
||||||
```
|
|
||||||
|
|
||||||
You can now run with the following Docker command, where you mount the configuration file inside the container, and the data directory too, as well as forwarding the port 6060. You will need to change this to match your own configuration.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run \
|
docker run \
|
||||||
-p 6060:6060 \
|
-p 6065:6065 \
|
||||||
-v $(pwd)/config.yml:/config.yml:ro \
|
-v ./config.yml:/config.yml:ro \
|
||||||
-v $(pwd)/data:/data \
|
-v ./data:/data \
|
||||||
ghcr.io/hacdias/webdav -c /config.yml
|
ghcr.io/hacdias/webdav -c /config.yml
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using [fail2ban](#fail2ban-setup), it would be helpful to add the parameters listed below. They will assist in analyzing the log.
|
If you are using [fail2ban](#fail2ban-setup), it would be helpful to add the parameters listed below. They will assist in analyzing
|
||||||
|
the log.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
--log-driver journald \
|
--log-driver journald \
|
||||||
--name webdav \
|
--name webdav \
|
||||||
@@ -57,7 +62,8 @@ If you are using [fail2ban](#fail2ban-setup), it would be helpful to add the par
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
The configuration can be provided as a YAML, JSON or TOML file. Below is an example of a YAML configuration file with all the options available, as well as what they mean.
|
The configuration can be provided as a YAML, JSON or TOML file. Below is an example of a YAML configuration file with
|
||||||
|
all the options available, as well as what they mean.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
address: 0.0.0.0
|
address: 0.0.0.0
|
||||||
@@ -84,8 +90,9 @@ behindProxy: false
|
|||||||
|
|
||||||
# The directory that will be able to be accessed by the users when connecting.
|
# The directory that will be able to be accessed by the users when connecting.
|
||||||
# This directory will be used by users unless they have their own 'directory' defined.
|
# This directory will be used by users unless they have their own 'directory' defined.
|
||||||
# Default is '.' (current directory).
|
# By default it points to the working directory. In the case of the compose file above,
|
||||||
directory: .
|
# that is /data.
|
||||||
|
directory: /data
|
||||||
|
|
||||||
# The default permissions for users. This is a case insensitive option. Possible
|
# The default permissions for users. This is a case insensitive option. Possible
|
||||||
# permissions: C (Create), R (Read), U (Update), D (Delete). You can combine multiple
|
# permissions: C (Create), R (Read), U (Update), D (Delete). You can combine multiple
|
||||||
@@ -94,7 +101,8 @@ permissions: R
|
|||||||
|
|
||||||
# The default permissions rules for users. Default is none. Rules are applied
|
# The default permissions rules for users. Default is none. Rules are applied
|
||||||
# from last to first, that is, the first rule that matches the request, starting
|
# from last to first, that is, the first rule that matches the request, starting
|
||||||
# from the end, will be applied to the request.
|
# from the end, will be applied to the request. Rule paths are always relative to
|
||||||
|
# the user's directory.
|
||||||
rules: []
|
rules: []
|
||||||
|
|
||||||
# The behavior of redefining the rules for users. It can be:
|
# The behavior of redefining the rules for users. It can be:
|
||||||
@@ -115,59 +123,86 @@ log:
|
|||||||
colors: true
|
colors: true
|
||||||
# Logging outputs. You can have more than one output. Default is only 'stderr'.
|
# Logging outputs. You can have more than one output. Default is only 'stderr'.
|
||||||
outputs:
|
outputs:
|
||||||
- stderr
|
- stderr
|
||||||
|
|
||||||
# CORS configuration
|
# CORS configuration
|
||||||
cors:
|
cors:
|
||||||
# Whether or not CORS configuration should be applied. Default is 'false'.
|
# Whether or not CORS configuration should be applied. Default is 'false'.
|
||||||
enabled: true
|
enabled: true
|
||||||
credentials: true
|
credentials: true
|
||||||
allowed_headers:
|
# The following are the default CORS settings when it is enabled.
|
||||||
- Depth
|
|
||||||
allowed_hosts:
|
allowed_hosts:
|
||||||
- http://localhost:8080
|
- *
|
||||||
|
allowed_headers:
|
||||||
|
- Authorization
|
||||||
|
- Content-Type
|
||||||
|
- Depth
|
||||||
|
- Destination
|
||||||
|
- If
|
||||||
|
- Lock-Token
|
||||||
|
- Overwrite
|
||||||
|
- TimeOut
|
||||||
|
- Translate
|
||||||
allowed_methods:
|
allowed_methods:
|
||||||
|
- COPY
|
||||||
|
- DELETE
|
||||||
- GET
|
- GET
|
||||||
exposed_headers:
|
- HEAD
|
||||||
- Content-Length
|
- LOCK
|
||||||
- Content-Range
|
- UNLOCK
|
||||||
|
- MKCOL
|
||||||
|
- MOVE
|
||||||
|
- OPTIONS
|
||||||
|
- POST
|
||||||
|
- PROPFIND
|
||||||
|
- PROPPATCH
|
||||||
|
- PUT
|
||||||
|
exposed_headers: []
|
||||||
|
|
||||||
# The list of users. If the list is empty, then there will be no authentication.
|
# You define here the list of users.
|
||||||
# Otherwise, basic authentication will automatically be configured.
|
# Basic authentication is automatically be configured when users are detected
|
||||||
#
|
# below, else there will be no authentication.
|
||||||
# If you're delegating the authentication to a different service, you can proxy
|
# Customize to your needs and don't forget to comment out the users you don't need.
|
||||||
# the username using basic authentication, and then disable webdav's password
|
|
||||||
# check using the option:
|
|
||||||
#
|
|
||||||
# noPassword: true
|
|
||||||
users:
|
users:
|
||||||
# Example 'admin' user with plaintext password.
|
# Example 'admin' user with plaintext password.
|
||||||
- username: admin
|
- username: admin
|
||||||
password: admin
|
password: admin
|
||||||
|
|
||||||
# Example 'john' user with bcrypt encrypted password, with custom directory.
|
# Example 'john' user with bcrypt encrypted password, with custom directory.
|
||||||
# You can generate a bcrypt-encrypted password by using the 'webdav bcrypt'
|
# Tip: you can generate a bcrypt-encrypted password by using the 'webdav bcrypt'
|
||||||
# command lint utility.
|
# command lint utility, or htpasswd on Linux.
|
||||||
- username: john
|
- username: john
|
||||||
password: "{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"
|
password: "{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"
|
||||||
directory: /another/path
|
directory: /data/john
|
||||||
# Example user whose details will be picked up from the environment.
|
# Example user whose details will be picked up from the environment.
|
||||||
- username: "{env}ENV_USERNAME"
|
- username: "{env}ENV_USERNAME"
|
||||||
password: "{env}ENV_PASSWORD"
|
password: "{env}ENV_PASSWORD"
|
||||||
|
# Example user with advanced control over his permissions
|
||||||
- username: basic
|
- username: basic
|
||||||
password: basic
|
password: basic
|
||||||
# Override default permissions.
|
permissions: CRUD # Override default permissions.
|
||||||
permissions: CRUD
|
|
||||||
rules:
|
rules:
|
||||||
# With this rule, the user CANNOT access /some/files.
|
# With this rule, the user CANNOT access {user directory}/some/files.
|
||||||
- path: /some/file
|
- path: /some/file
|
||||||
permissions: none
|
permissions: none
|
||||||
# With this rule, the user CAN create, read, update and delete within /public/access.
|
# With this rule, the user CAN create, read, update and delete within
|
||||||
|
# {user directory}/public/access.
|
||||||
- path: /public/access/
|
- path: /public/access/
|
||||||
permissions: CRUD
|
permissions: CRUD
|
||||||
# With this rule, the user CAN read and update all files ending with .js. It uses
|
# With this rule, the user CAN read and update all files ending with .js.
|
||||||
# a regular expression.
|
# It uses a regular expression.
|
||||||
- regex: "^.+.js$"
|
- regex: "^.+.js$"
|
||||||
permissions: RU
|
permissions: RU
|
||||||
|
# Example user for android SeedVault backuping
|
||||||
|
- username: android
|
||||||
|
password: "{bcrypt}$2y$10$zEP6oofmXFeHaeMfBNLnP.DO8m.H.Mwhd24/TOX2MWLxAExXi4qgi"
|
||||||
|
directory: /data/android
|
||||||
|
permissions: CRUD
|
||||||
|
|
||||||
|
# If you're delegating the authentication to a different service, you can proxy
|
||||||
|
# the username using basic authentication, and then disable webdav's password
|
||||||
|
# check using the option:
|
||||||
|
# noPassword: true
|
||||||
```
|
```
|
||||||
|
|
||||||
### CORS
|
### CORS
|
||||||
@@ -181,7 +216,9 @@ The `allowed_*` properties are optional, the default value for each of them will
|
|||||||
|
|
||||||
### Reverse Proxy Service
|
### Reverse Proxy Service
|
||||||
|
|
||||||
When using a reverse proxy implementation, like Caddy, Nginx, or Apache, note that you need to forward the correct headers in order to avoid 502 errors. Here's a Nginx configuration example:
|
When using a reverse proxy implementation, like Caddy, Nginx, or Apache, note that you need to forward the correct headers in order to avoid 502 errors.
|
||||||
|
|
||||||
|
#### Nginx Configuration Example
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location / {
|
location / {
|
||||||
@@ -191,6 +228,33 @@ location / {
|
|||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_redirect off;
|
proxy_redirect off;
|
||||||
|
|
||||||
|
# Ensure COPY and MOVE commands work. Change https://example.com to the
|
||||||
|
# correct address where the WebDAV server will be deployed at.
|
||||||
|
set $dest $http_destination;
|
||||||
|
if ($http_destination ~ "^https://example.com(?<path>(.+))") {
|
||||||
|
set $dest /$path;
|
||||||
|
}
|
||||||
|
proxy_set_header Destination $dest;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Caddy Configuration Example
|
||||||
|
|
||||||
|
```Caddyfile
|
||||||
|
example.com {
|
||||||
|
tls internal # for local development
|
||||||
|
# tls [email protected] # so that Caddy gets certs for you via Letsencrypt
|
||||||
|
|
||||||
|
# Rewrites destination to remove host and include only the path e.g. /test.txt
|
||||||
|
@hasDest header_regexp dest ^https?://[^/]+(.*)$
|
||||||
|
header @hasDest Destination {re.dest.1}
|
||||||
|
|
||||||
|
# if running on the same network in docker you can just point to the service name e.g. webdav:6065
|
||||||
|
reverse_proxy 127.0.0.1:6065 {
|
||||||
|
header_up X-Real-IP {remote_host}
|
||||||
|
header_up REMOTE-HOST {remote_host}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
+6
-3
@@ -1,6 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
@@ -80,6 +81,8 @@ set WD_CERT.`,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
server := &http.Server{Handler: handler}
|
||||||
|
|
||||||
// Trap exiting signals
|
// Trap exiting signals
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
|
|
||||||
@@ -88,9 +91,9 @@ set WD_CERT.`,
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
if cfg.TLS {
|
if cfg.TLS {
|
||||||
err = http.ServeTLS(listener, handler, cfg.Cert, cfg.Key)
|
err = server.ServeTLS(listener, cfg.Cert, cfg.Key)
|
||||||
} else {
|
} else {
|
||||||
err = http.Serve(listener, handler)
|
err = server.Serve(listener)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
@@ -104,7 +107,7 @@ set WD_CERT.`,
|
|||||||
signal := <-quit
|
signal := <-quit
|
||||||
|
|
||||||
zap.L().Info("caught signal, shutting down", zap.Stringer("signal", signal))
|
zap.L().Info("caught signal, shutting down", zap.Stringer("signal", signal))
|
||||||
_ = listener.Close()
|
_ = server.Shutdown(context.Background())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
|
|||||||
+14
@@ -0,0 +1,14 @@
|
|||||||
|
name: webdav
|
||||||
|
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
container_name: webdav
|
||||||
|
image: 'webdav:test'
|
||||||
|
ports:
|
||||||
|
- '6065:6065'
|
||||||
|
security_opt:
|
||||||
|
- label=type:container_runtime_t
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
- ./config.yml:/config.yml:ro
|
||||||
@@ -1,34 +1,36 @@
|
|||||||
module github.com/hacdias/webdav/v5
|
module github.com/hacdias/webdav/v5
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0
|
github.com/coreos/go-systemd/v22 v22.7.0
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1
|
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||||
github.com/rs/cors v1.11.1
|
github.com/rs/cors v1.11.1
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.10.2
|
||||||
github.com/spf13/pflag v1.0.6
|
github.com/spf13/pflag v1.0.10
|
||||||
github.com/spf13/viper v1.20.0
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
github.com/studio-b12/gowebdav v0.9.0
|
github.com/studio-b12/gowebdav v0.12.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.1
|
||||||
golang.org/x/crypto v0.36.0
|
golang.org/x/crypto v0.50.0
|
||||||
golang.org/x/net v0.37.0
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260423152011-b9e53593a607
|
||||||
|
golang.org/x/net v0.53.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.8.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.14.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.7.1 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/sys v0.31.0 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/text v0.23.0 // indirect
|
golang.org/x/sys v0.43.0 // indirect
|
||||||
|
golang.org/x/text v0.36.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
|
github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA=
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
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.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
@@ -18,8 +17,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
@@ -27,40 +26,45 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f
|
|||||||
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/sagikazarmark/locafero v0.8.0 h1:mXaMVw7IqxNBxfv3LdWt9MDmcWDQ1fagDH918lOdVaQ=
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
github.com/sagikazarmark/locafero v0.8.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk=
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA=
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo=
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU=
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY=
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/studio-b12/gowebdav v0.9.0 h1:1j1sc9gQnNxbXXM4M/CebPOX4aXYtr7MojAVcN4dHjU=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/studio-b12/gowebdav v0.9.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
github.com/studio-b12/gowebdav v0.12.0 h1:kFRtQECt8jmVAvA6RHBz3geXUGJHUZA6/IKpOVUs5kM=
|
||||||
|
github.com/studio-b12/gowebdav v0.12.0/go.mod h1:bHA7t77X/QFExdeAnDzK6vKM34kEZAcE1OX4MfiwjkE=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260423152011-b9e53593a607 h1:WlWLkLEVGjGS9LziRuLo1Ut+UkpzbXxFQh94eUUVjeg=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/crypto/x509roots/fallback v0.0.0-20260423152011-b9e53593a607/go.mod h1:+UoQFNBq2p2wO+Q6ddVtYc25GZ6VNdOMyyrd4nrqrKs=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||||
|
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
|
||||||
|
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
|
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||||
|
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|||||||
+7
-2
@@ -3,6 +3,7 @@ package lib
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -53,6 +54,10 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) {
|
|||||||
// Configuration file settings
|
// Configuration file settings
|
||||||
v.AddConfigPath(".")
|
v.AddConfigPath(".")
|
||||||
v.AddConfigPath("/etc/webdav/")
|
v.AddConfigPath("/etc/webdav/")
|
||||||
|
if path, err := os.Executable(); err == nil {
|
||||||
|
v.AddConfigPath(filepath.Dir(path))
|
||||||
|
}
|
||||||
|
|
||||||
v.SetConfigName("config")
|
v.SetConfigName("config")
|
||||||
if filename != "" {
|
if filename != "" {
|
||||||
v.SetConfigFile(filename)
|
v.SetConfigFile(filename)
|
||||||
@@ -81,9 +86,9 @@ func ParseConfig(filename string, flags *pflag.FlagSet) (*Config, error) {
|
|||||||
v.SetDefault("Log.Format", "console")
|
v.SetDefault("Log.Format", "console")
|
||||||
v.SetDefault("Log.Outputs", []string{"stderr"})
|
v.SetDefault("Log.Outputs", []string{"stderr"})
|
||||||
v.SetDefault("Log.Colors", true)
|
v.SetDefault("Log.Colors", true)
|
||||||
v.SetDefault("CORS.Allowed_Headers", []string{"*"})
|
|
||||||
v.SetDefault("CORS.Allowed_Hosts", []string{"*"})
|
v.SetDefault("CORS.Allowed_Hosts", []string{"*"})
|
||||||
v.SetDefault("CORS.Allowed_Methods", []string{"*"})
|
v.SetDefault("CORS.Allowed_Headers", []string{"Authorization", "Content-Type", "Depth", "Destination", "If", "Lock-Token", "Overwrite"})
|
||||||
|
v.SetDefault("CORS.Allowed_Methods", []string{"COPY", "DELETE", "GET", "HEAD", "LOCK", "MKCOL", "MOVE", "OPTIONS", "POST", "PROPFIND", "PROPPATCH", "PUT", "UNLOCK"})
|
||||||
|
|
||||||
// Read and unmarshal configuration
|
// Read and unmarshal configuration
|
||||||
err := v.ReadInConfig()
|
err := v.ReadInConfig()
|
||||||
|
|||||||
+2
-2
@@ -51,9 +51,9 @@ func TestConfigDefaults(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, dir, cfg.Directory)
|
require.Equal(t, dir, cfg.Directory)
|
||||||
|
|
||||||
require.EqualValues(t, []string{"*"}, cfg.CORS.AllowedHeaders)
|
|
||||||
require.EqualValues(t, []string{"*"}, cfg.CORS.AllowedHosts)
|
require.EqualValues(t, []string{"*"}, cfg.CORS.AllowedHosts)
|
||||||
require.EqualValues(t, []string{"*"}, cfg.CORS.AllowedMethods)
|
require.EqualValues(t, []string{"Authorization", "Content-Type", "Depth", "Destination", "If", "Lock-Token", "Overwrite"}, cfg.CORS.AllowedHeaders)
|
||||||
|
require.EqualValues(t, []string{"COPY", "DELETE", "GET", "HEAD", "LOCK", "MKCOL", "MOVE", "OPTIONS", "POST", "PROPFIND", "PROPPATCH", "PUT", "UNLOCK"}, cfg.CORS.AllowedMethods)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfigCascade(t *testing.T) {
|
func TestConfigCascade(t *testing.T) {
|
||||||
|
|||||||
+1
-1
@@ -47,7 +47,7 @@ type noSniffFileInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (w noSniffFileInfo) ContentType(ctx context.Context) (contentType string, err error) {
|
func (w noSniffFileInfo) ContentType(ctx context.Context) (contentType string, err error) {
|
||||||
if mimeType := mime.TypeByExtension(path.Ext(w.FileInfo.Name())); mimeType != "" {
|
if mimeType := mime.TypeByExtension(path.Ext(w.Name())); mimeType != "" {
|
||||||
// We can figure out the mime from the extension.
|
// We can figure out the mime from the extension.
|
||||||
return mimeType, nil
|
return mimeType, nil
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+32
-10
@@ -23,6 +23,13 @@ type Handler struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(c *Config) (http.Handler, error) {
|
func NewHandler(c *Config) (http.Handler, error) {
|
||||||
|
ls := webdav.NewMemLS()
|
||||||
|
|
||||||
|
logFunc := func(r *http.Request, err error) {
|
||||||
|
lZap := getRequestLogger(r, c.BehindProxy)
|
||||||
|
lZap.Debug("handle webdav request", zap.String("method", r.Method), zap.String("path", r.URL.Path), zap.Error(err))
|
||||||
|
}
|
||||||
|
|
||||||
h := &Handler{
|
h := &Handler{
|
||||||
noPassword: c.NoPassword,
|
noPassword: c.NoPassword,
|
||||||
behindProxy: c.BehindProxy,
|
behindProxy: c.BehindProxy,
|
||||||
@@ -36,7 +43,11 @@ func NewHandler(c *Config) (http.Handler, error) {
|
|||||||
Dir: webdav.Dir(c.Directory),
|
Dir: webdav.Dir(c.Directory),
|
||||||
noSniff: c.NoSniff,
|
noSniff: c.NoSniff,
|
||||||
},
|
},
|
||||||
LockSystem: webdav.NewMemLS(),
|
LockSystem: &lockSystem{
|
||||||
|
LockSystem: ls,
|
||||||
|
directory: c.Directory,
|
||||||
|
},
|
||||||
|
Logger: logFunc,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
users: map[string]*handlerUser{},
|
users: map[string]*handlerUser{},
|
||||||
@@ -51,7 +62,11 @@ func NewHandler(c *Config) (http.Handler, error) {
|
|||||||
Dir: webdav.Dir(u.Directory),
|
Dir: webdav.Dir(u.Directory),
|
||||||
noSniff: c.NoSniff,
|
noSniff: c.NoSniff,
|
||||||
},
|
},
|
||||||
LockSystem: webdav.NewMemLS(),
|
LockSystem: &lockSystem{
|
||||||
|
LockSystem: ls,
|
||||||
|
directory: u.Directory,
|
||||||
|
},
|
||||||
|
Logger: logFunc,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -82,13 +97,12 @@ func NewHandler(c *Config) (http.Handler, error) {
|
|||||||
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
user := h.user
|
user := h.user
|
||||||
|
|
||||||
|
lZap := getRequestLogger(r, h.behindProxy)
|
||||||
|
|
||||||
// Authentication
|
// Authentication
|
||||||
if len(h.users) > 0 {
|
if len(h.users) > 0 {
|
||||||
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
|
||||||
|
|
||||||
// Retrieve the real client IP address using the updated helper function
|
|
||||||
remoteAddr := getRealRemoteIP(r, h.behindProxy)
|
|
||||||
|
|
||||||
// Gets the correct user for this request.
|
// Gets the correct user for this request.
|
||||||
username, password, ok := r.BasicAuth()
|
username, password, ok := r.BasicAuth()
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -99,26 +113,26 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
user, ok = h.users[username]
|
user, ok = h.users[username]
|
||||||
if !ok {
|
if !ok {
|
||||||
// Log invalid username
|
// Log invalid username
|
||||||
zap.L().Info("invalid username", zap.String("username", username), zap.String("remote_address", remoteAddr))
|
lZap.Info("invalid username", zap.String("username", username))
|
||||||
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !h.noPassword && !user.checkPassword(password) {
|
if !h.noPassword && !user.checkPassword(password) {
|
||||||
// Log invalid password
|
// Log invalid password
|
||||||
zap.L().Info("invalid password", zap.String("username", username), zap.String("remote_address", remoteAddr))
|
lZap.Info("invalid password", zap.String("username", username))
|
||||||
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log successful authorization
|
// Log successful authorization
|
||||||
zap.L().Info("user authorized", zap.String("username", username), zap.String("remote_address", remoteAddr))
|
lZap.Info("user authorized", zap.String("username", username))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the HTTP request into an internal request type
|
// Convert the HTTP request into an internal request type
|
||||||
req, err := newRequest(r, h.user.Prefix)
|
req, err := newRequest(r, h.user.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
zap.L().Info("invalid request path or destination", zap.Error(err))
|
lZap.Info("invalid request path or destination", zap.Error(err))
|
||||||
http.Error(w, "Invalid request path or destination", http.StatusBadRequest)
|
http.Error(w, "Invalid request path or destination", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -129,7 +143,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
return !os.IsNotExist(err)
|
return !os.IsNotExist(err)
|
||||||
})
|
})
|
||||||
|
|
||||||
zap.L().Debug("allowed & method & path", zap.Bool("allowed", allowed), zap.String("method", r.Method), zap.String("path", r.URL.Path))
|
lZap.Debug("allowed & method & path", zap.Bool("allowed", allowed), zap.String("method", r.Method), zap.String("path", r.URL.Path))
|
||||||
|
|
||||||
if !allowed {
|
if !allowed {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
@@ -166,6 +180,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
user.ServeHTTP(w, r)
|
user.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRequestLogger creates a zap.Logger using the request remote ip.
|
||||||
|
func getRequestLogger(r *http.Request, behindProxy bool) *zap.Logger {
|
||||||
|
// Retrieve the real client IP address using the updated helper function
|
||||||
|
remoteAddr := getRealRemoteIP(r, behindProxy)
|
||||||
|
|
||||||
|
return zap.L().With(zap.String("remote_address", remoteAddr))
|
||||||
|
}
|
||||||
|
|
||||||
// getRealRemoteIP retrieves the client's actual IP address, considering reverse proxies.
|
// getRealRemoteIP retrieves the client's actual IP address, considering reverse proxies.
|
||||||
func getRealRemoteIP(r *http.Request, behindProxy bool) string {
|
func getRealRemoteIP(r *http.Request, behindProxy bool) string {
|
||||||
if behindProxy {
|
if behindProxy {
|
||||||
|
|||||||
+52
-1
@@ -217,7 +217,7 @@ users:
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerRules(t *testing.T) {
|
func TestServerRulesRestrictive(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
dir := makeTestDirectory(t, map[string][]byte{
|
dir := makeTestDirectory(t, map[string][]byte{
|
||||||
@@ -290,6 +290,57 @@ users:
|
|||||||
|
|
||||||
err = client.Write("/c/b.txt", []byte("new"), 0666)
|
err = client.Write("/c/b.txt", []byte("new"), 0666)
|
||||||
require.ErrorContains(t, err, "403")
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
err = client.MkdirAll("/d/foo/bar", 0666)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = client.Write("/d/foo/bar/test.txt", []byte("test"), 0666)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServerRulesAdditive(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir := makeTestDirectory(t, map[string][]byte{
|
||||||
|
"foo.txt": []byte("foo"),
|
||||||
|
"bar.js": []byte("foo js"),
|
||||||
|
"a/foo.js": []byte("foo js"),
|
||||||
|
"a/foo.txt": []byte("foo txt"),
|
||||||
|
"b/foo.txt": []byte("foo b"),
|
||||||
|
})
|
||||||
|
|
||||||
|
srv := makeTestServer(t, fmt.Sprintf(`
|
||||||
|
directory: %s
|
||||||
|
permissions: none
|
||||||
|
|
||||||
|
users:
|
||||||
|
- username: basic
|
||||||
|
password: basic
|
||||||
|
rules:
|
||||||
|
- regex: "^.+.js$"
|
||||||
|
permissions: R
|
||||||
|
- path: "/a/foo.txt"
|
||||||
|
permissions: CRU
|
||||||
|
- path: "/b/"
|
||||||
|
permissions: D
|
||||||
|
`, dir))
|
||||||
|
|
||||||
|
client := gowebdav.NewClient(srv.URL, "basic", "basic")
|
||||||
|
|
||||||
|
_, err := client.ReadDir("/")
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
err = client.Write("/foo.txt", []byte("new"), 0666)
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
err = client.Write("/new.txt", []byte("new"), 0666)
|
||||||
|
require.ErrorContains(t, err, "403")
|
||||||
|
|
||||||
|
err = client.Copy("/bar.js", "/a/foo.txt", true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = client.Remove("/b/foo.txt")
|
||||||
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestServerRulesPrefix(t *testing.T) {
|
func TestServerRulesPrefix(t *testing.T) {
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package lib
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/webdav"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ webdav.LockSystem = &lockSystem{}
|
||||||
|
|
||||||
|
// LockSystem wraps a [webdav.LockSystem] with a root directory, allowing
|
||||||
|
// to reuse the same [webdav.LockSystem] for multiple users with different base
|
||||||
|
// directories, meaning we can correctly lock the files across different users.
|
||||||
|
type lockSystem struct {
|
||||||
|
webdav.LockSystem
|
||||||
|
directory string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lockSystem) Confirm(now time.Time, name0, name1 string, conditions ...webdav.Condition) (release func(), err error) {
|
||||||
|
if name0 != "" {
|
||||||
|
name0 = filepath.Join(l.directory, name0)
|
||||||
|
}
|
||||||
|
|
||||||
|
if name1 != "" {
|
||||||
|
name1 = filepath.Join(l.directory, name1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return l.LockSystem.Confirm(now, name0, name1, conditions...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *lockSystem) Create(now time.Time, details webdav.LockDetails) (token string, err error) {
|
||||||
|
details.Root = filepath.Join(l.directory, details.Root)
|
||||||
|
return l.LockSystem.Create(now, details)
|
||||||
|
}
|
||||||
+3
-1
@@ -56,9 +56,11 @@ func (p UserPermissions) Allowed(r *request, fileExists func(string) bool) bool
|
|||||||
// we fail immediately. If no rule matches, we check the global permissions.
|
// we fail immediately. If no rule matches, we check the global permissions.
|
||||||
if r.method == "COPY" || r.method == "MOVE" {
|
if r.method == "COPY" || r.method == "MOVE" {
|
||||||
dst := r.destination
|
dst := r.destination
|
||||||
|
ruleMatched := false
|
||||||
|
|
||||||
for i := len(p.Rules) - 1; i >= 0; i-- {
|
for i := len(p.Rules) - 1; i >= 0; i-- {
|
||||||
if p.Rules[i].Matches(dst) {
|
if p.Rules[i].Matches(dst) {
|
||||||
|
ruleMatched = true
|
||||||
if !p.Rules[i].Permissions.AllowedDestination(r, fileExists) {
|
if !p.Rules[i].Permissions.AllowedDestination(r, fileExists) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -68,7 +70,7 @@ func (p UserPermissions) Allowed(r *request, fileExists func(string) bool) bool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !p.Permissions.AllowedDestination(r, fileExists) {
|
if !ruleMatched && !p.Permissions.AllowedDestination(r, fileExists) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/hacdias/webdav/v5/cmd"
|
"github.com/hacdias/webdav/v5/cmd"
|
||||||
|
|
||||||
|
_ "golang.org/x/crypto/x509roots/fallback"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended",
|
||||||
|
"group:allNonMajor",
|
||||||
|
"group:allDigest",
|
||||||
|
":disableDependencyDashboard",
|
||||||
|
":semanticCommitTypeAll(chore)"
|
||||||
|
],
|
||||||
|
"postUpdateOptions": [
|
||||||
|
"gomodUpdateImportPaths",
|
||||||
|
"gomodTidy"
|
||||||
|
],
|
||||||
|
"schedule": "* * * * 0,6"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user