[
  {
    "path": ".github/FUNDING.yml",
    "content": "---\n# These are supported funding model platforms\n\ngithub: [elliotmatson]\n"
  },
  {
    "path": ".github/workflows/docker.yml",
    "content": "---\n# This workflow uses actions that are not certified by GitHub.\n# They are provided by a third-party and are governed by\n# separate terms of service, privacy policy, and support\n# documentation.\n\n# GitHub recommends pinning actions to a commit SHA.\n# To get a newer version, you will need to update the SHA.\n# You can also reference a tag or branch,\n# but the action may change without warning.\n\nname: Docker Build, Push\n\non:\n  release:\n    types: [published, edited]\n  workflow_dispatch:\n\npermissions: read-all\n\nenv:\n  REGISTRY: ghcr.io\n  IMAGE_NAME: elliotmatson/pgadmin-config-creator\n  DOCKER_IMAGE: elliotmatson/pgadmin-config-creator\n\njobs:\n  build-and-push-image:\n    runs-on: ubuntu-latest\n    permissions:\n      security-events: write\n      contents: read\n      packages: write\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@v4\n      - name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@v4\n\n      - name: Log in to Docker Hub\n        uses: docker/login-action@v4\n        with:\n          username: ${{ secrets.DOCKER_USERNAME }}\n          password: ${{ secrets.DOCKER_PASSWORD }}\n\n      - name: Log in to the Container registry\n        uses: docker/login-action@v4\n        with:\n          registry: ${{ env.REGISTRY }}\n          username: ${{ github.actor }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Extract metadata (tags, labels) for Docker\n        id: meta\n        uses: docker/metadata-action@v6\n        with:\n          images: |\n            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\n            ${{ env.DOCKER_IMAGE }}\n          tags: |\n            type=ref,event=branch\n            type=ref,event=pr\n            type=semver,pattern={{version}}\n            type=semver,pattern={{major}}.{{minor}}\n\n      - name: Build and push Docker image\n        uses: docker/build-push-action@v7\n        with:\n          context: ./pgadmin-config-creator\n          platforms: linux/amd64,linux/arm64,linux/arm/v7\n          push: true\n          tags: ${{ steps.meta.outputs.tags }}\n          labels: ${{ steps.meta.outputs.labels }}\n"
  },
  {
    "path": ".github/workflows/lint.yml",
    "content": "---\nname: Lint\non:\n  push:\n  pull_request:\n\npermissions: {}\n\njobs:\n  run-lint:\n    name: Lint\n    runs-on: ubuntu-latest\n\n    permissions:\n      # to fix any linting issues\n      contents: write\n      packages: read\n      # To report GitHub Actions status checks\n      statuses: write\n\n    steps:\n      - name: Check out the codebase.\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n        with:\n          # Full git history is needed to get a proper list of changed files\n          fetch-depth: 0\n\n      - name: Lint Code Base\n        uses: github/super-linter/slim@v7\n        env:\n          VALIDATE_RENOVATE: false\n          FIX_YAML_PRETTIER: true\n          FIX_MARKDOWN_PRETTIER: true\n          FIX_MARKDOWN: true\n          FIX_JSON_PRETTIER: true\n          FIX_JSON: true\n          DEFAULT_BRANCH: main\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".github/workflows/stack-healthcheck.yml",
    "content": "---\nname: Stack Healthcheck\non:\n  push:\n  pull_request:\n  workflow_dispatch:\n\npermissions: read-all\n\njobs:\n  test:\n    name: Test Stack\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6\n      - name: Deploy the stack, timeout if not healthy after 2m\n        run: timeout 120 docker compose up --quiet-pull --wait\n      - name: Check creating database\n        run: docker compose logs | grep \"CREATE DATABASE\"\n      - name: Check database creation\n        run: docker compose logs | grep \"Success. You can now start the database server\"\n      - name: Check postgres init\n        run: docker compose logs | grep \"PostgreSQL init process complete; ready for start up.\"\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "---\nname: \"Close stale issues and PRs\"\non:\n  schedule:\n    - cron: \"0 */1 * * *\"\n  workflow_dispatch:\n\npermissions:\n  issues: write\n  pull-requests: write\n\njobs:\n  stale:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/stale@v10\n        with:\n          stale-issue-message: \"This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.\"\n          days-before-stale: 30\n          days-before-close: 7\n          exempt-issue-labels: no-stale\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright (c) 2022 Elliot Matson\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Davinci Resolve Project Server\n\n![Lint](https://github.com/elliotmatson/Docker-Davinci-Resolve-Project-Server/actions/workflows/lint.yml/badge.svg)\n![Healthcheck](https://github.com/elliotmatson/Docker-Davinci-Resolve-Project-Server/actions/workflows/stack-healthcheck.yml/badge.svg)\n![Docker Build](https://github.com/elliotmatson/Docker-Davinci-Resolve-Project-Server/actions/workflows/docker.yml/badge.svg)\n\nSimple Resolve project server with automatic backups\n\n## Table of Contents\n\n- [Davinci Resolve Project Server](#davinci-resolve-project-server)\n  - [Table of Contents](#table-of-contents)\n  - [Introduction](#introduction)\n    - [Features](#features)\n  - [Configuration](#configuration)\n    - [Database](#database)\n    - [Backups](#backups)\n    - [PGAdmin](#pgadmin)\n    - [Volume Locations](#volume-locations)\n  - [Installation](#installation)\n    - [QNAP Installation](#qnap-installation)\n    - [Synology](#synology)\n    - [Linux](#linux)\n  - [Different PostgreSQL versions](#different-postgresql-versions)\n    - [Setting up a PostgreSQL 9.5 or 11 Project Server](#setting-up-a-postgresql-95-or-11-project-server)\n  - [Thanks](#thanks)\n\n## Introduction\n\nThere are a lot of ways to host a Resolve project server, but each of them has their own set of issues. The official project server requires manual backups, and other options can be complicated for those that don't have access to an IT team. Hopefully this is a more reliable and simpler solution for smaller teams!\n\n### Features\n\n- **Lightweight** - Docker based, so doesn't require a full macOS or Windows machine or VM.\n- **Platform Independent** - can be installed on Windows, Mac, Linux, QNAP, Synology, RPi, really anything that can run Docker.\n- **Compatible with Resolve's existing backup/restore functions** - All backup files use the standard Resolve \\*.backup file syntax, and can be restored from the Resolve UI\n- **Built-in PGAdmin Server** - PGAdmin is a tool for administering a PostgreSQL Server, and is helpful for diagnosing problems and migrating/updating entire servers\n\n## Configuration\n\nThere are a few things we'll need to edit at the top of the docker-compose.yml file to configure our installation:\n\n```yaml\n---\nversion: \"3.8\"\nx-common:\n  database: &db-environment\n    POSTGRES_DB: database\n    POSTGRES_USER: &pg-user postgres\n    POSTGRES_PASSWORD: DaVinci\n    TZ: America/Chicago\n    POSTGRES_LOCATION: &db-location \"???:/var/lib/postgresql/data\"\n  backup: &backup-environment\n    SCHEDULE: \"@daily\"\n    BACKUP_KEEP_DAYS: 7\n    BACKUP_KEEP_WEEKS: 4\n    BACKUP_KEEP_MONTHS: 6\n    BACKUP_LOCATION: &bk-location \"???:/backups\"\n  admin: &admin-environment\n    PGADMIN_DEFAULT_EMAIL: admin@admin.com\n    PGADMIN_DEFAULT_PASSWORD: root\n    PGADMIN_PORT: &pgadmin-port \"3001:80\"\n```\n\n### Database\n\nTo configure the server itself, we'll want to configure the environment variables below:\n| Environment Variable |Meaning|\n|---|---|\n| POSTGRES_DB | Name of your database. Name it whatever you like. |\n| POSTGRES_USER | Username you will use to connect to your database. The Resolve default is \"postgres\" |\n| POSTGRES_PASSWORD | Password you will use to connect to your database. The Resolve default is \"DaVinci\" |\n| TZ | Your timezone, here is [a list](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)|\n| POSTGRES_LOCATION | Location your database will be stored. See [Volume Locations](#volume-locations) |\n\n### Backups\n\nTo configure the backups, we'll want to configure the variables below:\n| Environment Variable |Meaning|\n|---|---|\n| SCHEDULE | This is a [cron string](https://www.freeformatter.com/cron-expression-generator-quartz.html) for how often backups are created. can be \"@daily\", \"@every 1h\", etc |\n| BACKUP_KEEP_DAYS | Number of daily backups to keep before removal. |\n| BACKUP_KEEP_WEEKS | Number of weekly backups to keep before removal. |\n| BACKUP_KEEP_MONTHS | Number of monthly backups to keep before removal. |\n| BACKUP_LOCATION | Location your backups will be stored. See [Volume Locations](#volume-locations) |\n\n### PGAdmin\n\nTo configure PGAdmin, we'll want to configure the variables below:\n| Environment Variable |Meaning|\n|---|---|\n| PGADMIN_DEFAULT_EMAIL | Email used for PGAdmin login. Default is \"admin@admin.com\" |\n| PGADMIN_DEFAULT_PASSWORD | Password used for PGAdmin login. Default is \"root\" |\n| PGADMIN_PORT | String configuring port to expose PGAdmin on. Syntax is \"YOUR_PORT:80\" |\n\n### Volume Locations\n\nThe location of your database and backups depend on what platform you are installing on. You will need the full path to the folder you want them stored in. On a QNAP NAS for example, if I wanted to use a folder called \"Backups\" inside a shared folder named \"Videos\" for my backups location, the path would be `/shares/Videos/Backups/`, and my `BACKUP_LOCATION` value would look like this:\n\n```yaml\nBACKUP_LOCATION: &bk-location \"/shares/Videos/Backups/:/backups\"\n```\n\nOn Ubuntu, if I wanted to use a folder named \"database\" in the home directory of the user named \"johndoe\" for my database location, the path would be `/home/johndoe/database/`, and my `POSTGRES_LOCATION` value would look like this:\n\n```yaml\nPOSTGRES_LOCATION: &db-location \"johndoe/database/:/var/lib/postgresql/data\"\n```\n\nI recommend putting your database on an SSD, your access speed will be noticeably slower on a spinning drive.\n\nOnce you have configured these settings, save your modified docker-compose.yml file and move on to installation!\n\n## Installation\n\n### QNAP Installation\n\nInstalling on a QNAP NAS is relatively simple. One note, please put the database files on an SSD. You will thank me later\n\n1. If you don't already have it, install Container Station from the QNAP app store.\n2. In Container Station, click \"Create\", then click \"Create Application\"\n3. Name your application whatever you like (eg. ResolveServer)\n4. Copy/Paste your modified docker-compose.yml file, hit \"Validate YAML\" to test it, and if it passes, click \"Create\"\n5. Container Station will download the files it needs and start the app. Once it's done, you should be able to connect Resolve to the IP address of your QNAP using the database name and credentials\n\n### Synology\n\nSee [this discussion](https://github.com/elliotmatson/Docker-Davinci-Resolve-Project-Server/discussions/15#discussioncomment-4615278)\n\n### Linux\n\n1. Follow the [Docker installation instructions for your Linux distribution](https://docs.docker.com/engine/install/)\n2. Install [Docker Compose](https://docs.docker.com/compose/install/)\n3. Move your modified docker-compose.yml file to a folder on your Linux machine, then navigate to that folder in the terminal.\n4. Run:\n   `docker compose up -d`\n5. Docker-compose will download the files it needs and start the app. Once it's done, you should be able to connect Resolve to the IP address of your Linux Server instance using the database name and credentials\n\n## Different PostgreSQL versions\n\nGenerally, Resolve is not very tolerant of mismatched PostgreSQL versions. Resolve 18-20 use PostgreSQL 13, which is what this repository now defaults to. Resolve 17 and below use PostgreSQL 9.5. Unfortunately the major release 9.5 is EOL, and 9.5.4 in particular has a lot of vulnerabilities that make it insecure.\nSince most people are still using the default Resolve credentials for their server, security generally isn't the biggest concern, but if you are trying to secure your project server with an older version of Resolve, you will want to move to a supported version of PostgreSQL.\n\nResolve 17 and below still use a legacy feature that has been removed in PostgreSQL 12, so the latest major version that is useable is 11, which will be maintained until November 9, 2023.\n\n### Setting up a PostgreSQL 9.5 or 11 Project Server\n\nTo setup a PostgreSQL 9.5 or 11 server instead of 13, there are 2 lines that need to be changed in docker_compose.yml:\n\n```yaml\nservices:\n  postgres:\n    image: postgres:13\n    ...\n  pgbackups:\n    image: prodrigestivill/postgres-backup-local:13\n    ...\n...\n```\n\nto the following:\n\n```yaml\nservices:\n  postgres:\n    image: postgres:9.5\n    ...\n  pgbackups:\n    image: prodrigestivill/postgres-backup-local:9.5\n    ...\n...\n```\n\n## Thanks\n\n-[prodrigestivill](https://github.com/prodrigestivill/) for his [PostgreSQL Backup docker image](https://github.com/prodrigestivill/docker-postgres-backup-local)\n"
  },
  {
    "path": "docker-compose.yml",
    "content": "---\nversion: \"3.8\"\nx-common:\n  database: &db-environment\n    POSTGRES_DB: &pg-db database\n    POSTGRES_USER: &pg-user postgres\n    POSTGRES_PASSWORD: DaVinci\n    TZ: America/Chicago\n    POSTGRES_LOCATION: &db-location \"./db:/var/lib/postgresql/data\"\n  backup: &backup-environment\n    SCHEDULE: \"@daily\"\n    BACKUP_KEEP_DAYS: 7\n    BACKUP_KEEP_WEEKS: 4\n    BACKUP_KEEP_MONTHS: 6\n    BACKUP_LOCATION: &bk-location \"./backups:/backups\"\n  admin: &admin-environment\n    PGADMIN_DEFAULT_EMAIL: admin@admin.com\n    PGADMIN_DEFAULT_PASSWORD: root\n    PGADMIN_PORT: &pgadmin-port \"3001:80\"\n#\n# ------------------------------------------------------------------------------------------\n# DANGER ZONE BELOW\n#\n# The remainder of this file likely does not need to be changed.\n# Please only make modifications\n# below if you understand what you are doing.\n#\nservices:\n  postgres:\n    container_name: resolve_pgsql\n    image: postgres:13\n    restart: always\n    ports:\n      - \"5432:5432\"\n    environment:\n      <<: [*db-environment]\n    volumes:\n      - *db-location\n    healthcheck:\n      test: [\"CMD\", \"pg_isready\", \"-U\", *pg-user, \"-d\", *pg-db]\n      interval: 10s\n      timeout: 5s\n      retries: 5\n  pgbackups:\n    container_name: resolve_pgbackup\n    image: prodrigestivill/postgres-backup-local:13\n    restart: always\n    volumes:\n      - *bk-location\n    depends_on:\n      - postgres\n    environment:\n      <<: [*db-environment, *backup-environment]\n      POSTGRES_HOST: postgres\n      POSTGRES_EXTRA_OPTS: --blobs --format=custom --quote-all-identifiers\n      BACKUP_SUFFIX: .backup\n      HEALTHCHECK_PORT: 8080\n    healthcheck:\n      interval: 30s\n  pgadmin:\n    container_name: resolve_pgadmin\n    image: dpage/pgadmin4\n    restart: always\n    environment:\n      <<: [*admin-environment]\n      PGADMIN_SERVER_JSON_FILE: /pgadmin4-config/servers.json\n    ports:\n      - *pgadmin-port\n    volumes:\n      - pgadmin-config:/pgadmin4-config\n      - pgadmin:/var/lib/pgadmin\n      - *bk-location\n    entrypoint: \"/bin/sh\"\n    command:\n      - -c\n      - |\n        mkdir -p /var/lib/pgadmin/storage/$${PGADMIN_DEFAULT_EMAIL//@/_}/\n        ln -s /backups /var/lib/pgadmin/storage/$${PGADMIN_DEFAULT_EMAIL//@/_}/\n        /entrypoint.sh\n    depends_on:\n      - postgres\n      - pgadmin-config\n    healthcheck:\n      test:\n        [\n          \"CMD-SHELL\",\n          \"wget --no-verbose --tries=1 --spider http://localhost/misc/ping || exit 1\",\n        ]\n  pgadmin-config:\n    container_name: resolve_pgadmin-config-creator\n    image: ghcr.io/elliotmatson/pgadmin-config-creator:latest\n    restart: on-failure\n    environment:\n      <<: [*db-environment]\n    volumes:\n      - pgadmin-config:/config\n\nvolumes:\n  pgadmin-config:\n  pgadmin:\n"
  },
  {
    "path": "pgadmin-config-creator/Dockerfile",
    "content": "FROM python:3.14-slim\n\nRUN useradd -m pgadmin-config-creator\nRUN mkdir /app && mkdir /config\nCOPY . /app\nRUN chown -R pgadmin-config-creator:pgadmin-config-creator /app /config\n\nUSER pgadmin-config-creator\n\nWORKDIR /app\nCMD [\"python\", \"app.py\"]\nHEALTHCHECK CMD test -f /config/servers.json || exit 1\n"
  },
  {
    "path": "pgadmin-config-creator/app.py",
    "content": "\"\"\"Creates a default pgAdmin config file\"\"\"\n\nimport json\nimport os\nimport time\n\n# quit if config file already exists\nif os.path.exists(\"/config/servers.json\"):\n    print(\"/config/servers.json already exists.\")\nelse:\n    # open json file in docker volume\n    with open(\"/config/servers.json\", \"w\", encoding=\"utf-8\") as f:\n        print(\"created \" + f.name)\n        # template json string\n        TEMPLATE_STRING = (\n            '{\"Servers\": {\"1\": {\"Name\": \"DavinciResolve\", '\n            '\"Group\": \"Servers\", \"Port\": 5432, \"Username\": '\n            '\"postgres\", \"Host\": \"postgres\", \"SSLMode\": \"prefer\", \"MaintenanceDB\": \"postgres\"}}}'\n        )\n        data = json.loads(TEMPLATE_STRING)\n        # fix username\n        data[\"Servers\"][\"1\"][\"Username\"] = os.getenv(\"POSTGRES_USER\")\n        print(\"Printing json to file...\")\n        print(data)\n        # update config file\n        json.dump(data, f, indent=2)\n\nprint(\"Done. Sleeping...\")\n\n# Sleep until GHA Healthchecks are complete.\n# I'm sure there's a better way to get this to not\n# stall a docker-compose up --wait, but I don't know\n# what it is\ntime.sleep(125)\n"
  },
  {
    "path": "renovate.json",
    "content": "{\n  \"$schema\": \"https://docs.renovatebot.com/renovate-schema.json\",\n  \"extends\": [\n    \"config:recommended\"\n  ],\n  \"lockFileMaintenance\": {\n    \"enabled\": true,\n    \"automerge\": true\n  },\n  \"packageRules\": [\n    {\n      \"matchDepTypes\": [\n        \"devDependencies\"\n      ],\n      \"automerge\": true,\n      \"matchPackageNames\": [\n        \"/lint/\",\n        \"/prettier/\"\n      ]\n    },\n    {\n      \"matchUpdateTypes\": [\n        \"minor\",\n        \"patch\",\n        \"pin\",\n        \"digest\"\n      ],\n      \"automerge\": true\n    }\n  ]\n}\n"
  }
]