From e897a6c5ebb23976f9d13f14846502906c92ec7e Mon Sep 17 00:00:00 2001 From: darko Date: Mon, 12 Oct 2026 12:35:47 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B0=D0=B7=D0=B2=D1=91=D1=80=D1=82?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=20Debian=20?= =?UTF-8?q?12/13:=20Docker,=20nginx,=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=20=D0=BE=D0=B1=D1=80=D0=B0=D1=82=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=20=D0=BF=D1=80=D0=BE=D0=BA=D1=81=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - приложение перенесено в public/, инструкции в docs/ - Dockerfile (nginx без root-прав), docker-compose.yml с ограничениями - deploy/split-trainer.conf: CSP и заголовки безопасности, no-cache по ETag, /healthz, реальный IP из X-Forwarded-For только от частных сетей, редиректы без внутреннего хоста - deploy/install-debian.sh: установка и обновление без Docker - docs/DEPLOY.md: требования, установка, примеры прокси (nginx, Caddy, Traefik), подпуть Co-Authored-By: Claude Sonnet 5.5 --- .dockerignore | 7 ++ .gitattributes | 7 ++ Dockerfile | 9 ++ README.md | 26 ++-- deploy/install-debian.sh | 64 ++++++++++ deploy/split-trainer.conf | 58 +++++++++ docker-compose.yml | 25 ++++ docs/DEPLOY.md | 185 ++++++++++++++++++++++++++++ ИНСТРУКЦИЯ.md => docs/ИНСТРУКЦИЯ.md | 6 +- app.js => public/app.js | 0 data.js => public/data.js | 0 index.html => public/index.html | 0 styles.css => public/styles.css | 0 vial.js => public/vial.js | 0 14 files changed, 376 insertions(+), 11 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitattributes create mode 100644 Dockerfile create mode 100755 deploy/install-debian.sh create mode 100644 deploy/split-trainer.conf create mode 100644 docker-compose.yml create mode 100644 docs/DEPLOY.md rename ИНСТРУКЦИЯ.md => docs/ИНСТРУКЦИЯ.md (92%) rename app.js => public/app.js (100%) rename data.js => public/data.js (100%) rename index.html => public/index.html (100%) rename styles.css => public/styles.css (100%) rename vial.js => public/vial.js (100%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..20517f0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.claude +.gitignore +.gitattributes +*.md +*.vil +docs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..893de3c --- /dev/null +++ b/.gitattributes @@ -0,0 +1,7 @@ +# Файлы для сервера обязаны быть с LF, даже если репозиторий клонируют на Windows +* text=auto +*.sh text eol=lf +*.conf text eol=lf +*.yml text eol=lf +Dockerfile text eol=lf +.dockerignore text eol=lf diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a50552e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +# Сплит-тренер: статический сайт на nginx без root-прав (слушает 8080). +FROM nginxinc/nginx-unprivileged:1.28-alpine + +COPY deploy/split-trainer.conf /etc/nginx/conf.d/default.conf +COPY public/ /usr/share/nginx/html/ + +EXPOSE 8080 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -q --spider http://127.0.0.1:8080/healthz || exit 1 diff --git a/README.md b/README.md index 237dce3..aaf3113 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,24 @@ Тренажёр слепой печати для сплит-клавиатур: русская и английская раскладки, цифры, знаки препинания, статистика, чтение раскладки Vial (.vil) и упражнения на переключение слоёв. -Без сборки и зависимостей: откройте `index.html` в браузере. +Без сборки и зависимостей: статический сайт. -Подробно: [ИНСТРУКЦИЯ.md](ИНСТРУКЦИЯ.md). +## Быстрый старт -## Файлы +- **Локально:** откройте `public/index.html` в браузере. +- **На сервере (Debian 12/13), в том числе за обратным прокси:** -- `index.html` - запуск -- `app.js` - логика приложения -- `vial.js` - разбор файла Vial и маршруты по слоям -- `data.js` - раскладки, слова и тексты упражнений -- `styles.css` - оформление + ```bash + docker compose up -d --build # или: sudo ./deploy/install-debian.sh + ``` + + Требования, настройка прокси, обновление: [docs/DEPLOY.md](docs/DEPLOY.md). + +Как пользоваться: [docs/ИНСТРУКЦИЯ.md](docs/ИНСТРУКЦИЯ.md). + +## Структура + +- `public/` - сайт: `index.html`, `app.js` (логика), `vial.js` (разбор Vial и маршруты по слоям), `data.js` (раскладки, слова, тексты), `styles.css` +- `deploy/` - конфиг nginx и скрипт установки для Debian +- `Dockerfile`, `docker-compose.yml` - запуск в контейнере +- `docs/` - инструкции diff --git a/deploy/install-debian.sh b/deploy/install-debian.sh new file mode 100755 index 0000000..915fb04 --- /dev/null +++ b/deploy/install-debian.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# Установка и обновление Сплит-тренера на Debian 12/13: nginx + статические файлы. +# +# sudo ./deploy/install-debian.sh # за обратным прокси: слушаем 127.0.0.1:8080 +# sudo LISTEN=80 DOMAIN=trainer.example.com ./deploy/install-debian.sh # без прокси, напрямую на порту 80 +# +# Повторный запуск обновляет файлы сайта и конфиг. Ничего, кроме этого сайта, скрипт не трогает. +set -euo pipefail + +LISTEN="${LISTEN:-127.0.0.1:8080}" # адрес:порт или просто порт +DOMAIN="${DOMAIN:-_}" # server_name; "_" = любое имя +WEBROOT="${WEBROOT:-/var/www/split-trainer}" +NAME="split-trainer" + +die() { echo "Ошибка: $*" >&2; exit 1; } + +[ "$(id -u)" -eq 0 ] || die "запустите от root: sudo $0" + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +SRC="$HERE/../public" +CONF_TEMPLATE="$HERE/$NAME.conf" +[ -f "$SRC/index.html" ] || die "не найден $SRC/index.html: запускайте скрипт из клонированного репозитория" +[ -f "$CONF_TEMPLATE" ] || die "не найден $CONF_TEMPLATE" + +# Значения подставляются в sed: не пускаем туда ничего, кроме безопасных символов +[[ "$LISTEN" =~ ^[][0-9A-Za-z.:_-]+$ ]] || die "некорректный LISTEN: $LISTEN" +[[ "$DOMAIN" =~ ^[0-9A-Za-z._*-]+( [0-9A-Za-z._*-]+)*$ ]] || die "некорректный DOMAIN: $DOMAIN" +[[ "$WEBROOT" =~ ^/[0-9A-Za-z._/-]+$ ]] || die "некорректный WEBROOT: $WEBROOT" + +. /etc/os-release +if [ "${ID:-}" != "debian" ] || { [ "${VERSION_ID:-}" != "12" ] && [ "${VERSION_ID:-}" != "13" ]; }; then + [ "${FORCE:-0}" = "1" ] || die "скрипт рассчитан на Debian 12/13 (у вас: ${PRETTY_NAME:-неизвестно}). Чтобы всё равно продолжить: FORCE=1" +fi + +echo "==> Устанавливаю nginx" +export DEBIAN_FRONTEND=noninteractive +apt-get update -qq +apt-get install -y -qq --no-install-recommends nginx >/dev/null + +echo "==> Копирую сайт в $WEBROOT" +install -d -m 755 "$WEBROOT" +find "$WEBROOT" -mindepth 1 -maxdepth 1 -type f -delete # убираем только файлы прошлой версии +install -m 644 "$SRC"/* "$WEBROOT"/ + +echo "==> Настраиваю nginx (listen $LISTEN, server_name $DOMAIN)" +CONF="/etc/nginx/sites-available/$NAME.conf" +sed -e "s|^\(\s*\)listen 8080;.*|\1listen $LISTEN;|" \ + -e "s|^\(\s*\)server_name _;.*|\1server_name $DOMAIN;|" \ + -e "s|^\(\s*\)root /usr/share/nginx/html;.*|\1root $WEBROOT;|" \ + "$CONF_TEMPLATE" > "$CONF" +ln -sf "$CONF" "/etc/nginx/sites-enabled/$NAME.conf" + +nginx -t +systemctl enable --now nginx >/dev/null 2>&1 +systemctl reload nginx + +# Проверяем, что сайт действительно отвечает +HOSTPORT="$LISTEN"; [[ "$HOSTPORT" =~ ^[0-9]+$ ]] && HOSTPORT="127.0.0.1:$HOSTPORT" +[ "${HOSTPORT%%:*}" = "0.0.0.0" ] && HOSTPORT="127.0.0.1:${HOSTPORT##*:}" +if command -v curl >/dev/null 2>&1 && ! curl -fsS -m 5 -o /dev/null "http://$HOSTPORT/healthz" -H "Host: ${DOMAIN%% *}"; then + echo "Предупреждение: проверка http://$HOSTPORT/healthz не прошла. Если порт 80 занят сайтом по умолчанию, см. docs/DEPLOY.md" >&2 +fi + +echo "==> Готово: http://$HOSTPORT/" diff --git a/deploy/split-trainer.conf b/deploy/split-trainer.conf new file mode 100644 index 0000000..28a37af --- /dev/null +++ b/deploy/split-trainer.conf @@ -0,0 +1,58 @@ +# Сплит-тренер: статический сайт. Один и тот же файл годится и для контейнера, и для nginx на хосте. +# Скрипт deploy/install-debian.sh заменяет ровно три строки с пометкой "# <-": listen, server_name и root. +# Совместимо с nginx 1.22 (Debian 12) и 1.26 (Debian 13). + +server { + listen 8080; # <- порт или адрес:порт + server_name _; # <- имя сайта + root /usr/share/nginx/html; # <- каталог с файлами + index index.html; + + server_tokens off; + + # За обратным прокси редиректы не должны подставлять внутренний хост и порт + absolute_redirect off; + port_in_redirect off; + + # Настоящий адрес клиента (для логов) берём из X-Forwarded-For, но только от прокси из частных сетей и localhost + set_real_ip_from 127.0.0.1; + set_real_ip_from ::1; + set_real_ip_from 10.0.0.0/8; + set_real_ip_from 172.16.0.0/12; + set_real_ip_from 192.168.0.0/16; + set_real_ip_from fc00::/7; + real_ip_header X-Forwarded-For; + real_ip_recursive on; + + gzip on; + gzip_vary on; + gzip_min_length 512; + gzip_types text/css application/javascript text/javascript text/plain application/json image/svg+xml; + + # Проверка живости для мониторинга и healthcheck контейнера + location = /healthz { + access_log off; + default_type text/plain; + return 200 "ok\n"; + } + + # Все заголовки безопасности заданы в одном location: add_header не наследуется между уровнями. + # no-cache не запрещает кеш, а заставляет браузер сверять версию (ETag) при каждом открытии: + # после обновления никто не застрянет на старом app.js. + location / { + try_files $uri $uri/ =404; + + add_header Cache-Control "no-cache" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer" always; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; + add_header Content-Security-Policy "default-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'self'" always; + # HSTS не ставим здесь: HTTPS завершает внешний прокси, он и должен отдавать Strict-Transport-Security. + } + + # Скрытые файлы (.git, .env и т. п.) не отдаём никогда + location ~ /\. { + return 404; + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..8afd0d9 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +# Запуск: docker compose up -d --build +# По умолчанию порт открыт только на localhost: сайт предназначен для работы за обратным прокси. +# BIND=0.0.0.0 откроет его наружу, PORT=9000 сменит порт (переменные можно положить в файл .env рядом). +services: + split-trainer: + build: . + image: split-trainer:latest + container_name: split-trainer + restart: unless-stopped + ports: + - "${BIND:-127.0.0.1}:${PORT:-8080}:8080" + read_only: true + tmpfs: + - /tmp + cap_drop: + - ALL + security_opt: + - no-new-privileges:true + mem_limit: 64m + pids_limit: 64 + logging: + driver: json-file + options: + max-size: "5m" + max-file: "3" diff --git a/docs/DEPLOY.md b/docs/DEPLOY.md new file mode 100644 index 0000000..ae255be --- /dev/null +++ b/docs/DEPLOY.md @@ -0,0 +1,185 @@ +# Развёртывание на своём сервере (Debian 12 / 13) + +Сплит-тренер состоит из статических файлов (HTML, CSS, JS) и ничего не хранит на сервере. Нет бэкенда, базы данных, куки и обращений к внешним сайтам. Сервер только отдаёт файлы, поэтому требования минимальные. + +## 1. Минимальные требования + +### Сервер + +| | Минимум | Комментарий | +|---|---|---| +| ОС | Debian 12 (bookworm) или Debian 13 (trixie), amd64 или arm64 | | +| CPU / RAM | 1 vCPU, 128 МБ свободной памяти | nginx в простое занимает 5–10 МБ; для контейнера задан лимит 64 МБ | +| Диск | порядка 500 МБ с Docker Engine (сам образ занимает несколько десятков МБ) или ~10 МБ с nginx из пакетов | сам сайт меньше 1 МБ | +| Сеть | один TCP-порт; по умолчанию `127.0.0.1:8080` | наружу выставляет обратный прокси | +| Исходящий доступ | только на время установки (apt или docker pull) | во время работы сайту интернет не нужен | +| ПО (вариант A) | Docker Engine 24+ с плагином Compose v2 | рекомендуется официальный репозиторий: docs.docker.com/engine/install/debian | +| ПО (вариант B) | nginx 1.22+ из пакетов Debian (в Debian 12 это 1.22, в 13 это 1.26) | ставится скриптом | +| Права | root или sudo на время установки | сайт работает без root-прав | + +Нужно выбрать один вариант: A или B. + +### Домен и HTTPS +- Для работы сайта HTTPS **не обязателен**: приложение не использует API, требующие защищённого контекста. +- Но на публичном адресе HTTPS нужен: его даёт ваш обратный прокси (раздел 4). + +### Браузеры пользователей +Современные: Chrome и Edge 111+, Firefox 113+, Safari 16.2+ (примерно 2023 год и новее). Для работы нужен включённый JavaScript и `localStorage`. + +## 2. Что важно знать про данные + +- Прогресс и статистика хранятся **в браузере пользователя** (`localStorage`), на сервер ничего не отправляется. Резервировать на сервере нечего. +- Данные привязаны к адресу сайта (схема + домен + порт). Если сменить домен, пользователи начнут «с нуля»; смена пути (`/trainer/`) прогресс не затрагивает. +- Файл раскладки Vial (.vil) читается локально в браузере и никуда не загружается. + +## 3. Установка + +Склонируйте репозиторий на сервер: + +```bash +git clone https://gittea.khad1997pav.ru/khadzhibekovpa/split-trainer.git +cd split-trainer +``` + +### Вариант A: Docker (рекомендуется) + +```bash +docker compose up -d --build +curl -i http://127.0.0.1:8080/healthz # должно ответить 200 и "ok" +``` + +- Порт по умолчанию открыт **только на localhost**. Изменить: файл `.env` рядом с `docker-compose.yml`, например `PORT=9000` и/или `BIND=0.0.0.0` (откроет наружу: тогда закройте порт файрволом для всех, кроме прокси). +- Контейнер работает без root-прав, с файловой системой «только чтение», без лишних возможностей ядра и с лимитом памяти. +- Если прокси тоже в Docker, уберите блок `ports:` и подключите сервис к общей сети прокси: он слушает `8080` внутри контейнера. + +### Вариант B: nginx из пакетов Debian + +```bash +sudo ./deploy/install-debian.sh +curl -i http://127.0.0.1:8080/healthz +``` + +Скрипт ставит nginx, копирует файлы в `/var/www/split-trainer` и подключает отдельный конфиг `split-trainer.conf`. Чужие сайты и настройки он не трогает. + +Параметры (переменные окружения): + +| Переменная | По умолчанию | Назначение | +|---|---|---| +| `LISTEN` | `127.0.0.1:8080` | адрес и порт (или только порт) | +| `DOMAIN` | `_` | `server_name`, можно несколько через пробел | +| `WEBROOT` | `/var/www/split-trainer` | куда копировать файлы | +| `FORCE` | `0` | `1`: разрешить запуск не на Debian 12/13 | + +Без прокси, прямо на порту 80: + +```bash +sudo LISTEN=80 DOMAIN=trainer.example.com ./deploy/install-debian.sh +``` + +Если порт 80 уже занят «сайтом по умолчанию» nginx и вы не задали `DOMAIN`, будет открываться страница «Welcome to nginx»: укажите домен или отключите сайт по умолчанию (`sudo rm /etc/nginx/sites-enabled/default && sudo systemctl reload nginx`). + +### Обновление + +```bash +git pull +docker compose up -d --build # вариант A +# или +sudo ./deploy/install-debian.sh # вариант B +``` + +Браузеры подхватят новую версию сами: сайт отдаётся с `Cache-Control: no-cache`, то есть при каждом открытии версия сверяется по ETag. + +## 4. За обратным прокси + +От прокси ничего особенного не требуется: сайт статический, без WebSocket, без нестандартных заголовков, все ссылки внутри относительные. Достаточно передать запрос на `127.0.0.1:8080`. + +### nginx + +```nginx +server { + listen 443 ssl; + server_name trainer.example.com; + + ssl_certificate /etc/letsencrypt/live/trainer.example.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/trainer.example.com/privkey.pem; + + # HTTPS завершается здесь, поэтому HSTS должен ставить именно прокси + add_header Strict-Transport-Security "max-age=31536000" always; + + location / { + proxy_pass http://127.0.0.1:8080; + 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; + } +} +``` + +### Caddy + +``` +trainer.example.com { + reverse_proxy 127.0.0.1:8080 +} +``` + +HTTPS-сертификат Caddy получит сам. + +### Traefik (Docker, метки в `docker-compose.override.yml`) + +```yaml +services: + split-trainer: + ports: !reset [] + networks: [traefik] + labels: + - traefik.enable=true + - traefik.http.routers.trainer.rule=Host(`trainer.example.com`) + - traefik.http.routers.trainer.tls.certresolver=letsencrypt + - traefik.http.services.trainer.loadbalancer.server.port=8080 +networks: + traefik: + external: true +``` + +Имена сети и резолвера замените на свои. Тег `!reset` требует Docker Compose 2.24 или новее. + +### Под подпутём (`https://example.com/trainer/`) + +Приложение можно отдавать из подкаталога: все пути в нём относительные, свой конфиг менять не нужно. Прокси должен убрать префикс (завершающий `/` в `proxy_pass` обязателен): + +```nginx +location = /trainer { return 301 /trainer/; } +location /trainer/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; } +``` + +Для Caddy: + +``` +example.com { + redir /trainer /trainer/ + handle_path /trainer/* { reverse_proxy 127.0.0.1:8080 } +} +``` + +### Настоящий IP клиента в логах + +Конфиг доверяет заголовку `X-Forwarded-For` только от прокси из частных сетей и localhost (`127.0.0.0/8`, `10/8`, `172.16/12`, `192.168/16`). Если ваш прокси стоит на другом сервере с публичным адресом, добавьте его в `set_real_ip_from` в `deploy/split-trainer.conf` и пересоберите или переустановите. + +## 5. Что отдаёт сервер + +- `GET /healthz` отвечает `200 ok` (для мониторинга и healthcheck). +- Заголовки безопасности: строгий `Content-Security-Policy` (только свои скрипты и стили, никаких внешних адресов), `X-Content-Type-Options`, `Referrer-Policy: no-referrer`, `X-Frame-Options`, `Permissions-Policy`. +- Файлы сжимаются gzip, скрытые файлы (`.git`, `.env`) не отдаются, версия nginx в ответах скрыта. + +## 6. Если что-то не работает + +| Симптом | Причина и решение | +|---|---| +| 502 / 504 от прокси | Сайт не запущен или слушает другой порт: `docker compose ps`, `curl http://127.0.0.1:8080/healthz` | +| Белая страница под `/trainer/` | Нет завершающего `/` в `proxy_pass` или нет редиректа `/trainer` на `/trainer/` (см. выше) | +| После обновления видна старая версия | Обновите страницу с Ctrl+F5; если стоит CDN или кеш на прокси, исключите сайт из кеширования | +| Прогресс пропал после смены адреса | `localStorage` привязан к адресу сайта (схема + домен + порт); вернуть старый адрес | +| `nginx -t` ругается (вариант B) | Покажите вывод `sudo nginx -t`; чаще всего порт занят другим сайтом | +| Нужно изменить текст упражнений | Правьте `public/data.js` и обновите сайт командой из раздела «Обновление» | diff --git a/ИНСТРУКЦИЯ.md b/docs/ИНСТРУКЦИЯ.md similarity index 92% rename from ИНСТРУКЦИЯ.md rename to docs/ИНСТРУКЦИЯ.md index e950821..345c062 100644 --- a/ИНСТРУКЦИЯ.md +++ b/docs/ИНСТРУКЦИЯ.md @@ -2,10 +2,10 @@ ## 1. Запуск -1. Откройте файл `index.html` двойным кликом (лучше в Chrome или Edge). +1. Откройте файл `public/index.html` двойным кликом (лучше в Chrome или Edge). Если приложение установлено на сервере, откройте его адрес в браузере. 2. Ничего устанавливать не нужно. Интернет не нужен. -Прогресс и статистика хранятся в самом браузере. Не переносите папку с приложением в другое место и не меняйте браузер, иначе прогресс «потеряется» (он привязан к браузеру и адресу файла). +Прогресс и статистика хранятся в самом браузере. Не переносите папку с приложением в другое место и не меняйте браузер или адрес сайта, иначе прогресс «потеряется» (он привязан к браузеру и адресу). ## 2. Главное правило: раскладка Windows @@ -110,4 +110,4 @@ ## 9. Файлы приложения -`index.html` (запуск), `app.js` (логика), `vial.js` (чтение Vial и маршруты по слоям), `data.js` (раскладки, слова и тексты упражнений), `styles.css` (оформление). Слова и предложения для упражнений можно править в `data.js`. +В папке `public/`: `index.html` (запуск), `app.js` (логика), `vial.js` (чтение Vial и маршруты по слоям), `data.js` (раскладки, слова и тексты упражнений), `styles.css` (оформление). Слова и предложения для упражнений можно править в `data.js`. Установка на сервер описана в `docs/DEPLOY.md`. diff --git a/app.js b/public/app.js similarity index 100% rename from app.js rename to public/app.js diff --git a/data.js b/public/data.js similarity index 100% rename from data.js rename to public/data.js diff --git a/index.html b/public/index.html similarity index 100% rename from index.html rename to public/index.html diff --git a/styles.css b/public/styles.css similarity index 100% rename from styles.css rename to public/styles.css diff --git a/vial.js b/public/vial.js similarity index 100% rename from vial.js rename to public/vial.js