# 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)
` (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.
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.
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**
### 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.
### 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
This will change the title that appears in the browser tab, as well as the title that appears in the navigation bar.
### 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
## 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.
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.
### 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 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.
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".
### 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]
## 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.
2. Click on the `Integrations` tab.
3. Click on the `Create Webhook` button.
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".
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.
## 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.
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.
Here is what it looks like on Discord:
### 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.
Here is what it looks like on Discord:
## 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)
## 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.
## 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.
## 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.
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".
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.
### 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".
Next, make sure the options are like the following:
Finally, click "Next" and you will be presented with the public and private keys. Make sure the key format is set to PEM.
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`.
## 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.
### 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.
### 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.
### `mainBackgroundColor` [#mainbackgroundcolor]
This is the background color behind the content.
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 `