diff --git a/Dockerfile b/Dockerfile index 4a15f98d..52453541 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,54 +1,57 @@ -# ======================================================== -# Stage: Builder -# ======================================================== -FROM golang:1.24-alpine AS builder +# Stage 1: Build the Next.js application +FROM node:20-alpine AS builder + +# Set working directory WORKDIR /app -ARG TARGETARCH -RUN apk --no-cache --update add \ - build-base \ - gcc \ - wget \ - unzip +# Install dependencies +# Copy package.json and yarn.lock (or package-lock.json if using npm) +COPY package.json yarn.lock ./ +# Ensure corepack is enabled to use yarn specified in package.json +RUN corepack enable +RUN yarn install --frozen-lockfile --network-timeout 600000 +# Copy the rest of the application source code COPY . . -ENV CGO_ENABLED=1 -ENV CGO_CFLAGS="-D_LARGEFILE64_SOURCE" -RUN go build -ldflags "-w -s" -o build/x-ui main.go -RUN ./DockerInit.sh "$TARGETARCH" +# Build the Next.js application +# NEXT_PUBLIC_API_BASE_URL can be set here if it's fixed, +# or passed as an ARG during docker build, or as an ENV var at runtime. +# For flexibility, runtime ENV var is often preferred. +# ARG NEXT_PUBLIC_API_BASE_URL +# ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL} +RUN yarn build + +# Stage 2: Production environment +FROM node:20-alpine AS runner -# ======================================================== -# Stage: Final Image of 3x-ui -# ======================================================== -FROM alpine -ENV TZ=Asia/Tehran WORKDIR /app -RUN apk add --no-cache --update \ - ca-certificates \ - tzdata \ - fail2ban \ - bash +# Set environment variables +# ENV NODE_ENV=production # Already set by `next start` +# NEXT_PUBLIC_API_BASE_URL will be set at runtime via docker-compose or run command +# ENV PORT=3000 # Next.js default port is 3000, can be overridden -COPY --from=builder /app/build/ /app/ -COPY --from=builder /app/DockerEntrypoint.sh /app/ -COPY --from=builder /app/x-ui.sh /usr/bin/x-ui +# Copy built assets from the builder stage +# This includes the .next folder (production build) and public folder. +# For a standard Next.js build (not standalone or static export), +# we also need node_modules and package.json to run `next start`. +COPY --from=builder /app/.next ./.next +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json +# If yarn.lock is needed for `yarn start` with specific versions, copy it too. +# Usually for `yarn start` just package.json and production node_modules are needed. +# For yarn, yarn.lock is good practice to ensure consistent prod dependencies if any are direct. +COPY --from=builder /app/yarn.lock ./ +# Install production dependencies only +# Ensure corepack is enabled +RUN corepack enable +RUN yarn install --production --frozen-lockfile --network-timeout 600000 -# Configure fail2ban -RUN rm -f /etc/fail2ban/jail.d/alpine-ssh.conf \ - && cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local \ - && sed -i "s/^\[ssh\]$/&\nenabled = false/" /etc/fail2ban/jail.local \ - && sed -i "s/^\[sshd\]$/&\nenabled = false/" /etc/fail2ban/jail.local \ - && sed -i "s/#allowipv6 = auto/allowipv6 = auto/g" /etc/fail2ban/fail2ban.conf +# Expose port 3000 (default for Next.js) +EXPOSE 3000 -RUN chmod +x \ - /app/DockerEntrypoint.sh \ - /app/x-ui \ - /usr/bin/x-ui - -ENV XUI_ENABLE_FAIL2BAN="true" -VOLUME [ "/etc/x-ui" ] -CMD [ "./x-ui" ] -ENTRYPOINT [ "/app/DockerEntrypoint.sh" ] +# The "start" script in package.json runs "next start" +# This will serve the application from the .next folder. +CMD ["yarn", "start"] diff --git a/Dockerfile.backend b/Dockerfile.backend new file mode 100644 index 00000000..70f94f7a --- /dev/null +++ b/Dockerfile.backend @@ -0,0 +1,48 @@ +# Stage 1: Build the Go application +FROM golang:1.21-alpine AS builder + +WORKDIR /app + +# Copy go.mod and go.sum and download dependencies +COPY go.mod go.sum ./ +RUN go mod download + +# Copy the rest of the application source code +COPY . . + +# Build the Go application +# Assuming the main package is in the root and output is 'x-ui' or 'main' +# The original entrypoint seems to be related to x-ui.sh or DockerEntrypoint.sh +# We need to ensure the binary is built correctly. +# For 3x-ui, the main.go seems to be the entry point. +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o /app/x-ui main.go + +# Stage 2: Production environment +FROM alpine:latest + +WORKDIR /app + +# Copy the binary from the builder stage +COPY --from=builder /app/x-ui /app/x-ui +COPY --from=builder /app/x-ui.sh /app/x-ui.sh +COPY --from=builder /app/DockerEntrypoint.sh /app/DockerEntrypoint.sh +COPY --from=builder /app/config/name /app/config/name +COPY --from=builder /app/config/version /app/config/version + + +# Ensure necessary directories exist and have correct permissions if needed by the app +# The original compose file mounts $PWD/db/:/etc/x-ui/ and $PWD/cert/:/root/cert/ +# So, these paths should be available or created by the entrypoint script. +RUN mkdir -p /etc/x-ui && \ + mkdir -p /root/cert && \ + chmod +x /app/x-ui.sh /app/DockerEntrypoint.sh /app/x-ui + +# Expose default panel port (e.g., 2053, but this will be handled by docker-compose) +# The original compose uses network_mode: host, so ports are directly from the app. +# If we move away from network_mode: host, we'll need to EXPOSE the correct port here. +# Let's assume the Go app listens on a port defined by an ENV or config, e.g., 2053 +EXPOSE 2053 + +# Entrypoint +ENTRYPOINT ["/app/DockerEntrypoint.sh"] +CMD ["/app/x-ui"] # Default command if DockerEntrypoint.sh doesn't override diff --git a/README.md b/README.md index db5d6a18..4613530a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@
-**An Advanced Web Panel β’ Built on Xray Core** +**An Advanced Web Panel β’ Built on Xray Core β’ With a Modern React/Next.js Frontend** [](https://github.com/MHSanaei/3x-ui/releases) [](#) @@ -15,13 +15,13 @@ [](#) [](https://www.gnu.org/licenses/gpl-3.0.en.html) -> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes, please do not use it in a production environment +> **Disclaimer:** This project is only for personal learning and communication, please do not use it for illegal purposes. Using it in a production environment is at your own risk. **If this project is helpful to you, you may wish to give it a**:star2: @@ -29,569 +29,155 @@ - MATIC (polygon): `0x41C9548675D044c6Bfb425786C765bc37427256A` - LTC (Litecoin): `ltc1q2ach7x6d2zq0n4l0t4zl7d7xe2s6fs7a3vspwv` -## Install & Upgrade +## β¨ New Frontend! -``` -bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/refs/tags/v2.6.0/install.sh) -``` +This version of 3X-UI features a completely revamped frontend built with **React, Next.js, and Tailwind CSS**, offering a modern, responsive, and user-friendly experience. Key improvements include: +- Enhanced User Interface (UI) and User Experience (UX). +- Improved responsiveness for mobile and tablet devices. +- Integrated Dark Mode. +- Streamlined management of inbounds, clients, and settings. -## Install legacy Version (we don't recommend) +## π Installation (Docker Based - Recommended) -To install your desired version, use following installation command. e.g., ver `v1.7.9`: +This new version is designed to be run using Docker and Docker Compose for ease of installation, updates, and management. +**Prerequisites:** +- A Linux server (Ubuntu 22.04+, Debian 12+, CentOS 8+, Fedora 36+, or other compatible distributions). +- Root or sudo privileges. +- `curl` and `git` installed (the installation script will attempt to install them if missing). +- Docker and Docker Compose plugin (the installation script will attempt to install them if missing). + +**Quick Install Command:** + +```bash +bash <(curl -Ls https://raw.githubusercontent.com/MHSanaei/3x-ui/main/install.sh) ``` -VERSION=v1.7.9 && bash <(curl -Ls "https://raw.githubusercontent.com/mhsanaei/3x-ui/$VERSION/install.sh") $VERSION -``` +*(Note: The URL should point to the new `install.sh` in the `main` branch or a specific release tag once updated).* + +**Installation Steps Explained by the Script:** +1. Checks for root privileges and essential tools (`curl`, `git`). +2. Installs/Updates Docker and Docker Compose plugin if they are not present. +3. Prompts you for an installation directory (default: `/opt/3x-ui-docker`). +4. Clones (or updates an existing clone of) the 3x-ui repository into a source subdirectory (e.g., `3x-ui-source`). +5. Navigates into the source directory. +6. Creates necessary data directories (`db`, `cert`) for persistent storage. +7. Prompts you for HOST ports for the new frontend (default: 3000) and the backend panel (default: 2053). +8. Generates a `.env` file with these port configurations and sets up `NEXT_PUBLIC_API_BASE_URL` for communication between the frontend and backend containers. +9. Runs `docker compose up -d --build --remove-orphans` to build the Docker images (for both backend and the new frontend) and start the services in detached mode. + +After installation, the new frontend panel should be accessible at `http://For Shadowsocks, client configuration (method and password) is part of the main inbound settings. - The QR code / subscription link below uses these global settings. + Use the 'QR' button below to get the share link for the inbound.
} @@ -232,7 +231,7 @@ const ManageClientsPage: React.FC = () => { { (inbound?.protocol === 'shadowsocks') ? (No clients configured for this inbound.
} + {inbound?.protocol === 'shadowsocks' && ++ For Shadowsocks, client configuration (method and password) is part of the main inbound settings. + The QR code / subscription link below uses these global settings. +
+ } + + {(displayClients.length > 0 || inbound?.protocol === 'shadowsocks') && ( +Email / Identifier | + {(inbound?.protocol !== 'shadowsocks') &&{getClientIdentifierLabel(inbound?.protocol)} | } +Traffic (Up/Down) | +Quota | +Expiry | +Status | +Actions | +
---|---|---|---|---|---|---|
{inbound.remark || 'Shadowsocks Settings'} | +{formatBytes(inbound.up || 0)} / {formatBytes(inbound.down || 0)} | +{inbound.total > 0 ? formatBytes(inbound.total) : 'Unlimited'} | +{inbound.expiryTime > 0 ? new Date(inbound.expiryTime).toLocaleDateString() : 'Never'} | ++ {inbound.enable ? Enabled : Disabled } + | ++ + | +|
{client.email} | +{getClientIdentifier(client, inbound?.protocol)} | +{formatBytes(client.up || 0)} / {formatBytes(client.down || 0)} | ++ { (client.totalGB !== undefined && client.totalGB > 0) ? formatBytes(client.totalGB * 1024 * 1024 * 1024) : (client.actualTotal !== undefined && client.actualTotal > 0) ? formatBytes(client.actualTotal) : 'Unlimited' } + | ++ {client.actualExpiryTime && client.actualExpiryTime > 0 ? new Date(client.actualExpiryTime).toLocaleDateString() : client.expiryTime && client.expiryTime > 0 ? new Date(client.expiryTime).toLocaleDateString() + " (Def)" : 'Never'} + | ++ {client.enableClientStat === undefined ? 'N/A' : client.enableClientStat ? + Enabled : + Disabled + } + | ++ + + + + | +