diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..7615b3d --- /dev/null +++ b/.env.production.example @@ -0,0 +1,25 @@ +# Production environment variables for KSPGUTI Schedule + +# Proxy URL for schedule parsing (optional, defaults to https://lk.ks.psuti.ru) +PROXY_URL=https://lk.ks.psuti.ru + +# Telegram Bot API token for parsing failure notifications (optional) +# Get token from @BotFather on Telegram +PARSING_FAILURE_NOTIFICATIONS_TELEGRAM_BOTAPI_TOKEN= + +# Telegram Chat ID for receiving notifications (optional) +# You can get your chat ID by messaging @userinfobot on Telegram +PARSING_FAILURE_NOTIFICATIONS_TELEGRAM_CHAT_ID= + +# Node.js environment +NODE_ENV=production + +# Disable Next.js telemetry +NEXT_TELEMETRY_DISABLED=1 + +# Server port (default: 3000) +PORT=3000 + +# Server hostname (default: 0.0.0.0) +HOSTNAME=0.0.0.0 + diff --git a/Dockerfile b/Dockerfile index f838eb8..1e9e78d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,7 +11,7 @@ WORKDIR /app COPY package.json package-lock.json* pnpm-lock.yaml* ./ RUN \ if [ -f package-lock.json ]; then \ - npm ci; \ + npm ci --legacy-peer-deps; \ elif [ -f pnpm-lock.yaml ]; then \ corepack enable pnpm && corepack prepare pnpm@latest --activate && pnpm install --frozen-lockfile; \ else \ diff --git a/README.md b/README.md index e5c3620..5d635bb 100644 --- a/README.md +++ b/README.md @@ -87,6 +87,98 @@ The Dockerfile uses Next.js standalone output for optimized production builds. T - Health checks - Production optimizations +#### System installation (Linux systemd) + +Install the application directly on a Linux system as a systemd service: + +**Prerequisites:** +- Linux system with systemd +- Node.js 20+ installed +- Root/sudo access + +**Installation:** + +```bash +# Clone the repository +git clone +cd kspguti-schedule + +# Run the installation script +sudo ./scripts/install.sh +``` + +The installation script will: +- Check Node.js and npm versions +- Copy files to `/opt/kspguti-schedule` +- Install dependencies +- Build the production version +- Install and enable systemd service + +**Configuration:** + +1. Edit environment variables: +```bash +sudo nano /opt/kspguti-schedule/.env.production +``` + +2. Update systemd service if needed: +```bash +sudo nano /etc/systemd/system/kspguti-schedule.service +``` + +**Managing the service:** + +Use the management script for easy service control: + +```bash +# Start the service +sudo ./scripts/manage.sh start + +# Stop the service +sudo ./scripts/manage.sh stop + +# Restart the service +sudo ./scripts/manage.sh restart + +# Check status +./scripts/manage.sh status + +# View logs +./scripts/manage.sh logs +./scripts/manage.sh logs -f # Follow logs + +# Update application +sudo ./scripts/manage.sh update + +# Enable/disable autostart +sudo ./scripts/manage.sh enable +sudo ./scripts/manage.sh disable +``` + +Or use systemctl directly: + +```bash +sudo systemctl start kspguti-schedule +sudo systemctl stop kspguti-schedule +sudo systemctl restart kspguti-schedule +sudo systemctl status kspguti-schedule +sudo journalctl -u kspguti-schedule -f +``` + +**Service configuration:** + +- Installation directory: `/opt/kspguti-schedule` +- Service user: `www-data` +- Port: `3000` (configurable via environment variables) +- Logs: `journalctl -u kspguti-schedule` + +**Environment variables:** + +See `.env.production.example` for available options: +- `PROXY_URL` - URL for schedule parsing (optional) +- `PARSING_FAILURE_NOTIFICATIONS_TELEGRAM_BOTAPI_TOKEN` - Telegram bot token (optional) +- `PARSING_FAILURE_NOTIFICATIONS_TELEGRAM_CHAT_ID` - Telegram chat ID (optional) + #### Other platforms The project can be deployed to any platform supporting Node.js 20+: diff --git a/next.config.js b/next.config.js index 2045259..67165c1 100644 --- a/next.config.js +++ b/next.config.js @@ -2,11 +2,6 @@ const nextConfig = { reactStrictMode: true, output: 'standalone', - redirects: async () => [{ - permanent: false, - destination: '/ps7', - source: '/' - }], generateEtags: false } diff --git a/package-lock.json b/package-lock.json index c3da5a0..580611d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,14 +21,14 @@ "content-type": "^1.0.5", "date-fns": "^2.30.0", "jsdom": "^22.1.0", - "lucide-react": "^0.279.0", - "next": "latest", + "lucide-react": "^0.554.0", + "next": "16.0.3", "next-sitemap": "^4.2.3", "next-themes": "^0.2.1", "node-html-parser": "^6.1.10", "node-telegram-bot-api": "^0.63.0", - "react": "latest", - "react-dom": "latest", + "react": "19.2.0", + "react-dom": "19.2.0", "react-icons": "^4.11.0", "sass": "^1.69.3", "sharp": "^0.32.6", @@ -38,17 +38,21 @@ }, "devDependencies": { "@types/jsdom": "^21.1.3", - "@types/node": "latest", + "@types/node": "22.0.0", "@types/node-telegram-bot-api": "^0.61.8", - "@types/react": "latest", - "@types/react-dom": "latest", + "@types/react": "19.2.0", + "@types/react-dom": "19.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", - "autoprefixer": "latest", - "eslint": "latest", - "eslint-config-next": "latest", - "postcss": "latest", + "autoprefixer": "10.4.20", + "eslint": "8.57.0", + "eslint-config-next": "16.0.3", + "postcss": "8.4.47", "tailwindcss": "^3.4.18", - "typescript": "latest" + "typescript": "5.9.3" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=10.0.0" } }, "node_modules/@alloc/quick-lru": { @@ -401,82 +405,17 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", - "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", + "espree": "^9.6.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -484,7 +423,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -515,40 +454,13 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", + "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@floating-ui/core": { @@ -589,28 +501,44 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.14", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", + "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" + "@humanwhocodes/object-schema": "^2.0.2", + "debug": "^4.3.1", + "minimatch": "^3.0.5" }, "engines": { - "node": ">=18.18.0" + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, "node_modules/@humanwhocodes/module-importer": { @@ -627,19 +555,13 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "license": "BSD-3-Clause" }, "node_modules/@img/colour": { "version": "1.0.0", @@ -3054,13 +2976,6 @@ "integrity": "sha512-Hq9IMnfekuOCsEmYl4QX2HBrT+XsfXiupfrLLY8Dcf3Puf4BkBOxSbWYTITSOQAhJoYPBez+b4MJRpIYL65z8A==", "license": "MIT" }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/jsdom": { "version": "21.1.7", "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-21.1.7.tgz", @@ -3088,13 +3003,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", - "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.0.0.tgz", + "integrity": "sha512-VT7KSYudcPOzP5Q0wfbowyNLaVR8QWUdw+088uFWwfvpY6uCWaXpqV6ieLAu9WBcnTa7H4Z5RLK8I5t2FuOcqw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.16.0" + "undici-types": "~6.11.1" } }, "node_modules/@types/node-telegram-bot-api": { @@ -3110,9 +3025,9 @@ } }, "node_modules/@types/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz", - "integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz", + "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==", "dev": true, "license": "MIT", "dependencies": { @@ -3120,9 +3035,9 @@ } }, "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -3554,6 +3469,13 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", @@ -4193,9 +4115,9 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -4213,11 +4135,11 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", - "fraction.js": "^5.3.4", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", + "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.1.1", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -5461,63 +5383,60 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "8.57.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", + "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.0", + "@humanwhocodes/config-array": "^0.11.14", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", + "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-config-next": { @@ -5883,9 +5802,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5893,7 +5812,7 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -5912,6 +5831,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5923,17 +5852,17 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/eslint/node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "esutils": "^2.0.2" }, - "funding": { - "url": "https://opencollective.com/eslint" + "engines": { + "node": ">=6.0.0" } }, "node_modules/eslint/node_modules/minimatch": { @@ -5949,32 +5878,32 @@ "node": "*" } }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.15.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^3.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -6147,16 +6076,16 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/file-type": { @@ -6198,17 +6127,18 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -6278,16 +6208,16 @@ } }, "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", + "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", "dev": true, "license": "MIT", "engines": { "node": "*" }, "funding": { - "type": "github", + "type": "patreon", "url": "https://github.com/sponsors/rawify" } }, @@ -6297,6 +6227,13 @@ "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "license": "MIT" }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -6511,13 +6448,16 @@ } }, "node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, "engines": { - "node": ">=18" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6845,6 +6785,18 @@ "node": ">=0.8.19" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -7142,6 +7094,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -7643,12 +7605,12 @@ } }, "node_modules/lucide-react": { - "version": "0.279.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.279.0.tgz", - "integrity": "sha512-LJ8g66+Bxc3t3x9vKTeK3wn3xucrOQGfJ9ou9GsBwCt2offsrT2BB90XrTrIzE1noYYDe2O8jZaRHi6sAHXNxw==", + "version": "0.554.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz", + "integrity": "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==", "license": "ISC", "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/math-intrinsics": { @@ -8390,6 +8352,16 @@ "node": ">=8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -8495,9 +8467,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.4.47", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.47.tgz", + "integrity": "sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==", "dev": true, "funding": [ { @@ -8515,8 +8487,8 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", + "nanoid": "^3.3.7", + "picocolors": "^1.1.0", "source-map-js": "^1.2.1" }, "engines": { @@ -9228,6 +9200,69 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/rrweb-cssom": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.6.0.tgz", @@ -10248,6 +10283,13 @@ "b4a": "^1.6.4" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -10423,6 +10465,19 @@ "node": ">= 0.8.0" } }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -10794,9 +10849,9 @@ } }, "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.11.1.tgz", + "integrity": "sha512-mIDEX2ek50x0OlRgxryxsenE5XaQD4on5U2inY7RApK3SOJpofyw7uW2AyfMKkhAxXIceo2DeWGVGwyvng1GNQ==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index 12f5108..11b72b9 100644 --- a/package.json +++ b/package.json @@ -45,14 +45,14 @@ "@types/jsdom": "^21.1.3", "@types/node": "22.0.0", "@types/node-telegram-bot-api": "^0.61.8", - "@types/react": "19.0.0", - "@types/react-dom": "19.0.0", + "@types/react": "19.2.0", + "@types/react-dom": "19.2.0", "@typescript-eslint/eslint-plugin": "^6.7.3", "autoprefixer": "10.4.20", - "eslint": "9.0.0", + "eslint": "8.57.0", "eslint-config-next": "16.0.3", "postcss": "8.4.47", "tailwindcss": "^3.4.18", - "typescript": "5.6.0" + "typescript": "5.9.3" } } diff --git a/scripts/install.sh b/scripts/install.sh new file mode 100755 index 0000000..b10cec0 --- /dev/null +++ b/scripts/install.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Configuration +INSTALL_DIR="/opt/kspguti-schedule" +SERVICE_USER="www-data" +SERVICE_GROUP="www-data" +SERVICE_NAME="kspguti-schedule" +NODE_VERSION="20" + +echo -e "${GREEN}=== KSPGUTI Schedule Installation Script ===${NC}\n" + +# Check if running as root +if [ "$EUID" -ne 0 ]; then + echo -e "${RED}Please run as root (use sudo)${NC}" + exit 1 +fi + +# Check Node.js version +echo -e "${YELLOW}Checking Node.js...${NC}" +if ! command -v node &> /dev/null; then + echo -e "${RED}Node.js is not installed. Please install Node.js ${NODE_VERSION}+ first.${NC}" + exit 1 +fi + +NODE_VER=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) +if [ "$NODE_VER" -lt "$NODE_VERSION" ]; then + echo -e "${RED}Node.js version ${NODE_VERSION}+ is required. Current version: $(node -v)${NC}" + exit 1 +fi + +echo -e "${GREEN}Node.js version: $(node -v)${NC}" + +# Check npm +if ! command -v npm &> /dev/null; then + echo -e "${RED}npm is not installed.${NC}" + exit 1 +fi + +echo -e "${GREEN}npm version: $(npm -v)${NC}\n" + +# Get the directory where the script is located +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +PROJECT_DIR="$( cd "$SCRIPT_DIR/.." && pwd )" + +echo -e "${YELLOW}Project directory: $PROJECT_DIR${NC}" +echo -e "${YELLOW}Installation directory: $INSTALL_DIR${NC}\n" + +# Create installation directory +echo -e "${YELLOW}Creating installation directory...${NC}" +mkdir -p "$INSTALL_DIR" + +# Copy project files +echo -e "${YELLOW}Copying project files...${NC}" +rsync -av --exclude='node_modules' \ + --exclude='.next' \ + --exclude='.git' \ + --exclude='*.log' \ + --exclude='.env*' \ + --exclude='*.md' \ + "$PROJECT_DIR/" "$INSTALL_DIR/" + +# Create .env.production if it doesn't exist +if [ ! -f "$INSTALL_DIR/.env.production" ]; then + echo -e "${YELLOW}Creating .env.production from example...${NC}" + if [ -f "$INSTALL_DIR/.env.production.example" ]; then + cp "$INSTALL_DIR/.env.production.example" "$INSTALL_DIR/.env.production" + echo -e "${YELLOW}Please edit $INSTALL_DIR/.env.production with your configuration${NC}" + fi +fi + +# Install dependencies +echo -e "${YELLOW}Installing dependencies...${NC}" +cd "$INSTALL_DIR" +npm ci --legacy-peer-deps --production=false + +# Build the application +echo -e "${YELLOW}Building the application...${NC}" +npm run build + +# Set ownership +echo -e "${YELLOW}Setting ownership...${NC}" +chown -R "$SERVICE_USER:$SERVICE_GROUP" "$INSTALL_DIR" + +# Install systemd service +echo -e "${YELLOW}Installing systemd service...${NC}" +cp "$INSTALL_DIR/systemd/$SERVICE_NAME.service" "/etc/systemd/system/" +systemctl daemon-reload + +# Enable service +echo -e "${YELLOW}Enabling service...${NC}" +systemctl enable "$SERVICE_NAME.service" + +echo -e "\n${GREEN}=== Installation completed successfully! ===${NC}\n" +echo -e "${YELLOW}Next steps:${NC}" +echo -e "1. Edit environment variables: ${GREEN}$INSTALL_DIR/.env.production${NC}" +echo -e "2. Update systemd service if needed: ${GREEN}/etc/systemd/system/$SERVICE_NAME.service${NC}" +echo -e "3. Start the service: ${GREEN}systemctl start $SERVICE_NAME${NC}" +echo -e "4. Check status: ${GREEN}systemctl status $SERVICE_NAME${NC}" +echo -e "5. View logs: ${GREEN}journalctl -u $SERVICE_NAME -f${NC}\n" + diff --git a/scripts/manage.sh b/scripts/manage.sh new file mode 100755 index 0000000..f287c82 --- /dev/null +++ b/scripts/manage.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +SERVICE_NAME="kspguti-schedule" +INSTALL_DIR="/opt/kspguti-schedule" + +show_usage() { + echo -e "${BLUE}Usage: $0 {start|stop|restart|status|logs|update|enable|disable}${NC}" + echo "" + echo "Commands:" + echo " start - Start the service" + echo " stop - Stop the service" + echo " restart - Restart the service" + echo " status - Show service status" + echo " logs - Show service logs (use -f for follow)" + echo " update - Update the application (pull, install, build, restart)" + echo " enable - Enable service to start on boot" + echo " disable - Disable service from starting on boot" + exit 1 +} + +check_root() { + if [ "$1" != "status" ] && [ "$1" != "logs" ] && [ "$EUID" -ne 0 ]; then + echo -e "${RED}This command requires root privileges (use sudo)${NC}" + exit 1 + fi +} + +case "$1" in + start) + check_root "$1" + echo -e "${YELLOW}Starting $SERVICE_NAME...${NC}" + systemctl start "$SERVICE_NAME" + systemctl status "$SERVICE_NAME" --no-pager + ;; + stop) + check_root "$1" + echo -e "${YELLOW}Stopping $SERVICE_NAME...${NC}" + systemctl stop "$SERVICE_NAME" + echo -e "${GREEN}Service stopped${NC}" + ;; + restart) + check_root "$1" + echo -e "${YELLOW}Restarting $SERVICE_NAME...${NC}" + systemctl restart "$SERVICE_NAME" + systemctl status "$SERVICE_NAME" --no-pager + ;; + status) + systemctl status "$SERVICE_NAME" --no-pager + ;; + logs) + if [ "$2" == "-f" ]; then + journalctl -u "$SERVICE_NAME" -f + else + journalctl -u "$SERVICE_NAME" -n 50 --no-pager + fi + ;; + update) + check_root "$1" + echo -e "${YELLOW}Updating $SERVICE_NAME...${NC}" + + if [ ! -d "$INSTALL_DIR" ]; then + echo -e "${RED}Installation directory not found: $INSTALL_DIR${NC}" + exit 1 + fi + + cd "$INSTALL_DIR" + + # Stop service + echo -e "${YELLOW}Stopping service...${NC}" + systemctl stop "$SERVICE_NAME" + + # Pull latest changes (if using git) + if [ -d ".git" ]; then + echo -e "${YELLOW}Pulling latest changes...${NC}" + git pull + else + echo -e "${YELLOW}Not a git repository, skipping pull${NC}" + fi + + # Install dependencies + echo -e "${YELLOW}Installing dependencies...${NC}" + npm ci --legacy-peer-deps --production=false + + # Build + echo -e "${YELLOW}Building application...${NC}" + npm run build + + # Set ownership + chown -R www-data:www-data "$INSTALL_DIR" + + # Reload systemd and restart + systemctl daemon-reload + echo -e "${YELLOW}Starting service...${NC}" + systemctl start "$SERVICE_NAME" + + echo -e "${GREEN}Update completed!${NC}" + systemctl status "$SERVICE_NAME" --no-pager + ;; + enable) + check_root "$1" + echo -e "${YELLOW}Enabling $SERVICE_NAME to start on boot...${NC}" + systemctl enable "$SERVICE_NAME" + echo -e "${GREEN}Service enabled${NC}" + ;; + disable) + check_root "$1" + echo -e "${YELLOW}Disabling $SERVICE_NAME from starting on boot...${NC}" + systemctl disable "$SERVICE_NAME" + echo -e "${GREEN}Service disabled${NC}" + ;; + *) + show_usage + ;; +esac + +exit 0 + diff --git a/src/features/add-group/index.tsx b/src/features/add-group/index.tsx index 0496081..ee2a60a 100644 --- a/src/features/add-group/index.tsx +++ b/src/features/add-group/index.tsx @@ -43,15 +43,13 @@ function Popup({ open, onClose }: { Если вы хотите добавить свою группу на сайт, скиньтесь всей группой и задонатьте мне 500 ₽ - На сайте не только появится ваше расписание, но оно будет обновляться каждую неделю. А если во время парсинга произойдет ошибка — я сразу получу об этом уведомление в телеграм, потому что у меня настроен бот. + Для меня это будет очень хорошая поддержка🥺🥺🥺 - {/* eslint-disable-next-line @next/next/no-img-element */} - Для меня добавить вашу группу это даже не одна строка кода, а одно нажатие клавиши, но я хочу чтобы этот сайт был доступен только самым лучшим и избранным, достойных престижа - + diff --git a/src/pages/index.tsx b/src/pages/index.tsx index bc15884..8e34012 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,11 +1,15 @@ import { GetServerSidePropsResult } from 'next' +import { groups } from '@/shared/data/groups' export default function HomePage() { } export async function getServerSideProps(): Promise>> { + // Получаем первую группу из списка + const firstGroupId = Object.keys(groups)[0] + return { redirect: { - destination: '/ps7', + destination: `/${firstGroupId}`, permanent: true } } diff --git a/src/widgets/navbar/index.tsx b/src/widgets/navbar/index.tsx index 6cc516a..9f07c97 100644 --- a/src/widgets/navbar/index.tsx +++ b/src/widgets/navbar/index.tsx @@ -51,7 +51,7 @@ export function NavBar({ cacheAvailableFor }: {
- + diff --git a/systemd/kspguti-schedule.service b/systemd/kspguti-schedule.service new file mode 100644 index 0000000..2868f79 --- /dev/null +++ b/systemd/kspguti-schedule.service @@ -0,0 +1,39 @@ +[Unit] +Description=KSPGUTI Schedule - Next.js Application +After=network.target + +[Service] +Type=simple +User=www-data +Group=www-data +WorkingDirectory=/opt/kspguti-schedule +Environment=NODE_ENV=production +Environment=NEXT_TELEMETRY_DISABLED=1 +Environment=PORT=3000 +Environment=HOSTNAME=0.0.0.0 +# Uncomment and set your environment variables: +# Environment=PROXY_URL=https://lk.ks.psuti.ru +# Environment=PARSING_FAILURE_NOTIFICATIONS_TELEGRAM_BOTAPI_TOKEN=your_token_here +# Environment=PARSING_FAILURE_NOTIFICATIONS_TELEGRAM_CHAT_ID=your_chat_id_here + +# Load environment variables from file (optional) +# EnvironmentFile=/opt/kspguti-schedule/.env.production + +# Use standalone server from Next.js build +ExecStart=/usr/bin/node /opt/kspguti-schedule/.next/standalone/server.js +Restart=always +RestartSec=10 +StandardOutput=journal +StandardError=journal +SyslogIdentifier=kspguti-schedule + +# Security settings +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/kspguti-schedule + +[Install] +WantedBy=multi-user.target +