Compare commits
30 Commits
better-doc
...
main
Author | SHA1 | Date |
---|---|---|
Derrick Hammer | 7b20ce6a9a | |
dependabot[bot] | 2a401bc274 | |
dependabot[bot] | 596742fd98 | |
github-actions[bot] | fb0284b993 | |
Marius | e475df9895 | |
dependabot[bot] | 318aab451b | |
dependabot[bot] | 76fc247408 | |
dependabot[bot] | 912ff3df05 | |
dependabot[bot] | cfa859f866 | |
dependabot[bot] | d2bf62776b | |
Jonas Thelemann | db2b4918e2 | |
Marius | ee9f40158e | |
Marius | dcec3df83f | |
dependabot[bot] | a16d9a2ac6 | |
dependabot[bot] | 59afa4c213 | |
dependabot[bot] | 86518148b2 | |
dependabot[bot] | ae120982ae | |
dependabot[bot] | ec843312e5 | |
dependabot[bot] | 007e05618b | |
dependabot[bot] | c52a482720 | |
dependabot[bot] | d7b990e3c2 | |
dependabot[bot] | 3053ade119 | |
Christian Kaps | 7225439860 | |
Marius | 9cf626bf0c | |
Amir Noorani | 932bb30ad1 | |
dependabot[bot] | 4a46cf667e | |
dependabot[bot] | e48471d774 | |
dependabot[bot] | e8282eaa85 | |
dependabot[bot] | f4d0c2a0f0 | |
dependabot[bot] | b6a368c041 |
|
@ -0,0 +1,156 @@
|
||||||
|
# Taken from https://github.com/hrvey/combine-prs-workflow
|
||||||
|
# This action can be triggered manually to combine multiple PRs for
|
||||||
|
# dependency upgrades into a single PR. See the above links for
|
||||||
|
# more details.
|
||||||
|
name: 'Combine PRs'
|
||||||
|
|
||||||
|
# Controls when the action will run - in this case triggered manually
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
branchPrefix:
|
||||||
|
description: 'Branch prefix to find combinable PRs based on'
|
||||||
|
required: true
|
||||||
|
default: 'dependabot'
|
||||||
|
mustBeGreen:
|
||||||
|
description: 'Only combine PRs that are green (status is success). Set to false if repo does not run checks'
|
||||||
|
type: boolean
|
||||||
|
required: true
|
||||||
|
default: true
|
||||||
|
combineBranchName:
|
||||||
|
description: 'Name of the branch to combine PRs into'
|
||||||
|
required: true
|
||||||
|
default: 'combine-prs-branch'
|
||||||
|
ignoreLabel:
|
||||||
|
description: 'Exclude PRs with this label'
|
||||||
|
required: true
|
||||||
|
default: 'nocombine'
|
||||||
|
|
||||||
|
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||||
|
jobs:
|
||||||
|
# This workflow contains a single job called "combine-prs"
|
||||||
|
combine-prs:
|
||||||
|
# The type of runner that the job will run on
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||||
|
steps:
|
||||||
|
- uses: actions/github-script@v6
|
||||||
|
id: create-combined-pr
|
||||||
|
name: Create Combined PR
|
||||||
|
with:
|
||||||
|
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||||
|
script: |
|
||||||
|
const pulls = await github.paginate('GET /repos/:owner/:repo/pulls', {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo
|
||||||
|
});
|
||||||
|
let branchesAndPRStrings = [];
|
||||||
|
let baseBranch = null;
|
||||||
|
let baseBranchSHA = null;
|
||||||
|
for (const pull of pulls) {
|
||||||
|
const branch = pull['head']['ref'];
|
||||||
|
console.log('Pull for branch: ' + branch);
|
||||||
|
if (branch.startsWith('${{ github.event.inputs.branchPrefix }}')) {
|
||||||
|
console.log('Branch matched prefix: ' + branch);
|
||||||
|
let statusOK = true;
|
||||||
|
if(${{ github.event.inputs.mustBeGreen }}) {
|
||||||
|
console.log('Checking green status: ' + branch);
|
||||||
|
const stateQuery = `query($owner: String!, $repo: String!, $pull_number: Int!) {
|
||||||
|
repository(owner: $owner, name: $repo) {
|
||||||
|
pullRequest(number:$pull_number) {
|
||||||
|
commits(last: 1) {
|
||||||
|
nodes {
|
||||||
|
commit {
|
||||||
|
statusCheckRollup {
|
||||||
|
state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}`
|
||||||
|
const vars = {
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
pull_number: pull['number']
|
||||||
|
};
|
||||||
|
const result = await github.graphql(stateQuery, vars);
|
||||||
|
const [{ commit }] = result.repository.pullRequest.commits.nodes;
|
||||||
|
const state = commit.statusCheckRollup.state
|
||||||
|
console.log('Validating status: ' + state);
|
||||||
|
if(state != 'SUCCESS') {
|
||||||
|
console.log('Discarding ' + branch + ' with status ' + state);
|
||||||
|
statusOK = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('Checking labels: ' + branch);
|
||||||
|
const labels = pull['labels'];
|
||||||
|
for(const label of labels) {
|
||||||
|
const labelName = label['name'];
|
||||||
|
console.log('Checking label: ' + labelName);
|
||||||
|
if(labelName == '${{ github.event.inputs.ignoreLabel }}') {
|
||||||
|
console.log('Discarding ' + branch + ' with label ' + labelName);
|
||||||
|
statusOK = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (statusOK) {
|
||||||
|
console.log('Adding branch to array: ' + branch);
|
||||||
|
const prString = '#' + pull['number'] + ' ' + pull['title'];
|
||||||
|
branchesAndPRStrings.push({ branch, prString });
|
||||||
|
baseBranch = pull['base']['ref'];
|
||||||
|
baseBranchSHA = pull['base']['sha'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (branchesAndPRStrings.length == 0) {
|
||||||
|
core.setFailed('No PRs/branches matched criteria');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await github.rest.git.createRef({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
ref: 'refs/heads/' + '${{ github.event.inputs.combineBranchName }}',
|
||||||
|
sha: baseBranchSHA
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
core.setFailed('Failed to create combined branch - maybe a branch by that name already exists?');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let combinedPRs = [];
|
||||||
|
let mergeFailedPRs = [];
|
||||||
|
for(const { branch, prString } of branchesAndPRStrings) {
|
||||||
|
try {
|
||||||
|
await github.rest.repos.merge({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
base: '${{ github.event.inputs.combineBranchName }}',
|
||||||
|
head: branch,
|
||||||
|
});
|
||||||
|
console.log('Merged branch ' + branch);
|
||||||
|
combinedPRs.push(prString);
|
||||||
|
} catch (error) {
|
||||||
|
console.log('Failed to merge branch ' + branch);
|
||||||
|
mergeFailedPRs.push(prString);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Creating combined PR');
|
||||||
|
const combinedPRsString = combinedPRs.join('\n');
|
||||||
|
let body = '✅ This PR was created by the Combine PRs action by combining the following PRs:\n' + combinedPRsString;
|
||||||
|
if(mergeFailedPRs.length > 0) {
|
||||||
|
const mergeFailedPRsString = mergeFailedPRs.join('\n');
|
||||||
|
body += '\n\n⚠️ The following PRs were left out due to merge conflicts:\n' + mergeFailedPRsString
|
||||||
|
}
|
||||||
|
await github.rest.pulls.create({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
title: 'Combined PR',
|
||||||
|
head: '${{ github.event.inputs.combineBranchName }}',
|
||||||
|
base: baseBranch,
|
||||||
|
body: body
|
||||||
|
});
|
|
@ -9,7 +9,7 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
go-version: [1.18.x, 1.19.x]
|
go-version: [stable, oldstable]
|
||||||
platform: [ubuntu-latest, macos-latest, windows-latest]
|
platform: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
env:
|
env:
|
||||||
|
@ -21,7 +21,7 @@ jobs:
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Install Go
|
name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: ${{ matrix.go-version }}
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Docker meta
|
name: Docker meta
|
||||||
id: docker_meta
|
id: docker_meta
|
||||||
uses: docker/metadata-action@v4.3.0
|
uses: docker/metadata-action@v4.4.0
|
||||||
with:
|
with:
|
||||||
images: |
|
images: |
|
||||||
ghcr.io/tus/tusd
|
ghcr.io/tus/tusd
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
||||||
-
|
-
|
||||||
name: Set up Docker Buildx
|
name: Set up Docker Buildx
|
||||||
id: buildx
|
id: buildx
|
||||||
uses: docker/setup-buildx-action@v2.4.1
|
uses: docker/setup-buildx-action@v2.5.0
|
||||||
with:
|
with:
|
||||||
install: true
|
install: true
|
||||||
|
|
||||||
|
@ -81,10 +81,10 @@ jobs:
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Install Go 1.19
|
name: Install Go
|
||||||
uses: actions/setup-go@v3
|
uses: actions/setup-go@v4
|
||||||
with:
|
with:
|
||||||
go-version: '1.19.5'
|
go-version: 'stable'
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Build TUSD
|
name: Build TUSD
|
||||||
|
@ -105,7 +105,7 @@ jobs:
|
||||||
|
|
||||||
-
|
-
|
||||||
name: Deploy to heroku
|
name: Deploy to heroku
|
||||||
uses: akhileshns/heroku-deploy@v3.12.13
|
uses: akhileshns/heroku-deploy@v3.12.14
|
||||||
with:
|
with:
|
||||||
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
|
heroku_api_key: ${{secrets.HEROKU_API_KEY}}
|
||||||
heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
|
heroku_app_name: ${{secrets.HEROKU_APP_NAME}}
|
||||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.20.0-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.20.4-alpine AS builder
|
||||||
WORKDIR /go/src/github.com/tus/tusd
|
WORKDIR /go/src/github.com/tus/tusd
|
||||||
|
|
||||||
# Add gcc and libc-dev early so it is cached
|
# Add gcc and libc-dev early so it is cached
|
||||||
|
@ -19,13 +19,17 @@ COPY pkg/ ./pkg/
|
||||||
ARG GIT_VERSION
|
ARG GIT_VERSION
|
||||||
ARG GIT_COMMIT
|
ARG GIT_COMMIT
|
||||||
|
|
||||||
|
# Get the operating system and architecture to build for
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
RUN set -xe \
|
RUN set -xe \
|
||||||
&& GOOS=linux GOARCH=amd64 go build \
|
&& GOOS=$TARGETOS GOARCH=$TARGETARCH go build \
|
||||||
-ldflags="-X github.com/tus/tusd/cmd/tusd/cli.VersionName=${GIT_VERSION} -X github.com/tus/tusd/cmd/tusd/cli.GitCommit=${GIT_COMMIT} -X 'github.com/tus/tusd/cmd/tusd/cli.BuildDate=$(date --utc)'" \
|
-ldflags="-X github.com/tus/tusd/cmd/tusd/cli.VersionName=${GIT_VERSION} -X github.com/tus/tusd/cmd/tusd/cli.GitCommit=${GIT_COMMIT} -X 'github.com/tus/tusd/cmd/tusd/cli.BuildDate=$(date --utc)'" \
|
||||||
-o /go/bin/tusd ./cmd/tusd/main.go
|
-o /go/bin/tusd ./cmd/tusd/main.go
|
||||||
|
|
||||||
# start a new stage that copies in the binary built in the previous stage
|
# start a new stage that copies in the binary built in the previous stage
|
||||||
FROM alpine:3.17.2
|
FROM alpine:3.18.0
|
||||||
WORKDIR /srv/tusd-data
|
WORKDIR /srv/tusd-data
|
||||||
|
|
||||||
COPY ./docker/entrypoint.sh /usr/local/share/docker-entrypoint.sh
|
COPY ./docker/entrypoint.sh /usr/local/share/docker-entrypoint.sh
|
||||||
|
|
|
@ -23,6 +23,7 @@ var Flags struct {
|
||||||
ShowGreeting bool
|
ShowGreeting bool
|
||||||
DisableDownload bool
|
DisableDownload bool
|
||||||
DisableTermination bool
|
DisableTermination bool
|
||||||
|
DisableCors bool
|
||||||
Timeout int64
|
Timeout int64
|
||||||
S3Bucket string
|
S3Bucket string
|
||||||
S3ObjectPrefix string
|
S3ObjectPrefix string
|
||||||
|
@ -72,6 +73,7 @@ func ParseFlags() {
|
||||||
flag.BoolVar(&Flags.ShowGreeting, "show-greeting", true, "Show the greeting message")
|
flag.BoolVar(&Flags.ShowGreeting, "show-greeting", true, "Show the greeting message")
|
||||||
flag.BoolVar(&Flags.DisableDownload, "disable-download", false, "Disable the download endpoint")
|
flag.BoolVar(&Flags.DisableDownload, "disable-download", false, "Disable the download endpoint")
|
||||||
flag.BoolVar(&Flags.DisableTermination, "disable-termination", false, "Disable the termination endpoint")
|
flag.BoolVar(&Flags.DisableTermination, "disable-termination", false, "Disable the termination endpoint")
|
||||||
|
flag.BoolVar(&Flags.DisableCors, "disable-cors", false, "Disable CORS headers")
|
||||||
flag.Int64Var(&Flags.Timeout, "timeout", 6*1000, "Read timeout for connections in milliseconds. A zero value means that reads will not timeout")
|
flag.Int64Var(&Flags.Timeout, "timeout", 6*1000, "Read timeout for connections in milliseconds. A zero value means that reads will not timeout")
|
||||||
flag.StringVar(&Flags.S3Bucket, "s3-bucket", "", "Use AWS S3 with this bucket as storage backend (requires the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables to be set)")
|
flag.StringVar(&Flags.S3Bucket, "s3-bucket", "", "Use AWS S3 with this bucket as storage backend (requires the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables to be set)")
|
||||||
flag.StringVar(&Flags.S3ObjectPrefix, "s3-object-prefix", "", "Prefix for S3 object names")
|
flag.StringVar(&Flags.S3ObjectPrefix, "s3-object-prefix", "", "Prefix for S3 object names")
|
||||||
|
@ -106,7 +108,6 @@ func ParseFlags() {
|
||||||
flag.StringVar(&Flags.TLSCertFile, "tls-certificate", "", "Path to the file containing the x509 TLS certificate to be used. The file should also contain any intermediate certificates and the CA certificate.")
|
flag.StringVar(&Flags.TLSCertFile, "tls-certificate", "", "Path to the file containing the x509 TLS certificate to be used. The file should also contain any intermediate certificates and the CA certificate.")
|
||||||
flag.StringVar(&Flags.TLSKeyFile, "tls-key", "", "Path to the file containing the key for the TLS certificate.")
|
flag.StringVar(&Flags.TLSKeyFile, "tls-key", "", "Path to the file containing the key for the TLS certificate.")
|
||||||
flag.StringVar(&Flags.TLSMode, "tls-mode", "tls12", "Specify which TLS mode to use; valid modes are tls13, tls12, and tls12-strong.")
|
flag.StringVar(&Flags.TLSMode, "tls-mode", "tls12", "Specify which TLS mode to use; valid modes are tls13, tls12, and tls12-strong.")
|
||||||
|
|
||||||
flag.StringVar(&Flags.CPUProfile, "cpuprofile", "", "write cpu profile to file")
|
flag.StringVar(&Flags.CPUProfile, "cpuprofile", "", "write cpu profile to file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@ func Serve() {
|
||||||
RespectForwardedHeaders: Flags.BehindProxy,
|
RespectForwardedHeaders: Flags.BehindProxy,
|
||||||
DisableDownload: Flags.DisableDownload,
|
DisableDownload: Flags.DisableDownload,
|
||||||
DisableTermination: Flags.DisableTermination,
|
DisableTermination: Flags.DisableTermination,
|
||||||
|
DisableCors: Flags.DisableCors,
|
||||||
StoreComposer: Composer,
|
StoreComposer: Composer,
|
||||||
NotifyCompleteUploads: true,
|
NotifyCompleteUploads: true,
|
||||||
NotifyTerminatedUploads: true,
|
NotifyTerminatedUploads: true,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Hooks
|
# Hooks
|
||||||
|
|
||||||
When integrating tusd into an application, it is important to establish a communication channel between the two components. The tusd binary accomplishes this by providing a system which triggers actions when certain events happen, such as an upload being created or finished. This simple-but-powerful system enables uses ranging from logging over validation and authorization to processing the uploaded files.
|
When integrating tusd into an application, it is important to establish a communication channel between the two components. The tusd binary accomplishes this by providing a system which triggers actions when certain events happen, such as an upload being created or finished. This simple-but-powerful system enables use cases ranging from logging over validation and authorization to processing the uploaded files.
|
||||||
|
|
||||||
When a specific action happens during an upload (pre-create, post-receive, post-finish, or post-terminate), the hook system enables tusd to fire off a specific event. Tusd provides two ways of doing this:
|
When a specific action happens during an upload (pre-create, post-receive, post-finish, or post-terminate), the hook system enables tusd to fire off a specific event. Tusd provides two ways of doing this:
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,5 @@
|
||||||
# Running tusd
|
# Running tusd
|
||||||
|
|
||||||
Using tusd is as simple as invoked a single command. This guide walks you through the most important configuration options that are necessary for most applications. To see all options, simply inspect the output of `tusd --help`.
|
|
||||||
|
|
||||||
## General configuration
|
|
||||||
|
|
||||||
### Host and port
|
|
||||||
|
|
||||||
### Uploads
|
|
||||||
|
|
||||||
## Storage configuration
|
|
||||||
|
|
||||||
### Local disk
|
|
||||||
|
|
||||||
Starting the tusd upload server is as simple as invoking a single command. For example, following
|
Starting the tusd upload server is as simple as invoking a single command. For example, following
|
||||||
snippet demonstrates how to start a tusd process which accepts tus uploads at
|
snippet demonstrates how to start a tusd process which accepts tus uploads at
|
||||||
`http://localhost:1080/files/` (notice the trailing slash) and stores them locally in the `./data` directory:
|
`http://localhost:1080/files/` (notice the trailing slash) and stores them locally in the `./data` directory:
|
||||||
|
@ -26,7 +14,6 @@ $ tusd -upload-dir=./data
|
||||||
[tusd] 2019/09/29 21:10:50 Supported tus extensions: creation,creation-with-upload,termination,concatenation,creation-defer-length
|
[tusd] 2019/09/29 21:10:50 Supported tus extensions: creation,creation-with-upload,termination,concatenation,creation-defer-length
|
||||||
[tusd] 2019/09/29 21:10:50 You can now upload files to: http://0.0.0.0:1080/files/
|
[tusd] 2019/09/29 21:10:50 You can now upload files to: http://0.0.0.0:1080/files/
|
||||||
```
|
```
|
||||||
### AWS S3
|
|
||||||
|
|
||||||
Alternatively, if you want to store the uploads on an AWS S3 bucket, you only have to specify
|
Alternatively, if you want to store the uploads on an AWS S3 bucket, you only have to specify
|
||||||
the bucket and provide the corresponding access credentials and region information using
|
the bucket and provide the corresponding access credentials and region information using
|
||||||
|
@ -68,8 +55,6 @@ $ tusd -s3-bucket=my-test-bucket.com -s3-transfer-acceleration
|
||||||
tusd is also able to read the credentials automatically from a shared credentials file (~/.aws/credentials) as described in https://github.com/aws/aws-sdk-go#configuring-credentials.
|
tusd is also able to read the credentials automatically from a shared credentials file (~/.aws/credentials) as described in https://github.com/aws/aws-sdk-go#configuring-credentials.
|
||||||
But be mindful of the need to declare the AWS_REGION value which isn't conventionally associated with credentials.
|
But be mindful of the need to declare the AWS_REGION value which isn't conventionally associated with credentials.
|
||||||
|
|
||||||
### Google Cloud Storage
|
|
||||||
|
|
||||||
Furthermore, tusd also has support for storing uploads on Google Cloud Storage. In order to enable this feature, supply the path to your account file containing the necessary credentials:
|
Furthermore, tusd also has support for storing uploads on Google Cloud Storage. In order to enable this feature, supply the path to your account file containing the necessary credentials:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -81,7 +66,6 @@ $ tusd -gcs-bucket=my-test-bucket.com
|
||||||
[tusd] Using /files/ as the base path.
|
[tusd] Using /files/ as the base path.
|
||||||
[tusd] Using /metrics as the metrics path.
|
[tusd] Using /metrics as the metrics path.
|
||||||
```
|
```
|
||||||
### Azure Blob
|
|
||||||
|
|
||||||
Tusd also supports storing uploads on Microsoft Azure Blob Storage. In order to enable this feature, provide the
|
Tusd also supports storing uploads on Microsoft Azure Blob Storage. In order to enable this feature, provide the
|
||||||
corresponding access credentials using environment variables.
|
corresponding access credentials using environment variables.
|
||||||
|
@ -127,10 +111,6 @@ Using endpoint https://xxxxx.blob.core.windows.net
|
||||||
[tusd] Using /metrics as the metrics path.
|
[tusd] Using /metrics as the metrics path.
|
||||||
```
|
```
|
||||||
|
|
||||||
## Proxy configuration
|
|
||||||
|
|
||||||
## TLS configuration
|
|
||||||
|
|
||||||
TLS support for HTTPS connections can be enabled by supplying a certificate and private key. Note that the certificate file must include the entire chain of certificates up to the CA certificate. The default configuration supports TLSv1.2 and TLSv1.3. It is possible to use only TLSv1.3 with `-tls-mode=tls13`; alternately, it is possible to disable TLSv1.3 and use only 256-bit AES ciphersuites with `-tls-mode=tls12-strong`. The following example generates a self-signed certificate for `localhost` and then uses it to serve files on the loopback address; that this certificate is not appropriate for production use. Note also that the key file must not be encrypted/require a passphrase.
|
TLS support for HTTPS connections can be enabled by supplying a certificate and private key. Note that the certificate file must include the entire chain of certificates up to the CA certificate. The default configuration supports TLSv1.2 and TLSv1.3. It is possible to use only TLSv1.3 with `-tls-mode=tls13`; alternately, it is possible to disable TLSv1.3 and use only 256-bit AES ciphersuites with `-tls-mode=tls12-strong`. The following example generates a self-signed certificate for `localhost` and then uses it to serve files on the loopback address; that this certificate is not appropriate for production use. Note also that the key file must not be encrypted/require a passphrase.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -150,4 +130,97 @@ $ tusd -upload-dir=./data -host=127.0.0.1 -port=8443 -tls-certificate=localhost.
|
||||||
[tusd] You can now upload files to: https://127.0.0.1:8443/files/
|
[tusd] You can now upload files to: https://127.0.0.1:8443/files/
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hooks configuration
|
|
||||||
|
Besides these simple examples, tusd can be easily configured using a variety of command line
|
||||||
|
options:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ tusd -help
|
||||||
|
-azure-blob-access-tier string
|
||||||
|
Blob access tier when uploading new files (possible values: archive, cool, hot, '')
|
||||||
|
-azure-container-access-type string
|
||||||
|
Access type when creating a new container if it does not exist (possible values: blob, container, '')
|
||||||
|
-azure-endpoint string
|
||||||
|
Custom Endpoint to use for Azure BlockBlob Storage (requires azure-storage to be pass)
|
||||||
|
-azure-object-prefix string
|
||||||
|
Prefix for Azure object names
|
||||||
|
-azure-storage string
|
||||||
|
Use Azure BlockBlob Storage with this container name as a storage backend (requires the AZURE_STORAGE_ACCOUNT and AZURE_STORAGE_KEY environment variable to be set)
|
||||||
|
-base-path string
|
||||||
|
Basepath of the HTTP server (default "/files/")
|
||||||
|
-behind-proxy
|
||||||
|
Respect X-Forwarded-* and similar headers which may be set by proxies
|
||||||
|
-cpuprofile string
|
||||||
|
write cpu profile to file
|
||||||
|
-expose-metrics
|
||||||
|
Expose metrics about tusd usage (default true)
|
||||||
|
-gcs-bucket string
|
||||||
|
Use Google Cloud Storage with this bucket as storage backend (requires the GCS_SERVICE_ACCOUNT_FILE environment variable to be set)
|
||||||
|
-gcs-object-prefix string
|
||||||
|
Prefix for GCS object names
|
||||||
|
-hooks-dir string
|
||||||
|
Directory to search for available hooks scripts
|
||||||
|
-hooks-enabled-events string
|
||||||
|
Comma separated list of enabled hook events (e.g. post-create,post-finish). Leave empty to enable default events (default "pre-create,post-create,post-receive,post-terminate,post-finish")
|
||||||
|
-hooks-grpc string
|
||||||
|
An gRPC endpoint to which hook events will be sent to
|
||||||
|
-hooks-grpc-backoff int
|
||||||
|
Number of seconds to wait before retrying each retry (default 1)
|
||||||
|
-hooks-grpc-retry int
|
||||||
|
Number of times to retry on a server error or network timeout (default 3)
|
||||||
|
-hooks-http string
|
||||||
|
An HTTP endpoint to which hook events will be sent to
|
||||||
|
-hooks-http-backoff int
|
||||||
|
Number of seconds to wait before retrying each retry (default 1)
|
||||||
|
-hooks-http-forward-headers string
|
||||||
|
List of HTTP request headers to be forwarded from the client request to the hook endpoint
|
||||||
|
-hooks-http-retry int
|
||||||
|
Number of times to retry on a 500 or network timeout (default 3)
|
||||||
|
-hooks-plugin string
|
||||||
|
Path to a Go plugin for loading hook functions (only supported on Linux and macOS; highly EXPERIMENTAL and may BREAK in the future)
|
||||||
|
-hooks-stop-code int
|
||||||
|
Return code from post-receive hook which causes tusd to stop and delete the current upload. A zero value means that no uploads will be stopped
|
||||||
|
-host string
|
||||||
|
Host to bind HTTP server to (default "0.0.0.0")
|
||||||
|
-max-size int
|
||||||
|
Maximum size of a single upload in bytes
|
||||||
|
-metrics-path string
|
||||||
|
Path under which the metrics endpoint will be accessible (default "/metrics")
|
||||||
|
-port string
|
||||||
|
Port to bind HTTP server to (default "1080")
|
||||||
|
-s3-bucket string
|
||||||
|
Use AWS S3 with this bucket as storage backend (requires the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_REGION environment variables to be set)
|
||||||
|
-s3-disable-content-hashes
|
||||||
|
Disable the calculation of MD5 and SHA256 hashes for the content that gets uploaded to S3 for minimized CPU usage (experimental and may be removed in the future)
|
||||||
|
-s3-disable-ssl
|
||||||
|
Disable SSL and only use HTTP for communication with S3 (experimental and may be removed in the future)
|
||||||
|
-s3-endpoint string
|
||||||
|
Endpoint to use S3 compatible implementations like minio (requires s3-bucket to be pass)
|
||||||
|
-s3-object-prefix string
|
||||||
|
Prefix for S3 object names
|
||||||
|
-s3-part-size int
|
||||||
|
Size in bytes of the individual upload requests made to the S3 API. Defaults to 50MiB (experimental and may be removed in the future) (default 52428800)
|
||||||
|
-s3-transfer-acceleration
|
||||||
|
Use AWS S3 transfer acceleration endpoint (requires -s3-bucket option and Transfer Acceleration property on S3 bucket to be set)
|
||||||
|
-show-greeting
|
||||||
|
Show the greeting message (default true)
|
||||||
|
-timeout int
|
||||||
|
Read timeout for connections in milliseconds. A zero value means that reads will not timeout (default 6000)
|
||||||
|
-tls-certificate string
|
||||||
|
Path to the file containing the x509 TLS certificate to be used. The file should also contain any intermediate certificates and the CA certificate.
|
||||||
|
-tls-key string
|
||||||
|
Path to the file containing the key for the TLS certificate.
|
||||||
|
-tls-mode string
|
||||||
|
Specify which TLS mode to use; valid modes are tls13, tls12, and tls12-strong. (default "tls12")
|
||||||
|
-unix-sock string
|
||||||
|
If set, will listen to a UNIX socket at this location instead of a TCP socket
|
||||||
|
-upload-dir string
|
||||||
|
Directory to store uploads in (default "./data")
|
||||||
|
-disable-cors
|
||||||
|
Disables CORS headers. If set to true, tusd will not send any CORS related header. This is useful if you have a proxy sitting in front of tusd that handles CORS (default false)
|
||||||
|
-verbose
|
||||||
|
Enable verbose logging output (default true)
|
||||||
|
-version
|
||||||
|
Print tusd version information
|
||||||
|
|
||||||
|
```
|
||||||
|
|
18
go.mod
18
go.mod
|
@ -6,19 +6,19 @@ module github.com/tus/tusd
|
||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go/storage v1.29.0
|
cloud.google.com/go/storage v1.30.1
|
||||||
github.com/Azure/azure-storage-blob-go v0.14.0
|
github.com/Azure/azure-storage-blob-go v0.14.0
|
||||||
github.com/aws/aws-sdk-go v1.44.201
|
github.com/aws/aws-sdk-go v1.44.275
|
||||||
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40
|
github.com/bmizerany/pat v0.0.0-20170815010413-6226ea591a40
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
github.com/golang/protobuf v1.5.2
|
github.com/golang/protobuf v1.5.3
|
||||||
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
|
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0
|
||||||
github.com/prometheus/client_golang v1.14.0
|
github.com/prometheus/client_golang v1.15.1
|
||||||
github.com/sethgrid/pester v0.0.0-20190127155807-68a33a018ad0
|
github.com/sethgrid/pester v1.2.0
|
||||||
github.com/stretchr/testify v1.8.1
|
github.com/stretchr/testify v1.8.4
|
||||||
github.com/vimeo/go-util v1.4.1
|
github.com/vimeo/go-util v1.4.1
|
||||||
google.golang.org/api v0.110.0
|
google.golang.org/api v0.125.0
|
||||||
google.golang.org/grpc v1.53.0
|
google.golang.org/grpc v1.55.0
|
||||||
gopkg.in/Acconut/lockfile.v1 v1.1.0
|
gopkg.in/Acconut/lockfile.v1 v1.1.0
|
||||||
gopkg.in/h2non/gock.v1 v1.1.2
|
gopkg.in/h2non/gock.v1 v1.1.2
|
||||||
)
|
)
|
||||||
|
|
|
@ -28,6 +28,9 @@ type Config struct {
|
||||||
// DisableTermination indicates whether the server will refuse termination
|
// DisableTermination indicates whether the server will refuse termination
|
||||||
// requests of the uploaded file, by not mounting the DELETE handler.
|
// requests of the uploaded file, by not mounting the DELETE handler.
|
||||||
DisableTermination bool
|
DisableTermination bool
|
||||||
|
// Disable cors headers. If set to true, tusd will not send any CORS related header.
|
||||||
|
// This is useful if you have a proxy sitting in front of tusd that handles CORS.
|
||||||
|
DisableCors bool
|
||||||
// NotifyCompleteUploads indicates whether sending notifications about
|
// NotifyCompleteUploads indicates whether sending notifications about
|
||||||
// completed uploads using the CompleteUploads channel should be enabled.
|
// completed uploads using the CompleteUploads channel should be enabled.
|
||||||
NotifyCompleteUploads bool
|
NotifyCompleteUploads bool
|
||||||
|
|
|
@ -96,4 +96,20 @@ func TestCORS(t *testing.T) {
|
||||||
t.Errorf("expected header to contain METHOD but got: %#v", methods)
|
t.Errorf("expected header to contain METHOD but got: %#v", methods)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
SubTest(t, "Disable CORS", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
|
handler, _ := NewHandler(Config{
|
||||||
|
StoreComposer: composer,
|
||||||
|
DisableCors: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Method: "OPTIONS",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Origin": "tus.io",
|
||||||
|
},
|
||||||
|
Code: http.StatusOK,
|
||||||
|
ResHeader: map[string]string{},
|
||||||
|
}).Run(handler, t)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,24 @@ func NewHTTPError(err error, statusCode int) HTTPError {
|
||||||
return httpError{err, statusCode}
|
return httpError{err, statusCode}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type contextWithValues struct {
|
||||||
|
context.Context
|
||||||
|
valueHolder context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c contextWithValues) Value(key interface{}) interface{} {
|
||||||
|
return c.valueHolder.Value(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newContextWithValues(ctx context.Context) contextWithValues {
|
||||||
|
return contextWithValues{
|
||||||
|
// Use background to not get cancel event
|
||||||
|
Context: context.Background(),
|
||||||
|
// Use request context to get stored values
|
||||||
|
valueHolder: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrUnsupportedVersion = NewHTTPError(errors.New("unsupported version"), http.StatusPreconditionFailed)
|
ErrUnsupportedVersion = NewHTTPError(errors.New("unsupported version"), http.StatusPreconditionFailed)
|
||||||
ErrMaxSizeExceeded = NewHTTPError(errors.New("maximum size exceeded"), http.StatusRequestEntityTooLarge)
|
ErrMaxSizeExceeded = NewHTTPError(errors.New("maximum size exceeded"), http.StatusRequestEntityTooLarge)
|
||||||
|
@ -98,6 +116,12 @@ type HookEvent struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHookEvent(info FileInfo, r *http.Request) HookEvent {
|
func newHookEvent(info FileInfo, r *http.Request) HookEvent {
|
||||||
|
// The Host header field is not present in the header map, see https://pkg.go.dev/net/http#Request:
|
||||||
|
// > For incoming requests, the Host header is promoted to the
|
||||||
|
// > Request.Host field and removed from the Header map.
|
||||||
|
// That's why we add it back manually.
|
||||||
|
r.Header.Set("Host", r.Host)
|
||||||
|
|
||||||
return HookEvent{
|
return HookEvent{
|
||||||
Upload: info,
|
Upload: info,
|
||||||
HTTPRequest: HTTPRequest{
|
HTTPRequest: HTTPRequest{
|
||||||
|
@ -217,7 +241,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
|
||||||
|
|
||||||
header := w.Header()
|
header := w.Header()
|
||||||
|
|
||||||
if origin := r.Header.Get("Origin"); origin != "" {
|
if origin := r.Header.Get("Origin"); !handler.config.DisableCors && origin != "" {
|
||||||
header.Set("Access-Control-Allow-Origin", origin)
|
header.Set("Access-Control-Allow-Origin", origin)
|
||||||
|
|
||||||
if r.Method == "OPTIONS" {
|
if r.Method == "OPTIONS" {
|
||||||
|
@ -284,7 +308,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
|
||||||
// PostFile creates a new file upload using the datastore after validating the
|
// PostFile creates a new file upload using the datastore after validating the
|
||||||
// length and parsing the metadata.
|
// length and parsing the metadata.
|
||||||
func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := newContextWithValues(r.Context())
|
||||||
|
|
||||||
// Check for presence of application/offset+octet-stream. If another content
|
// Check for presence of application/offset+octet-stream. If another content
|
||||||
// type is defined, it will be ignored and treated as none was set because
|
// type is defined, it will be ignored and treated as none was set because
|
||||||
|
@ -427,7 +451,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
// HeadFile returns the length and offset for the HEAD request
|
// HeadFile returns the length and offset for the HEAD request
|
||||||
func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := newContextWithValues(r.Context())
|
||||||
|
|
||||||
id, err := extractIDFromPath(r.URL.Path)
|
id, err := extractIDFromPath(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -492,7 +516,7 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request)
|
||||||
// PatchFile adds a chunk to an upload. This operation is only allowed
|
// PatchFile adds a chunk to an upload. This operation is only allowed
|
||||||
// if enough space in the upload is left.
|
// if enough space in the upload is left.
|
||||||
func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := newContextWithValues(r.Context())
|
||||||
|
|
||||||
// Check for presence of application/offset+octet-stream
|
// Check for presence of application/offset+octet-stream
|
||||||
if r.Header.Get("Content-Type") != "application/offset+octet-stream" {
|
if r.Header.Get("Content-Type") != "application/offset+octet-stream" {
|
||||||
|
@ -721,7 +745,7 @@ func (handler *UnroutedHandler) finishUploadIfComplete(ctx context.Context, uplo
|
||||||
// GetFile handles requests to download a file using a GET request. This is not
|
// GetFile handles requests to download a file using a GET request. This is not
|
||||||
// part of the specification.
|
// part of the specification.
|
||||||
func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := newContextWithValues(r.Context())
|
||||||
|
|
||||||
id, err := extractIDFromPath(r.URL.Path)
|
id, err := extractIDFromPath(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -842,7 +866,7 @@ func filterContentType(info FileInfo) (contentType string, contentDisposition st
|
||||||
|
|
||||||
// DelFile terminates an upload permanently.
|
// DelFile terminates an upload permanently.
|
||||||
func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := context.Background()
|
ctx := newContextWithValues(r.Context())
|
||||||
|
|
||||||
// Abort the request handling if the required interface is not implemented
|
// Abort the request handling if the required interface is not implemented
|
||||||
if !handler.composer.UsesTerminater {
|
if !handler.composer.UsesTerminater {
|
||||||
|
|
Loading…
Reference in New Issue