# Contributing (/docs/contributing) # Bug Reports [#bug-reports] [Create an issue on GitHub](https://github.com/diced/zipline/issues/new?template=bug.yml), please include the following (if one of them is not applicable to the issue then it's not needed): * The steps to reproduce the bug * Logs of Zipline * Try to enable [debug logging](/docs/guides/debug) to provide more detailed logs * The version of Zipline, and whether or not you are using Docker (include the image digest/tag if possible) * Your OS & Browser including server OS * What you were expecting to see * How it can be fixed (if you know) # Feature Requests [#feature-requests] [Create a discussion on GitHub](https://github.com/diced/zipline/discussions/new?category=ideas), and please include the following: * Brief explanation of your feature in the title (very brief) * How it would work (be detailed) # Pull Requests [#pull-requests] Create a pull request on GitHub. If your PR does not pass the action checks, then please fix the errors. If your PR was submitted before a release, and I have pushed a new release, please make sure to update your PR to reflect any changes, usually this is handled by GitHub. ## Development [#development] Here's how to setup Zipline for development ### Prerequisites [#prerequisites] * [nodejs@24](https://nodejs.org/) * [pnpm@11](https://pnpm.io/installation) * [ffmpeg](https://ffmpeg.org/download.html) (for generating thumbnails, optional) * a [postgresql](https://postgresql.org) server #### Setup [#setup] You should probably use a `.env` file to manage your environment variables, here is an example .env file with every available environment variable: ```dotenv title=".env" DEBUG=zipline # required CORE_SECRET="a secret that is 32 characters long" # required DATABASE_URL="postgresql://postgres:postgres@localhost:5432/zipline?schema=public" # these are optional CORE_PORT=3000 CORE_HOSTNAME=0.0.0.0 # one of these is required DATASOURCE_TYPE="local" # DATASOURCE_TYPE="s3" # if DATASOURCE_TYPE=local DATASOURCE_LOCAL_DIRECTORY="/path/to/your/local/files" # if DATASOURCE_TYPE=s3 # DATASOURCE_S3_ACCESS_KEY_ID="your-access-key-id" # DATASOURCE_S3_SECRET_ACCESS_KEY="your-secret-access-key" # DATASOURCE_S3_REGION="your-region" # DATASOURCE_S3_BUCKET="your-bucket" # DATASOURCE_S3_ENDPOINT="your-endpoint" # ^ if using a custom endpoint other than aws s3 ``` Install dependencies: ```bash pnpm install ``` Finally you may start the development server: ```bash pnpm dev ``` If you wish to build the production version of Zipline, you can run the following command: ```bash pnpm build ``` And to run the production version of Zipline: ```bash pnpm start ``` #### Making changes to the database schema [#making-changes-to-the-database-schema] Zipline uses [prisma](https://www.prisma.io/) as its ORM, and as such, you will need to use the prisma CLI to facilitate any changes to the database schema. Once you have made a change to `prisma.schema`, you can run the script `db:migrate` to generate a migration file. This script doesn't apply the migration, as Zipline handles applying migrations itself on startup. ```bash pnpm db:migrate ``` If you wish to push changes to the database without generating a migration file, you can run the script `db:prototype`. This is only recommended for testing purposes, and should not be used in production. ```bash pnpm db:prototype ``` #### Linting and Formatting [#linting-and-formatting] Zipline will fail to build unless the code is properly formatted and linted. To format the code, you can run the following command: ```bash pnpm validate ``` #### Testing `zipline-ctl` [#testing-zipline-ctl] To build the ctl, you can run the following command: ```bash pnpm build:server ``` then run any command you want ```bash pnpm ctl help ``` # Migrate from v3 (/docs/migrate) You must be on `v3.7.12` or higher to use the export feature. Additionally, to see the `Export all Server Data (JSON)` button, you must be logged in as a super-admin. If you have lost access to your super-admin account, you can utilize the [set-user](https://v3.zipline.diced.sh/docs/guides/scripts/set-user) while still running v3. ## Preparation [#preparation] Before you start, navigate to your v3 instance's manage account page and follow the steps below to export your data:
### Export all Server Data [#export-all-server-data-step] Click the "Export all Server Data (JSON)" button to start preparing a JSON file that contains all your server data. Manage Account
### Confirm Export [#confirm-export-step] Click "Yes" after confirming the export. Export Data The export contains sensitive information, such as passwords, API keys, 2fa secrets, and more. Make sure to keep the file secure and delete it after the migration.
### Wait for the export file to be prepared [#wait-for-the-export-file-to-be-prepared-step] After clicking yes, the server will start preparing the export file. This may take a few seconds to a few minutes, depending on the size of your data. Once the file is ready, your browser will prompt you to download it. Make sure to save it in a secure location, as it contains sensitive information. Download Data
## Setting up v4 [#setting-up-v4] The process is made much easier when using Docker, so this guide will assume you are using Docker. If you are not using Docker, you can still follow along, but you will have to adjust the commands accordingly. For the first few steps, keep the v3 instance running. We will need to access the postgres database to create a new database.
### Pull the latest v4 image [#pull-the-latest-v4-image-step] ```bash docker pull ghcr.io/diced/zipline:latest ```
### Enter `psql` CLI [#enter-psql-cli-step] Enter the database of your old v3 instance, so that we can create the new database. ```bash docker compose exec postgres psql -U postgres # or whatever your postgres user is configured as ```
### Create a new database for v4 [#create-a-new-database-for-v4-step] ```sql CREATE DATABASE zipline_v4; -- you may name this anything ``` You may run into an error that says `ERROR: template database "template1" has a collation version mismatch`, run `ALTER DATABASE template1 REFRESH COLLATION VERSION;` in the postgres CLI to rebuild all of the objects in the template database using the new version, and you should be able to proceed with creating the database.
### Exit the postgres shell [#exit-the-postgres-shell-step] ```bash exit; ```
### Environment Variables [#environment-variables] Almost all environment variables supported in v3 are no longer supported in v4. This is because v4 has a new configuration system that is handled entirely through the dashboard. Only a handful of environment variables are needed to get v4 running. Here are some variables that you will need to keep/rename: * `CORE_DATABASE_URL` -> `DATABASE_URL` (change the database at the end, to the new database you created in the above steps) * `CORE_HOST` -> is now `CORE_HOSTNAME` * `CORE_PORT` * `CORE_SECRET` -> this must be a string of at least 32 characters, or it will be rejected. It is recommended to just generate a new secret. (you may want to close this in single quotes if you're using a password generator) * `DATASOURCE_TYPE` -> only supports `s3` or `local` * `DATASOURCE_LOCAL_DIRECTORY` * `DATASOURCE_S3_ACCESS_KEY_ID` * `DATASOURCE_S3_SECRET_ACCESS_KEY` * `DATASOURCE_S3_BUCKET` * `DATASOURCE_S3_REGION` * `DATASOURCE_S3_ENDPOINT` * `DATASOURCE_S3_REGION` * `DATASOURCE_S3_PORT` -> no longer supported, include the port in the endpoint * `DATASOURCE_S3_FORCE_S3_PATH` -> `DATASOURCE_S3_FORCE_PATH_STYLE` * `DATASOURCE_S3_USE_SSL` -> no longer supported * `SSL_CERT` -> no longer supported * `SSL_KEY` -> no longer supported * `SSL_ALLOW_HTTP1` -> no longer supported After these changes, your environment section in your docker-compose file may look something like this: ```yaml title="docker-compose.yml" environment: - DATABASE_URL=postgres://postgres:postgres@postgres:5432/zipline_v4 - CORE_HOSTNAME=0.0.0.0 - CORE_PORT=3000 - CORE_SECRET=supersecret234567890qwertyuiopasdfghjklzxcvbnm - DATASOURCE_TYPE=local - DATASOURCE_LOCAL_DIRECTORY=./uploads ``` or... ```yaml title="docker-compose.yml" environment: - DATABASE_URL=postgres://postgres:postgres@postgres:5432/zipline_v4 - CORE_HOSTNAME=0.0.0.0 - CORE_PORT=3000 - CORE_SECRET=supersecret234567890qwertyuiopasdfghjklzxcvbnm - DATASOURCE_TYPE=s3 - DATASOURCE_S3_ACCESS_KEY_ID=youraccesskey - DATASOURCE_S3_SECRET_ACCESS_KEY=yoursecretkey - DATASOURCE_S3_BUCKET=yourbucket - DATASOURCE_S3_REGION=yourregion - DATASOURCE_S3_ENDPOINT=yourendpoint # not needed for amazon aws s3 ``` All other environment variables will have no effect on the server. Zipline will fail to run if the database URL is the same as the one used in v3. This is because the database schema has changed significantly between v3 and v4. ## Migrating Data [#migrating-data]
### Restart the instance [#restart-the-instance-step] ```bash docker compose down docker compose up -d ``` If you would like you can view the logs to make sure everything is running correctly. ```bash docker compose logs -f ```
### Navigate to your instance and create a user [#navigate-to-your-instance-and-create-a-user-step] On the first launch, you will be prompted to setup a super-admin account. Create User Enter a username and password, then click "Continue", then "Finish".
### Navigate to Settings [#navigate-to-settings-step] After being redirected to the dashboard, click on your username in the top right corner, then click on "Settings" in the dropdown menu. Settings
### Scroll down to "Server Actions", and click on "Import Data". [#scroll-down-to-server-actions-and-click-on-import-data-step] Upload Export File
### Upload the export file [#upload-the-export-file-step] Click on "Upload Export (JSON)", and select the JSON file you downloaded earlier from your v3 instance.
### View the data to be imported [#view-the-data-to-be-imported-step] Once the file is selected, you will see a modal with all the data that will be imported. This includes the number of users, files, and other data that will be imported. Export Data Viewer
### Import Options [#import-options-step] After scrolling down within the modal, you will see two options: "Import Settings?" and "Select a user to import data from into the current user." Import Settings
### Import Settings [#import-settings] When checking this box, Zipline will attempt to import settings that are compatible with v4 from the v3 instance. * `CORE_RETURN_HTTPS` * `CORE_TEMP_DIRECTORY` * `CHUNKS_MAX_SIZE` * `CHUNKS_CHUNKS_SIZE` * `CHUNKS_ENABLED` * `UPLOADER_ROUTE` * `UPLOADER_LENGTH` * `UPLOADER_DISABLED_EXTENSIONS` * `UPLOADER_DEFAULT_EXPIRATION` * `UPLOADER_ASSUME_MIMETYPES` * `EXIF_REMOVE_GPS` * `URLS_ROUTE` * `URLS_LENGTH` * `WEBSITE_TITLE` * `WEBSITE_EXTERNAL_LINKS` * `FEATURES_DEFAULT_AVATAR` * `OAUTH_BYPASS_LOCAL_LOGIN` * `FEATURES_OAUTH_LOGIN_ONLY` * `OAUTH_GITHUB_CLIENT_ID` * `OAUTH_GITHUB_CLIENT_SECRET` * `OAUTH_DISCORD_CLIENT_ID` * `OAUTH_DISCORD_CLIENT_SECRET` * `OAUTH_DISCORD_REDIRECT_URI` * `OAUTH_GOOGLE_CLIENT_ID` * `OAUTH_GOOGLE_CLIENT_SECRET` * `OAUTH_GOOGLE_REDIRECT_URI` * `FEATURES_OAUTH_REGISTRATION` * `FEATURES_USER_REGISTRATION` * `FEATURES_ROBOTS_TXT` * `FEATURES_INVITES` * `FEATURES_INVITES_LENGTH` * `FEATURES_THUMBNAILS` * `MFA_TOTP_ISSUER` * `MFA_TOTP_ENABLED` * `CORE_STATS_INTERVAL` * `CORE_INVITES_INTERVAL` * `CORE_THUMBNAILS_INTERVAL` * `DISCORD_URL` * `DISCORD_USERNAME` * `DISCORD_AVATAR_URL` * `DISCORD_UPLOAD_URL` * `DISCORD_UPLOAD_USERNAME` * `DISCORD_UPLOAD_AVATAR_URL` * `DISCORD_SHORTEN_URL` * `DISCORD_SHORTEN_USERNAME` * `DISCORD_SHORTEN_AVATAR_URL` ### Import User Data [#import-user-data] This option allows you to merge data from a user in your export to the currently logged in user (the one you have just setup). This is useful if you used the same username in v3 and v4, as without selecting an option, the importer will skip over the user data that is already present. It is recommended to select the user you want to import data from, as this will merge the data from the user in the export to the currently logged in user. Users that were a super-admin on v3 will also be labelled in red. ## Finalize Migration [#finalize-migration]
### Import Data [#import-data-step] After selecting the options you want, click "Import Data". The server will now start importing the data. You will be prompted to confirm the import. Read the alert, then click "Import Data" to start the import. Confirm Import
### View Import Progress [#view-import-progress-step] The server will now start importing the data. This may take up to a few minutes (is generally pretty quick), depending on the size of the export.
### Import Success [#import-success-step] After the import finishes, you will see a success message: Import Success
## Post-Migration [#post-migration] If you had any issues with migration, it is recommended to turn on [debug logs](/docs/guides/debug), and run the import again. This will give you more information on what went wrong. If the import was successful, the next step is the make sure your files are being served properly: ### Local [#local] If you're using the local datasource and haven't changed the file directory, the same volume will continue to work. Your files will remain accessible at their previous URLs and should still appear on the dashboard. If you've opted to create a new local datasource for migrating to v4, simply copy over the data from your old datasource to the new one, and it should be accessible. ### S3 [#s3] If you're using the S3 datasource, you will need to make sure your S3 bucket is still accessible. If you have changed the bucket name, you will need to update the bucket name in the datasource settings. Other than that, if nothing was changed in the S3 settings, your files should still be accessible at their previous URLs. ### Troubleshooting [#troubleshooting] If you are having issues with the migration, please check the browser logs during the entire process, then check the server logs after the import has finished. If you are still having issues, feel free to join our [Discord server](/discord) and ask for help, or open an issue on [GitHub](/github). ## Other Changes [#other-changes] A lot of other changes have been made in v4, here are some notable ones: * The API has been completely overhauled, most of it will not be compatible with v3's API. * Old upload configurations will not be compatible with v4. To see what headers and options have changed, refer to [Upload Options](/docs/guides/upload-options). * The old `/r/{file}` route has been remamed to `/raw/{file}`. Zipline currently does redirect `/r/{file}` to `/raw/{file}`, but it is recommended to update any links you may have to the new route. # Security Policy (/docs/security) ## Reporting a Vulnerability [#reporting-a-vulnerability] Please report security issues to [GitHub Advisories](https://github.com/diced/zipline/security/advisories/new). When reporting a vulnerability please do not create a public issue as it may allow attackers to exploit the vulnerability before a fix is released. When reporting a vulnerability please include: * Exact details on how to reproduce the vulnerability * A proof of concept (if possible) * Detailed description of the vulnerability * Fill out as much of the security advisory form as possible * Any other details you think are relevant We will try to respond as quickly as possible. ## Supported Versions [#supported-versions] | Version | Supported | | ------- | --------- | | v4.x.x | ✅ | | v3.7.13 | ❌ | | v3.7.x | ❌ | | v3.x.x | ❌ | | v2.x.x | ❌ | | v1.x.x | ❌ | With the release of v4, we will only be updating and patching the latest version of Zipline. We will only patch security vulnerabilities in previous versions if they are critical. We recommend upgrading to v4 as soon as possible. ### EOL v3 [#eol-v3] On February 24, 2024, we [announced](https://github.com/diced/zipline/discussions/723) that Zipline v3 will reach its end of life (EOL) in June 2025. After this date, we will no longer provide updates, security patches, or resolve issues for Zipline v3. We recommend upgrading to v4 as soon as possible within this timeframe. # Authentication (/docs/api/authentication) Learn how to authenticate with the Zipline API using API keys and/or cookies. ## Getting your token [#getting-your-token] 1. Visit your dashboard 2. Click on your avatar/username in the top right corner 3. Click the "Copy token" button to copy your API token to your clipboard * Note this may not work in insecure contexts. If you have issues copying the token with the button, you can also find the token in the user settings page (Click your avatar/username > Settings) and under User you can copy the token/reveal it. Never share this token with anyone, this token has full access to your account and if you are a super-admin, it can be used to change settings. ### How to the token works [#how-to-the-token-works] Tokens are encrypted with the `CORE_SECRET` environment variable, so if you change the `CORE_SECRET`, any previously generated tokens will no longer work. Tokens are: * Encrypted with AES-256-GCM * Contains the generated at timestamp and a randomly generated string of characters * Validated on each request * Every token is unique, even if you generate multiple tokens in a short period of time, they will all be different due to the random string component and the timestamp. What makes a token yours specifically is a random string of characters that acts like another "secret". This token secret along with the instance secret (`CORE_SECRET`) are used to encrypt the token. ### Resetting the token [#resetting-the-token] Resetting the token resets the underlying user secret, which will invalidate all previously generated tokens. This means that if you reset your token, you will need to update any applications or scripts that use the old token with the new token. To reset your token, click the "Reset token" button after clicking the top right avatar/username in the dashboard. ### Using the token [#using-the-token] To authenticate with the Zipline API, include the following header in your requests: ```http Authorization: ``` ## Using a cookie [#using-a-cookie] Zipline allows you to authenticate via cookies as well, which can be useful for browser-based applications or apps that work well by requesting a username/password login flow. To authenticate with cookies, you need to first set a [POST /api/auth/login](/docs/api/api/auth/login/post). ```http POST /api/auth/login Content-Type: application/json { "username": "username", "password": "password" } ``` Which will return a response with a `Set-Cookie` header that contains the zipline session cookie. You can then include this cookie in subsequent requests to authenticate. ```http GET /api/user/files Cookie: zipline_session= ``` # Error Handling (/docs/api/errors) When the Zipline API encounters an error, it returns a consistent JSON payload along with an appropriate HTTP status code. This page explains the error response shape, the meaning of each error code range, and how to handle errors in your applications. ## Error response shape [#error-response-shape] Every error response from the API follows the same structure: ```json { "error": "E1014: File with this name already exists", "code": 1014, "statusCode": 400 } ``` | Field | Type | Description | | ------------ | -------- | --------------------------------------------------------------------------------------------------------- | | `error` | `string` | A human-readable message, prefixed with `E` (e.g. `E1014`). May contain additional context. | | `code` | `number` | The Zipline-specific error code. Use this for programmatic handling rather than parsing the error string. | | `statusCode` | `number` | The HTTP status code returned with the response. Always matches the response's actual HTTP status. | Some endpoints may include additional fields in the error response (for example, validation failures may include a list of fields that failed validation). Always handle unknown fields gracefully. ## Error code ranges [#error-code-ranges] Error codes are grouped into ranges based on the type of error. The first digit indicates the category, and each category maps to a specific HTTP status code by default: | Range | HTTP Status | Category | Description | | ----------- | ----------- | ------------------------------- | ------------------------------------------------------------ | | `1000-1999` | `400` | Validation / client errors | The request was malformed or contained invalid data. | | `2000-2999` | `401` | Session / authentication errors | The request was not authenticated or the session is invalid. | | `3000-3999` | `403` | Permission errors | The authenticated user lacks the permission to perform this. | | `4000-4999` | `404` | Not found errors | The requested resource does not exist. | | `5000-5999` | `413` | Constraint errors | The request exceeded a configured limit (size, quota, etc). | | `6000-6999` | `500` | Internal errors | Something went wrong on the server's side. | | `9000-9999` | varies | Catch-all errors | Generic fallback errors when a more specific code isn't set. | ## Handling errors in your code [#handling-errors-in-your-code] Because every error response shares the same shape, you can write a single helper to parse and react to errors. Below are examples in a few common languages. If using TypeScript, you may also find it useful to just import the `ApiError` type from the Zipline's source to ensure safety. ### JavaScript / TypeScript [#javascript--typescript] ```ts type ApiError = { error: string; code: number; statusCode: number; [key: string]: unknown; }; async function ziplineFetch(path: string, init?: RequestInit): Promise { const res = await fetch(`https://zipline.example.com${path}`, { ...init, headers: { Authorization: process.env.ZIPLINE_TOKEN!, ...init?.headers, }, }); if (!res.ok) { const err = (await res.json()) as ApiError; if (err.code >= 2000 && err.code < 3000) { throw new Error('Your token is invalid or expired.'); } if (err.code >= 5000 && err.code < 6000) { throw new Error('Upload too large or quota exceeded.'); } throw new Error(`Zipline API error: ${err.error}`); } return res.json() as Promise; } ``` ### Python [#python] ```python import os import requests class ZiplineError(Exception): def __init__(self, code: int, status: int, message: str): super().__init__(message) self.code = code self.status = status def zipline_request(method: str, path: str, **kwargs): res = requests.request( method, f'https://zipline.example.com{path}', headers={'Authorization': os.environ['ZIPLINE_TOKEN']}, **kwargs, ) if not res.ok: payload = res.json() raise ZiplineError( code=payload['code'], status=payload['statusCode'], message=payload['error'], ) return res.json() ``` ### cURL [#curl] ```bash curl -i -X POST https://zipline.example.com/api/upload \ -H "Authorization: $ZIPLINE_TOKEN" \ -F "file=@./image.png" ``` If the upload fails, the response body will contain the JSON error payload described above, with the HTTP status code matching `statusCode`. ## Best practices [#best-practices] * Use the `code` field for programmatic error handling * Don't retry `4xx` errors without changing the request, since these indicate a problem with the request * If possible log the entire error if they are `6xxx` internal errors, since these indicate a problem on Zipline's side rather than yours. ## Viewing errors in the API reference [#viewing-errors-in-the-api-reference] Each route in the API Reference lists all the errors, so viewing any route will show all up-to-date possible errors for any endpoint. # API Documentation (/docs/api) The Zipline API provides programmatic access to all core functionality of your Zipline instance, including file uploads, URL shortening, and user management. ## Base URL [#base-url] All API endpoints are prefixed with the base URL to your instance, e.g `https://zipline.example.com/api`. ## Errors [#errors] In the event of an error, the API will return a JSON response with the following structure: ```json { "error": "EXXXX: Error message", "code": 1000, "statusCode": 400 } ``` In any route, click the `4xx` response, and in the dropdown you can select different error codes to see the different error responses. ## API Reference [#api-reference] You can download the OpenAPI spec here, or visit the [`Generate OpenAPI Spec` github action's artifacts](https://github.com/diced/zipline/actions/workflows/openapi.yml) to download the latest version of the OpenAPI spec. The one that lives on the docs is automatically generated whenever the docs are built, and may not be the latest version of the spec. ## Next Steps [#next-steps] # Core (/docs/config/core) ## `DATABASE_URL` [#database_url] `DATABASE_URL` is a **required** environment variable, Zipline will fail to start without it. This variable should be a postgresql connection string. ```dotenv title=".env" DATABASE_URL=postgresql://user:password@localhost:5432/zipline ``` If you prefer, you can also set the individual components of the connection string using the following environment variables: `DATABASE_NAME`, `DATABASE_USERNAME`, `DATABASE_PASSWORD`, `DATABASE_HOST`, and `DATABASE_PORT`. ```dotenv title=".env" DATABASE_NAME=zipline DATABASE_USERNAME=user DATABASE_PASSWORD=password DATABASE_HOST=localhost DATABASE_PORT=5432 ``` If both `DATABASE_URL` and the individual components are provided, `DATABASE_URL` will take precedence. If any one of the individual components are missing, Zipline will fail to start. ## `CORE_HOSTNAME` [#core_hostname] `CORE_HOSTNAME` is an optional environment variable that specifies the hostname that Zipline will bind to, it defaults to `0.0.0.0`. If using Docker, you can set this to `0.0.0.0` so that you can access Zipline from outside the container (with a exposed port). If you want to listen on a UNIX socket, like `/tmp/zipline.sock` you can do so by setting this environment variable to the path to a socket. The `CORE_PORT` variable still needs to be set, but will obviously have no effect. ```dotenv title=".env" CORE_HOSTNAME=127.0.0.1 CORE_HOSTNAME=0.0.0.0 ``` ## `CORE_PORT` [#core_port] `CORE_PORT` is an optional environment variable that specifies the port that Zipline will bind to, it defaults to `3000`. ```dotenv title=".env" CORE_PORT=3000 ``` ## `CORE_SECRET` [#core_secret] `CORE_SECRET` is a **required** environment variable that is used to sign website cookies, and should be kept private. It is recommended to generate this value using a password manager, or with the following command: ```bash openssl rand -base64 32 # 32 is the length, you can change this ``` ```dotenv title=".env" CORE_SECRET="p5g9NUcnpSbSl+N/70rlv+YXcoL5X0LA6fTc8Anz74w=" ``` If you change `CORE_SECRET`, everyone will get logged out. If you want to keep your secret out of your environment variables, you can also point at a file instead: ```dotenv title=".env" CORE_SECRET_FILE=/run/secrets/core_secret ``` See [Reading Environment Variables from Files](/docs/config#reading-environment-variables-from-files). # Datasource (/docs/config/datasource) ## `DATASOURCE_TYPE` [#datasource_type] Must be one of the following values: * [`local`](#local-datasource) - Local file storage through a directory * [`s3`](#s3-datasource) - S3 compatible storage (AWS, Cloudflare R2, Minio, etc.) ```dotenv title=".env" DATASOURCE_TYPE=local DATASOURCE_TYPE=s3 ``` *** ## Local Datasource [#local-datasource] ### `DATASOURCE_LOCAL_DIRECTORY` [#datasource_local_directory] The directory where files will be stored. This directory must be writable by the Zipline server. If no value is provided, the default value is `./uploads` which will map to the `uploads` directory in the Zipline root directory. Paths are resolved relative to the Zipline root directory, so you can use `./` to specify the root directory, or even `../` to specify a directory outside of the Zipline root directory. It also supports absolute paths, such as `/uploads`. ```dotenv title=".env" DATASOURCE_LOCAL_DIRECTORY=./uploads ``` ## S3 Datasource [#s3-datasource] ### `DATASOURCE_S3_ACCESS_KEY_ID` [#datasource_s3_access_key_id] The access key ID for the S3 bucket. ```dotenv title=".env" DATASOURCE_S3_ACCESS_KEY_ID=access_key_id ``` ### `DATASOURCE_S3_SECRET_ACCESS_KEY` [#datasource_s3_secret_access_key] The secret access key for the S3 bucket. ```dotenv title=".env" DATASOURCE_S3_SECRET_ACCESS_KEY=secret ``` ### `DATASOURCE_S3_BUCKET` [#datasource_s3_bucket] The name of the S3 bucket. The bucket must have read/write access with the credentials provided. The bucket does not have to be public for Zipline to access it. ```dotenv title=".env" DATASOURCE_S3_BUCKET=zipline ``` ### `DATASOURCE_S3_REGION` [#datasource_s3_region] If your S3 provider requires a specific region, you can specify it here (it will default to `us-east-1`). AWS, for example, requires a region to be specified. * **AWS S3**: A valid AWS region, such as `us-west-2`, `us-east-1`, etc. This is required for AWS S3. * **Cloudflare R2**: Must be `us-east-1` or `auto` - cloudflare R2 doesn't use regions since they are automatically determined, `us-east-1` is an alias for `auto`. * **Backblaze B2**: Region provided when creating your account and bucket, it may look like `us-west-004`. * **Hetzner Object Storage**: One of `fs1`, `nbg1`, `hel1`. ```dotenv title=".env" DATASOURCE_S3_REGION=us-west-2 ``` ### `DATASOURCE_S3_ENDPOINT` [#datasource_s3_endpoint] This option can be used if you are using a different provider other than Amazon AWS S3. * **Cloudflare R2**: `https://.r2.cloudflarestorage.com` * **Backblaze B2**: `https://s3..backblazeb2.com` * **Hetzner Object Storage**: `https://.your-objectstorage.com/` ```dotenv title=".env" DATASOURCE_S3_ENDPOINT=https://123abc.r2.cloudflarestorage.com # Cloudflare R2 DATASOURCE_S3_ENDPOINT=https://s3.us-west-004.backblazeb2.com # Backblaze B2 DATASOURCE_S3_ENDPOINT=https://fs1.your-objectstorage.com # Hetzner Object Storage DATASOURCE_S3_ENDPOINT=https://s3.us-west-2.amazonaws.com # AWS S3 (recommended to not set if using AWS) DATASOURCE_S3_ENDPOINT=http://localhost:9000 # something like a locally hosted minio server ``` ### `DATASOURCE_S3_FORCE_PATH_STYLE` [#datasource_s3_force_path_style] This option can be used if your S3 provider requires path-style requests. This isn't needed for any of the example providers you may see above, but it might be needed for something like a locally hosted minio server. ```dotenv title=".env" DATASOURCE_S3_FORCE_PATH_STYLE=true ``` If you are using a provider other than AWS S3, you may need to set this option to `true`. If you notice errors when starting Zipline, or when uploading/getting files, try setting this option to `true`. If you are unsure whether or not your provider requires this, check their API documentation or contact their support. ### `DATASOURCE_S3_SUBDIRECTORY` [#datasource_s3_subdirectory] This option can be used to specify a subdirectory in the S3 bucket to store files in. Note that folders don't technically exist in S3, it's just a way the file is named. Displaying files in subdirectories might be different depending on the S3 provider you are using. ```dotenv title=".env" DATASOURCE_S3_SUBDIRECTORY=zipline ``` # Config (/docs/config) Zipline is primarily configured through environment variables. These can be set in a `.env` file in the root of the project, or through `docker-compose.yml` if using Docker. Most of Zipline's settings are also configurable through the settings dashboard. By default, only the `DATABASE_URL` and `CORE_SECRET` environment variables are required. The rest are optional and have default values. It is important that the `CORE_SECRET` is not easily guessable. This secret is used to sign website cookies, and should be kept private. If the secret is comprimised, it is recommended to change it immediately, as it can be used to gain unauthorized access to the website. ## Example [#example] ```dotenv title=".env" DATABASE_URL=postgresql://user:password@localhost:5432/zipline CORE_SECRET="secret" CORE_PORT=3000 CORE_HOSTNAME=0.0.0.0 DATASOURCE_TYPE=local DATASOURCE_LOCAL_DIRECTORY=./uploads ``` ## Reading Environment Variables from Files [#reading-environment-variables-from-files] If you want to set certain environment variables through file content, you can use the `_FILE` suffix. For example, instead of setting the `CORE_SECRET` variable directory, you can create a file named `secret.txt` containing the secret, and set the environment variable to the relative/full path to that file. ```dotenv title=".env" # Instead of this: CORE_SECRET="hello" # You can do this: CORE_SECRET_FILE=./secret.txt ``` This is useful for managing secrets in containerized environments, where secrets can be mounted as files. If using docker secrets, see [this page](https://docs.docker.com/engine/swarm/secrets/) for more information (secrets can be managed within docker compose files as well!). *** ## Links [#links] Variables in the `core` and `datasource` categories can only be configured through environment variables. All other variables can be set either through the settings dashboard or via environment variables listed in the [settings documentation](/docs/config/settings). Core configuration options Local file storage & S3 support Settings that can be managed through the web dashboard # Settings (/docs/config/settings) As of Zipline v4, most configuration options can be managed through the settings dashboard. However, some settings require a server restart. To access the settings dashboard, navigate to `/dashboard/admin/settings` on your Zipline instance. Only super administrators can view or change server settings. settings sidebar access Each section here on the docs has a table of environment variables that can be used instead of the dashboard. These environment variables can be set in your `.env` file or in the `docker-compose.yml` file if you are using Docker. Note that environment variables will override and take precedence over the settings dashboard values (once the environment variables are set, you can't edit them through the dashboard). ## Core [#core] This section is for core configuration, like whether or not to return HTTPS urls. Most of these options require a server restart after saving them. "Trust Proxy" can be enabled if you are running Zipline behind a reverse proxy (like Nginx or Caddy). This will allow Zipline to correctly determine certain values like the protocol, the client IP address, etc. Temporary files default to `./uploads/.tmp`. Setting `CORE_TEMP_DIRECTORY` to another filesystem, such as tmpfs, can reduce local upload performance. ### Variables [#variables] | Variable | Type | Example | | ------------------------ | --------- | --------------------- | | `CORE_RETURN_HTTPS_URLS` | `boolean` | `true` | | `CORE_DEFAULT_DOMAIN` | `string` | `zipline.example.com` | | `CORE_TEMP_DIRECTORY` | `string` | `./uploads/.tmp` | | `CORE_TRUST_PROXY` | `boolean` | `true` | See the other [core settings](/docs/config/core). ### More about Return HTTPS URLs [#more-about-return-https-urls] * When enabled, all URLs generated by Zipline will use the `https://` scheme instead of `http://`. This is useful if you are running Zipline behind a reverse proxy that handles SSL termination, and you want to ensure that all URLs generated by Zipline are secure. * When enabled, this will also affect cookies set by Zipline, as they will be marked as `Secure`, meaning they will only be sent over HTTPS connections. This change was made to improve security when using Zipline behind a reverse proxy with SSL termination, if you are not using HTTPS, cookies may not be sent by the browser leading to issues with authentication and sessions. ## Chunks [#chunks] This section is for partial uploads. When uploading through the dashboard, if the file is too large (`Max Chunk Size`), it will be split into smaller chunks (size of `Chunk Size`). This is useful for large files that may be interrupted during upload due to file size limitations on the server or DNS. ### Variables [#variables-1] | Variable | Type | Example | | ---------------- | --------- | ------- | | `CHUNKS_MAX` | `string` | `95mb` | | `CHUNKS_SIZE` | `string` | `25mb` | | `CHUNKS_ENABLED` | `boolean` | `true` | ## Tasks [#tasks] These are internal tasks that Zipline runs in the background on intervals. The default value for all of them is 30 minutes. All of settings require a server restart after saving. ### Variables [#variables-2] | Variable | Type | Example | | --------------------------------- | -------- | ------- | | `TASKS_DELETE_INTERVAL` | `string` | `30m` | | `TASKS_CLEAR_INVITES_INTERVAL` | `string` | `30m` | | `TASKS_MAX_VIEWS_INTERVAL` | `string` | `30m` | | `TASKS_THUMBNAILS_INTERVAL` | `string` | `30m` | | `TASKS_METRICS_INTERVAL` | `string` | `30m` | | `TASKS_CLEAN_THUMBNAILS_INTERVAL` | `string` | `30m` | ## Multi-Factor Authentication [#multi-factor-authentication] These are settings to manage passkey and time-based one-time password (TOTP) authentication. Passkeys are used as a method of passwordless authentication. They can be your phone or security key (whatever your OS supports!). When enabled, the login screen will include a "Login with Passkey" button below the login fields. Once clicked a dialog (varies by browser and OS, some may not support this feature) will appear with a passkey that can be used to login. windows security dialog macOS security dialog Time-based one-time passwords are generated by an authenticator app like Google Authenticator, Authy, 2FAS Auth. When enabled, after logging in with a username and password, the user will be prompted to enter a code from their authenticator app. ### Variables [#variables-3] | Variable | Type | Example | | ---------------------- | --------- | ----------------------------- | | `MFA_TOTP_ENABLED` | `boolean` | `true` | | `MFA_TOTP_ISSUER` | `string` | `Zipline` | | `MFA_PASSKEYS_ENABLED` | `boolean` | `true` | | `MFA_PASSKEYS_RP_ID` | `string` | `zipline.example.com` | | `MFA_PASSKEYS_ORIGIN` | `string` | `https://zipline.example.com` | ## Features [#features] These are various features that you can enable or disable. Some of these features may require a server restart after saving. Thumbnails are generated with `ffmpeg`, some files may not be supported. If you are having issues with thumbnails, feel free to open an issue on GitHub with relevant info. The command used to generate thumbnails is `ffmpeg -i tmpfile -y -vframes 1 -filter:v thumbnail -f mjpeg out.jpg`. If you are using S3, the file is downloaded and saved to the temp directory. `Thumbnails Number Threads` is the number of worker threads to spawn when generating thumbnails. It is recommended to set this to the number of CPU threads on your server. If you set this too high, it may cause performance issues. If you are noticing performance issues, try setting this value to `1`, so that only one worker thread is spawned. If this value is low, thumbnails will be grouped to be processed by the same worker thread, which may cause a bottleneck. Enabling instantaneous thumbnails, makes sure that thumbnails are generated after each video file is uploaded instead of on an interval. ### Variables [#variables-4] | Variable | Type | Example | | ------------------------------------- | --------- | ------- | | `FEATURES_IMAGE_COMPRESSION` | `boolean` | `true` | | `FEATURES_ROBOTS_TXT` | `boolean` | `true` | | `FEATURES_HEALTHCHECK` | `boolean` | `true` | | `FEATURES_USER_REGISTRATION` | `boolean` | `true` | | `FEATURES_OAUTH_REGISTRATION` | `boolean` | `true` | | `FEATURES_DELETE_ON_MAX_VIEWS` | `boolean` | `true` | | `FEATURES_THUMBNAILS_ENABLED` | `boolean` | `true` | | `FEATURES_THUMBNAILS_NUM_THREADS` | `number` | `4` | | `FEATURES_THUMBNAILS_FORMAT` | `string` | `jpg` | | `FEATURES_THUMBNAILS_INSTANTANEOUS` | `boolean` | `false` | | `FEATURES_METRICS_ENABLED` | `boolean` | `true` | | `FEATURES_METRICS_ADMIN_ONLY` | `boolean` | `false` | | `FEATURES_METRICS_SHOW_USER_SPECIFIC` | `boolean` | `true` | | `FEATURES_VERSION_CHECKING` | `boolean` | `true` | ## Files [#files] These settings are for viewing and uploading files. The route can be anything, even `/` if you want to serve files from the root of your domain. Enabling `Remove GPS Metadata` will attempt to remove any exif GPS metadata from images uploaded to Zipline. The `Assume Mimetypes` setting makes Zipline detect a file's MIME type from its actual contents instead of trusting what the browser claims. If your instance is public, see [Hardening](/docs/guides/hardening#assume-mime-types) for why you probably want this on along with `FILES_DISABLED_TYPES` and `FILES_DISABLED_TYPES_DEFAULT`. **Disabled Types** is a comma-separated list of MIME types to block or rewrite on upload (for example, `text/html, application/javascript`). It is recommended to enable **Assume Mimetypes** when using this setting, so files are classified by their actual contents rather than what the browser claims. **Default MIME for Disabled Types** sets the MIME type to serve for blocked types instead of rejecting the upload. A common value is `application/octet-stream`, which forces the browser to download the file instead of rendering it inline. Leave this blank to refuse uploads that match a disabled type entirely. **Extensionless URLs** allows file links to work without the file extension in the URL (for example, `/u/abc123` instead of `/u/abc123.png`). Upload responses include the extension by default; users can request an extensionless response URL from the dashboard upload options or with the [`x-zipline-extensionless` header](/docs/guides/upload-options#extensionless-urls). When multiple files could match an extensionless name, the most recently uploaded file is used. ### Variables [#variables-5] | Variable | Type | Example | | ----------------------------------- | ---------- | ---------------------------------- | | `FILES_ROUTE` | `string` | `/u` | | `FILES_LENGTH` | `number` | `6` | | `FILES_DEFAULT_FORMAT` | `string` | `random` | | `FILES_DISABLED_TYPES` | `string[]` | `text/html,application/javascript` | | `FILES_DISABLED_TYPES_DEFAULT` | `string` | `application/octet-stream` | | `FILES_DISABLED_EXTENSIONS` | `string[]` | `exe,bat,dmg` | | `FILES_MAX_FILE_SIZE` | `string` | `100mb` | | `FILES_MAX_FILES_PER_UPLOAD` | `number` | `10` | | `FILES_DEFAULT_EXPIRATION` | `string` | `30d` | | `FILES_ASSUME_MIMETYPES` | `boolean` | `true` | | `FILES_DEFAULT_DATE_FORMAT` | `string` | `YYYY-MM-DD_HH:mm:ss` | | `FILES_REMOVE_GPS_METADATA` | `boolean` | `true` | | `FILES_RANDOM_WORDS_NUM_ADJECTIVES` | `number` | `3` | | `FILES_RANDOM_WORDS_SEPARATOR` | `string` | `-` | | `FILES_DEFAULT_COMPRESSION_FORMAT` | `string` | `webp`, `jpg` | | `FILES_EXTENSIONLESS_URLS` | `boolean` | `true` | ## URL Shortener [#url-shortener] These settings are for the URL shortener feature. The route can be anything, even `/` if you want to serve shortened URLs from the root of your domain. ### Variables [#variables-6] | Variable | Type | Example | | ------------- | -------- | ------- | | `URLS_ROUTE` | `string` | `/go` | | `URLS_LENGTH` | `number` | `4` | ## Invites [#invites] Invites are used to invite users to your Zipline instance with the use of invite codes/links. When enabled, users will be able to send invites to other users. Pair this with **User Registration** disabled if you want an invite-only instance. See [Features](#if-you-want-only-your-friends-on-the-instance). ### Variables [#variables-7] | Variable | Type | Example | | ----------------- | --------- | ------- | | `INVITES_ENABLED` | `boolean` | `true` | | `INVITES_LENGTH` | `number` | `6` | ## Domains [#domains] These are pre-defined domains that can be selected when uploading files or shortening URLs. This is useful if you have multiple domains pointing to your Zipline instance and want to allow users to select which domain to use. | Variable | Type | Example | | --------- | ---------- | ---------------------------------- | | `DOMAINS` | `string[]` | `zip.example.com,zip1.example.com` | ## Ratelimit [#ratelimit] If enabled, the ratelimit will be enforced on the `/api/upload` and `/api/shorten` routes. If you set the `Max Requests`, you will have to also set the `Window` in seconds. The `Max Requests` is the number of requests allowed in the `Window` before the ratelimit is enforced. The allow list is a comma-separated list of IP addresses that are allowed to bypass the ratelimit. ### Variables [#variables-8] | Variable | Type | Example | | ------------------------ | ---------- | ----------------- | | `RATELIMIT_ENABLED` | `boolean` | `true` | | `RATELIMIT_MAX` | `number` | `10` | | `RATELIMIT_WINDOW` | `number` | `60` | | `RATELIMIT_ADMIN_BYPASS` | `boolean` | `true` | | `RATELIMIT_ALLOW_LIST` | `string[]` | `1.1.1.1,8.8.8.8` | ## Website [#website] These are settings for the dashboard, mostly user facing settings. The `Default Avatar` must be a path on the server to an image file. The default theme settings are used for the login and viewing image pages when not logged in. The `Terms of Service` should be a path on the server to a `.md` (Markdown) file. Once set, when a user registers, they will be prompted to read and agree to the terms of service which will be rendered markdown. External links can be configured through a JSON array. Each object in the array must have the `name` and `url` keys. The `name` is the name of the link, and the `url` is the URL to the link. Visit [External Links Builder](/docs/guides/external-links) to help generate the JSON array. ### Variables [#variables-9] | Variable | Type | Example | | ------------------------------- | --------- | -------------------------------------------------- | | `WEBSITE_TITLE` | `string` | `Zipline` | | `WEBSITE_TITLE_LOGO` | `string` | `https://example.com/logo.png` | | `WEBSITE_EXTERNAL_LINKS` | `json` | `"{\"name\":\"test\", \"url\": \"https://link\"}"` | | `WEBSITE_LOGIN_BACKGROUND` | `string` | `https://example.com/bg.png` | | `WEBSITE_LOGIN_BACKGROUND_BLUR` | `boolean` | `true` | | `WEBSITE_DEFAULT_AVATAR` | `string` | `/absolute/path/to/file` | | `WEBSITE_TOS` | `string` | `/absolute/path/to/markdown.md` | | `WEBSITE_THEME_DEFAULT` | `string` | `system` | | `WEBSITE_THEME_DARK` | `string` | `builtin:dark_blue` | | `WEBSITE_THEME_LIGHT` | `string` | `builtin:light_blue` | ## OAuth [#oauth] These settings enable the four OAuth providers that Zipline supports. The currently supported providers are Google, GitHub, Discord, and OIDC (like Okta, Authentik, Keycloak, etc.). Each provider requires a client ID and client secret. There are more detailed instructions on how to set up each provider below the settings. For OAuth providers to show up, the "OAuth Registration" toggle must be enabled in the [Features](#features) section. * [Google](/docs/guides/oauth/google) * [GitHub](/docs/guides/oauth/github) * [Discord](/docs/guides/oauth/discord) * [OIDC](/docs/guides/oauth/oidc) ### Variables [#variables-10] | Variable | Type | Example | | ----------------------------- | ---------- | ---------------------------------------- | | `OAUTH_BYPASS_LOCAL_LOGIN` | `boolean` | `true` | | `OAUTH_LOGIN_ONLY` | `boolean` | `true` | | `OAUTH_DISCORD_CLIENT_ID` | `string` | `discord-client-id` | | `OAUTH_DISCORD_CLIENT_SECRET` | `string` | `discord-client-secret` | | `OAUTH_DISCORD_REDIRECT_URI` | `string` | `https://zipline/api/auth/oauth/discord` | | `OAUTH_DISCORD_ALLOWED_IDS` | `string[]` | `id1,id2` | | `OAUTH_DISCORD_DENIED_IDS` | `string[]` | `id3,id4` | | `OAUTH_GOOGLE_CLIENT_ID` | `string` | `google-client-id` | | `OAUTH_GOOGLE_CLIENT_SECRET` | `string` | `google-client-secret` | | `OAUTH_GOOGLE_REDIRECT_URI` | `string` | `https://zipline/api/auth/oauth/google` | | `OAUTH_GITHUB_CLIENT_ID` | `string` | `github-client-id` | | `OAUTH_GITHUB_CLIENT_SECRET` | `string` | `github-client-secret` | | `OAUTH_GITHUB_REDIRECT_URI` | `string` | `https://zipline/api/auth/oauth/github` | | `OAUTH_OIDC_CLIENT_ID` | `string` | `oidc-client-id` | | `OAUTH_OIDC_CLIENT_SECRET` | `string` | `oidc-client-secret` | | `OAUTH_OIDC_AUTHORIZE_URL` | `string` | `https://oidc.example.com/auth` | | `OAUTH_OIDC_USERINFO_URL` | `string` | `https://oidc.example.com/user` | | `OAUTH_OIDC_TOKEN_URL` | `string` | `https://oidc.example.com/token` | | `OAUTH_OIDC_REDIRECT_URI` | `string` | `https://zipline/api/auth/oauth/oidc` | ## HTTP Webhooks [#http-webhooks] These settings are for HTTP webhooks. When enabled, Zipline will send a POST request to the URL with the file information. For more information on the payload, see the [HTTP Webhooks](/docs/guides/http-webhooks) guide. ### Variables [#variables-11] | Variable | Type | Example | | ------------------------- | -------- | ----------------------------- | | `HTTP_WEBHOOK_ON_UPLOAD` | `string` | `https://example.com/upload` | | `HTTP_WEBHOOK_ON_SHORTEN` | `string` | `https://example.com/shorten` | ## Discord Webhook [#discord-webhook] These settings are for Discord webhook notifications. If the main `Webhook URL`, `Username`, and `Avatar URL` are set, the On Upload and On Shorten hook will use them *unless*, they have their own values set. Content fields like `Content`, `Embed Title`, `Embed Description` can have [variables](/docs/guides/variables) in them for customization. ### Variables [#variables-12] | Variable | Type | Example | | -------------------------------- | -------- | -------------------------------------- | | `DISCORD_WEBHOOK_URL` | `string` | `https://discord.com/api/webhooks/...` | | `DISCORD_USERNAME` | `string` | `Zipline` | | `DISCORD_AVATAR_URL` | `string` | `https://example.com/avatar.png` | | `DISCORD_ON_UPLOAD_WEBHOOK_URL` | `string` | `https://discord.com/api/webhooks/...` | | `DISCORD_ON_UPLOAD_USERNAME` | `string` | `Zipline Uploads` | | `DISCORD_ON_UPLOAD_AVATAR_URL` | `string` | `https://example.com/avatar.png` | | `DISCORD_ON_UPLOAD_CONTENT` | `string` | `{user.username} uploaded {file.name}` | | `DISCORD_ON_UPLOAD_EMBED` | `json` | `{"title": "New upload"}` | | `DISCORD_ON_SHORTEN_WEBHOOK_URL` | `string` | `https://discord.com/api/webhooks/...` | | `DISCORD_ON_SHORTEN_USERNAME` | `string` | `Zipline Shortener` | | `DISCORD_ON_SHORTEN_AVATAR_URL` | `string` | `https://example.com/avatar.png` | | `DISCORD_ON_SHORTEN_CONTENT` | `string` | `{user.username} shortened a URL` | | `DISCORD_ON_SHORTEN_EMBED` | `json` | `{"title": "New shortened URL"}` | ## PWA [#pwa] These settings are for the Progressive Web App (PWA) feature. While it doesn't fully function as a PWA, Zipline does provide a manifest file so that you can install Zipline as an app on your devices. **Note:** When enabling PWAs, you need to make sure that favicons available in the `public/` directory. If you cloned the repository, these should be there already, but if you didn't (using docker), you can download them from the [GitHub repository](https://github.com/diced/zipline/tree/trunk/public/). ### Variables [#variables-13] | Variable | Type | Example | | ---------------------- | --------- | ---------------------- | | `PWA_ENABLED` | `boolean` | `true` | | `PWA_TITLE` | `string` | `Zipline PWA` | | `PWA_SHORT_NAME` | `string` | `Zipline` | | `PWA_DESCRIPTION` | `string` | `File & URL shortener` | | `PWA_BACKGROUND_COLOR` | `string` | `#ffffff` | | `PWA_THEME_COLOR` | `string` | `#000000` | # SSL (Deprecated) (/docs/config/ssl) Native SSL support is no longer supported as of Zipline 4.4.1. Please switch to using a reverse proxy to handle SSL. * See [NGINX or Caddy](/docs/guides/reverse-proxy) # Docker (/docs/get-started/docker) Zipline can be easily installed and run using Docker Compose. This is the recommended installation method for most users, as it abstracts away the complexities of setting up the environment and dependencies.
## Set up `docker-compose.yml` [#set-up-docker-composeyml-step] First, download the `docker-compose.yml` file: curl wget ```bash curl -LO https://zipline.diced.sh/docker-compose.yml ``` ```bash wget https://zipline.diced.sh/docker-compose.yml ``` **If you don't already have a `.env`** file with a database password and zipline secret, you can generate one with the following command: ```bash copy echo "POSTGRESQL_PASSWORD=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" > .env echo "CORE_SECRET=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" >> .env ``` ```bash openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n' ``` * `openssl rand -base64 42`: Generate 42 random bytes in base64 encoding. * `tr -dc A-Za-z0-9`: Remove all characters except for letters and numbers. * `cut -c -32`: Cut the string to 32 characters. * `tr -d '\n'`: Remove the newline character. Zipline will fail to start without the `POSTGRESQL_PASSWORD` and `CORE_SECRET` variables. You will also want to setup a [datasource](/docs/config/datasource) for your instance. By default, Zipline will use `./uploads` (the docker-compose.yml also includes a volume for this) for local file storage. You can configure a different datasource (e.g. S3) by setting the appropriate environment variables in the `.env` file.
## Run the server [#run-the-server-step] ```bash copy docker compose pull docker compose up -d ``` After starting, Zipline will be available at `http://:3000` where `` is the IP address of the machine running Zipline. You will be redirected to a setup page where you can configure the default super administrator username and password.
## Updating [#updating] To update Zipline, simply run the following command: ```bash copy docker compose pull docker compose up -d ``` This will pull the latest Zipline image and restart the server. ## Next steps [#next-steps] After setting up Zipline, you may want to: * Harden your instance by following the [hardening guide](/docs/guides/hardening) * Setup [2FA](/docs/guides/2fa) or [Passkeys](/docs/guides/passkeys) for extra security * Explore the [configuration options](/docs/config) to customize your instance * Setup a [reverse proxy](/docs/guides/reverse-proxy) for better performance and security # Get started (/docs/get-started) Easy installation with Docker Compose Not recommended for most users All the environment variables you can use Setting up a reverse proxy like NGINX and setting up SSL through it How to migrate your data from Zipline v3 to v4 Reference for the Zipline API # Building from source (/docs/get-started/source) Building Zipline from source is not recommended for most users, as it requires setting up a development environment and managing dependencies manually. However, if you want to contribute to the project or need to customize it in ways that are not supported by the pre-built Docker image, building from source can be a good option.
## Install Prerequisites [#install-prerequisites-step] * [nodejs@22](https://nodejs.org/) * [pnpm@11](https://pnpm.io/installation) * [ffmpeg](https://ffmpeg.org/download.html) (for generating thumbnails, optional) If you would like to see what versions Zipline has been successfully built on, visit the [actions history](https://github.com/diced/zipline/actions/workflows/build.yml?query=branch%3Av4). Click the latest one on the top, and you can see runs labelled like `build (20.x, amd64)`. This means that Zipline was successfully built on node version 20.x on an amd64 architecture.
## Clone the repository [#clone-the-repository-step] ```bash copy git clone https://github.com/diced/zipline cd zipline ```
## Install dependencies [#install-dependencies-step] ```bash copy pnpm install ```
## Building Zipline [#building-zipline-step] Before building Zipline, you will have to create a `.env` file, as well as have a PostgreSQL database running. An example .env looks like this: ```bash copy # Replace username and password with your PostgreSQL credentials DATABASE_URL=postgresql://username:password@localhost:5432/zipline # These two are optional, but you can set them to change the hostname and port CORE_HOSTNAME=0.0.0.0 CORE_PORT=3000 ``` You will also want to setup a [datasource](/docs/config/datasource) for your instance. By default, Zipline will use `./uploads` for local file storage. You can configure a different datasource (e.g. S3) by setting the appropriate environment variables in the `.env` file. You will also need to generate a secret for zipline, to do this you can use the command below to append it to your `.env` file: ```bash copy echo "CORE_SECRET=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" >> .env ``` What does this command do? ```bash openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n' ``` * `openssl rand -base64 42`: Generate 42 random bytes in base64 encoding. * `tr -dc A-Za-z0-9`: Remove all characters except for letters and numbers. * `cut -c -32`: Cut the string to 32 characters. * `tr -d '\n'`: Remove the newline character. Then build Zipline: ```bash copy pnpm build ```
## Run the server [#run-the-server-step] ```bash copy pnpm start ```
## Updating [#updating] To update Zipline, simply run the following command: ```bash copy git pull ``` Then update dependencies and build Zipline: ```bash copy pnpm install pnpm build ``` Then start the server: ```bash copy pnpm start ``` ## Next steps [#next-steps] After setting up Zipline, you may want to: * Harden your instance by following the [hardening guide](/docs/guides/hardening) * Setup [2FA](/docs/guides/2fa) or [Passkeys](/docs/guides/passkeys) for extra security * Explore the [configuration options](/docs/config) to customize your instance * Setup a [reverse proxy](/docs/guides/reverse-proxy) for better performance and security # 2FA (/docs/guides/2fa) TOTP (time-based one-time password) adds a six-digit code on top of your password at login. The code comes from an authenticator app on your phone like [2FAS](https://2fas.com/), [Authy](https://authy.com/), [Aegis](https://getaegis.app/), Google Authenticator, or whatever password manager you already use. ## Enabling TOTP on your instance [#enabling-totp-on-your-instance] 1. Head to **Server Settings** 2. Scroll down to **Multi-Factor Authentication** 3. Toggle **Enable TOTP** and click **Save** Enable 2FA ### Setting an issuer [#setting-an-issuer] The **Issuer** is the name shown in the authenticator app next to the code. Set it to something recognizable like `Zipline` or the name of your instance (e.g. `zipline.example.com`). Apps like Authy and 2FAS use this to pick an icon automatically, so users don't have to guess which account they're looking at. ### Environment variables [#environment-variables] ```dotenv title=".env" MFA_TOTP_ENABLED=true MFA_TOTP_ISSUER=Zipline ``` ## Setting up TOTP on your account [#setting-up-totp-on-your-account] Once you have enabled TOTP on your instance, users can now enable 2FA on their own accounts: 1. Click your avatar in the top right and head to **Manage Account** 2. Scroll down to **Multi-Factor Authentication** 3. Click **Enable TOTP** 4. Scan the QR code with your authenticator app, or copy the secret and paste it in manually 5. Enter the six-digit code your app shows you to confirm 6. Click **Save** Once set up, you'll be asked for a code every time you log in. ## Disabling TOTP on your account [#disabling-totp-on-your-account] 1. Head to **Manage Account** → **Multi-Factor Authentication** 2. Click **Disable TOTP** 3. Confirm with your current TOTP code ## Resetting TOTP for a user [#resetting-totp-for-a-user] If a user loses their authenticator and can't log in, an admin can clear their TOTP secret with [ziplinectl set-user](/docs/guides/ctl/ziplinectl-set-user): ```bash ziplinectl set-user -i totpSecret null ``` ## See also [#see-also] * [Passkeys](/docs/guides/passkeys) — passwordless login as an alternative or addition to TOTP * [MFA settings reference](/docs/config/settings#multi-factor-authentication) # Bytes (/docs/guides/bytes) Some settings use human readable byte sizes. Here is a table of the byte sizes and their human readable counterparts. | Unit | Bytes | | ---- | ------------------------------------------ | | b | 1 byte | | kb | 1024 bytes | | mb | 1024 \* 1024 bytes | | gb | 1024 \* 1024 \* 1024 bytes | | tb | 1024 \* 1024 \* 1024 \* 1024 bytes | | pb | 1024 \* 1024 \* 1024 \* 1024 \* 1024 bytes | ## Example [#example] * `1gb` will resolve to `1073741824` bytes * `1tb` will resolve to `1099511627776` bytes * `1` will resolve to `1` byte * `1kb` will resolve to `1024` bytes * you can use these values in any setting field that requires a byte size # Customizing the Dashboard (/docs/guides/customize-dash) Zipline offers a couple of settings that let you customize the dashboard to your liking. This guide will walk you through the available options. ## Server Settings [#server-settings] These settings are configured for the entire server, so they will show up for all users. ### Favicons [#favicons] Changing the favicon is as simple as replacing the `favicon.ico` file in the `public` directory. The favicon is the small icon that appears in the browser tab next to the page title. It is also used when a user bookmarks the page. Favicon ### Title [#title] If you want to change the title of the dashboard, you can do so by changing the title by heading over to the **Server Settings** 1. Scroll down to the **Website** section 2. Change the **Title** field to your liking Title This will change the title that appears in the browser tab, as well as the title that appears in the navigation bar. Title ### Title Logo [#title-logo] If you want to change the logo that appears in the navigation bar, you can do so by changing the **Title Logo** by heading over to the **Server Settings** 1. Scroll down to the **Website** section 2. Change the **Title Logo** field to the URL of the image you want to use Title Logo ## External Links [#external-links] External Links are links that are found at the bottom of the sidebar. To learn more about how to add external links, check out the [External Links](/docs/guides/external-links) guide. If you want to change the external links, you can do so by heading over to the **Server Settings** 1. Scroll down to the **Website** section 2. Change the **External Links** field to your liking. This field must be a valid JSON array, with each object containing a `name` and `url` field. External Links If you want help making the JSON array, you can use the [External Links Builder](/docs/guides/external-links#builder) tool. ### Terms of Service [#terms-of-service] Zipline allows you to add a Terms of Service link. The file provided must be a Markdown file, and it is accessible at `/auth/tos` when configured. If you want to change the Terms of Service, you can do so by heading over to the **Server Settings** 1. Scroll down to the **Website** section 2. Change the **Terms of Service** field to the the **path** of the Markdown file you want to use. If you are using docker, make sure to mount the file to the container. For example, when using docker-compose: ```yaml ... services: zipline: ... volumes: - ./path/to/tos.md:/zipline/tos.md ... ``` Then you can set the path to `/zipline/tos.md` in the **Terms of Service** field. Now, when users sign up they will see a checkbox requiring them to agree to the Terms of Service. Terms of Service ### Login Background [#login-background] If you want to change from the default solid color background on the login page, you can do so by changing the **Login Background** by heading over to the **Server Settings**. 1. Scroll down to the **Website** section 2. Change the **Login Background** field to the URL of the image you want to use Login Background ### Login Background Blur [#login-background-blur] Configures whether the login background should be blurred or not. ### Theme [#theme] Zipline allows you to change the theme of the dashboard as well. Users are free to set their own themes, but you can set a default theme that is used on view-routes and the dashboard when the user has not set their own theme yet. theme Setting the main theme to "System" will expose two more options: Dark theme and Light theme. This setting will be used when the users system color-scheme is dark or light respectively. Note that what counts as dark/light is dependent on the OS and browser. #### Custom Themes [#custom-themes] If you want to make your own theme, visit [theming](/docs/guides/themes) for more info. On the other hand, if you are looking to contribute a theme, you can do so by simply submitting a PR with the theme you want to add in! ## User Dashboard Settings [#user-dashboard-settings] Head over to your user settings page, and then you should see a section titled "Dashboard Settings". Dashboard Settings ### File Viewer [#file-viewer] As of Zipline 4.6, there are now two options for the "file viewer" which controls what view you get when clicking on a file. * Fullscreen: new viewer, is fullscreen and takes up the entire viewport. * Modal: the old viewer, most people recognize. This may or may not be deprecated in the future. ### Default Domain [#default-domain] This domain can be configured based on the server settings' domains or if there are none set, it will become an input where you can put in a domain. It's then used in anywhere a domain is needed, for example, when copying file urls. ### Home Page Sections [#home-page-sections] You can control which sections appear on the dashboard home page: * **Show recents**: Displays your three most recently uploaded files at the top of the home page. * **Show activity**: Displays an activity chart with your daily uploads and logins over a selectable window (1, 7, 14, or 30 days). * **Show file types**: Displays a table breaking down your uploads by MIME type. All three are enabled by default. Toggle them off in **Dashboard Settings** if you prefer a simpler home page. # Debug (/docs/guides/debug) ## Verbose Logs [#verbose-logs] Enabling debug logs will show more verbose logs from Zipline. This is mostly useful when debugging an issue with Zipline. This mode is helpful when reporting issues, as it provides more context about what is happening. ### Enabling [#enabling] To enable debug mode, set the `DEBUG` environment variable to `zipline`. Turn this off when you're done troubleshooting. It logs a lot and there's no reason to leave it running on an instance people actually use. ```dotenv title=".env" DEBUG=zipline ``` ### Disabling [#disabling] Simply remove the `DEBUG` environment variable. ### Example Output [#example-output] debug out ## Log formatting [#log-formatting] Log colors are enabled automatically when writing to a terminal. Set `ZIPLINE_NO_COLOR` to any value to disable color output (useful in Docker or when piping logs to a file): ```dotenv title=".env" ZIPLINE_NO_COLOR=true ``` To change the timestamp format in log lines, set `ZIPLINE_OVERRIDE_LOG_DATE_FORMAT` to a [date format string](https://day.js.org/docs/en/display/format): ```dotenv title=".env" ZIPLINE_OVERRIDE_LOG_DATE_FORMAT="YYYY-MM-DD HH:mm:ss" ``` By default, log output from `db` and `config` loggers is suppressed in worker threads. Set `ZIPLINE_OVERRIDE_DISABLED_WORKER_LOG` to any value to enable it: ```dotenv title=".env" ZIPLINE_OVERRIDE_DISABLED_WORKER_LOG=true ``` ## Database logging [#database-logging] To log Prisma database queries, set `ZIPLINE_DB_LOG` to `true`. You can also pass a comma-separated list of [Prisma log levels](https://www.prisma.io/docs/orm/prisma-client/observability-and-logging/logging) (for example, `query,info,warn,error`): ```dotenv title=".env" ZIPLINE_DB_LOG=true ``` ## Detailed Memory Usage [#detailed-memory-usage] Enabling detailed memory usage logging will log the memory usage of Zipline at 1 second intervals. This is useful for diagnosing memory leaks or high memory usage issues. ### Enabling [#enabling-1] To enable detailed memory usage, set the `ZIPLINE_MONITOR_MEMORY` environment variable to `true`. ### Disabling [#disabling-1] To disable detailed memory usage, set the `ZIPLINE_MONITOR_MEMORY` environment variable to `false` or remove it entirely. ### Example Output [#example-output-1] The output of the detailed memory usage will output to a file called `.memory.log` in the current working directory. Each line in the file has values seperated by commas, and each line represents an entry logged at 1 second intervals. ```json 1762909354,651100160,100545640,296501248,13717303,1183518,443816,2374767 1762909355,651132928,100567832,296501248,13718816,1184991,444319,2375661 1762909356,651182080,100593088,296501248,13720273,1186448,444625,2376558 1762909357,651182080,100609952,296501248,13721730,1187905,444929,2377380 1762909358,651280384,100636944,296501248,13723187,1189362,445310,2378343 ``` To parse the file, each value is in the following order: 1. ts (timestamp in UNIX epoch) 2. mem\_rss (resident set size) 3. mem\_heap\_used (heap used) 4. mem\_heap\_total (total heap) 5. mem\_external (external memory) 6. mem\_array\_buffers (array buffer memory) 7. cpu\_system (system CPU usage) 8. cpu\_user (user CPU usage) #### Parsing Examples [#parsing-examples] ```cpp title="read.cc" #include #include #include #include #include using namespace std; struct MemoryCpuLog { long long ts, mem_rss, mem_heap_used, mem_heap_total, mem_external, mem_array_buffers, cpu_system, cpu_user; }; bool parseLine(const string &line, MemoryCpuLog &entry) { stringstream ss(line); string part; vector fields = {&entry.ts, &entry.mem_rss, &entry.mem_heap_used, &entry.mem_heap_total, &entry.mem_external, &entry.mem_array_buffers, &entry.cpu_system, &entry.cpu_user}; for (auto *field : fields) { if (!getline(ss, part, ',')) return false; *field = stoll(part); } return true; } int main() { vector entries = {}; ifstream file(".memory.log"); if (!file.is_open()) { cerr << "failed" << endl; return 1; } string line; while (getline(file, line)) { MemoryCpuLog entry; if (parseLine(line, entry)) { entries.push_back(entry); } else { cerr << "failed" << endl; return 1; } } // do something with the entries return 0; } ``` ```ts title="read.ts" import { readFile } from 'fs/promises'; interface MemoryCpuLog { ts: number; mem_rss: number; mem_heap_used: number; mem_heap_total: number; mem_external: number; mem_array_buffers: number; cpu_user: number; cpu_system: number; } function readLine(line: string): MemoryCpuLog { const [ts, mem_rss, mem_heap_used, mem_heap_total, mem_external, mem_array_buffers, cpu_user, cpu_system] = line .trim() .split(',') .map((value) => Number(value)); return { ts, mem_rss, mem_heap_used, mem_heap_total, mem_external, mem_array_buffers, cpu_user, cpu_system, }; } async function readLog(file: string): Promise { const str = await readFile(file, 'utf-8'); const lines = str.trim().split('\n'); return lines.map(readLine); } const entries = await readLog('.memory.log'); ``` ```python title="read.py" import os LOG_FILE = ".memory.log" def read_log(): if not os.path.exists(LOG_FILE): return [] entries = [] with open(LOG_FILE, "r") as f: for line in f: line = line.strip() if not line or not line[0].isdigit(): continue try: parts = line.split(",") if len(parts) != 8: continue entry = { "ts": int(parts[0]), "mem_rss": int(parts[1]), "mem_heap_used": int(parts[2]), "mem_heap_total": int(parts[3]), "mem_external": int(parts[4]), "mem_array_buffers": int(parts[5]), "cpu_system": int(parts[6]), "cpu_user": int(parts[7]), } entries.append(entry) except Exception: continue return entries ``` ## OpenAPI schema [#openapi-schema] Set `ZIPLINE_OUTPUT_OPENAPI` to `true` to write the OpenAPI schema to `openapi.json` in the current working directory and exit. The server starts normally, registers all routes, then writes the file and shuts down. ```dotenv title=".env" ZIPLINE_OUTPUT_OPENAPI=true ``` ## Git commit SHA [#git-commit-sha] `ZIPLINE_GIT_SHA` sets the commit hash shown alongside the version on the [admin dashboard](/docs/guides/server-actions). Docker images set this automatically via a build argument. If unset, Zipline tries to read it from the local git repository. ```dotenv title=".env" ZIPLINE_GIT_SHA=abc1234 ``` # Discord Webhooks (/docs/guides/discord-webhooks) ## Creating a Webhook in Discord [#creating-a-webhook-in-discord] 1. Open the channel settings of the channel you want to send notifications to. Channel Settings 2. Click on the `Integrations` tab. 3. Click on the `Create Webhook` button. Create Webhook 4. After creating the webhook, the new webhook will appear in the list of webhooks for that channel. If you already have previous webhooks in this channel, it should appear at the bottom of the list with the name of "Captain Hook". Webhook List 5. Click on the webhook, and copy the webhook URL. Save this for later as you will need it to configure Zipline's Discord webhook notifications. Webhook URL ## Configuring Zipline [#configuring-zipline] 1. Go to your Zipline dashboard, and head over to the **Server Settings** page. 2. Scroll down to the **Discord Webhook** section. 3. Copy the webhook URL you saved earlier into the **Webhook URL** field. Webhook URL Field Additionally, you can configure a username and avatar URL for the webhook. This will change the name and avatar of the webhook in the Discord channel, and is optional. 4. Click on the `Save Changes` button to save the webhook URL and any other changes you made. ## Customizing the Webhook [#customizing-the-webhook] Upon scrolling down a bit more, you will see two sections: **On Upload** and **On Shorten**. These sections allow you to customize the messages that are sent to the Discord channel when a file is uploaded or a URL is shortened. Each section has its own webhook URL, username, and avatar URL fields. This allows you to have different webhooks for the different actions, but if the webhook URL is left blank the default webhook URL will be used. These only serve as "overrides" for the defaults in the above section. Customizing the Webhook Here is what it looks like on Discord: Discord Message ### Embeds [#embeds] Additionally, you can fruther customize the messages by using Discord embeds. To enable this just toggle the `Embed` option, and a new section will appear where you can customize the embed. Embeds Here is what it looks like on Discord: Discord Embed ## Variables [#variables] In the previous screenshots, you may have noticed the use of variables such as `{file.name}` and `{debug.jsonf}`. These are placeholders that will be replaced with the actual values when the message is sent to Discord. For more information, visit the [Variables](/docs/guides/variables) guide. If you would like to test out the variables, you can use the [Playground](/docs/guides/variables#playground) in the Variables guide. ## Other Resources [#other-resources] * [Discord Webhooks Documentation](https://discord.com/developers/docs/resources/webhook) * [HTTP Webhooks](/docs/guides/http-webhooks) * [Variables](/docs/guides/variables) # External Links (/docs/guides/external-links) sidebar ## Builder [#builder] Below is a utility to help you generate the JSON array for external links. # Hardening (/docs/guides/hardening) If your instance is just for you on a private network, you can skip most of this. If you're sharing the URL with friends, an internet stranger, or you just want to be a bit more careful about who can do what. Nothing here is exactly mandatory, pick and choose whatever you would like to have on. ## Use a long secret [#use-a-long-secret] `CORE_SECRET` signs your session cookies. If someone gets it, they can forge logins. So don't use `password123`, and don't share it. ```bash openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 ``` Zipline will only start once there is a secret longer than 32 characters in `CORE_SECRET`. If you do change the secret, everyone currently logged into your instance (including you), will be logged out. ## Keep sensitive values out of plain `.env` [#keep-sensitive-values-out-of-plain-env] Anything Zipline reads from the environment also supports a `_FILE` suffix that points to a file instead. Useful for secrets you don't want sitting in a `.env` someone can `cat`: ```dotenv title=".env" CORE_SECRET_FILE=/run/secrets/core_secret DATABASE_PASSWORD_FILE=/run/secrets/db_password ``` Zipline reads the file contents at startup and uses that as the value. ### With `docker compose` [#with-docker-compose] ```yaml title="docker-compose.yml" services: zipline: image: ghcr.io/diced/zipline:latest environment: CORE_SECRET_FILE: /run/secrets/core_secret secrets: - core_secret secrets: core_secret: file: ./secrets/core_secret.txt ``` See [Docker's secrets docs](https://docs.docker.com/engine/swarm/secrets/) for more information. ## Behind a reverse proxy? [#behind-a-reverse-proxy] If you're running Zipline behind nginx, Caddy, or similar, turn on **Trust Proxy**. Without it, Zipline thinks every request is coming from `127.0.0.1` (your proxy), which breaks rate limiting, breaks the real-IP shown in logs, and can cause weird cookie behavior. ```dotenv title=".env" CORE_TRUST_PROXY=true CORE_RETURN_HTTPS_URLS=true ``` See [Reverse Proxy](/docs/guides/reverse-proxy) for the full setup. ## Turn on MFA [#turn-on-mfa] Turning on any sort of multifactor authentication is a good idea: * **[TOTP / 2FA](/docs/guides/2fa)**: six-digit code from an authenticator app at login. * **[Passkeys](/docs/guides/passkeys)**: passwordless login with a security key, your phone, or device biometrics. ## Lock down who can sign up [#lock-down-who-can-sign-up] If you want to disallow people from registering an account: ```dotenv FEATURES_USER_REGISTRATION=false ``` If you would like to hand out invites: ```dotenv INVITES_ENABLED=true ``` ## OAuth Hardening [#oauth-hardening] If you prefer having users login through OAuth instead of Zipline's password login. ### Stop OAuth from creating new accounts [#stop-oauth-from-creating-new-accounts] Once your OAuth provider is set up, turn on **OAuth Login Only** (`OAUTH_LOGIN_ONLY=true`). Existing users can still log in with OAuth, but the OAuth flow will refuse to create new accounts. Combined with `FEATURES_USER_REGISTRATION=false`, your instance becomes existing-users-only. ### Skip the local login page [#skip-the-local-login-page] If nobody on your instance uses a local password anymore, turn on **Bypass Local Login** (`OAUTH_BYPASS_LOCAL_LOGIN=true`). Hitting `/auth/login` redirects straight to your provider, so people never see the username/password form. If you ever lock yourself out, append `?local=true` to the login URL to get the local form back. ### Discord: limit who can log in [#discord-limit-who-can-log-in] Discord doesn't have a built-in "only these users" gate, so Zipline handles it with two env vars: * `OAUTH_DISCORD_ALLOWED_IDS`: comma-separated Discord user IDs. Only these accounts can log in. * `OAUTH_DISCORD_DENIED_IDS`: comma-separated Discord user IDs. These accounts get blocked. ### OIDC [#oidc] Most OIDC platforms (Authentik, Authelia, Keycloak, Pocket-ID, Okta, etc.) have access policies. Configure your provider so only the people you want have access to the Zipline application. See [OAuth](/docs/guides/oauth) for setup details on each provider. Zipline as of 4.6.0 supports PKCE for OIDC OAuth clients, which is a more secure way to authenticate. ## Assume MIME types [#assume-mime-types] By default Zipline trusts whatever MIME type the browser claims a file is. That means someone can upload an HTML file with `Content-Type: image/png`. Turn on **Assume MIME types** to make Zipline detect the type from the actual file instead: ```dotenv title=".env" FILES_ASSUME_MIMETYPES=true ``` Then block the types you don't want served inline at all. This can be configured in the server settings dashboard under **Files** → **Disabled Types**, or with the environment variable: ```dotenv title=".env" FILES_DISABLED_TYPES="text/html,application/javascript" ``` If you still want people to be able to upload those, but not have the browser execute them, override what they get served as. Set **Default MIME for Disabled Types** in the dashboard, or use: ```dotenv title=".env" FILES_DISABLED_TYPES_DEFAULT="application/octet-stream" ``` That makes the browser download the file instead of trying to display it. Leaving `FILES_DISABLED_TYPES_DEFAULT` blank just refuses the upload entirely with error code `1065`. # HTTP Webhooks (/docs/guides/http-webhooks) If you have enabled [HTTP Webhooks](/docs/config/settings#http-webhooks) within the dashboard settings, Zipline will send a POST request to the URL with the file/url information. ## Events [#events] ### `upload` [#upload] This event is triggered when a file is uploaded to Zipline. | Header | Value | | ------------------------ | -------- | | `x-zipline-webhook` | `true` | | `x-zipline-webhook-type` | `upload` | ```json title="Example Upload application/json Payload" { "type": "upload", "data": { "user": { "id": "cm0ahwkkt00025216yi47btrb", "username": "administrator", "createdAt": "2024-08-26T04:25:45.005Z", "updatedAt": "2024-09-17T23:50:24.492Z", "role": "SUPERADMIN", "view": {}, "quota": null, "sessions": [ "hKb3nyHqCKE6RsHSX4w2KO3zDvjPrsct", "gOARAvpu9hQ7PW6XK6WXoIZ549CKweIf", "XAQf80SaJ8hORk5sgtzwGjV5E8qJ8zjY", "YEk0lvuTl3RB5dYjFNktf9cqnVJ8OIkv", "eSnaw6WVJ5mojRKztpELjvtMpW8nzTZt", "SzH6j84CWBIMhbWZyqFANZ10t6iKUUFd", "pvlJI8w8miXKDfFvQGrsckv4W4F6sP1N" ] }, "file": { "createdAt": "2024-09-19T19:14:37.938Z", "updatedAt": "2024-09-19T19:14:37.938Z", "deletesAt": null, "favorite": false, "id": "cm19o84j60008rjuy4ufwlw7d", "originalName": null, "name": "uGILmB.png", "size": 124104, "type": "image/png", "views": 0, "maxViews": null, "folderId": null, "thumbnail": null, "tags": [] }, "link": { "raw": "http://localhost:3000/raw/uGILmB.png", "returned": "http://localhost:3000/u/uGILmB.png" } } } ``` ### `shorten` [#shorten] This event is triggered when a URL is shortened. | Header | Value | | ------------------------ | --------- | | `x-zipline-webhook` | `true` | | `x-zipline-webhook-type` | `shorten` | ```json title="Example Shorten application/json Payload" { "type": "shorten", "data": { "user": { "id": "cm0ahwkkt00025216yi47btrb", "username": "administrator", "createdAt": "2024-08-26T04:25:45.005Z", "updatedAt": "2024-09-17T23:50:24.492Z", "role": "SUPERADMIN", "view": {}, "quota": null, "sessions": [ "hKb3nyHqCKE6RsHSX4w2KO3zDvjPrsct", "gOARAvpu9hQ7PW6XK6WXoIZ549CKweIf", "XAQf80SaJ8hORk5sgtzwGjV5E8qJ8zjY", "YEk0lvuTl3RB5dYjFNktf9cqnVJ8OIkv", "eSnaw6WVJ5mojRKztpELjvtMpW8nzTZt", "SzH6j84CWBIMhbWZyqFANZ10t6iKUUFd", "pvlJI8w8miXKDfFvQGrsckv4W4F6sP1N" ] }, "url": { "id": "cm19o9at4000arjuyg2n8t7rv", "createdAt": "2024-09-19T19:15:32.728Z", "updatedAt": "2024-09-19T19:15:32.728Z", "code": "AZCYqt", "vanity": "google", "destination": "https://google.com", "views": 0, "maxViews": null, "userId": "cm0ahwkkt00025216yi47btrb" }, "link": { "returned": "http://localhost:3333/go/google" } } } ``` ## Example Node.js Server [#example-nodejs-server] The following code is in TypeScript. ```ts title="webhook.ts" import { IncomingHttpHeaders, createServer } from 'http'; function receive( data: Uint8Array, { 'x-zipline-webhook': webhook, // will always just be "true" 'x-zipline-webhook-type': type, }: IncomingHttpHeaders, ) { const str = new TextDecoder().decode(data); const parsed = JSON.parse(str); // handle data in parsed... // if (type === 'upload') handleUpload(parsed); // if (type === 'shorten') handleShorten(parsed); console.log(`recv(${type}) data: `, parsed); } const server = createServer((req, res) => { const data = new Uint8Array(Number(req.headers['content-length'])); let offset = 0; req.on('data', (chunk) => { data.set(chunk, offset); offset += chunk.length; }); req.on('end', () => { receive(data, req.headers); res.statusCode = 200; res.end(); }); req.on('error', (err) => { console.error(err); res.statusCode = 500; res.end(); }); }); server.listen(3001, () => { console.log('Server is running on', server.address()); }); ``` ## Example Python Server [#example-python-server] This example uses [`http.server`](https://docs.python.org/3/library/http.server.html), but is not recommended to be used in production. ```python title="webhook.py" from http.server import HTTPServer, BaseHTTPRequestHandler import json class WebhookHandler(BaseHTTPRequestHandler): def do_POST(self): content_length = int(self.headers.get('Content-Length', 0)) data = self.rfile.read(content_length) webhook = self.headers.get('x-zipline-webhook') # Should always be "true" type = self.headers.get('x-zipline-webhook-type') try: parsed = json.loads(data.decode('utf-8')) print(f"recv({type}) data:", parsed) # handle data in parsed... # if type == 'upload': handle_upload(parsed) # if type == 'shorten': handle_shorten(parsed) except json.JSONDecodeError: self.send_response(400) self.end_headers() self.wfile.write(b'Invalid JSON') return self.send_response(200) self.end_headers() def run(server_class=HTTPServer, handler_class=WebhookHandler, port=3002): server_address = ('', port) httpd = server_class(server_address, handler_class) print(f'Server is running on port {port}') httpd.serve_forever() if __name__ == '__main__': run() ``` ## Exmaple Go Server [#exmaple-go-server] ```go title="webhook.go" package main import ( "encoding/json" "fmt" "io" "log" "net/http" ) func receive(data []byte, headers http.Header) { // var webhook = headers.Get("x-zipline-webhook") // will always just be "true" var type_ = headers.Get("x-zipline-webhook-type") var parsed map[string]interface{} if err := json.Unmarshal(data, &parsed); err != nil { log.Println("Invalid JSON:", err) return } // handle data in parsed... // if type_ == "upload" { handleUpload(parsed) } // if type_ == "shorten" { handleShorten(parsed) } fmt.Printf("recv(%s): %+v\n", type_, parsed) } func main() { http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) { body, err := io.ReadAll(r.Body) if err != nil { http.Error(w, "Failed to read request body", http.StatusInternalServerError) return } defer r.Body.Close() receive(body, r.Header) w.WriteHeader(http.StatusOK) }) port := 3002 fmt.Printf("Server is running on :%d\n", port) if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil { log.Fatal("Server failed:", err) } } ``` # Milliseconds (/docs/guides/ms) Some settings use human readable **relative** time. Here is a table of the time units and their human readable counterparts. | Unit (Case insensitive) | Milliseconds | | ------------------------------------------ | --------------------------- | | ms, msec, msecs, millisecond, milliseconds | 1 millisecond | | s, sec, secs, second, seconds | 1000 milliseconds | | m, min, mins, minute, minutes | 60,000 milliseconds | | h, hr, hrs, hour, hours | 3,600,000 milliseconds | | d, day, days | 86,400,000 milliseconds | | w, week, weeks | 604,800,000 milliseconds | | y, yr, yrs, year, years | 31,536,000,000 milliseconds | ## Example [#example] * `1s` is equal to `1000` milliseconds * `1m` is equal to `60,000` milliseconds * `1h`, `1 hrs`, etc. is equal to `3,600,000` milliseconds * `1 day`, `1d`, etc. is equal to `86,400,000` milliseconds * you can use these values in any setting that requires a time # Passkeys (/docs/guides/passkeys) Passkeys are a way to authenticate users without requiring them to enter a password. Instead, users can click "Login with a passkey" and follow their device prompts to authenticate. ## Enabling Passkeys [#enabling-passkeys] 1. Head to the **Server Settings** page 2. Scroll down to **Multi-Factor Authentication** 3. Toggle the **Passkeys** switch Additionally, you need to set the "RP ID" (Relying Party ID) and the Origin. * The **RP ID** is typically your domain name (e.g., `example.com`). If you are running Zipline locally or on an IP address, you can set it to `localhost` or the IP address. * The **Origin** is the full URL where your Zipline instance is hosted (e.g., `https://zipline.example.com` or `http://localhost:3000`). Without those two set correctly, passkeys will not work and will most likely throw errors during creation or authentication. Enable Passkeys ## Creating a Passkey [#creating-a-passkey] 1. Head to your user settings (click your user icon in the top right corner) 2. Scroll down to **Multi-Factor Authentication** 3. Click the **Manage Passkeys** button 4. Click the **Create a Passkey** button and follow the prompts on your device. 5. Give a name to your passkey and click **Save** If something went wrong during the creation of your passkey, it will not let you register a passkey for a short period of time. Create a Passkey ## Managing Passkeys [#managing-passkeys] 1. Head to your user settings (click your user icon in the top right corner) 2. Scroll down to **Multi-Factor Authentication** 3. Click the **Manage Passkeys** button Here you can see when your passkey was last used, created at, and choose to delete it. Manage Passkeys If you choose to delete a passkey, it will only be removed from being able to authenticate with Zipline, and only on Zipline's end. You will need to remove it from your device through however your security key/phone handles it. ## Additional Information [#additional-information] Feel free to test out passkeys with [passkeys.io](https://www.passkeys.io/) # Releases (/docs/guides/releases) Zipline is currently being maintained by me ([@diced](https://github.com/diced)) and only me. New releases can come out frequently or take a while depending on my schedule. If a new minor/patch release (4.x.x) is not out, you can build from source or use the `trunk` tag on docker for the latest commit version. ## `latest` [#latest] Updated every once in a while, the most frequent updates will be under `4.x.[number]` where `[number]` increments. These may come out every few weeks or so. This is the **most stable** version of Zipline and is recommended for production use. Just pull the latest image: ```bash docker pull ghcr.io/diced/zipline:latest ``` First switch to the latest tag, for example if the latest tag is `4.0.0`, run: ```bash git checkout v4.0.0 ``` Then follow the [update steps](/docs/get-started/source#updating) ## `trunk` [#trunk] Update every time a commit is pushed to the [`trunk` branch](https://github.com/diced/zipline/tree/trunk). This is the **least stable** version of Zipline and is not recommended for production use, but is useful if a bug is fixed in the latest commit and you want to use it right away without waiting for the next release. Just pull the latest image: ```bash docker pull ghcr.io/diced/zipline:trunk ``` Then switch to the `trunk` tag in your docker-compose file: ```yaml image: ghcr.io/diced/zipline:trunk ``` First switch to the `trunk` branch: ```bash git checkout trunk ``` Then follow the [update steps](/docs/get-started/source#updating) ## Pinning to a specific version [#pinning-to-a-specific-version] If you want to pin to a specific version or commit for stability reasons, you can do so by using the specific tag for that version or any commit hash. For example, if you wanted to pin to version `v4.3.2`, you can use the `v4.3.2` tag: ```bash docker pull ghcr.io/diced/zipline:v4.3.2 ``` Or if you wanted to pin to a specific commit, you can use the commit hash as the tag: Note that it must be the short version of the commit hash, which is the first 7 characters of the full commit hash. You can find this in the commit history on GitHub. ```bash docker pull ghcr.io/diced/zipline:abc1234 ``` # Reverse Proxy (/docs/guides/reverse-proxy) ## NGINX [#nginx] ```nginx title="/etc/nginx/sites-available/zipline.conf" server { listen 80; # Allows Zipline to handle large file uploads, feel free to change this value client_max_body_size 100M; # If you have a domain, replace with it for DNS resolution server_name ; location / { # If Zipline is running on a different port or hostname, change the port here proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` ### NGINX Proxy Manager [#nginx-proxy-manager] You may be using NGINX Proxy Manager, a web interface for managing NGINX reverse proxies. You can follow the guide below to set up Zipline with NGINX Proxy Manager. First navigate to your NGINX Proxy Manager dashboard, and click on the "Proxy Hosts" tab. Then click "Add Proxy Host". add proxy host You may change the hostname and port to match your Zipline instance. If you are using a domain, you can enter it in the "Domain Names" field. In this example it is set to `zipline.example.com`. Setting up SSL through NGINX Proxy Manager is also very simple, you can use the "SSL" tab to generate a certificate for your domain. npm ssl ### NGINX with SSL [#nginx-with-ssl] You will need to have a valid SSL certificate to use this configuration. If you don't have one, you can use [Let's Encrypt](https://letsencrypt.org/), or Cloudflare for example. ```nginx title="/etc/nginx/sites-available/zipline-ssl.conf" server { listen 443 ssl; client_max_body_size 100M; server_name ; # these paths can be anywhere, depending on where your keys are stored ssl_certificate /.pem; ssl_certificate_key /.key; location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } ``` #### Generating Cloudflare Certificates [#generating-cloudflare-certificates] If you are using cloudflare, you can generate a certificate within the dashboard. First visit [dash.cloudflare.com](https://dash.cloudflare.com) and select your domain. Then navigate to the SSL/TLS tab, and click "Client Certificates". Then click "Create Certificate". cf client certs Next, make sure the options are like the following: cf client certs 2 Finally, click "Next" and you will be presented with the public and private keys. Make sure the key format is set to PEM. cf client certs 3 You will have to save the **Certificate** to `.pem` and the **Private Key** to `.key`. For example if your domain is `zipline.example.com`, you will have the following files: ``` /zipline.example.com.pem /zipline.example.com.key ``` You can save these keys anywhere, but make sure your reverse proxy configuration points to the correct path. Then within NGINX, you can use the following configuration: ```nginx title="/etc/nginx/sites-available/zipline-ssl.conf" server { ... ssl_certificate /.pem; ssl_certificate_key /.key; ... } ``` #### Generating Certificates with Tailscale [#generating-certificates-with-tailscale] If you are using Tailscale, and want to create a certificate pair for your domain (e.g. `hostname.tails-scales.ts.net`) to use with NGINX, you can use the following commands: ```bash tailscale cert ``` This will output a `.key` and `.crt` file. You can then use these files in your NGINX configuration. Additionally, visit [this page](https://tailscale.com/kb/1080/cli#cert) for more information on how to use the certificates. ## Caddy [#caddy] Setting up Zipline with Caddy is very simple. You can use the following Caddyfile configuration: ```caddyfile title="Caddyfile" { reverse_proxy localhost:3000 } ``` ## After setting up [#after-setting-up] After you have set up your reverse proxy, there are a couple of ssettings you should set to make sure everything works correctly: * `CORE_TRUST_PROXY`: This setting tells Zipline to trust the `X-Forwarded-*` headers set by your reverse proxy. This is important for getting the correct client IP and protocol (HTTP or HTTPS) in your application. * `CORE_RETURN_HTTPS_URLS`: This setting tells Zipline to return `https://` URLs for file links and other URLs when the request comes in over HTTPS. This is important for ensuring that your website is fully secure and doesn't have mixed content issues. * If SSL terminates at your proxy, you should turn this on so that file links and other URLs are returned as `https://`. * This setting controls whether or not Zipline returns `https://` urls anywhere in the app. # Server Actions (/docs/guides/server-actions) Administrator tools live under **Administrator** in the sidebar. ## Administrator dashboard [#administrator-dashboard] The **Dashboard** page at `/dashboard/admin` is the admin home. It shows storage usage, version information, and quick links to metrics, users, settings, and other admin pages. ### Storage status [#storage-status] The admin dashboard shows how much storage your instance is using. For local storage, this includes total disk space and a usage percentage. For S3, it shows the bucket path and total bytes stored (S3 does not report total capacity). ## Server Actions [#server-actions] Maintenance actions are under **Server Actions** at `/dashboard/admin/actions`. Server Actions Server Actions Buttons ## Import/Export Data [#importexport-data] This will allow you to import data (from V3 or V4 exports) or export your current data (that can be imported here later.) **Import/Export is only available to super administrators.** For more information on how V3 imports work, visit [migrations](/docs/migrate). For more information on how V4 imports/exports work, visit [Import & Export](/docs/guides/backup/instance). The export file will return a JSON file that contains all your data, except for the actual files. This is to keep the file size down, and to allow you to handle files seperately. ## Clear Zero-Byte Files [#clear-zero-byte-files] This button does what it says, it will remove any files that have 0 bytes. After clicking the button, it will tell you how many files it will remove and ask you to confirm. ## Clear Temp Files [#clear-temp-files] This button will clear any files within the temporary directory configured. ## Requery Size of Files [#requery-size-of-files] This will requery the size of all files in the database. This is useful if you have moved files around, or they no longer exist and you want to clean up your instance. Requery Size of Files ### Force Update [#force-update] This will update every file regardless of the size has changed or not. This may take a while depending on the number of files you have. ### Force Delete [#force-delete] This will delete files that are in the database but no longer exist on the server. It will also clear files that are in the database but tagged with 0 bytes. ## Generate Thumbnails [#generate-thumbnails] Usually, thumbnails are generated on a scheduled interval, but this button will allow you to generate thumbnails for video files that don't have them yet. Generate Thumbnails ### Re-run [#re-run] This will generate thumbnails for all video files, even if they have been generated before. # SSL (/docs/guides/ssl) Zipline will no longer support SSL natively through built-in options in the future (versions after 4.4.1). It is recommended to use a reverse proxy like NGINX or Caddy to handle SSL termination instead. ## Using a Reverse Proxy instead [#using-a-reverse-proxy-instead] Instead of using Zipline's native SSL support, you can use a reverse proxy like NGINX or Caddy to handle SSL termination. For more information, visit [Reverse Proxies](/docs/guides/reverse-proxy). # Themes (/docs/guides/themes) You can create custom themes for Zipline by adding JSON files within the `themes/` directory (relative to the root of Zipline). ## Structure [#structure] Zipline uses [Mantine](https://mantine.dev) for the UI components, so the `theme_name.theme.json` can have any properties that Mantine supports (see [this](https://mantine.dev/theming/theme-object/) for available properties and documentation). This is recommended for advanced users. There are a few requirements when creating custom themes: * The file name must end with `.theme.json`. * The file must be a valid JSON file. * The file must be placed in the `themes/` directory. However, for most users just changing the colors are the most common use case. Below is a example of a theme file. ```json title="blue_dark.theme.json" { "name": "Dark Blue", "colorScheme": "dark", "colors": { "blue": [ "#FFFFFF", "#7C7DC2", "#7778C0", "#6C6FBC", "#575DB5", "#4D54B2", "#424BAE", "#3742AA", "#323EA8", "#2C39A6" ], "dark": [ "#FFFFFF", "#293747", "#6C7A8D", "#2d3e5a", "#222c47", "#171F35", "#181c28", "#0c101c", "#060824", "#00001E" ] }, "primaryColor": "blue", "mainBackgroundColor": "color-mix(in srgb, var(--mantine-color-dark-9), black 45%)" } ``` ### Main Properties [#main-properties] | Property | Description | | --------------------- | ---------------------------------------------------------------------------------------------- | | `name` | The name of the theme, which will show up for users in the dashboard. | | `colors` | The colors of the theme. See [this](#colors) for more. | | `primaryColor` | The primary color of the theme. This is used for buttons and such. | | `colorScheme` | The color scheme of the theme. Can be `light` or `dark`. | | `mainBackgroundColor` | The main background color of the theme. This is used for the main background of the dashboard. | ### `colors` [#colors] Colors must be an array of 10 elements. The first element should be the lightest color, and the last element should be the darkest color. The colors are used for various parts of the dashboard. For example the for the example theme above, the blue colors look like these: The `dark` colors are used for the dark theme only. If you are making a light theme, the `dark` colors will not be used. ### `primaryColor` [#primarycolor] This is the primary color of the theme. This is used for buttons and such. The value should be the key of the color in the `colors` object. themes primaryColor outlined ### `mainBackgroundColor` [#mainbackgroundcolor] This is the background color behind the content. themes mainBackgroundColor outlined The default themes use a css function called `color-mix` to make the darkest color (9th index) 45% darker. For example the below: ```css color-mix(in srgb, var(--mantine-color-dark-9), black 45%) ``` makes the `dark[9]` color 45% darker. In theory any value can be used here, any valid CSS function or variables will work here. ### `extraCss` [#extracss] This property is optional and can be used to add extra CSS to the theme. #### Adding Custom Fonts [#adding-custom-fonts] This example uses Google Fonts to add the "Ubuntu" font. First you will need to select a theme in [Google Fonts](https://fonts.google.com/), then after clicking "Get Fonts", click the "@import" button and then copy the contents that are inside the `