{"id":20510451,"url":"https://github.com/projectstorm/tornado","last_synced_at":"2025-08-31T20:12:51.058Z","repository":{"id":176444859,"uuid":"651831676","full_name":"projectstorm/tornado","owner":"projectstorm","description":"Concept board and image reference software for ambitious creatives and artists","archived":false,"fork":false,"pushed_at":"2023-07-10T07:55:58.000Z","size":10134,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-13T22:39:30.434Z","etag":null,"topics":["art","artists","concepts","creative","docker","images","media","mysql","node","reference","self-hosted","spa","typescript"],"latest_commit_sha":null,"homepage":"https://dylanvorster.com","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/projectstorm.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-06-10T08:22:12.000Z","updated_at":"2024-09-27T20:29:23.000Z","dependencies_parsed_at":null,"dependency_job_id":"a3d3f99e-9985-4557-8f63-2985909f861b","html_url":"https://github.com/projectstorm/tornado","commit_stats":null,"previous_names":["projectstorm/tornado"],"tags_count":15,"template":false,"template_full_name":null,"purl":"pkg:github/projectstorm/tornado","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectstorm%2Ftornado","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectstorm%2Ftornado/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectstorm%2Ftornado/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectstorm%2Ftornado/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/projectstorm","download_url":"https://codeload.github.com/projectstorm/tornado/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/projectstorm%2Ftornado/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273032934,"owners_count":25034067,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-31T02:00:09.071Z","response_time":79,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["art","artists","concepts","creative","docker","images","media","mysql","node","reference","self-hosted","spa","typescript"],"created_at":"2024-11-15T20:29:35.192Z","updated_at":"2025-08-31T20:12:51.038Z","avatar_url":"https://github.com/projectstorm.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Tornado\n\n[![Build](https://github.com/projectstorm/tornado/actions/workflows/test.yml/badge.svg)](https://github.com/projectstorm/tornado/actions/workflows/test.yml)\n[![Docker](https://img.shields.io/docker/pulls/projectstorm/tornado.svg)](https://hub.docker.com/r/projectstorm/tornado)\n[![pnpm](https://img.shields.io/badge/maintained%20with-pnpm-f9ad00.svg)](https://pnpm.io/)\n\n\nConcept and image reference board software for ambitious creatives 🎨\n\n(Inspired by the awesome projects https://www.pureref.com/ and https://vizref.com/)\n\n![](./images/example1.png)\n![](./images/example2.png)\n\n## What\n\nTornado is self-hosted software for the web (currently in development) that provides digital media artists with the ability to create concept and reference boards.\nYou can simply paste images you have copied in your clipboard, and then arrange them as you see fit.\n\n\n## Features:\n\n* SPA (Single Page App)\n* Multiple Users\n* Light and dark mode\n* Email + password authentication\n* Image resizing on the server (4 sizes based on the image zoom)\n* Uses window DPI to determine which image variant to serve\n* Canvas zoom and translate\n* Image paste from clipboard and translate\n* Name and rename boards\n* Crop images + the ability to re-crop the original image at any stage\n* Double click to focus images in the center of the screen\n* Fullscreen toggle\n* Resize images on any corner\n* Initial widths are computed based on the average sizes of the other images\n* Lock mode to prevent editing, auto unlock on image paste\n\n![](./images/screenshot1.png)\n![](./images/screenshot2.png)\n![](./images/screenshot3.png)\n![](./images/screenshot4.png)\n\n\n## Requirements\n\n* MySQL / MariaDB database\n* Node 18+\n\n## Development environment\n\n1. copy `.env.template` -\u003e `.env` and replace the variables with your own.\n2. Run ```pnpm build``` to run the initial typescript build.\n3. Run ```pnpm watch``` to start everything in watch mode, including the server and backing docker-compose services.\n\n## Configuration\n\nTornado is configured using environment variables.\n\n| name                | default | desc                                                                                           |\n|---------------------|---------|------------------------------------------------------------------------------------------------|\n| SITE_URL            |         | example: https://tornado.example.com                                                           |\n| PORT                | 8080    | Port to start the server on                                                                    |\n| DATABASE_URL        |         | mysql://{user}:{pass}@{host}:{port}/{db}                                                       |\n| ADMIN_USER_EMAIL    |         | Email address of the admin user (for login)                                                    |\n| ADMIN_USER_PASS     |         | Password of the admin user (this can be removed after the admin user is created on first boot) |\n| CONTENT_DIRECTORY   |         | The main directory where all content is uploaded, Tornado will created the child directories   |\n| UPLOAD_LIMIT        | 10MB    | Express upload limit (mostly relevant to image uploads)                                        |\n\n## Running with docker\n\nQuick start using docker-compose:\n\n```yaml\nversion: \"3\"\nservices:\n  tornado:\n    image: projectstorm/tornado:latest\n    ports:\n      - \"80:8080\"\n    environment:\n      DATABASE_URL: mysql://root:tornado@tornado_database:3306/mytornado\n      SITE_URL: http://localhost:80\n  tornado_database:\n    image: mysql\n    environment:\n      MYSQL_ROOT_PASSWORD: tornado\n      MYSQL_DATABASE: mytornado\n```\n\nFor HTTPS, consider using `nginx` with `certbot` and reverse proxy to the http port on your docker network, for example:\n\n```nginx\nserver {\n  listen 443 ssl;\n  server_name tornado.mydomain.net;\n  client_max_body_size 100M;\n\n  ssl_certificate /etc/letsencrypt/live/mydomain.net/fullchain.pem;\n  ssl_certificate_key /etc/letsencrypt/live/mydomain.net/privkey.pem;\n\n  location / {\n      proxy_redirect                    off;\n      proxy_http_version                1.1;\n      proxy_set_header Upgrade          $http_upgrade;\n      proxy_set_header Connection       \"upgrade\";\n      proxy_set_header Host             $host;\n      proxy_set_header X-Real-IP        $remote_addr;\n      proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;\n\n      proxy_pass http://tornado:8080;\n  }\n}\n```\n\nand something like this:\n\n```yaml\n...\n  nginx:\n    image: nginx\n    volumes:\n     - ./templates:/etc/nginx/templates\n     - ./data/certbot/conf:/etc/letsencrypt\n     - ./data/certbot/www:/var/www/certbot\n     - ./99-autoreload.sh:/docker-entrypoint.d/99-autoreload.sh\n    ports:\n     - \"80:80\"\n     - \"443:443\"\n  certbot:\n    restart: always\n    image: certbot/certbot\n    entrypoint: \"/bin/sh -c 'trap exit TERM; while :; do certbot renew; sleep 12h \u0026 wait $${!}; done;'\"\n    volumes:\n      - ./data/certbot/conf:/etc/letsencrypt\n      - ./data/certbot/www:/var/www/certbot\n...\n```\n\n## Release\n\n```pnpm version major/minor/patch \u0026\u0026 git push --tags origin master``` this will rollup a changelog, create a release and publish to docker with the `latest` tag + semver.\n\n_Note: Pull requests must have the appropriate label (eg 'feature') to be included in the release notes automatically._\n\n## About\n\nProject Author: Dylan Vorster (dylanvorster.com)\n\nLinks to a few of the awesome artists featured in the example images:\n\n* https://twitter.com/KilluKaela\n* https://twitter.com/IndigoBeatss\n* https://twitter.com/the_aftrmrkt\n* https://twitter.com/Xezeno1\n* https://twitter.com/for_riner\n* https://twitter.com/fwflunky\n* https://twitter.com/Dev_Voxy\n* https://twitter.com/_rat_riot\n* https://twitter.com/Frayvuir\n* https://twitter.com/DJayjesse\n* https://twitter.com/bl_s21\n* https://twitter.com/yumesan_yume\n* https://twitter.com/MegamanUMX","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprojectstorm%2Ftornado","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fprojectstorm%2Ftornado","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fprojectstorm%2Ftornado/lists"}