diff --git a/.air.toml b/.air.toml index ad1b87b5..19f54c2a 100644 --- a/.air.toml +++ b/.air.toml @@ -7,7 +7,7 @@ tmp_dir = "tmp" [build] # Just plain old shell command. You could use `make` as well. -cmd = "CGO_ENABLED=1 go build -tags=jsoniter -ldflags=\"-X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'\" -v -o ./tmp/main ." +cmd = "CGO_ENABLED=1 go build -tags=jsoniter,unembed -ldflags=\"-X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'\" -v -o ./tmp/main ." # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary. diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000..18db34bd --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,49 @@ +FROM mcr.microsoft.com/devcontainers/base:jammy + +# Combine installation steps for Nginx and Go to avoid repetitive update/cleanup commands +RUN apt-get update && \ + apt-get install -y --no-install-recommends curl gnupg2 ca-certificates lsb-release ubuntu-keyring jq && \ + \ + # Configure the Nginx repository + curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor > /usr/share/keyrings/nginx-archive-keyring.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" \ + > /etc/apt/sources.list.d/nginx.list && \ + printf "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ + > /etc/apt/preferences.d/99nginx && \ + \ + # Update package information and install Nginx + apt-get update && \ + apt-get install -y --no-install-recommends nginx inotify-tools file && \ + \ + # Automatically retrieve the latest stable Go version and install it, + # download the appropriate binary based on system architecture (amd64 or arm64) + GO_VERSION=$(curl -sSL "https://golang.org/dl/?mode=json" | \ + jq -r 'map(select(.stable)) | .[0].version' | sed 's/^go//') && \ + ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "arm64" ]; then \ + GO_ARCH=linux-arm64; \ + else \ + GO_ARCH=linux-amd64; \ + fi && \ + echo "Installing Go version: ${GO_VERSION} for architecture: ${GO_ARCH}" && \ + curl -sSL "https://golang.org/dl/go${GO_VERSION}.${GO_ARCH}.tar.gz" -o go.tar.gz && \ + rm -rf /usr/local/go && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz && \ + \ + # Remove jq and clean up to reduce image size + apt-get remove -y jq && \ + apt-get autoremove -y && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN cp -rp /etc/nginx /etc/nginx.orig + +# Set PATH to include Go installation and default go install binary location +ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}" + +# Install air with go install (requires Go 1.23 or higher) +RUN go install github.com/air-verse/air@latest + +# set zsh as default shell +RUN chsh -s $(which zsh) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..27a99d31 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,44 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu +{ + "name": "Ubuntu", + "dockerComposeFile": "docker-compose.yml", + "service": "nginx-ui", + "workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", + "shutdownAction": "stopCompose", + // Features to add to the dev container. More info: https://containers.dev/features. + "features": { + "ghcr.io/devcontainers/features/common-utils:2": { + "installOhMyZsh": true + }, + "ghcr.io/devcontainers/features/node:1": {} + }, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "", + + // Configure tool-specific properties. + "customizations": { + "vscode": { + "extensions": [ + "antfu.iconify", + "antfu.unocss", + "github.copilot", + "golang.go", + "vue.volar", + "ms-azuretools.vscode-docker" + ] + } + }, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + "remoteUser": "root", + "overrideCommand": false, + "postStartCommand": "./.devcontainer/start.sh", + "mounts": [ + "source=${localEnv:HOME}/.ssh,target=/root/.ssh,type=bind,consistency=cached" + ] +} diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000..63ab9b6e --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,57 @@ +services: + nginx-ui: + build: . + image: nginx-ui-dev + container_name: nginx-ui + volumes: + - ../..:/workspaces:cached + - ./go-path:/root/go + - ./data/nginx:/etc/nginx + command: sleep infinity + environment: + - NGINX_UI_CERT_CA_DIR=https://pebble:14000/dir + networks: + nginxui: + nginx-ui-2: + image: nginx-ui-dev + container_name: nginx-ui-2 + volumes: + - ../..:/workspaces:cached + - ./data/nginx-ui-2/nginx:/etc/nginx + - ./data/nginx-ui-2/nginx-ui:/etc/nginx-ui + working_dir: /workspaces/nginx-ui + command: ./.devcontainer/node-supervisor.sh + depends_on: + - nginx-ui + networks: + nginxui: + + pebble: + image: ghcr.io/letsencrypt/pebble:latest + volumes: + - ./pebble-test:/test + command: -config /test/config/pebble-config.json -strict -dnsserver challtestsrv:8053 + ports: + - 14000:14000 # HTTPS ACME API + - 15000:15000 # HTTPS Management API + environment: + - PEBBLE_VA_NOSLEEP=1 + - PEBBLE_VA_ALWAYS_VALID=1 + networks: + nginxui: + challtestsrv: + image: ghcr.io/letsencrypt/pebble-challtestsrv:latest + command: -defaultIPv6 "" -defaultIPv4 challtestsrv + ports: + - 8055:8055 # HTTP Management API + networks: + nginxui: + casdoor: + image: casbin/casdoor-all-in-one + ports: + - 8001:8000 + networks: + - nginxui + +networks: + nginxui: diff --git a/.devcontainer/init-nginx.sh b/.devcontainer/init-nginx.sh new file mode 100755 index 00000000..87080779 --- /dev/null +++ b/.devcontainer/init-nginx.sh @@ -0,0 +1,6 @@ +# init nginx config dir +if [ "$(ls -A /etc/nginx)" = "" ]; then + echo "Initialing Nginx config dir" + cp -rp /etc/nginx.orig/* /etc/nginx/ + echo "Initialed Nginx config dir" +fi \ No newline at end of file diff --git a/.devcontainer/node-supervisor.sh b/.devcontainer/node-supervisor.sh new file mode 100755 index 00000000..fe918c9e --- /dev/null +++ b/.devcontainer/node-supervisor.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# Configurable variables +SOURCE_FILE=/workspaces/nginx-ui/tmp/main +TARGET_PATH=/usr/local/bin/nginx-ui +CONFIG_FILE=/etc/nginx-ui/app.ini + +# init nginx +./.devcontainer/init-nginx.sh + +LOG_PREFIX="[Supervisor]" + +# Debug initial state +echo "$LOG_PREFIX Starting supervisor with:" +echo "$LOG_PREFIX SOURCE_FILE: $SOURCE_FILE" +echo "$LOG_PREFIX TARGET_PATH: $TARGET_PATH" +echo "$LOG_PREFIX CONFIG_FILE: $CONFIG_FILE" + +# Wait for initial file creation +while [[ ! -f "$SOURCE_FILE" ]]; do + echo "$LOG_PREFIX Waiting for $SOURCE_FILE to be created..." + sleep 1 +done + +# Initial copy and start +echo "$LOG_PREFIX Initial file detected, starting service..." +cp -fv "$SOURCE_FILE" "$TARGET_PATH" +chmod +x "$TARGET_PATH" +pkill -x nginx-ui || echo "$LOG_PREFIX No existing process to kill" +nohup "$TARGET_PATH" -config "$CONFIG_FILE" > /proc/1/fd/1 2>&1 & + +# Use proper field separation for inotify output +inotifywait -m -e close_write,moved_to,create,delete \ + --format "%T|%w%f|%e" \ + --timefmt "%F-%H:%M:%S" \ + "$(dirname "$SOURCE_FILE")" | +while IFS='|' read -r TIME FILE EVENT; do + echo "$LOG_PREFIX [${TIME}] Event: ${EVENT} - ${FILE}" + + # Handle atomic save operations + if [[ "$FILE" =~ .*-tmp-umask$ ]] || [[ "$EVENT" == "DELETE" ]]; then + echo "$LOG_PREFIX Detected build intermediate file, checking main..." + sleep 0.3 # Allow atomic replace completion + + if [[ -f "$SOURCE_FILE" ]]; then + echo "$LOG_PREFIX Valid main file detected after build" + FILE="$SOURCE_FILE" + else + echo "$LOG_PREFIX Main file missing after build operation" + continue + fi + fi + + if [[ "$FILE" == "$SOURCE_FILE" ]]; then + # Stability checks + echo "$LOG_PREFIX File metadata:" + ls -l "$FILE" + file "$FILE" + + # Wait for file stability with retries + retries=5 + while ((retries-- > 0)); do + if file "$FILE" | grep -q "executable"; then + break + fi + echo "$LOG_PREFIX Waiting for valid executable (${retries} retries left)..." + sleep 1 + done + + if ((retries <= 0)); then + echo "$LOG_PREFIX ERROR: File validation failed after 5 retries" + continue + fi + + # Copy and restart service + echo "$LOG_PREFIX Updating service..." + cp -fv "$FILE" "$TARGET_PATH" + chmod +x "$TARGET_PATH" + + echo "$LOG_PREFIX Killing existing process..." + pkill -x nginx-ui || echo "$LOG_PREFIX No process to kill" + + echo "$LOG_PREFIX Starting new process..." + nohup "$TARGET_PATH" -config "$CONFIG_FILE" > /proc/1/fd/1 2>&1 & + echo "$LOG_PREFIX Restart complete. New PID: $(pgrep nginx-ui)" + fi +done diff --git a/.devcontainer/pebble-test/certs/README.md b/.devcontainer/pebble-test/certs/README.md new file mode 100644 index 00000000..7cde76f5 --- /dev/null +++ b/.devcontainer/pebble-test/certs/README.md @@ -0,0 +1,25 @@ +# certs/ + +This directory contains a CA certificate (`pebble.minica.pem`) and a private key +(`pebble.minica.key.pem`) that are used to issue a end-entity certificate (See +`certs/localhost`) for the Pebble HTTPS server. + +To get your **testing code** to use Pebble without HTTPS errors you should +configure your ACME client to trust the `pebble.minica.pem` CA certificate. Your +ACME client should offer a runtime option to specify a list of root CAs that you +can configure to include the `pebble.minica.pem` file. + +**Do not** add this CA certificate to the system trust store or in production +code!!! The CA's private key is **public** and anyone can use it to issue +certificates that will be trusted by a system with the Pebble CA in the trust +store. + +To re-create all of the Pebble certificates run: + + minica -ca-cert pebble.minica.pem \ + -ca-key pebble.minica.key.pem \ + -domains localhost,pebble \ + -ip-addresses 127.0.0.1 + +From the `test/certs/` directory after [installing +MiniCA](https://github.com/jsha/minica#installation) diff --git a/.devcontainer/pebble-test/certs/localhost/README.md b/.devcontainer/pebble-test/certs/localhost/README.md new file mode 100644 index 00000000..efa49ae2 --- /dev/null +++ b/.devcontainer/pebble-test/certs/localhost/README.md @@ -0,0 +1,5 @@ +# certs/localhost + +This directory contains an end-entity (leaf) certificate (`cert.pem`) and +a private key (`key.pem`) for the Pebble HTTPS server. It includes `127.0.0.1` +as an IP address SAN, and `[localhost, pebble]` as DNS SANs. diff --git a/.devcontainer/pebble-test/certs/localhost/cert.pem b/.devcontainer/pebble-test/certs/localhost/cert.pem new file mode 100644 index 00000000..2866a2b4 --- /dev/null +++ b/.devcontainer/pebble-test/certs/localhost/cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDGzCCAgOgAwIBAgIIbEfayDFsBtwwDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMDcx +MjA2MTk0MjEwWjAUMRIwEAYDVQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCbFMW3DXXdErvQf2lCZ0qz0DGEWadDoF0O2neM5mVa +VQ7QGW0xc5Qwvn3Tl62C0JtwLpF0pG2BICIN+DHdVaIUwkf77iBS2doH1I3waE1I +8GkV9JrYmFY+j0dA1SwBmqUZNXhLNwZGq1a91nFSI59DZNy/JciqxoPX2K++ojU2 +FPpuXe2t51NmXMsszpa+TDqF/IeskA9A/ws6UIh4Mzhghx7oay2/qqj2IIPjAmJj +i73kdUvtEry3wmlkBvtVH50+FscS9WmPC5h3lDTk5nbzSAXKuFusotuqy3XTgY5B +PiRAwkZbEY43JNfqenQPHo7mNTt29i+NVVrBsnAa5ovrAgMBAAGjYzBhMA4GA1Ud +DwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0T +AQH/BAIwADAiBgNVHREEGzAZgglsb2NhbGhvc3SCBnBlYmJsZYcEfwAAATANBgkq +hkiG9w0BAQsFAAOCAQEAYIkXff8H28KS0KyLHtbbSOGU4sujHHVwiVXSATACsNAE +D0Qa8hdtTQ6AUqA6/n8/u1tk0O4rPE/cTpsM3IJFX9S3rZMRsguBP7BSr1Lq/XAB +7JP/CNHt+Z9aKCKcg11wIX9/B9F7pyKM3TdKgOpqXGV6TMuLjg5PlYWI/07lVGFW +/mSJDRs8bSCFmbRtEqc4lpwlrpz+kTTnX6G7JDLfLWYw/xXVqwFfdengcDTHCc8K +wtgGq/Gu6vcoBxIO3jaca+OIkMfxxXmGrcNdseuUCa3RMZ8Qy03DqGu6Y6XQyK4B +W8zIG6H9SVKkAznM2yfYhW8v2ktcaZ95/OBHY97ZIw== +-----END CERTIFICATE----- diff --git a/.devcontainer/pebble-test/certs/localhost/key.pem b/.devcontainer/pebble-test/certs/localhost/key.pem new file mode 100644 index 00000000..66be6daa --- /dev/null +++ b/.devcontainer/pebble-test/certs/localhost/key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAmxTFtw113RK70H9pQmdKs9AxhFmnQ6BdDtp3jOZlWlUO0Blt +MXOUML5905etgtCbcC6RdKRtgSAiDfgx3VWiFMJH++4gUtnaB9SN8GhNSPBpFfSa +2JhWPo9HQNUsAZqlGTV4SzcGRqtWvdZxUiOfQ2TcvyXIqsaD19ivvqI1NhT6bl3t +redTZlzLLM6Wvkw6hfyHrJAPQP8LOlCIeDM4YIce6Gstv6qo9iCD4wJiY4u95HVL +7RK8t8JpZAb7VR+dPhbHEvVpjwuYd5Q05OZ280gFyrhbrKLbqst104GOQT4kQMJG +WxGONyTX6np0Dx6O5jU7dvYvjVVawbJwGuaL6wIDAQABAoIBAGW9W/S6lO+DIcoo +PHL+9sg+tq2gb5ZzN3nOI45BfI6lrMEjXTqLG9ZasovFP2TJ3J/dPTnrwZdr8Et/ +357YViwORVFnKLeSCnMGpFPq6YEHj7mCrq+YSURjlRhYgbVPsi52oMOfhrOIJrEG +ZXPAwPRi0Ftqu1omQEqz8qA7JHOkjB2p0i2Xc/uOSJccCmUDMlksRYz8zFe8wHuD +XvUL2k23n2pBZ6wiez6Xjr0wUQ4ESI02x7PmYgA3aqF2Q6ECDwHhjVeQmAuypMF6 +IaTjIJkWdZCW96pPaK1t+5nTNZ+Mg7tpJ/PRE4BkJvqcfHEOOl6wAE8gSk5uVApY +ZRKGmGkCgYEAzF9iRXYo7A/UphL11bR0gqxB6qnQl54iLhqS/E6CVNcmwJ2d9pF8 +5HTfSo1/lOXT3hGV8gizN2S5RmWBrc9HBZ+dNrVo7FYeeBiHu+opbX1X/C1HC0m1 +wJNsyoXeqD1OFc1WbDpHz5iv4IOXzYdOdKiYEcTv5JkqE7jomqBLQk8CgYEAwkG/ +rnwr4ThUo/DG5oH+l0LVnHkrJY+BUSI33g3eQ3eM0MSbfJXGT7snh5puJW0oXP7Z +Gw88nK3Vnz2nTPesiwtO2OkUVgrIgWryIvKHaqrYnapZHuM+io30jbZOVaVTMR9c +X/7/d5/evwXuP7p2DIdZKQKKFgROm1XnhNqVgaUCgYBD/ogHbCR5RVsOVciMbRlG +UGEt3YmUp/vfMuAsKUKbT2mJM+dWHVlb+LZBa4pC06QFgfxNJi/aAhzSGvtmBEww +xsXbaceauZwxgJfIIUPfNZCMSdQVIVTi2Smcx6UofBz6i/Jw14MEwlvhamaa7qVf +kqflYYwelga1wRNCPopLaQKBgQCWsZqZKQqBNMm0Q9yIhN+TR+2d7QFjqeePoRPl +1qxNejhq25ojE607vNv1ff9kWUGuoqSZMUC76r6FQba/JoNbefI4otd7x/GzM9uS +8MHMJazU4okwROkHYwgLxxkNp6rZuJJYheB4VDTfyyH/ng5lubmY7rdgTQcNyZ5I +majRYQKBgAMKJ3RlII0qvAfNFZr4Y2bNIq+60Z+Qu2W5xokIHCFNly3W1XDDKGFe +CCPHSvQljinke3P9gPt2HVdXxcnku9VkTti+JygxuLkVg7E0/SWwrWfGsaMJs+84 +fK+mTZay2d3v24r9WKEKwLykngYPyZw5+BdWU0E+xx5lGUd3U4gG +-----END RSA PRIVATE KEY----- diff --git a/.devcontainer/pebble-test/certs/pebble.minica.key.pem b/.devcontainer/pebble-test/certs/pebble.minica.key.pem new file mode 100644 index 00000000..6a7fcd9d --- /dev/null +++ b/.devcontainer/pebble-test/certs/pebble.minica.key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAuVoGTaFSWp3Y+N5JC8lOdL8wmWpaM73UaNzhYiqA7ZqijzVk +TTtoQvQFDcUwyXKOdWHONrv1ld3z224Us504jjlbZwI5uoquCOZ2WJbRhmXrRgzk +Fq+/MtoFmPkhtO/DLjjtocgyIirVXN8Yl2APvB5brvRfCm6kktYeecsWfW/O3ikf +gdM7tmocwQiBypiloHOjdd5e2g8cWNw+rqvILSUVNLaLpsi23cxnLqVb424wz9dZ +5dO0REg1gSxtf4N5LSb6iGuAVoFNhzIeKzQ+svDg9x8tx/DGOghJS/jDgmxSY1qo +bTsXhcmWVfat5GJ5PQgLkCSjBBrjeBlOrc4VtQIDAQABAoIBAQCAoRoou6C0ZEDU +DScyN8TrvlcS0LzClaWYFFmRT5/jxOG1cr8l3elwNXpgYQ2Hb6mvim2ajHxVQg/e +oxlYwO4jvWhSJzg63c0DPjS5LAlCNO6+0Wlk2RheSPGDhLlAoPeZ10YKdS1dis5B +Qk4Fl1O0IHlOBCcEzV4GzPOfYDI+X6/f4xY7qz1s+CgoIxjIeiG+1/WpZQpYhobY +7CfSDdYDKtksXi7iQkc5earUAHBqZ1gQTq6e5LVm9AjRzENhMctFgcPs5zOjp2ak +PluixrA8LTAfu9wQzvxDkPl0UarZVxCerw6nlAziILpQ+U6PtoPZj49VpntTc+cq +1qjzkbhBAoGBANElJmFWY2X6LgBpszeqt0ZOSbkFg2bC0wHCJrMlRzUMEn83w9e8 +Z2Fqml9eCC5qxJcyxWDVQeoAX6090m0qgP8xNmGdafcVic2cUlrqtkqhhst2OHCO +MCQEB7cdsjiidNNrOgLbQ3i1bYID8BVLf/TDhEbRgvTewDaz6XPdoSIRAoGBAOLg +RuOec5gn50SrVycx8BLFO8AXjXojpZb1Xg26V5miz1IavSfDcgae/699ppSz+UWi +jGMFr/PokY2JxDVs3PyQLu7ahMzyFHr16Agvp5g5kq056XV+uI/HhqLHOWSQ09DS +1Vrj7FOYpKRzge3/AC7ty9Vr35uMiebpm4/CLFVlAoGALnsIJZfSbWaFdLgJCXUa +WDir77/G7T6dMIXanfPJ+IMfVUCqeLa5bxAHEOzP+qjl2giBjzy18nB00warTnGk +y5I/WMBoPW5++sAkGWqSatGtKGi0sGcZUdfHcy3ZXvbT6eyprtrWCuyfUsbXQ5RM +8rPFIQwNA6jBpSak2ohF+FECgYEAn+6IKncNd6pRfnfmdSvf1+uPxkcUJZCxb2xC +xByjGhvKWE+fHkPJwt8c0SIbZuJEC5Gds0RUF/XPfV4roZm/Yo9ldl02lp7kTxXA +XtzxIP8c5d5YM8qD4l8+Csu0Kq9pkeC+JFddxkRpc8A1TIehInPhZ+6mb6mvoMb3 +MW0pAX0CgYATT74RYuIYWZvx0TK4ZXIKTw2i6HObLF63Y6UwyPXXdEVie/ToYRNH +JIxE1weVpHvnHZvVD6D3yGk39ZsCIt31VvKpatWXlWBm875MbBc6kuIGsYT+mSSj +y9TXaE89E5zfL27nZe15QLJ+Xw8Io6PMLZ/jtC5TYoEixSZ9J8v6HA== +-----END RSA PRIVATE KEY----- diff --git a/.devcontainer/pebble-test/certs/pebble.minica.pem b/.devcontainer/pebble-test/certs/pebble.minica.pem new file mode 100644 index 00000000..a69a4c41 --- /dev/null +++ b/.devcontainer/pebble-test/certs/pebble.minica.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDCTCCAfGgAwIBAgIIJOLbes8sTr4wDQYJKoZIhvcNAQELBQAwIDEeMBwGA1UE +AxMVbWluaWNhIHJvb3QgY2EgMjRlMmRiMCAXDTE3MTIwNjE5NDIxMFoYDzIxMTcx +MjA2MTk0MjEwWjAgMR4wHAYDVQQDExVtaW5pY2Egcm9vdCBjYSAyNGUyZGIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC5WgZNoVJandj43kkLyU50vzCZ +alozvdRo3OFiKoDtmqKPNWRNO2hC9AUNxTDJco51Yc42u/WV3fPbbhSznTiOOVtn +Ajm6iq4I5nZYltGGZetGDOQWr78y2gWY+SG078MuOO2hyDIiKtVc3xiXYA+8Hluu +9F8KbqSS1h55yxZ9b87eKR+B0zu2ahzBCIHKmKWgc6N13l7aDxxY3D6uq8gtJRU0 +toumyLbdzGcupVvjbjDP11nl07RESDWBLG1/g3ktJvqIa4BWgU2HMh4rND6y8OD3 +Hy3H8MY6CElL+MOCbFJjWqhtOxeFyZZV9q3kYnk9CAuQJKMEGuN4GU6tzhW1AgMB +AAGjRTBDMA4GA1UdDwEB/wQEAwIChDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYB +BQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADANBgkqhkiG9w0BAQsFAAOCAQEAF85v +d40HK1ouDAtWeO1PbnWfGEmC5Xa478s9ddOd9Clvp2McYzNlAFfM7kdcj6xeiNhF +WPIfaGAi/QdURSL/6C1KsVDqlFBlTs9zYfh2g0UXGvJtj1maeih7zxFLvet+fqll +xseM4P9EVJaQxwuK/F78YBt0tCNfivC6JNZMgxKF59h0FBpH70ytUSHXdz7FKwix +Mfn3qEb9BXSk0Q3prNV5sOV3vgjEtB4THfDxSz9z3+DepVnW3vbbqwEbkXdk3j82 +2muVldgOUgTwK8eT+XdofVdntzU/kzygSAtAQwLJfn51fS1GvEcYGBc1bDryIqmF +p9BI7gVKtWSZYegicA== +-----END CERTIFICATE----- diff --git a/.devcontainer/pebble-test/config/load-generator-config.json b/.devcontainer/pebble-test/config/load-generator-config.json new file mode 100644 index 00000000..e3f97c16 --- /dev/null +++ b/.devcontainer/pebble-test/config/load-generator-config.json @@ -0,0 +1,26 @@ +{ + "plan": { + "actions": [ + "newAccount", + "newOrder", + "fulfillOrder", + "finalizeOrder" + ], + "rate": 10, + "runtime": "10s", + "rateDelta": "1/10s" + }, + "directoryURL": "https://localhost:14000/dir", + "domainBase": "com", + "challengeStrategy": "random", + "httpOneAddrs": [":5002"], + "tlsAlpnOneAddrs": [":5001"], + "dnsAddrs": [":8053"], + "fakeDNS": "127.0.0.1", + "regKeySize": 2048, + "certKeySize": 2048, + "regEmail": "loadtesting@letsencrypt.org", + "maxRegs": 20, + "maxNamesPerCert": 20, + "dontSaveState": true +} diff --git a/.devcontainer/pebble-test/config/pebble-config-external-account-bindings.json b/.devcontainer/pebble-test/config/pebble-config-external-account-bindings.json new file mode 100644 index 00000000..f427d15a --- /dev/null +++ b/.devcontainer/pebble-test/config/pebble-config-external-account-bindings.json @@ -0,0 +1,22 @@ +{ + "pebble": { + "listenAddress": "0.0.0.0:14000", + "managementListenAddress": "0.0.0.0:15000", + "certificate": "/test/certs/localhost/cert.pem", + "privateKey": "/test/certs/localhost/key.pem", + "httpPort": 5002, + "tlsPort": 5001, + "ocspResponderURL": "", + "retryAfter": { + "authz": 3, + "order": 5 + }, + "externalAccountBindingRequired": true, + "externalAccountMACKeys": { + "kid-1": "zWNDZM6eQGHWpSRTPal5eIUYFTu7EajVIoguysqZ9wG44nMEtx3MUAsUDkMTQ12W", + "kid-2": "b10lLJs8l1GPIzsLP0s6pMt8O0XVGnfTaCeROxQM0BIt2XrJMDHJZBM5NuQmQJQH", + "kid-3": "HjudV5qnbreN-n9WyFSH-t4HXuEx_XFen45zuxY-G1h6fr74V3cUM_dVlwQZBWmc" + }, + "certificateValidityPeriod": 157766400 + } +} diff --git a/.devcontainer/pebble-test/config/pebble-config.json b/.devcontainer/pebble-test/config/pebble-config.json new file mode 100644 index 00000000..be398b8c --- /dev/null +++ b/.devcontainer/pebble-test/config/pebble-config.json @@ -0,0 +1,27 @@ +{ + "pebble": { + "listenAddress": "0.0.0.0:14000", + "managementListenAddress": "0.0.0.0:15000", + "certificate": "/test/certs/localhost/cert.pem", + "privateKey": "/test/certs/localhost/key.pem", + "httpPort": 5002, + "tlsPort": 5001, + "ocspResponderURL": "", + "externalAccountBindingRequired": false, + "domainBlocklist": ["blocked-domain.example"], + "retryAfter": { + "authz": 3, + "order": 5 + }, + "profiles": { + "default": { + "description": "The profile you know and love", + "validityPeriod": 7776000 + }, + "shortlived": { + "description": "A short-lived cert profile, without actual enforcement", + "validityPeriod": 518400 + } + } + } +} diff --git a/.devcontainer/start.sh b/.devcontainer/start.sh new file mode 100755 index 00000000..184b6a1e --- /dev/null +++ b/.devcontainer/start.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# install zsh-autosuggestions +git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions + +if ! grep -q "zsh-autosuggestions" ~/.zshrc; then + # add zsh-autosuggestions to plugins list + sed -i "/^plugins=(/s/)/ zsh-autosuggestions)/" ~/.zshrc +fi + +# init nginx config dir +./.devcontainer/init-nginx.sh + +# install app dependencies +echo "Installing app dependencies" +cd app && pnpm install -f +cd .. + +# install docs dependencies +echo "Installing docs dependencies" +cd docs && pnpm install -f +cd .. diff --git a/.dockerignore b/.dockerignore index 8d6e49cd..7ce70ead 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,3 +2,6 @@ app/node_modules .idea tmp + +.devcontainer +.vscode diff --git a/.gitignore b/.gitignore index 6dd5e8bb..e910352e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,8 @@ nginx-ui resources/development/nginx app/.env app/.status_hash -casdoor.pub .idea/deployment.xml .idea/webServers.xml +.devcontainer/go-path +.devcontainer/data +.devcontainer/casdoor.pem diff --git a/.idea/nginx-ui.iml b/.idea/nginx-ui.iml index 5e764c4f..26f9e504 100644 --- a/.idea/nginx-ui.iml +++ b/.idea/nginx-ui.iml @@ -1,6 +1,14 @@ - + + + + + diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..191a1332 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,48 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Start Backend", + "type": "shell", + "command": "air", + "isBackground": true, + "presentation": { + "panel": "new" + }, + "problemMatcher": [] + }, + { + "label": "Start Frontend", + "type": "shell", + "command": "cd app && pnpm dev", + "isBackground": true, + "presentation": { + "panel": "new" + } + }, + { + "label": "Start Documentation", + "type": "shell", + "command": "cd docs && pnpm docs:dev", + "isBackground": true, + "presentation": { + "panel": "new" + }, + "problemMatcher": [] + }, + { + "label": "Start All Services", + "dependsOrder": "parallel", + "dependsOn": [ + "Start Backend", + "Start Frontend", + "Start Documentation" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [] + } + ] +} diff --git a/api/cluster/environment.go b/api/cluster/environment.go index fe6a6be5..d63e9ed7 100644 --- a/api/cluster/environment.go +++ b/api/cluster/environment.go @@ -4,6 +4,10 @@ import ( "crypto/sha256" "encoding/hex" "encoding/json" + "io" + "net/http" + "time" + "github.com/0xJacky/Nginx-UI/api" "github.com/0xJacky/Nginx-UI/internal/analytic" "github.com/0xJacky/Nginx-UI/internal/cluster" @@ -14,9 +18,6 @@ import ( "github.com/spf13/cast" "github.com/uozi-tech/cosy" "gorm.io/gorm" - "io" - "net/http" - "time" ) func GetEnvironment(c *gin.Context) { @@ -151,23 +152,10 @@ func EditEnvironment(c *gin.Context) { } func DeleteEnvironment(c *gin.Context) { - id := cast.ToUint64(c.Param("id")) - envQuery := query.Environment - - env, err := envQuery.FirstByID(id) - if err != nil { - api.ErrHandler(c, err) - return - } - err = envQuery.DeleteByID(env.ID) - if err != nil { - api.ErrHandler(c, err) - return - } - - go analytic.RestartRetrieveNodesStatus() - - c.JSON(http.StatusNoContent, nil) + cosy.Core[model.Environment](c). + ExecutedHook(func(c *cosy.Ctx[model.Environment]) { + go analytic.RestartRetrieveNodesStatus() + }).Destroy() } func LoadEnvironmentFromSettings(c *gin.Context) { diff --git a/app/.env b/app/.env index d9f8542d..18122654 100644 --- a/app/.env +++ b/app/.env @@ -1 +1 @@ -VITE_PROXY_TARGET=http://127.0.0.1:9001 +VITE_PROXY_TARGET=http://127.0.0.1:9000 diff --git a/app/app.go b/app/app.go index 804dd769..f00ad958 100644 --- a/app/app.go +++ b/app/app.go @@ -1,3 +1,5 @@ +//go:build !unembed + package app import ( @@ -6,3 +8,5 @@ import ( //go:embed i18n.json dist/* dist/*/* src/language/* src/language/*/* var DistFS embed.FS + +var VersionPath = "dist/version.json" diff --git a/app/app_unembed.go b/app/app_unembed.go new file mode 100644 index 00000000..43733be7 --- /dev/null +++ b/app/app_unembed.go @@ -0,0 +1,10 @@ +//go:build unembed + +package app + +import "embed" + +//go:embed i18n.json src/language/* src/language/*/* src/version.json +var DistFS embed.FS + +var VersionPath = "src/version.json" diff --git a/app/src/App.vue b/app/src/App.vue index 5c637708..747a9c1a 100644 --- a/app/src/App.vue +++ b/app/src/App.vue @@ -4,7 +4,6 @@ import gettext from '@/gettext' import { useSettingsStore } from '@/pinia' import { theme } from 'ant-design-vue' import en_US from 'ant-design-vue/es/locale/en_US' - import zh_CN from 'ant-design-vue/es/locale/zh_CN' import zh_TW from 'ant-design-vue/es/locale/zh_TW' // This starter template is using Vue 3