diff --git a/.github/workflows/deploy-nightly.yml b/.github/workflows/deploy-nightly.yml index a94e2994..ae0d9787 100644 --- a/.github/workflows/deploy-nightly.yml +++ b/.github/workflows/deploy-nightly.yml @@ -29,10 +29,7 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - - name: Set environment variables - run: echo RELEASE=$(cat ./src/package.json | jq -r .release) >> $GITHUB_ENV - + - name: Build & Publish Docker Image uses: docker/build-push-action@v5 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 976583f5..84e5b178 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,10 +30,10 @@ jobs: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - + - name: Set environment variables run: echo RELEASE=$(cat ./src/package.json | jq -r .release) >> $GITHUB_ENV - + - name: Build & Publish Docker Image uses: docker/build-push-action@v5 with: diff --git a/.github/workflows/npm-update-bot.yml b/.github/workflows/npm-update-bot.yml index 82b47f74..8c377680 100644 --- a/.github/workflows/npm-update-bot.yml +++ b/.github/workflows/npm-update-bot.yml @@ -13,6 +13,9 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + with: + repository: wg-easy/wg-easy + ref: master - name: Setup Node uses: actions/setup-node@v4 with: @@ -35,4 +38,4 @@ jobs: git config --global user.email 'npmupbot@users.noreply.github.com' git add . git commit -am "npm: package updates" || true - git push || true + git push diff --git a/.gitignore b/.gitignore index 77574b5e..85dbdfa3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /config /wg0.conf -/wg0.json \ No newline at end of file +/wg0.json +.DS_Store diff --git a/Dockerfile b/Dockerfile index 0605feb9..985b03e7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,22 +3,14 @@ FROM docker.io/library/node:18-alpine AS build_node_modules -# Hide fund and update-notifier message -RUN npm config set -g fund false &&\ - npm config set -g update-notifier false - # Copy Web UI COPY src/ /app/ WORKDIR /app -RUN npm ci +RUN npm ci --omit=dev + # Copy build result to a new image. # This saves a lot of disk space. FROM docker.io/library/node:18-alpine - -# Hide fund and update-notifier message -RUN npm config set -g fund false &&\ - npm config set -g update-notifier false - COPY --from=build_node_modules /app /app # Move node_modules one directory up, so during development @@ -34,14 +26,14 @@ RUN mv /app/node_modules /node_modules RUN npm i -g nodemon # Install Linux packages -RUN apk add -U --no-cache \ +RUN apk add --no-cache \ dpkg \ dumb-init \ iptables \ iptables-legacy \ wireguard-tools -# Symlink iptables +# Use iptables-legacy RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save # Expose Ports diff --git a/README.md b/README.md index 8f235204..3c1a5a99 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,7 @@ You have found the easiest way to install & manage WireGuard on any Linux host! * Statistics for which clients are connected. * Tx/Rx charts for each connected client. * Gravatar support. -* Automatic Light / Dark Mode -* Sessionless HTTP API authentication +* Automatic Light / Dark Mode (WIP: available in `nightly` build) ## Requirements @@ -65,7 +64,7 @@ $ docker run -d \ > 💡 Replace `YOUR_SERVER_IP` with your WAN IP, or a Dynamic DNS hostname. -> +> > 💡 Replace `YOUR_ADMIN_PASSWORD` with a password to log in on the Web UI. The Web UI will now be available on `http://0.0.0.0:51821`. @@ -87,11 +86,11 @@ These options can be configured by setting environment variables using `-e KEY=" | `PASSWORD` | - | `foobar123` | When set, requires a password when logging in to the Web UI. | | `WG_HOST` | - | `vpn.myserver.com` | The public hostname of your VPN server. | | `WG_DEVICE` | `eth0` | `ens6f0` | Ethernet device the wireguard traffic should be forwarded through. | -| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will always listen on `51820` inside the Docker container. | +| `WG_PORT` | `51820` | `12345` | The public UDP port of your VPN server. WireGuard will always listen on 51820 inside the Docker container. | | `WG_MTU` | `null` | `1420` | The MTU the clients will use. Server uses default WG MTU. | | `WG_PERSISTENT_KEEPALIVE` | `0` | `25` | Value in seconds to keep the "connection" open. If this value is 0, then connections won't be kept alive. | | `WG_DEFAULT_ADDRESS` | `10.8.0.x` | `10.6.0.x` | Clients IP address range. | -| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. | +| `WG_DEFAULT_DNS` | `1.1.1.1` | `8.8.8.8, 8.8.4.4` | DNS server clients will use. If set to blank value, clients will not use any DNS. | | `WG_ALLOWED_IPS` | `0.0.0.0/0, ::/0` | `192.168.15.0/24, 10.0.1.0/24` | Allowed IPs clients will use. | | `WG_PRE_UP` | `...` | - | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L19) for the default value. | | `WG_POST_UP` | `...` | `iptables ...` | See [config.js](https://github.com/wg-easy/wg-easy/blob/master/src/config.js#L20) for the default value. | @@ -116,3 +115,5 @@ And then run the `docker run -d \ ...` command above again. * [Using WireGuard-Easy with Pi-Hole](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-Pi-Hole) * [Using WireGuard-Easy with nginx/SSL](https://github.com/wg-easy/wg-easy/wiki/Using-WireGuard-Easy-with-nginx-SSL) + +For less common or specific edge-case scenarios, please refer to the detailed information provided in the [Wiki](https://github.com/wg-easy/wg-easy/wiki). diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 65dfc570..08e46978 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,6 +5,6 @@ services: command: npm run serve volumes: - ./src/:/app/ - environment: + environment: # - PASSWORD=p - WG_HOST=192.168.1.233 diff --git a/docker-compose.yml b/docker-compose.yml index 52811814..d9ddcf7c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,7 +21,7 @@ services: # - WG_POST_UP=echo "Post Up" > /etc/wireguard/post-up.txt # - WG_PRE_DOWN=echo "Pre Down" > /etc/wireguard/pre-down.txt # - WG_POST_DOWN=echo "Post Down" > /etc/wireguard/post-down.txt - + image: ghcr.io/wg-easy/wg-easy container_name: wg-easy volumes: diff --git a/docs/changelog.json b/docs/changelog.json index 7f49c22e..34f64b37 100644 --- a/docs/changelog.json +++ b/docs/changelog.json @@ -7,7 +7,6 @@ "6": "Many small performance improvements & bug fixes. Enjoy!", "7": "Improved the look & performance of the upload/download chart.", "8": "Updated to Node.js v18.", - "8": "Updated to Node.js v18.", "9": "Fixed issue running on devices with older kernels.", "10": "Added sessionless HTTP API auth & automatic dark mode." } diff --git a/package.json b/package.json index 704c58d0..ded9b4e9 100644 --- a/package.json +++ b/package.json @@ -5,4 +5,4 @@ "serve": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up", "start": "docker run --env WG_HOST=0.0.0.0 --name wg-easy --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl=\"net.ipv4.conf.all.src_valid_mark=1\" --mount type=bind,source=\"$(pwd)\"/config,target=/etc/wireguard -p 51820:51820/udp -p 51821:51821/tcp wg-easy" } -} \ No newline at end of file +} diff --git a/src/.eslintrc.json b/src/.eslintrc.json index 9fc48da5..613dd7d5 100644 --- a/src/.eslintrc.json +++ b/src/.eslintrc.json @@ -8,4 +8,4 @@ "no-shadow": "off", "max-len": "off" } -} \ No newline at end of file +} diff --git a/src/.gitignore b/src/.gitignore index 30bc1627..07e6e472 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1 +1 @@ -/node_modules \ No newline at end of file +/node_modules diff --git a/src/lib/Server.js b/src/lib/Server.js index 17838886..fbefef91 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -83,16 +83,9 @@ module.exports = class Server { } if (req.path.startsWith('/api/') && req.headers['authorization']) { - const authorizationHash = bcrypt.createHash('bcrypt') - .update(req.headers['authorization']) - .digest('hex'); - const passwordHash = bcrypt.createHash('bcrypt') - .update(PASSWORD) - .digest('hex'); - if (bcrypt.timingSafeEqual(Buffer.from(authorizationHash), Buffer.from(passwordHash))) { + if (bcrypt.compareSync(req.headers['authorization'], bcrypt.hashSync(PASSWORD, 10))) { return next(); } - return res.status(401).json({ error: 'Incorrect Password', }); diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index cadb47e2..1d432a30 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -67,7 +67,7 @@ module.exports = class WireGuard { throw err; }); - // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o eth0 -j MASQUERADE`); + // await Util.exec(`iptables -t nat -A POSTROUTING -s ${WG_DEFAULT_ADDRESS.replace('x', '0')}/24 -o ' + WG_DEVICE + ' -j MASQUERADE`); // await Util.exec('iptables -A INPUT -p udp -m udp --dport 51820 -j ACCEPT'); // await Util.exec('iptables -A FORWARD -i wg0 -j ACCEPT'); // await Util.exec('iptables -A FORWARD -o wg0 -j ACCEPT'); @@ -95,7 +95,7 @@ module.exports = class WireGuard { [Interface] PrivateKey = ${config.server.privateKey} Address = ${config.server.address}/24 -ListenPort = ${WG_PORT} +ListenPort = 51820 PreUp = ${WG_PRE_UP} PostUp = ${WG_POST_UP} PreDown = ${WG_PRE_DOWN} diff --git a/src/package-lock.json b/src/package-lock.json index 4e0764ad..aa0ab6b7 100644 --- a/src/package-lock.json +++ b/src/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "eslint-config-athom": "^3.1.3", - "tailwindcss": "^3.4.0" + "tailwindcss": "^3.4.1" }, "engines": { "node": "18" @@ -492,16 +492,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.17.0.tgz", - "integrity": "sha512-Vih/4xLXmY7V490dGwBQJTpIZxH4ZFH6eCVmQ4RFkB+wmaCTDAx4dtgoWwMNGKLkqRY1L6rPqzEbjorRnDo4rQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.18.1.tgz", + "integrity": "sha512-nISDRYnnIpk7VCFrGcu1rnZfM1Dh9LRHnfgdkjcbi/l7g16VYRri3TjXi9Ir4lOZSw5N/gnV/3H7jIPQ8Q4daA==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/type-utils": "6.17.0", - "@typescript-eslint/utils": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/type-utils": "6.18.1", + "@typescript-eslint/utils": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -527,15 +527,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.17.0.tgz", - "integrity": "sha512-C4bBaX2orvhK+LlwrY8oWGmSl4WolCfYm513gEccdWZj0CwGadbIADb0FtVEcI+WzUyjyoBj2JRP8g25E6IB8A==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.18.1.tgz", + "integrity": "sha512-zct/MdJnVaRRNy9e84XnVtRv9Vf91/qqe+hZJtKanjojud4wAVy/7lXxJmMyX6X6J+xc6c//YEWvpeif8cAhWA==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4" }, "engines": { @@ -555,13 +555,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.17.0.tgz", - "integrity": "sha512-RX7a8lwgOi7am0k17NUO0+ZmMOX4PpjLtLRgLmT1d3lBYdWH4ssBUbwdmc5pdRX8rXon8v9x8vaoOSpkHfcXGA==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.18.1.tgz", + "integrity": "sha512-BgdBwXPFmZzaZUuw6wKiHKIovms97a7eTImjkXCZE04TGHysG+0hDQPmygyvgtkoB/aOQwSM/nWv3LzrOIQOBw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0" + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -572,13 +572,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.17.0.tgz", - "integrity": "sha512-hDXcWmnbtn4P2B37ka3nil3yi3VCQO2QEB9gBiHJmQp5wmyQWqnjA85+ZcE8c4FqnaB6lBwMrPkgd4aBYz3iNg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.18.1.tgz", + "integrity": "sha512-wyOSKhuzHeU/5pcRDP2G2Ndci+4g653V43gXTpt4nbyoIOAASkGDA9JIAgbQCdCkcr1MvpSYWzxTz0olCn8+/Q==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.17.0", - "@typescript-eslint/utils": "6.17.0", + "@typescript-eslint/typescript-estree": "6.18.1", + "@typescript-eslint/utils": "6.18.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -599,9 +599,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.17.0.tgz", - "integrity": "sha512-qRKs9tvc3a4RBcL/9PXtKSehI/q8wuU9xYJxe97WFxnzH8NWWtcW3ffNS+EWg8uPvIerhjsEZ+rHtDqOCiH57A==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.18.1.tgz", + "integrity": "sha512-4TuMAe+tc5oA7wwfqMtB0Y5OrREPF1GeJBAjqwgZh1lEMH5PJQgWgHGfYufVB51LtjD+peZylmeyxUXPfENLCw==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -612,13 +612,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.17.0.tgz", - "integrity": "sha512-gVQe+SLdNPfjlJn5VNGhlOhrXz4cajwFd5kAgWtZ9dCZf4XJf8xmgCTLIqec7aha3JwgLI2CK6GY1043FRxZwg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.18.1.tgz", + "integrity": "sha512-fv9B94UAhywPRhUeeV/v+3SBDvcPiLxRZJw/xZeeGgRLQZ6rLMG+8krrJUyIf6s1ecWTzlsbp0rlw7n9sjufHA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/visitor-keys": "6.17.0", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/visitor-keys": "6.18.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -640,17 +640,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.17.0.tgz", - "integrity": "sha512-LofsSPjN/ITNkzV47hxas2JCsNCEnGhVvocfyOcLzT9c/tSZE7SfhS/iWtzP1lKNOEfLhRTZz6xqI8N2RzweSQ==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.18.1.tgz", + "integrity": "sha512-zZmTuVZvD1wpoceHvoQpOiewmWu3uP9FuTWo8vqpy2ffsmfCE8mklRPi+vmnIYAIk9t/4kOThri2QCDgor+OpQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.17.0", - "@typescript-eslint/types": "6.17.0", - "@typescript-eslint/typescript-estree": "6.17.0", + "@typescript-eslint/scope-manager": "6.18.1", + "@typescript-eslint/types": "6.18.1", + "@typescript-eslint/typescript-estree": "6.18.1", "semver": "^7.5.4" }, "engines": { @@ -665,12 +665,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.17.0.tgz", - "integrity": "sha512-H6VwB/k3IuIeQOyYczyyKN8wH6ed8EwliaYHLxOIhyF0dYEIsN8+Bk3GE19qafeMKyZJJHP8+O1HiFhFLUNKSg==", + "version": "6.18.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.18.1.tgz", + "integrity": "sha512-/kvt0C5lRqGoCfsbmm7/CwMqoSkY3zzHLIjdhHZQW3VFrnz7ATecOHR7nb7V+xn4286MBxfnQfQhAmCI0u+bJA==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.17.0", + "@typescript-eslint/types": "6.18.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -3639,9 +3639,9 @@ } }, "node_modules/postcss": { - "version": "8.4.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz", - "integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==", + "version": "8.4.33", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.33.tgz", + "integrity": "sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==", "dev": true, "funding": [ { @@ -4553,9 +4553,9 @@ "peer": true }, "node_modules/tailwindcss": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", - "integrity": "sha512-VigzymniH77knD1dryXbyxR+ePHihHociZbXnLZHUyzf2MMs2ZVqlUrZ3FvpXP8pno9JzmILt1sZPD19M3IxtA==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", + "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", diff --git a/src/package.json b/src/package.json index b37a542c..a62e7694 100644 --- a/src/package.json +++ b/src/package.json @@ -22,7 +22,7 @@ }, "devDependencies": { "eslint-config-athom": "^3.1.3", - "tailwindcss": "^3.4.0" + "tailwindcss": "^3.4.1" }, "nodemonConfig": { "ignore": [ diff --git a/src/tailwind.config.js b/src/tailwind.config.js index 9c6718c9..0594481d 100644 --- a/src/tailwind.config.js +++ b/src/tailwind.config.js @@ -3,6 +3,6 @@ 'use strict'; module.exports = { - darkMode: 'class', + darkMode: 'media', content: ['./www/**/*.{html,js}'], }; diff --git a/src/www/css/app.css b/src/www/css/app.css index b1c46108..e9fe0c7c 100644 --- a/src/www/css/app.css +++ b/src/www/css/app.css @@ -1715,4 +1715,4 @@ video { .md\:pb-0 { padding-bottom: 0px; } -} \ No newline at end of file +} diff --git a/src/www/manifest.json b/src/www/manifest.json index 5214f3f3..e0834f74 100644 --- a/src/www/manifest.json +++ b/src/www/manifest.json @@ -8,4 +8,4 @@ "type": "image/png" } ] -} \ No newline at end of file +} diff --git a/src/www/src/css/app.css b/src/www/src/css/app.css index bd6213e1..b5c61c95 100644 --- a/src/www/src/css/app.css +++ b/src/www/src/css/app.css @@ -1,3 +1,3 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities;