diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8fe0238 --- /dev/null +++ b/.env.example @@ -0,0 +1,148 @@ +# Erebrus Node v2 — environment template +# +# Where to put this file: +# Host install → /etc/erebrus/erebrus.env (systemd EnvironmentFile) +# Docker install → /opt/erebrus/.env (docker compose --env-file) +# Local dev → copy to .env in repo root +# +# Never commit real secrets (MNEMONIC, NODE_KEY, EREBRUS_ORG_ENROLLMENT_SECRET). + +# ============================================================================= +# REQUIRED — all runs (Validate() hard-fails if missing) +# ============================================================================= +MNEMONIC= # BIP39 12-word phrase: wallet, PeerID, DID +WG_ENDPOINT_HOST= # Public IP or DNS name clients dial + +# ============================================================================= +# REQUIRED — release mode only (not enforced at boot; peer API fails without it) +# ============================================================================= +# RUNTYPE=release +# NODE_KEY= # Per-node bearer for /api/v2/peers/* (gateway mints if unset) + +# ============================================================================= +# DEBUG profile — local smoke / gateway dev (copy these values) +# ============================================================================= +# RUNTYPE=debug +# SERVER=127.0.0.1 +# HTTP_PORT=9080 +# MNEMONIC=abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about +# WG_ENDPOINT_HOST=127.0.0.1 +# GATEWAY_URL=http://127.0.0.1:8080 +# ENABLE_STEALTH=false +# STATE_DIR=./var/lib/erebrus +# NODE_API_TOKEN= # leave unset — peer API is open in debug only + +# ============================================================================= +# RELEASE profile — production node (installer container path; public by default) +# ============================================================================= +RUNTYPE=release +EREBRUS_ACCESS=public +EREBRUS_MODE=container +EREBRUS_NETWORK_PROFILE=bridge +SERVER=0.0.0.0 +HTTP_PORT=9080 +NODE_NAME=erebrus-node +REGION=unknown +GATEWAY_URL=https://gateway.erebrus.io +EREBRUS_ORG_ENROLLMENT_SECRET= +NODE_KEY= +ENABLE_STEALTH=true +STEALTH_TCP_PORT=8443 +STEALTH_UDP_PORT=4443 +STATE_DIR=/var/lib/erebrus + +# ============================================================================= +# RELEASE profile — public access node (any deploy; host for app-hosting DNS) +# ============================================================================= +# EREBRUS_ACCESS=public +# EREBRUS_MODE=container # or host for bare metal + wildcard DNS +# EREBRUS_NETWORK_PROFILE=bridge # host-network when EREBRUS_MODE=host +# STEALTH_TCP_PORT=443 +# STEALTH_UDP_PORT=443 +# ENABLE_APP_HOSTING=true +# APP_WILDCARD_DOMAIN=apps.example.com +# PUBLIC_DOMAIN=apps.example.com +# WILDCARD_DOMAIN=*.apps.example.com +# PUBLIC_GATEWAY_ENABLED=true + +# ============================================================================= +# Deploy + access (independent knobs) +# ============================================================================= +# EREBRUS_MODE=container|host how the node runs (Docker vs bare metal) +# EREBRUS_ACCESS=private|public gateway directory visibility (org controls private access) +# EREBRUS_NETWORK_PROFILE=bridge|host-network|native + +# ============================================================================= +# API bind +# ============================================================================= +# API_BIND_ADDR= # overrides SERVER when set +# UNSAFE_PUBLIC_API=false # acknowledge risk when binding 0.0.0.0 publicly + +# ============================================================================= +# Gateway integration (optional; empty GATEWAY_URL disables control plane) +# ============================================================================= +# GATEWAY_URL=https://gateway.erebrus.io +# GATEWAY_AUTO_REGISTER=true +# EREBRUS_ORG_ENROLLMENT_SECRET= # from org create/GET (owner/admin); required for auto-register +# WALLET_CHAIN=sol # sol | evm — signs machine enrollment challenge +# API_PUBLIC_URL= # gateway peer provision URL (default: http://WG_ENDPOINT_HOST:HTTP_PORT) +# NODE_KEY= # optional pre-register bearer; gateway mints if empty (persisted) +# GATEWAY_PUBLIC_KEY= # optional override; normally saved at registration +# NODE_ID= # persisted after registration; skip auto-register if set with NODE_TOKEN +# NODE_TOKEN= +# GATEWAY_PEER_MULTIADDR= # libp2p bootstrap (DHT advertise only) + +# ============================================================================= +# WireGuard +# ============================================================================= +WG_CONF_DIR=/etc/wireguard +WG_INTERFACE_NAME=wg0 +WG_ENDPOINT_PORT=51820 # alias: WG_PORT +WG_IPv4_SUBNET=10.0.0.1/16 +WG_DNS=1.1.1.1 +WG_POST_UP=iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE +WG_POST_DOWN=iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE + +# ============================================================================= +# Stealth carriers (sing-box) — DPI-resistant fallbacks when WG UDP is blocked +# Canonical ports: STEALTH_TCP_PORT / STEALTH_UDP_PORT +# Legacy aliases: VLESS_PORT / HYSTERIA2_PORT (same values if you prefer old names) +# ============================================================================= +# ENABLE_STEALTH=true +# STEALTH_TCP_PORT=8443 # VLESS+REALITY (tcp); gateway production: 443 +# STEALTH_UDP_PORT=4443 # Hysteria2 (udp); gateway production: 443 +REALITY_SERVER_NAMES=www.microsoft.com +REALITY_HANDSHAKE_SERVER= +HYSTERIA2_OBFS_PASSWORD= +ENABLE_TUIC=false + +# ============================================================================= +# Public edge / app hosting (gateway mode, opt-in) +# ============================================================================= +# ENABLE_APP_HOSTING=false +# APP_WILDCARD_DOMAIN= +# PUBLIC_DOMAIN= +# WILDCARD_DOMAIN= +# PUBLIC_GATEWAY_ENABLED=false +# PUBLIC_HTTP_PORT=80 +# PUBLIC_HTTPS_PORT=443 +# AUTO_TLS=true + +# ============================================================================= +# Private DNS (opt-in; backed by service registry) +# ============================================================================= +PRIVATE_DNS_ENABLED=false +PRIVATE_DNS_DOMAIN=ere +PRIVATE_DNS_ADDR=10.0.0.1 +UPSTREAM_DNS=1.1.1.1 +DNS_QUERY_LOGS=false + +# ============================================================================= +# Registrar (on-chain; noop in v2.0) +# ============================================================================= +CHAIN_REGISTRATION=off # off | solana (future) + +# ============================================================================= +# Docker only — set by docker-compose.yml; do not set on host/systemd installs +# ============================================================================= +# LOAD_CONFIG_FILE=TRUE \ No newline at end of file diff --git a/.github/workflows/build-file.yml b/.github/workflows/build-file.yml deleted file mode 100644 index dbcd42c..0000000 --- a/.github/workflows/build-file.yml +++ /dev/null @@ -1,52 +0,0 @@ -name: "Docker Image Build and Publish (Non-Main)" - -on: - push: - branches-ignore: - - "main" - -env: - REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository }} - -jobs: - build: - name: Build and Push Docker Image - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - - name: Setup Docker buildx - uses: sigstore/cosign-installer@v3.1.1 - - name: Check install! - run: cosign version - - - name: Login to GitHub Container Registry - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.GHCR_USERNAME }} - password: ${{ secrets.GHCR_TOKEN }} - - - name: Downcase Repository Name - run: echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - - - name: Build and Push Docker Image - run: | - export CURRENT_BRANCH=${GITHUB_REF#refs/heads/} - export TAG=$CURRENT_BRANCH - export GITHUB_REF_IMAGE=${{ env.REGISTRY }}/$REPO:$GITHUB_SHA - export GITHUB_BRANCH_IMAGE=${{ env.REGISTRY }}/$REPO:$TAG - docker build -t $GITHUB_REF_IMAGE -t $GITHUB_BRANCH_IMAGE . - echo "Pushing Image to GitHub Container Registry" - docker push $GITHUB_REF_IMAGE - docker push $GITHUB_BRANCH_IMAGE - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3bc66e8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,36 @@ +name: ci + +on: + push: + branches: [main, v2] + pull_request: + +jobs: + go: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + # with_reality_server enables the sing-box REALITY server (stealth carrier); + # required or the stealth start-test fails. Keep in sync with Makefile/Dockerfile. + - run: go vet -tags with_reality_server ./... + - run: go build -tags with_reality_server ./... + - run: go test -tags with_reality_server ./... + - uses: golangci/golangci-lint-action@v6 + continue-on-error: true + with: + version: latest + args: --build-tags with_reality_server + + gitleaks: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: gitleaks/gitleaks-action@v2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml deleted file mode 100644 index 628e68c..0000000 --- a/.github/workflows/docker-publish.yml +++ /dev/null @@ -1,115 +0,0 @@ -name: "[MAIN] Docker Image Build and Publish" - -on: - push: - branches: ["main"] - -env: - # Use docker.io for Docker Hub if empty - REGISTRY: ghcr.io - # github.repository as / - IMAGE_NAME: ${{ github.repository }} - -jobs: - deploy: - name: Deploy to GitHub Container Registry - runs-on: ubuntu-latest - - permissions: - contents: read - packages: write - # This is used to complete the identity challenge - # with sigstore/fulcio when running outside of PRs. - id-token: write - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Workaround: https://github.com/docker/build-push-action/issues/461 - - name: Setup Docker buildx - uses: sigstore/cosign-installer@v3.1.1 - - name: Check install! - run: cosign version - - - name: Login to GitHub Container Registry ${{ env.REGISTRY }} - if: github.event_name != 'pull_request' - uses: docker/login-action@v1 - with: - registry: ${{ env.REGISTRY }} - username: ${{ secrets.GHCR_USERNAME }} - password: ${{ secrets.GHCR_TOKEN }} - - - name: downcase REPO - run: echo "REPO=${GITHUB_REPOSITORY,,}" >>${GITHUB_ENV} - - - name: Build and Push Docker Image - run: | - export CURRENT_BRANCH=${GITHUB_REF#refs/heads/} - export TAG=$([[ $CURRENT_BRANCH == "main" ]] && echo $CURRENT_BRANCH || echo "latest") - export GITHUB_REF_IMAGE=ghcr.io/netsepio/erebrus:$GITHUB_SHA - export GITHUB_BRANCH_IMAGE=ghcr.io/netsepio/erebrus:$TAG - docker build -t $GITHUB_REF_IMAGE -t $GITHUB_BRANCH_IMAGE . - echo "Pushing Image to GitHub Container Registry" - docker push $GITHUB_REF_IMAGE - docker push $GITHUB_BRANCH_IMAGE - - name: Deploy on US server - if: github.ref == 'refs/heads/prod' - uses: appleboy/ssh-action@v0.1.7 - with: - host: ${{ secrets.DEV_REMOTE_SERVER_ADDRESS_US01 }} - username: ${{ secrets.DEV_SERVER_USERNAME }} - key: ${{ secrets.DEV_REMOTE_SERVER_KEY }} - port: ${{ secrets.DEV_SSH_PORT }} - script: | - pwd - cd erebrus - docker stop erebrus && docker rm erebrus && docker image rm ghcr.io/netsepio/erebrus:main - echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin - docker pull ghcr.io/netsepio/erebrus:main - docker run -d -p 9080:9080/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" --restart unless-stopped -v /home/ubuntu/erebrus/wireguard/:/etc/wireguard/ --name erebrus --env-file .env ghcr.io/netsepio/erebrus:main - - name: Deploy on EU server - if: github.ref == 'refs/heads/prod' - uses: appleboy/ssh-action@v0.1.7 - with: - host: ${{ secrets.DEV_REMOTE_SERVER_ADDRESS_EU01 }} - username: ${{ secrets.DEV_SERVER_USERNAME }} - key: ${{ secrets.DEV_REMOTE_SERVER_KEY }} - port: ${{ secrets.DEV_SSH_PORT }} - script: | - pwd - cd erebrus - docker stop erebrus && docker rm erebrus && docker image rm ghcr.io/netsepio/erebrus:main - echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin - docker pull ghcr.io/netsepio/erebrus:main - docker run -d -p 9080:9080/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" --restart unless-stopped -v /home/ubuntu/erebrus/wireguard/:/etc/wireguard/ --name erebrus --env-file .env ghcr.io/netsepio/erebrus:main - - name: Deploy on CA server - if: github.ref == 'refs/heads/prod' - uses: appleboy/ssh-action@v0.1.7 - with: - host: ${{ secrets.DEV_REMOTE_SERVER_ADDRESS_CA01 }} - username: ${{ secrets.DEV_SERVER_USERNAME }} - key: ${{ secrets.DEV_REMOTE_SERVER_KEY }} - port: ${{ secrets.DEV_SSH_PORT }} - script: | - pwd - cd erebrus - docker stop erebrus && docker rm erebrus && docker image rm ghcr.io/netsepio/erebrus:main - echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin - docker pull ghcr.io/netsepio/erebrus:main - docker run -d -p 9080:9080/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" --restart unless-stopped -v /home/ubuntu/erebrus/wireguard/:/etc/wireguard/ --name erebrus --env-file .env ghcr.io/netsepio/erebrus:main - - name: Deploy on SG server - if: github.ref == 'refs/heads/prod' - uses: appleboy/ssh-action@v0.1.7 - with: - host: ${{ secrets.DEV_REMOTE_SERVER_ADDRESS_SG01 }} - username: ${{ secrets.DEV_SERVER_USERNAME }} - key: ${{ secrets.DEV_REMOTE_SERVER_KEY }} - port: ${{ secrets.DEV_SSH_PORT }} - script: | - pwd - cd erebrus - docker stop erebrus && docker rm erebrus && docker image rm ghcr.io/netsepio/erebrus:main - echo ${{ secrets.GHCR_TOKEN }} | docker login ghcr.io -u ${{ secrets.GHCR_USERNAME }} --password-stdin - docker pull ghcr.io/netsepio/erebrus:main - docker run -d -p 9080:9080/tcp -p 9002:9002/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" --restart unless-stopped -v /home/ubuntu/erebrus/wireguard/:/etc/wireguard/ --name erebrus --env-file .env ghcr.io/netsepio/erebrus:main diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..3078f02 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,47 @@ +name: docker + +# Build and publish the node image to GHCR. The Dockerfile sets +# -tags with_reality_server, so the stealth REALITY server is compiled in. +on: + push: + branches: [main, v2] + tags: ["v*"] + +env: + REGISTRY: ghcr.io + IMAGE: ghcr.io/netsepio/erebrus + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.IMAGE }} + tags: | + type=ref,event=branch + type=ref,event=tag + type=sha + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/erebrus-release.yml b/.github/workflows/erebrus-release.yml deleted file mode 100644 index a4774e0..0000000 --- a/.github/workflows/erebrus-release.yml +++ /dev/null @@ -1,67 +0,0 @@ -name: Build Erebrus Binary - -on: - push: - branches: - - main - - node-features - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - arch: [amd64, arm64] - os: [linux, darwin] - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version: stable - - - name: Set Environment Variables - run: | - echo "GOOS=${{ matrix.os }}" >> $GITHUB_ENV - echo "GOARCH=${{ matrix.arch }}" >> $GITHUB_ENV - echo "BINARY_NAME=erebrus-${{ matrix.os }}-${{ matrix.arch }}" >> $GITHUB_ENV - - - name: Build Erebrus Binary - run: | - go mod tidy - CGO_ENABLED=0 go build -o $BINARY_NAME . - - - name: Upload Artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ env.BINARY_NAME }} - path: ${{ env.BINARY_NAME }} - - release: - needs: build - runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/node-features' - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Get Commit SHA - id: get_sha - run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - - name: Download All Artifacts - uses: actions/download-artifact@v4 - with: - path: ./artifacts - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.sha }} - files: ./artifacts/** - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..e2913c6 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: release + +# Build static Linux node binaries and attach them to a GitHub Release on tag. +# Nodes are Linux-only; the binary embeds the sing-box REALITY server. +on: + push: + tags: ["v*"] + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + arch: [amd64, arm64] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + - name: Build + env: + GOOS: linux + GOARCH: ${{ matrix.arch }} + CGO_ENABLED: "0" + run: | + BIN="erebrus-linux-${{ matrix.arch }}" + go build -tags with_reality_server \ + -ldflags "-s -w -X github.com/NetSepio/erebrus/internal/config.Version=${GITHUB_REF_NAME}" \ + -o "$BIN" ./cmd/erebrus + sha256sum "$BIN" > "$BIN.sha256" + - uses: actions/upload-artifact@v4 + with: + name: erebrus-linux-${{ matrix.arch }} + path: erebrus-linux-${{ matrix.arch }}* + + release: + needs: build + runs-on: ubuntu-latest + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + - uses: softprops/action-gh-release@v2 + with: + files: dist/** + generate_release_notes: true diff --git a/.gitignore b/.gitignore index a172d6d..54e502b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,10 +26,10 @@ *.sln *.sw? -#executable binary +#executable binary (root build output only — must not match the cmd/erebrus pkg) -erebrus -erebrus-linux-x64 +/erebrus +/erebrus-linux-x64 # bin folder diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 0000000..bbf607a --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,12 @@ +# gitleaks config — Erebrus node +# Uses the default gitleaks ruleset; this file only adds allowlists. +[extend] +useDefault = true + +[allowlist] +description = "templates and docs with placeholder values" +paths = [ + '''\.env\.example$''', + '''docs/.*''', + '''\.sample-env$''', +] diff --git a/.sample-env b/.sample-env deleted file mode 100644 index 7ee5b08..0000000 --- a/.sample-env +++ /dev/null @@ -1,57 +0,0 @@ -#Application Parameters -LOAD_CONFIG_FILE=false -RUNTYPE=debug -SERVER=0.0.0.0 -HTTP_PORT=9080 -GRPC_PORT=9090 -REGION=EU - -# PASETO Specifications -PASETO_EXPIRATION_IN_HOURS=168 -AUTH_EULA=I Accept the Erebrus Terms of Service https://erebrus.io/terms.html for accessing the application. -SIGNED_BY=Erebrus -FOOTER=Erebrus 2024 - -#Node Specifications -HOST_IP=ip_addr -DOMAIN=http://ip_addr:9080/ -NODE_NAME= -MNEMONIC= -CHAIN_NAME= -NODE_TYPE= -NODE_CONFIG= - -#Gateway Specifications -GATEWAY_WALLET=0x0 -GATEWAY_DOMAIN=https://gateway.erebrus.io/ -GATEWAY_PEERID=/ip4/52.14.92.177/tcp/9001/p2p/12D3KooWJSMKigKLzehhhmppTjX7iQprA7558uU52hqvKqyjbELf - -#Wireguard Specifications -WG_CONF_DIR= -WG_CLIENTS_DIR= -WG_INTERFACE_NAME=wg0.conf -WG_ENDPOINT_HOST=ip_addr -WG_ENDPOINT_PORT=51820 -WG_IPv4_SUBNET=10.0.0.1/16 -WG_IPv6_SUBNET=fd9f:0000::10:0:0:1/64 -WG_DNS=1.1.1.1 -WG_ALLOWED_IP_1=0.0.0.0/0 -WG_ALLOWED_IP_2=::/0 -WG_PRE_UP=echo WireGuard PreUp -WG_POST_UP=iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE -WG_PRE_DOWN=echo WireGuard PreDown -WG_POST_DOWN=iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE - -#Service Specifications -SERVICE_CONF_DIR=./erebrus -CADDY_CONF_DIR=/etc/caddy -CADDY_INTERFACE_NAME=Caddyfile - -# AI Agent Specifications -EREBRUS_DOMAIN= -DOCKER_IMAGE_AGENT="ghcr.io/netsepio/cyrene" - -#Peaq Integration -CONTRACT_ADDRESS=0x291eC3328b56d5ECebdF993c3712a400Cb7569c3 -RPC_URL=https://evm.peaq.network -NODE_ACCESS= diff --git a/Dockerfile b/Dockerfile index 70a7540..6db95a7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,32 @@ -#LABEL Maintainer Punarv Name punarv@netsepio.com +# syntax=docker/dockerfile:1 FROM golang:alpine AS build-app RUN apk update && apk add --no-cache git WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download COPY . . -RUN go build -ldflags "-X main.version=1.1.1-alpha -X main.codeHash=$(git rev-parse HEAD)" -o erebrus . +# with_reality_server enables the sing-box REALITY server used by the VLESS +# stealth carrier; keep in sync with the Makefile. +RUN go build -tags "with_reality_server" \ + -ldflags "-X github.com/NetSepio/erebrus/internal/config.Version=2.0.0-$(git rev-parse --short HEAD 2>/dev/null || echo dev)" \ + -o erebrus ./cmd/erebrus FROM alpine:latest -RUN apk update && apk add --no-cache git WORKDIR /app +RUN apk update && apk add --no-cache bash wireguard-tools iptables ip6tables bind-tools ca-certificates COPY --from=build-app /app/erebrus . -COPY --from=build-app /app/webapp ./webapp -COPY wg-watcher.sh . -RUN chmod +x ./erebrus ./wg-watcher.sh -RUN apk update && apk add --no-cache bash openresolv bind-tools wireguard-tools gettext inotify-tools iptables -ENV LOAD_CONFIG_FILE=$LOAD_CONFIG_FILE RUNTYPE=$RUNTYPE SERVER=$SERVER HTTP_PORT=$HTTP_PORT GRPC_PORT=$GRPC_PORT GATEWAY_DOMAIN=$GATEWAY_DOMAIN -ENV NODE_NAME=$NODE_NAME REGION=$REGION DOMAIN=$DOMAIN REGION_NAME=$REGION_NAME REGION_CODE=$REGION_CODE -ENV WG_CONF_DIR=$WG_CONF_DIR WG_CLIENTS_DIR=$WG_CLIENTS_DIR WG_KEYS_DIR=$WG_KEYS_DIR WG_INTERFACE_NAME=$WG_INTERFACE_NAME -ENV WG_ENDPOINT_HOST=$WG_ENDPOINT_HOST WG_ENDPOINT_PORT=$WG_ENDPOINT_PORT WG_IPv4_SUBNET=$WG_IPv4_SUBNET WG_IPv6_SUBNET=$WG_IPv6_SUBNET -ENV WG_DNS=$WG_DNS WG_ALLOWED_IP_1=$WG_ALLOWED_IP_1 WG_ALLOWED_IP_2=$WG_ALLOWED_IP_2 -ENV WG_PRE_UP=$WG_PRE_UP WG_POST_UP=$WG_POST_UP WG_PRE_DOWN=$WG_PRE_DOWN WG_POST_DOWN=$WG_POST_DOWN -ENV NODE_CONFIG=$NODE_CONFIG NODE_TYPE=$NODE_TYPE -RUN echo $'#!/usr/bin/env bash\n\ - set -eo pipefail\n\ - /app/erebrus &\n\ - ./wg-watcher.sh\n\ - sleep infinity' > /app/start.sh && chmod +x /app/start.sh -ENTRYPOINT ["/app/start.sh"] \ No newline at end of file +RUN chmod +x ./erebrus + +# HTTP API +EXPOSE 9080/tcp +# WireGuard fast path +EXPOSE 51820/udp +# Stealth carriers: VLESS+REALITY (TCP) and Hysteria2 (UDP/QUIC) +EXPOSE 8443/tcp +EXPOSE 4443/udp + +# Node state (SQLite + generated secrets/keys) should be a mounted volume. +VOLUME ["/var/lib/erebrus", "/etc/wireguard"] + +ENTRYPOINT ["/app/erebrus"] diff --git a/Makefile b/Makefile index b371814..12f205b 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,35 @@ -swagger: - GO111MODULE=off swagger generate spec -o ./docs/swagger.yml --scan-models - -markdown: - swagger generate markdown -f ./docs/swagger.yml --output=./docs/docs.md - -# Binary name +# Binary name and entrypoint BINARY_NAME=erebrus +PKG=./cmd/erebrus # Go parameters GOCMD=go GOBUILD=$(GOCMD) build GOINSTALL=$(GOCMD) install +GOTEST=$(GOCMD) test +GOVET=$(GOCMD) vet GOCLEAN=$(GOCMD) clean -# Build the project +# Build tags. with_reality_server enables the sing-box REALITY *server* used by +# the VLESS stealth carrier; without it REALITY inbounds fail to start at +# runtime. Keep this in sync with the Dockerfile. +BUILD_TAGS=with_reality_server + +# Build the node binary build: - $(GOBUILD) -o $(BINARY_NAME) -v + $(GOBUILD) -tags "$(BUILD_TAGS)" -o $(BINARY_NAME) -v $(PKG) # Install the binary install: - $(GOINSTALL) + $(GOINSTALL) -tags "$(BUILD_TAGS)" $(PKG) + +# Run vet across all packages (stealth needs the tag to type-check fully) +vet: + $(GOVET) -tags "$(BUILD_TAGS)" ./... + +# Run the test suite +test: + $(GOTEST) -tags "$(BUILD_TAGS)" ./... # Clean build files clean: @@ -29,4 +39,4 @@ clean: # Build and install all: build install -.PHONY: build install clean all \ No newline at end of file +.PHONY: build install vet test clean all diff --git a/README.md b/README.md index 989e5a5..f606d83 100644 --- a/README.md +++ b/README.md @@ -1,31 +1,59 @@ - # Erebrus -Erebrus, a decentralized VPN, that ensures your privacy, security, and transparent data practices. It's open-source, ensuring no hidden data tracking or logging. Complementing it, our DePIN initiative lets anyone worldwide participate as a node, contributing either physical servers or virtual machines and earning incentives. With this, we pave the way for safer, decentralized Wi-Fi hotspots, making unreliable public Wi-Fi a thing of the past. +Erebrus is a decentralized VPN (DePIN) that protects your privacy and security with no hidden tracking or logging. Anyone worldwide can run a node — on a physical server or a VM — and earn incentives, helping build a censorship-resistant network. -For more details visit [here](https://erebrus.io). +For more details visit [erebrus.io](https://erebrus.io). ## Features -- Easy Client and Server management. -- LibP2P integration for peer discovery -- Supports REST and gRPC (QUIC upcoming). -- Email VPN configuration to clients easily. +- WireGuard fast path with a SQLite-backed, race-free peer store. +- **Stealth carriers** for restrictive networks: when WireGuard's UDP is throttled or DPI-blocked, the same tunnel is wrapped in an embedded sing-box transport that looks like ordinary internet traffic: + - **VLESS + REALITY** (`:8443/tcp`) — presents as a real TLS session to a borrowed SNI. + - **Hysteria2** (`:4443/udp`) — QUIC/HTTP3 with optional Salamander obfuscation. +- libp2p identity + DID (`did:erebrus:`) derived from a mnemonic. +- HTTP REST API (`/api/v2`) and Prometheus `/metrics`. +- Optional App-Hosting: expose a VPN-connected app to the public internet (host mode). + +## Install a node + +Linux only (x86_64 / arm64). A node needs a **static, internet-routable public IP**, real bandwidth, and open ports (`9080/tcp`, `51820/udp`, `8443/tcp`, `4443/udp`). The installer verifies all three. + +```bash +curl -fsSL https://erebrus.io/install.sh | bash +``` + +You'll be asked to pick a mode: + +- **docker** (recommended) — zero-hassle: WireGuard + stealth carriers in a container. +- **host** — bare-metal via systemd; additionally supports **App-Hosting** (needs a wildcard DNS record, e.g. `*.apps.example.com → `, so the gateway can mint per-app CNAMEs). + +Non-interactive example: + +```bash +curl -fsSL https://erebrus.io/install.sh | \ + MNEMONIC="..." WG_ENDPOINT_HOST="vpn.example.com" bash -s -- --mode docker --yes +``` -## Deploy Erebrus Node +## Build from source -- Refer docs here [setup docs](https://github.com/NetSepio/erebrus/blob/main/docs/node.md). +The REALITY server requires a build tag, wired into the Makefile and Dockerfile: -## Get Started +```bash +make build # go build -tags with_reality_server -o erebrus ./cmd/erebrus +make test +``` -To deploy Erebrus, you need to follow the documentation given below, +## Dashboard -- First you will need to setup Wireguard and Watcher, for that use [setup docs](https://github.com/NetSepio/erebrus/blob/main/docs/setup.md). -- After setup , you will have choices for deploying Erebrus. Refer [Deploy docs](https://github.com/NetSepio/erebrus/blob/main/docs/deploy.md) +Every node serves a local dashboard at `http://:9080/` — intro, live stats +(connected users, bandwidth, throughput, uptime), and the API reference. It reads +only public, coarse aggregates (`/api/v2/status`, `/api/v2/stats`). -## API Docs +## Docs -Download Postman collection for Erebrus from [here](https://github.com/NetSepio/erebrus/blob/main/docs/Erebrus.postman_collection.json). There are two types of docs available: +- [docs/NODE.md](docs/NODE.md) — running, configuring, and managing a node (ports, env reference, troubleshooting). +- [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) — package layout and the stealth carrier topology. +- [docs/SECURITY-AUDIT.md](docs/SECURITY-AUDIT.md) — data-capture inventory, threat model, and operator hardening. +- [docs/node-api.openapi.yaml](docs/node-api.openapi.yaml) — the `/api/v2` REST contract. -- You can refer docs from github [here](https://github.com/NetSepio/erebrus/blob/main/docs/docs.md) -- There is a web based doc available on Erebrus route /docs.You can refer it after deployment +The REST surface lives under `/api/v2` (status, stats, peers CRUD, credentials); node status is public at `GET /api/v2/status`. diff --git a/api/api.go b/api/api.go deleted file mode 100644 index 09fcecd..0000000 --- a/api/api.go +++ /dev/null @@ -1,14 +0,0 @@ -package api - -import ( - v1 "github.com/NetSepio/erebrus/api/v1" - "github.com/gin-gonic/gin" -) - -// ApplyRoutes Setup API EndPoints -func ApplyRoutes(r *gin.Engine) { - api := r.Group("/api") - { - v1.ApplyRoutes(api) - } -} diff --git a/api/v1/agents/agents.go b/api/v1/agents/agents.go deleted file mode 100644 index 953e7d4..0000000 --- a/api/v1/agents/agents.go +++ /dev/null @@ -1,616 +0,0 @@ -package agents - -import ( - "encoding/json" - "fmt" - "log" - "net" - "net/http" - "os" - "os/exec" - "path/filepath" - "strings" - "time" - - "github.com/NetSepio/erebrus/api/v1/middleware" - caddy "github.com/NetSepio/erebrus/api/v1/service" - "github.com/NetSepio/erebrus/model" - "github.com/gin-gonic/gin" -) - -// ApplyRoutes applies router to gin Router -func ApplyRoutes(r *gin.RouterGroup) { - g := r.Group("/agents") - { - g.POST("", addAgent) - g.GET("", getAgents) - g.GET(":agentId", getAgent) - g.DELETE(":agentId", deleteAgent) - g.PATCH("/manage/:agentId", manageAgent) - } -} - -var agentsFilePath string - -func init() { - // Initialize the agentsFilePath during package initialization - homeDir, err := os.UserHomeDir() - if err != nil { - log.Fatalf("Error getting home directory: %v", err) - } - // Create the "erebrus" folder(SERVICE_CONF_DIR) inside the home directory if it doesn't exist - erebrusDir := filepath.Join(homeDir, "erebrus") - // err = os.MkdirAll(erebrusDir, os.ModePerm) - // if err != nil { - // log.Fatalf("Error creating erebrus directory: %v", err) - // } - - // Set the path for agents.json inside the erebrus folder - agentsFilePath = filepath.Join(erebrusDir, "agents.json") - - monitorAndRecoverAgents() - -} - -// Load agents from file -func loadAgents() ([]model.Agent, error) { - file, err := os.Open(agentsFilePath) - if err != nil { - if os.IsNotExist(err) { - return []model.Agent{}, nil - } - return nil, err - } - defer file.Close() - - var agents []model.Agent - if err := json.NewDecoder(file).Decode(&agents); err != nil { - return nil, err - } - return agents, nil -} - -// Save agents to file -func saveAgents(newAgent model.Agent) error { - // Load existing agents - agents, err := loadAgents() - if err != nil { - return err - } - - agents = append(agents, newAgent) - - file, err := os.Create(agentsFilePath) - if err != nil { - return err - } - defer file.Close() - - // Encode the updated agents list into the file with indentation - encoder := json.NewEncoder(file) - encoder.SetIndent("", " ") - return encoder.Encode(agents) -} - -// GET /agents -func getAgents(c *gin.Context) { - agents, err := loadAgents() - if err != nil { - log.Printf("Error loading agents: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agents"}) - return - } - c.JSON(http.StatusOK, gin.H{"agents": agents}) -} - -// GET /agents/:agentId -func getAgent(c *gin.Context) { - agentID := c.Param("agentId") - if agentID == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID is required"}) - return - } - - agents, err := loadAgents() - if err != nil { - log.Printf("Error loading agents: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agents"}) - return - } - - for _, agent := range agents { - if strings.EqualFold(agent.ID, agentID) { - c.JSON(http.StatusOK, gin.H{ - "agent": gin.H{ - "id": agent.ID, - "name": agent.Name, - "clients": agent.Clients, - "domain": agent.Domain, - "status": agent.Status, - "avatar_img": agent.AvatarImg, - "cover_img": agent.CoverImg, - "voice_model": agent.VoiceModel, - "organization": agent.Organization, - }, - }) - return - } - } - - c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"}) -} - -// Function to find an available port on the host machine -func getAvailablePort() (int, error) { - listener, err := net.Listen("tcp", ":0") - if err != nil { - return 0, err - } - defer listener.Close() - return listener.Addr().(*net.TCPAddr).Port, nil -} - -// POST /agents -func addAgent(c *gin.Context) { - log.Println("Received request to add an agent.") - - // Get additional fields from form data - avatarImg := c.PostForm("avatar_img") - coverImg := c.PostForm("cover_img") - voiceModel := c.PostForm("voice_model") - organization := c.PostForm("organization") - - // Retrieve the file from the request - file, err := c.FormFile("character_file") - if err != nil { - log.Printf("Error retrieving character file: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Failed to retrieve character file"}) - return - } - - log.Printf("Uploaded file: %s", file.Filename) - - // Read the saved file - content, err := file.Open() - if err != nil { - log.Printf("Error opening file: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read file"}) - return - } - - // Decode the JSON content - var character model.CharacterFile - - if err := json.NewDecoder(content).Decode(&character); err != nil { - log.Printf("Invalid JSON format: %v", err) - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON file"}) - return - } - - // Ensure the "name" field is present - if character.Name == "" { - log.Printf("Missing 'name' field in JSON file") - c.JSON(http.StatusBadRequest, gin.H{"error": "'name' field is required in the JSON file"}) - return - } - - agentName := character.Name - - // Ensure the characters directory exists - if _, err := os.Stat("./characters"); os.IsNotExist(err) { - log.Println("Characters directory does not exist. Creating...") - if err := os.Mkdir("./characters", 0755); err != nil { - log.Printf("Error creating characters directory: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create characters directory"}) - return - } - } - - characterFilePath := fmt.Sprintf("./characters/%s/%s", agentName, file.Filename) - - // Save the file to the characters directory - log.Printf("Saving character file to %s", characterFilePath) - if err := c.SaveUploadedFile(file, characterFilePath); err != nil { - log.Printf("Error saving character file: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to save character file: %s", err.Error())}) - return - } - - // Ensure the Docker image is present - docker_url := c.DefaultPostForm("docker_url", "") - if docker_url == "" { - docker_url = os.Getenv("DOCKER_IMAGE_AGENT") - } - dockerImage := docker_url - log.Printf("Checking Docker image: %s", dockerImage) - pullCmd := exec.Command("docker", "pull", dockerImage) - if output, err := pullCmd.CombinedOutput(); err != nil { - log.Printf("Error pulling Docker image: %s", string(output)) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to pull Docker image: %s", string(output))}) - return - } - - // Find an available port - exposedPort, err := getAvailablePort() - if err != nil { - log.Printf("Error finding available port: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to find an available port"}) - return - } - - // Run Docker container - log.Printf("Starting Docker container for agent: %s on port: %d", agentName, exposedPort) - dockerCmd := exec.Command( - "docker", "run", "-d", - "--name", agentName, - "-p", fmt.Sprintf("%d:3000", exposedPort), - "-v", fmt.Sprintf("%s:/app/characters", "./characters"), - dockerImage, - "pnpm", "start", fmt.Sprintf("--character=/app/characters/%s/%s", agentName, file.Filename), - ) - - output, err := dockerCmd.CombinedOutput() - if err != nil { - log.Printf("Error starting Docker container: %s", string(output)) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to start Docker container: %s", string(output))}) - return - } - - log.Printf("Docker container started successfully: %s", string(output)) - - // Wait for 30 seconds after Docker container is started - log.Printf("Waiting 30 seconds for agent container to become ready at http://localhost:%d/agents", exposedPort) - time.Sleep(30 * time.Second) - maxRetries := 60 // Maximum number of retries (60 attempts = 60 seconds with 1-second interval) - for i := 0; i < maxRetries; i++ { - agentEndpoint := fmt.Sprintf("http://localhost:%d/agents", exposedPort) - resp, err := http.Get(agentEndpoint) - if err != nil { - log.Printf("Attempt %d/%d: Container not ready yet: %v", i+1, maxRetries, err) - time.Sleep(time.Second) - continue - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusOK { - log.Printf("Container is ready after %d seconds", i+1) - break - } - - log.Printf("Attempt %d/%d: Received status code %d, waiting...", i+1, maxRetries, resp.StatusCode) - time.Sleep(time.Second) - } - - // Determine the domain - domain := c.DefaultPostForm("domain", "") - if domain == "" { - domain = os.Getenv("EREBRUS_DOMAIN") - } - - // Call the AddServicesDirect function from the caddy package - log.Printf("Adding services for domain: %s", domain) - if err := caddy.AddServicesDirect(domain, agentName, exposedPort); err != nil { - log.Printf("Error adding services: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to add services"}) - return - } - - // Allow the container to start and stabilize - agentEndpoint := fmt.Sprintf("http://localhost:%d/agents", exposedPort) - log.Println("Fetching agents from container at", agentEndpoint) - resp, err := http.Get(agentEndpoint) - if err != nil { - log.Printf("Error fetching agents: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch agents from container"}) - return - } - defer resp.Body.Close() - - var agentsResponse struct { - Agents []model.Agent `json:"agents"` - } - - if err := json.NewDecoder(resp.Body).Decode(&agentsResponse); err != nil { - log.Printf("Error parsing agents response: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse agents response"}) - return - } - - // Filter agents by the requested name - var createdAgent *model.Agent - for _, agent := range agentsResponse.Agents { - if strings.EqualFold(agent.Name, agentName) { - createdAgent = &agent - break - } - } - - if createdAgent == nil { - log.Printf("Agent creation failed for: %s", agentName) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Agent creation failed"}) - return - } - domain = agentName + "." + domain - - createdAgent.Port = exposedPort - createdAgent.Domain = domain - createdAgent.Status = "active" - createdAgent.AvatarImg = avatarImg - createdAgent.CoverImg = coverImg - createdAgent.VoiceModel = voiceModel - createdAgent.Organization = organization - saveAgents(*createdAgent) - - response := model.AgentResponse{ - ID: createdAgent.ID, - Name: createdAgent.Name, - Clients: createdAgent.Clients, - Status: createdAgent.Status, - AvatarImg: createdAgent.AvatarImg, - CoverImg: createdAgent.CoverImg, - VoiceModel: createdAgent.VoiceModel, - Organization: createdAgent.Organization, - } - - log.Printf("Agent created successfully: %+v", response) - c.JSON(http.StatusOK, gin.H{"agent": response, "domain": domain}) -} - -// DELETE /agents/:agentId -func deleteAgent(c *gin.Context) { - agentID := c.Param("agentId") - if agentID == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID is required"}) - return - } - - // Load existing agents - agents, err := loadAgents() - if err != nil { - log.Printf("Error loading agents: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agents"}) - return - } - - // Find the agent by ID and remove it - var indexToDelete int = -1 - for i, agent := range agents { - if strings.EqualFold(agent.ID, agentID) { - indexToDelete = i - break - } - } - - // Stop and remove the Docker container for the deleted agent - dockerCmd := exec.Command("docker", "stop", agents[indexToDelete].Name) - if output, err := dockerCmd.CombinedOutput(); err != nil { - log.Printf("Error stopping Docker container: %s", string(output)) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to stop Docker container: %s", string(output))}) - return - } - - dockerRemoveCmd := exec.Command("docker", "rm", agents[indexToDelete].Name) - if output, err := dockerRemoveCmd.CombinedOutput(); err != nil { - log.Printf("Error removing Docker container: %s", string(output)) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to remove Docker container: %s", string(output))}) - return - } - - // If the agent is not found, return an error - if indexToDelete == -1 { - c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"}) - return - } - - //to delete from caddyfile and caddy.json - middleware.DeleteService(agents[indexToDelete].Name) - - // Remove the agent from the list - agents = append(agents[:indexToDelete], agents[indexToDelete+1:]...) - - // Save the updated list of agents - if err := saveAgentsList(agents); err != nil { - log.Printf("Error saving agents: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save agents"}) - return - } - - // Respond with success - log.Printf("Agent %s deleted successfully", agentID) - c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Agent %s deleted successfully", agentID)}) -} - -// Save the list of agents back to the file after deletion -func saveAgentsList(agents []model.Agent) error { - // Open or create the file to save the agents list - file, err := os.Create(agentsFilePath) - if err != nil { - return err - } - defer file.Close() - - // Encode the updated agents list into the file with indentation - encoder := json.NewEncoder(file) - encoder.SetIndent("", " ") - return encoder.Encode(agents) -} - -func manageAgent(c *gin.Context) { - agentID := c.Param("agentId") - if agentID == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID is required"}) - return - } - - action := c.Query("action") - if action != "pause" && action != "resume" { - c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid action. Use 'pause' or 'resume'"}) - return - } - - // Load existing agents - agents, err := loadAgents() - if err != nil { - log.Printf("Error loading agents: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to load agents"}) - return - } - - var dockerAction string - if action == "pause" { - dockerAction = "pause" - } else { - dockerAction = "unpause" - } - - // Find the agent by ID and remove it - var agentIndex int = -1 - for i, agent := range agents { - if strings.EqualFold(agent.ID, agentID) { - agentIndex = i - if dockerAction == "pause" { - agents[i].Status = "inactive" - } else { - agents[i].Status = "active" - } - break - } - } - - // If the agent is not found, return an error - if agentIndex == -1 { - c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"}) - return - } - - // Write the updated data back to the file - updatedData, err := json.MarshalIndent(agents, "", " ") - if err != nil { - log.Printf("Error marshalling updated JSON: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update agents data"}) - return - } - - file, err := os.Create(agentsFilePath) - if err != nil { - log.Printf("Error creating agents.json: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save updated agents"}) - return - } - - defer file.Close() - - if _, err := file.Write(updatedData); err != nil { - log.Printf("Error writing to agents.json: %v", err) - c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to write agents data"}) - return - } - - // Execute the pause or resume action - actionCmd := exec.Command("docker", dockerAction, agents[agentIndex].Name) - actionOutput, err := actionCmd.CombinedOutput() - if err != nil { - log.Printf("Error performing action '%s' on Agent: %s, Output: %s", dockerAction, err, string(actionOutput)) - c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to %s Agent: %s", dockerAction, string(actionOutput))}) - return - } - - log.Printf("Successfully performed action '%s' on Agent '%s'", dockerAction, agentID) - c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("Agent '%s' %sed successfully", agentID, dockerAction)}) -} - -func monitorAndRecoverAgents() { - ticker := time.NewTicker(15 * time.Second) - go func() { - for range ticker.C { - agents, err := loadAgents() - if err != nil { - log.Printf("Error loading agents for recovery: %v", err) - continue - } - - for _, agent := range agents { - // Check container status - cmd := exec.Command("docker", "inspect", "-f", "{{.State.Running}}", agent.Name) - output, err := cmd.Output() - if err != nil { - log.Printf("Error checking container status for %s: %v", agent.Name, err) - recreateErr := recreateAgent(agent) - if recreateErr != nil { - log.Printf("Failed to recreate agent %s: %v", agent.Name, recreateErr) - } - continue - } - - status := strings.TrimSpace(string(output)) - if status != "true" { - log.Printf("Agent %s is not running. Attempting to restart...", agent.Name) - - // Restart the container - restartCmd := exec.Command("docker", "restart", agent.Name) - if restartOutput, restartErr := restartCmd.CombinedOutput(); restartErr != nil { - log.Printf("Failed to restart agent %s: %v, Output: %s", - agent.Name, restartErr, string(restartOutput)) - - // If restart fails, try to recreate the container - recreateErr := recreateAgent(agent) - if recreateErr != nil { - log.Printf("Failed to recreate agent %s: %v", agent.Name, recreateErr) - } - - return - } - - // check the status of agent and and set it accordingly for container - if agent.Status == "inactive" { - actionCmd := exec.Command("docker", "pause", agent.Name) - actionOutput, err := actionCmd.CombinedOutput() - if err != nil { - log.Printf("Error performing action pause on Agent: %s, Output: %s", err, string(actionOutput)) - return - } - } - - log.Printf("Agent %s is restored", agent.Name) - } - - } - } - }() -} - -func recreateAgent(agent model.Agent) error { - // Stop and remove existing container if it exists - stopCmd := exec.Command("docker", "stop", agent.Name) - stopCmd.Run() - - removeCmd := exec.Command("docker", "rm", agent.Name) - removeCmd.Run() - - // Recreate the container using the original parameters - dockerCmd := exec.Command( - "docker", "run", "-d", - "--name", agent.Name, - "-p", fmt.Sprintf("%d:3000", agent.Port), - "-v", fmt.Sprintf("%s:/app/characters", "./characters"), - os.Getenv("DOCKER_IMAGE_AGENT"), - "pnpm", "start", fmt.Sprintf("--character=/app/characters/%s/%s.character.json", agent.Name, agent.Name), - ) - - output, err := dockerCmd.CombinedOutput() - if err != nil { - return fmt.Errorf("failed to recreate container: %v, output: %s", err, string(output)) - } - - if agent.Status == "inactive" { - actionCmd := exec.Command("docker", "pause", agent.Name) - actionOutput, err := actionCmd.CombinedOutput() - if err != nil { - log.Printf("Error performing action pause on Agent: %s, Output: %s", err, string(actionOutput)) - } - } - - fmt.Println("Successfully recreated the agent container:", agent.Name) - - return nil -} diff --git a/api/v1/agents/ensureinstallation.go b/api/v1/agents/ensureinstallation.go deleted file mode 100644 index 218cd8b..0000000 --- a/api/v1/agents/ensureinstallation.go +++ /dev/null @@ -1,129 +0,0 @@ -package agents - -import ( - "bytes" - "fmt" - "log" - "os/exec" -) - -func EnsureDockerAndCaddy() { - // Check and install Docker - if !isCommandAvailable("docker") { - log.Println("Docker is not installed. Installing Docker...") - err := installDocker() - if err != nil { - log.Fatalf("Failed to install Docker: %v", err) - } - log.Println("Docker installed successfully.") - } else { - log.Println("Docker is already installed.") - } - - // Test Docker functionality - log.Println("Testing Docker functionality...") - testDocker() - - // Check and install Caddy - if !isCommandAvailable("caddy") { - log.Println("Caddy is not installed. Installing Caddy...") - err := installCaddy() - if err != nil { - log.Fatalf("Failed to install Caddy: %v", err) - } - log.Println("Caddy installed successfully.") - } else { - log.Println("Caddy is already installed.") - } - - // Start Caddy - log.Println("Starting Caddy") - if err := runCommand(exec.Command("systemctl", "restart", "caddy")); err != nil { - log.Fatalf("Failed to restart caddy: %v", err) - } else { - log.Println("Caddy Started Successfully") - } - -} - -// Check if a command is available -func isCommandAvailable(cmd string) bool { - _, err := exec.LookPath(cmd) - return err == nil -} - -// Install Docker -func installDocker() error { - cmd := exec.Command("sh", "-c", ` - curl -fsSL https://get.docker.com | sh - `) - if err := runCommand(cmd); err != nil { - return err - } - - // Enable and start Docker service - log.Println("Enabling and starting Docker service...") - enableCmd := exec.Command("systemctl", "enable", "--now", "docker") - if err := runCommand(enableCmd); err != nil { - return fmt.Errorf("failed to enable/start Docker: %w", err) - } - - log.Println("Docker service enabled and started successfully.") - return nil -} - -// Install Caddy -func installCaddy() error { - cmd := exec.Command("sh", "-c", ` - apt-get update -qq && - apt-get install -y debian-keyring debian-archive-keyring apt-transport-https && - curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/caddy-archive-keyring.gpg && - echo "deb [signed-by=/usr/share/keyrings/caddy-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian all main" > /etc/apt/sources.list.d/caddy-stable.list && - apt-get update -qq && - apt-get install -y caddy - `) - return runCommand(cmd) -} - -// Test Docker functionality -func testDocker() { - log.Println("Pulling Alpine image...") - if err := runCommand(exec.Command("systemctl", "enable", "docker")); err != nil { - log.Fatalf("Failed to enable docker: %v", err) - } - if err := runCommand(exec.Command("systemctl", "restart", "docker")); err != nil { - log.Fatalf("Failed to restart docker: %v", err) - } - log.Println("Successfully restarted Docker") - if err := runCommand(exec.Command("docker", "pull", "alpine")); err != nil { - log.Fatalf("Failed to pull Alpine image: %v", err) - } - log.Println("Successfully pulled Alpine image.") - - log.Println("Running Alpine container...") - if err := runCommand(exec.Command("docker", "run", "--name", "alpine-test", "-d", "alpine", "sleep", "10")); err != nil { - log.Fatalf("Failed to run Alpine container: %v", err) - } - log.Println("Successfully ran Alpine container.") - - log.Println("Deleting Alpine container...") - if err := runCommand(exec.Command("docker", "rm", "-f", "alpine-test")); err != nil { - log.Fatalf("Failed to delete Alpine container: %v", err) - } - log.Println("Successfully deleted Alpine container.") -} - -// Helper function to run commands and capture output -func runCommand(cmd *exec.Cmd) error { - var out bytes.Buffer - var stderr bytes.Buffer - cmd.Stdout = &out - cmd.Stderr = &stderr - err := cmd.Run() - if err != nil { - log.Printf("Command failed: %s\nOutput: %s\nError: %s", cmd.String(), out.String(), stderr.String()) - return err - } - log.Printf("Command succeeded: %s\nOutput: %s", cmd.String(), out.String()) - return nil -} diff --git a/api/v1/authenticate/authenticate.go b/api/v1/authenticate/authenticate.go deleted file mode 100644 index 993eda6..0000000 --- a/api/v1/authenticate/authenticate.go +++ /dev/null @@ -1,170 +0,0 @@ -package authenticate - -import ( - "fmt" - "net/http" - "os" - - "github.com/NetSepio/erebrus/api/v1/authenticate/challengeid" - "github.com/NetSepio/erebrus/util/pkg/auth" - "github.com/NetSepio/erebrus/util/pkg/claims" - - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" -) - -// ApplyRoutes applies router to gin Router -func ApplyRoutes(r *gin.RouterGroup) { - g := r.Group("/authenticate") - { - g.GET("", challengeid.GetChallengeId) - g.POST("", authenticate) - - } -} - -func authenticate(c *gin.Context) { - - var req AuthenticateRequest - err := c.BindJSON(&req) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("Invalid request payload") - - errResponse := ErrAuthenticate(err.Error()) - c.JSON(http.StatusForbidden, errResponse) - return - } - userAuthEULA := os.Getenv("AUTH_EULA") - message := userAuthEULA + req.ChallengeId - - var ( - isCorrect bool - walletAddress string - ) - - switch req.ChainName { - case "ethereum", "peaq": - userAuthEULA := userAuthEULA - message := userAuthEULA + req.ChallengeId - walletAddress, isCorrect, err = CheckSignEthereum(req.Signature, req.ChallengeId, message) - - if err == ErrChallengeIdNotFound { - - log.WithFields(log.Fields{"err": err}).Errorf("Challenge Id not found") - - c.JSON(http.StatusNotFound, ErrAuthenticate("Challenge Id not found")) - - return - } - - if err != nil { - fmt.Println("error", err) - - log.WithFields(log.Fields{"err": err}).Errorf("failed to CheckSignature, error %v", err.Error()) - - c.JSON(http.StatusNotFound, ErrAuthenticate("failed to CheckSignature, error :"+err.Error())) - return - } - - case "aptos": - userAuthEULA := userAuthEULA - message := fmt.Sprintf("APTOS\nmessage: %v\nnonce: %v", userAuthEULA, req.ChallengeId) - walletAddress, isCorrect, err = CheckSignAptos(req.Signature, req.ChallengeId, message, req.PubKey) - - if err == ErrChallengeIdNotFound { - log.WithFields(log.Fields{"err": err}).Errorf("Challenge Id not found") - - c.JSON(http.StatusNotFound, ErrAuthenticate("Challenge Id not found")) - - return - } - - if err != nil { - - log.WithFields(log.Fields{"err": err}).Errorf("failed to CheckSignature, error %v", err.Error()) - - c.JSON(http.StatusNotFound, ErrAuthenticate("failed to CheckSignature, error :"+err.Error())) - return - } - - case "sui": - walletAddress, isCorrect, err = CheckSignSui(req.Signature, req.ChallengeId) - - if err == ErrChallengeIdNotFound { - - log.WithFields(log.Fields{"err": err}).Errorf("Challenge Id not found") - - c.JSON(http.StatusNotFound, ErrAuthenticate("Challenge Id not found")) - return - } - - if err != nil { - log.WithFields(log.Fields{"err": err}).Errorf("failed to CheckSignature, error %v", err.Error()) - - c.JSON(http.StatusNotFound, ErrAuthenticate("failed to CheckSignature, error : "+err.Error())) - return - } - - case "solana": - walletAddress, isCorrect, err = CheckSignSolana(req.Signature, req.ChallengeId, message, req.PubKey) - - if err == ErrChallengeIdNotFound { - log.WithFields(log.Fields{"err": err}).Errorf("Challenge Id not found") - c.JSON(http.StatusNotFound, ErrAuthenticate("Challenge Id not found")) - return - } - - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Errorf("failed to CheckSignature, error : %v", err.Error()) - errResponse := ErrAuthenticate("failed to CheckSignature, error :" + err.Error()) - c.JSON(http.StatusInternalServerError, errResponse) - return - - } - - default: - info := "chain name must be between solana, peaq, aptos, sui, eclipse, ethereum" - log.WithFields(log.Fields{ - "err": err, - }).Errorf("Invalid chain name, INFO : %s\n", info) - errResponse := ErrAuthenticate("failed to CheckSignature, error :" + "Invalid chain name, INFO : " + info) - c.JSON(http.StatusInternalServerError, errResponse) - return - } - if isCorrect { - customClaims := claims.New(walletAddress) - pasetoToken, err := auth.GenerateTokenPaseto(customClaims) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to generate token") - errResponse := ErrAuthenticate(err.Error()) - c.JSON(http.StatusInternalServerError, errResponse) - return - } - delete(challengeid.Data, req.ChallengeId) - payload := AuthenticatePayload{ - Status: 200, - Success: true, - Message: "Successfully Authenticated", - Token: pasetoToken, - } - c.JSON(http.StatusAccepted, payload) - } else { - errResponse := ErrAuthenticate("Forbidden") - c.JSON(http.StatusForbidden, errResponse) - return - } -} - -func ErrAuthenticate(errvalue string) AuthenticatePayload { - var payload AuthenticatePayload - payload.Success = false - payload.Status = 401 - payload.Message = errvalue - return payload -} diff --git a/api/v1/authenticate/challengeid/challengeid.go b/api/v1/authenticate/challengeid/challengeid.go deleted file mode 100644 index 29be963..0000000 --- a/api/v1/authenticate/challengeid/challengeid.go +++ /dev/null @@ -1,192 +0,0 @@ -package challengeid - -import ( - "encoding/hex" - "math/big" - "net/http" - "os" - "regexp" - "strings" - "time" - - "github.com/NetSepio/erebrus/core" - - "github.com/gin-gonic/gin" - "github.com/google/uuid" - log "github.com/sirupsen/logrus" -) - -type FlowId struct { - WalletAddress string - FlowId string `gorm:"primary_key"` -} -type MemoryDB struct { - WalletAddress string - ChainName string - Timestamp time.Time -} - -var Data map[string]MemoryDB - -// Get walletAddress, chain and return eula, challengeId -func GetChallengeId(c *gin.Context) { - walletAddress := c.Query("walletAddress") - chainName := c.Query("chainName") - - if walletAddress == "" { - log.WithFields(log.Fields{ - "err": "empty Wallet Address", - }).Error("failed to create client") - - response := core.MakeErrorResponse(403, "Empty Wallet Address", nil, nil, nil) - c.JSON(http.StatusForbidden, response) - return - } - - if chainName == "" { - log.WithFields(log.Fields{ - "err": "empty Chain name", - }).Error("failed to create client") - - response := core.MakeErrorResponse(403, "Empty Wallet Address", nil, nil, nil) - c.JSON(http.StatusForbidden, response) - return - } - - if err := ValidateAddress(chainName, walletAddress); err != nil { - - info := "chain name = " + chainName + "; please pass chain name between solana, peaq, aptos, sui, eclipse, ethereum" - - switch err { - case ErrInvalidChain: - log.WithFields(log.Fields{"err": ErrInvalidChain}).Error("failed to create client") - response := core.MakeErrorResponse(http.StatusNotAcceptable, ErrInvalidChain.Error()+info, nil, nil, nil) - c.JSON(http.StatusNotAcceptable, response) - return - case ErrInvalidAddress: - log.WithFields(log.Fields{"err": ErrInvalidAddress}).Error("failed to create client") - response := core.MakeErrorResponse(http.StatusNotAcceptable, ErrInvalidAddress.Error(), nil, nil, nil) - c.JSON(http.StatusNotAcceptable, response) - return - } - return - } - - challengeId, err := GenerateChallengeId(walletAddress, chainName) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to create FlowId") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - userAuthEULA := os.Getenv("AUTH_EULA") - payload := GetChallengeIdPayload{ - ChallengeId: challengeId, - Eula: userAuthEULA, - } - c.JSON(200, payload) -} - -func GenerateChallengeId(walletAddress string, chainName string) (string, error) { - challengeId := uuid.NewString() - var dbdata MemoryDB - dbdata.WalletAddress = walletAddress - dbdata.Timestamp = time.Now() - dbdata.ChainName = chainName - Data = map[string]MemoryDB{ - challengeId: dbdata, - } - return challengeId, nil -} - -// ValidateAddress validates a wallet address for the specified blockchain -func ValidateAddress(chain, address string) error { - // Convert chain name to lowercase for case-insensitive comparison - - switch chain { - case "ethereum": - if !ValidateAddressEtherium(address) { - return ErrInvalidAddress - } - case "solana", "eclipse": - if !ValidateSolanaAddress(address) { - return ErrInvalidAddress - } - - case "peaq": - if !ValidatePeaqAddress(address) { - return ErrInvalidAddress - } - - case "aptos": - if !ValidateAptosAddress(address) { - return ErrInvalidAddress - } - - case "sui": - if !ValidateSuiAddress(address) { - return ErrInvalidAddress - } - - default: - return ErrInvalidChain - } - - return nil -} - -// ValidateSolanaAddress checks if the given string is a valid Solana wallet address -func ValidateSolanaAddress(address string) bool { - if len(address) < 32 || len(address) > 44 { - return false - } - - // Solana addresses only contain base58 characters - matched, _ := regexp.MatchString("^[1-9A-HJ-NP-Za-km-z]+$", address) - return matched -} - -// ValidatePeaqAddress checks if the given string is a valid Peaq wallet address -func ValidatePeaqAddress(address string) bool { - if len(address) != 48 || !strings.HasPrefix(address, "5") { - return false - } - - // Peaq addresses only contain base58 characters - matched, _ := regexp.MatchString("^[1-9A-HJ-NP-Za-km-z]+$", address) - return matched -} - -// ValidateAptosAddress checks if the given string is a valid Aptos wallet address -func ValidateAptosAddress(address string) bool { - if len(address) != 66 || !strings.HasPrefix(address, "0x") { - return false - } - - // Remove "0x" prefix and check if remaining string is valid hex - address = strings.TrimPrefix(address, "0x") - _, err := hex.DecodeString(address) - return err == nil -} - -// ValidateSuiAddress checks if the given string is a valid Sui wallet address -func ValidateSuiAddress(address string) bool { - if len(address) != 42 || !strings.HasPrefix(address, "0x") { - return false - } - - // Remove "0x" prefix and check if remaining string is valid hex - address = strings.TrimPrefix(address, "0x") - _, err := hex.DecodeString(address) - return err == nil -} - -func ValidateAddressEtherium(address string) bool { - if len(address) != 42 || !strings.HasPrefix(address, "0x") { - return false - } - _, isValid := big.NewInt(0).SetString(address[2:], 16) - return isValid -} diff --git a/api/v1/authenticate/challengeid/types.go b/api/v1/authenticate/challengeid/types.go deleted file mode 100644 index a323dce..0000000 --- a/api/v1/authenticate/challengeid/types.go +++ /dev/null @@ -1,13 +0,0 @@ -package challengeid - -import "errors" - -type GetChallengeIdPayload struct { - Eula string `json:"eula,omitempty"` - ChallengeId string `json:"challangeId"` -} - -var ( - ErrInvalidChain = errors.New("unsupported blockchain") - ErrInvalidAddress = errors.New("invalid address format") -) diff --git a/api/v1/authenticate/paseto/paseto.go b/api/v1/authenticate/paseto/paseto.go deleted file mode 100644 index fd37f36..0000000 --- a/api/v1/authenticate/paseto/paseto.go +++ /dev/null @@ -1,63 +0,0 @@ -package paseto - -import ( - "encoding/json" - "errors" - "fmt" - "net/http" - "strings" - - gopaseto "aidanwoods.dev/go-paseto" - log "github.com/sirupsen/logrus" - - "github.com/NetSepio/erebrus/util/pkg/auth" - "github.com/NetSepio/erebrus/util/pkg/claims" - "github.com/gin-gonic/gin" -) - -var ( - ErrAuthHeaderMissing = errors.New("authorization header is required") -) - -func PASETO(c *gin.Context) { - var headers GenericAuthHeaders - err := c.BindHeader(&headers) - if err != nil { - err = fmt.Errorf("failed to bind header, %s", err) - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - - c.AbortWithStatus(http.StatusInternalServerError) - return - } - if headers.Authorization == "" { - log.WithFields(log.Fields{ - "err": err, - }).Error("Autherisation header is missing") - c.Abort() - return - } - token := headers.Authorization - splitToken := strings.Split(token, "Bearer ") - pasetoToken := splitToken[1] - parser := gopaseto.NewParser() - parser.AddRule(gopaseto.NotExpired()) - publickey := auth.Getpublickey() - parsedToken, err := parser.ParseV4Public(publickey, pasetoToken, nil) - if err != nil { - err = fmt.Errorf("failed to scan claims for paseto token, %s", err) - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bindfailed to scan claims for paseto token") - c.AbortWithStatus(http.StatusUnauthorized) - return - } else { - jsonvalue := parsedToken.ClaimsJSON() - ClaimsValue := claims.CustomClaims{} - json.Unmarshal(jsonvalue, &ClaimsValue) - c.Set("walletAddress", ClaimsValue.WalletAddress) - c.Next() - } - -} diff --git a/api/v1/authenticate/paseto/types.go b/api/v1/authenticate/paseto/types.go deleted file mode 100644 index 725a3a3..0000000 --- a/api/v1/authenticate/paseto/types.go +++ /dev/null @@ -1,5 +0,0 @@ -package paseto - -type GenericAuthHeaders struct { - Authorization string -} diff --git a/api/v1/authenticate/types.go b/api/v1/authenticate/types.go deleted file mode 100644 index 6bcaec5..0000000 --- a/api/v1/authenticate/types.go +++ /dev/null @@ -1,15 +0,0 @@ -package authenticate - -type AuthenticateRequest struct { - ChallengeId string `json:"challengeId" binding:"required"` - Signature string `json:"signature" binding:"required"` - PubKey string `json:"pubKey" binding:"omitempty"` - ChainName string `json:"chainName" binding:"required"` -} - -type AuthenticatePayload struct { - Status int64 `json:"status"` - Success bool `json:"success"` - Message string `json:"message"` - Token string `json:"token"` -} diff --git a/api/v1/authenticate/validate.chain.authentication.go b/api/v1/authenticate/validate.chain.authentication.go deleted file mode 100644 index 3a54d78..0000000 --- a/api/v1/authenticate/validate.chain.authentication.go +++ /dev/null @@ -1,168 +0,0 @@ -package authenticate - -import ( - "crypto/ecdsa" - "crypto/ed25519" - "crypto/elliptic" - "encoding/base64" - "encoding/hex" - "errors" - "fmt" - "math/big" - "strings" - - "github.com/NetSepio/erebrus/api/v1/authenticate/challengeid" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" - "github.com/minio/blake2b-simd" - "github.com/mr-tron/base58" - "golang.org/x/crypto/nacl/sign" - "golang.org/x/crypto/sha3" -) - -var ErrChallengeIdNotFound = errors.New("challenge id not found") - -func CheckSignAptos(signature string, challangeId string, message string, pubKey string) (string, bool, error) { - signatureInBytes, err := hexutil.Decode(signature) - if err != nil { - return "", false, err - } - - sha3_i := sha3.New256() - signatureInBytes = append(signatureInBytes, []byte(message)...) - pubBytes, err := hexutil.Decode(pubKey) - if err != nil { - return "", false, err - } - sha3_i.Write(pubBytes) - sha3_i.Write([]byte{0}) - hash := sha3_i.Sum(nil) - addr := hexutil.Encode(hash) - - dbData, exists := challengeid.Data[challangeId] - if !exists { - return "", false, ErrChallengeIdNotFound - } - - if !strings.EqualFold(addr, dbData.WalletAddress) { - return "", false, err - } - - msgGot, matches := sign.Open(nil, signatureInBytes, (*[32]byte)(pubBytes)) - if !matches || string(msgGot) != message { - return "", false, err - } - return dbData.WalletAddress, true, nil - -} - -func CheckSignEthereum(signature string, flowId string, message string) (string, bool, error) { - - newMsg := fmt.Sprintf("\x19Ethereum Signed Message:\n%v%v", len(message), message) - - // fmt.Println("newMsg : ", newMsg) - - newMsgHash := crypto.Keccak256Hash([]byte(newMsg)) - signatureInBytes, err := hexutil.Decode(signature) - if err != nil { - return "", false, err - } - // check if the signature is in the [R || S || V] format - if len(signatureInBytes) != 65 { - return "", false, errors.New("invalid signature length") - } - if signatureInBytes[64] == 27 || signatureInBytes[64] == 28 { - signatureInBytes[64] -= 27 - } - pubKey, err := crypto.SigToPub(newMsgHash.Bytes(), signatureInBytes) - - if err != nil { - return "", false, err - } - - //Get address from public key - walletAddress := crypto.PubkeyToAddress(*pubKey) - - flowIdData := challengeid.Data[flowId] - if (challengeid.MemoryDB{}) == flowIdData { - return "", false, ErrChallengeIdNotFound - } - if strings.EqualFold(flowIdData.WalletAddress, walletAddress.String()) { - return flowIdData.WalletAddress, true, nil - } else { - return "", false, errors.New("mismatch wallet_address") - } -} - -func CheckSignSui(signature string, challangeId string) (string, bool, error) { - // Decode signature - signatureBytes, err := base64.StdEncoding.DecodeString(signature) - if err != nil { - return "", false, err - } - - // Assuming ED25519 signature format - size := 32 - - publicKey := signatureBytes[len(signatureBytes)-size:] - pubKey := &ecdsa.PublicKey{ - Curve: elliptic.P256(), // Curve is not used in serialization - X: new(big.Int).SetBytes(publicKey[:]), // Set X coordinate - Y: new(big.Int).SetBytes(publicKey[32:]), // Set Y coordinate - } - if pubKey.X == nil || pubKey.Y == nil { - return "", false, err - } - // Serialize the public key into bytes - pubKeyBytes := pubKey.X.Bytes() - - // Pad X coordinate bytes to ensure they are the same length as the curve's bit size - paddingLen := (pubKey.Curve.Params().BitSize + 7) / 8 - pubKeyBytes = append(make([]byte, paddingLen-len(pubKeyBytes)), pubKeyBytes...) - - // Concatenate the signature scheme flag (0x00 for Ed25519) with the serialized public key bytes - concatenatedBytes := append([]byte{0x00}, pubKeyBytes...) - - // Compute the BLAKE2b hash - hash := blake2b.Sum256(concatenatedBytes) - - // The resulting hash is the Sui address - suiAddress := "0x" + hex.EncodeToString(hash[:]) - - dbData, exists := challengeid.Data[challangeId] - if !exists { - return "", false, ErrChallengeIdNotFound - } - - if !strings.EqualFold(suiAddress, dbData.WalletAddress) { - return "", false, err - } - - return dbData.WalletAddress, true, nil -} - -func CheckSignSolana(signature string, challangeId string, message string, pubKey string) (string, bool, error) { - - bytes, err := base58.Decode(pubKey) - if err != nil { - return "", false, err - } - messageAsBytes := []byte(message) - - signedMessageAsBytes, err := hex.DecodeString(signature) - - if err != nil { - - return "", false, err - } - - dbData, exists := challengeid.Data[challangeId] - if !exists { - return "", false, ErrChallengeIdNotFound - } - - ed25519.Verify(bytes, messageAsBytes, signedMessageAsBytes) - - return dbData.WalletAddress, true, nil - -} diff --git a/api/v1/client/client.go b/api/v1/client/client.go deleted file mode 100644 index 56a5064..0000000 --- a/api/v1/client/client.go +++ /dev/null @@ -1,224 +0,0 @@ -package client - -import ( - "net/http" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" - "github.com/skip2/go-qrcode" -) - -// ApplyRoutes applies router to gin Route -func ApplyRoutes(r *gin.RouterGroup) { - g := r.Group("/client") - { - g.GET("", readClients) - g.GET("/:id", readClient) - g.POST("", registerClient) - g.PATCH("/:id", updateClient) - g.DELETE("/:id", deleteClient) - g.GET("/:id/config", configClient) - } -} - -// swagger:route POST /client Client createClient -// -// Create client -// -// Create client based on the given client model. -// responses: -// 201: clientSucessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse - -func registerClient(c *gin.Context) { - var data model.Client - if err := c.ShouldBindJSON(&data); err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - - response := core.MakeErrorResponse(400, err.Error(), nil, nil, nil) - c.JSON(http.StatusUnprocessableEntity, response) - return - } - - client, err := core.RegisterClient(&data) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to create client") - - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - server, err := core.ReadServer() - if err != nil { - log.WithFields(util.StandardFields).Error("Failure in reading server") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - response := core.MakeSucessResponse(201, "client created", server, client, nil) - - c.JSON(http.StatusOK, response) -} - -// swagger:route GET /client/{id} Client readClient -// -// # Read client -// -// Return client based on the given uuid. -// responses: -// -// 200: clientSucessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func readClient(c *gin.Context) { - id := c.Param("id") - - client, err := core.ReadClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read client") - - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - response := core.MakeSucessResponse(200, "client details", nil, client, nil) - - c.JSON(http.StatusOK, response) -} - -// swagger:route PATCH /client/{id} Client updateClient -// -// # Update client -// -// Update client based on the given uuid and client model. -// responses: -// -// 200: clientSucessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func updateClient(c *gin.Context) { - var data model.Client - id := c.Param("id") - if err := c.ShouldBindJSON(&data); err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - - response := core.MakeErrorResponse(400, err.Error(), nil, nil, nil) - c.JSON(http.StatusUnprocessableEntity, response) - return - } - - client, err := core.UpdateClient(id, &data) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to update client") - - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - response := core.MakeSucessResponse(200, "client updated", nil, client, nil) - - c.JSON(http.StatusOK, response) -} - -// swagger:route DELETE /client/{id} Client deleteClient -// -// # Delete client -// -// Delete client based on the given uuid. -// responses: -// -// 200: sucessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func deleteClient(c *gin.Context) { - id := c.Param("id") - err := core.DeleteClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to remove client") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - response := core.MakeSucessResponse(200, "client deleted", nil, nil, nil) - c.JSON(http.StatusOK, response) -} - -// swagger:route GET /client Client readClients -// -// # Read All Clients -// Get all clients in the server. -// responses: -// -// 200: clientsSucessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func readClients(c *gin.Context) { - clients, err := core.ReadClients() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to list clients") - - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - response := core.MakeSucessResponse(200, "clients details", nil, nil, clients) - - c.JSON(http.StatusOK, response) -} - -func configClient(c *gin.Context) { - configData, err := core.ReadClientConfig(c.Param("id")) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to read client config") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - formatQr := c.DefaultQuery("qrcode", "false") - if formatQr == "false" { - // return config as txt file - c.Header("Content-Disposition", "attachment; filename="+c.Param("id")+".conf") - c.Data(http.StatusOK, "application/config", configData) - return - } - // return config as png qrcode - png, err := qrcode.Encode(string(configData), qrcode.Medium, 250) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to create qrcode") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - c.Data(http.StatusOK, "image/png", png) - -} diff --git a/api/v1/client/client_doc.go b/api/v1/client/client_doc.go deleted file mode 100644 index 1bf457c..0000000 --- a/api/v1/client/client_doc.go +++ /dev/null @@ -1,253 +0,0 @@ -package client - -// swagger:response clientSucessResponse -// Response when the operation suceeds. -type ClientSucessResponse struct { - // in: body - Body struct { - // example: 201 - Status int64 - // example: true - Sucess bool - // example: sucess message - Message string - Body Client `json:"client"` - } -} - -// swagger:response clientsSucessResponse -// Response for read all clients. -type ClientsSucessResponse struct { - // in: body - Body struct { - // example: 201 - Status int64 - // example: true - Sucess bool - // example: sucess message - Message string - Body []Client `json:"clients"` - } -} - -// swagger:response sucessResponse -// Response when the operation suceeds. -type SucessResponse struct { - // in: body - Body struct { - // example: 200 - Status int64 - // example: true - Sucess bool - // example: sucess message - Message string - } -} - -// swagger:response badRequestResponse -// Response when the operation failed with Bad Request. -type BadRequestResponse struct { - // in:body - Body struct { - // example: 400 - Status int64 - // example: false - Sucess bool - // example: error message - Error string - } -} - -// swagger:response unauthorizedResponse -// Response when the operation failed with Bad Request. -type UnauthorizedResponse struct { - // in:body - Body struct { - // example: 401 - Status int64 - // example: false - Sucess bool - // example: error message - Error string - } -} - -// swagger:response serverErrorResponse -// Response when the operation failed with Server Error. -type ServerErrorResponse struct { - // in:body - Body struct { - // example: 500 - Status int64 - // example: false - Sucess bool - // example: error message - Error string - } -} - -// swagger:parameters readClient updateClient deleteClient configClient emailClient -type ClientIDParam struct { - //The Identifier of the Client - // in: path - Id string `json:"id"` -} - -// swagger:parameters createClient -type ClientCreateReqparam struct { - // Requestbody used for create and update client operations. - // in: body - Body ClientReq `json:"client"` -} - -// swagger:parameters updateClient -type ClientUpdateReqparam struct { - // Requestbody used for create and update client operations. - // in: body - Body ClientUpdateReq `json:"client"` -} - -// swagger:model -// model for client details. -type Client struct { - - //Client identifier - // example: 6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab - UUID string `json:"uuid"` - //Name of the client - // example: jon snow - Name string `json:"name"` - //Tags for client device - // example: ["laptop","PC"] - Tags []string `json:"tags"` - //Email that the client device belongs - // example: jonsnow@mail.com - Email string `json:"email"` - //Status signal for client - // example: true - Enable bool `json:"enable"` - // example: true - IgnorePersistentKeepalive bool `json:"ignorePersistentKeepalive"` - //Preshared key for the client - // example: twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc= - PresharedKey string `json:"presharedKey"` - //IP addresses allowed to connect - // example: ["0.0.0.0/0","::/0"] - AllowedIPs []string `json:"allowedIPs"` - //Address range client must will assigned - // example: ["10.0.0.2/32"] - Address []string `json:"address"` - //Private key for the client - // example: KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg= - PrivateKey string `json:"privateKey"` - //Public key for the client - // example: YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4= - PublicKey string `json:"publicKey"` - //Denoting person creates the client - // example: jonsnow@mail.com - CreatedBy string `json:"createdBy"` - // Denoting person updates the client - // example: jonsnow@mail.com - UpdatedBy string `json:"updatedBy"` - //Time the client is created - // example: 1642409076544 - Created int64 `json:"created"` - //Time the client is last updated - // example: 1642409076544 - Updated int64 `json:"updated"` -} - -// swagger:model -// model for client details. -type ClientReq struct { - - // required: true - // example: jon snow - Name string `json:"name"` - //Tags for client device - // required: true - // example: ["laptop","PC"] - Tags []string `json:"tags"` - //Email that the client device belongs - // required: true - // example: jonsnow@mail.com - Email string `json:"email"` - //Status signal for client - // required: true - // example: true - Enable bool `json:"enable"` - //IP addresses allowed to connect - // required: true - // example: ["0.0.0.0/0","::/0"] - AllowedIPs []string `json:"allowedIPs"` - //Address range client must will assigned - // required: true - // example: ["10.0.0.0/24"] - Address []string `json:"address"` - //Denoting person creates the client - // required: true - // example: jonsnow@mail.com - CreatedBy string `json:"createdBy"` - // Denoting person updates the client - // required: true - // example: jonsnow@mail.com - UpdatedBy string `json:"updatedBy"` -} - -// swagger:model -// model for client details. -type ClientUpdateReq struct { - //Client identifier - // required: true - // example: 6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab - UUID string `json:"uuid"` - //Name of the client - // required: true - // example: jon snow - Name string `json:"name"` - //Tags for client device - // required: true - // example: ["laptop","PC"] - Tags []string `json:"tags"` - - //Email that the client device belongs - // required: true - // example: jonsnow@mail.com - Email string `json:"email"` - //Status signal for client - // required: true - // example: true - Enable bool `json:"enable"` - // example: true - IgnorePersistentKeepalive bool `json:"ignorePersistentKeepalive"` - //Preshared key for the client - // example: twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc= - PresharedKey string `json:"presharedKey"` - //IP addresses allowed to connect - // required: true - // example: ["0.0.0.0/0","::/0"] - AllowedIPs []string `json:"allowedIPs"` - //IP addresses allowed to connect - // required: true - // example: ["10.0.0.2/32"] - Address []string `json:"address"` - //Private key for the client - // example: KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg= - PrivateKey string `json:"privateKey"` - //Public key for the client - // example: YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4= - PublicKey string `json:"publicKey"` - //Denoting person creates the client - // example: jonsnow@mail.com - CreatedBy string `json:"createdBy"` - // Denoting person updates the client - // example: jonsnow@mail.com - // required: true - UpdatedBy string `json:"updatedBy"` - //Time the client is created - // example: 1642409076544 - Created int64 `json:"created"` - //Time the client is last updated - // example: 1642409076544 - Updated int64 `json:"updated"` -} diff --git a/api/v1/middleware/caddy.go b/api/v1/middleware/caddy.go deleted file mode 100644 index 4f96d9e..0000000 --- a/api/v1/middleware/caddy.go +++ /dev/null @@ -1,305 +0,0 @@ -package middleware - -import ( - "encoding/json" - "fmt" - "io" - "os" - // "os/exec" - "path/filepath" - "strconv" - - "github.com/NetSepio/erebrus/api/v1/service/template" - "github.com/NetSepio/erebrus/api/v1/service/util" - "github.com/NetSepio/erebrus/model" -) - -// IsValid check if model is valid -func IsValidService(name string, port int, ipAddress string) (int, string, error) { - // Check if the name is empty - fmt.Printf("Checking service name: %s, port: %d\n", name, port) - if name == "" { - fmt.Println("Service name is empty") - return -1, "Services Name is required", nil - } - - // Check the name field length - fmt.Printf("Service name length: %d\n", len(name)) - if len(name) < 4 || len(name) > 50 { - fmt.Println("Service name length is invalid") - return -1, "Services Name field must be between 4-12 chars", nil - } - - // Read existing services - fmt.Println("Reading web services...") - Services, err := ReadServices() - if err != nil { - if err.Error() == "caddy file is empty while reading file" { - fmt.Println("Caddy file is empty, proceeding to create a new Services") - } else { - fmt.Printf("Error reading web services: %v\n", err) - return -1, "", err - } - } else { - fmt.Printf("Read web services successfully: %+v\n", Services) - } - - // Check if the name or port is already in use - if Services != nil { - for _, service := range Services.Services { - fmt.Printf("Checking service: %+v\n", service) - if service.Name == name { - fmt.Println("Service name already exists") - return -1, "Service Already exists", nil - } else if service.IpAddress == ipAddress && service.Port == strconv.Itoa(port) { - fmt.Println("Port and IP address combination is already in use") - return -1, "Port and IP address combination already in use", nil - } - } - } - - // Validate the format of the name - // if !util.IsLetter(name) { - // fmt.Println("Service name is not alphanumeric") - // return -1, "Services Name should be Alphanumeric", nil - // } - - fmt.Println("Service name and port are valid") - return 1, "", nil -} - -// ReadServices fetches all the Web Tunnel services -func ReadServices() (*model.ServicesList, error) { - // Get the CADDY_CONF_DIR environment variable - caddyConfDir := os.Getenv("CADDY_CONF_DIR") - if caddyConfDir == "" { - return nil, fmt.Errorf("CADDY_CONF_DIR environment variable is not set") - } - - // Ensure the directory exists - if _, err := os.Stat(caddyConfDir); os.IsNotExist(err) { - err = os.MkdirAll(caddyConfDir, 0755) // Create the directory with proper permissions - if err != nil { - return nil, fmt.Errorf("failed to create directory %s: %w", caddyConfDir, err) - } - } - - // Construct the file path - filePath := filepath.Join(caddyConfDir, "caddy.json") - fmt.Println("filePath : ", filePath) - - // Check if the file exists - if _, err := os.Stat(filePath); os.IsNotExist(err) { - // Create the file at the specified location - file, err := os.Create(filePath) - if err != nil { - return nil, fmt.Errorf("failed to create file at %s: %w", filePath, err) - } - defer file.Close() - - // Initialize with empty JSON structure - if _, writeErr := file.WriteString(`{"services": []}`); writeErr != nil { - return nil, fmt.Errorf("failed to write initial JSON to file: %w", writeErr) - } - } - - // Open the file - file, err := os.Open(filePath) - if err != nil { - return nil, fmt.Errorf("failed to open file: %w", err) - } - defer file.Close() - - // Read the file contents - b, err := io.ReadAll(file) - if err != nil { - return nil, fmt.Errorf("failed to read file: %w", err) - } - - // Handle empty file - if len(b) == 0 { - fmt.Println("Caddy file is empty while reading file", &model.ServicesList{Services: []model.Service{}}) - return &model.ServicesList{Services: []model.Service{}}, nil - } - - // Parse the JSON contents - var Services model.ServicesList - err = json.Unmarshal(b, &Services) - if err != nil { - return nil, fmt.Errorf("failed to parse JSON: %w", err) - } - - return &Services, nil -} - -// ReadWebTunnel fetches a Web Tunnel -func ReadService(tunnelName string) (*model.Service, error) { - Services, err := ReadServices() - if err != nil { - return nil, err - } - - var data model.Service - for _, Service := range Services.Services { - // print all the services - if Service.Name == tunnelName { - data.Name = Service.Name - data.Port = Service.Port - data.CreatedAt = Service.CreatedAt - data.Domain = Service.Domain - data.Status = Service.Status - break - } - } - - return &data, nil -} - -func AddServices(newService model.Service) error { - // Read existing services - servicesList, err := ReadServices() - if err != nil { - if err.Error() == "caddy file is empty while reading file" { - util.LogError("Caddy file is empty, proceeding to create a new Services", nil) - servicesList = &model.ServicesList{Services: []model.Service{}} // Initialize an empty Services struct - } else { - return err - } - } - - // Ensure the services list is initialized - if servicesList == nil || servicesList.Services == nil { - servicesList = &model.ServicesList{Services: []model.Service{}} - } - - // Append the new service - servicesList.Services = append(servicesList.Services, newService) - - // Marshal the updated services list to JSON - updatedJSON, err := json.MarshalIndent(servicesList, "", " ") - if err != nil { - util.LogError("JSON Marshal error: ", err) - return err - } - - //to save/update in /etc/caddy and service_conf_dir - err = SaveToFile(updatedJSON) - if err != nil { - util.LogError("failed to save/update data in config files: ", err) - return err - } - - // Update the Caddy configuration - err = UpdateCaddyConfig() - if err != nil { - util.LogError("Caddy configuration update error: ", err) - return err - } - - return nil -} - -func DeleteService(serviceName string) error { - services, err := ReadServices() - if err != nil { - return err - } - - var updatedServices []model.Service - for _, service := range services.Services { - if service.Name == serviceName { - continue - } - updatedServices = append(updatedServices, service) - } - - newServices := &model.ServicesList{ - Services: updatedServices, - } - - jsonData, err := json.MarshalIndent(newServices, "", " ") - if err != nil { - util.LogError("JSON Marshal error: ", err) - return err - } - - err = SaveToFile(jsonData) - if err != nil { - util.LogError("failed to save/update data in config files: ", err) - return err - } - - err = UpdateCaddyConfig() - if err != nil { - return err - } - - return nil -} - -// UpdateCaddyConfig updates Caddyfile -func UpdateCaddyConfig() error { - Services, err := ReadServices() - if err != nil { - return err - } - - path := filepath.Join(os.Getenv("CADDY_CONF_DIR"), os.Getenv("CADDY_INTERFACE_NAME")) - if util.FileExists(path) { - os.Remove(path) - } - - for _, Services := range Services.Services { - _, err := template.CaddyConfigTempl(Services) - if err != nil { - util.LogError("Caddy update error: ", err) - return err - } - } - - return nil -} - -func SaveToFile(updatedJSON []byte) error { - // Write the updated configuration back to the file - caddyConfigPath := filepath.Join(os.Getenv("CADDY_CONF_DIR"), "caddy.json") - - fmt.Println("caddyConfigPath : ", caddyConfigPath) - err := util.WriteFile(caddyConfigPath, updatedJSON) - if err != nil { - util.LogError("File write error: ", err) - return err - } - - // Write the updated configuration to the SERVICE_CONF_DIR in $HOME - homeDir, err := os.UserHomeDir() - if err != nil { - util.LogError("Error getting home directory: ", err) - return err - } - - serviceConfDir := filepath.Join(homeDir, os.Getenv("SERVICE_CONF_DIR")) - err = os.MkdirAll(serviceConfDir, 0755) // Ensure the directory exists - if err != nil { - util.LogError("Error creating SERVICE_CONF_DIR: ", err) - return err - } - - serviceConfigPath := filepath.Join(serviceConfDir, "caddy.json") - fmt.Println("serviceConfigPath: ", serviceConfigPath) - err = util.WriteFile(serviceConfigPath, updatedJSON) - if err != nil { - util.LogError("File write error for SERVICE_CONF_DIR: ", err) - return err - } - - // // Restart the Caddy service - // cmd := exec.Command("sudo", "systemctl", "restart", "caddy") - // err = cmd.Run() - // if err != nil { - // util.LogError("Failed to restart Caddy service: ", err) - // return err - // } - - return nil -} diff --git a/api/v1/middleware/middleware.go b/api/v1/middleware/middleware.go deleted file mode 100644 index 6842871..0000000 --- a/api/v1/middleware/middleware.go +++ /dev/null @@ -1,22 +0,0 @@ -package middleware - -import ( - "os" - - log "github.com/sirupsen/logrus" -) - -func CheckGatewayAccess(decryptedWalletAddress any) bool { - AllowedWalletAddress := os.Getenv("GATEWAY_WALLET") - if AllowedWalletAddress == "*" { - return true - } - if decryptedWalletAddress != AllowedWalletAddress { - log.WithFields(log.Fields{ - "err": "Updates Not Allowed for the Given Wallet Address", - }).Error("Updates Not Allowed for the Given Wallet Address") - return false - } - return true - -} diff --git a/api/v1/server/server.go b/api/v1/server/server.go deleted file mode 100644 index 40ed8ae..0000000 --- a/api/v1/server/server.go +++ /dev/null @@ -1,114 +0,0 @@ -package server - -import ( - "net/http" - "os" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" - "github.com/NetSepio/erebrus/util/pkg/speedtest" - - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" -) - -// ApplyRoutes applies router to gin Router -func ApplyRoutes(r *gin.RouterGroup) { - g := r.Group("/server") - { - g.GET("", readServer) - g.PATCH("", updateServer) - g.GET("/config", configServer) - g.GET("/speed", getServerSpeed) - } -} - -// swagger:route GET /server Server readServer -// -// # Read Server -// -// Retrieves the server details. -// responses: -// -// 200: serverSuccessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func readServer(c *gin.Context) { - server, err := core.ReadServer() - if err != nil { - log.WithFields(util.StandardFields).Error("Failure in reading server") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - response := core.MakeSucessResponse(200, "server details", server, nil, nil) - c.JSON(http.StatusOK, response) -} - -// swagger:route PATCH /server Server updateServer -// -// # Update Server -// -// Update the server with given details. -// responses: -// -// 200: serverSuccessResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func updateServer(c *gin.Context) { - var data model.Server - if err := c.ShouldBindJSON(&data); err != nil { - log.WithFields(util.StandardFields).Error("failed to bind") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - server, err := core.UpdateServer(&data) - if err != nil { - log.WithFields(util.StandardFields).Error("failed to update server") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - response := core.MakeSucessResponse(200, "server updated", server, nil, nil) - - c.JSON(http.StatusOK, response) -} - -// swagger:route GET /server/config Server configServer -// -// Get Server Configuration -// Retrieves the server configuration details. -// responses: -// -// 200: configResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func configServer(c *gin.Context) { - configData, err := core.ReadWgConfigFile() - if err != nil { - log.WithFields(util.StandardFields).Error("Failed to read wireguard config file") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - // return config as txt file - c.Header("Content-Disposition", "attachment; filename="+os.Getenv("WG_INTERFACE_NAME")+"") - c.Data(http.StatusOK, "application/config", configData) -} - -func getServerSpeed(c *gin.Context) { - res, err := speedtest.GetSpeedtestResults() - if err != nil { - log.WithFields(util.StandardFields).Error("Failed to read server speed") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - c.JSON(http.StatusOK, res) -} diff --git a/api/v1/server/server_doc.go b/api/v1/server/server_doc.go deleted file mode 100644 index 009817e..0000000 --- a/api/v1/server/server_doc.go +++ /dev/null @@ -1,113 +0,0 @@ -package server - -// swagger:response serverSucessResponse -// Response when the operation suceeds. -type ServerSucessResponse struct { - // in: body - Body struct { - // example: 201 - Status int64 - // example: true - Sucess bool - // example: sucess message - Message string - Body Server `json:"server"` - } -} - -// swagger:response serverStatusResponse -// Response for Server Status. -type ServerStatusResponse struct { - // in: body - Body Status -} - -// swagger:parameters updateServer -type ServerUpdateReqparam struct { - // Requestbody used for update server operations. - // in: body - Body Server `json:"server"` -} - -// swagger:model -// model for server details. -type Server struct { - //Server address - // example: ["10.0.0.1/24"] - Address []string `json:"address"` - //Port the server listens - // example: 51280 - ListenPort int64 `json:"listenPort"` - Mtu int64 `json:"mtu"` - //Private key for the server - // example: UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE= - PrivateKey string `json:"privateKey"` - //Public key for the server - // example: T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks= - PublicKey string `json:"publicKey"` - //Endpoint of the server - // example: region.example.com - Endpoint string `json:"endpoint"` - //Persistent keep alive for server - // example: 16 - PersistentKeepalive int64 `json:"persistentKeepalive"` - //DNS of the VPN server - // example: ["1.1.1.1"] - DNS []string `json:"dns"` - //IP addresses allowed to connect - // example: ["0.0.0.0/0","::/0" ] - AllowedIPs []string `json:"allowedips"` - //Pre up command - // example: echo WireGuard PreUp - PreUp string `json:"preUp"` - //Post up command - // example: iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE - PostUp string `json:"postUp"` - //Pre down command - // example: echo WireGuard PreDown - PreDown string `json:"preDown"` - //Post down command - // example: iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE - PostDown string `json:"postDown"` - // Updater email address - // example: admin@mail.com - UpdatedBy string `json:"updatedBy"` - //Time when server is created - // example: 26103870 - Created int64 `json:"created"` - //Time when server is created - // example: 26103870 - Updated int64 `json:"updated"` -} - -// swagger:model -// model for server status. -type Status struct { - //Server version - // example: 1.0 - Version string `json:"Version,omitempty"` - //Server Hostname - // example: ubuntu - Hostname string `json:"Hostname,omitempty"` - // Domain which server is running - // example: vpn.example.com - Domain string `json:"Domain,omitempty"` - // Server's public IP - // example: 14.10.35.65 - PublicIP string `json:"PublicIP,omitempty"` - // Port which gRPC service is running - // example: 5000 - GRPCPort string `json:"gRPCPort,omitempty"` - // Private IP of server host - // example: 10.0.1.5 - PrivateIP string `json:"PrivateIP,omitempty"` - // Port which HTTP service is running - // example: 4000 - HttpPort string `json:"HttpPort,omitempty"` - // Region where server running - // example:India/Banglore - Region string `json:"Region,omitempty"` - // VPN port - // example: 5128 - VPNPort string `json:"VPNPort,omitempty"` -} diff --git a/api/v1/service/caddy.go b/api/v1/service/caddy.go deleted file mode 100644 index cfdf45a..0000000 --- a/api/v1/service/caddy.go +++ /dev/null @@ -1,227 +0,0 @@ -package caddy - -import ( - "fmt" - "net/http" - "os" - "strconv" - "time" - - "github.com/NetSepio/erebrus/api/v1/middleware" - "github.com/NetSepio/erebrus/api/v1/service/util" - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" - "github.com/gin-gonic/gin" -) - -// ApplyRoutes applies router to gin Router -func ApplyRoutes(r *gin.RouterGroup) { - - g := r.Group("/caddy") - { - g.POST("", AddServices) - g.GET("", getServices) - g.GET(":name", getService) - g.DELETE(":name", deleteService) - } -} - -var resp map[string]interface{} - -// addTunnel adds new tunnel config -func AddServices(c *gin.Context) { - //post form parameters - var payload ServicePayload - - // Bind JSON payload to the struct - if err := c.ShouldBindJSON(&payload); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) - return - } - - // convert port string to int - portInt, err := strconv.Atoi(payload.Port) - if err != nil { - resp = util.Message(400, "Invalid Port") - c.JSON(http.StatusInternalServerError, resp) - return - } - - for { - - // check validity of Services name and port - value, msg, err := middleware.IsValidService(payload.Name, portInt, payload.IPAddress) - - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin..."+err.Error()) - c.JSON(http.StatusOK, resp) - break - } else if value == -1 { - if msg == "Port Already in use" { - continue - } - - resp = util.Message(404, msg) - c.JSON(http.StatusBadRequest, resp) - break - } else if value == 1 { - //create a Services struct object - var data model.Service - data.Name = payload.Name - data.Type = os.Getenv("NODE_TYPE") - data.Port = payload.Port - data.Domain = os.Getenv("DOMAIN") - data.IpAddress = payload.IPAddress - data.CreatedAt = time.Now().UTC().Format(time.RFC3339) - - //to add Services config - err := middleware.AddServices(data) - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin..."+err.Error()) - c.JSON(http.StatusInternalServerError, resp) - break - } else { - resp = util.MessageService(200, data) - c.JSON(http.StatusOK, resp) - break - } - } - } -} - -// getServices gets all Services config -func getServices(c *gin.Context) { - services, err := middleware.ReadServices() - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin...") - c.JSON(http.StatusInternalServerError, resp) - return - } - c.JSON(http.StatusOK, services) -} - -// getServices get specific Services config -func getService(c *gin.Context) { - //get parameter - name := c.Param("name") - - //read Services config - Services, err := middleware.ReadService(name) - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin...") - c.JSON(http.StatusInternalServerError, resp) - } - - //check if Services exists - if Services.Name == "" { - resp = util.Message(404, "Service Doesn't Exists") - c.JSON(http.StatusNotFound, resp) - } else { - port, err := strconv.Atoi(Services.Port) - if err != nil { - util.LogError("string conv error: ", err) - resp = util.Message(500, "Server error, Try after some time or Contact Admin...") - c.JSON(http.StatusInternalServerError, resp) - } else { - status, err := core.ScanPort(port) - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin...") - c.JSON(http.StatusInternalServerError, resp) - } else { - Services.Status = status - resp = util.MessageService(200, *Services) - c.JSON(http.StatusOK, resp) - } - } - } -} - -func deleteService(c *gin.Context) { - //get parameter - name := c.Param("name") - - //read Services config - Services, err := middleware.ReadService(name) - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin...") - c.JSON(http.StatusInternalServerError, resp) - } - - //check if Services exists - if Services.Name == "" { - resp = util.Message(400, "Service Doesn't Exists") - c.JSON(http.StatusBadRequest, resp) - } else { - //delete Services config - err = middleware.DeleteService(name) - if err != nil { - resp = util.Message(500, "Server error, Try after some time or Contact Admin...") - c.JSON(http.StatusInternalServerError, resp) - } else { - resp = util.Message(200, "Deleted Services "+name) - c.JSON(http.StatusOK, resp) - } - } - -} - -// func MiddlewareForCaddy(c *gin.Context) { - -// //check if NODE_CONFIG is set to standard or hpc - -// if strings.ToLower(os.Getenv("NODE_CONFIG")) != "standard" && strings.ToLower(os.Getenv("NODE_CONFIG")) != "hpc" { -// util.LogError("NODE_CONFIG not allowed", nil) -// c.JSON(http.StatusNotAcceptable, resp) -// os.Exit(1) -// } -// } - -// NodeConfigMiddleware checks if NODE_CONFIG is set to "standard" or "hpc". -func MiddlewareForCaddy() gin.HandlerFunc { - return func(c *gin.Context) { - nodeConfig := os.Getenv("NODE_CONFIG") - - if nodeConfig != "standard" && nodeConfig != "hpc" { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid NODE_CONFIG value. It must be 'standard' or 'hpc'.", - }) - c.Abort() // Stop further processing of the request - return - } - - // Pass to the next middleware/handler - c.Next() - } -} - -// AddServicesDirect adds a service using direct arguments. -func AddServicesDirect(domain string, agentName string, port int) error { - ipAddress := "127.0.0.1" // Replace with actual IP logic if needed - - // Validate the service - value, msg, err := middleware.IsValidService(agentName, port, ipAddress) - if err != nil { - return fmt.Errorf("server error: %v", err) - } - - if value == -1 { - return fmt.Errorf("validation failed: %s", msg) - } - - // Create a Services struct object - var data model.Service - data.Name = agentName - data.Type = os.Getenv("NODE_TYPE") - data.Port = strconv.Itoa(port) - data.Domain = agentName + "." + domain - data.IpAddress = ipAddress - data.CreatedAt = time.Now().UTC().Format(time.RFC3339) - - // Add the service - err = middleware.AddServices(data) - if err != nil { - return fmt.Errorf("error adding service: %v", err) - } - - return nil -} diff --git a/api/v1/service/template/template.go b/api/v1/service/template/template.go deleted file mode 100644 index 11a2473..0000000 --- a/api/v1/service/template/template.go +++ /dev/null @@ -1,69 +0,0 @@ -package template - -import ( - "bytes" - "fmt" - "html/template" - "os" - "path/filepath" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" -) - -var ( - caddyTpl = ` -# {{.Name}}, {{.IpAddress}}, {{.Port}}, {{.CreatedAt}} -{{.Domain}} { - reverse_proxy {{.IpAddress}}:{{.Port}} - log { - output file /var/log/caddy/{{.Domain}}.access.log { - roll_size 3MiB - roll_keep 5 - roll_keep_for 48h - } - format console - } - encode gzip zstd - - tls support@netsepio.com { - protocols tls1.2 tls1.3 - } -} -` -) - -// Caddy configuration file template -func CaddyConfigTempl(tunnel model.Service) ([]byte, error) { - t, err := template.New("config").Parse(caddyTpl) - if err != nil { - return nil, err - } - - var tplBuff bytes.Buffer - err = t.Execute(&tplBuff, tunnel) - if err != nil { - return nil, err - } - - // Get the directory path and ensure it exists - configDir := os.Getenv("CADDY_CONF_DIR") - if configDir == "" { - return nil, fmt.Errorf("CADDY_CONF_DIR environment variable is not set") - } - - // Ensure the directory exists - err = os.MkdirAll(configDir, 0755) // 0755 for read/write/execute permissions - if err != nil { - return nil, fmt.Errorf("error creating directory %s: %w", configDir, err) - } - - // Write the file - configFilePath := filepath.Join(configDir, os.Getenv("CADDY_INTERFACE_NAME")) - err = core.Writefile(configFilePath, tplBuff.Bytes()) - if err != nil { - return nil, fmt.Errorf("error writing file %s: %w", configFilePath, err) - } - - return tplBuff.Bytes(), nil -} diff --git a/api/v1/service/type.go b/api/v1/service/type.go deleted file mode 100644 index 819179b..0000000 --- a/api/v1/service/type.go +++ /dev/null @@ -1,7 +0,0 @@ -package caddy - -type ServicePayload struct { - Name string `json:"name" binding:"required"` - IPAddress string `json:"ipAddress" binding:"required"` - Port string `json:"port" binding:"required"` -} diff --git a/api/v1/service/util/util.go b/api/v1/service/util/util.go deleted file mode 100644 index 9f9baef..0000000 --- a/api/v1/service/util/util.go +++ /dev/null @@ -1,89 +0,0 @@ -package util - -import ( - "os" - "regexp" - - "github.com/NetSepio/erebrus/model" - log "github.com/sirupsen/logrus" -) - -var IsLetter = regexp.MustCompile(`^[a-z0-9]+$`).MatchString - -// StandardFields for logger -var StandardFields = log.Fields{ - "hostname": "HostServer", - "appname": "ServiceAPI", -} - -// ReadFile file content -func ReadFile(path string) (bytes []byte, err error) { - bytes, err = os.ReadFile(path) - if err != nil { - return nil, err - } - - return bytes, nil -} - -// WriteFile content to file -func WriteFile(path string, bytes []byte) (err error) { - err = os.WriteFile(path, bytes, 0644) - if err != nil { - return err - } - - return nil -} - -// FileExists check if file exists -func FileExists(name string) bool { - info, err := os.Stat(name) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} - -func CreateJSONFile(path string) error { - file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - return err - } - - _, err = file.Write([]byte("[]")) - if err != nil { - return err - } - - return nil -} - -// CheckError for checking any errors -func CheckError(message string, err error) { - if err != nil { - log.WithFields(StandardFields).Fatalf("%s %+v", message, err) - } -} - -// LogErrors for checking any errors -func LogError(message string, err error) { - if err != nil { - log.WithFields(StandardFields).Warnf("%s %+v", message, err) - } -} - -// Message Return Response as map -func Message(status int, message string) map[string]interface{} { - return map[string]interface{}{"status": status, "message": message} -} - -// MessageByte Return Response as byte array -func MessageService(status int, message model.Service) map[string]interface{} { - return map[string]interface{}{"status": status, "message": message} -} - -// MessageByte Return Response as byte array -func MessageServices(status int, message []model.Service) map[string]interface{} { - return map[string]interface{}{"status": status, "message": message} -} diff --git a/api/v1/status/status.go b/api/v1/status/status.go deleted file mode 100644 index 72ccdd9..0000000 --- a/api/v1/status/status.go +++ /dev/null @@ -1,39 +0,0 @@ -package status - -import ( - "net/http" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/util" - - "github.com/gin-gonic/gin" - log "github.com/sirupsen/logrus" -) - -// ApplyRoutes applies router to gin Router -func ApplyRoutes(r *gin.RouterGroup) { - r.GET("/status", GetStatus) -} - -// swagger:route GET /server/status Server statusServer -// -// # Get Server status -// -// Retrieves the server status details. -// responses: -// -// 200: serverStatusResponse -// 400: badRequestResponse -// 401: unauthorizedResponse -// 500: serverErrorResponse -func GetStatus(c *gin.Context) { - status_data, err := core.GetServerStatus() - if err != nil { - log.WithFields(util.StandardFields).Error("Failed to get server status") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - c.JSON(http.StatusInternalServerError, response) - return - } - - c.JSON(http.StatusOK, status_data) -} diff --git a/api/v1/status/status_doc.go b/api/v1/status/status_doc.go deleted file mode 100644 index 7fcaf1d..0000000 --- a/api/v1/status/status_doc.go +++ /dev/null @@ -1,33 +0,0 @@ -package status - -// swagger:model -// model for server status. -type Status struct { - //Server version - // example: 1.0 - Version string `json:"Version,omitempty"` - //Server Hostname - // example: ubuntu - Hostname string `json:"Hostname,omitempty"` - // Domain which server is running - // example: vpn.example.com - Domain string `json:"Domain,omitempty"` - // Server's public IP - // example: 14.10.35.65 - PublicIP string `json:"PublicIP,omitempty"` - // Port which gRPC service is running - // example: 5000 - GRPCPort string `json:"gRPCPort,omitempty"` - // Private IP of server host - // example: 10.0.1.5 - PrivateIP string `json:"PrivateIP,omitempty"` - // Port which HTTP service is running - // example: 4000 - HttpPort string `json:"HttpPort,omitempty"` - // Region where server running - // example:India/Banglore - Region string `json:"Region,omitempty"` - // VPN port - // example: 5128 - VPNPort string `json:"VPNPort,omitempty"` -} diff --git a/api/v1/v1.go b/api/v1/v1.go deleted file mode 100644 index c366b95..0000000 --- a/api/v1/v1.go +++ /dev/null @@ -1,31 +0,0 @@ -package v1 - -import ( - "os" - - "github.com/NetSepio/erebrus/api/v1/agents" - "github.com/NetSepio/erebrus/api/v1/authenticate" - "github.com/NetSepio/erebrus/api/v1/client" - "github.com/NetSepio/erebrus/api/v1/server" - caddy "github.com/NetSepio/erebrus/api/v1/service" - "github.com/NetSepio/erebrus/api/v1/status" - - "github.com/gin-gonic/gin" -) - -// ApplyRoutes Setup API EndPoints -func ApplyRoutes(r *gin.RouterGroup) { - v1 := r.Group("/v1.0") - { - client.ApplyRoutes(v1) - server.ApplyRoutes(v1) - status.ApplyRoutes(v1) - authenticate.ApplyRoutes(v1) - nodeConfig := os.Getenv("NODE_CONFIG") - if nodeConfig == "STANDARD" || nodeConfig == "HPC" || nodeConfig == "NEXUS" { - caddy.ApplyRoutes(v1) - agents.ApplyRoutes(v1) - } - - } -} diff --git a/cmd/erebrus/domains.go b/cmd/erebrus/domains.go new file mode 100644 index 0000000..d15dee6 --- /dev/null +++ b/cmd/erebrus/domains.go @@ -0,0 +1,22 @@ +package main + +import ( + "context" + "fmt" + + "github.com/NetSepio/erebrus/internal/store" +) + +func runServiceDomain(st *store.Store, ctx context.Context, args []string) error { + if len(args) < 3 { + return fmt.Errorf("usage: erebrus services domain add|remove ") + } + switch args[0] { + case "add": + return st.AddServiceDomain(ctx, args[1], args[2]) + case "remove": + return st.RemoveServiceDomain(ctx, args[1], args[2]) + default: + return fmt.Errorf("unknown domain subcommand %q", args[0]) + } +} diff --git a/cmd/erebrus/init.go b/cmd/erebrus/init.go new file mode 100644 index 0000000..ded9ba0 --- /dev/null +++ b/cmd/erebrus/init.go @@ -0,0 +1,251 @@ +package main + +import ( + "crypto/rand" + "encoding/base64" + "fmt" + "net/http" + "os" + "time" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/initcfg" + "github.com/NetSepio/erebrus/internal/p2p" +) + +func runInitCLI(args []string) error { + var ( + access = "public" + profile = "" + publicAddr = os.Getenv("WG_ENDPOINT_HOST") + gatewayURL = envOr("GATEWAY_URL", "https://gateway.erebrus.io") + nodeName = envOr("NODE_NAME", hostnameOr("erebrus-node")) + region = envOr("REGION", "unknown") + mnemonic = os.Getenv("MNEMONIC") + apiToken = os.Getenv("NODE_API_TOKEN") + envPath = initcfg.DefaultEnvPath + yes = false + appHosting = false + appDomain = "" + ) + + for i := 0; i < len(args); i++ { + switch args[i] { + case "--access", "--mode": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for %s", args[i-1]) + } + access = args[i] + case "--network-profile": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for --network-profile") + } + profile = args[i] + case "--public-address", "--wg-endpoint-host": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for public address") + } + publicAddr = args[i] + case "--gateway-url": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for --gateway-url") + } + gatewayURL = args[i] + case "--node-name": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for --node-name") + } + nodeName = args[i] + case "--region": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for --region") + } + region = args[i] + case "--env-file": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for --env-file") + } + envPath = args[i] + case "--enable-app-hosting": + appHosting = true + case "--domain": + i++ + if i >= len(args) { + return fmt.Errorf("missing value for --domain") + } + appDomain = args[i] + case "-y", "--yes": + yes = true + case "-h", "--help": + printInitHelp() + return nil + default: + return fmt.Errorf("unknown argument: %s", args[i]) + } + } + + mode, err := initcfg.ParseAccessMode(access) + if err != nil { + return err + } + if publicAddr == "" { + return fmt.Errorf("public address is required (--public-address or WG_ENDPOINT_HOST)") + } + + if mnemonic == "" { + mnemonic, err = p2p.GenerateMnemonic() + if err != nil { + return err + } + fmt.Println("Generated a new node identity (12-word recovery phrase).") + fmt.Println("Back it up now — it cannot be recovered.") + if !yes { + fmt.Print("Press Enter to continue...") + fmt.Scanln() + } + } + if apiToken == "" { + apiToken = randToken() + } + + var netProfile config.NetworkProfile + if profile != "" { + m, err := config.ParseModeSettings(string(mode), "", profile) + if err != nil { + return err + } + netProfile = m.NetworkProfile + } + + opts := initcfg.Options{ + AccessMode: mode, + NetworkProfile: netProfile, + NodeName: nodeName, + Region: region, + Mnemonic: mnemonic, + NodeAPIToken: apiToken, + GatewayURL: gatewayURL, + PublicAddress: publicAddr, + EnableStealth: true, + EnableAppHosting: appHosting, + AppWildcardDomain: appDomain, + PublicGatewayEnabled: appHosting, + } + if appHosting && appDomain != "" { + opts.PublicDomain = appDomain + opts.WildcardDomain = "*." + appDomain + } + + if err := initcfg.WriteFile(envPath, opts); err != nil { + return err + } + fmt.Printf("Wrote internal config: %s\n", envPath) + fmt.Printf("Node API key: %s\n", apiToken) + fmt.Println() + fmt.Println("Next steps:") + fmt.Printf(" 1. Link for systemd: EnvironmentFile=%s\n", envPath) + fmt.Println(" 2. systemctl enable --now erebrus") + fmt.Println(" 3. erebrus status") + return nil +} + +func printInitHelp() { + fmt.Println(`Usage: erebrus init [options] + +Initialize a bare-metal node (writes internal env file — do not edit by hand). + +Options: + --access private|public Gateway visibility (default: public) + --network-profile bridge|host-network|native + --public-address Public address clients dial (required) + --gateway-url Control plane URL + --node-name + --region + --enable-app-hosting Public edge (public mode) + --domain e.g. apps.example.com + --env-file default: /etc/erebrus/erebrus.env + -y, --yes Non-interactive + +After start, verify with: erebrus status`) +} + +func randToken() string { + b := make([]byte, 24) + _, _ = rand.Read(b) + s := base64.RawURLEncoding.EncodeToString(b) + if len(s) > 32 { + s = s[:32] + } + return s +} + +func hostnameOr(def string) string { + if h, err := os.Hostname(); err == nil && h != "" { + return h + } + return def +} + +func runDoctorCLI(args []string) error { + if len(args) == 0 { + return fmt.Errorf("usage: erebrus doctor network|gateway|config") + } + switch args[0] { + case "config": + return doctorConfig() + case "network": + return doctorNetwork() + case "gateway": + return doctorGateway() + default: + return fmt.Errorf("unknown doctor target: %s", args[0]) + } +} + +func doctorConfig() error { + path := initcfg.DefaultEnvPath + if p := os.Getenv("EREBRUS_ENV_FILE"); p != "" { + path = p + } + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("internal config not found at %s", path) + } + if info.Mode().Perm() > 0o600 { + fmt.Printf("! config permissions %o — recommend 600\n", info.Mode().Perm()) + } + _ = os.Setenv("LOAD_CONFIG_FILE", "") + // Load via godotenv from file would need reading - use preboot with env from file + fmt.Printf("ok config file exists: %s (%d bytes)\n", path, info.Size()) + fmt.Println(" Run: erebrus status --preboot (with EnvironmentFile loaded) or erebrus status after start") + return nil +} + +func doctorNetwork() error { + return runStatusCLI([]string{"--json"}) +} + +func doctorGateway() error { + url := envOr("GATEWAY_URL", "") + if url == "" { + return fmt.Errorf("GATEWAY_URL not set") + } + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get(url + "/healthz") + if err != nil { + return fmt.Errorf("gateway unreachable: %w", err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("gateway healthz returned %d", resp.StatusCode) + } + fmt.Printf("ok gateway reachable: %s/healthz\n", url) + return runStatusCLI(nil) +} \ No newline at end of file diff --git a/cmd/erebrus/main.go b/cmd/erebrus/main.go new file mode 100644 index 0000000..217f29a --- /dev/null +++ b/cmd/erebrus/main.go @@ -0,0 +1,364 @@ +// Command erebrus is the Erebrus v2 VPN node. It serves an HTTP REST API +// (/api/v2), manages WireGuard peers backed by SQLite, derives its identity +// and DID from a mnemonic, and advertises on the libp2p DHT. The v1 gRPC +// server, libp2p status pubsub, Docker-agent and Caddy subsystems are gone. +package main + +import ( + "context" + "fmt" + "log/slog" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/NetSepio/erebrus/internal/api" + "github.com/NetSepio/erebrus/internal/config" + dnspkg "github.com/NetSepio/erebrus/internal/dns" + "github.com/NetSepio/erebrus/internal/edge" + "github.com/NetSepio/erebrus/internal/gatewayclient" + "github.com/NetSepio/erebrus/internal/node" + "github.com/NetSepio/erebrus/internal/p2p" + "github.com/NetSepio/erebrus/internal/readiness" + "github.com/NetSepio/erebrus/internal/registrar" + "github.com/NetSepio/erebrus/internal/services" + "github.com/NetSepio/erebrus/internal/stealth" + "github.com/NetSepio/erebrus/internal/store" + "github.com/NetSepio/erebrus/internal/telemetry" + "github.com/NetSepio/erebrus/internal/transport/probe" + "github.com/NetSepio/erebrus/internal/wg" + "github.com/joho/godotenv" +) + +func main() { + // Lightweight CLI subcommands used by the installer and operators. These run + // without loading the full node configuration. + if len(os.Args) > 1 { + switch os.Args[1] { + case "genmnemonic": + m, err := p2p.GenerateMnemonic() + if err != nil { + fmt.Fprintln(os.Stderr, "genmnemonic:", err) + os.Exit(1) + } + fmt.Println(m) + return + case "version", "--version", "-v": + fmt.Println(config.Version) + return + case "templates": + if err := runTemplatesCLI(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "templates:", err) + os.Exit(1) + } + return + case "serve": + if err := runServeCLI(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "serve:", err) + os.Exit(1) + } + return + case "services": + if err := runServicesCLI(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "services:", err) + os.Exit(1) + } + return + case "rotate": + if len(os.Args) < 3 || os.Args[2] != "carriers" { + fmt.Fprintln(os.Stderr, "usage: erebrus rotate carriers [--grace-period 24h] [--peer ]") + os.Exit(2) + } + if err := runRotateCarriers(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "rotate:", err) + os.Exit(1) + } + return + case "status": + if err := runStatusCLI(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "status:", err) + os.Exit(1) + } + return + case "init": + if err := runInitCLI(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "init:", err) + os.Exit(1) + } + return + case "doctor": + if err := runDoctorCLI(os.Args[2:]); err != nil { + fmt.Fprintln(os.Stderr, "doctor:", err) + os.Exit(1) + } + return + } + } + + if os.Getenv("LOAD_CONFIG_FILE") == "" { + _ = godotenv.Load() + } + + cfg := config.Load() + telemetry.InitLogger(cfg.RunType == "debug") + + if err := cfg.Validate(); err != nil { + slog.Error("invalid configuration", "err", err) + os.Exit(1) + } + for _, w := range cfg.Mode.Warnings { + slog.Warn(w) + } + slog.Info("runtime settings", + "access", cfg.Mode.RuntimeMode, + "deploy", cfg.Mode.Deploy, + "network_profile", cfg.Mode.NetworkProfile, + "api_bind", fmt.Sprintf("%s:%s", cfg.BindAddr, cfg.HTTPPort), + ) + + if err := run(cfg); err != nil { + slog.Error("node exited with error", "err", err) + os.Exit(1) + } +} + +func run(cfg *config.Config) error { + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + if best, ok := probe.Select(ctx, &probe.LocalProber{ + StealthEnabled: cfg.EnableStealth, + WGPort: cfg.WGEndpointPortInt(), + VLESSPort: cfg.VLESSPortInt(), + Hysteria2Port: cfg.Hysteria2PortInt(), + }, cfg.EnableStealth); ok { + slog.Info("transport ladder", "preferred", best.Kind, "score", best.Score) + } + + if err := os.MkdirAll(cfg.StateDir, 0o700); err != nil { + return fmt.Errorf("create state dir: %w", err) + } + + // Identity / DID from the mnemonic. + peerID, did, err := p2p.PeerIDFromMnemonic(cfg.Mnemonic) + if err != nil { + return fmt.Errorf("derive identity: %w", err) + } + slog.Info("node identity", "peer_id", peerID, "did", did, "node", cfg.NodeName) + + // Store. + st, err := store.Open(cfg.DBPath()) + if err != nil { + return fmt.Errorf("open store: %w", err) + } + defer st.Close() + + // Metrics. + metrics := telemetry.NewMetrics() + + // WireGuard. + wgm := wg.New(cfg, st, wg.NewController()) + wgErr := wgm.Init(ctx) + wgOK := wgErr == nil + if !wgOK { + // Non-fatal: the conf is written; the interface may need NET_ADMIN. + slog.Warn("wireguard interface init incomplete", "err", wgErr) + } + + // Stealth carriers (sing-box VLESS+REALITY / Hysteria2). Init always runs so + // credential bundles can advertise carrier params; Start is a no-op when + // disabled. A start failure (e.g. port in use) is non-fatal — WireGuard + // still serves the fast path. + stealthMgr := stealth.New(cfg, st) + stealthOK := false + if err := stealthMgr.Init(ctx); err != nil { + slog.Warn("stealth init failed; carriers unavailable", "err", err) + } else if err := stealthMgr.Start(ctx); err != nil { + slog.Warn("stealth carriers failed to start", "err", err) + } else { + stealthOK = cfg.EnableStealth + if cfg.EnableStealth { + slog.Info("stealth carriers listening", "vless_port", cfg.VLESSPort, "hysteria2_port", cfg.Hysteria2Port) + } + defer stealthMgr.Close() + } + + // libp2p host (identity + DID + DHT advertise). Best-effort. + p2pNode, err := p2p.Start(ctx, cfg.Mnemonic, cfg.P2PListenPort, cfg.GatewayPeerMultiaddr) + if err != nil { + slog.Warn("libp2p host failed to start", "err", err) + } else { + defer p2pNode.Close() + } + + // On-chain registration (noop in v2.0). + reg := registrar.New(cfg.ChainRegistration) + if err := reg.Register(ctx, registrar.NodeIdentity{ + PeerID: peerID, + DID: did, + IPHash: registrar.HashIP(cfg.WGEndpointHost), + Region: cfg.Region, + Wallet: "", + Version: cfg.Version, + }); err != nil { + slog.Warn("registrar register failed", "err", err) + } + + // Private DNS (optional). + svcReg := &services.Registry{St: st} + if cfg.PrivateDNSEnabled { + dnsCfg := dnspkg.Config{ + Enabled: true, + Domain: cfg.PrivateDNSDomain, + ListenAddr: dnspkg.DefaultListenAddr(cfg.WGIPv4Subnet, cfg.PrivateDNSAddr), + Upstream: cfg.UpstreamDNS, + QueryLogs: cfg.DNSQueryLogs, + } + if err := dnsCfg.Validate(); err != nil { + slog.Warn("private DNS disabled", "err", err) + } else { + go func() { + if err := dnspkg.New(dnsCfg, svcReg).Start(ctx); err != nil { + slog.Warn("private DNS stopped", "err", err) + } + }() + } + } + + // Core service + HTTP API. + svc := node.New(cfg, st, wgm, stealthMgr, metrics) + apiServer := api.NewServer(cfg, svc, api.Identity{PeerID: peerID, DID: did}) + apiServer.SetWireGuardPublicKeyProvider(wgm.ServerPublicKey) + svc.SetAPIStatusHook(apiServer.SetStatus) + + // Public edge proxy (Gateway Mode only, opt-in). + if cfg.Mode.IsPublic() && cfg.PublicGatewayEnabled { + edgeProxy := &edge.Proxy{Reg: svcReg, St: st, WildcardDomain: cfg.WildcardDomain} + edgeSrv := &http.Server{ + Addr: ":9081", + Handler: edgeProxy.Handler(), + ReadHeaderTimeout: 10 * time.Second, + } + go func() { + slog.Info("public edge proxy listening", "addr", edgeSrv.Addr) + if err := edgeSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + slog.Warn("edge proxy error", "err", err) + } + }() + defer func() { + shut, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + _ = edgeSrv.Shutdown(shut) + }() + } + + // Gateway control plane (WebSocket + PASETO). Best-effort when configured. + var gwClient *gatewayclient.Client + if cfg.GatewayEnabled() { + creds, err := gatewayclient.LoadCredentials(ctx, st) + if err != nil { + slog.Warn("load gateway credentials failed", "err", err) + creds = &gatewayclient.Credentials{} + } + nodeID := creds.NodeID + nodeToken := creds.NodeToken + if nodeID == "" { + nodeID = cfg.NodeID + } + if nodeToken == "" { + nodeToken = cfg.NodeToken + } + if creds.NodeKey != "" { + cfg.NodeKey = creds.NodeKey + cfg.NodeAPIToken = creds.NodeKey + } + if creds.GatewayPublicKey != "" { + cfg.GatewayPublicKey = creds.GatewayPublicKey + } + if (nodeID == "" || nodeToken == "") && cfg.GatewayAutoRegister { + reg, err := gatewayclient.Register(ctx, gatewayclient.RegistrationInput{ + GatewayURL: cfg.GatewayURL, + OrgEnrollmentSecret: cfg.OrgEnrollmentSecret, + WalletChain: cfg.WalletChain, + Mnemonic: cfg.Mnemonic, + PeerID: peerID, + DID: did, + Name: cfg.NodeName, + Region: cfg.Region, + APIBaseURL: cfg.PublicAPIBaseURL(), + NodeKey: cfg.EffectiveNodeKey(), + AccessMode: cfg.Mode.GatewayAccessMode(), + }) + if err != nil { + slog.Warn("gateway registration failed", "err", err) + } else { + nodeID, nodeToken = reg.NodeID, reg.NodeToken + cfg.NodeID = nodeID + cfg.NodeToken = nodeToken + cfg.NodeKey = reg.NodeKey + cfg.NodeAPIToken = reg.NodeKey + if reg.GatewayPublicKey != "" { + cfg.GatewayPublicKey = reg.GatewayPublicKey + } + if err := gatewayclient.SaveCredentials(ctx, st, &gatewayclient.Credentials{ + NodeID: nodeID, NodeToken: nodeToken, NodeKey: reg.NodeKey, GatewayPublicKey: cfg.GatewayPublicKey, + }); err != nil { + slog.Warn("persist gateway credentials failed", "err", err) + } else { + slog.Info("gateway registered", "node_id", nodeID) + } + } + } + cfg.NodeID = nodeID + if nodeID != "" && nodeToken != "" { + bridge := node.NewGatewayBridge(svc, peerID, did, nodeID) + gwClient = gatewayclient.New(cfg.GatewayURL, nodeID, nodeToken, bridge, bridge, bridge.Status) + go gwClient.Run(ctx) + } else { + slog.Warn("gateway URL set but node credentials missing — WS control plane disabled") + } + } + + apiServer.SetReadinessProvider(func() readiness.Input { + gwReg := false + gwConn := false + if cfg.GatewayEnabled() { + if cred, err := gatewayclient.LoadCredentials(ctx, st); err == nil && cred.NodeID != "" && cred.NodeToken != "" { + gwReg = true + } + if gwClient != nil { + gwConn = gwClient.Connected() + } + } + return readiness.Input{ + Cfg: cfg, + IdentityConfigured: true, + GatewayRegistered: gwReg, + GatewayConnected: gwConn, + WireGuardOK: wgOK, + StealthListening: stealthOK, + } + }) + + srv := &http.Server{ + Addr: fmt.Sprintf("%s:%s", cfg.BindAddr, cfg.HTTPPort), + Handler: apiServer.Router(), + ReadHeaderTimeout: 10 * time.Second, + } + + go func() { + slog.Info("HTTP API listening", "addr", srv.Addr) + if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { + slog.Error("http server error", "err", err) + stop() + } + }() + + <-ctx.Done() + slog.Info("shutting down") + shutCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + return srv.Shutdown(shutCtx) +} diff --git a/cmd/erebrus/publish.go b/cmd/erebrus/publish.go new file mode 100644 index 0000000..822b6b0 --- /dev/null +++ b/cmd/erebrus/publish.go @@ -0,0 +1,55 @@ +package main + +import ( + "context" + "fmt" + "strings" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/services" +) + +func runServicePublish(reg *services.Registry, ctx context.Context, args []string) error { + if len(args) < 1 { + return fmt.Errorf("usage: erebrus services publish [--public]") + } + id := args[0] + public := false + for _, a := range args[1:] { + if a == "--public" { + public = true + } + } + cfg := config.Load() + svc, err := reg.Get(ctx, id) + if err != nil { + return err + } + if !cfg.Mode.IsPublic() { + return fmt.Errorf("public publish requires public access mode") + } + domain := cfg.PublicDomain + if domain == "" { + domain = cfg.AppWildcardDomain + } + hostname := "" + if public && domain != "" { + hostname = fmt.Sprintf("%s.%s", svc.Name, strings.TrimPrefix(domain, "*.")) + } + st := reg.St + if err := st.SetServicePublic(ctx, id, hostname, public); err != nil { + return err + } + if public { + svc.Visibility = "public" + svc.AuthMode = "public" + } + svc.Public = public + svc.PublicHost = hostname + _, err = reg.Publish(ctx, *svc) + return err +} + +func runServiceUnpublish(reg *services.Registry, ctx context.Context, id string) error { + return reg.St.SetServicePublic(ctx, id, "", false) +} diff --git a/cmd/erebrus/rotate.go b/cmd/erebrus/rotate.go new file mode 100644 index 0000000..85daf8c --- /dev/null +++ b/cmd/erebrus/rotate.go @@ -0,0 +1,67 @@ +package main + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/NetSepio/erebrus/internal/carriers" + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/stealth" + "github.com/NetSepio/erebrus/internal/store" +) + +func runRotateCarriers(args []string) error { + grace := 24 * time.Hour + peerID := "" + for i := 0; i < len(args); i++ { + switch args[i] { + case "--grace-period": + if i+1 >= len(args) { + return fmt.Errorf("--grace-period requires a value") + } + d, err := time.ParseDuration(args[i+1]) + if err != nil { + return fmt.Errorf("invalid grace period: %w", err) + } + grace = d + i++ + case "--peer": + if i+1 >= len(args) { + return fmt.Errorf("--peer requires a value") + } + peerID = args[i+1] + i++ + case "carriers": + continue + default: + if strings.HasPrefix(args[i], "-") { + return fmt.Errorf("unknown flag %s", args[i]) + } + } + } + + // Carrier rotation is a local DB operation: it only needs the state store + // and the stealth secrets (node_settings). It must NOT run full node + // validation (WG_ENDPOINT_HOST/MNEMONIC) or bind the carrier ports — doing + // so would clash with an already-running node. + cfg := config.Load() + st, err := store.Open(cfg.DBPath()) + if err != nil { + return err + } + defer st.Close() + + stealthMgr := stealth.New(cfg, st) + if err := stealthMgr.Init(context.Background()); err != nil { // loads/creates secrets, no listeners + return err + } + + rot := &carriers.Rotator{St: st, Stealth: stealthMgr} + if err := rot.Rotate(context.Background(), carriers.Options{GracePeriod: grace, PeerID: peerID}); err != nil { + return err + } + fmt.Println("carrier secrets rotated. Restart the node to serve the new credentials; old ones remain valid for the grace period.") + return nil +} diff --git a/cmd/erebrus/services.go b/cmd/erebrus/services.go new file mode 100644 index 0000000..e877939 --- /dev/null +++ b/cmd/erebrus/services.go @@ -0,0 +1,105 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strconv" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/services" + "github.com/NetSepio/erebrus/internal/store" +) + +func runServicesCLI(args []string) error { + if len(args) == 0 { + return fmt.Errorf("usage: erebrus services list|inspect |remove |publish [--public]|unpublish |domain add|remove ") + } + cfg := config.Load() + st, err := store.Open(cfg.DBPath()) + if err != nil { + return err + } + defer st.Close() + reg := &services.Registry{St: st} + ctx := context.Background() + + switch args[0] { + case "list": + items, err := reg.List(ctx) + if err != nil { + return err + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(items) + case "inspect": + if len(args) < 2 { + return fmt.Errorf("usage: erebrus services inspect ") + } + svc, err := reg.Get(ctx, args[1]) + if err != nil { + return err + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(svc) + case "remove": + if len(args) < 2 { + return fmt.Errorf("usage: erebrus services remove ") + } + return reg.Remove(ctx, args[1]) + case "publish": + return runServicePublish(reg, ctx, args[1:]) + case "unpublish": + if len(args) < 2 { + return fmt.Errorf("usage: erebrus services unpublish ") + } + return runServiceUnpublish(reg, ctx, args[1]) + case "domain": + return runServiceDomain(st, ctx, args[1:]) + default: + return fmt.Errorf("unknown services subcommand %q", args[0]) + } +} + +func runServeCLI(args []string) error { + name, port, typ := "", 0, "" + for i := 0; i < len(args); i++ { + switch args[i] { + case "--name": + name = args[i+1] + i++ + case "--port": + p, err := strconv.Atoi(args[i+1]) + if err != nil { + return err + } + port = p + i++ + case "--type": + typ = args[i+1] + i++ + } + } + if name == "" || port == 0 { + return fmt.Errorf("usage: erebrus serve --name --port [--type ]") + } + cfg := config.Load() + st, err := store.Open(cfg.DBPath()) + if err != nil { + return err + } + defer st.Close() + reg := &services.Registry{St: st} + svc, err := reg.Publish(context.Background(), services.Service{ + Name: name, Port: port, Type: typ, + }) + if err != nil { + return err + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(svc) +} diff --git a/cmd/erebrus/status.go b/cmd/erebrus/status.go new file mode 100644 index 0000000..c4818c6 --- /dev/null +++ b/cmd/erebrus/status.go @@ -0,0 +1,159 @@ +package main + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/readiness" +) + +func runStatusCLI(args []string) error { + preboot := false + jsonOut := false + url := fmt.Sprintf("http://127.0.0.1:%s/api/v2/status", envOr("HTTP_PORT", "9080")) + + for _, a := range args { + switch a { + case "--preboot": + preboot = true + case "--json": + jsonOut = true + default: + return fmt.Errorf("unknown flag: %s", a) + } + } + + if preboot { + cfg := config.Load() + if err := cfg.Validate(); err != nil { + return err + } + rep := readiness.Preboot(cfg) + return printStatus(rep, jsonOut) + } + + client := &http.Client{Timeout: 5 * time.Second} + resp, err := client.Get(url) + if err != nil { + return fmt.Errorf("node not reachable at %s: %w (is erebrus running?)", url, err) + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("status %d: %s", resp.StatusCode, string(body)) + } + + if jsonOut { + fmt.Println(string(body)) + var out struct { + Readiness readiness.Report `json:"readiness"` + } + if err := json.Unmarshal(body, &out); err != nil { + return err + } + if !out.Readiness.OK { + os.Exit(1) + } + return nil + } + + var out struct { + AccessMode string `json:"access_mode"` + Identity struct { + PeerID string `json:"peer_id"` + DID string `json:"did"` + WalletChain string `json:"wallet_chain"` + WalletLabel string `json:"wallet_chain_label"` + WalletAddress string `json:"wallet_address"` + } `json:"identity"` + Endpoints struct { + WireGuard struct { + PublicKey string `json:"public_key"` + Endpoint string `json:"endpoint"` + } `json:"wireguard"` + } `json:"endpoints"` + Readiness readiness.Report `json:"readiness"` + Capabilities map[string]any `json:"capabilities"` + } + if err := json.Unmarshal(body, &out); err != nil { + return err + } + + fmt.Printf("Access: %s\n", out.AccessMode) + if hint, ok := out.Capabilities["access_hint"].(string); ok && hint != "" { + fmt.Printf(" %s\n", hint) + } + if region, ok := out.Capabilities["region_label"].(string); ok && region != "" { + fmt.Printf("Region: %s\n", region) + } + fmt.Printf("Peer ID: %s\n", out.Identity.PeerID) + fmt.Printf("DID: %s\n", out.Identity.DID) + if out.Identity.WalletAddress != "" { + label := out.Identity.WalletLabel + if label == "" { + label = out.Identity.WalletChain + } + fmt.Printf("Wallet (%s): %s\n", label, out.Identity.WalletAddress) + } + if out.Endpoints.WireGuard.PublicKey != "" { + fmt.Printf("WireGuard public key: %s\n", out.Endpoints.WireGuard.PublicKey) + if out.Endpoints.WireGuard.Endpoint != "" { + fmt.Printf("WireGuard endpoint: %s\n", out.Endpoints.WireGuard.Endpoint) + } + } + fmt.Printf("Readiness: %s\n", readiness.SummaryLine(out.Readiness)) + for _, c := range out.Readiness.Checks { + mark := "ok" + if !c.OK { + mark = "FAIL" + } + opt := "" + if c.Optional { + opt = " (optional)" + } + fmt.Printf(" [%s] %s%s", mark, c.ID, opt) + if c.Detail != "" { + fmt.Printf(" — %s", c.Detail) + } + fmt.Println() + } + for _, w := range out.Readiness.Warnings { + fmt.Printf(" ! %s\n", w) + } + if !out.Readiness.OK { + os.Exit(1) + } + return nil +} + +func printStatus(rep readiness.Report, jsonOut bool) error { + if jsonOut { + b, _ := json.MarshalIndent(rep, "", " ") + fmt.Println(string(b)) + } else { + fmt.Printf("Readiness (preboot): %s\n", readiness.SummaryLine(rep)) + for _, c := range rep.Checks { + mark := "ok" + if !c.OK { + mark = "FAIL" + } + fmt.Printf(" [%s] %s — %s\n", mark, c.ID, c.Detail) + } + } + if !rep.OK { + os.Exit(1) + } + return nil +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} \ No newline at end of file diff --git a/cmd/erebrus/templates.go b/cmd/erebrus/templates.go new file mode 100644 index 0000000..32422ea --- /dev/null +++ b/cmd/erebrus/templates.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/services" + "github.com/NetSepio/erebrus/internal/store" + "github.com/NetSepio/erebrus/internal/templates" +) + +func runTemplatesCLI(args []string) error { + if len(args) == 0 { + return fmt.Errorf("usage: erebrus templates list|install ") + } + switch args[0] { + case "list": + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(templates.Catalog()) + case "install": + if len(args) < 2 { + return fmt.Errorf("usage: erebrus templates install ") + } + cfg := config.Load() + st, err := store.Open(cfg.DBPath()) + if err != nil { + return err + } + defer st.Close() + reg := &services.Registry{St: st} + svc, err := templates.Install(context.Background(), reg, args[1]) + if err != nil { + return err + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(svc) + default: + return fmt.Errorf("unknown templates subcommand %q", args[0]) + } +} diff --git a/contract/peaq_contract.go b/contract/peaq_contract.go deleted file mode 100644 index 73d7cae..0000000 --- a/contract/peaq_contract.go +++ /dev/null @@ -1,3110 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package contract - -import ( - "errors" - "math/big" - "strings" - - ethereum "github.com/ethereum/go-ethereum" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/event" -) - -// Reference imports to suppress errors if they are not otherwise used. -var ( - _ = errors.New - _ = big.NewInt - _ = strings.NewReader - _ = ethereum.NotFound - _ = bind.Bind - _ = common.Big1 - _ = types.BloomLookup - _ = event.NewSubscription - _ = abi.ConvertType -) - -// DIDAttribute is an auto generated low-level Go binding around an user-defined struct. -type DIDAttribute struct { - Name []byte - Value []byte - Validity uint32 - Created *big.Int -} - -// ContractMetaData contains all meta data concerning the Contract contract. -var ContractMetaData = &bind.MetaData{ - ABI: "[{\"type\":\"constructor\",\"inputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"AccessControlBadConfirmation\",\"type\":\"error\",\"inputs\":[]},{\"name\":\"AccessControlUnauthorizedAccount\",\"type\":\"error\",\"inputs\":[{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"neededRole\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}]},{\"name\":\"ERC721IncorrectOwner\",\"type\":\"error\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"name\":\"ERC721InsufficientApproval\",\"type\":\"error\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"ERC721InvalidApprover\",\"type\":\"error\",\"inputs\":[{\"name\":\"approver\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"name\":\"ERC721InvalidOperator\",\"type\":\"error\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"name\":\"ERC721InvalidOwner\",\"type\":\"error\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"name\":\"ERC721InvalidReceiver\",\"type\":\"error\",\"inputs\":[{\"name\":\"receiver\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"name\":\"ERC721InvalidSender\",\"type\":\"error\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"internalType\":\"address\"}]},{\"name\":\"ERC721NonexistentToken\",\"type\":\"error\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}]},{\"name\":\"AddAttribute\",\"type\":\"event\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"did_account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"validity\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"name\":\"Approval\",\"type\":\"event\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"approved\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"name\":\"ApprovalForAll\",\"type\":\"event\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"operator\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"approved\",\"type\":\"bool\",\"indexed\":false,\"internalType\":\"bool\"}],\"anonymous\":false},{\"name\":\"CheckpointCreated\",\"type\":\"event\",\"inputs\":[{\"name\":\"nodeId\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"}],\"anonymous\":false},{\"name\":\"NodeDeactivated\",\"type\":\"event\",\"inputs\":[{\"name\":\"nodeId\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"nodeAddr\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"name\":\"NodeRegistered\",\"type\":\"event\",\"inputs\":[{\"name\":\"id\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"did\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"name\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"addr\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"spec\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"config\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"ipAddress\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"region\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"location\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"metadata\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"owner\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"registrant\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"name\":\"NodeStatusUpdated\",\"type\":\"event\",\"inputs\":[{\"name\":\"nodeId\",\"type\":\"string\",\"indexed\":false,\"internalType\":\"string\"},{\"name\":\"newStatus\",\"type\":\"uint8\",\"indexed\":false,\"internalType\":\"enumNetSepioV1.Status\"}],\"anonymous\":false},{\"name\":\"RemoveAttribute\",\"type\":\"event\",\"inputs\":[{\"name\":\"did_account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"}],\"anonymous\":false},{\"name\":\"RoleAdminChanged\",\"type\":\"event\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"previousAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"newAdminRole\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"}],\"anonymous\":false},{\"name\":\"RoleGranted\",\"type\":\"event\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"name\":\"RoleRevoked\",\"type\":\"event\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"indexed\":true,\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"sender\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"}],\"anonymous\":false},{\"name\":\"Transfer\",\"type\":\"event\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"indexed\":true,\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"indexed\":true,\"internalType\":\"uint256\"}],\"anonymous\":false},{\"name\":\"UpdateAttribute\",\"type\":\"event\",\"inputs\":[{\"name\":\"sender\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"did_account\",\"type\":\"address\",\"indexed\":false,\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"indexed\":false,\"internalType\":\"bytes\"},{\"name\":\"validity\",\"type\":\"uint32\",\"indexed\":false,\"internalType\":\"uint32\"}],\"anonymous\":false},{\"name\":\"ADMIN_ROLE\",\"type\":\"function\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"name\":\"DEFAULT_ADMIN_ROLE\",\"type\":\"function\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"name\":\"OPERATOR_ROLE\",\"type\":\"function\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"name\":\"addAttribute\",\"type\":\"function\",\"inputs\":[{\"name\":\"did_account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"validity_for\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"name\":\"approve\",\"type\":\"function\",\"inputs\":[{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"balanceOf\",\"type\":\"function\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"name\":\"checkpoint\",\"type\":\"function\",\"inputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"name\":\"counter\",\"type\":\"function\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"stateMutability\":\"view\"},{\"name\":\"createCheckpoint\",\"type\":\"function\",\"inputs\":[{\"name\":\"nodeId\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"data\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"deactivateNode\",\"type\":\"function\",\"inputs\":[{\"name\":\"nodeId\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"getApproved\",\"type\":\"function\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"name\":\"getRoleAdmin\",\"type\":\"function\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"name\":\"grantRole\",\"type\":\"function\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"hasRole\",\"type\":\"function\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"name\":\"isApprovedForAll\",\"type\":\"function\",\"inputs\":[{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"name\":\"name\",\"type\":\"function\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"name\":\"nodes\",\"type\":\"function\",\"inputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[{\"name\":\"addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"did\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"spec\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"config\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"ipAddress\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"region\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"location\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"metadata\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"owner\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"status\",\"type\":\"uint8\",\"internalType\":\"enumNetSepioV1.Status\"}],\"stateMutability\":\"view\"},{\"name\":\"ownerOf\",\"type\":\"function\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"address\",\"internalType\":\"address\"}],\"stateMutability\":\"view\"},{\"name\":\"readAttribute\",\"type\":\"function\",\"inputs\":[{\"name\":\"did_account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"tuple\",\"components\":[{\"name\":\"name\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"validity\",\"type\":\"uint32\",\"internalType\":\"uint32\"},{\"name\":\"created\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"internalType\":\"structDID.Attribute\"}],\"stateMutability\":\"view\"},{\"name\":\"registerNode\",\"type\":\"function\",\"inputs\":[{\"name\":\"_addr\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"id\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"did\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"name\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"spec\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"config\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"ipAddress\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"region\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"location\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"metadata\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"nftMetadata\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"_owner\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"removeAttribute\",\"type\":\"function\",\"inputs\":[{\"name\":\"did_account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"name\":\"renounceRole\",\"type\":\"function\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"callerConfirmation\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"revokeRole\",\"type\":\"function\",\"inputs\":[{\"name\":\"role\",\"type\":\"bytes32\",\"internalType\":\"bytes32\"},{\"name\":\"account\",\"type\":\"address\",\"internalType\":\"address\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"safeTransferFrom\",\"type\":\"function\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"safeTransferFrom\",\"type\":\"function\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"data\",\"type\":\"bytes\",\"internalType\":\"bytes\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"setApprovalForAll\",\"type\":\"function\",\"inputs\":[{\"name\":\"operator\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"approved\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"supportsInterface\",\"type\":\"function\",\"inputs\":[{\"name\":\"interfaceId\",\"type\":\"bytes4\",\"internalType\":\"bytes4\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"view\"},{\"name\":\"symbol\",\"type\":\"function\",\"inputs\":[],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"name\":\"tokenIdToNodeId\",\"type\":\"function\",\"inputs\":[{\"name\":\"\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"name\":\"tokenURI\",\"type\":\"function\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[{\"name\":\"\",\"type\":\"string\",\"internalType\":\"string\"}],\"stateMutability\":\"view\"},{\"name\":\"transferFrom\",\"type\":\"function\",\"inputs\":[{\"name\":\"from\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"to\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"updateAttribute\",\"type\":\"function\",\"inputs\":[{\"name\":\"did_account\",\"type\":\"address\",\"internalType\":\"address\"},{\"name\":\"name\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"value\",\"type\":\"bytes\",\"internalType\":\"bytes\"},{\"name\":\"validity_for\",\"type\":\"uint32\",\"internalType\":\"uint32\"}],\"outputs\":[{\"name\":\"\",\"type\":\"bool\",\"internalType\":\"bool\"}],\"stateMutability\":\"nonpayable\"},{\"name\":\"updateNodeStatus\",\"type\":\"function\",\"inputs\":[{\"name\":\"id\",\"type\":\"string\",\"internalType\":\"string\"},{\"name\":\"newStatus\",\"type\":\"uint8\",\"internalType\":\"enumNetSepioV1.Status\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"},{\"name\":\"updateTokenURI\",\"type\":\"function\",\"inputs\":[{\"name\":\"tokenId\",\"type\":\"uint256\",\"internalType\":\"uint256\"},{\"name\":\"uri\",\"type\":\"string\",\"internalType\":\"string\"}],\"outputs\":[],\"stateMutability\":\"nonpayable\"}]", -} - -// ContractABI is the input ABI used to generate the binding from. -// Deprecated: Use ContractMetaData.ABI instead. -var ContractABI = ContractMetaData.ABI - -// Contract is an auto generated Go binding around an Ethereum contract. -type Contract struct { - ContractCaller // Read-only binding to the contract - ContractTransactor // Write-only binding to the contract - ContractFilterer // Log filterer for contract events -} - -// ContractCaller is an auto generated read-only Go binding around an Ethereum contract. -type ContractCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ContractTransactor is an auto generated write-only Go binding around an Ethereum contract. -type ContractTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ContractFilterer is an auto generated log filtering Go binding around an Ethereum contract events. -type ContractFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ContractSession is an auto generated Go binding around an Ethereum contract, -// with pre-set call and transact options. -type ContractSession struct { - Contract *Contract // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ContractCallerSession is an auto generated read-only Go binding around an Ethereum contract, -// with pre-set call options. -type ContractCallerSession struct { - Contract *ContractCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// ContractTransactorSession is an auto generated write-only Go binding around an Ethereum contract, -// with pre-set transact options. -type ContractTransactorSession struct { - Contract *ContractTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ContractRaw is an auto generated low-level Go binding around an Ethereum contract. -type ContractRaw struct { - Contract *Contract // Generic contract binding to access the raw methods on -} - -// ContractCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. -type ContractCallerRaw struct { - Contract *ContractCaller // Generic read-only contract binding to access the raw methods on -} - -// ContractTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. -type ContractTransactorRaw struct { - Contract *ContractTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewContract creates a new instance of Contract, bound to a specific deployed contract. -func NewContract(address common.Address, backend bind.ContractBackend) (*Contract, error) { - contract, err := bindContract(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &Contract{ContractCaller: ContractCaller{contract: contract}, ContractTransactor: ContractTransactor{contract: contract}, ContractFilterer: ContractFilterer{contract: contract}}, nil -} - -// NewContractCaller creates a new read-only instance of Contract, bound to a specific deployed contract. -func NewContractCaller(address common.Address, caller bind.ContractCaller) (*ContractCaller, error) { - contract, err := bindContract(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ContractCaller{contract: contract}, nil -} - -// NewContractTransactor creates a new write-only instance of Contract, bound to a specific deployed contract. -func NewContractTransactor(address common.Address, transactor bind.ContractTransactor) (*ContractTransactor, error) { - contract, err := bindContract(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ContractTransactor{contract: contract}, nil -} - -// NewContractFilterer creates a new log filterer instance of Contract, bound to a specific deployed contract. -func NewContractFilterer(address common.Address, filterer bind.ContractFilterer) (*ContractFilterer, error) { - contract, err := bindContract(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ContractFilterer{contract: contract}, nil -} - -// bindContract binds a generic wrapper to an already deployed contract. -func bindContract(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := ContractMetaData.GetAbi() - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, *parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Contract *ContractRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Contract.Contract.ContractCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Contract *ContractRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Contract.Contract.ContractTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Contract *ContractRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Contract.Contract.ContractTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Contract *ContractCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { - return _Contract.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Contract *ContractTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Contract.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Contract *ContractTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Contract.Contract.contract.Transact(opts, method, params...) -} - -// ADMINROLE is a free data retrieval call binding the contract method 0x75b238fc. -// -// Solidity: function ADMIN_ROLE() view returns(bytes32) -func (_Contract *ContractCaller) ADMINROLE(opts *bind.CallOpts) ([32]byte, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "ADMIN_ROLE") - - if err != nil { - return *new([32]byte), err - } - - out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) - - return out0, err - -} - -// ADMINROLE is a free data retrieval call binding the contract method 0x75b238fc. -// -// Solidity: function ADMIN_ROLE() view returns(bytes32) -func (_Contract *ContractSession) ADMINROLE() ([32]byte, error) { - return _Contract.Contract.ADMINROLE(&_Contract.CallOpts) -} - -// ADMINROLE is a free data retrieval call binding the contract method 0x75b238fc. -// -// Solidity: function ADMIN_ROLE() view returns(bytes32) -func (_Contract *ContractCallerSession) ADMINROLE() ([32]byte, error) { - return _Contract.Contract.ADMINROLE(&_Contract.CallOpts) -} - -// DEFAULTADMINROLE is a free data retrieval call binding the contract method 0xa217fddf. -// -// Solidity: function DEFAULT_ADMIN_ROLE() view returns(bytes32) -func (_Contract *ContractCaller) DEFAULTADMINROLE(opts *bind.CallOpts) ([32]byte, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "DEFAULT_ADMIN_ROLE") - - if err != nil { - return *new([32]byte), err - } - - out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) - - return out0, err - -} - -// DEFAULTADMINROLE is a free data retrieval call binding the contract method 0xa217fddf. -// -// Solidity: function DEFAULT_ADMIN_ROLE() view returns(bytes32) -func (_Contract *ContractSession) DEFAULTADMINROLE() ([32]byte, error) { - return _Contract.Contract.DEFAULTADMINROLE(&_Contract.CallOpts) -} - -// DEFAULTADMINROLE is a free data retrieval call binding the contract method 0xa217fddf. -// -// Solidity: function DEFAULT_ADMIN_ROLE() view returns(bytes32) -func (_Contract *ContractCallerSession) DEFAULTADMINROLE() ([32]byte, error) { - return _Contract.Contract.DEFAULTADMINROLE(&_Contract.CallOpts) -} - -// OPERATORROLE is a free data retrieval call binding the contract method 0xf5b541a6. -// -// Solidity: function OPERATOR_ROLE() view returns(bytes32) -func (_Contract *ContractCaller) OPERATORROLE(opts *bind.CallOpts) ([32]byte, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "OPERATOR_ROLE") - - if err != nil { - return *new([32]byte), err - } - - out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) - - return out0, err - -} - -// OPERATORROLE is a free data retrieval call binding the contract method 0xf5b541a6. -// -// Solidity: function OPERATOR_ROLE() view returns(bytes32) -func (_Contract *ContractSession) OPERATORROLE() ([32]byte, error) { - return _Contract.Contract.OPERATORROLE(&_Contract.CallOpts) -} - -// OPERATORROLE is a free data retrieval call binding the contract method 0xf5b541a6. -// -// Solidity: function OPERATOR_ROLE() view returns(bytes32) -func (_Contract *ContractCallerSession) OPERATORROLE() ([32]byte, error) { - return _Contract.Contract.OPERATORROLE(&_Contract.CallOpts) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address owner) view returns(uint256) -func (_Contract *ContractCaller) BalanceOf(opts *bind.CallOpts, owner common.Address) (*big.Int, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "balanceOf", owner) - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address owner) view returns(uint256) -func (_Contract *ContractSession) BalanceOf(owner common.Address) (*big.Int, error) { - return _Contract.Contract.BalanceOf(&_Contract.CallOpts, owner) -} - -// BalanceOf is a free data retrieval call binding the contract method 0x70a08231. -// -// Solidity: function balanceOf(address owner) view returns(uint256) -func (_Contract *ContractCallerSession) BalanceOf(owner common.Address) (*big.Int, error) { - return _Contract.Contract.BalanceOf(&_Contract.CallOpts, owner) -} - -// Checkpoint is a free data retrieval call binding the contract method 0x6697a925. -// -// Solidity: function checkpoint(string ) view returns(string) -func (_Contract *ContractCaller) Checkpoint(opts *bind.CallOpts, arg0 string) (string, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "checkpoint", arg0) - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Checkpoint is a free data retrieval call binding the contract method 0x6697a925. -// -// Solidity: function checkpoint(string ) view returns(string) -func (_Contract *ContractSession) Checkpoint(arg0 string) (string, error) { - return _Contract.Contract.Checkpoint(&_Contract.CallOpts, arg0) -} - -// Checkpoint is a free data retrieval call binding the contract method 0x6697a925. -// -// Solidity: function checkpoint(string ) view returns(string) -func (_Contract *ContractCallerSession) Checkpoint(arg0 string) (string, error) { - return _Contract.Contract.Checkpoint(&_Contract.CallOpts, arg0) -} - -// Counter is a free data retrieval call binding the contract method 0x61bc221a. -// -// Solidity: function counter() view returns(uint256) -func (_Contract *ContractCaller) Counter(opts *bind.CallOpts) (*big.Int, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "counter") - - if err != nil { - return *new(*big.Int), err - } - - out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) - - return out0, err - -} - -// Counter is a free data retrieval call binding the contract method 0x61bc221a. -// -// Solidity: function counter() view returns(uint256) -func (_Contract *ContractSession) Counter() (*big.Int, error) { - return _Contract.Contract.Counter(&_Contract.CallOpts) -} - -// Counter is a free data retrieval call binding the contract method 0x61bc221a. -// -// Solidity: function counter() view returns(uint256) -func (_Contract *ContractCallerSession) Counter() (*big.Int, error) { - return _Contract.Contract.Counter(&_Contract.CallOpts) -} - -// GetApproved is a free data retrieval call binding the contract method 0x081812fc. -// -// Solidity: function getApproved(uint256 tokenId) view returns(address) -func (_Contract *ContractCaller) GetApproved(opts *bind.CallOpts, tokenId *big.Int) (common.Address, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "getApproved", tokenId) - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// GetApproved is a free data retrieval call binding the contract method 0x081812fc. -// -// Solidity: function getApproved(uint256 tokenId) view returns(address) -func (_Contract *ContractSession) GetApproved(tokenId *big.Int) (common.Address, error) { - return _Contract.Contract.GetApproved(&_Contract.CallOpts, tokenId) -} - -// GetApproved is a free data retrieval call binding the contract method 0x081812fc. -// -// Solidity: function getApproved(uint256 tokenId) view returns(address) -func (_Contract *ContractCallerSession) GetApproved(tokenId *big.Int) (common.Address, error) { - return _Contract.Contract.GetApproved(&_Contract.CallOpts, tokenId) -} - -// GetRoleAdmin is a free data retrieval call binding the contract method 0x248a9ca3. -// -// Solidity: function getRoleAdmin(bytes32 role) view returns(bytes32) -func (_Contract *ContractCaller) GetRoleAdmin(opts *bind.CallOpts, role [32]byte) ([32]byte, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "getRoleAdmin", role) - - if err != nil { - return *new([32]byte), err - } - - out0 := *abi.ConvertType(out[0], new([32]byte)).(*[32]byte) - - return out0, err - -} - -// GetRoleAdmin is a free data retrieval call binding the contract method 0x248a9ca3. -// -// Solidity: function getRoleAdmin(bytes32 role) view returns(bytes32) -func (_Contract *ContractSession) GetRoleAdmin(role [32]byte) ([32]byte, error) { - return _Contract.Contract.GetRoleAdmin(&_Contract.CallOpts, role) -} - -// GetRoleAdmin is a free data retrieval call binding the contract method 0x248a9ca3. -// -// Solidity: function getRoleAdmin(bytes32 role) view returns(bytes32) -func (_Contract *ContractCallerSession) GetRoleAdmin(role [32]byte) ([32]byte, error) { - return _Contract.Contract.GetRoleAdmin(&_Contract.CallOpts, role) -} - -// HasRole is a free data retrieval call binding the contract method 0x91d14854. -// -// Solidity: function hasRole(bytes32 role, address account) view returns(bool) -func (_Contract *ContractCaller) HasRole(opts *bind.CallOpts, role [32]byte, account common.Address) (bool, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "hasRole", role, account) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// HasRole is a free data retrieval call binding the contract method 0x91d14854. -// -// Solidity: function hasRole(bytes32 role, address account) view returns(bool) -func (_Contract *ContractSession) HasRole(role [32]byte, account common.Address) (bool, error) { - return _Contract.Contract.HasRole(&_Contract.CallOpts, role, account) -} - -// HasRole is a free data retrieval call binding the contract method 0x91d14854. -// -// Solidity: function hasRole(bytes32 role, address account) view returns(bool) -func (_Contract *ContractCallerSession) HasRole(role [32]byte, account common.Address) (bool, error) { - return _Contract.Contract.HasRole(&_Contract.CallOpts, role, account) -} - -// IsApprovedForAll is a free data retrieval call binding the contract method 0xe985e9c5. -// -// Solidity: function isApprovedForAll(address owner, address operator) view returns(bool) -func (_Contract *ContractCaller) IsApprovedForAll(opts *bind.CallOpts, owner common.Address, operator common.Address) (bool, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "isApprovedForAll", owner, operator) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// IsApprovedForAll is a free data retrieval call binding the contract method 0xe985e9c5. -// -// Solidity: function isApprovedForAll(address owner, address operator) view returns(bool) -func (_Contract *ContractSession) IsApprovedForAll(owner common.Address, operator common.Address) (bool, error) { - return _Contract.Contract.IsApprovedForAll(&_Contract.CallOpts, owner, operator) -} - -// IsApprovedForAll is a free data retrieval call binding the contract method 0xe985e9c5. -// -// Solidity: function isApprovedForAll(address owner, address operator) view returns(bool) -func (_Contract *ContractCallerSession) IsApprovedForAll(owner common.Address, operator common.Address) (bool, error) { - return _Contract.Contract.IsApprovedForAll(&_Contract.CallOpts, owner, operator) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_Contract *ContractCaller) Name(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "name") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_Contract *ContractSession) Name() (string, error) { - return _Contract.Contract.Name(&_Contract.CallOpts) -} - -// Name is a free data retrieval call binding the contract method 0x06fdde03. -// -// Solidity: function name() view returns(string) -func (_Contract *ContractCallerSession) Name() (string, error) { - return _Contract.Contract.Name(&_Contract.CallOpts) -} - -// Nodes is a free data retrieval call binding the contract method 0xf5417ca2. -// -// Solidity: function nodes(string ) view returns(address addr, string did, string name, string spec, string config, string ipAddress, string region, string location, string metadata, address owner, uint256 tokenId, uint8 status) -func (_Contract *ContractCaller) Nodes(opts *bind.CallOpts, arg0 string) (struct { - Addr common.Address - Did string - Name string - Spec string - Config string - IpAddress string - Region string - Location string - Metadata string - Owner common.Address - TokenId *big.Int - Status uint8 -}, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "nodes", arg0) - - outstruct := new(struct { - Addr common.Address - Did string - Name string - Spec string - Config string - IpAddress string - Region string - Location string - Metadata string - Owner common.Address - TokenId *big.Int - Status uint8 - }) - if err != nil { - return *outstruct, err - } - - outstruct.Addr = *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - outstruct.Did = *abi.ConvertType(out[1], new(string)).(*string) - outstruct.Name = *abi.ConvertType(out[2], new(string)).(*string) - outstruct.Spec = *abi.ConvertType(out[3], new(string)).(*string) - outstruct.Config = *abi.ConvertType(out[4], new(string)).(*string) - outstruct.IpAddress = *abi.ConvertType(out[5], new(string)).(*string) - outstruct.Region = *abi.ConvertType(out[6], new(string)).(*string) - outstruct.Location = *abi.ConvertType(out[7], new(string)).(*string) - outstruct.Metadata = *abi.ConvertType(out[8], new(string)).(*string) - outstruct.Owner = *abi.ConvertType(out[9], new(common.Address)).(*common.Address) - outstruct.TokenId = *abi.ConvertType(out[10], new(*big.Int)).(**big.Int) - outstruct.Status = *abi.ConvertType(out[11], new(uint8)).(*uint8) - - return *outstruct, err - -} - -// Nodes is a free data retrieval call binding the contract method 0xf5417ca2. -// -// Solidity: function nodes(string ) view returns(address addr, string did, string name, string spec, string config, string ipAddress, string region, string location, string metadata, address owner, uint256 tokenId, uint8 status) -func (_Contract *ContractSession) Nodes(arg0 string) (struct { - Addr common.Address - Did string - Name string - Spec string - Config string - IpAddress string - Region string - Location string - Metadata string - Owner common.Address - TokenId *big.Int - Status uint8 -}, error) { - return _Contract.Contract.Nodes(&_Contract.CallOpts, arg0) -} - -// Nodes is a free data retrieval call binding the contract method 0xf5417ca2. -// -// Solidity: function nodes(string ) view returns(address addr, string did, string name, string spec, string config, string ipAddress, string region, string location, string metadata, address owner, uint256 tokenId, uint8 status) -func (_Contract *ContractCallerSession) Nodes(arg0 string) (struct { - Addr common.Address - Did string - Name string - Spec string - Config string - IpAddress string - Region string - Location string - Metadata string - Owner common.Address - TokenId *big.Int - Status uint8 -}, error) { - return _Contract.Contract.Nodes(&_Contract.CallOpts, arg0) -} - -// OwnerOf is a free data retrieval call binding the contract method 0x6352211e. -// -// Solidity: function ownerOf(uint256 tokenId) view returns(address) -func (_Contract *ContractCaller) OwnerOf(opts *bind.CallOpts, tokenId *big.Int) (common.Address, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "ownerOf", tokenId) - - if err != nil { - return *new(common.Address), err - } - - out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) - - return out0, err - -} - -// OwnerOf is a free data retrieval call binding the contract method 0x6352211e. -// -// Solidity: function ownerOf(uint256 tokenId) view returns(address) -func (_Contract *ContractSession) OwnerOf(tokenId *big.Int) (common.Address, error) { - return _Contract.Contract.OwnerOf(&_Contract.CallOpts, tokenId) -} - -// OwnerOf is a free data retrieval call binding the contract method 0x6352211e. -// -// Solidity: function ownerOf(uint256 tokenId) view returns(address) -func (_Contract *ContractCallerSession) OwnerOf(tokenId *big.Int) (common.Address, error) { - return _Contract.Contract.OwnerOf(&_Contract.CallOpts, tokenId) -} - -// ReadAttribute is a free data retrieval call binding the contract method 0xb2028b7d. -// -// Solidity: function readAttribute(address did_account, bytes name) view returns((bytes,bytes,uint32,uint256)) -func (_Contract *ContractCaller) ReadAttribute(opts *bind.CallOpts, did_account common.Address, name []byte) (DIDAttribute, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "readAttribute", did_account, name) - - if err != nil { - return *new(DIDAttribute), err - } - - out0 := *abi.ConvertType(out[0], new(DIDAttribute)).(*DIDAttribute) - - return out0, err - -} - -// ReadAttribute is a free data retrieval call binding the contract method 0xb2028b7d. -// -// Solidity: function readAttribute(address did_account, bytes name) view returns((bytes,bytes,uint32,uint256)) -func (_Contract *ContractSession) ReadAttribute(did_account common.Address, name []byte) (DIDAttribute, error) { - return _Contract.Contract.ReadAttribute(&_Contract.CallOpts, did_account, name) -} - -// ReadAttribute is a free data retrieval call binding the contract method 0xb2028b7d. -// -// Solidity: function readAttribute(address did_account, bytes name) view returns((bytes,bytes,uint32,uint256)) -func (_Contract *ContractCallerSession) ReadAttribute(did_account common.Address, name []byte) (DIDAttribute, error) { - return _Contract.Contract.ReadAttribute(&_Contract.CallOpts, did_account, name) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) -func (_Contract *ContractCaller) SupportsInterface(opts *bind.CallOpts, interfaceId [4]byte) (bool, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "supportsInterface", interfaceId) - - if err != nil { - return *new(bool), err - } - - out0 := *abi.ConvertType(out[0], new(bool)).(*bool) - - return out0, err - -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) -func (_Contract *ContractSession) SupportsInterface(interfaceId [4]byte) (bool, error) { - return _Contract.Contract.SupportsInterface(&_Contract.CallOpts, interfaceId) -} - -// SupportsInterface is a free data retrieval call binding the contract method 0x01ffc9a7. -// -// Solidity: function supportsInterface(bytes4 interfaceId) view returns(bool) -func (_Contract *ContractCallerSession) SupportsInterface(interfaceId [4]byte) (bool, error) { - return _Contract.Contract.SupportsInterface(&_Contract.CallOpts, interfaceId) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_Contract *ContractCaller) Symbol(opts *bind.CallOpts) (string, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "symbol") - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_Contract *ContractSession) Symbol() (string, error) { - return _Contract.Contract.Symbol(&_Contract.CallOpts) -} - -// Symbol is a free data retrieval call binding the contract method 0x95d89b41. -// -// Solidity: function symbol() view returns(string) -func (_Contract *ContractCallerSession) Symbol() (string, error) { - return _Contract.Contract.Symbol(&_Contract.CallOpts) -} - -// TokenIdToNodeId is a free data retrieval call binding the contract method 0x6bdf3486. -// -// Solidity: function tokenIdToNodeId(uint256 ) view returns(string) -func (_Contract *ContractCaller) TokenIdToNodeId(opts *bind.CallOpts, arg0 *big.Int) (string, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "tokenIdToNodeId", arg0) - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// TokenIdToNodeId is a free data retrieval call binding the contract method 0x6bdf3486. -// -// Solidity: function tokenIdToNodeId(uint256 ) view returns(string) -func (_Contract *ContractSession) TokenIdToNodeId(arg0 *big.Int) (string, error) { - return _Contract.Contract.TokenIdToNodeId(&_Contract.CallOpts, arg0) -} - -// TokenIdToNodeId is a free data retrieval call binding the contract method 0x6bdf3486. -// -// Solidity: function tokenIdToNodeId(uint256 ) view returns(string) -func (_Contract *ContractCallerSession) TokenIdToNodeId(arg0 *big.Int) (string, error) { - return _Contract.Contract.TokenIdToNodeId(&_Contract.CallOpts, arg0) -} - -// TokenURI is a free data retrieval call binding the contract method 0xc87b56dd. -// -// Solidity: function tokenURI(uint256 tokenId) view returns(string) -func (_Contract *ContractCaller) TokenURI(opts *bind.CallOpts, tokenId *big.Int) (string, error) { - var out []interface{} - err := _Contract.contract.Call(opts, &out, "tokenURI", tokenId) - - if err != nil { - return *new(string), err - } - - out0 := *abi.ConvertType(out[0], new(string)).(*string) - - return out0, err - -} - -// TokenURI is a free data retrieval call binding the contract method 0xc87b56dd. -// -// Solidity: function tokenURI(uint256 tokenId) view returns(string) -func (_Contract *ContractSession) TokenURI(tokenId *big.Int) (string, error) { - return _Contract.Contract.TokenURI(&_Contract.CallOpts, tokenId) -} - -// TokenURI is a free data retrieval call binding the contract method 0xc87b56dd. -// -// Solidity: function tokenURI(uint256 tokenId) view returns(string) -func (_Contract *ContractCallerSession) TokenURI(tokenId *big.Int) (string, error) { - return _Contract.Contract.TokenURI(&_Contract.CallOpts, tokenId) -} - -// AddAttribute is a paid mutator transaction binding the contract method 0xcc4a70ca. -// -// Solidity: function addAttribute(address did_account, bytes name, bytes value, uint32 validity_for) returns(bool) -func (_Contract *ContractTransactor) AddAttribute(opts *bind.TransactOpts, did_account common.Address, name []byte, value []byte, validity_for uint32) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "addAttribute", did_account, name, value, validity_for) -} - -// AddAttribute is a paid mutator transaction binding the contract method 0xcc4a70ca. -// -// Solidity: function addAttribute(address did_account, bytes name, bytes value, uint32 validity_for) returns(bool) -func (_Contract *ContractSession) AddAttribute(did_account common.Address, name []byte, value []byte, validity_for uint32) (*types.Transaction, error) { - return _Contract.Contract.AddAttribute(&_Contract.TransactOpts, did_account, name, value, validity_for) -} - -// AddAttribute is a paid mutator transaction binding the contract method 0xcc4a70ca. -// -// Solidity: function addAttribute(address did_account, bytes name, bytes value, uint32 validity_for) returns(bool) -func (_Contract *ContractTransactorSession) AddAttribute(did_account common.Address, name []byte, value []byte, validity_for uint32) (*types.Transaction, error) { - return _Contract.Contract.AddAttribute(&_Contract.TransactOpts, did_account, name, value, validity_for) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address to, uint256 tokenId) returns() -func (_Contract *ContractTransactor) Approve(opts *bind.TransactOpts, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "approve", to, tokenId) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address to, uint256 tokenId) returns() -func (_Contract *ContractSession) Approve(to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.Contract.Approve(&_Contract.TransactOpts, to, tokenId) -} - -// Approve is a paid mutator transaction binding the contract method 0x095ea7b3. -// -// Solidity: function approve(address to, uint256 tokenId) returns() -func (_Contract *ContractTransactorSession) Approve(to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.Contract.Approve(&_Contract.TransactOpts, to, tokenId) -} - -// CreateCheckpoint is a paid mutator transaction binding the contract method 0x5b5bf994. -// -// Solidity: function createCheckpoint(string nodeId, string data) returns() -func (_Contract *ContractTransactor) CreateCheckpoint(opts *bind.TransactOpts, nodeId string, data string) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "createCheckpoint", nodeId, data) -} - -// CreateCheckpoint is a paid mutator transaction binding the contract method 0x5b5bf994. -// -// Solidity: function createCheckpoint(string nodeId, string data) returns() -func (_Contract *ContractSession) CreateCheckpoint(nodeId string, data string) (*types.Transaction, error) { - return _Contract.Contract.CreateCheckpoint(&_Contract.TransactOpts, nodeId, data) -} - -// CreateCheckpoint is a paid mutator transaction binding the contract method 0x5b5bf994. -// -// Solidity: function createCheckpoint(string nodeId, string data) returns() -func (_Contract *ContractTransactorSession) CreateCheckpoint(nodeId string, data string) (*types.Transaction, error) { - return _Contract.Contract.CreateCheckpoint(&_Contract.TransactOpts, nodeId, data) -} - -// DeactivateNode is a paid mutator transaction binding the contract method 0x420c26de. -// -// Solidity: function deactivateNode(string nodeId) returns() -func (_Contract *ContractTransactor) DeactivateNode(opts *bind.TransactOpts, nodeId string) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "deactivateNode", nodeId) -} - -// DeactivateNode is a paid mutator transaction binding the contract method 0x420c26de. -// -// Solidity: function deactivateNode(string nodeId) returns() -func (_Contract *ContractSession) DeactivateNode(nodeId string) (*types.Transaction, error) { - return _Contract.Contract.DeactivateNode(&_Contract.TransactOpts, nodeId) -} - -// DeactivateNode is a paid mutator transaction binding the contract method 0x420c26de. -// -// Solidity: function deactivateNode(string nodeId) returns() -func (_Contract *ContractTransactorSession) DeactivateNode(nodeId string) (*types.Transaction, error) { - return _Contract.Contract.DeactivateNode(&_Contract.TransactOpts, nodeId) -} - -// GrantRole is a paid mutator transaction binding the contract method 0x2f2ff15d. -// -// Solidity: function grantRole(bytes32 role, address account) returns() -func (_Contract *ContractTransactor) GrantRole(opts *bind.TransactOpts, role [32]byte, account common.Address) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "grantRole", role, account) -} - -// GrantRole is a paid mutator transaction binding the contract method 0x2f2ff15d. -// -// Solidity: function grantRole(bytes32 role, address account) returns() -func (_Contract *ContractSession) GrantRole(role [32]byte, account common.Address) (*types.Transaction, error) { - return _Contract.Contract.GrantRole(&_Contract.TransactOpts, role, account) -} - -// GrantRole is a paid mutator transaction binding the contract method 0x2f2ff15d. -// -// Solidity: function grantRole(bytes32 role, address account) returns() -func (_Contract *ContractTransactorSession) GrantRole(role [32]byte, account common.Address) (*types.Transaction, error) { - return _Contract.Contract.GrantRole(&_Contract.TransactOpts, role, account) -} - -// RegisterNode is a paid mutator transaction binding the contract method 0x99c6359e. -// -// Solidity: function registerNode(address _addr, string id, string did, string name, string spec, string config, string ipAddress, string region, string location, string metadata, string nftMetadata, address _owner) returns() -func (_Contract *ContractTransactor) RegisterNode(opts *bind.TransactOpts, _addr common.Address, id string, did string, name string, spec string, config string, ipAddress string, region string, location string, metadata string, nftMetadata string, _owner common.Address) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "registerNode", _addr, id, did, name, spec, config, ipAddress, region, location, metadata, nftMetadata, _owner) -} - -// RegisterNode is a paid mutator transaction binding the contract method 0x99c6359e. -// -// Solidity: function registerNode(address _addr, string id, string did, string name, string spec, string config, string ipAddress, string region, string location, string metadata, string nftMetadata, address _owner) returns() -func (_Contract *ContractSession) RegisterNode(_addr common.Address, id string, did string, name string, spec string, config string, ipAddress string, region string, location string, metadata string, nftMetadata string, _owner common.Address) (*types.Transaction, error) { - return _Contract.Contract.RegisterNode(&_Contract.TransactOpts, _addr, id, did, name, spec, config, ipAddress, region, location, metadata, nftMetadata, _owner) -} - -// RegisterNode is a paid mutator transaction binding the contract method 0x99c6359e. -// -// Solidity: function registerNode(address _addr, string id, string did, string name, string spec, string config, string ipAddress, string region, string location, string metadata, string nftMetadata, address _owner) returns() -func (_Contract *ContractTransactorSession) RegisterNode(_addr common.Address, id string, did string, name string, spec string, config string, ipAddress string, region string, location string, metadata string, nftMetadata string, _owner common.Address) (*types.Transaction, error) { - return _Contract.Contract.RegisterNode(&_Contract.TransactOpts, _addr, id, did, name, spec, config, ipAddress, region, location, metadata, nftMetadata, _owner) -} - -// RemoveAttribute is a paid mutator transaction binding the contract method 0xe8a81690. -// -// Solidity: function removeAttribute(address did_account, bytes name) returns(bool) -func (_Contract *ContractTransactor) RemoveAttribute(opts *bind.TransactOpts, did_account common.Address, name []byte) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "removeAttribute", did_account, name) -} - -// RemoveAttribute is a paid mutator transaction binding the contract method 0xe8a81690. -// -// Solidity: function removeAttribute(address did_account, bytes name) returns(bool) -func (_Contract *ContractSession) RemoveAttribute(did_account common.Address, name []byte) (*types.Transaction, error) { - return _Contract.Contract.RemoveAttribute(&_Contract.TransactOpts, did_account, name) -} - -// RemoveAttribute is a paid mutator transaction binding the contract method 0xe8a81690. -// -// Solidity: function removeAttribute(address did_account, bytes name) returns(bool) -func (_Contract *ContractTransactorSession) RemoveAttribute(did_account common.Address, name []byte) (*types.Transaction, error) { - return _Contract.Contract.RemoveAttribute(&_Contract.TransactOpts, did_account, name) -} - -// RenounceRole is a paid mutator transaction binding the contract method 0x36568abe. -// -// Solidity: function renounceRole(bytes32 role, address callerConfirmation) returns() -func (_Contract *ContractTransactor) RenounceRole(opts *bind.TransactOpts, role [32]byte, callerConfirmation common.Address) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "renounceRole", role, callerConfirmation) -} - -// RenounceRole is a paid mutator transaction binding the contract method 0x36568abe. -// -// Solidity: function renounceRole(bytes32 role, address callerConfirmation) returns() -func (_Contract *ContractSession) RenounceRole(role [32]byte, callerConfirmation common.Address) (*types.Transaction, error) { - return _Contract.Contract.RenounceRole(&_Contract.TransactOpts, role, callerConfirmation) -} - -// RenounceRole is a paid mutator transaction binding the contract method 0x36568abe. -// -// Solidity: function renounceRole(bytes32 role, address callerConfirmation) returns() -func (_Contract *ContractTransactorSession) RenounceRole(role [32]byte, callerConfirmation common.Address) (*types.Transaction, error) { - return _Contract.Contract.RenounceRole(&_Contract.TransactOpts, role, callerConfirmation) -} - -// RevokeRole is a paid mutator transaction binding the contract method 0xd547741f. -// -// Solidity: function revokeRole(bytes32 role, address account) returns() -func (_Contract *ContractTransactor) RevokeRole(opts *bind.TransactOpts, role [32]byte, account common.Address) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "revokeRole", role, account) -} - -// RevokeRole is a paid mutator transaction binding the contract method 0xd547741f. -// -// Solidity: function revokeRole(bytes32 role, address account) returns() -func (_Contract *ContractSession) RevokeRole(role [32]byte, account common.Address) (*types.Transaction, error) { - return _Contract.Contract.RevokeRole(&_Contract.TransactOpts, role, account) -} - -// RevokeRole is a paid mutator transaction binding the contract method 0xd547741f. -// -// Solidity: function revokeRole(bytes32 role, address account) returns() -func (_Contract *ContractTransactorSession) RevokeRole(role [32]byte, account common.Address) (*types.Transaction, error) { - return _Contract.Contract.RevokeRole(&_Contract.TransactOpts, role, account) -} - -// SafeTransferFrom is a paid mutator transaction binding the contract method 0x42842e0e. -// -// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId) returns() -func (_Contract *ContractTransactor) SafeTransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "safeTransferFrom", from, to, tokenId) -} - -// SafeTransferFrom is a paid mutator transaction binding the contract method 0x42842e0e. -// -// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId) returns() -func (_Contract *ContractSession) SafeTransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.Contract.SafeTransferFrom(&_Contract.TransactOpts, from, to, tokenId) -} - -// SafeTransferFrom is a paid mutator transaction binding the contract method 0x42842e0e. -// -// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId) returns() -func (_Contract *ContractTransactorSession) SafeTransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.Contract.SafeTransferFrom(&_Contract.TransactOpts, from, to, tokenId) -} - -// SafeTransferFrom0 is a paid mutator transaction binding the contract method 0xb88d4fde. -// -// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) returns() -func (_Contract *ContractTransactor) SafeTransferFrom0(opts *bind.TransactOpts, from common.Address, to common.Address, tokenId *big.Int, data []byte) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "safeTransferFrom0", from, to, tokenId, data) -} - -// SafeTransferFrom0 is a paid mutator transaction binding the contract method 0xb88d4fde. -// -// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) returns() -func (_Contract *ContractSession) SafeTransferFrom0(from common.Address, to common.Address, tokenId *big.Int, data []byte) (*types.Transaction, error) { - return _Contract.Contract.SafeTransferFrom0(&_Contract.TransactOpts, from, to, tokenId, data) -} - -// SafeTransferFrom0 is a paid mutator transaction binding the contract method 0xb88d4fde. -// -// Solidity: function safeTransferFrom(address from, address to, uint256 tokenId, bytes data) returns() -func (_Contract *ContractTransactorSession) SafeTransferFrom0(from common.Address, to common.Address, tokenId *big.Int, data []byte) (*types.Transaction, error) { - return _Contract.Contract.SafeTransferFrom0(&_Contract.TransactOpts, from, to, tokenId, data) -} - -// SetApprovalForAll is a paid mutator transaction binding the contract method 0xa22cb465. -// -// Solidity: function setApprovalForAll(address operator, bool approved) returns() -func (_Contract *ContractTransactor) SetApprovalForAll(opts *bind.TransactOpts, operator common.Address, approved bool) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "setApprovalForAll", operator, approved) -} - -// SetApprovalForAll is a paid mutator transaction binding the contract method 0xa22cb465. -// -// Solidity: function setApprovalForAll(address operator, bool approved) returns() -func (_Contract *ContractSession) SetApprovalForAll(operator common.Address, approved bool) (*types.Transaction, error) { - return _Contract.Contract.SetApprovalForAll(&_Contract.TransactOpts, operator, approved) -} - -// SetApprovalForAll is a paid mutator transaction binding the contract method 0xa22cb465. -// -// Solidity: function setApprovalForAll(address operator, bool approved) returns() -func (_Contract *ContractTransactorSession) SetApprovalForAll(operator common.Address, approved bool) (*types.Transaction, error) { - return _Contract.Contract.SetApprovalForAll(&_Contract.TransactOpts, operator, approved) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address from, address to, uint256 tokenId) returns() -func (_Contract *ContractTransactor) TransferFrom(opts *bind.TransactOpts, from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "transferFrom", from, to, tokenId) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address from, address to, uint256 tokenId) returns() -func (_Contract *ContractSession) TransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.Contract.TransferFrom(&_Contract.TransactOpts, from, to, tokenId) -} - -// TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. -// -// Solidity: function transferFrom(address from, address to, uint256 tokenId) returns() -func (_Contract *ContractTransactorSession) TransferFrom(from common.Address, to common.Address, tokenId *big.Int) (*types.Transaction, error) { - return _Contract.Contract.TransferFrom(&_Contract.TransactOpts, from, to, tokenId) -} - -// UpdateAttribute is a paid mutator transaction binding the contract method 0x68b4b2c1. -// -// Solidity: function updateAttribute(address did_account, bytes name, bytes value, uint32 validity_for) returns(bool) -func (_Contract *ContractTransactor) UpdateAttribute(opts *bind.TransactOpts, did_account common.Address, name []byte, value []byte, validity_for uint32) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "updateAttribute", did_account, name, value, validity_for) -} - -// UpdateAttribute is a paid mutator transaction binding the contract method 0x68b4b2c1. -// -// Solidity: function updateAttribute(address did_account, bytes name, bytes value, uint32 validity_for) returns(bool) -func (_Contract *ContractSession) UpdateAttribute(did_account common.Address, name []byte, value []byte, validity_for uint32) (*types.Transaction, error) { - return _Contract.Contract.UpdateAttribute(&_Contract.TransactOpts, did_account, name, value, validity_for) -} - -// UpdateAttribute is a paid mutator transaction binding the contract method 0x68b4b2c1. -// -// Solidity: function updateAttribute(address did_account, bytes name, bytes value, uint32 validity_for) returns(bool) -func (_Contract *ContractTransactorSession) UpdateAttribute(did_account common.Address, name []byte, value []byte, validity_for uint32) (*types.Transaction, error) { - return _Contract.Contract.UpdateAttribute(&_Contract.TransactOpts, did_account, name, value, validity_for) -} - -// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xca046c62. -// -// Solidity: function updateNodeStatus(string id, uint8 newStatus) returns() -func (_Contract *ContractTransactor) UpdateNodeStatus(opts *bind.TransactOpts, id string, newStatus uint8) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "updateNodeStatus", id, newStatus) -} - -// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xca046c62. -// -// Solidity: function updateNodeStatus(string id, uint8 newStatus) returns() -func (_Contract *ContractSession) UpdateNodeStatus(id string, newStatus uint8) (*types.Transaction, error) { - return _Contract.Contract.UpdateNodeStatus(&_Contract.TransactOpts, id, newStatus) -} - -// UpdateNodeStatus is a paid mutator transaction binding the contract method 0xca046c62. -// -// Solidity: function updateNodeStatus(string id, uint8 newStatus) returns() -func (_Contract *ContractTransactorSession) UpdateNodeStatus(id string, newStatus uint8) (*types.Transaction, error) { - return _Contract.Contract.UpdateNodeStatus(&_Contract.TransactOpts, id, newStatus) -} - -// UpdateTokenURI is a paid mutator transaction binding the contract method 0x18e97fd1. -// -// Solidity: function updateTokenURI(uint256 tokenId, string uri) returns() -func (_Contract *ContractTransactor) UpdateTokenURI(opts *bind.TransactOpts, tokenId *big.Int, uri string) (*types.Transaction, error) { - return _Contract.contract.Transact(opts, "updateTokenURI", tokenId, uri) -} - -// UpdateTokenURI is a paid mutator transaction binding the contract method 0x18e97fd1. -// -// Solidity: function updateTokenURI(uint256 tokenId, string uri) returns() -func (_Contract *ContractSession) UpdateTokenURI(tokenId *big.Int, uri string) (*types.Transaction, error) { - return _Contract.Contract.UpdateTokenURI(&_Contract.TransactOpts, tokenId, uri) -} - -// UpdateTokenURI is a paid mutator transaction binding the contract method 0x18e97fd1. -// -// Solidity: function updateTokenURI(uint256 tokenId, string uri) returns() -func (_Contract *ContractTransactorSession) UpdateTokenURI(tokenId *big.Int, uri string) (*types.Transaction, error) { - return _Contract.Contract.UpdateTokenURI(&_Contract.TransactOpts, tokenId, uri) -} - -// ContractAddAttributeIterator is returned from FilterAddAttribute and is used to iterate over the raw logs and unpacked data for AddAttribute events raised by the Contract contract. -type ContractAddAttributeIterator struct { - Event *ContractAddAttribute // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractAddAttributeIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractAddAttribute) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractAddAttribute) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractAddAttributeIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractAddAttributeIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractAddAttribute represents a AddAttribute event raised by the Contract contract. -type ContractAddAttribute struct { - Sender common.Address - DidAccount common.Address - Name []byte - Value []byte - Validity uint32 - Raw types.Log // Blockchain specific contextual infos -} - -// FilterAddAttribute is a free log retrieval operation binding the contract event 0x13aef52bc4a99da04591533072e304017e3fb76f43e7fadd25eb7f514c5ef6e5. -// -// Solidity: event AddAttribute(address sender, address did_account, bytes name, bytes value, uint32 validity) -func (_Contract *ContractFilterer) FilterAddAttribute(opts *bind.FilterOpts) (*ContractAddAttributeIterator, error) { - - logs, sub, err := _Contract.contract.FilterLogs(opts, "AddAttribute") - if err != nil { - return nil, err - } - return &ContractAddAttributeIterator{contract: _Contract.contract, event: "AddAttribute", logs: logs, sub: sub}, nil -} - -// WatchAddAttribute is a free log subscription operation binding the contract event 0x13aef52bc4a99da04591533072e304017e3fb76f43e7fadd25eb7f514c5ef6e5. -// -// Solidity: event AddAttribute(address sender, address did_account, bytes name, bytes value, uint32 validity) -func (_Contract *ContractFilterer) WatchAddAttribute(opts *bind.WatchOpts, sink chan<- *ContractAddAttribute) (event.Subscription, error) { - - logs, sub, err := _Contract.contract.WatchLogs(opts, "AddAttribute") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractAddAttribute) - if err := _Contract.contract.UnpackLog(event, "AddAttribute", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseAddAttribute is a log parse operation binding the contract event 0x13aef52bc4a99da04591533072e304017e3fb76f43e7fadd25eb7f514c5ef6e5. -// -// Solidity: event AddAttribute(address sender, address did_account, bytes name, bytes value, uint32 validity) -func (_Contract *ContractFilterer) ParseAddAttribute(log types.Log) (*ContractAddAttribute, error) { - event := new(ContractAddAttribute) - if err := _Contract.contract.UnpackLog(event, "AddAttribute", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the Contract contract. -type ContractApprovalIterator struct { - Event *ContractApproval // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractApprovalIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractApproval) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractApprovalIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractApprovalIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractApproval represents a Approval event raised by the Contract contract. -type ContractApproval struct { - Owner common.Address - Approved common.Address - TokenId *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) -func (_Contract *ContractFilterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, approved []common.Address, tokenId []*big.Int) (*ContractApprovalIterator, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var approvedRule []interface{} - for _, approvedItem := range approved { - approvedRule = append(approvedRule, approvedItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "Approval", ownerRule, approvedRule, tokenIdRule) - if err != nil { - return nil, err - } - return &ContractApprovalIterator{contract: _Contract.contract, event: "Approval", logs: logs, sub: sub}, nil -} - -// WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) -func (_Contract *ContractFilterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *ContractApproval, owner []common.Address, approved []common.Address, tokenId []*big.Int) (event.Subscription, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var approvedRule []interface{} - for _, approvedItem := range approved { - approvedRule = append(approvedRule, approvedItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "Approval", ownerRule, approvedRule, tokenIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractApproval) - if err := _Contract.contract.UnpackLog(event, "Approval", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. -// -// Solidity: event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId) -func (_Contract *ContractFilterer) ParseApproval(log types.Log) (*ContractApproval, error) { - event := new(ContractApproval) - if err := _Contract.contract.UnpackLog(event, "Approval", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractApprovalForAllIterator is returned from FilterApprovalForAll and is used to iterate over the raw logs and unpacked data for ApprovalForAll events raised by the Contract contract. -type ContractApprovalForAllIterator struct { - Event *ContractApprovalForAll // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractApprovalForAllIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractApprovalForAll) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractApprovalForAll) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractApprovalForAllIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractApprovalForAllIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractApprovalForAll represents a ApprovalForAll event raised by the Contract contract. -type ContractApprovalForAll struct { - Owner common.Address - Operator common.Address - Approved bool - Raw types.Log // Blockchain specific contextual infos -} - -// FilterApprovalForAll is a free log retrieval operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. -// -// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) -func (_Contract *ContractFilterer) FilterApprovalForAll(opts *bind.FilterOpts, owner []common.Address, operator []common.Address) (*ContractApprovalForAllIterator, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var operatorRule []interface{} - for _, operatorItem := range operator { - operatorRule = append(operatorRule, operatorItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "ApprovalForAll", ownerRule, operatorRule) - if err != nil { - return nil, err - } - return &ContractApprovalForAllIterator{contract: _Contract.contract, event: "ApprovalForAll", logs: logs, sub: sub}, nil -} - -// WatchApprovalForAll is a free log subscription operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. -// -// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) -func (_Contract *ContractFilterer) WatchApprovalForAll(opts *bind.WatchOpts, sink chan<- *ContractApprovalForAll, owner []common.Address, operator []common.Address) (event.Subscription, error) { - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var operatorRule []interface{} - for _, operatorItem := range operator { - operatorRule = append(operatorRule, operatorItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "ApprovalForAll", ownerRule, operatorRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractApprovalForAll) - if err := _Contract.contract.UnpackLog(event, "ApprovalForAll", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseApprovalForAll is a log parse operation binding the contract event 0x17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31. -// -// Solidity: event ApprovalForAll(address indexed owner, address indexed operator, bool approved) -func (_Contract *ContractFilterer) ParseApprovalForAll(log types.Log) (*ContractApprovalForAll, error) { - event := new(ContractApprovalForAll) - if err := _Contract.contract.UnpackLog(event, "ApprovalForAll", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractCheckpointCreatedIterator is returned from FilterCheckpointCreated and is used to iterate over the raw logs and unpacked data for CheckpointCreated events raised by the Contract contract. -type ContractCheckpointCreatedIterator struct { - Event *ContractCheckpointCreated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractCheckpointCreatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractCheckpointCreated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractCheckpointCreated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractCheckpointCreatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractCheckpointCreatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractCheckpointCreated represents a CheckpointCreated event raised by the Contract contract. -type ContractCheckpointCreated struct { - NodeId string - Data string - Raw types.Log // Blockchain specific contextual infos -} - -// FilterCheckpointCreated is a free log retrieval operation binding the contract event 0xd67d1994a0f3d42767c76ed3d9c8d88e3488034af6f2b3609baebf227a4602be. -// -// Solidity: event CheckpointCreated(string nodeId, string data) -func (_Contract *ContractFilterer) FilterCheckpointCreated(opts *bind.FilterOpts) (*ContractCheckpointCreatedIterator, error) { - - logs, sub, err := _Contract.contract.FilterLogs(opts, "CheckpointCreated") - if err != nil { - return nil, err - } - return &ContractCheckpointCreatedIterator{contract: _Contract.contract, event: "CheckpointCreated", logs: logs, sub: sub}, nil -} - -// WatchCheckpointCreated is a free log subscription operation binding the contract event 0xd67d1994a0f3d42767c76ed3d9c8d88e3488034af6f2b3609baebf227a4602be. -// -// Solidity: event CheckpointCreated(string nodeId, string data) -func (_Contract *ContractFilterer) WatchCheckpointCreated(opts *bind.WatchOpts, sink chan<- *ContractCheckpointCreated) (event.Subscription, error) { - - logs, sub, err := _Contract.contract.WatchLogs(opts, "CheckpointCreated") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractCheckpointCreated) - if err := _Contract.contract.UnpackLog(event, "CheckpointCreated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseCheckpointCreated is a log parse operation binding the contract event 0xd67d1994a0f3d42767c76ed3d9c8d88e3488034af6f2b3609baebf227a4602be. -// -// Solidity: event CheckpointCreated(string nodeId, string data) -func (_Contract *ContractFilterer) ParseCheckpointCreated(log types.Log) (*ContractCheckpointCreated, error) { - event := new(ContractCheckpointCreated) - if err := _Contract.contract.UnpackLog(event, "CheckpointCreated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractNodeDeactivatedIterator is returned from FilterNodeDeactivated and is used to iterate over the raw logs and unpacked data for NodeDeactivated events raised by the Contract contract. -type ContractNodeDeactivatedIterator struct { - Event *ContractNodeDeactivated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractNodeDeactivatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractNodeDeactivated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractNodeDeactivated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractNodeDeactivatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractNodeDeactivatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractNodeDeactivated represents a NodeDeactivated event raised by the Contract contract. -type ContractNodeDeactivated struct { - NodeId string - NodeAddr common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNodeDeactivated is a free log retrieval operation binding the contract event 0xaef63763d36c0a36d98696af3570ce7d26b6015fd8798129f85e10047fb7361b. -// -// Solidity: event NodeDeactivated(string nodeId, address indexed nodeAddr) -func (_Contract *ContractFilterer) FilterNodeDeactivated(opts *bind.FilterOpts, nodeAddr []common.Address) (*ContractNodeDeactivatedIterator, error) { - - var nodeAddrRule []interface{} - for _, nodeAddrItem := range nodeAddr { - nodeAddrRule = append(nodeAddrRule, nodeAddrItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "NodeDeactivated", nodeAddrRule) - if err != nil { - return nil, err - } - return &ContractNodeDeactivatedIterator{contract: _Contract.contract, event: "NodeDeactivated", logs: logs, sub: sub}, nil -} - -// WatchNodeDeactivated is a free log subscription operation binding the contract event 0xaef63763d36c0a36d98696af3570ce7d26b6015fd8798129f85e10047fb7361b. -// -// Solidity: event NodeDeactivated(string nodeId, address indexed nodeAddr) -func (_Contract *ContractFilterer) WatchNodeDeactivated(opts *bind.WatchOpts, sink chan<- *ContractNodeDeactivated, nodeAddr []common.Address) (event.Subscription, error) { - - var nodeAddrRule []interface{} - for _, nodeAddrItem := range nodeAddr { - nodeAddrRule = append(nodeAddrRule, nodeAddrItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "NodeDeactivated", nodeAddrRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractNodeDeactivated) - if err := _Contract.contract.UnpackLog(event, "NodeDeactivated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseNodeDeactivated is a log parse operation binding the contract event 0xaef63763d36c0a36d98696af3570ce7d26b6015fd8798129f85e10047fb7361b. -// -// Solidity: event NodeDeactivated(string nodeId, address indexed nodeAddr) -func (_Contract *ContractFilterer) ParseNodeDeactivated(log types.Log) (*ContractNodeDeactivated, error) { - event := new(ContractNodeDeactivated) - if err := _Contract.contract.UnpackLog(event, "NodeDeactivated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractNodeRegisteredIterator is returned from FilterNodeRegistered and is used to iterate over the raw logs and unpacked data for NodeRegistered events raised by the Contract contract. -type ContractNodeRegisteredIterator struct { - Event *ContractNodeRegistered // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractNodeRegisteredIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractNodeRegistered) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractNodeRegistered) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractNodeRegisteredIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractNodeRegisteredIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractNodeRegistered represents a NodeRegistered event raised by the Contract contract. -type ContractNodeRegistered struct { - Id string - Did string - Name string - Addr common.Address - Spec string - Config string - IpAddress string - Region string - Location string - Metadata string - Owner common.Address - Registrant common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNodeRegistered is a free log retrieval operation binding the contract event 0xe8bad63cf7bc329f82dbffea067529b741fe31701eea4aa34eb646029fd24f0c. -// -// Solidity: event NodeRegistered(string id, string did, string name, address indexed addr, string spec, string config, string ipAddress, string region, string location, string metadata, address indexed owner, address indexed registrant) -func (_Contract *ContractFilterer) FilterNodeRegistered(opts *bind.FilterOpts, addr []common.Address, owner []common.Address, registrant []common.Address) (*ContractNodeRegisteredIterator, error) { - - var addrRule []interface{} - for _, addrItem := range addr { - addrRule = append(addrRule, addrItem) - } - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var registrantRule []interface{} - for _, registrantItem := range registrant { - registrantRule = append(registrantRule, registrantItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "NodeRegistered", addrRule, ownerRule, registrantRule) - if err != nil { - return nil, err - } - return &ContractNodeRegisteredIterator{contract: _Contract.contract, event: "NodeRegistered", logs: logs, sub: sub}, nil -} - -// WatchNodeRegistered is a free log subscription operation binding the contract event 0xe8bad63cf7bc329f82dbffea067529b741fe31701eea4aa34eb646029fd24f0c. -// -// Solidity: event NodeRegistered(string id, string did, string name, address indexed addr, string spec, string config, string ipAddress, string region, string location, string metadata, address indexed owner, address indexed registrant) -func (_Contract *ContractFilterer) WatchNodeRegistered(opts *bind.WatchOpts, sink chan<- *ContractNodeRegistered, addr []common.Address, owner []common.Address, registrant []common.Address) (event.Subscription, error) { - - var addrRule []interface{} - for _, addrItem := range addr { - addrRule = append(addrRule, addrItem) - } - - var ownerRule []interface{} - for _, ownerItem := range owner { - ownerRule = append(ownerRule, ownerItem) - } - var registrantRule []interface{} - for _, registrantItem := range registrant { - registrantRule = append(registrantRule, registrantItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "NodeRegistered", addrRule, ownerRule, registrantRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractNodeRegistered) - if err := _Contract.contract.UnpackLog(event, "NodeRegistered", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseNodeRegistered is a log parse operation binding the contract event 0xe8bad63cf7bc329f82dbffea067529b741fe31701eea4aa34eb646029fd24f0c. -// -// Solidity: event NodeRegistered(string id, string did, string name, address indexed addr, string spec, string config, string ipAddress, string region, string location, string metadata, address indexed owner, address indexed registrant) -func (_Contract *ContractFilterer) ParseNodeRegistered(log types.Log) (*ContractNodeRegistered, error) { - event := new(ContractNodeRegistered) - if err := _Contract.contract.UnpackLog(event, "NodeRegistered", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractNodeStatusUpdatedIterator is returned from FilterNodeStatusUpdated and is used to iterate over the raw logs and unpacked data for NodeStatusUpdated events raised by the Contract contract. -type ContractNodeStatusUpdatedIterator struct { - Event *ContractNodeStatusUpdated // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractNodeStatusUpdatedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractNodeStatusUpdated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractNodeStatusUpdated) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractNodeStatusUpdatedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractNodeStatusUpdatedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractNodeStatusUpdated represents a NodeStatusUpdated event raised by the Contract contract. -type ContractNodeStatusUpdated struct { - NodeId string - NewStatus uint8 - Raw types.Log // Blockchain specific contextual infos -} - -// FilterNodeStatusUpdated is a free log retrieval operation binding the contract event 0xb43103a3adf1026cd2a4cd979b4e0d2b35045e82cf13b4b8aa839f7b28b8c082. -// -// Solidity: event NodeStatusUpdated(string nodeId, uint8 newStatus) -func (_Contract *ContractFilterer) FilterNodeStatusUpdated(opts *bind.FilterOpts) (*ContractNodeStatusUpdatedIterator, error) { - - logs, sub, err := _Contract.contract.FilterLogs(opts, "NodeStatusUpdated") - if err != nil { - return nil, err - } - return &ContractNodeStatusUpdatedIterator{contract: _Contract.contract, event: "NodeStatusUpdated", logs: logs, sub: sub}, nil -} - -// WatchNodeStatusUpdated is a free log subscription operation binding the contract event 0xb43103a3adf1026cd2a4cd979b4e0d2b35045e82cf13b4b8aa839f7b28b8c082. -// -// Solidity: event NodeStatusUpdated(string nodeId, uint8 newStatus) -func (_Contract *ContractFilterer) WatchNodeStatusUpdated(opts *bind.WatchOpts, sink chan<- *ContractNodeStatusUpdated) (event.Subscription, error) { - - logs, sub, err := _Contract.contract.WatchLogs(opts, "NodeStatusUpdated") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractNodeStatusUpdated) - if err := _Contract.contract.UnpackLog(event, "NodeStatusUpdated", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseNodeStatusUpdated is a log parse operation binding the contract event 0xb43103a3adf1026cd2a4cd979b4e0d2b35045e82cf13b4b8aa839f7b28b8c082. -// -// Solidity: event NodeStatusUpdated(string nodeId, uint8 newStatus) -func (_Contract *ContractFilterer) ParseNodeStatusUpdated(log types.Log) (*ContractNodeStatusUpdated, error) { - event := new(ContractNodeStatusUpdated) - if err := _Contract.contract.UnpackLog(event, "NodeStatusUpdated", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractRemoveAttributeIterator is returned from FilterRemoveAttribute and is used to iterate over the raw logs and unpacked data for RemoveAttribute events raised by the Contract contract. -type ContractRemoveAttributeIterator struct { - Event *ContractRemoveAttribute // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractRemoveAttributeIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractRemoveAttribute) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractRemoveAttribute) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractRemoveAttributeIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractRemoveAttributeIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractRemoveAttribute represents a RemoveAttribute event raised by the Contract contract. -type ContractRemoveAttribute struct { - DidAccount common.Address - Name []byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRemoveAttribute is a free log retrieval operation binding the contract event 0x8d8507f4f79e585e0cbbdeff53c51bfb6ffa6d44fbcc86e46ece2fad02b5d902. -// -// Solidity: event RemoveAttribute(address did_account, bytes name) -func (_Contract *ContractFilterer) FilterRemoveAttribute(opts *bind.FilterOpts) (*ContractRemoveAttributeIterator, error) { - - logs, sub, err := _Contract.contract.FilterLogs(opts, "RemoveAttribute") - if err != nil { - return nil, err - } - return &ContractRemoveAttributeIterator{contract: _Contract.contract, event: "RemoveAttribute", logs: logs, sub: sub}, nil -} - -// WatchRemoveAttribute is a free log subscription operation binding the contract event 0x8d8507f4f79e585e0cbbdeff53c51bfb6ffa6d44fbcc86e46ece2fad02b5d902. -// -// Solidity: event RemoveAttribute(address did_account, bytes name) -func (_Contract *ContractFilterer) WatchRemoveAttribute(opts *bind.WatchOpts, sink chan<- *ContractRemoveAttribute) (event.Subscription, error) { - - logs, sub, err := _Contract.contract.WatchLogs(opts, "RemoveAttribute") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractRemoveAttribute) - if err := _Contract.contract.UnpackLog(event, "RemoveAttribute", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRemoveAttribute is a log parse operation binding the contract event 0x8d8507f4f79e585e0cbbdeff53c51bfb6ffa6d44fbcc86e46ece2fad02b5d902. -// -// Solidity: event RemoveAttribute(address did_account, bytes name) -func (_Contract *ContractFilterer) ParseRemoveAttribute(log types.Log) (*ContractRemoveAttribute, error) { - event := new(ContractRemoveAttribute) - if err := _Contract.contract.UnpackLog(event, "RemoveAttribute", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractRoleAdminChangedIterator is returned from FilterRoleAdminChanged and is used to iterate over the raw logs and unpacked data for RoleAdminChanged events raised by the Contract contract. -type ContractRoleAdminChangedIterator struct { - Event *ContractRoleAdminChanged // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractRoleAdminChangedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractRoleAdminChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractRoleAdminChanged) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractRoleAdminChangedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractRoleAdminChangedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractRoleAdminChanged represents a RoleAdminChanged event raised by the Contract contract. -type ContractRoleAdminChanged struct { - Role [32]byte - PreviousAdminRole [32]byte - NewAdminRole [32]byte - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRoleAdminChanged is a free log retrieval operation binding the contract event 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff. -// -// Solidity: event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole) -func (_Contract *ContractFilterer) FilterRoleAdminChanged(opts *bind.FilterOpts, role [][32]byte, previousAdminRole [][32]byte, newAdminRole [][32]byte) (*ContractRoleAdminChangedIterator, error) { - - var roleRule []interface{} - for _, roleItem := range role { - roleRule = append(roleRule, roleItem) - } - var previousAdminRoleRule []interface{} - for _, previousAdminRoleItem := range previousAdminRole { - previousAdminRoleRule = append(previousAdminRoleRule, previousAdminRoleItem) - } - var newAdminRoleRule []interface{} - for _, newAdminRoleItem := range newAdminRole { - newAdminRoleRule = append(newAdminRoleRule, newAdminRoleItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "RoleAdminChanged", roleRule, previousAdminRoleRule, newAdminRoleRule) - if err != nil { - return nil, err - } - return &ContractRoleAdminChangedIterator{contract: _Contract.contract, event: "RoleAdminChanged", logs: logs, sub: sub}, nil -} - -// WatchRoleAdminChanged is a free log subscription operation binding the contract event 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff. -// -// Solidity: event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole) -func (_Contract *ContractFilterer) WatchRoleAdminChanged(opts *bind.WatchOpts, sink chan<- *ContractRoleAdminChanged, role [][32]byte, previousAdminRole [][32]byte, newAdminRole [][32]byte) (event.Subscription, error) { - - var roleRule []interface{} - for _, roleItem := range role { - roleRule = append(roleRule, roleItem) - } - var previousAdminRoleRule []interface{} - for _, previousAdminRoleItem := range previousAdminRole { - previousAdminRoleRule = append(previousAdminRoleRule, previousAdminRoleItem) - } - var newAdminRoleRule []interface{} - for _, newAdminRoleItem := range newAdminRole { - newAdminRoleRule = append(newAdminRoleRule, newAdminRoleItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "RoleAdminChanged", roleRule, previousAdminRoleRule, newAdminRoleRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractRoleAdminChanged) - if err := _Contract.contract.UnpackLog(event, "RoleAdminChanged", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRoleAdminChanged is a log parse operation binding the contract event 0xbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff. -// -// Solidity: event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole) -func (_Contract *ContractFilterer) ParseRoleAdminChanged(log types.Log) (*ContractRoleAdminChanged, error) { - event := new(ContractRoleAdminChanged) - if err := _Contract.contract.UnpackLog(event, "RoleAdminChanged", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractRoleGrantedIterator is returned from FilterRoleGranted and is used to iterate over the raw logs and unpacked data for RoleGranted events raised by the Contract contract. -type ContractRoleGrantedIterator struct { - Event *ContractRoleGranted // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractRoleGrantedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractRoleGranted) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractRoleGranted) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractRoleGrantedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractRoleGrantedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractRoleGranted represents a RoleGranted event raised by the Contract contract. -type ContractRoleGranted struct { - Role [32]byte - Account common.Address - Sender common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRoleGranted is a free log retrieval operation binding the contract event 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d. -// -// Solidity: event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender) -func (_Contract *ContractFilterer) FilterRoleGranted(opts *bind.FilterOpts, role [][32]byte, account []common.Address, sender []common.Address) (*ContractRoleGrantedIterator, error) { - - var roleRule []interface{} - for _, roleItem := range role { - roleRule = append(roleRule, roleItem) - } - var accountRule []interface{} - for _, accountItem := range account { - accountRule = append(accountRule, accountItem) - } - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "RoleGranted", roleRule, accountRule, senderRule) - if err != nil { - return nil, err - } - return &ContractRoleGrantedIterator{contract: _Contract.contract, event: "RoleGranted", logs: logs, sub: sub}, nil -} - -// WatchRoleGranted is a free log subscription operation binding the contract event 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d. -// -// Solidity: event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender) -func (_Contract *ContractFilterer) WatchRoleGranted(opts *bind.WatchOpts, sink chan<- *ContractRoleGranted, role [][32]byte, account []common.Address, sender []common.Address) (event.Subscription, error) { - - var roleRule []interface{} - for _, roleItem := range role { - roleRule = append(roleRule, roleItem) - } - var accountRule []interface{} - for _, accountItem := range account { - accountRule = append(accountRule, accountItem) - } - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "RoleGranted", roleRule, accountRule, senderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractRoleGranted) - if err := _Contract.contract.UnpackLog(event, "RoleGranted", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRoleGranted is a log parse operation binding the contract event 0x2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d. -// -// Solidity: event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender) -func (_Contract *ContractFilterer) ParseRoleGranted(log types.Log) (*ContractRoleGranted, error) { - event := new(ContractRoleGranted) - if err := _Contract.contract.UnpackLog(event, "RoleGranted", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractRoleRevokedIterator is returned from FilterRoleRevoked and is used to iterate over the raw logs and unpacked data for RoleRevoked events raised by the Contract contract. -type ContractRoleRevokedIterator struct { - Event *ContractRoleRevoked // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractRoleRevokedIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractRoleRevoked) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractRoleRevoked) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractRoleRevokedIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractRoleRevokedIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractRoleRevoked represents a RoleRevoked event raised by the Contract contract. -type ContractRoleRevoked struct { - Role [32]byte - Account common.Address - Sender common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterRoleRevoked is a free log retrieval operation binding the contract event 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b. -// -// Solidity: event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender) -func (_Contract *ContractFilterer) FilterRoleRevoked(opts *bind.FilterOpts, role [][32]byte, account []common.Address, sender []common.Address) (*ContractRoleRevokedIterator, error) { - - var roleRule []interface{} - for _, roleItem := range role { - roleRule = append(roleRule, roleItem) - } - var accountRule []interface{} - for _, accountItem := range account { - accountRule = append(accountRule, accountItem) - } - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "RoleRevoked", roleRule, accountRule, senderRule) - if err != nil { - return nil, err - } - return &ContractRoleRevokedIterator{contract: _Contract.contract, event: "RoleRevoked", logs: logs, sub: sub}, nil -} - -// WatchRoleRevoked is a free log subscription operation binding the contract event 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b. -// -// Solidity: event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender) -func (_Contract *ContractFilterer) WatchRoleRevoked(opts *bind.WatchOpts, sink chan<- *ContractRoleRevoked, role [][32]byte, account []common.Address, sender []common.Address) (event.Subscription, error) { - - var roleRule []interface{} - for _, roleItem := range role { - roleRule = append(roleRule, roleItem) - } - var accountRule []interface{} - for _, accountItem := range account { - accountRule = append(accountRule, accountItem) - } - var senderRule []interface{} - for _, senderItem := range sender { - senderRule = append(senderRule, senderItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "RoleRevoked", roleRule, accountRule, senderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractRoleRevoked) - if err := _Contract.contract.UnpackLog(event, "RoleRevoked", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseRoleRevoked is a log parse operation binding the contract event 0xf6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b. -// -// Solidity: event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender) -func (_Contract *ContractFilterer) ParseRoleRevoked(log types.Log) (*ContractRoleRevoked, error) { - event := new(ContractRoleRevoked) - if err := _Contract.contract.UnpackLog(event, "RoleRevoked", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the Contract contract. -type ContractTransferIterator struct { - Event *ContractTransfer // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractTransferIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractTransfer) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractTransferIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractTransferIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractTransfer represents a Transfer event raised by the Contract contract. -type ContractTransfer struct { - From common.Address - To common.Address - TokenId *big.Int - Raw types.Log // Blockchain specific contextual infos -} - -// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) -func (_Contract *ContractFilterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address, tokenId []*big.Int) (*ContractTransferIterator, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Contract.contract.FilterLogs(opts, "Transfer", fromRule, toRule, tokenIdRule) - if err != nil { - return nil, err - } - return &ContractTransferIterator{contract: _Contract.contract, event: "Transfer", logs: logs, sub: sub}, nil -} - -// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) -func (_Contract *ContractFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *ContractTransfer, from []common.Address, to []common.Address, tokenId []*big.Int) (event.Subscription, error) { - - var fromRule []interface{} - for _, fromItem := range from { - fromRule = append(fromRule, fromItem) - } - var toRule []interface{} - for _, toItem := range to { - toRule = append(toRule, toItem) - } - var tokenIdRule []interface{} - for _, tokenIdItem := range tokenId { - tokenIdRule = append(tokenIdRule, tokenIdItem) - } - - logs, sub, err := _Contract.contract.WatchLogs(opts, "Transfer", fromRule, toRule, tokenIdRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractTransfer) - if err := _Contract.contract.UnpackLog(event, "Transfer", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. -// -// Solidity: event Transfer(address indexed from, address indexed to, uint256 indexed tokenId) -func (_Contract *ContractFilterer) ParseTransfer(log types.Log) (*ContractTransfer, error) { - event := new(ContractTransfer) - if err := _Contract.contract.UnpackLog(event, "Transfer", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - -// ContractUpdateAttributeIterator is returned from FilterUpdateAttribute and is used to iterate over the raw logs and unpacked data for UpdateAttribute events raised by the Contract contract. -type ContractUpdateAttributeIterator struct { - Event *ContractUpdateAttribute // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ContractUpdateAttributeIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ContractUpdateAttribute) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ContractUpdateAttribute) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *ContractUpdateAttributeIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ContractUpdateAttributeIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ContractUpdateAttribute represents a UpdateAttribute event raised by the Contract contract. -type ContractUpdateAttribute struct { - Sender common.Address - DidAccount common.Address - Name []byte - Value []byte - Validity uint32 - Raw types.Log // Blockchain specific contextual infos -} - -// FilterUpdateAttribute is a free log retrieval operation binding the contract event 0x018be4d5e2634aefdb51c4ddd186f33072cfb5f6baf685579c2e214995dc9e4f. -// -// Solidity: event UpdateAttribute(address sender, address did_account, bytes name, bytes value, uint32 validity) -func (_Contract *ContractFilterer) FilterUpdateAttribute(opts *bind.FilterOpts) (*ContractUpdateAttributeIterator, error) { - - logs, sub, err := _Contract.contract.FilterLogs(opts, "UpdateAttribute") - if err != nil { - return nil, err - } - return &ContractUpdateAttributeIterator{contract: _Contract.contract, event: "UpdateAttribute", logs: logs, sub: sub}, nil -} - -// WatchUpdateAttribute is a free log subscription operation binding the contract event 0x018be4d5e2634aefdb51c4ddd186f33072cfb5f6baf685579c2e214995dc9e4f. -// -// Solidity: event UpdateAttribute(address sender, address did_account, bytes name, bytes value, uint32 validity) -func (_Contract *ContractFilterer) WatchUpdateAttribute(opts *bind.WatchOpts, sink chan<- *ContractUpdateAttribute) (event.Subscription, error) { - - logs, sub, err := _Contract.contract.WatchLogs(opts, "UpdateAttribute") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ContractUpdateAttribute) - if err := _Contract.contract.UnpackLog(event, "UpdateAttribute", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseUpdateAttribute is a log parse operation binding the contract event 0x018be4d5e2634aefdb51c4ddd186f33072cfb5f6baf685579c2e214995dc9e4f. -// -// Solidity: event UpdateAttribute(address sender, address did_account, bytes name, bytes value, uint32 validity) -func (_Contract *ContractFilterer) ParseUpdateAttribute(log types.Log) (*ContractUpdateAttribute, error) { - event := new(ContractUpdateAttribute) - if err := _Contract.contract.UnpackLog(event, "UpdateAttribute", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} diff --git a/core/call_contract.go b/core/call_contract.go deleted file mode 100644 index cade63d..0000000 --- a/core/call_contract.go +++ /dev/null @@ -1,1325 +0,0 @@ -package core - -import ( - "crypto/sha256" - "encoding/json" - "fmt" - "io" - "math/big" - "net" - "net/http" - "os" - "os/exec" - "runtime" - "strings" - "bytes" - "mime/multipart" - "strconv" - "time" - - "github.com/NetSepio/erebrus/contract" - ethcrypto "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/accounts/abi/bind" - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/ethclient" - "github.com/libp2p/go-libp2p" - libp2pcrypto "github.com/libp2p/go-libp2p/core/crypto" - libp2phost "github.com/libp2p/go-libp2p/core/host" - // "github.com/libp2p/go-libp2p/core/peer" - bip39 "github.com/tyler-smith/go-bip39" - bip32 "github.com/tyler-smith/go-bip32" - log "github.com/sirupsen/logrus" - "github.com/shirou/gopsutil/v3/cpu" - "github.com/shirou/gopsutil/v3/mem" - "github.com/shirou/gopsutil/v3/disk" - "crypto/ecdsa" - "context" - "golang.org/x/crypto/sha3" - "github.com/ethereum/go-ethereum/accounts/abi" - "github.com/ethereum/go-ethereum/core/types" -) - -const ( - colorRed = "\033[31m" - colorGreen = "\033[32m" - colorYellow = "\033[33m" - colorBlue = "\033[34m" - colorPurple = "\033[35m" - colorCyan = "\033[36m" - colorReset = "\033[0m" -) - -type PeaqIPInfo struct { - IP string `json:"ip"` - City string `json:"city"` - Region string `json:"region"` - Country string `json:"country"` - Loc string `json:"loc"` -} - -type SystemMetadata struct { - OS string `json:"os"` - Architecture string `json:"architecture"` - NumCPU int `json:"num_cpu"` - Hostname string `json:"hostname"` - LocalIPs []string `json:"local_ips"` - Environment string `json:"environment"` // "cloud" or "local" - GoVersion string `json:"go_version"` - RuntimeVersion string `json:"runtime_version"` - TotalRAM uint64 `json:"total_ram"` - UsedRAM uint64 `json:"used_ram"` - FreeRAM uint64 `json:"free_ram"` - TotalDisk uint64 `json:"total_disk"` - UsedDisk uint64 `json:"used_disk"` - FreeDisk uint64 `json:"free_disk"` - CPUUsage float64 `json:"cpu_usage"` - Version string `json:"version"` - CodeHash string `json:"code_hash"` -} - -type NFTAttribute struct { - TraitType string `json:"trait_type"` - Value string `json:"value"` -} - -type NFTMetadata struct { - Name string `json:"name"` - Description string `json:"description"` - Image string `json:"image"` - ExternalURL string `json:"externalUrl"` - Attributes []NFTAttribute `json:"attributes"` -} - -type IPFSResponse struct { - Name string `json:"Name"` - Hash string `json:"Hash"` - Size string `json:"Size"` -} - -// Custom reader for deterministic key generation -type reader struct { - seed []byte - pos int -} - -func (r *reader) Read(p []byte) (n int, err error) { - copy(p, r.seed) - return len(r.seed), nil -} - -func bytesReader(seed []byte) *reader { - return &reader{seed: seed} -} - -// makeBasicHost creates a LibP2P host with a deterministic peer ID using mnemonics -func makeBasicHost() (libp2phost.Host, error) { - // Get mnemonic from environment variable or use default - mnemonic := os.Getenv("MNEMONIC") - if mnemonic == "" { - log.Warn("MNEMONIC not set, using default mnemonic") - mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - } - - // Convert mnemonic to a BIP-32 seed - seed := bip39.NewSeed(mnemonic, "") - - // Derive a master key from the seed - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - return nil, fmt.Errorf("failed to create master key: %v", err) - } - - // Derive a child key - childKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild) - if err != nil { - return nil, fmt.Errorf("failed to derive child key: %v", err) - } - - // Convert the private key to an Ed25519 key - hashedKey := sha256.Sum256(childKey.Key) - priv, _, err := libp2pcrypto.GenerateKeyPairWithReader(libp2pcrypto.Ed25519, 256, bytesReader(hashedKey[:])) - if err != nil { - return nil, fmt.Errorf("failed to generate libp2p key: %v", err) - } - - opts := []libp2p.Option{ - libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/9002"), - libp2p.Identity(priv), - libp2p.DisableRelay(), - } - - host, err := libp2p.New(opts...) - if err != nil { - return nil, fmt.Errorf("failed to create libp2p host: %v", err) - } - - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🌟 LibP2P Host Created%s\n", colorGreen, colorReset) - fmt.Printf("%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🆔 Peer ID:%s %s\n", colorCyan, colorReset, host.ID().String()) - fmt.Printf("%s📡 Addresses:%s\n", colorCyan, colorReset) - for _, addr := range host.Addrs() { - fmt.Printf(" %s%s%s\n", colorBlue, addr.String(), colorReset) - } - fmt.Printf("%s%s%s\n\n", colorYellow, "====================================", colorReset) - - return host, nil -} - -var libp2pHost libp2phost.Host - -func GeneratePeaqDID() (string, error) { - var err error - if libp2pHost == nil { - libp2pHost, err = makeBasicHost() - if err != nil { - return "", fmt.Errorf("%s❌ Failed to create LibP2P host: %v%s", colorRed, err, colorReset) - } - } - - peerID := libp2pHost.ID().String() - return peerID, nil -} - -func init() { - log.SetFormatter(&log.TextFormatter{ - ForceColors: true, - FullTimestamp: true, - TimestampFormat: "2006-01-02 15:04:05", - }) -} - -func getLocalIPs() ([]string, error) { - var ips []string - addrs, err := net.InterfaceAddrs() - if err != nil { - return nil, err - } - for _, addr := range addrs { - if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - ips = append(ips, ipnet.IP.String()) - } - } - } - return ips, nil -} - -// isCloudEnvironment detects if the system is running in a cloud environment. -func isCloudEnvironment() string { - // Check for hypervisor UUID, used by major cloud providers - if content, err := os.ReadFile("/sys/hypervisor/uuid"); err == nil { - uuid := strings.ToLower(strings.TrimSpace(string(content))) - if strings.HasPrefix(uuid, "ec2") || strings.HasPrefix(uuid, "google") { - return "cloud" - } - } - - // Check for cloud-init presence - if _, err := os.Stat("/var/lib/cloud"); err == nil { - return "cloud" - } - - return "consumer" -} - -func uploadToIPFS(data string) (string, error) { - var b bytes.Buffer - w := multipart.NewWriter(&b) - - fw, err := w.CreateFormFile("file", "data.json") - if err != nil { - return "", fmt.Errorf("error creating form file: %v", err) - } - - _, err = io.Copy(fw, bytes.NewReader([]byte(data))) - if err != nil { - return "", fmt.Errorf("error copying data: %v", err) - } - - w.Close() - - req, err := http.NewRequest("POST", "https://ipfs.erebrus.io/api/v0/add?cid-version=1", &b) - if err != nil { - return "", fmt.Errorf("error creating request: %v", err) - } - - req.Header.Set("Content-Type", w.FormDataContentType()) - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return "", fmt.Errorf("error sending request: %v", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return "", fmt.Errorf("error reading response: %v", err) - } - - var ipfsResp IPFSResponse - if err := json.Unmarshal(body, &ipfsResp); err != nil { - return "", fmt.Errorf("error parsing response: %v", err) - } - - fmt.Printf("%s✅ IPFS Upload Successful%s\n", colorGreen, colorReset) - fmt.Printf("%s🔗 IPFS Hash:%s %s\n", colorPurple, colorReset, ipfsResp.Hash) - fmt.Printf("%s%s%s\n\n", colorYellow, "====================================", colorReset) - - return fmt.Sprintf("ipfs://%s", ipfsResp.Hash), nil -} - -func getSystemMetadata() (string, error) { - hostname, err := os.Hostname() - if err != nil { - hostname = "unknown" - } - - localIPs, err := getLocalIPs() - if err != nil { - localIPs = []string{"unknown"} - } - - environment := isCloudEnvironment() - - // Get memory info - memInfo, err := mem.VirtualMemory() - if err != nil { - return "", fmt.Errorf("failed to get memory stats: %v", err) - } - - // Get disk usage - diskInfo, err := disk.Usage("/") - if err != nil { - return "", fmt.Errorf("failed to get disk stats: %v", err) - } - - // Get version and code hash - codeHash, version := GetCodeHashAndVersion() - - metadata := SystemMetadata{ - OS: runtime.GOOS, - Architecture: runtime.GOARCH, - NumCPU: runtime.NumCPU(), - Hostname: hostname, - LocalIPs: localIPs, - Environment: environment, - GoVersion: runtime.Version(), - RuntimeVersion: runtime.Version(), - // Memory in bytes - TotalRAM: memInfo.Total, - UsedRAM: memInfo.Used, - FreeRAM: memInfo.Free, - // Disk space in bytes - TotalDisk: diskInfo.Total, - UsedDisk: diskInfo.Used, - FreeDisk: diskInfo.Free, - CPUUsage: getCPUUsage(), - // Version info - Version: version, - CodeHash: codeHash, - } - - // Print the metadata in a formatted way - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ System Metadata ═══════════", colorReset) - fmt.Printf("%s• OS/Arch:%s %s/%s\n", colorCyan, colorReset, metadata.OS, metadata.Architecture) - fmt.Printf("%s• Hostname:%s %s\n", colorCyan, colorReset, metadata.Hostname) - fmt.Printf("%s• Environment:%s %s\n", colorCyan, colorReset, metadata.Environment) - fmt.Printf("%s• Local IPs:%s %s\n", colorCyan, colorReset, strings.Join(metadata.LocalIPs, ", ")) - fmt.Printf("%s• CPU:%s %d cores (Usage: %.2f%%)\n", colorCyan, colorReset, metadata.NumCPU, metadata.CPUUsage) - fmt.Printf("%s• Memory:%s %.2f/%.2f GB (%.2f GB free)\n", colorCyan, colorReset, - float64(metadata.UsedRAM)/1024/1024/1024, - float64(metadata.TotalRAM)/1024/1024/1024, - float64(metadata.FreeRAM)/1024/1024/1024) - fmt.Printf("%s• Disk:%s %.2f/%.2f GB (%.2f GB free)\n", colorCyan, colorReset, - float64(metadata.UsedDisk)/1024/1024/1024, - float64(metadata.TotalDisk)/1024/1024/1024, - float64(metadata.FreeDisk)/1024/1024/1024) - fmt.Printf("%s• Go Version:%s %s\n", colorCyan, colorReset, metadata.GoVersion) - fmt.Printf("%s• Runtime Version:%s %s\n", colorCyan, colorReset, metadata.RuntimeVersion) - fmt.Printf("%s• Version:%s %s\n", colorCyan, colorReset, metadata.Version) - fmt.Printf("%s• Code Hash:%s %s\n", colorCyan, colorReset, metadata.CodeHash) - fmt.Printf("%s%s%s\n", colorYellow, "══════════════════════════════════════", colorReset) - - metadataJSON, err := json.Marshal(metadata) - if err != nil { - return "", fmt.Errorf("failed to marshal system metadata: %v", err) - } - - // Upload to IPFS - ipfsPath, err := uploadToIPFS(string(metadataJSON)) - if err != nil { - return "", fmt.Errorf("failed to upload metadata to IPFS: %v", err) - } - - return ipfsPath, nil -} - -// Helper function to get CPU usage -func getCPUUsage() float64 { - // Get CPU usage percentage - percentage, err := cpu.Percent(time.Second, false) - if err != nil { - return 0.0 - } - if len(percentage) > 0 { - return percentage[0] - } - return 0.0 -} - -func generateNFTMetadata(nodeName string, nodeSpec string, nodeConfig string) (string, error) { - configValue := os.Getenv("NODE_CONFIG") - if configValue == "" { - configValue = "STANDARD" - } - - accessValue := os.Getenv("NODE_ACCESS") - if accessValue == "" { - accessValue = "public" - } - - nodeID, err := GeneratePeaqDID() - if err != nil { - return "", fmt.Errorf("%s❌ Failed to generate node ID for metadata: %v%s", colorRed, err, colorReset) - } - - metadata := NFTMetadata{ - Name: fmt.Sprintf("%s | Erebrus Node", nodeName), - Description: "This Soulbound NFT is more than just a token—it's a declaration of digital sovereignty. " + - "As an Erebrus Node, it stands as an unyielding pillar of privacy and security, forging a path " + - "beyond the reach of Big Tech's surveillance and censorship. This is not just technology; it's a " + - "revolution. Welcome to the frontlines of digital freedom. Thank you for being a part of the movement.", - Image: "ipfs://bafybeig6unjraufdpiwnzrqudl5vy3ozep2pzc3hwiiqd4lgcjfhaockpm", - ExternalURL: "https://erebrus.io", - Attributes: []NFTAttribute{ - {TraitType: "id", Value: nodeID}, - {TraitType: "name", Value: nodeName}, - {TraitType: "spec", Value: "erebrus"}, - {TraitType: "config", Value: configValue}, - {TraitType: "access", Value: accessValue}, - {TraitType: "status", Value: "registered"}, - }, - } - - nftMetadataJSON, err := json.Marshal(metadata) - if err != nil { - return "", fmt.Errorf("%s❌ Failed to marshal NFT metadata: %v%s", colorRed, err, colorReset) - } - - // Log NFT metadata in a colorful box - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ NFT Metadata ═══════════", colorReset) - fmt.Printf("%s• Name:%s %s\n", colorCyan, colorReset, metadata.Name) - fmt.Printf("%s• Description:%s %s\n", colorCyan, colorReset, metadata.Description) - fmt.Printf("%s• Image:%s %s\n", colorCyan, colorReset, metadata.Image) - fmt.Printf("%s• External URL:%s %s\n", colorCyan, colorReset, metadata.ExternalURL) - fmt.Printf("%s• Attributes:%s\n", colorCyan, colorReset) - for _, attr := range metadata.Attributes { - fmt.Printf(" %s◦ %s:%s %s\n", colorPurple, attr.TraitType, colorReset, attr.Value) - } - fmt.Printf("%s%s%s\n\n", colorYellow, "══════════════════════════════════", colorReset) - - return string(nftMetadataJSON), nil -} - -// AddDIDAttribute adds DID attributes to the PEAQ DID registry contract -func AddDIDAttribute(nodeID string, systemMetadata string, nftMetadata string, privateKey *ecdsa.PrivateKey) error { - chainName := strings.ToLower(os.Getenv("CHAIN_NAME")) - if chainName != "peaq" && chainName != "monadtestnet" && chainName != "risetestnet" { - return nil - } - - didRegistryContractAddress := "0x0000000000000000000000000000000000000800" - - // Connect to the Ethereum client - rpcURL := os.Getenv("RPC_URL") - client, err := ethclient.Dial(rpcURL) - if err != nil { - return fmt.Errorf("Failed to connect to the Ethereum client: %v", err) - } - - chainID, err := client.NetworkID(context.Background()) - if err != nil { - return fmt.Errorf("Failed to get network ID: %v", err) - } - - // Get the wallet address from the private key - publicKey := privateKey.Public().(*ecdsa.PublicKey) - fromAddress := ethcrypto.PubkeyToAddress(*publicKey) - - // Fetch the correct nonce - nonce, err := client.PendingNonceAt(context.Background(), fromAddress) - if err != nil { - return fmt.Errorf("Failed to get nonce: %v", err) - } - - gasPrice, err := client.SuggestGasPrice(context.Background()) - if err != nil { - return fmt.Errorf("Failed to get gas price: %v", err) - } - - // Get IP info from ipinfo.io for creating IP info IPFS hash - resp, err := http.Get("https://ipinfo.io/json") - if err != nil { - return fmt.Errorf("Failed to get IP info: %v", err) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("Failed to read IP info response: %v", err) - } - - var ipInfo PeaqIPInfo - if err := json.Unmarshal(body, &ipInfo); err != nil { - return fmt.Errorf("Failed to parse IP info: %v", err) - } - - // Hash the IP address using SHA-3 - ipHash := sha3.Sum256([]byte(ipInfo.IP)) - hashedIP := fmt.Sprintf("0x%x", ipHash) - - ipInfoData := map[string]interface{}{ - "ip": hashedIP, - "region": ipInfo.Region, - "location": ipInfo.Loc, - "country": ipInfo.Country, - "city": ipInfo.City, - } - - ipInfoJSON, err := json.Marshal(ipInfoData) - if err != nil { - return fmt.Errorf("Failed to marshal IP info: %v", err) - } - - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%sUploading IP Info to IPFS:%s\n", colorCyan, colorReset) - ipInfoIPFS, err := uploadToIPFS(string(ipInfoJSON)) - if err != nil { - return fmt.Errorf("Failed to upload IP info to IPFS: %v", err) - } - - // Extract CID from ipfs:// URL format - systemMetadataCID := strings.TrimPrefix(systemMetadata, "ipfs://") - nftMetadataCID := strings.TrimPrefix(nftMetadata, "ipfs://") - ipInfoCID := strings.TrimPrefix(ipInfoIPFS, "ipfs://") - - // Create the DID in the format did:peaq:{nodeID}#netsepio - didAccount := fromAddress - name := fmt.Sprintf("did:peaq:%s#netsepio", fromAddress.Hex()) - - valueObject := []map[string]string{ - {"ID": "#node", "Type": "nodeInfo", "ServiceEndpoint": fmt.Sprintf("ipfs://%s", nftMetadataCID)}, - {"ID": "#system", "Type": "systemInfo", "ServiceEndpoint": fmt.Sprintf("ipfs://%s", systemMetadataCID)}, - {"ID": "#ip", "Type": "ipInfo", "ServiceEndpoint": fmt.Sprintf("ipfs://%s", ipInfoCID)}, - } - - valueJSON, err := json.Marshal(valueObject) - if err != nil { - return fmt.Errorf("Failed to encode JSON: %v", err) - } - - // Set validity for 1 year (31536000 seconds) - validityFor := uint32(31536000) - - parsedABI, err := abi.JSON(strings.NewReader(`[ { "name": "addAttribute", "type": "function", "inputs": [ { "name": "did_account", "type": "address" }, { "name": "name", "type": "bytes" }, { "name": "value", "type": "bytes" }, { "name": "validity_for", "type": "uint32" } ] } ]`)) - if err != nil { - return fmt.Errorf("Failed to parse ABI: %v", err) - } - - data, err := parsedABI.Pack("addAttribute", didAccount, []byte(name), valueJSON, validityFor) - if err != nil { - return fmt.Errorf("Failed to encode transaction data: %v", err) - } - - tx := types.NewTransaction( - nonce, - common.HexToAddress(didRegistryContractAddress), - big.NewInt(0), - 200000, // Gas limit - gasPrice, - data, - ) - - signer := types.LatestSignerForChainID(chainID) - signedTx, err := types.SignTx(tx, signer, privateKey) - if err != nil { - return fmt.Errorf("Failed to sign transaction: %v", err) - } - - err = client.SendTransaction(context.Background(), signedTx) - if err != nil { - return fmt.Errorf("Failed to send transaction: %v", err) - } - - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ DID Attribute Added ═══════════", colorReset) - fmt.Printf("%s• DID Account:%s %s\n", colorCyan, colorReset, didAccount.Hex()) - fmt.Printf("%s• DID Name:%s %s\n", colorCyan, colorReset, name) - fmt.Printf("%s• Transaction Hash:%s %s\n", colorCyan, colorReset, signedTx.Hash().Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "══════════════════════════════════════", colorReset) - - return nil -} - -func RegisterNodeOnChain() error { - chainName := strings.ToLower(os.Getenv("CHAIN_NAME")) - if chainName != "peaq" && chainName != "monadtestnet" && chainName != "risetestnet" { - return nil - } - - // Connect to the Ethereum client - rpcURL := os.Getenv("RPC_URL") - client, err := ethclient.Dial(rpcURL) - if err != nil { - return fmt.Errorf("%s❌ Failed to connect to the Ethereum client: %v%s", colorRed, err, colorReset) - } - - // Create a new instance of the contract - contractAddress := common.HexToAddress(os.Getenv("CONTRACT_ADDRESS")) - instance, err := contract.NewContract(contractAddress, client) - if err != nil { - return fmt.Errorf("%s❌ Failed to instantiate contract: %v%s", colorRed, err, colorReset) - } - - nodeID, err := GeneratePeaqDID() - if err != nil { - return fmt.Errorf("%s❌ Failed to generate DID: %v%s", colorRed, err, colorReset) - } - - // Get wallet details from mnemonic - mnemonic := os.Getenv("MNEMONIC") - if mnemonic == "" { - return fmt.Errorf("%s❌ MNEMONIC not found in environment variables%s", colorRed, colorReset) - } - - privateKey, ownerAddress, err := deriveWalletFromMnemonic(mnemonic) - if err != nil { - return fmt.Errorf("%s❌ Failed to derive wallet from mnemonic: %v%s", colorRed, err, colorReset) - } - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ Wallet Details ═══════════", colorReset) - fmt.Printf("%s• Mnemonic:%s %s\n", colorCyan, colorReset, mnemonic) - fmt.Printf("%s• Private Key:%s %x\n", colorCyan, colorReset, ethcrypto.FromECDSA(privateKey)) - fmt.Printf("%s• Wallet Address:%s %s\n", colorCyan, colorReset, ownerAddress) - fmt.Printf("%s%s%s\n\n", colorYellow, "══════════════════════════════════", colorReset) - - // Generate the standard DID format for use in the contract - nodeDID := fmt.Sprintf("did:%s:%s", "netsepio", nodeID) - - // Get chain ID from RPC URL - chainID, err := getChainID(rpcURL) - if err != nil { - return fmt.Errorf("%s❌ Failed to get chain ID: %v%s", colorRed, err, colorReset) - } - - // Create auth with derived wallet - auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) - if err != nil { - return fmt.Errorf("%s❌ Failed to create transactor: %v%s", colorRed, err, colorReset) - } - - // Get node address from wallet - nodeAddress := auth.From - - // Prepare registration parameters - nodeName := os.Getenv("NODE_NAME") - nodeSpec := "erebrus" - nodeConfig := os.Getenv("NODE_CONFIG") - - // Get system metadata and upload to IPFS - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%sUploading System Metadata to IPFS:%s\n", colorCyan, colorReset) - metadata, err := getSystemMetadata() - if err != nil { - return fmt.Errorf("%s❌ Failed to get system metadata: %v%s", colorRed, err, colorReset) - } - - // Generate NFT metadata and upload to IPFS - nftMetadataJSON, err := generateNFTMetadata(nodeName, nodeSpec, nodeConfig) - if err != nil { - return fmt.Errorf("%s❌ Failed to generate NFT metadata: %v%s", colorRed, err, colorReset) - } - - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%sUploading NFT Metadata to IPFS:%s\n", colorCyan, colorReset) - // Upload NFT metadata to IPFS - nftMetadata, err := uploadToIPFS(nftMetadataJSON) - if err != nil { - return fmt.Errorf("%s❌ Failed to upload NFT metadata to IPFS: %v%s", colorRed, err, colorReset) - } - - // Use the derived owner address - owner := ownerAddress - - // Get IP info from ipinfo.io - resp, err := http.Get("https://ipinfo.io/json") - if err != nil { - return fmt.Errorf("%s❌ Failed to get IP info: %v%s", colorRed, err, colorReset) - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed to read IP info response: %v", err) - } - - var ipInfo PeaqIPInfo - if err := json.Unmarshal(body, &ipInfo); err != nil { - return fmt.Errorf("failed to parse IP info: %v", err) - } - - // Hash the IP address using SHA-3 - ipHash := sha3.Sum256([]byte(ipInfo.IP)) - hashedIP := fmt.Sprintf("0x%x", ipHash) - - // Print all parameters being passed to RegisterNode - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ RegisterNode Parameters ═══════════", colorReset) - fmt.Printf("%s• Node Address:%s %s\n", colorCyan, colorReset, nodeAddress.Hex()) - fmt.Printf("%s• Node ID:%s %s\n", colorCyan, colorReset, nodeID) - - if strings.ToLower(chainName) == "peaq" { - displayDID := fmt.Sprintf("did:peaq:%s#netsepio", ownerAddress.Hex()) - fmt.Printf("%s• Node DID:%s %s\n", colorCyan, colorReset, displayDID) - } else { - fmt.Printf("%s• Node DID:%s %s\n", colorCyan, colorReset, nodeDID) - } - fmt.Printf("%s• Node Name:%s %s\n", colorCyan, colorReset, nodeName) - fmt.Printf("%s• Node Spec:%s %s\n", colorCyan, colorReset, nodeSpec) - fmt.Printf("%s• Node Config:%s %s\n", colorCyan, colorReset, nodeConfig) - fmt.Printf("%s• IP Address (Original):%s %s\n", colorCyan, colorReset, ipInfo.IP) - fmt.Printf("%s• IP Address (Hashed):%s %s\n", colorCyan, colorReset, hashedIP) - fmt.Printf("%s• Region:%s %s\n", colorCyan, colorReset, ipInfo.Region) - fmt.Printf("%s• Location:%s %s\n", colorCyan, colorReset, ipInfo.Loc) - fmt.Printf("%s• Metadata:%s %s\n", colorCyan, colorReset, metadata) - fmt.Printf("%s• NFT Metadata:%s %s\n", colorCyan, colorReset, nftMetadata) - fmt.Printf("%s• Owner:%s %s\n", colorCyan, colorReset, owner.Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "══════════════════════════════════════════", colorReset) - - tx, err := instance.RegisterNode( - auth, - nodeAddress, // _addr - nodeID, // id - nodeDID, // did (new parameter) - nodeName, // name - nodeSpec, // spec - nodeConfig, // config - hashedIP, // ipAddress (now using hashed IP) - ipInfo.Region, // region - ipInfo.Loc, // location (coordinates) - metadata, // metadata - nftMetadata, // nftMetadata - owner, // _owner - ) - - if err != nil { - if strings.Contains(err.Error(), "Node already exists") { - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ Node Status ═══════════", colorReset) - fmt.Printf("%s• Status:%s Already Registered\n", colorCyan, colorReset) - fmt.Printf("%s• Node ID:%s %s\n", colorCyan, colorReset, nodeID) - - if strings.ToLower(chainName) == "peaq" { - displayDID := fmt.Sprintf("did:peaq:%s#netsepio", ownerAddress.Hex()) - fmt.Printf("%s• Node DID:%s %s\n", colorCyan, colorReset, displayDID) - } else { - fmt.Printf("%s• Node DID:%s %s\n", colorCyan, colorReset, nodeDID) - } - - // Get node details for already registered node - node, err := instance.Nodes(nil, nodeID) - if err != nil { - return fmt.Errorf("Failed to get node details: %v", err) - } - - tokenOwner, err := instance.OwnerOf(nil, node.TokenId) - if err != nil { - return fmt.Errorf("Failed to get token owner: %v", err) - } - - fmt.Printf("%s• Token ID:%s %s\n", colorCyan, colorReset, node.TokenId.String()) - fmt.Printf("%s• Token Owner:%s %s\n", colorCyan, colorReset, tokenOwner.Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "═════════════════════════════════", colorReset) - - // Start periodic checkpoints for already registered node - log.WithFields(log.Fields{ - "nodeID": nodeID, - "interval": "15 minutes", - }).Info("Starting periodic checkpoint creation for existing node") - - CreatePeriodicCheckpoints(nodeID, client, instance, auth) - } else { - return fmt.Errorf("Failed to register node: %v", err) - } - } else { - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ Node Registration ═══════════", colorReset) - fmt.Printf("%s• Status:%s Registration Initiated\n", colorCyan, colorReset) - fmt.Printf("%s• Node ID:%s %s\n", colorCyan, colorReset, nodeID) - - if strings.ToLower(chainName) == "peaq" { - displayDID := fmt.Sprintf("did:peaq:%s#netsepio", ownerAddress.Hex()) - fmt.Printf("%s• Node DID:%s %s\n", colorCyan, colorReset, displayDID) - } else { - fmt.Printf("%s• Node DID:%s %s\n", colorCyan, colorReset, nodeDID) - } - - fmt.Printf("%s• Transaction:%s %s\n", colorCyan, colorReset, tx.Hash().Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "══════════════════════════════════════", colorReset) - - // Wait for transaction to be mined - receipt, err := bind.WaitMined(context.Background(), client, tx) - if err != nil { - return fmt.Errorf("Failed to wait for registration transaction: %v", err) - } - if receipt.Status == 0 { - return fmt.Errorf("Registration transaction failed") - } - - // Wait for node to be fully registered by checking token ownership - fmt.Printf("%s• Waiting for registration to complete...%s\n", colorCyan, colorReset) - maxRetries := 30 // Maximum number of retries - retryDelay := 10 * time.Second // Delay between retries - - for i := 0; i < maxRetries; i++ { - // Get node details to get tokenId - node, err := instance.Nodes(nil, nodeID) - if err != nil { - time.Sleep(retryDelay) - continue - } - - // Try to get token owner - tokenOwner, err := instance.OwnerOf(nil, node.TokenId) - if err != nil { - time.Sleep(retryDelay) - continue - } - - if tokenOwner != (common.Address{}) { - fmt.Printf("%s• Registration Complete%s\n", colorGreen, colorReset) - fmt.Printf("%s• Token ID:%s %s\n", colorCyan, colorReset, node.TokenId.String()) - fmt.Printf("%s• Token Owner:%s %s\n", colorCyan, colorReset, tokenOwner.Hex()) - - // Add DID attributes only after successful registration of a new node - err = AddDIDAttribute(nodeID, metadata, nftMetadata, privateKey) - if err != nil { - log.WithError(err).Warn("Failed to add DID attributes") - } - - // Start periodic checkpoints only after confirmed registration - log.WithFields(log.Fields{ - "nodeID": nodeID, - "interval": "15 minutes", - }).Info("Starting periodic checkpoint creation") - - CreatePeriodicCheckpoints(nodeID, client, instance, auth) - return nil - } - - time.Sleep(retryDelay) - } - - return fmt.Errorf("Timeout waiting for node registration to complete") - } - - return nil -} - -func CreatePeriodicCheckpoints(nodeID string, client *ethclient.Client, instance *contract.Contract, auth *bind.TransactOpts) { - checkpointIntervalStr := os.Getenv("CHECKPOINT_INTERVAL_MINUTES") - checkpointInterval := 15 * time.Minute // Default: 15 minutes - - if checkpointIntervalStr != "" { - intervalMinutes, err := strconv.Atoi(checkpointIntervalStr) - if err == nil && intervalMinutes > 0 { - checkpointInterval = time.Duration(intervalMinutes) * time.Minute - } else { - log.WithFields(log.Fields{ - "providedValue": checkpointIntervalStr, - "defaultValue": "15 minutes", - }).Warn("Invalid CHECKPOINT_INTERVAL_MINUTES, using default") - } - } - - ticker := time.NewTicker(checkpointInterval) - - // Log the start of checkpoint creation - log.WithFields(log.Fields{ - "nodeID": nodeID, - "interval": fmt.Sprintf("%d minutes", int(checkpointInterval.Minutes())), - }).Info("Periodic checkpoint creation initialized") - - go func() { - // Create first checkpoint immediately - createCheckpoint(nodeID, instance, auth) - - // Then create checkpoints periodically - for range ticker.C { - createCheckpoint(nodeID, instance, auth) - } - }() -} - -// SystemMetrics represents the system metrics for checkpoints -type SystemMetrics struct { - Timestamp int64 `json:"timestamp"` - ConnectedClients int `json:"connected_clients"` - ClientStats []ClientStats `json:"client_stats"` - Uptime string `json:"uptime"` -} - -// ClientStats represents the bandwidth statistics of a WireGuard client -type ClientStats struct { - Client string `json:"client"` - RX string `json:"rx"` - TX string `json:"tx"` -} - -var ( - programStartTime = time.Now() // Store program start time -) - -// getSystemMetrics collects system metrics including WireGuard stats -func getSystemMetrics() (*SystemMetrics, error) { - // Get WireGuard client stats - clients, err := getBandwidthStats() - if err != nil { - log.WithError(err).Warn("Failed to get bandwidth stats") - } - - // Calculate program uptime - uptime := time.Since(programStartTime) - uptimeStr := formatDuration(uptime) - - metrics := &SystemMetrics{ - Timestamp: time.Now().Unix(), - ConnectedClients: len(clients), - ClientStats: clients, - Uptime: uptimeStr, - } - - return metrics, nil -} - -// formatDuration converts duration to a human-readable string -func formatDuration(d time.Duration) string { - days := int(d.Hours()) / 24 - hours := int(d.Hours()) % 24 - minutes := int(d.Minutes()) % 60 - - var parts []string - if days > 0 { - if days == 1 { - parts = append(parts, "1 day") - } else { - parts = append(parts, fmt.Sprintf("%d days", days)) - } - } - if hours > 0 { - if hours == 1 { - parts = append(parts, "1 hour") - } else { - parts = append(parts, fmt.Sprintf("%d hours", hours)) - } - } - if minutes > 0 || len(parts) == 0 { - if minutes == 1 { - parts = append(parts, "1 minute") - } else { - parts = append(parts, fmt.Sprintf("%d minutes", minutes)) - } - } - - return fmt.Sprintf("up %s", strings.Join(parts, ", ")) -} - -// getBandwidthStats fetches the bandwidth stats of WireGuard clients -func getBandwidthStats() ([]ClientStats, error) { - var clients []ClientStats - - // Get the latest handshakes - cmd := exec.Command("bash", "-c", "wg show wg0 latest-handshakes") - var out bytes.Buffer - cmd.Stdout = &out - if err := cmd.Run(); err != nil { - return nil, err - } - - nowCmd := exec.Command("date", "+%s") - nowOut, err := nowCmd.Output() - if err != nil { - return nil, err - } - - now, err := strconv.Atoi(strings.TrimSpace(string(nowOut))) - if err != nil { - return nil, err - } - - var activeClients []string - for _, line := range strings.Split(out.String(), "\n") { - fields := strings.Fields(line) - if len(fields) < 2 { - continue - } - - handshakeTime, err := strconv.Atoi(fields[1]) - if err != nil { - continue - } - - if handshakeTime > 0 && (now-handshakeTime) < 120 { - activeClients = append(activeClients, fields[0]) - } - } - - // Get the transfer stats - cmd = exec.Command("wg", "show", "wg0", "transfer") - out.Reset() - cmd.Stdout = &out - if err := cmd.Run(); err != nil { - return nil, err - } - - transferStats := out.String() - for _, client := range activeClients { - for _, line := range strings.Split(transferStats, "\n") { - if strings.Contains(line, client) { - fields := strings.Fields(line) - if len(fields) < 3 { - continue - } - - rxBytes, err := strconv.ParseFloat(fields[1], 64) - if err != nil { - continue - } - txBytes, err := strconv.ParseFloat(fields[2], 64) - if err != nil { - continue - } - - rxMB := rxBytes / 1024 / 1024 - txMB := txBytes / 1024 / 1024 - - clients = append(clients, ClientStats{ - Client: client, - RX: strconv.FormatFloat(rxMB, 'f', 4, 64) + " MB", - TX: strconv.FormatFloat(txMB, 'f', 4, 64) + " MB", - }) - } - } - } - - return clients, nil -} - -// For wallet derivation logging -func deriveWalletFromMnemonic(mnemonic string) (*ecdsa.PrivateKey, common.Address, error) { - walletAddress, privateKey, err := GenerateEthereumWalletAddress(mnemonic) - if err != nil { - return nil, common.Address{}, fmt.Errorf("failed to generate wallet: %v", err) - } - - address := common.HexToAddress(walletAddress) - return privateKey, address, nil -} - -func getChainID(rpcURL string) (*big.Int, error) { - client, err := ethclient.Dial(rpcURL) - if err != nil { - return nil, fmt.Errorf("failed to connect to RPC: %v", err) - } - defer client.Close() - - chainID, err := client.ChainID(context.Background()) - if err != nil { - return nil, fmt.Errorf("failed to get chain ID: %v", err) - } - - return chainID, nil -} - -func createCheckpoint(nodeID string, instance *contract.Contract, auth *bind.TransactOpts) { - startTime := time.Now() - - // Get system metrics - metrics, err := getSystemMetrics() - if err != nil { - log.WithFields(log.Fields{ - "nodeID": nodeID, - "error": err, - }).Error("Failed to get system metrics") - return - } - - // Convert metrics to JSON - dataJSON, err := json.Marshal(metrics) - if err != nil { - log.WithFields(log.Fields{ - "nodeID": nodeID, - "error": err, - }).Error("Failed to marshal checkpoint data") - return - } - - // Get wallet details from mnemonic - mnemonic := os.Getenv("MNEMONIC") - if mnemonic == "" { - log.Error("MNEMONIC not found in environment variables") - return - } - - privateKey, _, err := deriveWalletFromMnemonic(mnemonic) - if err != nil { - log.WithError(err).Error("Failed to derive wallet from mnemonic") - return - } - - // Get chain ID from RPC URL - rpcURL := os.Getenv("RPC_URL") - if rpcURL == "" { - log.Error("RPC_URL not found in environment variables") - return - } - - chainID, err := getChainID(rpcURL) - if err != nil { - log.WithError(err).Error("Failed to get chain ID") - return - } - - // Create new auth with the derived private key and chain ID - newAuth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) - if err != nil { - log.WithError(err).Error("Failed to create transaction auth") - return - } - - // Copy over any existing auth settings - if auth != nil { - newAuth.GasLimit = auth.GasLimit - newAuth.GasPrice = auth.GasPrice - newAuth.Nonce = auth.Nonce - } - - // Create checkpoint transaction - tx, err := instance.CreateCheckpoint(newAuth, nodeID, string(dataJSON)) - if err != nil { - log.WithFields(log.Fields{ - "nodeID": nodeID, - "error": err, - }).Error("Failed to create checkpoint") - return - } - - duration := time.Since(startTime) - - fmt.Printf("\n%s%s%s\n", colorYellow, "═══════════ Checkpoint Created ═══════════", colorReset) - fmt.Printf("%s• Node ID:%s %s\n", colorCyan, colorReset, nodeID) - fmt.Printf("%s• Time:%s %s\n", colorCyan, colorReset, startTime.Format(time.RFC3339)) - fmt.Printf("%s• Duration:%s %s\n", colorCyan, colorReset, duration) - fmt.Printf("%s• Transaction:%s %s\n", colorCyan, colorReset, tx.Hash().Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "══════════════════════════════════════", colorReset) -} - -// GetNodeStatus retrieves the current status of the node from the contract -func GetNodeStatus() (*NodeStatus, error) { - chainName := strings.ToLower(os.Getenv("CHAIN_NAME")) - if chainName != "peaq" && chainName != "monadtestnet" && chainName != "risetestnet" { - return nil, fmt.Errorf("Chain not configured") - } - - // Connect to the Ethereum client - client, err := ethclient.Dial(os.Getenv("RPC_URL")) - if err != nil { - return nil, fmt.Errorf("Failed to connect to the Ethereum client: %v", err) - } - - // Create a new instance of the contract - contractAddress := common.HexToAddress(os.Getenv("CONTRACT_ADDRESS")) - instance, err := contract.NewContract(contractAddress, client) - if err != nil { - return nil, fmt.Errorf("Failed to instantiate contract: %v", err) - } - - // Get the node ID - nodeID, err := GeneratePeaqDID() - if err != nil { - return nil, fmt.Errorf("Failed to generate Peaq DID: %v", err) - } - - // Get node data from the contract - node, err := instance.Nodes(&bind.CallOpts{}, nodeID) - if err != nil { - return nil, fmt.Errorf("Failed to get node data: %v", err) - } - - // Get latest checkpoint - checkpoint, err := instance.Checkpoint(&bind.CallOpts{}, nodeID) - if err != nil { - log.WithError(err).Warn("Failed to get checkpoint data") - } - - return &NodeStatus{ - ID: nodeID, - Name: node.Name, - Spec: node.Spec, - Config: node.Config, - IPAddress: node.IpAddress, - Region: node.Region, - Location: node.Location, - Owner: node.Owner, - TokenID: node.TokenId, - Status: node.Status, - Checkpoint: checkpoint, - }, nil -} - -// NodeStatus represents the current status of a node -type NodeStatus struct { - ID string - Name string - Spec string - Config string - IPAddress string - Region string - Location string - Owner common.Address - TokenID *big.Int - Status uint8 - Checkpoint string -} - -// GetStatusText returns the text representation of the node status -func (ns *NodeStatus) GetStatusText() string { - statusText := []string{"Offline", "Online", "Maintenance", "Deactivated"} - if ns.Status < uint8(len(statusText)) { - return statusText[ns.Status] - } - return "Unknown" -} - -// GetStatusEmoji returns the emoji representation of the node status -func (ns *NodeStatus) GetStatusEmoji() string { - statusEmoji := []string{"🔴", "🟢", "🟡", "⚫"} - if ns.Status < uint8(len(statusEmoji)) { - return statusEmoji[ns.Status] - } - return "❓" -} - -// DeactivateNode deactivates the node in the contract -func DeactivateNode() error { - chainName := strings.ToLower(os.Getenv("CHAIN_NAME")) - if chainName != "peaq" && chainName != "monadtestnet" && chainName != "risetestnet" { - return fmt.Errorf("Chain not configured") - } - - // Connect to the Ethereum client - client, err := ethclient.Dial(os.Getenv("RPC_URL")) - if err != nil { - return fmt.Errorf("Failed to connect to the Ethereum client: %v", err) - } - - // Create a new instance of the contract - contractAddress := common.HexToAddress(os.Getenv("CONTRACT_ADDRESS")) - instance, err := contract.NewContract(contractAddress, client) - if err != nil { - return fmt.Errorf("Failed to instantiate contract: %v", err) - } - - // Get the node ID - nodeID, err := GeneratePeaqDID() - if err != nil { - return fmt.Errorf("Failed to generate Peaq DID: %v", err) - } - - // Create auth options for the transaction - privateKey, err := ethcrypto.HexToECDSA(os.Getenv("PRIVATE_KEY")) - if err != nil { - return fmt.Errorf("Failed to create private key: %v", err) - } - - chainID, ok := new(big.Int).SetString(os.Getenv("CHAIN_ID"), 10) - if !ok { - return fmt.Errorf("Failed to parse CHAIN_ID") - } - - auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) - if err != nil { - return fmt.Errorf("Failed to create transactor: %v", err) - } - - // Call deactivateNode function - tx, err := instance.DeactivateNode(auth, nodeID) - if err != nil { - return fmt.Errorf("Failed to deactivate node: %v", err) - } - - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🔄 Node Deactivation%s\n", colorGreen, colorReset) - fmt.Printf("%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🆔 Node ID:%s %s\n", colorCyan, colorReset, nodeID) - fmt.Printf("%s📝 Transaction Hash:%s %s\n", colorCyan, colorReset, tx.Hash().Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "====================================", colorReset) - - return nil -} - -// ActivateNode sets the node status to Online -func ActivateNode() error { - chainName := strings.ToLower(os.Getenv("CHAIN_NAME")) - if chainName != "peaq" && chainName != "monadtestnet" && chainName != "risetestnet" { - return fmt.Errorf("Chain not configured") - } - - // Connect to the Ethereum client - client, err := ethclient.Dial(os.Getenv("RPC_URL")) - if err != nil { - return fmt.Errorf("Failed to connect to the Ethereum client: %v", err) - } - - // Create a new instance of the contract - contractAddress := common.HexToAddress(os.Getenv("CONTRACT_ADDRESS")) - instance, err := contract.NewContract(contractAddress, client) - if err != nil { - return fmt.Errorf("Failed to instantiate contract: %v", err) - } - - // Get the node ID - nodeID, err := GeneratePeaqDID() - if err != nil { - return fmt.Errorf("Failed to generate Peaq DID: %v", err) - } - - // Create auth options for the transaction - privateKey, err := ethcrypto.HexToECDSA(os.Getenv("PRIVATE_KEY")) - if err != nil { - return fmt.Errorf("Failed to create private key: %v", err) - } - - chainID, ok := new(big.Int).SetString(os.Getenv("CHAIN_ID"), 10) - if !ok { - return fmt.Errorf("Failed to parse CHAIN_ID") - } - - auth, err := bind.NewKeyedTransactorWithChainID(privateKey, chainID) - if err != nil { - return fmt.Errorf("Failed to create transactor: %v", err) - } - - // Call updateNodeStatus function with Online status (1) - tx, err := instance.UpdateNodeStatus(auth, nodeID, 1) // 1 represents Online status - if err != nil { - return fmt.Errorf("Failed to activate node: %v", err) - } - - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🔄 Node Activation%s\n", colorGreen, colorReset) - fmt.Printf("%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🆔 Node ID:%s %s\n", colorCyan, colorReset, nodeID) - fmt.Printf("%s📝 Transaction Hash:%s %s\n", colorCyan, colorReset, tx.Hash().Hex()) - fmt.Printf("%s%s%s\n\n", colorYellow, "====================================", colorReset) - - return nil -} - diff --git a/core/cli.go b/core/cli.go deleted file mode 100644 index 3fbf53b..0000000 --- a/core/cli.go +++ /dev/null @@ -1,101 +0,0 @@ -package core - -import ( - "fmt" - "os" - - "github.com/NetSepio/erebrus/util" - "github.com/spf13/cobra" -) - -var rootCmd = &cobra.Command{ - Use: "erebrus", - Short: "Erebrus is a decentralized VPN node", - Long: `Erebrus is a decentralized VPN node that provides secure and private internet access. -Complete documentation is available at https://erebrus.io`, -} - -var versionCmd = &cobra.Command{ - Use: "version", - Short: "Print the version number of Erebrus", - Run: func(cmd *cobra.Command, args []string) { - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s📦 Erebrus Version%s\n", colorGreen, colorReset) - fmt.Printf("%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🔖 Version:%s %s\n", colorCyan, colorReset, util.Version) - fmt.Printf("%s%s%s\n\n", colorYellow, "====================================", colorReset) - }, -} - -var statusCmd = &cobra.Command{ - Use: "status", - Short: "Show the current status of the Erebrus node", - Run: func(cmd *cobra.Command, args []string) { - status, err := GetNodeStatus() - if err != nil { - fmt.Printf("\n%s%s%s\n", colorRed, err.Error(), colorReset) - os.Exit(1) - } - - // Print node status - fmt.Printf("\n%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s📊 Node Status%s\n", colorGreen, colorReset) - fmt.Printf("%s%s%s\n", colorYellow, "====================================", colorReset) - fmt.Printf("%s🆔 Node ID:%s %s\n", colorCyan, colorReset, status.ID) - fmt.Printf("%s📛 Name:%s %s\n", colorCyan, colorReset, status.Name) - fmt.Printf("%s📝 Spec:%s %s\n", colorCyan, colorReset, status.Spec) - fmt.Printf("%s⚙️ Config:%s %s\n", colorCyan, colorReset, status.Config) - fmt.Printf("%s🌐 IP Address:%s %s\n", colorCyan, colorReset, status.IPAddress) - fmt.Printf("%s🗺 Region:%s %s\n", colorCyan, colorReset, status.Region) - fmt.Printf("%s📍 Location:%s %s\n", colorCyan, colorReset, status.Location) - fmt.Printf("%s👤 Owner:%s %s\n", colorCyan, colorReset, status.Owner.Hex()) - fmt.Printf("%s🎫 Token ID:%s %v\n", colorCyan, colorReset, status.TokenID) - fmt.Printf("%s%s Status:%s %s %s\n", colorCyan, status.GetStatusEmoji(), colorReset, status.GetStatusText(), colorReset) - - if status.Checkpoint != "" { - fmt.Printf("%s📡 Latest Checkpoint:%s %s\n", colorCyan, colorReset, status.Checkpoint) - } - - fmt.Printf("%s%s%s\n\n", colorYellow, "====================================", colorReset) - }, -} - -var deactivateCmd = &cobra.Command{ - Use: "deactivate", - Short: "Deactivate the Erebrus node", - Run: func(cmd *cobra.Command, args []string) { - if err := DeactivateNode(); err != nil { - fmt.Printf("\n%s❌ Error: %s%s\n", colorRed, err.Error(), colorReset) - os.Exit(1) - } - fmt.Printf("%s✅ Node successfully deactivated%s\n", colorGreen, colorReset) - }, -} - -var activateCmd = &cobra.Command{ - Use: "activate", - Short: "Activate the Erebrus node", - Run: func(cmd *cobra.Command, args []string) { - if err := ActivateNode(); err != nil { - fmt.Printf("\n%s❌ Error: %s%s\n", colorRed, err.Error(), colorReset) - os.Exit(1) - } - fmt.Printf("%s✅ Node successfully activated%s\n", colorGreen, colorReset) - }, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func init() { - rootCmd.AddCommand(versionCmd) - rootCmd.AddCommand(statusCmd) - rootCmd.AddCommand(deactivateCmd) - rootCmd.AddCommand(activateCmd) -} - diff --git a/core/client.go b/core/client.go deleted file mode 100644 index 764bcfd..0000000 --- a/core/client.go +++ /dev/null @@ -1,256 +0,0 @@ -package core - -import ( - // "crypto/rand" - "errors" - // "fmt" - // "math/big" - "os" - "path/filepath" - "regexp" - "sort" - "strings" - - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/storage" - "github.com/NetSepio/erebrus/template" - "github.com/NetSepio/erebrus/util" - "github.com/NetSepio/erebrus/util/pkg/stats" - uuid "github.com/google/uuid" - log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" - "google.golang.org/protobuf/types/known/timestamppb" -) - -// RegisterClient client with all necessary data -func RegisterClient(client *model.Client) (*model.Client, error) { - // check if client is valid - errs := client.IsValid() - if len(errs) != 0 { - for _, err := range errs { - log.WithFields(log.Fields{ - "err": err, - }).Error("client validation error") - } - return nil, errors.New("failed to validate client") - } - - u, err := uuid.NewRandom() - client.UUID = u.String() - - presharedKey, err := wgtypes.GenerateKey() - if err != nil { - return nil, err - } - client.PresharedKey = presharedKey.String() - - reserverIps, err := GetAllReservedIps() - if err != nil { - return nil, err - } - - ips := make([]string, 0) - for _, network := range client.Address { - ip, err := util.GetAvailableIP(network, reserverIps) - if err != nil { - return nil, err - } - if util.IsIPv6(ip) { - ip = ip + "/128" - } else { - ip = ip + "/32" - } - ips = append(ips, ip) - } - client.Address = ips - client.CreatedAt = timestamppb.Now().AsTime().UnixMilli() - - client.UpdatedAt = client.CreatedAt - - err = storage.Serialize(client.UUID, client) - if err != nil { - return nil, err - } - - v, err := storage.Deserialize(client.UUID) - if err != nil { - return nil, err - } - client = v.(*model.Client) - - // data modified, dump new config - return client, UpdateServerConfigWg() -} - -// ReadClient client by id -func ReadClient(id string) (*model.Client, error) { - v, err := storage.Deserialize(id) - if err != nil { - return nil, err - } - client := v.(*model.Client) - pkey := client.PublicKey - clientStats, err := stats.GetWireGuardStatsForPeer(pkey) - if err == nil { - client.ReceiveBytes = clientStats.ReceivedBytes - client.TransmitBytes = clientStats.TransmittedBytes - } - - return client, nil -} - -// UpdateClient preserve keys -func UpdateClient(UUID string, client *model.Client) (*model.Client, error) { - v, err := storage.Deserialize(UUID) - if err != nil { - return nil, err - } - current := v.(*model.Client) - - if current.UUID != client.UUID { - return nil, errors.New("records UUID mismatch") - } - - // check if client is valid - errs := client.IsValid() - if len(errs) != 0 { - for _, err := range errs { - log.WithFields(log.Fields{ - "err": err, - }).Error("client validation error") - } - return nil, errors.New("failed to validate client") - } - - // Keep Keys - client.PublicKey = current.PublicKey - client.PresharedKey = current.PresharedKey - client.UpdatedAt = timestamppb.Now().AsTime().UnixMilli() - - err = storage.Serialize(client.UUID, client) - if err != nil { - return nil, err - } - - v, err = storage.Deserialize(UUID) - if err != nil { - return nil, err - } - client = v.(*model.Client) - - // data modified, dump new config - return client, UpdateServerConfigWg() -} - -// DeleteClient from disk -func DeleteClient(id string) error { - path := filepath.Join(os.Getenv("WG_CLIENTS_DIR"), id) - err := os.Remove(path) - if err != nil { - return err - } - - // data modified, dump new config - return UpdateServerConfigWg() -} - -// ReadClients all clients -func ReadClients() ([]*model.Client, error) { - clients := make([]*model.Client, 0) - - files, err := os.ReadDir(filepath.Join(os.Getenv("WG_CLIENTS_DIR"))) - if err != nil { - return nil, err - } - - for _, f := range files { - // clients file name is an uuid - _, err := uuid.Parse(f.Name()) - if err == nil { - c, err := storage.Deserialize(f.Name()) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - "path": f.Name(), - }).Error("failed to deserialize client") - } else { - cl := c.(*model.Client) - pkey := cl.PublicKey - clientStats, err := stats.GetWireGuardStatsForPeer(pkey) - if err == nil { - cl.ReceiveBytes = clientStats.ReceivedBytes - cl.TransmitBytes = clientStats.TransmittedBytes - } - - clients = append(clients, cl) - } - } - } - - sort.Slice(clients, func(i, j int) bool { - return clients[i].CreatedAt < (clients[j].CreatedAt) - }) - - return clients, nil -} - -func ReadClientConfig(id string) ([]byte, error) { - client, err := ReadClient(id) - if err != nil { - return nil, err - } - - server, err := ReadServer() - if err != nil { - return nil, err - } - - configDataWg, err := template.DumpClientWg(client, server) - if err != nil { - return nil, err - } - - return configDataWg, nil -} - -// LENGTH 16 -// func GeneratePeaqDID(length int) (string, string, error) { -// if length <= 0 { -// length = 55 -// } - -// const validChars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -// result := make([]byte, length) - -// for i := 0; i < length; i++ { -// randomIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(validChars)))) -// if err != nil { -// return "", "", fmt.Errorf("failed to generate random number: %v", err) -// } -// result[i] = validChars[randomIndex.Int64()] -// fmt.Println("result : ", result) -// } - -// return fmt.Sprintf("did:peaq:%s", string(result)), string(result), nil -// } - -func IsValidPeaqDID(did string) bool { - // Check if the DID starts with "did:peaq:" - if !strings.HasPrefix(did, "did:peaq:") { - return false - } - - // Extract the id-string part - idString := strings.TrimPrefix(did, "did:peaq:") - - // Check if the id-string is not empty - if len(idString) == 0 { - return false - } - - // Define the allowed characters for idchar - idcharRegex := regexp.MustCompile(`^[1-9A-HJ-NP-Za-km-z]+$`) - - // Check if the id-string contains only valid idchar characters - return idcharRegex.MatchString(idString) -} diff --git a/core/ipinfo.go b/core/ipinfo.go deleted file mode 100644 index a8ad27d..0000000 --- a/core/ipinfo.go +++ /dev/null @@ -1,51 +0,0 @@ -package core - -import ( - "encoding/json" - "fmt" - "io" - "net/http" -) - -type IPInfo struct { - IP string `json:"ip"` - City string `json:"city"` - Region string `json:"region"` - Country string `json:"country"` - Location string `json:"loc"` - Org string `json:"org"` - Postal string `json:"postal"` - Timezone string `json:"timezone"` -} - -var GlobalIPInfo IPInfo - -func GetIPInfo() { - resp, err := http.Get("https://ipinfo.io/json") - if err != nil { - fmt.Println("Error:", err) - return - } - defer resp.Body.Close() - - body, err := io.ReadAll(resp.Body) - if err != nil { - fmt.Println("Error:", err) - return - } - - err = json.Unmarshal(body, &GlobalIPInfo) - if err != nil { - fmt.Println("Error:", err) - return - } - - fmt.Printf("IP: %s\n", GlobalIPInfo.IP) - fmt.Printf("City: %s\n", GlobalIPInfo.City) - fmt.Printf("Region: %s\n", GlobalIPInfo.Region) - fmt.Printf("Country: %s\n", GlobalIPInfo.Country) - fmt.Printf("Location: %s\n", GlobalIPInfo.Location) - fmt.Printf("Organization: %s\n", GlobalIPInfo.Org) - fmt.Printf("Postal: %s\n", GlobalIPInfo.Postal) - fmt.Printf("Timezone: %s\n", GlobalIPInfo.Timezone) -} diff --git a/core/node.go b/core/node.go deleted file mode 100644 index feb1ada..0000000 --- a/core/node.go +++ /dev/null @@ -1,290 +0,0 @@ -package core - -import ( - "crypto/ecdsa" - "crypto/ed25519" - "encoding/hex" - "fmt" - "log" - "os" - "strings" - - "github.com/blocto/solana-go-sdk/pkg/hdwallet" - "github.com/blocto/solana-go-sdk/types" - "github.com/ethereum/go-ethereum/crypto" - "github.com/tyler-smith/go-bip32" - "github.com/tyler-smith/go-bip39" - "golang.org/x/crypto/sha3" -) - -// These variables will be set at build time -var ( - Version string - CodeHash string -) - -var ( - NodeName string - ChainName string - NodeType string - NodeConfig string -) -var WalletAddress string - -// Function to load the node details from the environment and save it to the global variable -func LoadNodeDetails() { - // Get the CHAIN_NAME variable from the environment - NodeName = os.Getenv("NODE_NAME") - - ChainName = os.Getenv("CHAIN_NAME") - if ChainName == "" { - log.Fatalf("CHAIN_NAME environment variable is not set") - } else { - ChainName = strings.ToLower(ChainName) - if ChainName == "solana" || ChainName == "eclipse" { - GenerateWalletAddressSolanaAndEclipse(os.Getenv("MNEMONIC")) - } else if ChainName == "ethereum" { - GenerateEthereumWalletAddress(os.Getenv("MNEMONIC")) - } else if ChainName == "sui" { - GenerateWalletAddressSui(os.Getenv("MNEMONIC")) - } else if ChainName == "aptos" { - GenerateWalletAddressAptos(os.Getenv("MNEMONIC")) - } - } - fmt.Printf("Chain Name: %s\n", ChainName) - - NodeType = os.Getenv("NODE_TYPE") - if NodeType == "" { - log.Fatalf("NODE_TYPE environment variable is not set") - } - fmt.Printf("Node Type: %s\n", NodeType) - - NodeConfig = os.Getenv("NODE_CONFIG") - if NodeConfig == "" { - log.Fatalf("NODE_CONFIG environment variable is not set") - } - fmt.Printf("Node Config: %s\n", NodeConfig) -} - -// GenerateEthereumWalletAddress generates an Ethereum wallet address from the given mnemonic -func GenerateEthereumWalletAddress(mnemonic string) (string, *ecdsa.PrivateKey, error) { - // Validate the mnemonic - if !bip39.IsMnemonicValid(mnemonic) { - log.Fatal("Invalid mnemonic") - } - - // Derive a seed from the mnemonic - seed := bip39.NewSeed(mnemonic, "") - - // Generate a master key using BIP32 - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - log.Fatal(err) - } - - // Derive a child key (using the Ethereum derivation path m/44'/60'/0'/0/0) - childKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild + 44) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(bip32.FirstHardenedChild + 60) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(bip32.FirstHardenedChild + 0) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(0) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(0) - if err != nil { - log.Fatal(err) - } - - // Generate ECDSA private key from the child key - privateKey, err := crypto.ToECDSA(childKey.Key) - if err != nil { - log.Fatal(err) - } - - // Get the public key in uncompressed format - publicKey := privateKey.Public().(*ecdsa.PublicKey) - publicKeyBytes := crypto.FromECDSAPub(publicKey) - - // log.Println("Private Key:", hex.EncodeToString(crypto.FromECDSA(privateKey))) - // log.Println("Public Key:", hex.EncodeToString(publicKeyBytes)) - - // Generate the Ethereum address - keccak := sha3.NewLegacyKeccak256() - keccak.Write(publicKeyBytes[1:]) // Skip the first byte (0x04) of the uncompressed public key - walletAddress := keccak.Sum(nil)[12:] // Take the last 20 bytes - - // Convert to checksummed address - WalletAddress = toChecksumAddress(hex.EncodeToString(walletAddress)) - // log.Println("Ethereum Wallet Address:", WalletAddress) - return WalletAddress, privateKey, nil -} - -// toChecksumAddress converts an address to checksummed format -func toChecksumAddress(address string) string { - address = strings.ToLower(address) - keccak := sha3.NewLegacyKeccak256() - keccak.Write([]byte(address)) - hash := keccak.Sum(nil) - - var checksumAddress strings.Builder - checksumAddress.WriteString("0x") - - for i, c := range address { - if c >= '0' && c <= '9' { - checksumAddress.WriteRune(c) - } else { - if hash[i/2]>>uint(4*(1-i%2))&0xF >= 8 { - checksumAddress.WriteRune(c - 'a' + 'A') - } else { - checksumAddress.WriteRune(c) - } - } - } - - return checksumAddress.String() -} - -// GenerateWalletAddressSolana generates a Solana wallet address from the given mnemonic -func GenerateWalletAddressSolanaAndEclipse(mnemonic string) { - // Validate the mnemonic - if !bip39.IsMnemonicValid(mnemonic) { - fmt.Println("Invalid mnemonic") - return - } - // mnemonic := "curtain century depth trim slogan stay case human farm ivory case merge" - seed := bip39.NewSeed(mnemonic, "") // (mnemonic, password) - path := `m/44'/501'/0'/0'` - derivedKey, _ := hdwallet.Derived(path, seed) - account, _ := types.AccountFromSeed(derivedKey.PrivateKey) - - WalletAddress = account.PublicKey.ToBase58() - - fmt.Printf("Solona OR Eclipse Wallet Address: %s\n", WalletAddress) -} - -// GenerateWalletAddressSui generates a Sui wallet address from the given mnemonic -func GenerateWalletAddressSui(mnemonic string) { - // Validate the mnemonic - if !bip39.IsMnemonicValid(mnemonic) { - log.Fatal("Invalid mnemonic") - } - log.Println("Mnemonic:", mnemonic) - - // Derive a seed from the mnemonic - seed := bip39.NewSeed(mnemonic, "") - - // Generate a master key using BIP32 - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - log.Fatal(err) - } - - // Derive a child key (using the Sui derivation path m/44'/784'/0'/0/0) - childKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild + 44) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(bip32.FirstHardenedChild + 784) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(bip32.FirstHardenedChild + 0) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(0) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(0) - if err != nil { - log.Fatal(err) - } - - // Generate ED25519 keys from the child key - privateKey := ed25519.NewKeyFromSeed(childKey.Key) - publicKey := privateKey.Public().(ed25519.PublicKey) - - log.Println("Private Key:", hex.EncodeToString(privateKey)) - log.Println("Public Key:", hex.EncodeToString(publicKey)) - - // Generate wallet address (using SHA3-256) - hash := sha3.New256() - hash.Write(publicKey) - walletAddress := hash.Sum(nil) - - WalletAddress = "0x" + hex.EncodeToString(walletAddress) - log.Println("Sui Wallet Address:", WalletAddress) -} - -// GenerateWalletAddressAptos generates an Aptos wallet address from the given mnemonic -func GenerateWalletAddressAptos(mnemonic string) { - // Validate the mnemonic - if !bip39.IsMnemonicValid(mnemonic) { - log.Fatal("Invalid mnemonic") - } - log.Println("Mnemonic:", mnemonic) - - // Derive a seed from the mnemonic - seed := bip39.NewSeed(mnemonic, "") - - // Generate a master key using BIP32 - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - log.Fatal(err) - } - - // Derive a child key (using the Aptos derivation path m/44'/637'/0'/0/0) - childKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild + 44) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(bip32.FirstHardenedChild + 637) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(bip32.FirstHardenedChild + 0) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(0) - if err != nil { - log.Fatal(err) - } - childKey, err = childKey.NewChildKey(0) - if err != nil { - log.Fatal(err) - } - - // Generate ED25519 keys from the child key - privateKey := ed25519.NewKeyFromSeed(childKey.Key) - publicKey := privateKey.Public().(ed25519.PublicKey) - - log.Println("Private Key:", hex.EncodeToString(privateKey)) - log.Println("Public Key:", hex.EncodeToString(publicKey)) - - // Generate wallet address (using SHA3-256) - hash := sha3.New256() - hash.Write(publicKey) - walletAddress := hash.Sum(nil) - - WalletAddress = "0x" + hex.EncodeToString(walletAddress) - log.Println("Aptos Wallet Address:", WalletAddress) -} - - - -func GetCodeHashAndVersion() (string, string) { - CodeHash = "4f5610aae32077a92ac570eeff5f3a404052fd94" - Version = "1.1.1" - return CodeHash, Version -} diff --git a/core/server.go b/core/server.go deleted file mode 100644 index 555ce31..0000000 --- a/core/server.go +++ /dev/null @@ -1,259 +0,0 @@ -package core - -import ( - "encoding/json" - "errors" - "io" - "net" - "net/http" - "os" - "path/filepath" - "strconv" - "time" - - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/storage" - "github.com/NetSepio/erebrus/template" - "github.com/NetSepio/erebrus/util" - log "github.com/sirupsen/logrus" - "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -// ReadServer object, create default one -func ReadServer() (*model.Server, error) { - if !util.FileExists(filepath.Join(os.Getenv("WG_CONF_DIR"), "server.json")) { - server := &model.Server{} - - key, err := wgtypes.GeneratePrivateKey() - if err != nil { - return nil, err - } - server.PrivateKey = key.String() - server.PublicKey = key.PublicKey().String() - server.Endpoint = os.Getenv("WG_ENDPOINT_HOST") - listenPort, _ := strconv.ParseInt(os.Getenv("WG_ENDPOINT_PORT"), 10, 32) - - util.CheckError("Error while reading listen port:", err) - server.ListenPort = listenPort - - server.Address = make([]string, 0) - // server.Address = append(server.Address, os.Getenv("WG_IPv6_SUBNET")) // "fd9f:6666::10:0:0:1/64" - server.Address = append(server.Address, os.Getenv("WG_IPv4_SUBNET")) // "10.0.0.1/24" - - server.DNS = make([]string, 0) - // server.DNS = append(server.DNS, "fd9f::10:0:0:2") - server.DNS = append(server.DNS, os.Getenv("WG_DNS")) // "1.1.1.1" - - server.AllowedIPs = make([]string, 0) - server.AllowedIPs = append(server.AllowedIPs, os.Getenv("WG_ALLOWED_IP_1")) // "0.0.0.0/0" - server.AllowedIPs = append(server.AllowedIPs, os.Getenv("WG_ALLOWED_IP_2")) // "::/0" - - server.PersistentKeepalive = 16 - server.Mtu = 0 - server.PreUp = os.Getenv("WG_PRE_UP") // "echo WireGuard PreUp" - server.PostUp = os.Getenv("WG_POST_UP") // "echo WireGuard PostUp" - server.PreDown = os.Getenv("WG_PRE_DOWN") // "echo WireGuard PreDown" - server.PostDown = os.Getenv("WG_POST_DOWN") // "echo WireGuard PostDown" - server.CreatedAt = int64(time.Now().Nanosecond()) - server.UpdatedAt = server.CreatedAt - - err = storage.Serialize("server.json", server) - if err != nil { - return nil, err - } - - // server.json was missing, dump wg config after creation - err = UpdateServerConfigWg() - if err != nil { - return nil, err - } - } - - c, err := storage.Deserialize("server.json") - if err != nil { - return nil, err - } - - return c.(*model.Server), nil -} - -// UpdateServer keep private values from existing one -func UpdateServer(server *model.Server) (*model.Server, error) { - current, err := storage.Deserialize("server.json") - if err != nil { - return nil, err - } - - // check if server is valid - errs := server.IsValid() - if len(errs) != 0 { - for _, err := range errs { - log.WithFields(log.Fields{ - "err": err, - }).Error("server validation error") - } - return nil, errors.New("failed to validate server") - } - - server.PrivateKey = current.(*model.Server).PrivateKey - server.PublicKey = current.(*model.Server).PublicKey - //server.PresharedKey = current.(*model.Server).PresharedKey - server.UpdatedAt = int64(time.Now().Nanosecond()) - - err = storage.Serialize("server.json", server) - if err != nil { - return nil, err - } - - v, err := storage.Deserialize("server.json") - if err != nil { - return nil, err - } - server = v.(*model.Server) - - return server, UpdateServerConfigWg() -} - -// UpdateServerConfigWg in wg format -func UpdateServerConfigWg() error { - clients, err := ReadClients() - if err != nil { - return err - } - - server, err := ReadServer() - if err != nil { - return err - } - - _, err = template.DumpServerWg(clients, server) - if err != nil { - return err - } - - return nil -} - -// GetAllReservedIps the list of all reserved IPs, client and server -func GetAllReservedIps() ([]string, error) { - clients, err := ReadClients() - if err != nil { - return nil, err - } - - server, err := ReadServer() - if err != nil { - return nil, err - } - - reserverIps := make([]string, 0) - - for _, client := range clients { - for _, cidr := range client.Address { - ip, err := util.GetIPFromCidr(cidr) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - "cidr": cidr, - }).Error("failed to ip from cidr") - } else { - reserverIps = append(reserverIps, ip) - } - } - } - - for _, cidr := range server.Address { - ip, err := util.GetIPFromCidr(cidr) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - "cidr": err, - }).Error("failed to ip from cidr") - } else { - reserverIps = append(reserverIps, ip) - } - } - - return reserverIps, nil -} - -// ReadWgConfigFile return content of wireguard config file -func ReadWgConfigFile() ([]byte, error) { - return util.ReadFile(filepath.Join(os.Getenv("WG_CONF_DIR"), os.Getenv("WG_INTERFACE_NAME"))) -} - -// Method to get the server status -func GetServerStatus() (*model.Status, error) { - var response = &model.Status{} - resp, err := http.Get("https://ipinfo.io/ip") - if err != nil { - return nil, err - } - ip, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - response.PublicIP = string(ip) - hostname, _ := os.Hostname() - response.Hostname = hostname - response.Domain = os.Getenv("DOMAIN") - response.GRPCPort = os.Getenv("GRPC_PORT") - response.Version = util.Version - response.HttpPort = os.Getenv("HTTP_PORT") - response.Region = os.Getenv("REGION") - response.VPNPort = os.Getenv("WG_ENDPOINT_PORT") - - serverStatus, err := storage.Deserialize("server.json") - - if err != nil { - log.WithFields(util.StandardFields).Fatal(err) - } else { - var server model.Server - bodybytes, _ := json.Marshal(serverStatus) - json.Unmarshal(bodybytes, &server) - response.PublicKey = server.PublicKey - response.PersistentKeepalive = server.PersistentKeepalive - response.DNS = server.DNS - } - var privateip string - addrs, _ := net.InterfaceAddrs() - - for _, address := range addrs { - // check the address type and if it is not a loopback the display it - if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { - if ipnet.IP.To4() != nil { - privateip = ipnet.IP.String() + "," - } - } - } - response.PrivateIP = privateip - - return response, nil -} - -// success response message -func MakeSucessResponse(status int64, message string, server *model.Server, client *model.Client, clients []*model.Client) *model.Response { - return &model.Response{ - Status: status, - Message: message, - Server: server, - Client: client, - Clients: clients, - Success: true, - Error: "", - } -} - -// error response message -func MakeErrorResponse(status int64, err string, server *model.Server, client *model.Client, clients []*model.Client) *model.Response { - return &model.Response{ - Status: status, - Message: "", - Server: server, - Client: client, - Clients: clients, - Success: false, - Error: err, - } - -} diff --git a/core/service.core.go b/core/service.core.go deleted file mode 100644 index addb455..0000000 --- a/core/service.core.go +++ /dev/null @@ -1,101 +0,0 @@ -package core - -import ( - "fmt" - "math/rand" - "net" - "os" - "path/filepath" - "strings" - "time" - - "github.com/NetSepio/erebrus/api/v1/service/util" -) - -// var AppConfDir = "./conf" -var CaddyJSON = "caddy.json" - -// WG_CONF_DIR -var CaddyConfDir = os.Getenv("WG_CONF_DIR") -var CaddyFile = os.Getenv("CADDY_INTERFACE_NAME") - -// Init initializes json file for caddy -func Init() { - //caddy.json path - wd, err := os.Getwd() - if err != nil { - fmt.Println("\n🚨 Error:", err) - } else { - fmt.Println("\n✅ Current Working Directory:", wd) - } - fmt.Println("\n📂 Current Path:", wd) - - path := filepath.Join(os.Getenv("SERVICE_CONF_DIR"), CaddyJSON) - //check if exists - if !util.FileExists(path) { - err := util.CreateJSONFile(path) - if err != nil { - util.CheckError("caddy.json error: ", err) - } - } -} - -// Writefile appends data to file -func Writefile(path string, bytes []byte) (err error) { - file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) - if err != nil { - util.LogError("File Open error: ", err) - return err - } - - defer file.Close() - - _, err = file.WriteString(string(bytes)) - if err != nil { - util.LogError("File write error: ", err) - return err - } - - return nil -} - -// ScanPort checks avilability of port -func ScanPort(port int) (string, error) { - ip := os.Getenv("SERVER") - timer := 500 * time.Millisecond - - target := fmt.Sprintf("%s:%d", ip, port) - conn, err := net.DialTimeout("tcp", target, timer) - - if err != nil { - if strings.Contains(err.Error(), "too many open files") { - time.Sleep(timer) - ScanPort(port) - } else { - return "inactive", nil - } - return "", err - } - - conn.Close() - return "active", nil -} - -// GetPort returns available port based on random generation -func GetPort(max, min int) (int, error) { - port := rand.Intn(max-min) + min - - status, err := ScanPort(port) - if err != nil { - util.LogError("Scan Port error: ", err) - return -1, err - } - - if status == "inactive" { - return port, nil - } else if status == "active" { - GetPort(max, min) - } - - return -1, nil -} diff --git a/doc.go b/doc.go deleted file mode 100644 index c10788a..0000000 --- a/doc.go +++ /dev/null @@ -1,27 +0,0 @@ -// Erebrus -// -// Erebrus is an open source VPN solution from The NetSepio, that helps to deploy your own VPN solution in -// minutes.The vision of Erebrus is to deliver Cyber security to everyone . -// -// Features of Erebrus were, Easy Client and Server management, Supports REST and gRPC, Email VPN configuration to clients easily. -// -// This documentation guides you, How to use Erebrus endpoints and It's Request and Response briefly. -// -// Schemes: http, https -// Host: localhost -// BasePath: /api/v1.0 -// Version: 1.0.0 -// License: GPL-3.0 https://opensource.org/licenses/GPL-3.0 -// Contact: Sambath Kumar -// -// Consumes: -// - application/json -// - application/x-protobuf -// -// Produces: -// - application/json -// - application/x-protobuf -// - application/config -// -// swagger:meta -package main diff --git a/docker-compose.yml b/docker-compose.yml index 694e652..85f972f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,35 +1,78 @@ -version: "3.9" services: erebrus: build: . + image: erebrus:v2 container_name: erebrus - privileged: true - environment: + restart: unless-stopped + # NET_ADMIN + module access let the node bring up the WireGuard interface. + cap_add: + - NET_ADMIN + - SYS_MODULE + sysctls: + - net.ipv4.ip_forward=1 + - net.ipv6.conf.all.disable_ipv6=0 + environment: + # Docker injects env from --env-file; LOAD_CONFIG_FILE skips godotenv inside the binary. LOAD_CONFIG_FILE: "TRUE" - RUNTYPE: "RUNTYPE" - SERVER: "0.0.0.0" - PORT: "9080" - HTTP_PORT: "8080" - GATEWAY_DOMAIN: "https://gateway.erebrus.io" - WG_CONF_DIR: "/etc/wireguard" - WG_KEYS_DIR: "/etc/wireguard/keys" - WG_CLIENTS_DIR: "/etc/wireguard/clients" - WG_INTERFACE_NAME: "wg0.conf" - WG_ENDPOINT_HOST: "region.erebrus.io" - WG_ENDPOINT_PORT: "51820" - WG_IPv4_SUBNET: "fd9f:0000::10:0:0:1/64" - WG_IPv6_SUBNET: "10.0.0.1/24" - WG_DNS: "1.1.1.1" - WG_ALLOWED_IP_1: "0.0.0.0/0" - WG_ALLOWED_IP_2: "::/0" - WG_PRE_UP: "echo WireGuard PreUp" - WG_POST_UP: "iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE" - WG_PRE_DOWN: "echo WireGuard PreDown" - WG_POST_DOWN: "iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE" + RUNTYPE: "${RUNTYPE:-release}" + EREBRUS_ACCESS: "${EREBRUS_ACCESS:-private}" + EREBRUS_MODE: "${EREBRUS_MODE:-container}" + EREBRUS_NETWORK_PROFILE: "${EREBRUS_NETWORK_PROFILE:-bridge}" + SERVER: "${SERVER:-0.0.0.0}" + HTTP_PORT: "${HTTP_PORT:-9080}" + NODE_NAME: "${NODE_NAME:-erebrus-node}" + REGION: "${REGION:-unknown}" + MNEMONIC: "${MNEMONIC}" + WG_ENDPOINT_HOST: "${WG_ENDPOINT_HOST}" + NODE_API_TOKEN: "${NODE_API_TOKEN:-}" + GATEWAY_URL: "${GATEWAY_URL:-}" + GATEWAY_AUTO_REGISTER: "${GATEWAY_AUTO_REGISTER:-true}" + WALLET_CHAIN: "${WALLET_CHAIN:-sol}" + AUTH_EULA: "${AUTH_EULA:-I accept the Erebrus Terms of Service https://erebrus.network/terms.}" + API_PUBLIC_URL: "${API_PUBLIC_URL:-}" + NODE_ID: "${NODE_ID:-}" + NODE_TOKEN: "${NODE_TOKEN:-}" + GATEWAY_PEER_MULTIADDR: "${GATEWAY_PEER_MULTIADDR:-}" + # WireGuard + WG_CONF_DIR: "${WG_CONF_DIR:-/etc/wireguard}" + WG_INTERFACE_NAME: "${WG_INTERFACE_NAME:-wg0}" + WG_ENDPOINT_PORT: "${WG_ENDPOINT_PORT:-51820}" + WG_IPv4_SUBNET: "${WG_IPv4_SUBNET:-10.0.0.1/16}" + WG_DNS: "${WG_DNS:-1.1.1.1}" + WG_POST_UP: "${WG_POST_UP:-iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE}" + WG_POST_DOWN: "${WG_POST_DOWN:-iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE}" + # Stealth carriers (sing-box) + ENABLE_STEALTH: "${ENABLE_STEALTH:-true}" + STEALTH_TCP_PORT: "${STEALTH_TCP_PORT:-8443}" + STEALTH_UDP_PORT: "${STEALTH_UDP_PORT:-4443}" + REALITY_SERVER_NAMES: "${REALITY_SERVER_NAMES:-www.microsoft.com}" + REALITY_HANDSHAKE_SERVER: "${REALITY_HANDSHAKE_SERVER:-}" + HYSTERIA2_OBFS_PASSWORD: "${HYSTERIA2_OBFS_PASSWORD:-}" + ENABLE_TUIC: "${ENABLE_TUIC:-false}" + # Public edge / app hosting + ENABLE_APP_HOSTING: "${ENABLE_APP_HOSTING:-false}" + APP_WILDCARD_DOMAIN: "${APP_WILDCARD_DOMAIN:-}" + PUBLIC_DOMAIN: "${PUBLIC_DOMAIN:-}" + WILDCARD_DOMAIN: "${WILDCARD_DOMAIN:-}" + PUBLIC_GATEWAY_ENABLED: "${PUBLIC_GATEWAY_ENABLED:-false}" + # Private DNS + PRIVATE_DNS_ENABLED: "${PRIVATE_DNS_ENABLED:-false}" + PRIVATE_DNS_DOMAIN: "${PRIVATE_DNS_DOMAIN:-ere}" + PRIVATE_DNS_ADDR: "${PRIVATE_DNS_ADDR:-}" + UPSTREAM_DNS: "${UPSTREAM_DNS:-1.1.1.1}" + DNS_QUERY_LOGS: "${DNS_QUERY_LOGS:-false}" + # State + registrar + STATE_DIR: "${STATE_DIR:-/var/lib/erebrus}" + CHAIN_REGISTRATION: "${CHAIN_REGISTRATION:-off}" ports: - - '9080:9080/tcp' - - '8080:8080/tcp' + - "${HTTP_PORT:-9080}:${HTTP_PORT:-9080}/tcp" + - "${WG_ENDPOINT_PORT:-51820}:${WG_ENDPOINT_PORT:-51820}/udp" + - "${STEALTH_TCP_PORT:-8443}:${STEALTH_TCP_PORT:-8443}/tcp" + - "${STEALTH_UDP_PORT:-4443}:${STEALTH_UDP_PORT:-4443}/udp" volumes: - - /etc/erebrus/wireguard:/etc/wireguard - sysctls: - - net.ipv6.conf.all.disable_ipv6=0 \ No newline at end of file + - erebrus-state:/var/lib/erebrus + - erebrus-wireguard:/etc/wireguard + +volumes: + erebrus-state: + erebrus-wireguard: \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 0000000..5909614 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,53 @@ +# Node architecture (v2) + +The node is a single Go binary (`cmd/erebrus`) plus a SQLite state file. It has no +external service dependencies at runtime. + +## Packages + +| Package | Responsibility | +|---------|----------------| +| `internal/config` | Environment-derived configuration + helpers. | +| `internal/store` | SQLite persistence: peers, node settings/secrets, race-free IP allocation. | +| `internal/wg` | WireGuard server: keypair, interface/peer config rendering, live sync via `wgctrl`. | +| `internal/stealth` | Embedded sing-box: VLESS+REALITY and Hysteria2 carriers + client profile/URI generation. | +| `internal/p2p` | libp2p identity + DID derived from the mnemonic; DHT advertise. | +| `internal/registrar` | On-chain registration interface (no-op in v2.0; Solana later). | +| `internal/node` | Core service tying store + wg + stealth together; builds credential bundles. | +| `internal/api` | Gin REST surface under `/api/v2` + Prometheus `/metrics`. | +| `internal/telemetry` | Structured logging + metrics. | + +## Stealth topology ("WireGuard is the endpoint") + +When WireGuard's UDP is throttled or DPI-blocked, the **same** WireGuard tunnel is +wrapped in a carrier that looks like ordinary internet traffic: + +``` +client ──WireGuard(UDP)──────────────────────────────▶ :51820 (fast path) + +client ──WG inside VLESS+REALITY(TCP:8443)──┐ + ├─▶ sing-box ─▶ 127.0.0.1:51820 ─▶ WireGuard +client ──WG inside Hysteria2(QUIC:4443)─────┘ +``` + +Key properties: + +- **One shared carrier secret per node.** REALITY keypair/short-id, the VLESS UUID, + the Hysteria2 password and a self-signed Hysteria2 cert are generated once and + persisted in SQLite `node_settings`. +- **Not an open proxy.** The carriers' `direct` outbound is pinned to + `127.0.0.1:`, so a carrier connection can only ever reach the local + WireGuard listener — never arbitrary internet hosts. +- **Auth stays in WireGuard.** The shared secret only gets you to the WG door; you + still need a registered WireGuard key to get a tunnel. No per-peer sing-box user + management, so the sing-box instance never restarts on peer churn. + +The credential bundle a client receives therefore contains the WireGuard config +**and** the carrier share URIs + a complete sing-box client profile that nests +WireGuard inside the chosen carrier. + +## Build tag + +The sing-box REALITY *server* is gated behind `with_reality_server`. The binary +**must** be built with it (`make build`, the Dockerfile, and CI all set it) or the +REALITY inbound fails to start at runtime. diff --git a/docs/CLOUD.md b/docs/CLOUD.md new file mode 100644 index 0000000..a93c53b --- /dev/null +++ b/docs/CLOUD.md @@ -0,0 +1,49 @@ +# Cloud deployment checklist + +Operators do **not** hand-edit `.env` files. Use the installer or `erebrus init`, then verify with `erebrus status`. + +## Access modes + +| Mode | Who can connect | +|------|-----------------| +| **private** | You and your devices only | +| **shared** | You plus wallet addresses you allow on the gateway | +| **public** | Entitled network users (host earnings via gateway — future) | + +Deployment profile (`EREBRUS_NETWORK_PROFILE`) is separate: `bridge` for Docker, `host-network` for bare metal. + +## Ports (docker / private) + +| Port | Proto | +|------|-------| +| 9080 | tcp | +| 51820 | udp | +| 8443 | tcp | +| 4443 | udp | + +Public bare-metal nodes use stealth on **443/tcp** and **443/udp**. + +## Install (docker) + +```bash +curl -fsSL https://raw.githubusercontent.com/NetSepio/erebrus/v2/install.sh | \ + WG_ENDPOINT_HOST="" \ + bash -s -- --mode docker --yes +``` + +## Install (bare metal) + +```bash +sudo erebrus init --access private --public-address --yes +# configure systemd EnvironmentFile=/etc/erebrus/erebrus.env +sudo systemctl enable --now erebrus +``` + +## Verify + +```bash +erebrus status +curl -s http://127.0.0.1:9080/api/v2/status | jq '.readiness' +``` + +Back up your **node identity** (12-word phrase) from installer/init output — it is never shown again in status. \ No newline at end of file diff --git a/docs/Erebrus.postman_collection.json b/docs/Erebrus.postman_collection.json deleted file mode 100644 index 0a1f29e..0000000 --- a/docs/Erebrus.postman_collection.json +++ /dev/null @@ -1,956 +0,0 @@ -{ - "info": { - "_postman_id": "287fffc4-bf3c-4930-a257-484d317d1107", - "name": "Erebrus", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "item": [ - { - "name": "Client", - "item": [ - { - "name": "Read Client", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb" - ] - } - }, - "response": [ - { - "name": "Read Client", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb" - ] - } - }, - "_postman_previewlanguage": null, - "header": null, - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"client details\",\n \"client\": {\n \"UUID\": \"681e13e3-c07f-4a4b-8949-57cc17f6f8fb\",\n \"Name\": \"jon snow\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"1mKT+5X/1THA0mgl8ecQdN+cULVpIDIc1odTpplfU2M=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.3/32\"\n ],\n \"PrivateKey\": \"0CZgS3p5hJxeuMipQ0U5SMDqLW/8jKZjkxRhaXST+W4=\",\n \"PublicKey\": \"d/cU8479v1Y7R5q47xxMlhCQgJSOBz32XUnTwl+/LCc=\",\n \"CreatedBy\": \"jonsnow@gmail.com\",\n \"Created\": 1642831493472,\n \"Updated\": 1642831493472\n }\n}" - } - ] - }, - { - "name": "Get Client Config", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb/config", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "config" - ] - } - }, - "response": [ - { - "name": "Get client config", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb/config", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "config" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "raw", - "header": [ - { - "key": "Content-Disposition", - "value": "attachment; filename=681e13e3-c07f-4a4b-8949-57cc17f6f8fb.conf" - }, - { - "key": "Content-Type", - "value": "application/config" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:11:39 GMT" - }, - { - "key": "Content-Length", - "value": "325" - } - ], - "cookie": [], - "body": "[Interface]\nAddress = 10.0.0.3/32\nPrivateKey = 0CZgS3p5hJxeuMipQ0U5SMDqLW/8jKZjkxRhaXST+W4=\nDNS = 1.1.1.1\n\n[Peer]\nPublicKey = T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks=\nPresharedKey = 1mKT+5X/1THA0mgl8ecQdN+cULVpIDIc1odTpplfU2M=\nAllowedIPs = 0.0.0.0/0, ::/0\nEndpoint = region..network:51820\nPersistentKeepalive = 16\n" - } - ] - }, - { - "name": "Email Client Configuration", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb/email", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "email" - ] - } - }, - "response": [ - { - "name": "Email Client configuration", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb/email", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "email" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:32:53 GMT" - }, - { - "key": "Content-Length", - "value": "69" - } - ], - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"client configuration emailed\"\n}" - } - ] - }, - { - "name": "Read all Clients", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client" - ] - } - }, - "response": [ - { - "name": "Read all Clients", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:35:00 GMT" - }, - { - "key": "Content-Length", - "value": "983" - } - ], - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"clients details\",\n \"clients\": [\n {\n \"UUID\": \"6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab\",\n \"Name\": \"Sambath-MAC1\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"sachinmugu@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.2/32\"\n ],\n \"PrivateKey\": \"KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg=\",\n \"PublicKey\": \"YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4=\",\n \"CreatedBy\": \"sachinmugu@gmail.com\",\n \"Created\": 1642409076544,\n \"Updated\": 1642409076544\n },\n {\n \"UUID\": \"681e13e3-c07f-4a4b-8949-57cc17f6f8fb\",\n \"Name\": \"jon snow\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"1mKT+5X/1THA0mgl8ecQdN+cULVpIDIc1odTpplfU2M=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.3/32\"\n ],\n \"PrivateKey\": \"0CZgS3p5hJxeuMipQ0U5SMDqLW/8jKZjkxRhaXST+W4=\",\n \"PublicKey\": \"d/cU8479v1Y7R5q47xxMlhCQgJSOBz32XUnTwl+/LCc=\",\n \"CreatedBy\": \"jonsnow@gmail.com\",\n \"Created\": 1642831493472,\n \"Updated\": 1642831493472\n }\n ]\n}" - } - ] - }, - { - "name": "Update Client", - "request": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"UUID\":\"681e13e3-c07f-4a4b-8949-57cc17f6f8fb\",\n \"Name\": \"jon snow updated\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"TwOplBZS7q8tXSqt6Q1YyGzjXwaCXAzwX7QqkJQ5Jmg=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.0/24\"\n ],\n \"createdBy\": \"jonsnow@gmail.com\"\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb" - ] - } - }, - "response": [ - { - "name": "Update Client", - "originalRequest": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"UUID\":\"681e13e3-c07f-4a4b-8949-57cc17f6f8fb\",\n \"Name\": \"jon snow updated\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"TwOplBZS7q8tXSqt6Q1YyGzjXwaCXAzwX7QqkJQ5Jmg=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.0/24\"\n ],\n \"createdBy\": \"jonsnow@gmail.com\"\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:37:22 GMT" - }, - { - "key": "Content-Length", - "value": "501" - } - ], - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"client updated\",\n \"client\": {\n \"UUID\": \"681e13e3-c07f-4a4b-8949-57cc17f6f8fb\",\n \"Name\": \"jon snow updated\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"TwOplBZS7q8tXSqt6Q1YyGzjXwaCXAzwX7QqkJQ5Jmg=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.0/24\"\n ],\n \"PrivateKey\": \"0CZgS3p5hJxeuMipQ0U5SMDqLW/8jKZjkxRhaXST+W4=\",\n \"PublicKey\": \"d/cU8479v1Y7R5q47xxMlhCQgJSOBz32XUnTwl+/LCc=\",\n \"CreatedBy\": \"jonsnow@gmail.com\",\n \"Updated\": 1642833442898\n }\n}" - } - ] - }, - { - "name": "Delete Client", - "request": { - "method": "DELETE", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb" - ] - } - }, - "response": [ - { - "name": "Delete Client", - "originalRequest": { - "method": "DELETE", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/client/681e13e3-c07f-4a4b-8949-57cc17f6f8fb", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client", - "681e13e3-c07f-4a4b-8949-57cc17f6f8fb" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:38:14 GMT" - }, - { - "key": "Content-Length", - "value": "55" - } - ], - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"client deleted\"\n}" - } - ] - }, - { - "name": "Create Client", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \n \"Name\": \"jon snow\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"TwOplBZS7q8tXSqt6Q1YyGzjXwaCXAzwX7QqkJQ5Jmg=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.0/24\"\n ],\n \"createdBy\": \"jonsnow@gmail.com\"\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:9080/api/v1.0/client", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client" - ] - } - }, - "response": [ - { - "name": "Create Client", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \n \"Name\": \"jon snow\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"TwOplBZS7q8tXSqt6Q1YyGzjXwaCXAzwX7QqkJQ5Jmg=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.0/24\"\n ],\n \"createdBy\": \"jonsnow@gmail.com\"\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:9080/api/v1.0/client", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "client" - ] - } - }, - "_postman_previewlanguage": null, - "header": null, - "cookie": [], - "body": "{\n \"status\": 201,\n \"sucess\": true,\n \"message\": \"client created\",\n \"client\": {\n \"UUID\": \"681e13e3-c07f-4a4b-8949-57cc17f6f8fb\",\n \"Name\": \"jon snow\",\n \"Tags\": [\n \"laptop\",\n \"PC\"\n ],\n \"Email\": \"johnsnow@gmail.com\",\n \"Enable\": true,\n \"PresharedKey\": \"1mKT+5X/1THA0mgl8ecQdN+cULVpIDIc1odTpplfU2M=\",\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"Address\": [\n \"10.0.0.3/32\"\n ],\n \"PrivateKey\": \"0CZgS3p5hJxeuMipQ0U5SMDqLW/8jKZjkxRhaXST+W4=\",\n \"PublicKey\": \"d/cU8479v1Y7R5q47xxMlhCQgJSOBz32XUnTwl+/LCc=\",\n \"CreatedBy\": \"jonsnow@gmail.com\",\n \"Created\": 1642831493472,\n \"Updated\": 1642831493472\n }\n}" - } - ] - } - ] - }, - { - "name": "Server", - "item": [ - { - "name": "Get Server Info", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/server", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server" - ] - } - }, - "response": [ - { - "name": "Get Server Info", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/server", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:42:53 GMT" - }, - { - "key": "Content-Length", - "value": "725" - } - ], - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"server details\",\n \"server\": {\n \"Address\": [\n \"10.0.0.1/24\"\n ],\n \"ListenPort\": 51820,\n \"PrivateKey\": \"UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE=\",\n \"PublicKey\": \"T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks=\",\n \"Endpoint\": \"region..network\",\n \"PersistentKeepalive\": 16,\n \"DNS\": [\n \"1.1.1.1\"\n ],\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"PreUp\": \"echo WireGuard PreUp\",\n \"PostUp\": \"iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\",\n \"PreDown\": \"echo WireGuard PreDown\",\n \"PostDown\": \"iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\",\n \"Created\": 26103870,\n \"Updated\": 26103870\n }\n}" - } - ] - }, - { - "name": "Get Server Configuration", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/server/config", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server", - "config" - ] - } - }, - "response": [ - { - "name": "Get Server Configuration", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/server/config", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server", - "config" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "raw", - "header": [ - { - "key": "Content-Disposition", - "value": "attachment; filename=wg0.conf" - }, - { - "key": "Content-Type", - "value": "application/config" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:43:09 GMT" - }, - { - "key": "Content-Length", - "value": "756" - } - ], - "cookie": [], - "body": "# Updated: 26103870 / Created: 26103870\n[Interface]\nAddress = 10.0.0.1/24\nListenPort = 51820\nPrivateKey = UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE=\n\nPreUp = echo WireGuard PreUp\nPostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\nPreDown = echo WireGuard PreDown\nPostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\n# Sambath-MAC1 / sachinmugu@gmail.com / Updated: 1642409076544 / Created: 1642409076544\n# friendly_name = Sambath-MAC1\n[Peer]\nPublicKey = YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4=\nPresharedKey = twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc=\nAllowedIPs = 10.0.0.2/32\n" - } - ] - }, - { - "name": "Get Server Status", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/server/status", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server", - "status" - ] - } - }, - "response": [ - { - "name": "Get Server Status", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:9080/api/v1.0/server/status", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:50:46 GMT" - }, - { - "key": "Content-Length", - "value": "198" - } - ], - "cookie": [], - "body": "{\n \"Version\": \"1.0\",\n \"Hostname\": \"sambath-WS\",\n \"Domain\": \"eu01..network\",\n \"PublicIP\": \"118.246.197.100\",\n \"gRPCPort\": \"9090\",\n \"PrivateIP\": \"172.20.0.1,\",\n \"HttpPort\": \"9080\",\n \"Region\": \"eu01\",\n \"VPNPort\": \"51820\"\n}" - } - ] - }, - { - "name": "Update Server", - "request": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"Address\": [\n \"10.0.0.1/24\"\n ],\n \"ListenPort\": 51822,\n \"PrivateKey\": \"oKgGti0Kv3tacXGc+X6evdhVm0ILjmlVXHVPdzuCqEQ=\",\n \"PublicKey\": \"0iG5xe5wp7lGQSYr9qxL8lQ0Ce3CHVMSvW12ziY5TUA=\",\n \"Endpoint\": \"region..network\",\n \"PersistentKeepalive\": 16,\n \"DNS\": [\n \"1.1.1.1\"\n ],\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"PreUp\": \"echo WireGuard PreUp\",\n \"PostUp\": \"iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\",\n \"PreDown\": \"echo WireGuard PreDown\",\n \"PostDown\": \"iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\",\n \"Created\": 704810668,\n \"Updated\": 122649583\n }", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:9080/api/v1.0/server", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server" - ] - } - }, - "response": [ - { - "name": "Update Server", - "originalRequest": { - "method": "PATCH", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"Address\": [\n \"10.0.0.1/24\"\n ],\n \"ListenPort\": 51822,\n \"PrivateKey\": \"oKgGti0Kv3tacXGc+X6evdhVm0ILjmlVXHVPdzuCqEQ=\",\n \"PublicKey\": \"0iG5xe5wp7lGQSYr9qxL8lQ0Ce3CHVMSvW12ziY5TUA=\",\n \"Endpoint\": \"region..network\",\n \"PersistentKeepalive\": 16,\n \"DNS\": [\n \"1.1.1.1\"\n ],\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"PreUp\": \"echo WireGuard PreUp\",\n \"PostUp\": \"iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\",\n \"PreDown\": \"echo WireGuard PreDown\",\n \"PostDown\": \"iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\",\n \"Created\": 704810668,\n \"Updated\": 122649583\n }", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:9080/api/v1.0/server", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "9080", - "path": [ - "api", - "v1.0", - "server" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json; charset=utf-8" - }, - { - "key": "Strict-Transport-Security", - "value": "max-age=5184000; includeSubDomains" - }, - { - "key": "X-Content-Type-Options", - "value": "nosniff" - }, - { - "key": "X-Dns-Prefetch-Control", - "value": "off" - }, - { - "key": "X-Download-Options", - "value": "noopen" - }, - { - "key": "X-Frame-Options", - "value": "DENY" - }, - { - "key": "X-Xss-Protection", - "value": "1; mode=block" - }, - { - "key": "Date", - "value": "Sat, 22 Jan 2022 06:53:23 GMT" - }, - { - "key": "Content-Length", - "value": "727" - } - ], - "cookie": [], - "body": "{\n \"status\": 200,\n \"sucess\": true,\n \"message\": \"server updated\",\n \"server\": {\n \"Address\": [\n \"10.0.0.1/24\"\n ],\n \"ListenPort\": 51822,\n \"PrivateKey\": \"UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE=\",\n \"PublicKey\": \"T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks=\",\n \"Endpoint\": \"region..network\",\n \"PersistentKeepalive\": 16,\n \"DNS\": [\n \"1.1.1.1\"\n ],\n \"AllowedIPs\": [\n \"0.0.0.0/0\",\n \"::/0\"\n ],\n \"PreUp\": \"echo WireGuard PreUp\",\n \"PostUp\": \"iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE\",\n \"PreDown\": \"echo WireGuard PreDown\",\n \"PostDown\": \"iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE\",\n \"Created\": 704810668,\n \"Updated\": 582391761\n }\n}" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/docs/INTERNAL-ENV.md b/docs/INTERNAL-ENV.md new file mode 100644 index 0000000..f134a85 --- /dev/null +++ b/docs/INTERNAL-ENV.md @@ -0,0 +1,25 @@ +# Internal environment reference (developers / installers) + +End operators should use `erebrus status`, not this document. + +## File locations + +| Path | Used by | +|------|---------| +| `/opt/erebrus/.env` | Docker install (`docker compose --env-file`) | +| `/etc/erebrus/erebrus.env` | Bare metal (`systemd EnvironmentFile`) | + +## Required bootstrap + +| Variable | Purpose | +|----------|---------| +| `MNEMONIC` | Node identity (12-word phrase) | +| `WG_ENDPOINT_HOST` | Public address clients dial | + +## Release-only + +| Variable | Purpose | +|----------|---------| +| `NODE_API_TOKEN` | Bearer for peer API | + +See [`.env.example`](../.env.example) for the full internal template. \ No newline at end of file diff --git a/docs/NODE.md b/docs/NODE.md new file mode 100644 index 0000000..b235303 --- /dev/null +++ b/docs/NODE.md @@ -0,0 +1,109 @@ +# Running an Erebrus node + +A node is a Linux host (x86_64/arm64) with a **static, internet-routable public IP**, +real bandwidth, and open ports. It serves WireGuard plus two DPI-resistant stealth +carriers, and exposes a small REST API the gateway and operators use. + +## Quick install + +```bash +curl -fsSL https://erebrus.io/install.sh | bash +``` + +The installer runs preflight checks (static IP / NAT, up+down bandwidth, inbound +port reachability), then asks for an install **mode**: + +| Mode | What you get | Use it when | +|------|--------------|-------------| +| **docker** | WireGuard + stealth carriers in a container (compose). | You just want to run a VPN node. Recommended. | +| **host** | Bare-metal via `systemd`. Adds **App-Hosting** (expose a VPN-connected app to the internet). | You want app/port exposure and can set a wildcard DNS record. | + +Non-interactive: + +```bash +curl -fsSL https://erebrus.io/install.sh | \ + MNEMONIC="..." WG_ENDPOINT_HOST="vpn.example.com" bash -s -- --mode docker --yes +``` + +## Ports + +| Port | Proto | Purpose | +|------|-------|---------| +| 9080 | tcp | REST API (`/api/v2`) + `/metrics` | +| 51820 | udp | WireGuard fast path | +| 8443 | tcp | VLESS + REALITY stealth carrier | +| 4443 | udp | Hysteria2 stealth carrier | +| 80, 443 | tcp | Caddy ingress — **host mode + App-Hosting only** | + +Open all of these in your cloud firewall / security group. UDP can't be probed +remotely, so double-check 51820 and 4443. + +## Configuration + +Full reference: [`.env.example`](../.env.example). The only required values are +`MNEMONIC` (the node identity — back it up) and `WG_ENDPOINT_HOST`. The installer +generates a `MNEMONIC` and `NODE_API_TOKEN` for you if unset. + +- **docker** config: `${INSTALL_DIR}/.env` (default `/opt/erebrus/.env`) +- **host** config: `/etc/erebrus/erebrus.env` + +## Managing the node + +**docker** +```bash +cd /opt/erebrus +docker compose ps +docker compose logs -f +docker compose restart +docker compose down +``` + +**host** +```bash +systemctl status erebrus +journalctl -u erebrus -f +systemctl restart erebrus +``` + +## Verify + +```bash +# Carriers advertised +curl -s http://127.0.0.1:9080/api/v2/status | jq '.protocols, .capabilities.stealth' +# → ["wireguard","vless-reality","hysteria2"] and true + +# Provision a peer and inspect the unified credential bundle +TOKEN= +PUB=$(wg genkey | wg pubkey) +curl -s -X PUT http://127.0.0.1:9080/api/v2/peers/test \ + -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \ + -d "{\"name\":\"test\",\"wg_public_key\":\"$PUB\"}" | jq +``` + +The bundle returns the WireGuard config plus `vless://` / `hysteria2://` share URIs +and a ready sing-box client profile (WireGuard tunnelled through the carrier). + +## App-Hosting (host mode) + +Create a wildcard DNS record pointing at the node: + +``` +*.apps.example.com A +``` + +The gateway then mints per-app CNAMEs under it and routes public traffic through the +node to the chosen VPN client's port. (Route automation lands with the gateway; the +installer prepares the host — Caddy + the wildcard domain.) + +## Troubleshooting + +- **`reality server is not included in this build`** — the binary was built without + `-tags with_reality_server`. Use `make build` / the provided Dockerfile. +- **WireGuard interface won't come up** — the host needs the `wireguard` kernel + module and `NET_ADMIN`. In containers, run with `--cap-add=NET_ADMIN` (compose + already does). Load it on the host with `modprobe wireguard` if missing. +- **Peer create returns 500 in local dev** — expected when there's no live WG + device; the credentials endpoint still renders bundles. On a real host with + `NET_ADMIN` it succeeds. +- **Stealth ports not reachable** — confirm the cloud firewall allows 8443/tcp and + 4443/udp; `ss -tlnp | grep 8443` and `ss -ulnp | grep 4443` show them locally. diff --git a/docs/SECURITY-AUDIT.md b/docs/SECURITY-AUDIT.md new file mode 100644 index 0000000..da4b31f --- /dev/null +++ b/docs/SECURITY-AUDIT.md @@ -0,0 +1,193 @@ +# Erebrus Node — Security & Data-Capture Audit (v2.0) + +_Scope: the `erebrus` node (this repo) and its trust boundaries with clients, +the gateway, and the host. Last reviewed 2026-06-14 against the v2 codebase. +This is an internal pre-release review, not a third-party pentest — an external +audit is still recommended before a large public launch._ + +--- + +## 1. Architecture & trust boundaries + +``` + client ── WireGuard / VLESS+REALITY / Hysteria2 ──▶ NODE ──▶ internet + │ + HTTPS + WebSocket (control) ▼ + GATEWAY +``` + +| Boundary | Carries | Trust | +|---|---|---| +| client ↔ node (data plane) | the user's traffic | end-to-end via WireGuard keys; node is the exit | +| gateway → node (`/api/v2/peers`) | provisioning + credential bundles | bearer `NODE_API_TOKEN` | +| node → gateway (WS) | identity, heartbeat, per-client byte deltas | node PASETO | +| node ↔ host | SQLite DB, `config.env`, WG kernel iface | host root | + +The node is the **exit point**: it necessarily sees the source (client) and can +observe destination IPs of forwarded packets at the network layer. The design +goal is to **store and transmit as little of that as possible**. + +--- + +## 2. Data-capture inventory (privacy posture) + +### Stored on the node (SQLite, `STATE_DIR/erebrus.db`) +- **Per peer:** id, name, wallet address, WG public key, assigned tunnel IP, + WG preshared key, generated proxy UUID/password, timestamps, expiry. +- **Node settings:** WG server private/public key, REALITY private/public key + + short-id, VLESS UUID, Hysteria2 password, Hysteria2 self-signed cert + key. + +### NOT stored (by design) +- No traffic content, destination IPs/domains, connection logs, or DNS queries. +- No per-flow records. The node keeps **no activity log**. + +### Transmitted to the gateway (authenticated WS) +- Identity (`peer_id`, `did`, `ip_hash`), spec (cpu/mem/region/**raw IP**), + capabilities, endpoints (+ public keys). +- Heartbeat: cpu/mem %, **cumulative interface rx/tx**, self-speedtest. +- `usage_report` (60s): **per-client rx/tx byte deltas + last handshake**, keyed + by the gateway-issued client UUID. + +> **Metadata note (important):** the gateway can join client UUID → wallet, so +> *per-wallet bandwidth and online-time metadata exists at the gateway*, even +> though the node logs nothing. This is inherent to metered DePIN billing. +> It is **traffic metadata, not content or destinations**. Document this in the +> user-facing privacy policy. If stronger privacy is wanted later, aggregate +> usage before it leaves the node. + +### Logs (slog JSON → stderr) +- Node identity and operational warnings/errors only. Tokens, client keys, and + request bodies are **not** logged. Internal error strings are no longer echoed + to API clients (see F4). + +### Third parties +- **DNS** defaults to `1.1.1.1` (Cloudflare) → see F5. + +--- + +## 3. Findings + +Severity: 🔴 high · 🟠 medium · 🟡 low · 🟢 informational. Status reflects this repo. + +| # | Severity | Finding | Status | +|---|---|---|---| +| F1 | 🔴 | Node API auth failed *open* when `NODE_API_TOKEN` unset | ✅ Fixed | +| F2 | 🟠 | Non-constant-time token comparison (timing oracle) | ✅ Fixed | +| F3 | 🔴 | Node API + credential bundles served over plaintext HTTP | ⚠️ Operator | +| F4 | 🟡 | Internal error strings echoed to API clients | ✅ Fixed | +| F5 | 🟠 | DNS sent to a third party (Cloudflare) by default | ⚠️ Operator / roadmap | +| F6 | 🟠 | Key material at rest unencrypted in SQLite / `config.env` | ⚠️ Partially mitigated | +| F7 | 🟡 | `/metrics` and `/api/v2/stats` are public (info disclosure) | ⚠️ By design / operator | +| F8 | 🟠 | No application-level rate limiting (brute-force / DoS) | ⚠️ Operator | +| F9 | 🟢 | Open-relay via stealth carriers | ✅ Mitigated by design | +| F10 | 🟡 | Shared node-wide carrier secret; partial rotation only | ⚠️ Roadmap | +| F11 | 🟡 | Hysteria2 self-signed cert + client `insecure` | 🟢 Accepted | + +### F1 — Auth fail-open (FIXED) +`bearerAuth` previously allowed all requests when `NODE_API_TOKEN` was empty. +Now it **fails closed in release mode** (503 until configured) and only allows +open access under `RUNTYPE=debug`. The installer always generates a strong token. + +### F2 — Timing-safe token compare (FIXED) +Token comparison now uses `crypto/subtle.ConstantTimeCompare`. + +### F3 — Plaintext node API (OPERATOR — top priority) +`:9080` is plain HTTP. The `NODE_API_TOKEN` and full credential bundles +(client WG config, share URIs) traverse it in cleartext; an on-path attacker +between gateway and node could steal the token (→ full peer control) or +intercept bundles. **Mitigations:** +- Terminate TLS in front of the node (Caddy/nginx/Cloudflare) **or** +- restrict `:9080` to the gateway only (cloud firewall / private network / a + management WireGuard link), never exposing it to the public internet. +The installer's preflight opens `:9080`; production deployments should put it +behind TLS or a firewall. _Roadmap: gateway↔node mTLS / PASETO-signed calls._ + +### F4 — Error leakage (FIXED) +Peer handlers returned raw `err.Error()` (potentially driver/SQL text). They now +log detail server-side and return generic messages. + +### F5 — DNS leakage (OPERATOR / ROADMAP) +With `WG_DNS=1.1.1.1`, clients' DNS resolves at Cloudflare. Operators wanting +no third party should run a local resolver and set `WG_DNS` to it; the +node-internal DNS (Phase 5, `miekg/dns`) will make this the default for +app-hosting nodes. + +### F6 — Secrets at rest (PARTIALLY MITIGATED) +The DB holds the WG server private key, REALITY key, Hy2 cert key and per-peer +PSKs; `config.env` holds the mnemonic. Host compromise ⇒ node impersonation. +WireGuard's forward secrecy protects *past* sessions (ephemeral session keys), +but a stolen static key lets an attacker impersonate the node going forward. +**Mitigations in repo:** `STATE_DIR` is `0700`; the DB (+WAL/SHM) is now forced +to `0600`; the installer writes `config.env` `0600`. **Operator:** use full-disk +encryption; restrict host access; rotate the mnemonic ⇒ new node identity. + +### F7 — Public metrics/stats (BY DESIGN) +`/metrics` (Prometheus) and `/api/v2/stats` (the dashboard's coarse aggregates: +connected count, cumulative bytes, uptime) are unauthenticated. They expose **no +per-client data**. Operators who consider even aggregates sensitive should +firewall `:9080` to trusted scrapers, or front it with auth. + +### F8 — No rate limiting (OPERATOR) +There is no app-level throttle on the API or the data-plane listeners. Risks: +token brute-force (bounded by the 401 fail-closed + strong token), address-pool +exhaustion via mass provisioning (requires the node token, held only by the +gateway), and UDP floods on WG/Hy2. **Mitigations:** provisioning is +gateway-gated by entitlement; put a rate-limiting reverse proxy and/or +fail2ban in front; rely on the cloud provider's UDP flood protection. + +### F9 — Open-relay prevention (MITIGATED — call-out) +A classic risk for proxy carriers is becoming an open internet relay. The +stealth carriers' `direct` outbound is **pinned to `127.0.0.1:`**, so a +carrier connection can only ever reach the local WireGuard listener — never an +arbitrary host. The shared carrier secret gets a client *to the WG door only*; +it still needs a registered WG keypair to get a tunnel. Auth stays in WireGuard. + +### F10 — Carrier secret rotation (ROADMAP) +The VLESS UUID / Hy2 password are node-wide and shared with every client. A leak +lets a holder reach the WG door (not the VPN itself). `rotate_reality` rotates +REALITY short-ids, but not the VLESS UUID / Hy2 password — add a full +carrier-secret rotation command. + +### F11 — Hysteria2 self-signed TLS (ACCEPTED) +Hy2 uses a self-signed cert; clients connect with `insecure`. An active MITM on +`:4443` sees only the **inner WireGuard-encrypted payload** (the client pins the +node's WG public key from the bundle), so confidentiality holds. REALITY (the +TCP carrier) resists MITM by design. + +### Verified safe +- **SQL injection:** all store queries are parameterized (`$n` / `?`). ✅ +- **Command injection:** `wg-quick`/iptables run only operator-configured + `WG_POST_UP/DOWN`; no user-controlled input reaches a shell. ✅ +- **IP allocation races:** peer IP allocation is a single immediate SQLite + transaction — race-free under concurrency. ✅ +- **Peer name injection:** names are used only as labels (URL-escaped in share + URIs), never written into the WG conf. ✅ + +--- + +## 4. Operator hardening checklist + +- [ ] Set a strong `NODE_API_TOKEN` (the installer generates 32 bytes) — never blank. +- [ ] Do **not** expose `:9080` to the public internet: TLS-terminate it, or + firewall it to the gateway only. +- [ ] Open only what's needed: `51820/udp`, `8443/tcp`, `4443/udp` publicly. +- [ ] Enable full-disk encryption; keep `config.env` and `STATE_DIR` `0600/0700`. +- [ ] Run on a dedicated host/VM; minimise other services. +- [ ] Consider a local DNS resolver (avoid the Cloudflare default). +- [ ] Keep the OS + `wireguard` module patched; rebuild the image for sing-box CVEs. +- [ ] Back up the mnemonic securely; rotating it changes the node identity. + +## 5. Release-readiness sweep (other observations) + +- ✅ Build is reproducible and requires `-tags with_reality_server` (Makefile, + Dockerfile, CI all set it). +- ✅ No secrets committed; `.gitleaks.toml` + CI gitleaks job in place. +- ✅ `/healthz` added for orchestration probes. +- 🟡 The node does not self-measure speedtest in v2 (removed with v1 `util`); + the gateway receives speed via heartbeat only if the node reports it — wire a + periodic speedtest in Phase 2 if the directory UX needs it. +- 🟡 No graceful WireGuard teardown on SIGTERM beyond process exit; `wg-quick + down` relies on `PostDown`. Acceptable; document for operators. +- 🟡 Container runs as root for `NET_ADMIN`; consider dropping to a capability + set (`cap_add: NET_ADMIN` only) — the compose already uses `cap_add` rather + than `privileged`. diff --git a/docs/deploy.md b/docs/deploy.md deleted file mode 100644 index 8d7e70e..0000000 --- a/docs/deploy.md +++ /dev/null @@ -1,67 +0,0 @@ -# Erebrus Deployment Docs - -## Build from Source -``` -go build -ldflags "-X main.version=1.0.0 -X main.codeHash=$(git rev-parse HEAD)" -o erebrus -``` - -## Install and Deploy using binary - -1. Make sure all setup were done -2. Download the suitable binary for your operating system from [here](https://github.com/NetSepio/erebrus/releases/) -3. create a .env file in same directory and define the environment for erebrus . you can use template from [.sample-env](https://github.com/NetSepio/erebrus/blob/main/.sample-env) -4. Run - -## Install and Deploy using Docker - -1. Make sure all setup were done -2. Pull the ererbus docker image -``` -docker pull network/erebrus:latest -``` -3. Run the Image -``` -docker run -d -p 9080:9080/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" \ --e LOAD_CONFIG_FILE="FALSE" \ --e RUNTYPE='debug' \ --e SERVER='0.0.0.0' \ --e GRPC_PORT='9080' \ --e WG_CONF_DIR='/etc/wireguard' \ --e WG_KEYS_DIR='/etc/wireguard/keys' \ --e WG_INTERFACE_NAME='wg0.conf' \ --e WG_ENDPOINT_HOST='your endpoint' \ --e WG_ENDPOINT_PORT='51820' \ --e WG_IPv4_SUBNET='10.0.0.1/24' \ --e WG_IPv6_SUBNET='fd9f:0000::10:0:0:1/64' \ --e WG_DNS='1.1.1.1' \ --e WG_ALLOWED_IP_1='0.0.0.0/0' \ --e WG_ALLOWED_IP_2='::/0' \ --e WG_PRE_UP='echo WireGuard PreUp' \ --e WG_POST_UP='iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE' \ --e WG_PRE_DOWN='echo WireGuard PreDown' \ --e WG_POST_DOWN='iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE' \ ---restart unless-stopped \ ---name erebrus-region \ -erebrus -``` - -or -``` -docker run -d -p 9080:9080/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" --restart unless-stopped -v /home/ubuntu/erebrus/wireguard/:/etc/wireguard/ --name erebrus --env-file .env ghcr.io/netsepio/erebrus:main -``` -4. Use the following commands -``` -docker exec -it erebrus bash -``` - -``` -sudo netstat -pna | grep 51820 -``` - -``` -sudo lsof -i -P -n | grep 51820 -``` - -``` -docker rm -f $(docker ps -aq) -``` \ No newline at end of file diff --git a/docs/docker.sh b/docs/docker.sh deleted file mode 100644 index 98fe989..0000000 --- a/docs/docker.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -sudo apt update -sudo apt install -y apt-transport-https ca-certificates curl software-properties-common - -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - -sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" -sudo apt update -sudo apt install -y docker-ce -docker --version -sudo groupadd docker -sudo usermod -aG docker $USER -newgrp docker \ No newline at end of file diff --git a/docs/docs.md b/docs/docs.md deleted file mode 100644 index 28fc38b..0000000 --- a/docs/docs.md +++ /dev/null @@ -1,1613 +0,0 @@ - - - -# Erebrus -Erebrus is an open source VPN solution from The NetSepio, that helps to deploy your own VPN solution in -minutes.The vision of Erebrus is to deliver Cyber security to everyone . - -Features of Erebrus were, Easy Client and Server management, Supports REST and gRPC, Email VPN configuration to clients easily. - -This documentation guides you, How to use Erebrus endpoints and It's Request and Response briefly. - - -## Informations - -### Version - -1.0.0 - -### License - -[GPL-3.0](https://opensource.org/licenses/GPL-3.0) - -### Contact - -Sambath Kumar sachinmugu@gmail.com - -## Content negotiation - -### URI Schemes - * http - * https - -### Consumes - * application/json - * application/x-protobuf - -### Produces - * application/config - * application/octet-stream - * application/json - * application/x-protobuf - -## All endpoints - -### client - -| Method | URI | Name | Summary | -|---------|---------|--------|---------| -| GET | /api/v1.0/client/{id}/config | [config client](#config-client) | Get client configuration | -| POST | /api/v1.0/client | [create client](#create-client) | Create client | -| DELETE | /api/v1.0/client/{id} | [delete client](#delete-client) | Delete client | -| GET | /api/v1.0/client/{id}/email | [email client](#email-client) | Email client Configuration | -| GET | /api/v1.0/client/{id} | [read client](#read-client) | Read client | -| GET | /api/v1.0/client | [read clients](#read-clients) | Read All Clients | -| PATCH | /api/v1.0/client/{id} | [update client](#update-client) | Update client | - - - -### serverops - -| Method | URI | Name | Summary | -|---------|---------|--------|---------| -| GET | /api/v1.0/server/config | [config server](#config-server) | | -| GET | /api/v1.0/server | [read server](#read-server) | Read Server | -| GET | /api/v1.0/server/status | [status server](#status-server) | Get Server status | -| PATCH | /api/v1.0/server | [update server](#update-server) | Update Server | - - - -## Paths - -### Get client configuration (*configClient*) - -``` -GET /api/v1.0/client/{id}/config -``` - -Return client configuration file in byte format based on the given uuid. - -#### Produces - * application/json - * application/octet-stream - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| id | `path` | string | `string` | | ✓ | | The Identifier of the Client | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#config-client-200) | OK | | | [schema](#config-client-200-schema) | -| [400](#config-client-400) | Bad Request | | | [schema](#config-client-400-schema) | -| [401](#config-client-401) | Unauthorized | | | [schema](#config-client-401-schema) | -| [500](#config-client-500) | Internal Server Error | | | [schema](#config-client-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[ConfigClientOKBody](#config-client-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[ConfigClientBadRequestBody](#config-client-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[ConfigClientUnauthorizedBody](#config-client-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[ConfigClientInternalServerErrorBody](#config-client-internal-server-error-body) - -###### Inlined models - -** ConfigClientBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ConfigClientInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ConfigClientOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Data | string| `string` | | | | `File Download` | - - - -** ConfigClientUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### config server (*configServer*) - -``` -GET /api/v1.0/server/config -``` - -Get Server Configuration -Retrieves the server configuration details. - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#config-server-200) | OK | | | [schema](#config-server-200-schema) | -| [400](#config-server-400) | Bad Request | | | [schema](#config-server-400-schema) | -| [401](#config-server-401) | Unauthorized | | | [schema](#config-server-401-schema) | -| [500](#config-server-500) | Internal Server Error | | | [schema](#config-server-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[ConfigServerOKBody](#config-server-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[ConfigServerBadRequestBody](#config-server-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[ConfigServerUnauthorizedBody](#config-server-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[ConfigServerInternalServerErrorBody](#config-server-internal-server-error-body) - -###### Inlined models - -** ConfigServerBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ConfigServerInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ConfigServerOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Data | string| `string` | | | | `File Download` | - - - -** ConfigServerUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Create client (*createClient*) - -``` -POST /api/v1.0/client -``` - -Create client based on the given client model. - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| client | `body` | [ClientReq](#client-req) | `models.ClientReq` | | | | Requestbody used for create and update client operations. | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [201](#create-client-201) | Created | | | [schema](#create-client-201-schema) | -| [400](#create-client-400) | Bad Request | | | [schema](#create-client-400-schema) | -| [401](#create-client-401) | Unauthorized | | | [schema](#create-client-401-schema) | -| [500](#create-client-500) | Internal Server Error | | | [schema](#create-client-500-schema) | - -#### Responses - - -##### 201 -Status: Created - -###### Schema - - - -[CreateClientCreatedBody](#create-client-created-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[CreateClientBadRequestBody](#create-client-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[CreateClientUnauthorizedBody](#create-client-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[CreateClientInternalServerErrorBody](#create-client-internal-server-error-body) - -###### Inlined models - -** CreateClientBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** CreateClientCreatedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `201` | -| Sucess | boolean| `bool` | | | | `true` | -| client | [Client](#client)| `models.Client` | | | | | - - - -** CreateClientInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** CreateClientUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Delete client (*deleteClient*) - -``` -DELETE /api/v1.0/client/{id} -``` - -Delete client based on the given uuid. - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| id | `path` | string | `string` | | ✓ | | The Identifier of the Client | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#delete-client-200) | OK | | | [schema](#delete-client-200-schema) | -| [400](#delete-client-400) | Bad Request | | | [schema](#delete-client-400-schema) | -| [401](#delete-client-401) | Unauthorized | | | [schema](#delete-client-401-schema) | -| [500](#delete-client-500) | Internal Server Error | | | [schema](#delete-client-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[DeleteClientOKBody](#delete-client-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[DeleteClientBadRequestBody](#delete-client-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[DeleteClientUnauthorizedBody](#delete-client-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[DeleteClientInternalServerErrorBody](#delete-client-internal-server-error-body) - -###### Inlined models - -** DeleteClientBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** DeleteClientInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** DeleteClientOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `200` | -| Sucess | boolean| `bool` | | | | `true` | - - - -** DeleteClientUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Email client Configuration (*emailClient*) - -``` -GET /api/v1.0/client/{id}/email -``` - -Email the configuration file of the client to the email associated with client. - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| id | `path` | string | `string` | | ✓ | | The Identifier of the Client | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#email-client-200) | OK | | | [schema](#email-client-200-schema) | -| [400](#email-client-400) | Bad Request | | | [schema](#email-client-400-schema) | -| [401](#email-client-401) | Unauthorized | | | [schema](#email-client-401-schema) | -| [500](#email-client-500) | Internal Server Error | | | [schema](#email-client-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[EmailClientOKBody](#email-client-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[EmailClientBadRequestBody](#email-client-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[EmailClientUnauthorizedBody](#email-client-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[EmailClientInternalServerErrorBody](#email-client-internal-server-error-body) - -###### Inlined models - -** EmailClientBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** EmailClientInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** EmailClientOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `200` | -| Sucess | boolean| `bool` | | | | `true` | - - - -** EmailClientUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Read client (*readClient*) - -``` -GET /api/v1.0/client/{id} -``` - -Return client based on the given uuid. - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| id | `path` | string | `string` | | ✓ | | The Identifier of the Client | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#read-client-200) | OK | | | [schema](#read-client-200-schema) | -| [400](#read-client-400) | Bad Request | | | [schema](#read-client-400-schema) | -| [401](#read-client-401) | Unauthorized | | | [schema](#read-client-401-schema) | -| [500](#read-client-500) | Internal Server Error | | | [schema](#read-client-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[ReadClientOKBody](#read-client-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[ReadClientBadRequestBody](#read-client-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[ReadClientUnauthorizedBody](#read-client-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[ReadClientInternalServerErrorBody](#read-client-internal-server-error-body) - -###### Inlined models - -** ReadClientBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ReadClientInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ReadClientOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `201` | -| Sucess | boolean| `bool` | | | | `true` | -| client | [Client](#client)| `models.Client` | | | | | - - - -** ReadClientUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Read All Clients (*readClients*) - -``` -GET /api/v1.0/client -``` - -Get all clients in the server. - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#read-clients-200) | OK | | | [schema](#read-clients-200-schema) | -| [400](#read-clients-400) | Bad Request | | | [schema](#read-clients-400-schema) | -| [401](#read-clients-401) | Unauthorized | | | [schema](#read-clients-401-schema) | -| [500](#read-clients-500) | Internal Server Error | | | [schema](#read-clients-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[ReadClientsOKBody](#read-clients-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[ReadClientsBadRequestBody](#read-clients-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[ReadClientsUnauthorizedBody](#read-clients-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[ReadClientsInternalServerErrorBody](#read-clients-internal-server-error-body) - -###### Inlined models - -** ReadClientsBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ReadClientsInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ReadClientsOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Body | [][Client](#client)| `[]*models.Client` | | | | | -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `201` | -| Sucess | boolean| `bool` | | | | `true` | - - - -** ReadClientsUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Read Server (*readServer*) - -``` -GET /api/v1.0/server -``` - -Retrieves the server details. - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#read-server-200) | OK | | | [schema](#read-server-200-schema) | -| [400](#read-server-400) | Bad Request | | | [schema](#read-server-400-schema) | -| [401](#read-server-401) | Unauthorized | | | [schema](#read-server-401-schema) | -| [500](#read-server-500) | Internal Server Error | | | [schema](#read-server-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[ReadServerOKBody](#read-server-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[ReadServerBadRequestBody](#read-server-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[ReadServerUnauthorizedBody](#read-server-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[ReadServerInternalServerErrorBody](#read-server-internal-server-error-body) - -###### Inlined models - -** ReadServerBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ReadServerInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** ReadServerOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `201` | -| Sucess | boolean| `bool` | | | | `true` | -| server | [Server](#server)| `models.Server` | | | | | - - - -** ReadServerUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Get Server status (*statusServer*) - -``` -GET /api/v1.0/server/status -``` - -Retrieves the server status details. - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#status-server-200) | OK | | | [schema](#status-server-200-schema) | -| [400](#status-server-400) | Bad Request | | | [schema](#status-server-400-schema) | -| [401](#status-server-401) | Unauthorized | | | [schema](#status-server-401-schema) | -| [500](#status-server-500) | Internal Server Error | | | [schema](#status-server-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[Status](#status) - -##### 400 -Status: Bad Request - -###### Schema - - - -[StatusServerBadRequestBody](#status-server-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[StatusServerUnauthorizedBody](#status-server-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[StatusServerInternalServerErrorBody](#status-server-internal-server-error-body) - -###### Inlined models - -** StatusServerBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** StatusServerInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** StatusServerUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Update client (*updateClient*) - -``` -PATCH /api/v1.0/client/{id} -``` - -Update client based on the given uuid and client model. - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| id | `path` | string | `string` | | ✓ | | The Identifier of the Client | -| client | `body` | [ClientUpdateReq](#client-update-req) | `models.ClientUpdateReq` | | | | Requestbody used for create and update client operations. | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#update-client-200) | OK | | | [schema](#update-client-200-schema) | -| [400](#update-client-400) | Bad Request | | | [schema](#update-client-400-schema) | -| [401](#update-client-401) | Unauthorized | | | [schema](#update-client-401-schema) | -| [500](#update-client-500) | Internal Server Error | | | [schema](#update-client-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[UpdateClientOKBody](#update-client-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[UpdateClientBadRequestBody](#update-client-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[UpdateClientUnauthorizedBody](#update-client-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[UpdateClientInternalServerErrorBody](#update-client-internal-server-error-body) - -###### Inlined models - -** UpdateClientBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** UpdateClientInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** UpdateClientOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `201` | -| Sucess | boolean| `bool` | | | | `true` | -| client | [Client](#client)| `models.Client` | | | | | - - - -** UpdateClientUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -### Update Server (*updateServer*) - -``` -PATCH /api/v1.0/server -``` - -Update the server with given details. - -#### Parameters - -| Name | Source | Type | Go type | Separator | Required | Default | Description | -|------|--------|------|---------|-----------| :------: |---------|-------------| -| server | `body` | [Server](#server) | `models.Server` | | | | Requestbody used for update server operations. | - -#### All responses -| Code | Status | Description | Has headers | Schema | -|------|--------|-------------|:-----------:|--------| -| [200](#update-server-200) | OK | | | [schema](#update-server-200-schema) | -| [400](#update-server-400) | Bad Request | | | [schema](#update-server-400-schema) | -| [401](#update-server-401) | Unauthorized | | | [schema](#update-server-401-schema) | -| [500](#update-server-500) | Internal Server Error | | | [schema](#update-server-500-schema) | - -#### Responses - - -##### 200 -Status: OK - -###### Schema - - - -[UpdateServerOKBody](#update-server-o-k-body) - -##### 400 -Status: Bad Request - -###### Schema - - - -[UpdateServerBadRequestBody](#update-server-bad-request-body) - -##### 401 -Status: Unauthorized - -###### Schema - - - -[UpdateServerUnauthorizedBody](#update-server-unauthorized-body) - -##### 500 -Status: Internal Server Error - -###### Schema - - - -[UpdateServerInternalServerErrorBody](#update-server-internal-server-error-body) - -###### Inlined models - -** UpdateServerBadRequestBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `400` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** UpdateServerInternalServerErrorBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `500` | -| Sucess | boolean| `bool` | | | | `false` | - - - -** UpdateServerOKBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Message | string| `string` | | | | `sucess message` | -| Status | int64 (formatted integer)| `int64` | | | | `201` | -| Sucess | boolean| `bool` | | | | `true` | -| server | [Server](#server)| `models.Server` | | | | | - - - -** UpdateServerUnauthorizedBody** - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Error | string| `string` | | | | `error message` | -| Status | int64 (formatted integer)| `int64` | | | | `401` | -| Sucess | boolean| `bool` | | | | `false` | - - - -## Models - -### Client - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Address | []string| `[]string` | | | Address range client must will assigned | `["10.0.0.2/32"]` | -| AllowedIPs | []string| `[]string` | | | IP addresses allowed to connect | `["0.0.0.0/0","::/0"]` | -| Created | int64 (formatted integer)| `int64` | | | Time the client is created | `1642409076544` | -| CreatedBy | string| `string` | | | Denoting person creates the client | `jonsnow@mail.com` | -| Email | string| `string` | | | Email that the client device belongs | `jonsnow@mail.com` | -| Enable | boolean| `bool` | | | Status signal for client | `true` | -| IgnorePersistentKeepalive | boolean| `bool` | | | | `true` | -| Name | string| `string` | | | Name of the client | `jon snow` | -| PresharedKey | string| `string` | | | Preshared key for the client | `twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc=` | -| PrivateKey | string| `string` | | | Private key for the client | `KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg=` | -| PublicKey | string| `string` | | | Public key for the client | `YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4=` | -| Tags | []string| `[]string` | | | Tags for client device | `["laptop","PC"]` | -| UUID | string| `string` | | | Client identifier | `6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab` | -| Updated | int64 (formatted integer)| `int64` | | | Time the client is last updated | `1642409076544` | -| UpdatedBy | string| `string` | | | Denoting person updates the client | `jonsnow@mail.com` | - - - -### ClientReq - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Address | []string| `[]string` | ✓ | | Address range client must will assigned | `["10.0.0.0/24"]` | -| AllowedIPs | []string| `[]string` | ✓ | | IP addresses allowed to connect | `["0.0.0.0/0","::/0"]` | -| CreatedBy | string| `string` | ✓ | | Denoting person creates the client | `jonsnow@mail.com` | -| Email | string| `string` | ✓ | | Email that the client device belongs | `jonsnow@mail.com` | -| Enable | boolean| `bool` | ✓ | | Status signal for client | `true` | -| Name | string| `string` | ✓ | | | `jon snow` | -| Tags | []string| `[]string` | ✓ | | Tags for client device | `["laptop","PC"]` | -| UpdatedBy | string| `string` | ✓ | | Denoting person updates the client | `jonsnow@mail.com` | - - - -### ClientUpdateReq - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Address | []string| `[]string` | ✓ | | IP addresses allowed to connect | `["10.0.0.2/32"]` | -| AllowedIPs | []string| `[]string` | ✓ | | IP addresses allowed to connect | `["0.0.0.0/0","::/0"]` | -| Created | int64 (formatted integer)| `int64` | | | Time the client is created | `1642409076544` | -| CreatedBy | string| `string` | | | Denoting person creates the client | `jonsnow@mail.com` | -| Email | string| `string` | ✓ | | Email that the client device belongs | `jonsnow@mail.com` | -| Enable | boolean| `bool` | ✓ | | Status signal for client | `true` | -| IgnorePersistentKeepalive | boolean| `bool` | | | | `true` | -| Name | string| `string` | ✓ | | Name of the client | `jon snow` | -| PresharedKey | string| `string` | | | Preshared key for the client | `twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc=` | -| PrivateKey | string| `string` | | | Private key for the client | `KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg=` | -| PublicKey | string| `string` | | | Public key for the client | `YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4=` | -| Tags | []string| `[]string` | ✓ | | Tags for client device | `["laptop","PC"]` | -| UUID | string| `string` | ✓ | | Client identifier | `6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab` | -| Updated | int64 (formatted integer)| `int64` | | | Time the client is last updated | `1642409076544` | -| UpdatedBy | string| `string` | ✓ | | Denoting person updates the client | `jonsnow@mail.com` | - - - -### Server - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Address | []string| `[]string` | | | Server address | `["10.0.0.1/24"]` | -| AllowedIPs | []string| `[]string` | | | IP addresses allowed to connect | `["0.0.0.0/0","::/0"]` | -| Created | int64 (formatted integer)| `int64` | | | Time when server is created | `26103870` | -| DNS | []string| `[]string` | | | DNS of the VPN server | `["1.1.1.1"]` | -| Endpoint | string| `string` | | | Endpoint of the server | `region.example.com` | -| ListenPort | int64 (formatted integer)| `int64` | | | Port the server listens | `51280` | -| Mtu | int64 (formatted integer)| `int64` | | | | | -| PersistentKeepalive | int64 (formatted integer)| `int64` | | | Persistent keep alive for server | `16` | -| PostDown | string| `string` | | | Post down command | `iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE` | -| PostUp | string| `string` | | | Post up command | `iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE` | -| PreDown | string| `string` | | | Pre down command | `echo WireGuard PreDown` | -| PreUp | string| `string` | | | Pre up command | `echo WireGuard PreUp` | -| PrivateKey | string| `string` | | | Private key for the server | `UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE=` | -| PublicKey | string| `string` | | | Public key for the server | `T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks=` | -| Updated | int64 (formatted integer)| `int64` | | | Time when server is created | `26103870` | -| UpdatedBy | string| `string` | | | Updater email address | `admin@mail.com` | - - - -### Status - - - - - - -**Properties** - -| Name | Type | Go type | Required | Default | Description | Example | -|------|------|---------|:--------:| ------- |-------------|---------| -| Domain | string| `string` | | | Domain which server is running | `vpn.example.com` | -| GRPCPort | string| `string` | | | Port which gRPC service is running | `5000` | -| Hostname | string| `string` | | | Server Hostname | `ubuntu` | -| HttpPort | string| `string` | | | Port which HTTP service is running | `4000` | -| PrivateIP | string| `string` | | | Private IP of server host | `10.0.1.5` | -| PublicIP | string| `string` | | | Server's public IP | `14.10.35.65` | -| Region | string| `string` | | | Region where server running | `India/Banglore` | -| VPNPort | string| `string` | | | VPN port | `5128` | -| Version | string| `string` | | | Server version | `1.0` | - - diff --git a/docs/node-api.openapi.yaml b/docs/node-api.openapi.yaml new file mode 100644 index 0000000..f7bc294 --- /dev/null +++ b/docs/node-api.openapi.yaml @@ -0,0 +1,189 @@ +openapi: 3.0.3 +info: + title: Erebrus Node API v2 + version: 2.0.0 + description: | + REST API served by every Erebrus VPN node. The only intended caller is the + Erebrus gateway (and node-local tooling); end users never talk to a node + directly. All /api/v2 routes require a gateway-issued, node-scoped PASETO + bearer token except /api/v2/status and /metrics. + + FROZEN (v2.0): additive changes only. See ../../../erebrus-gateway/docs/v2/ws-protocol.md + for the WebSocket control plane. +servers: + - url: http://{host}:9080 + variables: + host: + default: localhost +security: + - paseto: [] +paths: + /api/v2/status: + get: + summary: Public node status (unauthenticated) + security: [] + responses: + "200": + content: + application/json: + schema: + type: object + properties: + version: { type: string, example: "2.0.0" } + region: { type: string, example: "SG" } + status: { type: string, enum: [online, draining] } + peer_id: { type: string } + did: { type: string, example: "did:erebrus:12D3KooW..." } + capabilities: + $ref: "#/components/schemas/Capabilities" + protocols: + type: array + items: { type: string, enum: [wireguard, vless_reality, hysteria2] } + description: Node identity and advertised protocols. No secrets, no peer data. + /api/v2/stats: + get: + summary: Coarse public stats for the local dashboard (unauthenticated) + security: [] + responses: + "200": + content: + application/json: + schema: + type: object + properties: + status: { type: string, enum: [online, draining] } + version: { type: string } + region: { type: string } + protocols: { type: array, items: { type: string } } + total_peers: { type: integer, description: Provisioned peers in the store } + connected_peers: { type: integer, description: WireGuard handshake in the last 3 minutes } + rx_bytes: { type: integer, format: int64 } + tx_bytes: { type: integer, format: int64 } + uptime_sec: { type: integer, format: int64 } + description: Aggregates only — never per-client rows. + /healthz: + get: + summary: Liveness probe (unauthenticated) + security: [] + responses: + "200": { description: '{ "status": "ok" }' } + /metrics: + get: + summary: Prometheus metrics (unauthenticated, bind/firewall to taste) + security: [] + responses: + "200": { description: Prometheus text exposition format } + /api/v2/peers: + get: + summary: List provisioned peers (ids and metadata only, no credentials) + responses: + "200": + description: Peer list + content: + application/json: + schema: + type: array + items: { $ref: "#/components/schemas/PeerInfo" } + /api/v2/peers/{id}: + parameters: + - { name: id, in: path, required: true, schema: { type: string, format: uuid }, description: Gateway-issued VPN client UUID } + put: + summary: Create or update a peer (idempotent upsert) + description: | + Provisions the peer across ALL protocols atomically: allocates a + WireGuard IP, generates VLESS UUID and Hysteria2 password, applies the + WireGuard peer via wgctrl, and rebuilds sing-box inbounds. Repeating + the call with the same id and public key returns the same bundle + (gateway retries are safe). Returns 409 while the node is draining. + requestBody: + required: true + content: + application/json: + schema: + type: object + required: [name, wg_public_key] + properties: + name: { type: string, maxLength: 64 } + wallet: { type: string, description: Owner wallet address (informational) } + wg_public_key: { type: string, description: Client-generated Curve25519 public key, base64 } + wg_preshared_key: { type: string, description: Optional client-generated PSK, base64 } + expires_at: { type: integer, format: int64, description: Unix seconds; 0 = no expiry } + responses: + "200": { description: Peer upserted, content: { application/json: { schema: { $ref: "#/components/schemas/CredentialBundle" } } } } + "400": { description: Invalid key or body } + "409": { description: Node is draining or subnet exhausted } + delete: + summary: Remove a peer from all protocols + responses: + "204": { description: Peer removed (idempotent — also returned if absent) } + /api/v2/peers/{id}/credentials: + parameters: + - { name: id, in: path, required: true, schema: { type: string, format: uuid } } + get: + summary: Re-fetch the credential bundle for an existing peer + description: | + Same response as PUT. Used by the gateway's authenticated config + re-fetch (replaces the v1 Walrus blob flow). The WireGuard client_conf + contains a placeholder for the private key, which only the end client + ever held. + responses: + "200": { description: Bundle, content: { application/json: { schema: { $ref: "#/components/schemas/CredentialBundle" } } } } + "404": { description: Unknown peer } + /api/v2/apps: + get: + summary: List hosted apps (Phase 5; 404 unless ENABLE_APP_HOSTING) + responses: + "200": { description: App list — schema frozen in Phase 5 addendum } +components: + securitySchemes: + paseto: + type: http + scheme: bearer + description: Node-scoped PASETO issued by the gateway at node registration. + schemas: + Capabilities: + type: object + properties: + app_hosting: { type: boolean } + wildcard_domain: { type: string, example: "*.node-sg-1.erebrus.network" } + PeerInfo: + type: object + properties: + id: { type: string, format: uuid } + name: { type: string } + wg_allowed_ip: { type: string, example: "10.0.0.7/32" } + enabled: { type: boolean } + created_at: { type: integer, format: int64 } + expires_at: { type: integer, format: int64 } + CredentialBundle: + type: object + description: Everything a client needs for every protocol, in one response. + properties: + id: { type: string, format: uuid } + wireguard: + type: object + properties: + client_conf: + type: string + description: | + Complete wg-quick [Interface]/[Peer] file with the literal + placeholder REPLACE_WITH_PRIVATE_KEY for the client's private + key (the node never sees it). + server_public_key: { type: string } + endpoint: { type: string, example: "203.0.113.10:51820" } + address: { type: string, example: "10.0.0.7/32" } + dns: { type: string, example: "10.0.0.1" } + vless_uri: + type: string + description: vless:// share URI (REALITY, flow=xtls-rprx-vision) + example: "vless://c0a4f1de-...@203.0.113.10:8443?security=reality&pbk=...&sid=...&sni=www.microsoft.com&flow=xtls-rprx-vision&type=tcp#erebrus-sg" + hysteria2_uri: + type: string + example: "hysteria2://pw@203.0.113.10:4443/?insecure=1&sni=node#erebrus-sg" + singbox_profile: + type: object + description: | + Complete sing-box client config fragment: outbounds for wg + (endpoint), vless and hysteria2, WITHOUT selector/urltest groups — + the gateway injects the "auto"/"stealth" groups before handing the + profile to end clients. diff --git a/docs/node.md b/docs/node.md deleted file mode 100644 index 90d8b04..0000000 --- a/docs/node.md +++ /dev/null @@ -1,33 +0,0 @@ -# Host your Erebrus node - -## Install and Deploy using Docker - -1. Install docker using this [script](https://github.com/NetSepio/erebrus/blob/main/docs/setup.md) (For Ubuntu server). Or refer the official [documentation](https://docs.docker.com/engine/install) - -2. create a .env file in same directory and define the environment for erebrus . you can use template from [.sample-env](https://github.com/NetSepio/erebrus/blob/main/.sample-env). Make sure to put the correct server URL. Example: -``` -NODE_NAME=blazing_icarus" -DOMAIN=http://255.255.255.255:9080 -HOST_IP=255.255.255.255 -WG_ENDPOINT_HOST=255.255.255.255 -``` -replace `255.255.255.255` with the server IP address - -3. Open incoming request to ports: TCP Ports `9080`(http),` 9090`(gRPC),` 9002`(p2p) and UDP port `51820` of your server to communicate with the gateway - -4. Pull the ererbus docker image -``` -docker pull ghcr.io/netsepio/erebrus:main -``` -5. Run the Image - -``` -docker run -d -p 9080:9080/tcp -p 9002:9002/tcp -p 51820:51820/udp \ ---cap-add=NET_ADMIN --cap-add=SYS_MODULE \ ---sysctl="net.ipv4.conf.all.src_valid_mark=1" \ ---sysctl="net.ipv6.conf.all.forwarding=1" \ ---restart unless-stopped \ --v ~/wireguard/:/etc/wireguard/ \ ---name erebrus --env-file .env \ -ghcr.io/netsepio/erebrus:main -``` \ No newline at end of file diff --git a/docs/setup.md b/docs/setup.md deleted file mode 100644 index 4e7b82c..0000000 --- a/docs/setup.md +++ /dev/null @@ -1,38 +0,0 @@ -# Erebrus Setup Docs - -# Watcher Setup - -### For Ubuntu 21.04 - -After placing the .path and .service files in /etc/systemd/system, Run: - -1. `sudo systemctl daemon-reload` - -2. `sudo systemctl enable wg-watcher.path && sudo systemctl start wg-watcher.path` - Created symlink /etc/systemd/system/multi-user.target.wants/wg-watcher.path → /etc/systemd/system/wg-watcher.path. - -3. `sudo systemctl enable wg-watcher.service && sudo systemctl start wg-watcher.service` - Created symlink /etc/systemd/system/multi-user.target.wants/wg-watcher.service → /etc/systemd/system/wg-watcher.service. - -4. `sudo systemctl status wg-watcher.path` - -5. `sudo systemctl status wg-watcher.service` - -# WireGuard Setup - -1. `sudo apt install wireguard` -2. `modprobe wireguard` -3. `lsmod | grep wireguard` -4. Set the Linux kernel to forward the traffic: - -```bash -cat << EOF >> max.conf -net.ipv4.ip_forward=1 -net.ipv6.conf.all.forwarding=1 -EOF -sysctl -p -``` - -5. `wg-quick up wg0` -6. `chmod 600 /etc/wireguard/wg0.conf` -7. `systemctl enable wg-quick@wg0` diff --git a/docs/setup.sh b/docs/setup.sh deleted file mode 100644 index b97db35..0000000 --- a/docs/setup.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/bin/bash -# setup erebrus node on ubuntu/debian server - -# Update the package index -sudo apt-get update -sudo apt-get install ca-certificates curl gnupg -sudo install -m 0755 -d /etc/apt/keyrings -curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg -sudo chmod a+r /etc/apt/keyrings/docker.gpg - -echo \ - "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ - $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ - sudo tee /etc/apt/sources.list.d/docker.list > /dev/null -sudo apt-get update - -sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin - -# Configure Docker to be used without root -sudo groupadd docker -sudo usermod -aG docker $USER -newgrp docker - -# Start Docker services -sudo systemctl start docker -sudo systemctl enable docker - -docker run -d -p 9080:9080/tcp -p 51820:51820/udp --cap-add=NET_ADMIN --cap-add=SYS_MODULE --sysctl="net.ipv4.conf.all.src_valid_mark=1" --sysctl="net.ipv6.conf.all.forwarding=1" --restart unless-stopped --name erebrus --env-file .env ghcr.io/netsepio/erebrus:main diff --git a/docs/swagger.yml b/docs/swagger.yml deleted file mode 100644 index 2b8af2c..0000000 --- a/docs/swagger.yml +++ /dev/null @@ -1,786 +0,0 @@ -basePath: /api/v1.0 -consumes: -- application/json -- application/x-protobuf -definitions: - Client: - properties: - address: - description: Address range client must will assigned - example: - - 10.0.0.2/32 - items: - type: string - type: array - x-go-name: Address - allowedIPs: - description: IP addresses allowed to connect - example: - - 0.0.0.0/0 - - ::/0 - items: - type: string - type: array - x-go-name: AllowedIPs - created: - description: Time the client is created - example: 1642409076544 - format: int64 - type: integer - x-go-name: Created - createdBy: - description: Denoting person creates the client - example: jonsnow@mail.com - type: string - x-go-name: CreatedBy - email: - description: Email that the client device belongs - example: jonsnow@mail.com - type: string - x-go-name: Email - enable: - description: Status signal for client - example: true - type: boolean - x-go-name: Enable - ignorePersistentKeepalive: - example: true - type: boolean - x-go-name: IgnorePersistentKeepalive - name: - description: Name of the client - example: jon snow - type: string - x-go-name: Name - presharedKey: - description: Preshared key for the client - example: twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc= - type: string - x-go-name: PresharedKey - privateKey: - description: Private key for the client - example: KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg= - type: string - x-go-name: PrivateKey - publicKey: - description: Public key for the client - example: YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4= - type: string - x-go-name: PublicKey - tags: - description: Tags for client device - example: - - laptop - - PC - items: - type: string - type: array - x-go-name: Tags - updated: - description: Time the client is last updated - example: 1642409076544 - format: int64 - type: integer - x-go-name: Updated - updatedBy: - description: Denoting person updates the client - example: jonsnow@mail.com - type: string - x-go-name: UpdatedBy - uuid: - description: Client identifier - example: 6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab - type: string - x-go-name: UUID - type: object - x-go-package: _/home/sambath/Golang/Revotic-Engineering/erebrus/api/v1/client - ClientReq: - properties: - address: - description: Address range client must will assigned - example: - - 10.0.0.0/24 - items: - type: string - type: array - x-go-name: Address - allowedIPs: - description: IP addresses allowed to connect - example: - - 0.0.0.0/0 - - ::/0 - items: - type: string - type: array - x-go-name: AllowedIPs - createdBy: - description: Denoting person creates the client - example: jonsnow@mail.com - type: string - x-go-name: CreatedBy - email: - description: Email that the client device belongs - example: jonsnow@mail.com - type: string - x-go-name: Email - enable: - description: Status signal for client - example: true - type: boolean - x-go-name: Enable - name: - example: jon snow - type: string - x-go-name: Name - tags: - description: Tags for client device - example: - - laptop - - PC - items: - type: string - type: array - x-go-name: Tags - updatedBy: - description: Denoting person updates the client - example: jonsnow@mail.com - type: string - x-go-name: UpdatedBy - required: - - name - - tags - - email - - enable - - allowedIPs - - address - - createdBy - - updatedBy - type: object - x-go-package: _/home/sambath/Golang/Revotic-Engineering/erebrus/api/v1/client - ClientUpdateReq: - properties: - address: - description: IP addresses allowed to connect - example: - - 10.0.0.2/32 - items: - type: string - type: array - x-go-name: Address - allowedIPs: - description: IP addresses allowed to connect - example: - - 0.0.0.0/0 - - ::/0 - items: - type: string - type: array - x-go-name: AllowedIPs - created: - description: Time the client is created - example: 1642409076544 - format: int64 - type: integer - x-go-name: Created - createdBy: - description: Denoting person creates the client - example: jonsnow@mail.com - type: string - x-go-name: CreatedBy - email: - description: Email that the client device belongs - example: jonsnow@mail.com - type: string - x-go-name: Email - enable: - description: Status signal for client - example: true - type: boolean - x-go-name: Enable - ignorePersistentKeepalive: - example: true - type: boolean - x-go-name: IgnorePersistentKeepalive - name: - description: Name of the client - example: jon snow - type: string - x-go-name: Name - presharedKey: - description: Preshared key for the client - example: twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc= - type: string - x-go-name: PresharedKey - privateKey: - description: Private key for the client - example: KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg= - type: string - x-go-name: PrivateKey - publicKey: - description: Public key for the client - example: YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4= - type: string - x-go-name: PublicKey - tags: - description: Tags for client device - example: - - laptop - - PC - items: - type: string - type: array - x-go-name: Tags - updated: - description: Time the client is last updated - example: 1642409076544 - format: int64 - type: integer - x-go-name: Updated - updatedBy: - description: Denoting person updates the client - example: jonsnow@mail.com - type: string - x-go-name: UpdatedBy - uuid: - description: Client identifier - example: 6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab - type: string - x-go-name: UUID - required: - - uuid - - name - - tags - - email - - enable - - allowedIPs - - address - - updatedBy - type: object - x-go-package: _/home/sambath/Golang/Revotic-Engineering/erebrus/api/v1/client - Server: - properties: - address: - description: Server address - example: - - 10.0.0.1/24 - items: - type: string - type: array - x-go-name: Address - allowedips: - description: IP addresses allowed to connect - example: - - 0.0.0.0/0 - - ::/0 - items: - type: string - type: array - x-go-name: AllowedIPs - created: - description: Time when server is created - example: 26103870 - format: int64 - type: integer - x-go-name: Created - dns: - description: DNS of the VPN server - example: - - 1.1.1.1 - items: - type: string - type: array - x-go-name: DNS - endpoint: - description: Endpoint of the server - example: region.example.com - type: string - x-go-name: Endpoint - listenPort: - description: Port the server listens - example: 51280 - format: int64 - type: integer - x-go-name: ListenPort - mtu: - format: int64 - type: integer - x-go-name: Mtu - persistentKeepalive: - description: Persistent keep alive for server - example: 16 - format: int64 - type: integer - x-go-name: PersistentKeepalive - postDown: - description: Post down command - example: iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j - ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE - type: string - x-go-name: PostDown - postUp: - description: Post up command - example: iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j - ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE - type: string - x-go-name: PostUp - preDown: - description: Pre down command - example: echo WireGuard PreDown - type: string - x-go-name: PreDown - preUp: - description: Pre up command - example: echo WireGuard PreUp - type: string - x-go-name: PreUp - privateKey: - description: Private key for the server - example: UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE= - type: string - x-go-name: PrivateKey - publicKey: - description: Public key for the server - example: T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks= - type: string - x-go-name: PublicKey - updated: - description: Time when server is created - example: 26103870 - format: int64 - type: integer - x-go-name: Updated - updatedBy: - description: Updater email address - example: admin@mail.com - type: string - x-go-name: UpdatedBy - type: object - x-go-package: _/home/sambath/Golang/Revotic-Engineering/erebrus/api/v1/server - Status: - properties: - Domain: - description: Domain which server is running - example: vpn.example.com - type: string - Hostname: - description: Server Hostname - example: ubuntu - type: string - HttpPort: - description: Port which HTTP service is running - example: "4000" - type: string - PrivateIP: - description: Private IP of server host - example: 10.0.1.5 - type: string - PublicIP: - description: Server's public IP - example: 14.10.35.65 - type: string - Region: - description: Region where server running - example: India/Banglore - type: string - VPNPort: - description: VPN port - example: "5128" - type: string - Version: - description: Server version - example: "1.0" - type: string - gRPCPort: - description: Port which gRPC service is running - example: "5000" - type: string - x-go-name: GRPCPort - type: object - x-go-package: _/home/sambath/Golang/Revotic-Engineering/erebrus/api/v1/server -host: localhost -info: - contact: - email: sachinmugu@gmail.com - name: Sambath Kumar - description: |- - Erebrus is an open source VPN solution from The NetSepio, that helps to deploy your own VPN solution in - minutes.The vision of Erebrus is to deliver Cyber security to everyone . - - Features of Erebrus were, Easy Client and Server management, Supports REST and gRPC, Email VPN configuration to clients easily. - - This documentation guides you, How to use Erebrus endpoints and It's Request and Response briefly. - license: - name: GPL-3.0 - url: https://opensource.org/licenses/GPL-3.0 - title: Erebrus - version: 1.0.0 -paths: - /client: - get: - description: Get all clients in the server. - operationId: readClients - responses: - "200": - $ref: '#/responses/clientsSucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Read All Clients - tags: - - Client - post: - description: Create client based on the given client model. - operationId: createClient - parameters: - - description: Requestbody used for create and update client operations. - in: body - name: client - schema: - $ref: '#/definitions/ClientReq' - x-go-name: Body - responses: - "201": - $ref: '#/responses/clientSucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Create client - tags: - - Client - /client/{id}: - delete: - description: Delete client based on the given uuid. - operationId: deleteClient - parameters: - - description: The Identifier of the Client - in: path - name: id - required: true - type: string - x-go-name: Id - responses: - "200": - $ref: '#/responses/sucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Delete client - tags: - - Client - get: - description: Return client based on the given uuid. - operationId: readClient - parameters: - - description: The Identifier of the Client - in: path - name: id - required: true - type: string - x-go-name: Id - responses: - "200": - $ref: '#/responses/clientSucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Read client - tags: - - Client - patch: - description: Update client based on the given uuid and client model. - operationId: updateClient - parameters: - - description: The Identifier of the Client - in: path - name: id - required: true - type: string - x-go-name: Id - - description: Requestbody used for create and update client operations. - in: body - name: client - schema: - $ref: '#/definitions/ClientUpdateReq' - x-go-name: Body - responses: - "200": - $ref: '#/responses/clientSucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Update client - tags: - - Client - /client/{id}/config: - get: - description: Return client configuration file in byte format based on the given - uuid. - operationId: configClient - parameters: - - description: The Identifier of the Client - in: path - name: id - required: true - type: string - x-go-name: Id - produces: - - application/octet-stream - - application/json - responses: - "200": - $ref: '#/responses/configResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Get client configuration - tags: - - Client - /client/{id}/email: - get: - description: Email the configuration file of the client to the email associated - with client. - operationId: emailClient - parameters: - - description: The Identifier of the Client - in: path - name: id - required: true - type: string - x-go-name: Id - responses: - "200": - $ref: '#/responses/sucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Email client Configuration - tags: - - Client - /server: - get: - description: Retrieves the server details. - operationId: readServer - responses: - "200": - $ref: '#/responses/serverSucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Read Server - tags: - - Server - patch: - description: Update the server with given details. - operationId: updateServer - parameters: - - description: Requestbody used for update server operations. - in: body - name: server - schema: - $ref: '#/definitions/Server' - x-go-name: Body - responses: - "200": - $ref: '#/responses/serverSucessResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Update Server - tags: - - Server - /server/config: - get: - description: |- - Get Server Configuration - Retrieves the server configuration details. - operationId: configServer - responses: - "200": - $ref: '#/responses/configResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - tags: - - Server - /server/status: - get: - description: Retrieves the server status details. - operationId: statusServer - responses: - "200": - $ref: '#/responses/serverStatusResponse' - "400": - $ref: '#/responses/badRequestResponse' - "401": - $ref: '#/responses/unauthorizedResponse' - "500": - $ref: '#/responses/serverErrorResponse' - summary: Get Server status - tags: - - Server -produces: -- application/json -- application/x-protobuf -- application/config -responses: - badRequestResponse: - description: "" - schema: - properties: - Error: - example: error message - type: string - Status: - example: 400 - format: int64 - type: integer - Sucess: - example: false - type: boolean - type: object - clientSucessResponse: - description: "" - schema: - properties: - Message: - example: sucess message - type: string - Status: - example: 201 - format: int64 - type: integer - Sucess: - example: true - type: boolean - client: - $ref: '#/definitions/Client' - type: object - clientsSucessResponse: - description: "" - schema: - properties: - Message: - example: sucess message - type: string - Status: - example: 201 - format: int64 - type: integer - Sucess: - example: true - type: boolean - clients: - items: - $ref: '#/definitions/Client' - type: array - x-go-name: Body - type: object - configResponse: - description: "" - schema: - properties: - content: - example: File Download - type: string - x-go-name: Data - type: object - serverErrorResponse: - description: "" - schema: - properties: - Error: - example: error message - type: string - Status: - example: 500 - format: int64 - type: integer - Sucess: - example: false - type: boolean - type: object - serverStatusResponse: - description: "" - schema: - $ref: '#/definitions/Status' - serverSucessResponse: - description: "" - schema: - properties: - Message: - example: sucess message - type: string - Status: - example: 201 - format: int64 - type: integer - Sucess: - example: true - type: boolean - server: - $ref: '#/definitions/Server' - type: object - sucessResponse: - description: "" - schema: - properties: - Message: - example: sucess message - type: string - Status: - example: 200 - format: int64 - type: integer - Sucess: - example: true - type: boolean - type: object - unauthorizedResponse: - description: "" - schema: - properties: - Error: - example: error message - type: string - Status: - example: 401 - format: int64 - type: integer - Sucess: - example: false - type: boolean - type: object -schemes: -- http -- https -swagger: "2.0" diff --git a/gRPC/gRPC.go b/gRPC/gRPC.go deleted file mode 100644 index 952bd48..0000000 --- a/gRPC/gRPC.go +++ /dev/null @@ -1,11 +0,0 @@ -package grpc - -import ( - v1 "github.com/NetSepio/erebrus/gRPC/v1" - "google.golang.org/grpc" -) - -func Initialize() *grpc.Server { - grpc_server := v1.Initialize() - return grpc_server -} diff --git a/gRPC/v1/authenticate/authenticate.go b/gRPC/v1/authenticate/authenticate.go deleted file mode 100644 index cf9a1dc..0000000 --- a/gRPC/v1/authenticate/authenticate.go +++ /dev/null @@ -1 +0,0 @@ -package authenticate diff --git a/gRPC/v1/authenticate/paseto/paseto.go b/gRPC/v1/authenticate/paseto/paseto.go deleted file mode 100644 index 4cccb52..0000000 --- a/gRPC/v1/authenticate/paseto/paseto.go +++ /dev/null @@ -1,43 +0,0 @@ -package paseto - -import ( - context "context" - "encoding/json" - "fmt" - - gopaseto "aidanwoods.dev/go-paseto" - "github.com/NetSepio/erebrus/util/pkg/auth" - "github.com/NetSepio/erebrus/util/pkg/claims" - log "github.com/sirupsen/logrus" - "google.golang.org/grpc/metadata" -) - -func PASETO(ctx context.Context) (context.Context, error) { - md, _ := metadata.FromIncomingContext(ctx) - token := md["authorization"][0] - if token == "" { - log.WithFields(log.Fields{ - "err": "Authorization header is missing", - }).Error("Authorization header is missing") - new_ctx := context.WithValue(ctx, "error", 1) - return new_ctx, nil - } - parser := gopaseto.NewParser() - parser.AddRule(gopaseto.NotExpired()) - publickey := auth.Getpublickey() - parsedToken, err := parser.ParseV4Public(publickey, token, nil) - if err != nil { - err = fmt.Errorf("failed to scan claims for paseto token, %s", err) - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bindfailed to scan claims for paseto token") - new_ctx := context.WithValue(ctx, "error", 1) - return new_ctx, nil - } - jsonvalue := parsedToken.ClaimsJSON() - ClaimsValue := claims.CustomClaims{} - json.Unmarshal(jsonvalue, &ClaimsValue) - new_ctx := context.WithValue(ctx, "walletAddress", ClaimsValue.WalletAddress) - - return new_ctx, nil -} diff --git a/gRPC/v1/authenticate/selector/selector.go b/gRPC/v1/authenticate/selector/selector.go deleted file mode 100644 index 0f4ba48..0000000 --- a/gRPC/v1/authenticate/selector/selector.go +++ /dev/null @@ -1,17 +0,0 @@ -package selector - -import ( - "context" - - "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors" -) - -func LoginSkip(_ context.Context, c interceptors.CallMeta) bool { - methods := []string{"server.ServerService", "client.ClientService"} - for _, s := range methods { - if c.Service == s { - return true - } - } - return false -} diff --git a/gRPC/v1/client/client.go b/gRPC/v1/client/client.go deleted file mode 100644 index 26a578b..0000000 --- a/gRPC/v1/client/client.go +++ /dev/null @@ -1,119 +0,0 @@ -package client - -import ( - "context" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" - log "github.com/sirupsen/logrus" -) - -// gRPC client service struct -type ClientService struct { - UnimplementedClientServiceServer -} - -// Method to get Client information -func (cs *ClientService) GetClientInformation(ctx context.Context, request *ClientRequest) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - id := request.UUID - log.WithFields(util.StandardFieldsGRPC).Info("Client Information Request ,for:", id) - client, err := core.ReadClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to read client") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(200, "Client Information Fetched", nil, client, nil) - - return response, nil -} - -// Method to create client -func (cs *ClientService) CreateClient(ctx context.Context, request *model.Client) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - client, err := core.RegisterClient(request) - log.WithFields(util.StandardFieldsGRPC).Info("Client Creation Request") - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to read client") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(201, "Client Created", nil, client, nil) - return response, nil -} - -// Method to update client -func (cs *ClientService) UpdateClient(ctx context.Context, request *UpdateRequest) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - id := request.UUID - log.WithFields(util.StandardFieldsGRPC).Info("Client Update Request ,for:", id) - client, err := core.UpdateClient(id, request.Client) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to read client") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(200, "Client Updated", nil, client, nil) - return response, nil -} - -// Method to delete client -func (cs *ClientService) DeleteClient(ctx context.Context, request *ClientRequest) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - id := request.UUID - log.WithFields(util.StandardFieldsGRPC).Info("Delete Client Request ,for:", id) - err := core.DeleteClient(id) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to read client") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(200, "Client Deleted", nil, nil, nil) - return response, nil -} - -// Method to get all clients -func (cs *ClientService) GetClients(ctx context.Context, request *Empty) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - log.WithFields(util.StandardFieldsGRPC).Info("Request For Get All Clients") - clients, err := core.ReadClients() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to read client") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(200, "Client Information Fetched", nil, nil, clients) - return response, nil -} diff --git a/gRPC/v1/client/client.pb.go b/gRPC/v1/client/client.pb.go deleted file mode 100644 index cd23f90..0000000 --- a/gRPC/v1/client/client.pb.go +++ /dev/null @@ -1,373 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.25.0-devel -// protoc v3.14.0 -// source: gRPC/v1/client/client.proto - -package client - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - model"github.com/NetSepio/erebrus/model" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type ClientRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` -} - -func (x *ClientRequest) Reset() { - *x = ClientRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_client_client_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *ClientRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*ClientRequest) ProtoMessage() {} - -func (x *ClientRequest) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_client_client_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use ClientRequest.ProtoReflect.Descriptor instead. -func (*ClientRequest) Descriptor() ([]byte, []int) { - return file_gRPC_v1_client_client_proto_rawDescGZIP(), []int{0} -} - -func (x *ClientRequest) GetUUID() string { - if x != nil { - return x.UUID - } - return "" -} - -type Empty struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *Empty) Reset() { - *x = Empty{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_client_client_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Empty) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Empty) ProtoMessage() {} - -func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_client_client_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Empty.ProtoReflect.Descriptor instead. -func (*Empty) Descriptor() ([]byte, []int) { - return file_gRPC_v1_client_client_proto_rawDescGZIP(), []int{1} -} - -type Config struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Config []byte `protobuf:"bytes,1,opt,name=Config,proto3" json:"Config,omitempty"` -} - -func (x *Config) Reset() { - *x = Config{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_client_client_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Config) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Config) ProtoMessage() {} - -func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_client_client_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Config.ProtoReflect.Descriptor instead. -func (*Config) Descriptor() ([]byte, []int) { - return file_gRPC_v1_client_client_proto_rawDescGZIP(), []int{2} -} - -func (x *Config) GetConfig() []byte { - if x != nil { - return x.Config - } - return nil -} - -type UpdateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` - Client *model.Client `protobuf:"bytes,2,opt,name=client,proto3" json:"client,omitempty"` -} - -func (x *UpdateRequest) Reset() { - *x = UpdateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_client_client_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *UpdateRequest) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*UpdateRequest) ProtoMessage() {} - -func (x *UpdateRequest) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_client_client_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use UpdateRequest.ProtoReflect.Descriptor instead. -func (*UpdateRequest) Descriptor() ([]byte, []int) { - return file_gRPC_v1_client_client_proto_rawDescGZIP(), []int{3} -} - -func (x *UpdateRequest) GetUUID() string { - if x != nil { - return x.UUID - } - return "" -} - -func (x *UpdateRequest) GetClient() *model.Client { - if x != nil { - return x.Client - } - return nil -} - -var File_gRPC_v1_client_client_proto protoreflect.FileDescriptor - -var file_gRPC_v1_client_client_proto_rawDesc = []byte{ - 0x0a, 0x1b, 0x67, 0x52, 0x50, 0x43, 0x2f, 0x76, 0x31, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x1a, 0x11, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x23, 0x0a, 0x0d, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, 0x49, - 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x22, 0x07, 0x0a, - 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x20, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x12, 0x16, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, - 0x52, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x22, 0x4a, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, 0x49, - 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x12, 0x25, 0x0a, - 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, - 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, - 0x69, 0x65, 0x6e, 0x74, 0x32, 0xa1, 0x03, 0x0a, 0x0d, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x3e, 0x0a, 0x14, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x15, - 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3f, 0x0a, 0x16, 0x47, 0x65, 0x74, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x18, 0x45, 0x6d, 0x61, 0x69, 0x6c, - 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, 0x0c, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x0d, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x1a, 0x0f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, - 0x6c, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0c, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x69, - 0x65, 0x6e, 0x74, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x0f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x36, 0x0a, 0x0c, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x12, 0x15, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2e, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, - 0x6c, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2c, 0x0a, 0x0a, 0x47, 0x65, - 0x74, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x0d, 0x2e, 0x63, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_gRPC_v1_client_client_proto_rawDescOnce sync.Once - file_gRPC_v1_client_client_proto_rawDescData = file_gRPC_v1_client_client_proto_rawDesc -) - -func file_gRPC_v1_client_client_proto_rawDescGZIP() []byte { - file_gRPC_v1_client_client_proto_rawDescOnce.Do(func() { - file_gRPC_v1_client_client_proto_rawDescData = protoimpl.X.CompressGZIP(file_gRPC_v1_client_client_proto_rawDescData) - }) - return file_gRPC_v1_client_client_proto_rawDescData -} - -var file_gRPC_v1_client_client_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_gRPC_v1_client_client_proto_goTypes = []interface{}{ - (*ClientRequest)(nil), // 0: client.ClientRequest - (*Empty)(nil), // 1: client.Empty - (*Config)(nil), // 2: client.Config - (*UpdateRequest)(nil), // 3: client.UpdateRequest - (*model.Client)(nil), // 4: model.Client - (*model.Response)(nil), // 5: model.Response -} -var file_gRPC_v1_client_client_proto_depIdxs = []int32{ - 4, // 0: client.UpdateRequest.client:type_name -> model.Client - 0, // 1: client.ClientService.GetClientInformation:input_type -> client.ClientRequest - 0, // 2: client.ClientService.GetClientConfiguration:input_type -> client.ClientRequest - 0, // 3: client.ClientService.EmailClientConfiguration:input_type -> client.ClientRequest - 4, // 4: client.ClientService.CreateClient:input_type -> model.Client - 3, // 5: client.ClientService.UpdateClient:input_type -> client.UpdateRequest - 0, // 6: client.ClientService.DeleteClient:input_type -> client.ClientRequest - 1, // 7: client.ClientService.GetClients:input_type -> client.Empty - 5, // 8: client.ClientService.GetClientInformation:output_type -> model.Response - 2, // 9: client.ClientService.GetClientConfiguration:output_type -> client.Config - 2, // 10: client.ClientService.EmailClientConfiguration:output_type -> client.Config - 5, // 11: client.ClientService.CreateClient:output_type -> model.Response - 5, // 12: client.ClientService.UpdateClient:output_type -> model.Response - 5, // 13: client.ClientService.DeleteClient:output_type -> model.Response - 5, // 14: client.ClientService.GetClients:output_type -> model.Response - 8, // [8:15] is the sub-list for method output_type - 1, // [1:8] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name -} - -func init() { file_gRPC_v1_client_client_proto_init() } -func file_gRPC_v1_client_client_proto_init() { - if File_gRPC_v1_client_client_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_gRPC_v1_client_client_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ClientRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_gRPC_v1_client_client_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_gRPC_v1_client_client_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_gRPC_v1_client_client_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_gRPC_v1_client_client_proto_rawDesc, - NumEnums: 0, - NumMessages: 4, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_gRPC_v1_client_client_proto_goTypes, - DependencyIndexes: file_gRPC_v1_client_client_proto_depIdxs, - MessageInfos: file_gRPC_v1_client_client_proto_msgTypes, - }.Build() - File_gRPC_v1_client_client_proto = out.File - file_gRPC_v1_client_client_proto_rawDesc = nil - file_gRPC_v1_client_client_proto_goTypes = nil - file_gRPC_v1_client_client_proto_depIdxs = nil -} diff --git a/gRPC/v1/client/client.proto b/gRPC/v1/client/client.proto deleted file mode 100644 index f69512f..0000000 --- a/gRPC/v1/client/client.proto +++ /dev/null @@ -1,30 +0,0 @@ -syntax="proto3"; - -package client; - -import "model/model.proto"; - -message ClientRequest{ - string UUID=1; -} - -message Empty{ - -} - -message Config{ - bytes Config=1; -} - -message UpdateRequest{ - string UUID=1; - model.Client client=2; -} - -service ClientService{ - rpc GetClientInformation(ClientRequest) returns (model.Response); - rpc RegisterClient(model.Client) returns (model.Response); - rpc UpdateClient(UpdateRequest) returns (model.Response); - rpc DeleteClient(ClientRequest) returns (model.Response); - rpc GetClients(Empty) returns (model.Response); -} diff --git a/gRPC/v1/client/client_grpc.pb.go b/gRPC/v1/client/client_grpc.pb.go deleted file mode 100644 index db8750b..0000000 --- a/gRPC/v1/client/client_grpc.pb.go +++ /dev/null @@ -1,318 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. - -package client - -import ( - context "context" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" - model"github.com/NetSepio/erebrus/model" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// ClientServiceClient is the client API for ClientService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ClientServiceClient interface { - GetClientInformation(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*model.Response, error) - GetClientConfiguration(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*Config, error) - EmailClientConfiguration(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*Config, error) - CreateClient(ctx context.Context, in *model.Client, opts ...grpc.CallOption) (*model.Response, error) - UpdateClient(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*model.Response, error) - DeleteClient(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*model.Response, error) - GetClients(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*model.Response, error) -} - -type clientServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewClientServiceClient(cc grpc.ClientConnInterface) ClientServiceClient { - return &clientServiceClient{cc} -} - -func (c *clientServiceClient) GetClientInformation(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/client.ClientService/GetClientInformation", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clientServiceClient) GetClientConfiguration(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*Config, error) { - out := new(Config) - err := c.cc.Invoke(ctx, "/client.ClientService/GetClientConfiguration", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clientServiceClient) EmailClientConfiguration(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*Config, error) { - out := new(Config) - err := c.cc.Invoke(ctx, "/client.ClientService/EmailClientConfiguration", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clientServiceClient) CreateClient(ctx context.Context, in *model.Client, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/client.ClientService/CreateClient", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clientServiceClient) UpdateClient(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/client.ClientService/UpdateClient", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clientServiceClient) DeleteClient(ctx context.Context, in *ClientRequest, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/client.ClientService/DeleteClient", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *clientServiceClient) GetClients(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/client.ClientService/GetClients", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ClientServiceServer is the server API for ClientService service. -// All implementations must embed UnimplementedClientServiceServer -// for forward compatibility -type ClientServiceServer interface { - GetClientInformation(context.Context, *ClientRequest) (*model.Response, error) - GetClientConfiguration(context.Context, *ClientRequest) (*Config, error) - EmailClientConfiguration(context.Context, *ClientRequest) (*Config, error) - CreateClient(context.Context, *model.Client) (*model.Response, error) - UpdateClient(context.Context, *UpdateRequest) (*model.Response, error) - DeleteClient(context.Context, *ClientRequest) (*model.Response, error) - GetClients(context.Context, *Empty) (*model.Response, error) - mustEmbedUnimplementedClientServiceServer() -} - -// UnimplementedClientServiceServer must be embedded to have forward compatible implementations. -type UnimplementedClientServiceServer struct { -} - -func (UnimplementedClientServiceServer) GetClientInformation(context.Context, *ClientRequest) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetClientInformation not implemented") -} -func (UnimplementedClientServiceServer) GetClientConfiguration(context.Context, *ClientRequest) (*Config, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetClientConfiguration not implemented") -} -func (UnimplementedClientServiceServer) EmailClientConfiguration(context.Context, *ClientRequest) (*Config, error) { - return nil, status.Errorf(codes.Unimplemented, "method EmailClientConfiguration not implemented") -} -func (UnimplementedClientServiceServer) CreateClient(context.Context, *model.Client) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateClient not implemented") -} -func (UnimplementedClientServiceServer) UpdateClient(context.Context, *UpdateRequest) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateClient not implemented") -} -func (UnimplementedClientServiceServer) DeleteClient(context.Context, *ClientRequest) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteClient not implemented") -} -func (UnimplementedClientServiceServer) GetClients(context.Context, *Empty) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetClients not implemented") -} -func (UnimplementedClientServiceServer) mustEmbedUnimplementedClientServiceServer() {} - -// UnsafeClientServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ClientServiceServer will -// result in compilation errors. -type UnsafeClientServiceServer interface { - mustEmbedUnimplementedClientServiceServer() -} - -func RegisterClientServiceServer(s grpc.ServiceRegistrar, srv ClientServiceServer) { - s.RegisterService(&ClientService_ServiceDesc, srv) -} - -func _ClientService_GetClientInformation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ClientRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).GetClientInformation(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/GetClientInformation", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).GetClientInformation(ctx, req.(*ClientRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClientService_GetClientConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ClientRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).GetClientConfiguration(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/GetClientConfiguration", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).GetClientConfiguration(ctx, req.(*ClientRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClientService_EmailClientConfiguration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ClientRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).EmailClientConfiguration(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/EmailClientConfiguration", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).EmailClientConfiguration(ctx, req.(*ClientRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClientService_CreateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(model.Client) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).CreateClient(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/CreateClient", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).CreateClient(ctx, req.(*model.Client)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClientService_UpdateClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpdateRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).UpdateClient(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/UpdateClient", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).UpdateClient(ctx, req.(*UpdateRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClientService_DeleteClient_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ClientRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).DeleteClient(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/DeleteClient", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).DeleteClient(ctx, req.(*ClientRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _ClientService_GetClients_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ClientServiceServer).GetClients(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/client.ClientService/GetClients", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ClientServiceServer).GetClients(ctx, req.(*Empty)) - } - return interceptor(ctx, in, info, handler) -} - -// ClientService_ServiceDesc is the grpc.ServiceDesc for ClientService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ClientService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "client.ClientService", - HandlerType: (*ClientServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetClientInformation", - Handler: _ClientService_GetClientInformation_Handler, - }, - { - MethodName: "GetClientConfiguration", - Handler: _ClientService_GetClientConfiguration_Handler, - }, - { - MethodName: "EmailClientConfiguration", - Handler: _ClientService_EmailClientConfiguration_Handler, - }, - { - MethodName: "CreateClient", - Handler: _ClientService_CreateClient_Handler, - }, - { - MethodName: "UpdateClient", - Handler: _ClientService_UpdateClient_Handler, - }, - { - MethodName: "DeleteClient", - Handler: _ClientService_DeleteClient_Handler, - }, - { - MethodName: "GetClients", - Handler: _ClientService_GetClients_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "gRPC/v1/client/client.proto", -} diff --git a/gRPC/v1/client/client_test.go b/gRPC/v1/client/client_test.go deleted file mode 100644 index 71dd3b9..0000000 --- a/gRPC/v1/client/client_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package client - -import ( - "context" - "testing" - - "github.com/NetSepio/erebrus/model" -) - -func TestCreateClient(t *testing.T) { - var client *ClientService - data := new(model.Client) - data.Name = "sambath kumar" - data.Tags = []string{"home"} - data.CreatedBy = "sambath@mail.com" - data.WalletAddress = "0xqwertyuioASDFGHJ" - data.Enable = true - data.AllowedIPs = []string{"0.0.0.0/0", "::/0"} - data.Address = []string{"10.0.0.1/24"} - response, err := client.CreateClient(context.Background(), data) - if err != nil { - t.Error(err) - } else { - t.Log("Sucess") - t.Log(response) - } -} diff --git a/gRPC/v1/server/server.go b/gRPC/v1/server/server.go deleted file mode 100644 index 13b4fc4..0000000 --- a/gRPC/v1/server/server.go +++ /dev/null @@ -1,71 +0,0 @@ -package server - -import ( - "context" - "errors" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" - log "github.com/sirupsen/logrus" -) - -type ServerService struct { - UnimplementedServerServiceServer -} - -// Method to get server information -func (ss *ServerService) GetServerInformation(ctx context.Context, request *Empty) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - log.WithFields(util.StandardFieldsGRPC).Info("Request For Sever Information") - server, err := core.ReadServer() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to get server info") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(200, "Server Information Fetched", server, nil, nil) - return response, nil -} - -// method to get server configuration -func (ss *ServerService) GetServerConfiguraion(ctx context.Context, request *Empty) (*Config, error) { - if ctx.Value("error") == 1 { - return &Config{Status: 500, Success: false, Error: "bad token"}, nil - } - log.WithFields(util.StandardFieldsGRPC).Info("Request For Sever Configurtaion") - configData, err := core.ReadWgConfigFile() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("unable to read server configuration") - - return nil, errors.New(err.Error()) - } - - return &Config{Config: configData, Status: 200, Success: true}, nil -} - -// Method to update server -func (ss *ServerService) UpdateServer(ctx context.Context, request *model.Server) (*model.Response, error) { - if ctx.Value("error") == 1 { - response := core.MakeErrorResponse(500, "Bad Token", nil, nil, nil) - return response, nil - } - log.WithFields(util.StandardFieldsGRPC).Info("Request For Update Server") - server, err := core.UpdateServer(request) - if err != nil { - log.WithFields(util.StandardFields).Error("Failed to update server") - response := core.MakeErrorResponse(500, err.Error(), nil, nil, nil) - return response, err - } - - response := core.MakeSucessResponse(200, "Server Updated", server, nil, nil) - return response, nil -} diff --git a/gRPC/v1/server/server.pb.go b/gRPC/v1/server/server.pb.go deleted file mode 100644 index 8828a1d..0000000 --- a/gRPC/v1/server/server.pb.go +++ /dev/null @@ -1,246 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.12 -// source: gRPC/v1/server/server.proto - -package server - -import ( - model "github.com/NetSepio/erebrus/model" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Empty struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *Empty) Reset() { - *x = Empty{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_server_server_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Empty) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Empty) ProtoMessage() {} - -func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_server_server_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Empty.ProtoReflect.Descriptor instead. -func (*Empty) Descriptor() ([]byte, []int) { - return file_gRPC_v1_server_server_proto_rawDescGZIP(), []int{0} -} - -type Config struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Config []byte `protobuf:"bytes,1,opt,name=Config,proto3" json:"Config,omitempty"` - Status int64 `protobuf:"varint,2,opt,name=status,proto3" json:"status,omitempty"` - Success bool `protobuf:"varint,3,opt,name=success,proto3" json:"success,omitempty"` - Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` -} - -func (x *Config) Reset() { - *x = Config{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_server_server_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Config) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Config) ProtoMessage() {} - -func (x *Config) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_server_server_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Config.ProtoReflect.Descriptor instead. -func (*Config) Descriptor() ([]byte, []int) { - return file_gRPC_v1_server_server_proto_rawDescGZIP(), []int{1} -} - -func (x *Config) GetConfig() []byte { - if x != nil { - return x.Config - } - return nil -} - -func (x *Config) GetStatus() int64 { - if x != nil { - return x.Status - } - return 0 -} - -func (x *Config) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *Config) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -var File_gRPC_v1_server_server_proto protoreflect.FileDescriptor - -var file_gRPC_v1_server_server_proto_rawDesc = []byte{ - 0x0a, 0x1b, 0x67, 0x52, 0x50, 0x43, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, - 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x1a, 0x11, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x22, 0x68, 0x0a, 0x06, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x06, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, - 0x75, 0x63, 0x63, 0x65, 0x73, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x32, 0xaf, 0x01, 0x0a, 0x0d, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x36, 0x0a, - 0x14, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x72, 0x6d, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0d, 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0f, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x36, 0x0a, 0x15, 0x47, 0x65, 0x74, 0x53, 0x65, 0x72, 0x76, - 0x65, 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x69, 0x6f, 0x6e, 0x12, 0x0d, - 0x2e, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0e, 0x2e, - 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x2e, 0x0a, - 0x0c, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x0d, 0x2e, - 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x1a, 0x0f, 0x2e, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x34, 0x5a, - 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x65, 0x4c, - 0x61, 0x7a, 0x61, 0x72, 0x75, 0x73, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x72, - 0x65, 0x62, 0x72, 0x75, 0x73, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x3b, 0x73, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_gRPC_v1_server_server_proto_rawDescOnce sync.Once - file_gRPC_v1_server_server_proto_rawDescData = file_gRPC_v1_server_server_proto_rawDesc -) - -func file_gRPC_v1_server_server_proto_rawDescGZIP() []byte { - file_gRPC_v1_server_server_proto_rawDescOnce.Do(func() { - file_gRPC_v1_server_server_proto_rawDescData = protoimpl.X.CompressGZIP(file_gRPC_v1_server_server_proto_rawDescData) - }) - return file_gRPC_v1_server_server_proto_rawDescData -} - -var file_gRPC_v1_server_server_proto_msgTypes = make([]protoimpl.MessageInfo, 2) -var file_gRPC_v1_server_server_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: server.Empty - (*Config)(nil), // 1: server.Config - (*model.Server)(nil), // 2: model.Server - (*model.Response)(nil), // 3: model.Response -} -var file_gRPC_v1_server_server_proto_depIdxs = []int32{ - 0, // 0: server.ServerService.GetServerInformation:input_type -> server.Empty - 0, // 1: server.ServerService.GetServerConfiguraion:input_type -> server.Empty - 2, // 2: server.ServerService.UpdateServer:input_type -> model.Server - 3, // 3: server.ServerService.GetServerInformation:output_type -> model.Response - 1, // 4: server.ServerService.GetServerConfiguraion:output_type -> server.Config - 3, // 5: server.ServerService.UpdateServer:output_type -> model.Response - 3, // [3:6] is the sub-list for method output_type - 0, // [0:3] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_gRPC_v1_server_server_proto_init() } -func file_gRPC_v1_server_server_proto_init() { - if File_gRPC_v1_server_server_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_gRPC_v1_server_server_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_gRPC_v1_server_server_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Config); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_gRPC_v1_server_server_proto_rawDesc, - NumEnums: 0, - NumMessages: 2, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_gRPC_v1_server_server_proto_goTypes, - DependencyIndexes: file_gRPC_v1_server_server_proto_depIdxs, - MessageInfos: file_gRPC_v1_server_server_proto_msgTypes, - }.Build() - File_gRPC_v1_server_server_proto = out.File - file_gRPC_v1_server_server_proto_rawDesc = nil - file_gRPC_v1_server_server_proto_goTypes = nil - file_gRPC_v1_server_server_proto_depIdxs = nil -} diff --git a/gRPC/v1/server/server.proto b/gRPC/v1/server/server.proto deleted file mode 100644 index 7875d91..0000000 --- a/gRPC/v1/server/server.proto +++ /dev/null @@ -1,24 +0,0 @@ -syntax="proto3"; - -import "model/model.proto"; - -package server; - -option go_package = "github.com/NetSepio/erebrus/server;server"; - -message Empty{ - -} - -message Config{ - bytes Config=1; - int64 status=2; - bool success=3; - string error=4; -} - -service ServerService{ - rpc GetServerInformation(Empty) returns (model.Response); - rpc GetServerConfiguraion(Empty) returns(Config); - rpc UpdateServer(model.Server) returns (model.Response); -} \ No newline at end of file diff --git a/gRPC/v1/server/server_grpc.pb.go b/gRPC/v1/server/server_grpc.pb.go deleted file mode 100644 index df3e1e9..0000000 --- a/gRPC/v1/server/server_grpc.pb.go +++ /dev/null @@ -1,178 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.12 -// source: gRPC/v1/server/server.proto - -package server - -import ( - context "context" - model "github.com/NetSepio/erebrus/model" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// ServerServiceClient is the client API for ServerService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type ServerServiceClient interface { - GetServerInformation(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*model.Response, error) - GetServerConfiguraion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Config, error) - UpdateServer(ctx context.Context, in *model.Server, opts ...grpc.CallOption) (*model.Response, error) -} - -type serverServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewServerServiceClient(cc grpc.ClientConnInterface) ServerServiceClient { - return &serverServiceClient{cc} -} - -func (c *serverServiceClient) GetServerInformation(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/server.ServerService/GetServerInformation", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serverServiceClient) GetServerConfiguraion(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Config, error) { - out := new(Config) - err := c.cc.Invoke(ctx, "/server.ServerService/GetServerConfiguraion", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *serverServiceClient) UpdateServer(ctx context.Context, in *model.Server, opts ...grpc.CallOption) (*model.Response, error) { - out := new(model.Response) - err := c.cc.Invoke(ctx, "/server.ServerService/UpdateServer", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// ServerServiceServer is the server API for ServerService service. -// All implementations must embed UnimplementedServerServiceServer -// for forward compatibility -type ServerServiceServer interface { - GetServerInformation(context.Context, *Empty) (*model.Response, error) - GetServerConfiguraion(context.Context, *Empty) (*Config, error) - UpdateServer(context.Context, *model.Server) (*model.Response, error) - mustEmbedUnimplementedServerServiceServer() -} - -// UnimplementedServerServiceServer must be embedded to have forward compatible implementations. -type UnimplementedServerServiceServer struct { -} - -func (UnimplementedServerServiceServer) GetServerInformation(context.Context, *Empty) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetServerInformation not implemented") -} -func (UnimplementedServerServiceServer) GetServerConfiguraion(context.Context, *Empty) (*Config, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetServerConfiguraion not implemented") -} -func (UnimplementedServerServiceServer) UpdateServer(context.Context, *model.Server) (*model.Response, error) { - return nil, status.Errorf(codes.Unimplemented, "method UpdateServer not implemented") -} -func (UnimplementedServerServiceServer) mustEmbedUnimplementedServerServiceServer() {} - -// UnsafeServerServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to ServerServiceServer will -// result in compilation errors. -type UnsafeServerServiceServer interface { - mustEmbedUnimplementedServerServiceServer() -} - -func RegisterServerServiceServer(s grpc.ServiceRegistrar, srv ServerServiceServer) { - s.RegisterService(&ServerService_ServiceDesc, srv) -} - -func _ServerService_GetServerInformation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServerServiceServer).GetServerInformation(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/server.ServerService/GetServerInformation", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServerServiceServer).GetServerInformation(ctx, req.(*Empty)) - } - return interceptor(ctx, in, info, handler) -} - -func _ServerService_GetServerConfiguraion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServerServiceServer).GetServerConfiguraion(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/server.ServerService/GetServerConfiguraion", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServerServiceServer).GetServerConfiguraion(ctx, req.(*Empty)) - } - return interceptor(ctx, in, info, handler) -} - -func _ServerService_UpdateServer_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(model.Server) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(ServerServiceServer).UpdateServer(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/server.ServerService/UpdateServer", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(ServerServiceServer).UpdateServer(ctx, req.(*model.Server)) - } - return interceptor(ctx, in, info, handler) -} - -// ServerService_ServiceDesc is the grpc.ServiceDesc for ServerService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var ServerService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "server.ServerService", - HandlerType: (*ServerServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetServerInformation", - Handler: _ServerService_GetServerInformation_Handler, - }, - { - MethodName: "GetServerConfiguraion", - Handler: _ServerService_GetServerConfiguraion_Handler, - }, - { - MethodName: "UpdateServer", - Handler: _ServerService_UpdateServer_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "gRPC/v1/server/server.proto", -} diff --git a/gRPC/v1/status/status.go b/gRPC/v1/status/status.go deleted file mode 100644 index 41e36ef..0000000 --- a/gRPC/v1/status/status.go +++ /dev/null @@ -1,27 +0,0 @@ -package status - -import ( - "context" - "errors" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" - log "github.com/sirupsen/logrus" -) - -type StatusService struct { - UnimplementedStatusServiceServer -} - -func (s *StatusService) GetStatus(ctx context.Context, request *Empty) (*model.Status, error) { - log.WithFields(util.StandardFieldsGRPC).Info("Request For Server Status") - status, err := core.GetServerStatus() - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("Failed to get Server Status") - return nil, errors.New(err.Error()) - } - return status, nil -} diff --git a/gRPC/v1/status/status.pb.go b/gRPC/v1/status/status.pb.go deleted file mode 100644 index 1018c1a..0000000 --- a/gRPC/v1/status/status.pb.go +++ /dev/null @@ -1,143 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.30.0 -// protoc v3.21.12 -// source: gRPC/v1/status/status.proto - -package status - -import ( - model "github.com/NetSepio/erebrus/model" - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Empty struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields -} - -func (x *Empty) Reset() { - *x = Empty{} - if protoimpl.UnsafeEnabled { - mi := &file_gRPC_v1_status_status_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Empty) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Empty) ProtoMessage() {} - -func (x *Empty) ProtoReflect() protoreflect.Message { - mi := &file_gRPC_v1_status_status_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Empty.ProtoReflect.Descriptor instead. -func (*Empty) Descriptor() ([]byte, []int) { - return file_gRPC_v1_status_status_proto_rawDescGZIP(), []int{0} -} - -var File_gRPC_v1_status_status_proto protoreflect.FileDescriptor - -var file_gRPC_v1_status_status_proto_rawDesc = []byte{ - 0x0a, 0x1b, 0x67, 0x52, 0x50, 0x43, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x73, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x1a, 0x11, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, - 0x79, 0x32, 0x3a, 0x0a, 0x0d, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x12, 0x29, 0x0a, 0x09, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, - 0x0d, 0x2e, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x0d, - 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x42, 0x34, 0x5a, - 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x54, 0x68, 0x65, 0x4c, - 0x61, 0x7a, 0x61, 0x72, 0x75, 0x73, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x2f, 0x65, 0x72, - 0x65, 0x62, 0x72, 0x75, 0x73, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x3b, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_gRPC_v1_status_status_proto_rawDescOnce sync.Once - file_gRPC_v1_status_status_proto_rawDescData = file_gRPC_v1_status_status_proto_rawDesc -) - -func file_gRPC_v1_status_status_proto_rawDescGZIP() []byte { - file_gRPC_v1_status_status_proto_rawDescOnce.Do(func() { - file_gRPC_v1_status_status_proto_rawDescData = protoimpl.X.CompressGZIP(file_gRPC_v1_status_status_proto_rawDescData) - }) - return file_gRPC_v1_status_status_proto_rawDescData -} - -var file_gRPC_v1_status_status_proto_msgTypes = make([]protoimpl.MessageInfo, 1) -var file_gRPC_v1_status_status_proto_goTypes = []interface{}{ - (*Empty)(nil), // 0: status.Empty - (*model.Status)(nil), // 1: model.Status -} -var file_gRPC_v1_status_status_proto_depIdxs = []int32{ - 0, // 0: status.StatusService.GetStatus:input_type -> status.Empty - 1, // 1: status.StatusService.GetStatus:output_type -> model.Status - 1, // [1:2] is the sub-list for method output_type - 0, // [0:1] is the sub-list for method input_type - 0, // [0:0] is the sub-list for extension type_name - 0, // [0:0] is the sub-list for extension extendee - 0, // [0:0] is the sub-list for field type_name -} - -func init() { file_gRPC_v1_status_status_proto_init() } -func file_gRPC_v1_status_status_proto_init() { - if File_gRPC_v1_status_status_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_gRPC_v1_status_status_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_gRPC_v1_status_status_proto_rawDesc, - NumEnums: 0, - NumMessages: 1, - NumExtensions: 0, - NumServices: 1, - }, - GoTypes: file_gRPC_v1_status_status_proto_goTypes, - DependencyIndexes: file_gRPC_v1_status_status_proto_depIdxs, - MessageInfos: file_gRPC_v1_status_status_proto_msgTypes, - }.Build() - File_gRPC_v1_status_status_proto = out.File - file_gRPC_v1_status_status_proto_rawDesc = nil - file_gRPC_v1_status_status_proto_goTypes = nil - file_gRPC_v1_status_status_proto_depIdxs = nil -} diff --git a/gRPC/v1/status/status.proto b/gRPC/v1/status/status.proto deleted file mode 100644 index d362182..0000000 --- a/gRPC/v1/status/status.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax="proto3"; - -package status; - -option go_package = "github.com/NetSepio/erebrus/status;status"; - -import "model/model.proto"; - -message Empty{ -} - -service StatusService { - rpc GetStatus(Empty) returns (model.Status); -} \ No newline at end of file diff --git a/gRPC/v1/status/status_grpc.pb.go b/gRPC/v1/status/status_grpc.pb.go deleted file mode 100644 index 0d29540..0000000 --- a/gRPC/v1/status/status_grpc.pb.go +++ /dev/null @@ -1,106 +0,0 @@ -// Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.21.12 -// source: gRPC/v1/status/status.proto - -package status - -import ( - context "context" - model "github.com/NetSepio/erebrus/model" - grpc "google.golang.org/grpc" - codes "google.golang.org/grpc/codes" - status "google.golang.org/grpc/status" -) - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 - -// StatusServiceClient is the client API for StatusService service. -// -// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. -type StatusServiceClient interface { - GetStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*model.Status, error) -} - -type statusServiceClient struct { - cc grpc.ClientConnInterface -} - -func NewStatusServiceClient(cc grpc.ClientConnInterface) StatusServiceClient { - return &statusServiceClient{cc} -} - -func (c *statusServiceClient) GetStatus(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*model.Status, error) { - out := new(model.Status) - err := c.cc.Invoke(ctx, "/status.StatusService/GetStatus", in, out, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// StatusServiceServer is the server API for StatusService service. -// All implementations must embed UnimplementedStatusServiceServer -// for forward compatibility -type StatusServiceServer interface { - GetStatus(context.Context, *Empty) (*model.Status, error) - mustEmbedUnimplementedStatusServiceServer() -} - -// UnimplementedStatusServiceServer must be embedded to have forward compatible implementations. -type UnimplementedStatusServiceServer struct { -} - -func (UnimplementedStatusServiceServer) GetStatus(context.Context, *Empty) (*model.Status, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetStatus not implemented") -} -func (UnimplementedStatusServiceServer) mustEmbedUnimplementedStatusServiceServer() {} - -// UnsafeStatusServiceServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to StatusServiceServer will -// result in compilation errors. -type UnsafeStatusServiceServer interface { - mustEmbedUnimplementedStatusServiceServer() -} - -func RegisterStatusServiceServer(s grpc.ServiceRegistrar, srv StatusServiceServer) { - s.RegisterService(&StatusService_ServiceDesc, srv) -} - -func _StatusService_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(Empty) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(StatusServiceServer).GetStatus(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/status.StatusService/GetStatus", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(StatusServiceServer).GetStatus(ctx, req.(*Empty)) - } - return interceptor(ctx, in, info, handler) -} - -// StatusService_ServiceDesc is the grpc.ServiceDesc for StatusService service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var StatusService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "status.StatusService", - HandlerType: (*StatusServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetStatus", - Handler: _StatusService_GetStatus_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "gRPC/v1/status/status.proto", -} diff --git a/gRPC/v1/v1.go b/gRPC/v1/v1.go deleted file mode 100644 index 51fa5de..0000000 --- a/gRPC/v1/v1.go +++ /dev/null @@ -1,33 +0,0 @@ -package v1 - -import ( - "github.com/NetSepio/erebrus/gRPC/v1/authenticate/paseto" - "github.com/NetSepio/erebrus/gRPC/v1/authenticate/selector" - "github.com/NetSepio/erebrus/gRPC/v1/client" - "github.com/NetSepio/erebrus/gRPC/v1/server" - "github.com/NetSepio/erebrus/gRPC/v1/status" - "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth" - selector_middleware "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/selector" - "google.golang.org/grpc" -) - -func Initialize() *grpc.Server { - - //get the instance of server and client services - ServerService := &server.ServerService{} - ClientService := &client.ClientService{} - StatusService := &status.StatusService{} - - //creating a new gRPC server - grpc_server := grpc.NewServer( - grpc.ChainStreamInterceptor(selector_middleware.StreamServerInterceptor( - auth.StreamServerInterceptor(paseto.PASETO), selector_middleware.MatchFunc(selector.LoginSkip))), - grpc.ChainUnaryInterceptor(selector_middleware.UnaryServerInterceptor( - auth.UnaryServerInterceptor(paseto.PASETO), selector_middleware.MatchFunc(selector.LoginSkip))), - ) - server.RegisterServerServiceServer(grpc_server, ServerService) - client.RegisterClientServiceServer(grpc_server, ClientService) - status.RegisterStatusServiceServer(grpc_server, StatusService) - - return grpc_server -} diff --git a/go.mod b/go.mod index a9b8156..ab5eedf 100644 --- a/go.mod +++ b/go.mod @@ -1,98 +1,81 @@ module github.com/NetSepio/erebrus -go 1.23.4 +go 1.25.0 require ( - aidanwoods.dev/go-paseto v1.5.3 github.com/blocto/solana-go-sdk v1.30.0 - github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e - github.com/docker/docker v27.5.0+incompatible - github.com/ethereum/go-ethereum v1.15.0 - github.com/gin-contrib/cors v1.7.3 - github.com/gin-contrib/static v1.1.3 + github.com/ethereum/go-ethereum v1.14.12 github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 + github.com/gorilla/websocket v1.5.3 github.com/joho/godotenv v1.5.1 github.com/libp2p/go-libp2p v0.38.1 github.com/libp2p/go-libp2p-kad-dht v0.25.2 - github.com/libp2p/go-libp2p-pubsub v0.12.0 - github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 + github.com/miekg/dns v1.1.63 github.com/mr-tron/base58 v1.2.0 github.com/multiformats/go-multiaddr v0.14.0 - github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/shirou/gopsutil/v3 v3.24.5 - github.com/showwin/speedtest-go v1.7.10 - github.com/sirupsen/logrus v1.9.3 - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e - github.com/spf13/cobra v1.9.1 + github.com/prometheus/client_golang v1.23.2 + github.com/sagernet/sing v0.6.10 + github.com/sagernet/sing-box v1.11.15 github.com/tyler-smith/go-bip32 v1.0.0 github.com/tyler-smith/go-bip39 v1.1.0 - golang.org/x/crypto v0.32.0 + github.com/vk-rv/pvx v0.0.0-20210912195928-ac00bc32f6e7 + golang.org/x/crypto v0.51.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 - google.golang.org/grpc v1.69.4 - google.golang.org/protobuf v1.36.3 + modernc.org/sqlite v1.52.0 ) require ( - aidanwoods.dev/go-result v0.1.0 // indirect - cloud.google.com/go/compute/metadata v0.5.2 // indirect filippo.io/edwards25519 v1.0.0-rc.1 // indirect github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e // indirect github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec // indirect - github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/VictoriaMetrics/fastcache v1.12.2 // indirect + github.com/andybalholm/brotli v1.0.6 // indirect github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect - github.com/bits-and-blooms/bitset v1.17.0 // indirect github.com/bytedance/sonic v1.12.6 // indirect github.com/bytedance/sonic/loader v0.2.1 // indirect + github.com/caddyserver/certmagic v0.20.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.3.7 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect - github.com/consensys/bavard v0.1.22 // indirect - github.com/consensys/gnark-crypto v0.14.0 // indirect github.com/containerd/cgroups v1.1.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect - github.com/crate-crypto/go-kzg-4844 v1.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c // indirect - github.com/deckarep/golang-set/v2 v2.6.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/docker/go-units v0.5.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/elastic/gosigar v0.14.3 // indirect - github.com/ethereum/c-kzg-4844 v1.0.0 // indirect - github.com/ethereum/go-verkle v0.2.2 // indirect github.com/flynn/noise v1.1.0 // indirect github.com/francoispqt/gojay v1.2.13 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.7 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-chi/chi/v5 v5.2.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.23.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/gobwas/httphead v0.1.0 // indirect + github.com/gobwas/pool v0.2.1 // indirect github.com/goccy/go-json v0.10.4 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect - github.com/gofrs/flock v0.8.1 // indirect + github.com/gofrs/uuid/v5 v5.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect - github.com/google/go-cmp v0.6.0 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/go-cmp v0.7.0 // indirect github.com/google/gopacket v1.1.19 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/gorilla/websocket v1.5.3 // indirect + github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/holiman/bloomfilter/v2 v2.0.3 // indirect - github.com/holiman/uint256 v1.3.2 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/holiman/uint256 v1.3.1 // indirect github.com/huin/goupnp v1.3.0 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/ipfs/boxo v0.24.0 // indirect github.com/ipfs/go-cid v0.4.1 // indirect github.com/ipfs/go-datastore v0.6.0 // indirect @@ -104,10 +87,13 @@ require ( github.com/jbenet/goprocess v0.1.4 // indirect github.com/josharian/native v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.11 // indirect + github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/koron/go-ssdp v0.0.4 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/libdns/alidns v1.0.3 // indirect + github.com/libdns/cloudflare v0.1.1 // indirect + github.com/libdns/libdns v0.2.2 // indirect github.com/libp2p/go-buffer-pool v0.1.0 // indirect github.com/libp2p/go-cidranger v1.1.0 // indirect github.com/libp2p/go-flow-metrics v0.2.0 // indirect @@ -120,18 +106,17 @@ require ( github.com/libp2p/go-netroute v0.2.2 // indirect github.com/libp2p/go-reuseport v0.4.0 // indirect github.com/libp2p/go-yamux/v4 v4.0.1 // indirect - github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/logrusorgru/aurora v2.0.3+incompatible // indirect github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mdlayher/genetlink v1.3.2 // indirect github.com/mdlayher/netlink v1.7.2 // indirect github.com/mdlayher/socket v0.5.1 // indirect - github.com/miekg/dns v1.1.62 // indirect + github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 // indirect + github.com/mholt/acmez v1.2.0 // indirect github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b // indirect github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc // indirect github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mmcloughlin/addchain v0.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/multiformats/go-base32 v0.1.0 // indirect @@ -144,10 +129,11 @@ require ( github.com/multiformats/go-multistream v0.6.0 // indirect github.com/multiformats/go-varint v0.0.7 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect github.com/onsi/ginkgo/v2 v2.22.0 // indirect github.com/opencontainers/runtime-spec v1.2.0 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect + github.com/oschwald/maxminddb-golang v1.12.0 // indirect github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pion/datachannel v1.5.10 // indirect @@ -170,50 +156,69 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/polydawn/refmt v0.89.0 // indirect - github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect - github.com/prometheus/client_golang v1.20.5 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.61.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect github.com/quic-go/qpack v0.5.1 // indirect + github.com/quic-go/qtls-go1-20 v0.4.1 // indirect github.com/quic-go/quic-go v0.48.2 // indirect github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 // indirect github.com/raulk/go-watchdog v1.3.0 // indirect - github.com/rivo/uniseg v0.2.0 // indirect - github.com/shirou/gopsutil v3.21.11+incompatible // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a // indirect + github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 // indirect + github.com/sagernet/fswatch v0.1.1 // indirect + github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff // indirect + github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a // indirect + github.com/sagernet/nftables v0.3.0-beta.4 // indirect + github.com/sagernet/quic-go v0.49.0-beta.1 // indirect + github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 // indirect + github.com/sagernet/sing-dns v0.4.6 // indirect + github.com/sagernet/sing-mux v0.3.2 // indirect + github.com/sagernet/sing-quic v0.4.4 // indirect + github.com/sagernet/sing-tun v0.6.9 // indirect + github.com/sagernet/sing-vmess v0.2.3 // indirect + github.com/sagernet/smux v1.5.34-mod.2 // indirect + github.com/sagernet/utls v1.6.7 // indirect + github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 // indirect github.com/spaolacci/murmur3 v1.1.0 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/stretchr/testify v1.10.0 // indirect - github.com/supranational/blst v0.3.13 // indirect - github.com/tklauser/go-sysconf v0.3.12 // indirect - github.com/tklauser/numcpus v0.6.1 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/vishvananda/netns v0.0.4 // indirect github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 // indirect github.com/wlynxg/anet v0.0.5 // indirect - github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zeebo/blake3 v0.2.3 // indirect go.opencensus.io v0.24.0 // indirect - go.opentelemetry.io/otel v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.31.0 // indirect - go.opentelemetry.io/otel/trace v1.31.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect go.uber.org/dig v1.18.0 // indirect go.uber.org/fx v1.23.0 // indirect go.uber.org/mock v0.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect + go.yaml.in/yaml/v2 v2.4.2 // indirect + go4.org/netipx v0.0.0-20231129151722-fdeea329fbba // indirect golang.org/x/arch v0.12.0 // indirect golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect - golang.org/x/mod v0.22.0 // indirect - golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.29.0 // indirect + golang.org/x/mod v0.35.0 // indirect + golang.org/x/net v0.54.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/time v0.7.0 // indirect + golang.org/x/tools v0.44.0 // indirect golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 // indirect - gonum.org/v1/gonum v0.15.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + gonum.org/v1/gonum v0.17.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/grpc v1.81.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/blake3 v1.3.0 // indirect - rsc.io/tmplfunc v0.0.3 // indirect + modernc.org/libc v1.72.3 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect ) diff --git a/go.sum b/go.sum index 1018a0d..4c48eef 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,7 @@ -aidanwoods.dev/go-paseto v1.5.3 h1:y3pRY9MLWBhfO9VuCN0Bkyxa7Xmkt5coipYJfaOZgOs= -aidanwoods.dev/go-paseto v1.5.3/go.mod h1://T4uDrCXnzls7pKeCXaQ/zC3xv0KtgGMk4wnlOAHSs= -aidanwoods.dev/go-result v0.1.0 h1:y/BMIRX6q3HwaorX1Wzrjo3WUdiYeyWbvGe18hKS3K8= -aidanwoods.dev/go-result v0.1.0/go.mod h1:yridkWghM7AXSFA6wzx0IbsurIm1Lhuro3rYef8FBHM= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= -cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= -cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= @@ -16,18 +10,12 @@ filippo.io/edwards25519 v1.0.0-rc.1 h1:m0VOOB23frXZvAOK44usCgLWvtsxIoMCTBGJZlpmG filippo.io/edwards25519 v1.0.0-rc.1/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= -github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e h1:ahyvB3q25YnZWly5Gq1ekg6jcmWaGj/vG/MhF4aisoc= github.com/FactomProject/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:kGUqhHd//musdITWjFvNTHn90WG9bMLBEPQZ17Cmlpw= github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec h1:1Qb69mGp/UtRPn422BH4/Y4Q3SLUrD9KHuDkm8iodFc= github.com/FactomProject/btcutilecc v0.0.0-20130527213604-d3a63a5752ec/go.mod h1:CD8UlnlLDiqb36L110uqiP2iSflVjx9g/3U9hCI4q2U= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/VictoriaMetrics/fastcache v1.12.2 h1:N0y9ASrJ0F6h0QaC3o6uJb3NIZ9VKLjCM7NQbSmF7WI= -github.com/VictoriaMetrics/fastcache v1.12.2/go.mod h1:AmC+Nzz1+3G2eCPapF6UcsnkThDcMsQicp4xDukwJYI= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= +github.com/andybalholm/brotli v1.0.6 h1:Yf9fFpf49Zrxb9NlQaluyE92/+X7UVHlhMNJN2sxfOI= +github.com/andybalholm/brotli v1.0.6/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= @@ -36,8 +24,6 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bits-and-blooms/bitset v1.17.0 h1:1X2TS7aHz1ELcC0yU1y2stUs/0ig5oMU6STFZGrhvHI= -github.com/bits-and-blooms/bitset v1.17.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= github.com/blocto/solana-go-sdk v1.30.0 h1:GEh4GDjYk1lMhV/hqJDCyuDeCuc5dianbN33yxL88NU= github.com/blocto/solana-go-sdk v1.30.0/go.mod h1:Xoyhhb3hrGpEQ5rJps5a3OgMwDpmEhrd9bgzFKkkwMs= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= @@ -47,14 +33,15 @@ github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKz github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.1 h1:1GgorWTqf12TA8mma4DDSbaQigE2wOgQo7iCjjJv3+E= github.com/bytedance/sonic/loader v0.2.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/caddyserver/certmagic v0.20.0 h1:bTw7LcEZAh9ucYCRXyCpIrSAGplplI0vGYJ4BpCQ/Fc= +github.com/caddyserver/certmagic v0.20.0/go.mod h1:N4sXgpICQUskEWpj7zVzvWD41p3NYacrNoZYiRM2jTg= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/cp v0.1.0 h1:SE+dxFebS7Iik5LK0tsi1k9ZCxEaFX4AjQmoyA+1dJk= -github.com/cespare/cp v0.1.0/go.mod h1:SOGHArjBr4JWaSDEVpWpo/hNg6RoKrls6Oh40hiwW+s= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -62,22 +49,6 @@ github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQ github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e h1:0XBUw73chJ1VYSsfvcPvVT7auykAJce9FpRr10L6Qhw= github.com/cmars/basen v0.0.0-20150613233007-fe3947df716e/go.mod h1:P13beTBKr5Q18lJe1rIoLUqjM+CB1zYrRg44ZqGuQSA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I= -github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4= -github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= -github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= -github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA= -github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU= -github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30= -github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= -github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= -github.com/consensys/bavard v0.1.22 h1:Uw2CGvbXSZWhqK59X0VG/zOjpTFuOMcPLStrp1ihI0A= -github.com/consensys/bavard v0.1.22/go.mod h1:k/zVjHHC4B+PQy1Pg7fgvG3ALicQw540Crag8qx+dZs= -github.com/consensys/gnark-crypto v0.14.0 h1:DDBdl4HaBtdQsq/wfMwJvZNE80sHidrK3Nfrefatm0E= -github.com/consensys/gnark-crypto v0.14.0/go.mod h1:CU4UijNPsHawiVGNxe9co07FkzCeWHHrb1li/n1XoU0= github.com/containerd/cgroups v0.0.0-20201119153540-4cbc285b3327/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= @@ -87,31 +58,21 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a h1:W8mUrRp6NOVl3J+MYp5kPMoUZPp7aOYHtaua31lwRHg= -github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a/go.mod h1:sTwzHBvIzm2RfVCGNEBZgRyjwK40bVoun3ZnGOCafNM= -github.com/crate-crypto/go-kzg-4844 v1.1.0 h1:EN/u9k2TF6OWSHrCCDBBU6GLNMq88OspHHlMnHfoyU4= -github.com/crate-crypto/go-kzg-4844 v1.1.0/go.mod h1:JolLjpSff1tCCJKaJx4psrlEdlXuJEC996PL3tTAFks= -github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e h1:5jVSh2l/ho6ajWhSPNN84eHEdq3dp0T7+f6r3Tc6hsk= -github.com/danielkov/gin-helmet v0.0.0-20171108135313-1387e224435e/go.mod h1:IJgIiGUARc4aOr4bOQ85klmjsShkEEfiRc6q/yBSfo8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c h1:pFUpOrbxDR6AkioZ1ySsx5yxlDQZ8stG2b88gTPxgJU= github.com/davidlazar/go-crypto v0.0.0-20200604182044-b73af7476f6c/go.mod h1:6UhI8N9EjYm1c2odKpFpAYeR8dsBeM7PtzQhRgxRr9U= -github.com/deckarep/golang-set/v2 v2.6.0 h1:XfcQbWM1LlMB8BsJ8N9vW5ehnnPVIw0je80NsVHagjM= -github.com/deckarep/golang-set/v2 v2.6.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y= github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= -github.com/docker/docker v27.5.0+incompatible h1:um++2NcQtGRTz5eEgO6aJimo6/JxrTXC941hd05JO6U= -github.com/docker/docker v27.5.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/elastic/gosigar v0.12.0/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/uo= github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs= @@ -119,12 +80,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA= -github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= -github.com/ethereum/go-ethereum v1.15.0 h1:LLb2jCPsbJZcB4INw+E/MgzUX5wlR6SdwXcv09/1ME4= -github.com/ethereum/go-ethereum v1.15.0/go.mod h1:4q+4t48P2C03sjqGvTXix5lEOplf5dz4CTosbjt5tGs= -github.com/ethereum/go-verkle v0.2.2 h1:I2W0WjnrFUIzzVPwm8ykY+7pL2d4VhlsePn4j7cnFk8= -github.com/ethereum/go-verkle v0.2.2/go.mod h1:M3b90YRnzqKyyzBEWJGqj8Qff4IDeXnzFw0P9bFw3uk= +github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrTwcCQSe4= +github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flynn/noise v1.1.0 h1:KjPQoQCEFdZDiP03phOvGi11+SVVhBG2wOWAorLsstg= github.com/flynn/noise v1.1.0/go.mod h1:xbMo+0i6+IGbYdJhF31t2eR1BIU0CYc12+BNAKwUTag= @@ -133,29 +90,24 @@ github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiD github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= -github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps= -github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns= -github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-contrib/static v1.1.3 h1:WLOpkBtMDJ3gATFZgNJyVibFMio/UHonnueqJsQ0w4U= -github.com/gin-contrib/static v1.1.3/go.mod h1:zejpJ/YWp8cZj/6EpiL5f/+skv5daQTNwRx1E8Pci30= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -169,20 +121,22 @@ github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaC github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= +github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= -github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -202,10 +156,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= -github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -213,22 +166,18 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -241,30 +190,22 @@ github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORR github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0 h1:kQ0NI7W1B3HwiN5gAYtY+XFItDPbLBwYRxAqbFTyDes= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.2.0/go.mod h1:zrT2dxOAjNFPRGjTUe2Xmb4q4YdUwVvQFV6xiCSf+z0= github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE= -github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4 h1:X4egAf/gcS1zATw6wn4Ej8vjuVGxeHdan+bRb2ebyv4= -github.com/holiman/billy v0.0.0-20240216141850-2abb0c79d3c4/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc= -github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= -github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= -github.com/holiman/uint256 v1.3.2 h1:a9EgMPSC1AAaj1SZL5zIQD3WbwTuHrMGOerLjGmM/TA= -github.com/holiman/uint256 v1.3.2/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/holiman/uint256 v1.3.1 h1:JfTzmih28bittyHM8z360dCjIA9dbPIBlcTI6lmctQs= +github.com/holiman/uint256 v1.3.1/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ipfs/boxo v0.24.0 h1:D9gTU3QdxyjPMlJ6QfqhHTG3TIJPplKzjXLO2J30h9U= github.com/ipfs/boxo v0.24.0/go.mod h1:iP7xUPpHq2QAmVAjwtQvsNBTxTwLpFuy6ZpiRFwmzDA= github.com/ipfs/go-block-format v0.2.0 h1:ZqrkxBA2ICbDRbK8KJs/u0O3dlp6gmAuuXUJNiW1Ycs= @@ -307,9 +248,10 @@ github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfV github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= @@ -326,10 +268,15 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4= -github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/libdns/alidns v1.0.3 h1:LFHuGnbseq5+HCeGa1aW8awyX/4M2psB9962fdD2+yQ= +github.com/libdns/alidns v1.0.3/go.mod h1:e18uAG6GanfRhcJj6/tps2rCMzQJaYVcGKT+ELjdjGE= +github.com/libdns/cloudflare v0.1.1 h1:FVPfWwP8zZCqj268LZjmkDleXlHPlFU9KC4OJ3yn054= +github.com/libdns/cloudflare v0.1.1/go.mod h1:9VK91idpOjg6v7/WbjkEW49bSCxj00ALesIFDhJ8PBU= +github.com/libdns/libdns v0.2.0/go.mod h1:yQCXzk1lEZmmCPa857bnk4TsOiqYasqpyOEeSObbb40= +github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= +github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8= github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/libp2p/go-cidranger v1.1.0 h1:ewPN8EZ0dd1LSnrtuwd4709PXVcITVeuwbag38yPW7c= @@ -344,8 +291,6 @@ github.com/libp2p/go-libp2p-kad-dht v0.25.2 h1:FOIk9gHoe4YRWXTu8SY9Z1d0RILol0Trt github.com/libp2p/go-libp2p-kad-dht v0.25.2/go.mod h1:6za56ncRHYXX4Nc2vn8z7CZK0P4QiMcrn77acKLM2Oo= github.com/libp2p/go-libp2p-kbucket v0.6.4 h1:OjfiYxU42TKQSB8t8WYd8MKhYhMJeO2If+NiuKfb6iQ= github.com/libp2p/go-libp2p-kbucket v0.6.4/go.mod h1:jp6w82sczYaBsAypt5ayACcRJi0lgsba7o4TzJKEfWA= -github.com/libp2p/go-libp2p-pubsub v0.12.0 h1:PENNZjSfk8KYxANRlpipdS7+BfLmOl3L2E/6vSNjbdI= -github.com/libp2p/go-libp2p-pubsub v0.12.0/go.mod h1:Oi0zw9aw8/Y5GC99zt+Ef2gYAl+0nZlwdJonDyOz/sE= github.com/libp2p/go-libp2p-record v0.2.0 h1:oiNUOCWno2BFuxt3my4i1frNrt7PerzB3queqa1NkQ0= github.com/libp2p/go-libp2p-record v0.2.0/go.mod h1:I+3zMkvvg5m2OcSdoL0KPljyJyvNDFGKX7QdlpYUcwk= github.com/libp2p/go-libp2p-routing-helpers v0.7.4 h1:6LqS1Bzn5CfDJ4tzvP9uwh42IB7TJLNFJA6dEeGBv84= @@ -362,20 +307,15 @@ github.com/libp2p/go-reuseport v0.4.0 h1:nR5KU7hD0WxXCJbmw7r2rhRYruNRl2koHw8fQsc github.com/libp2p/go-reuseport v0.4.0/go.mod h1:ZtI03j/wO5hZVDFo2jKywN6bYKWLOy8Se6DrI2E1cLU= github.com/libp2p/go-yamux/v4 v4.0.1 h1:FfDR4S1wj6Bw2Pqbc8Uz7pCxeRBPbwsBbEdfwiCypkQ= github.com/libp2p/go-yamux/v4 v4.0.1/go.mod h1:NWjl8ZTLOGlozrXSOZ/HlfG++39iKNnM5wwmtQP1YB4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= -github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8= +github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd h1:br0buuQ854V8u83wA0rVZ8ttrq5CpaPZdvrK0LP2lOk= github.com/marten-seemann/tcp v0.0.0-20210406111302-dfbc87cc63fd/go.mod h1:QuCEs1Nt24+FYQEqAAncTDPJIuGs+LxK1MCiFL25pMU= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= -github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= @@ -383,9 +323,13 @@ github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/ github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= +github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422 h1:zGeQt3UyNydIVrMRB97AA5WsYEau/TyCnRtTf1yUmJY= +github.com/metacubex/tfo-go v0.0.0-20241231083714-66613d49c422/go.mod h1:l9oLnLoEXyGZ5RVLsh7QCC5XsouTUyKk4F2nLm2DHLw= +github.com/mholt/acmez v1.2.0 h1:1hhLxSgY5FvH5HCnGUuwbKY2VQVo8IU7rxXKSnZ7F30= +github.com/mholt/acmez v1.2.0/go.mod h1:VT9YwH1xgNX1kmYY89gY8xPJC84BFAisjo8Egigt4kE= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= -github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= -github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= +github.com/miekg/dns v1.1.63 h1:8M5aAw6OMZfFXTT7K5V0Eu5YiiL8l7nUAkyN6C9YwaY= +github.com/miekg/dns v1.1.63/go.mod h1:6NGHfjhpmr5lt3XPLuyfDJi5AXbNIPM9PY6H6sF1Nfs= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721 h1:RlZweED6sbSArvlE924+mUcZuXKLBHA35U7LN621Bws= github.com/mikioh/ipaddr v0.0.0-20190404000644-d465c8ab6721/go.mod h1:Ickgr2WtCLZ2MDGd4Gr0geeCH5HybhRJbonOgQpvSxc= github.com/mikioh/tcp v0.0.0-20190314235350-803a9b46060c h1:bzE/A84HN25pxAuk9Eej1Kz9OUelF97nAc82bDquQI8= @@ -394,18 +338,10 @@ github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b h1:z78hV3sbSMAUoyUM github.com/mikioh/tcpinfo v0.0.0-20190314235526-30a79bb1804b/go.mod h1:lxPUiZwKoFL8DUUmalo2yJJUCxbPKtm8OKfqr2/FTNU= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc h1:PTfri+PuQmWDqERdnNMiD9ZejrlswWrCpBEZgWOiTrc= github.com/mikioh/tcpopt v0.0.0-20190314235656-172688c1accc/go.mod h1:cGKTAVKx4SxOuR/czcZ/E2RSJ3sfHs8FpHhQ5CWMf9s= -github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g= github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ= github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/mapstructure v1.4.1 h1:CpVNEelQCZBooIPDn+AR3NpivK/TIKU8bDxdASFVQag= -github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A= -github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4= -github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= -github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= -github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -439,10 +375,10 @@ github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/n github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= @@ -453,8 +389,8 @@ github.com/opencontainers/runtime-spec v1.2.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/ github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= -github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= -github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= +github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= @@ -488,8 +424,6 @@ github.com/pion/srtp/v2 v2.0.20 h1:HNNny4s+OUmG280ETrCdgFndp4ufx3/uy85EawYEhTk= github.com/pion/srtp/v2 v2.0.20/go.mod h1:0KJQjA99A6/a0DOVTu1PhDSw0CXF2jTkqOoMg3ODqdA= github.com/pion/stun v0.6.1 h1:8lp6YejULeHBF8NmV8e2787BogQhduZugh5PdhDyyN4= github.com/pion/stun v0.6.1/go.mod h1:/hO7APkX4hZKu/D0f2lHzNyvdkTGtIy3NDmLR7kSz/8= -github.com/pion/stun/v2 v2.0.0 h1:A5+wXKLAypxQri59+tmQKVs7+l6mMM+3d+eER9ifRU0= -github.com/pion/stun/v2 v2.0.0/go.mod h1:22qRSh08fSEttYUmJZGlriq9+03jtVmXNODgLccj8GQ= github.com/pion/transport/v2 v2.2.1/go.mod h1:cXXWavvCnFF6McHTft3DWS9iic2Mftcz1Aq29pGcU5g= github.com/pion/transport/v2 v2.2.3/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= github.com/pion/transport/v2 v2.2.4/go.mod h1:q2U/tf9FEfnSBGSW6w5Qp5PFWRLRj3NjLhCCgpRK4p0= @@ -510,52 +444,74 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/polydawn/refmt v0.89.0 h1:ADJTApkvkeBZsN0tBTx8QjpD9JkmxbKp0cxfr9qszm4= github.com/polydawn/refmt v0.89.0/go.mod h1:/zvteZs/GwLtCgZ4BL6CBsk9IKIlexP43ObX9AxTqTw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= -github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ= -github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= +github.com/quic-go/qtls-go1-20 v0.4.1 h1:D33340mCNDAIKBqXuAvexTNMUByrYmFYVfKfDN5nfFs= +github.com/quic-go/qtls-go1-20 v0.4.1/go.mod h1:X9Nh97ZL80Z+bX/gUXMbipO6OxdiDi58b/fMC9mAL+k= github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66 h1:4WFk6u3sOT6pLa1kQ50ZVdm8BQFgJNA117cepZxtLIg= github.com/quic-go/webtransport-go v0.8.1-0.20241018022711-4ac2c9250e66/go.mod h1:Vp72IJajgeOL6ddqrAhmp7IM9zbTcgkQxD/YdxrVwMw= github.com/raulk/go-watchdog v1.3.0 h1:oUmdlHxdkXRJlwfG0O9omj8ukerm8MEQavSiDTEtBsk= github.com/raulk/go-watchdog v1.3.0/go.mod h1:fIvOnLbF0b0ZwkB9YU4mOW9Did//4vPZtDqv66NfsMU= -github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik= -github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a h1:+NkI2670SQpQWvkkD2QgdTuzQG263YZ+2emfpeyGqW0= +github.com/sagernet/bbolt v0.0.0-20231014093535-ea5cb2fe9f0a/go.mod h1:63s7jpZqcDAIpj8oI/1v4Izok+npJOHACFCU6+huCkM= +github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1 h1:YbmpqPQEMdlk9oFSKYWRqVuu9qzNiOayIonKmv1gCXY= +github.com/sagernet/cloudflare-tls v0.0.0-20231208171750-a4483c1b7cd1/go.mod h1:J2yAxTFPDjrDPhuAi9aWFz2L3ox9it4qAluBBbN0H5k= +github.com/sagernet/fswatch v0.1.1 h1:YqID+93B7VRfqIH3PArW/XpJv5H4OLEVWDfProGoRQs= +github.com/sagernet/fswatch v0.1.1/go.mod h1:nz85laH0mkQqJfaOrqPpkwtU1znMFNVTpT/5oRsVz/o= +github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff h1:mlohw3360Wg1BNGook/UHnISXhUx4Gd/3tVLs5T0nSs= +github.com/sagernet/gvisor v0.0.0-20241123041152-536d05261cff/go.mod h1:ehZwnT2UpmOWAHFL48XdBhnd4Qu4hN2O3Ji0us3ZHMw= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a h1:ObwtHN2VpqE0ZNjr6sGeT00J8uU7JF4cNUdb44/Duis= +github.com/sagernet/netlink v0.0.0-20240612041022-b9a21c07ac6a/go.mod h1:xLnfdiJbSp8rNqYEdIW/6eDO4mVoogml14Bh2hSiFpM= +github.com/sagernet/nftables v0.3.0-beta.4 h1:kbULlAwAC3jvdGAC1P5Fa3GSxVwQJibNenDW2zaXr8I= +github.com/sagernet/nftables v0.3.0-beta.4/go.mod h1:OQXAjvjNGGFxaTgVCSTRIhYB5/llyVDeapVoENYBDS8= +github.com/sagernet/quic-go v0.49.0-beta.1 h1:3LdoCzVVfYRibZns1tYWSIoB65fpTmrwy+yfK8DQ8Jk= +github.com/sagernet/quic-go v0.49.0-beta.1/go.mod h1:uesWD1Ihrldq1M3XtjuEvIUqi8WHNsRs71b3Lt1+p/U= +github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691 h1:5Th31OC6yj8byLGkEnIYp6grlXfo1QYUfiYFGjewIdc= +github.com/sagernet/reality v0.0.0-20230406110435-ee17307e7691/go.mod h1:B8lp4WkQ1PwNnrVMM6KyuFR20pU8jYBD+A4EhJovEXU= +github.com/sagernet/sing v0.6.9/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing v0.6.10 h1:Jey1tePgH9bjFuK1fQI3D9T+bPOQ4SdHMjuS4sYjDv4= +github.com/sagernet/sing v0.6.10/go.mod h1:ARkL0gM13/Iv5VCZmci/NuoOlePoIsW0m7BWfln/Hak= +github.com/sagernet/sing-box v1.11.15 h1:K4IK4U3DBQYmRJVYQE/NdsKW5ezqG3BMiF6sQGhWZHY= +github.com/sagernet/sing-box v1.11.15/go.mod h1:E/V6629+bJOeU3HvW0IUCjF0x4UFyq+82jMZoHxxozw= +github.com/sagernet/sing-dns v0.4.6 h1:mjZC0o6d5sQ1sraoOBbK3G3apCbuL8wWYwu2RNu5rbM= +github.com/sagernet/sing-dns v0.4.6/go.mod h1:dweQs54ng2YGzoJfz+F9dGuDNdP5pJ3PLeggnK5VWc8= +github.com/sagernet/sing-mux v0.3.2 h1:meZVFiiStvHThb/trcpAkCrmtJOuItG5Dzl1RRP5/NE= +github.com/sagernet/sing-mux v0.3.2/go.mod h1:pht8iFY4c9Xltj7rhVd208npkNaeCxzyXCgulDPLUDA= +github.com/sagernet/sing-quic v0.4.4 h1:qqOCLnzHbqKkj/wBcXEI3rhSyqoGlqDdv2S6mz2d/JA= +github.com/sagernet/sing-quic v0.4.4/go.mod h1:tqPa0/Wqa19MkkSlKVZZX5sHxtiDR9BROcn4ufcbVdY= +github.com/sagernet/sing-tun v0.6.9 h1:uP8O4Q7U9QesjWumgxd2S9fjT3c6aEPWl5RB6uBdVB8= +github.com/sagernet/sing-tun v0.6.9/go.mod h1:fisFCbC4Vfb6HqQNcwPJi2CDK2bf0Xapyz3j3t4cnHE= +github.com/sagernet/sing-vmess v0.2.3 h1:z6Ym8dnZG7k1fP3+54vz8G0tvRVJeOoTFFeUPwXTD44= +github.com/sagernet/sing-vmess v0.2.3/go.mod h1:jDAZ0A0St1zVRkyvhAPRySOFfhC+4SQtO5VYyeFotgA= +github.com/sagernet/smux v1.5.34-mod.2 h1:gkmBjIjlJ2zQKpLigOkFur5kBKdV6bNRoFu2WkltRQ4= +github.com/sagernet/smux v1.5.34-mod.2/go.mod h1:0KW0+R+ycvA2INW4gbsd7BNyg+HEfLIAxa5N02/28Zc= +github.com/sagernet/utls v1.6.7 h1:Ep3+aJ8FUGGta+II2IEVNUc3EDhaRCZINWkj/LloIA8= +github.com/sagernet/utls v1.6.7/go.mod h1:Uua1TKO/FFuAhLr9rkaVnnrTmmiItzDjv1BUb2+ERwM= +github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854 h1:6uUiZcDRnZSAegryaUGwPC/Fj13JSHwiTftrXhMmYOc= +github.com/sagernet/ws v0.0.0-20231204124109-acfe8907c854/go.mod h1:LtfoSK3+NG57tvnVEHgcuBW9ujgE8enPSgzgwStwCAA= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= -github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= -github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/showwin/speedtest-go v1.7.10 h1:9o5zb7KsuzZKn+IE2//z5btLKJ870JwO6ETayUkqRFw= -github.com/showwin/speedtest-go v1.7.10/go.mod h1:Ei7OCTmNPdWofMadzcfgq1rUO7mvJy9Jycj//G7vyfA= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= @@ -580,10 +536,6 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= -github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= github.com/smartystreets/assertions v1.2.0 h1:42S6lae5dvLc7BrLu/0ugRtcFVjoJNMC/N3yZFZkDFs= github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/goconvey v1.7.2 h1:9RBaZCeXEQ3UselpuwUQHltGVXvdwm6cv1hgR6gDIPg= @@ -592,10 +544,6 @@ github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:Udh github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -611,17 +559,9 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/supranational/blst v0.3.13 h1:AYeSxdOMacwu7FBmpfloBz5pbFXDmJL33RuwnKtmTjk= -github.com/supranational/blst v0.3.13/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7 h1:epCh84lMvA70Z7CTTCmYQn2CKbY8j86K7/FAIr141uY= -github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= -github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= -github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= -github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= -github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/tyler-smith/go-bip32 v1.0.0 h1:sDR9juArbUgX+bO/iblgZnMPeWY1KZMUC2AFUJdv5KE= @@ -631,12 +571,13 @@ github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3C github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/vishvananda/netns v0.0.4 h1:Oeaw1EM2JMxD51g9uhtC0D7erkIjgmj8+JZc26m1YX8= +github.com/vishvananda/netns v0.0.4/go.mod h1:SpkAiCQRtJ6TvvxPnOSyH3BMl6unz3xZlaprSwhNNJM= +github.com/vk-rv/pvx v0.0.0-20210912195928-ac00bc32f6e7 h1:vtVSgwci/6UByJ63SF6Q+FopoGygR8wioQ8YofF3gLs= +github.com/vk-rv/pvx v0.0.0-20210912195928-ac00bc32f6e7/go.mod h1:zawtmN8x0Tjv1NZ4t0LVs0xii/WtSMDwCrq7fSAOMLk= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0 h1:GDDkbFiaK8jsSDJfjId/PEGEShv6ugrt4kYsC5UIDaQ= github.com/warpfork/go-wish v0.0.0-20220906213052-39a1cc7a02d0/go.mod h1:x6AKhvSSexNrVSrViXSHUEbICjmGXhtgABaHIySUSGw= github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1 h1:EKhdznlJHPMoKr0XTrX+IlJs1LH3lyx2nfr1dOlZ79k= @@ -644,27 +585,31 @@ github.com/whyrusleeping/go-keyspace v0.0.0-20160322163242-5b898ac5add1/go.mod h github.com/wlynxg/anet v0.0.3/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= -github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= +github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= +github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= +github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= +github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= -go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= -go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= -go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= -go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= -go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= -go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= -go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= -go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= -go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/dig v1.18.0 h1:imUL1UiY0Mg4bqbFfsRQO5G4CGRBec/ZujWTvSVp3pw= @@ -685,7 +630,11 @@ go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba h1:0b9z3AuHCjxk0x/opv64kcgZLBseWJUpBw5I82+2U4M= +go4.org/netipx v0.0.0-20231129151722-fdeea329fbba/go.mod h1:PLyyIXexvUFg3Owu6p/WfdlivPbZJsZdgWZlrGope/Y= golang.org/x/arch v0.12.0 h1:UsYJhbzPYGsT0HbEdmYcqtCv8UNGvnaL561NnIUvaKg= golang.org/x/arch v0.12.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= @@ -698,13 +647,14 @@ golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200602180216-279210d13fed/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= -golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= -golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= +golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI= +golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= @@ -721,8 +671,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= +golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -746,14 +696,12 @@ golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= -golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -765,8 +713,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -774,21 +722,17 @@ golang.org/x/sys v0.0.0-20181029174526-d69651ed3497/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -796,10 +740,13 @@ golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4= +golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -815,12 +762,12 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= +golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -840,8 +787,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= +golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= +golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -850,8 +797,8 @@ golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uI golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10 h1:3GDAcqdIg1ozBNLgPy4SLT84nfcBjr6rhGtXYtrkWLU= golang.zx2c4.com/wireguard/wgctrl v0.0.0-20241231184526-a9ab2273dd10/go.mod h1:T97yPqesLiNrOYxkwmhMI0ZIlJDm+p0PMR8eRVeR5tQ= -gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ= -gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= @@ -866,8 +813,8 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= @@ -876,8 +823,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= -google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/grpc v1.81.1 h1:VnnIIZ88UzOOKLukQi+ImGz8O1Wdp8nAGGnvOfEIWQQ= +google.golang.org/grpc v1.81.1/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -887,8 +834,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -896,8 +843,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntN gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -917,8 +862,34 @@ launchpad.net/gocheck v0.0.0-20140225173054-000000000087 h1:Izowp2XBH6Ya6rv+hqbc launchpad.net/gocheck v0.0.0-20140225173054-000000000087/go.mod h1:hj7XX3B/0A+80Vse0e+BUHsHMTEhd0O4cpUHr/e/BUM= lukechampine.com/blake3 v1.3.0 h1:sJ3XhFINmHSrYCgl958hscfIa3bw8x4DqMP3u1YvoYE= lukechampine.com/blake3 v1.3.0/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k= +modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY= +modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ= +modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo= +modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU= +modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.52.0 h1:p4dhYh2tXZCiyaqHwRVJDjIGKWyXayiQpThxgDzJaxo= +modernc.org/sqlite v1.52.0/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= -rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= diff --git a/install-node.sh b/install-node.sh deleted file mode 100644 index 4f9e063..0000000 --- a/install-node.sh +++ /dev/null @@ -1,2004 +0,0 @@ -#!/usr/bin/env bash -# Initialize logging -# Function to display help text -print_help() { - cat < "$LOG_FILE" - log_info "=== Erebrus Node Installation Started ===" - log_info "Installation directory: $INSTALL_DIR" - log_info "Installation mode: ${INSTALLATION_MODE:-binary}" - log_info "Timestamp: $(date)" -} - -# Centralized logging functions -log_info() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $message" >> "$LOG_FILE" -} - -log_error() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $message" >> "$LOG_FILE" -} - -log_success() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $message" >> "$LOG_FILE" -} - -log_warning() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARNING] $message" >> "$LOG_FILE" -} - -format_status() { - local status="$1" - case "$status" in - "✔ Complete") echo "[\033[32m$status\033[0m]" ;; - "✘ Skipped") echo "[\033[33m$status\033[0m]" ;; - "✘ Failed") echo "[\033[31m$status\033[0m]" ;; - "In Progress")echo "[\033[34m$status\033[0m]" ;; - "Pending") echo "[$status]" ;; - *) echo "[$status]" ;; - esac -} - -display_header() { - # clear everything including scrollback buffer - printf '\033[2J\033[3J\033[H' - local header_buffer="" - - # Add the logo to buffer - header_buffer+="$(tput clear)$(tput civis)" - header_buffer+="\e[94m" - header_buffer+=$(cat << "EOF" -/$$$$$$$$ /$$ -| $$_____/ | $$ -| $$ /$$$$$$ /$$$$$$ | $$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$ -| $$$$$ /$$__ $$ /$$__ $$| $$__ $$ /$$__ $$| $$ | $$ /$$_____/ -| $$__/ | $$ \__/| $$$$$$$$| $$ \ $$| $$ \__/| $$ | $$| $$$$$$ -| $$ | $$ | $$_____/| $$ | $$| $$ | $$ | $$ \____ $$ -| $$$$$$$$| $$ | $$$$$$$| $$$$$$$/| $$ | $$$$$$/ /$$$$$$$/ -|________/|__/ \_______/|_______/ |__/ \______/ |_______/ -EOF -) - header_buffer+="\e[0m\n\n" - header_buffer+="\033[1m\033[4mErebrus Node Software Installer v1.1\033[0m\n" - # printf '─%.0s' {1..80} - - # Add separator and requirements - header_buffer+=$(printf '─%.0s' {1..100}) - header_buffer+="\n\e[1mRequirements:\e[0m\n" - header_buffer+="→ Erebrus node needs static public IP that is routable from internet & controlled by you.\n" - header_buffer+="→ Ports 9080, 9002, 9003, 51820, and 8088 must be open on your firewall and/or host system.\n" - header_buffer+=$(printf '─%.0s' {1..100}) - header_buffer+="\n" - - # Add status lines - header_buffer+="\033[1m🔧 Configure Node: \033[0m$(format_status "${STAGE_STATUS[0]}")\n" - header_buffer+="\033[1m📦 Install Packages: \033[0m$(format_status "${STAGE_STATUS[1]}")\n" - header_buffer+="\033[1m🚀 Run Node: \033[0m$(format_status "${STAGE_STATUS[2]}")\n" - - # Add final separator - header_buffer+=$(printf '─%.0s' {1..100}) - header_buffer+="\n" - - # Print the entire buffer at once - echo -e "$header_buffer" - - # Save cursor position after printing everything - tput sc - log_info "Header displayed successfully" -} - -# Function to clear all subprocess output -function clear_subprocess_output() { - # Go to saved position after header (status lines) - tput rc - - # Move down 4 lines (3 status lines + 1 separator line) - # tput cud - - # Clear everything from current position to end of screen - tput ed - log_info "Subprocess output cleared" -} - -# Function to show spinner -show_spinner() { - local pid=$1 - local msg=$2 - local delay=0.2 - local spinstr='|/-\' - - log_info "Starting subprocess: $msg" - - # Disable keyboard input echoing and save terminal settings - stty -echo - local old_tty_settings=$(stty -g) - - # Print the initial message with brackets and spinner placeholder - printf "\n%s [ ]" "$msg" - printf "\b\b" # Move cursor back inside the brackets - - # Start the spinner - while kill -0 $pid 2>/dev/null; do - local temp=${spinstr#?} - printf "%c\b" "$spinstr" # Print spinner char and move back - local spinstr=$temp${spinstr%"$temp"} - sleep $delay - - # Clear any input to prevent line breaks - read -t 0.1 -n 10000 discard 2>/dev/null || true - done - - # Get the exit status of the process - wait $pid - local exit_status=$? - - # Update with Done/Failed in brackets and add newline - if [ $exit_status -eq 0 ]; then - printf "\033[32mSuccess\033[0m]\n" - log_success "Subprocess completed: $msg" - else - printf "\033[31mFailed\033[0m]\n" - log_error "Subprocess failed: $msg (exit code: $exit_status)" - fi - - # Restore terminal settings - stty "$old_tty_settings" - stty echo - - return $exit_status -} - -# Function to check and create installation directory -create_install_directory() { - local base_dir="$1" - - if [[ -z "$base_dir" ]]; then - log_error "create_install_directory: base_dir parameter is required" - return 1 - fi - - # Set installation directory to base_dir/erebrus - INSTALL_DIR="${base_dir}/erebrus" - log_info "Setting installation directory to: $INSTALL_DIR" - - # Check if directory already exists - if [ -d "$INSTALL_DIR" ]; then - log_info "Installation directory already exists: $INSTALL_DIR" - return 0 - fi - - log_info "Creating installation directory: $INSTALL_DIR" - - # Try to create without sudo first - if mkdir -p "$INSTALL_DIR/wireguard" 2>/dev/null && chown -R $(id -u -n):$(id -g -n) "$INSTALL_DIR" 2>/dev/null; then - log_success "Installation directory created successfully: $INSTALL_DIR" - return 0 - else - # Try with sudo - printf "Creating directory '%s' requires elevated permissions.\n" "$INSTALL_DIR" - if sudo mkdir -p "$INSTALL_DIR/wireguard" && sudo chown -R $(whoami):$(whoami) "$INSTALL_DIR"; then - printf "Directory '%s' created successfully.\n" "$INSTALL_DIR" - log_success "Installation directory created with sudo: $INSTALL_DIR" - return 0 - else - printf "Error: Failed to create directory '%s'.\n" "$INSTALL_DIR" - log_error "Failed to create installation directory: $INSTALL_DIR" - return 1 - fi - fi -} - -# Function to get the public IP address -get_public_ip() { - log_info "Attempting to get public IP address" - local ip=$(curl -s ifconfig.io 2>>"$LOG_FILE") - if [[ -n "$ip" ]]; then - log_success "Public IP detected: $ip" - echo "$ip" - else - log_error "Failed to detect public IP address" - echo "" - fi -} - -# Function to get region -get_region() { - log_info "Attempting to get region" - local region=$(curl -s ifconfig.io/country_code 2>>"$LOG_FILE") - if [[ -n "$region" ]]; then - log_success "Region detected: $region" - echo "$region" - else - log_error "Failed to detect region" - echo "US" - fi -} - -# Function to check if Docker is installed -is_docker_installed() { - log_info "Checking if Docker is installed" - if command -v docker > /dev/null && command -v docker-compose > /dev/null; then - log_success "Docker is already installed" - return 0 - else - log_info "Docker is not installed" - return 1 - fi -} - -# This function does the actual test and returns success/failure. It doesn't print any messages -do_ip_port_test() { - local host_ip=$1 - local port=$2 - local listener_pid="" - - log_info "Performing IP:Port reachability test on $host_ip:$port" - - if sudo lsof -i TCP:"$port" >/dev/null 2>&1; then - log_warning "Port $port is already in use, skipping reachability test" - return 0 - fi - - # Prefer socat - if command -v socat >/dev/null 2>&1; then - log_info "Using socat for testing IP and port" - socat TCP-LISTEN:"$port",fork,reuseaddr - >/dev/null 2>&1 & - listener_pid=$! - # Fallback to python3 - elif command -v python3 >/dev/null 2>&1; then - log_info "Using python3 for testing IP and port" - python3 -c " -import socket -s = socket.socket() -s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) -s.bind(('0.0.0.0', $port)) -s.listen(1) -conn, addr = s.accept() -conn.close() -s.close() -" >/dev/null 2>&1 & - listener_pid=$! - else - log_warning "No supported listener tool (socat/python3); skipping IP reachability test" - return 0 - fi - - ( sleep 5 && kill -0 "$listener_pid" 2>/dev/null && kill "$listener_pid" ) & - - sleep 2 - - if echo "test" | nc "$host_ip" "$port" >/dev/null 2>&1; then - kill "$listener_pid" >/dev/null 2>&1 - log_success "IP reachability test passed for $host_ip:$port" - return 0 - else - kill "$listener_pid" >/dev/null 2>&1 - log_error "IP reachability test failed for $host_ip:$port" - return 1 - fi -} - -# Function to test if the IP is directly reachable from the internet -test_ip_reachability() { - local host_ip=$HOST_IP - local port=9080 - - do_ip_port_test "$host_ip" "$port" & - show_spinner $! "→ Verifying IP & port reachability" - return $? -} - -# Docker check_node_status() has been deprecated. See bottom of script if needed. -check_node_status() { - log_info "Checking node status" - local container_running=0 - local port_9080_listening=0 - local port_9002_listening=0 - local port_8088_listening=0 - local service_responding=0 - - # Check if container 'erebrus' is running (more precise check) - if [ "$INSTALLATION_MODE" = "container" ]; then - if sudo docker ps --format "table {{.Names}}" | grep -q "^erebrus$"; then - container_running=1 - log_info "Erebrus container is running" - else - log_info "Erebrus container is not running" - fi - fi - - # Check specific ports more efficiently - if sudo lsof -i :9080 -sTCP:LISTEN >/dev/null 2>&1; then - port_9080_listening=1 - log_info "Port 9080 is listening" - else - log_info "Port 9080 is not listening" - fi - - if sudo lsof -i :9002 -sTCP:LISTEN >/dev/null 2>&1; then - port_9002_listening=1 - log_info "Port 9002 is listening" - else - log_info "Port 9002 is not listening" - fi - - if sudo lsof -i :8088 -sTCP:LISTEN >/dev/null 2>&1; then - port_8088_listening=1 - log_info "Xray Port 8088 is listening" - else - log_info "Xray Port 8088 is not listening" - fi - - # HTTP health check to verify service is actually responding - if [ "$port_9080_listening" -eq 1 ]; then - if curl -s --connect-timeout 5 --max-time 10 "http://localhost:9080" >/dev/null 2>&1 || \ - curl -s --connect-timeout 5 --max-time 10 "http://localhost:9080/health" >/dev/null 2>&1 || \ - curl -s --connect-timeout 5 --max-time 10 "http://localhost:9080/api" >/dev/null 2>&1; then - service_responding=1 - log_info "Erebrus service is responding to HTTP requests" - else - log_warning "Port 9080 is listening but service is not responding to HTTP requests" - fi - fi - - # Determine overall status - local status_ok=0 - if [ "$INSTALLATION_MODE" = "container" ]; then - if [[ "$container_running" -eq 1 && "$port_9080_listening" -eq 1 && "$port_9002_listening" -eq 1 ]]; then - status_ok=1 - fi - else - if [[ "$port_9080_listening" -eq 1 && "$port_9002_listening" -eq 1 && "$port_8088_listening" -eq 1 ]]; then - status_ok=1 - fi - fi - - # Check if service is also responding - if [[ "$status_ok" -eq 1 && "$service_responding" -eq 1 ]]; then - log_success "Node status check passed - Service is fully operational" - return 0 - elif [[ "$status_ok" -eq 1 ]]; then - log_success "Node status check passed - Ports are listening" - return 0 - else - log_error "Node status check failed" - return 1 - fi -} - -validate_post_install() { - echo "🔍 Preparing to validate installation..." - (sleep 5 && check_node_status) & - show_spinner $! "→ Validating installation" - return $? -} - -check_mnemonic_format() { - log_info "Validating mnemonic format" - local mnemonic="$1" - # Split the mnemonic into an array of words - IFS=' ' read -r -a words <<< "$mnemonic" - - # Define the required number of words in the mnemonic (12, 15, 18, 21, or 24 typically for BIP39) - local required_words=(12 15 18 21 24) - - # Check if the mnemonic has the correct number of words - local num_words=${#words[@]} - if ! [[ " ${required_words[*]} " =~ " $num_words " ]]; then - log_error "Invalid mnemonic: wrong number of words ($num_words). Expected: 12, 15, 18, 21, or 24" - return 1 - fi - - # Check if each word in the mnemonic is valid - for word in "${words[@]}"; do - if [[ ! "$word" =~ ^[a-zA-Z]+$ ]]; then - log_error "Invalid mnemonic: word '$word' contains non-alphabetic characters" - return 1 - fi - done - log_success "Mnemonic format validation passed ($num_words words)" - return 0 -} - -print_final_message() { - log_info "Generating final installation message" - - # Check if any enabled stages failed - local has_failures=false - local enabled_stages=0 - local completed_stages=0 - - for i in {0..2}; do - local status="${STAGE_STATUS[$i]}" - if [[ "$status" == "✘ Failed" || "$status" == "✘ Blocked" ]]; then - has_failures=true - fi - if [[ "$status" != "✘ Skipped" ]]; then - enabled_stages=$((enabled_stages + 1)) - if [[ "$status" == "✔ Complete" ]]; then - completed_stages=$((completed_stages + 1)) - fi - fi - done - - if [[ "$has_failures" == true ]]; then - printf "\e[31mInstallation failed due to stage failures.\e[0m\n" - printf "See $LOG_FILE for details.\n" - log_error "Installation failed - One or more stages failed" - elif [[ $enabled_stages -eq 0 ]]; then - printf "\e[33mNo stages were enabled to run.\e[0m\n" - log_warning "No stages were enabled to run" - elif [[ $completed_stages -eq $enabled_stages ]]; then - printf "\e[32mErebrus node installation is finished.\e[0m\n" - printf "Erebrus Node API is accessible at http://${HOST_IP}:9080\n" - printf "Refer \e[4mhttps://github.com/NetSepio/erebrus/blob/main/docs/docs.md\e[0m for API documentation.\n" - printf "\nYou can now manage the node using the \e[1merebrus\e[0m command. Try:\n" - printf " \e[36merebrus status\e[0m\n" - printf "\n\e[32mAll stages completed successfully!\e[0m\n\n" - log_success "Installation completed successfully - Node is running" - else - printf "\e[33mSome enabled stages did not complete successfully.\e[0m\n" - printf "See $LOG_FILE for details.\n" - log_warning "Some enabled stages did not complete successfully" - fi -} - -#Function to enable IP forwarding on host. Required for wireguard to forward traffic -enable_ip_forwarding() { - local os - os=$(uname) - local config_file - local setting - - if [[ "$os" == "Linux" ]]; then - config_file="/etc/sysctl.d/99-erebrus.conf" - setting="net.ipv4.ip_forward=1" - - log_info "Configuring IP forwarding in $config_file" - - # Remove any conflicting settings from the target file if it exists - if [[ -f "$config_file" ]]; then - sudo sed -i '/^net\.ipv4\.ip_forward/d' "$config_file" - fi - - # Add the correct setting - echo "$setting" | sudo tee "$config_file" > /dev/null - log_info "IP forwarding setting written to $config_file" - - # Apply all sysctl settings from all config files - log_info "Applying sysctl settings using sysctl --system..." - if sudo sysctl --system >> "$LOG_FILE" 2>&1; then - # Verify the setting actually took effect - if [[ "$(sysctl -n net.ipv4.ip_forward)" == "1" ]]; then - log_success "IP forwarding is enabled and verified" - return 0 - else - log_error "sysctl applied, but IP forwarding not active" - return 1 - fi - else - log_error "Failed to apply sysctl settings with sysctl --system" - return 1 - fi - - elif [[ "$os" == "Darwin" ]]; then - config_file="/etc/sysctl.conf" - setting="net.inet.ip.forwarding=1" - - # Enable immediately - if [[ $(sysctl -n net.inet.ip.forwarding) -eq 1 ]]; then - log_info "IP forwarding is already enabled on this macOS system" - else - log_info "Enabling IP forwarding immediately" - sudo sysctl -w net.inet.ip.forwarding=1 - fi - - # Persist setting - if [[ ! -f "$config_file" ]]; then - echo "$setting" | sudo tee "$config_file" > /dev/null - log_info "Created $config_file and enabled IP forwarding persistently" - else - if grep -qE "^${setting}$" "$config_file"; then - log_info "IP forwarding is already enabled in $config_file" - else - sudo sed -i.bak '/net\.inet\.ip\.forwarding/d' "$config_file" - echo "$setting" | sudo tee -a "$config_file" > /dev/null - log_info "IP forwarding added to $config_file" - fi - fi - - log_info "Note: On macOS, a reboot may be required for persistent IP forwarding to take effect." - - else - log_error "Unsupported OS: $os" - return 1 - fi - - return 0 -} - -#Test if IP forwarding is enable on host -test_ip_forwarding() { - log_info "Checking IP forwarding setting..." - enable_ip_forwarding & - show_spinner $! "→ Validating IP Forwarding" - return $? -} - -# Stage #1 - Configure Node environment variables -configure_node() { - log_info "=== Starting Stage 1: Configure Node ===" - echo "📋 Configuring node..." - - # Prompt for installation directory and validate input - read -p "Enter installation directory (default: current directory): " INSTALL_DIR_INPUT - # Set base directory from input or use current default - BASE_DIR=${INSTALL_DIR_INPUT:-$(pwd)} - echo "Installation directory set to "$BASE_DIR"" - log_info "User input for installation directory: $INSTALL_DIR_INPUT" - - # Create the installation directory - if ! create_install_directory "$BASE_DIR"; then - log_error "Failed to create installation directory" - return 1 - fi - - # Configure .env for xray-only installation mode - if $INSTALL_XRAY_ONLY; then - log_info "INSTALL_XRAY_ONLY=true, XRAY_ENABLED=${XRAY_ENABLED}" - if [[ -f "${INSTALL_DIR}/.env" ]]; then - # Verify file is writable - if [[ ! -w "${INSTALL_DIR}/.env" ]]; then - log_error "Cannot write to ${INSTALL_DIR}/.env: Permission denied" - return 1 - fi - # Debug: Log current .env content - log_info "Current .env content before update:" - log_info "$(cat "${INSTALL_DIR}/.env")" - # Check if XRAY_ENABLED exists in .env - if grep -q "^XRAY_ENABLED=" "${INSTALL_DIR}/.env"; then - # Update existing XRAY_ENABLED (cross-platform sed) - if sed -i.bak "s/^XRAY_ENABLED=.*/XRAY_ENABLED=${XRAY_ENABLED}/" "${INSTALL_DIR}/.env" 2>/dev/null || sed -i "" "s/^XRAY_ENABLED=.*/XRAY_ENABLED=${XRAY_ENABLED}/" "${INSTALL_DIR}/.env"; then - log_info "Updated XRAY_ENABLED to ${XRAY_ENABLED} in ${INSTALL_DIR}/.env" - else - log_error "Failed to update XRAY_ENABLED in ${INSTALL_DIR}/.env" - return 1 - fi - # Remove backup file if created - rm -f "${INSTALL_DIR}/.env.bak" - else - # Append XRAY_ENABLED - printf "\n# #Erebrus Xray Installation Flag\nXRAY_ENABLED=${XRAY_ENABLED}\n" >> "${INSTALL_DIR}/.env" - log_info "Appended XRAY_ENABLED=${XRAY_ENABLED} to ${INSTALL_DIR}/.env" - fi - # Debug: Log .env content after update - log_info "Current .env content after update:" - log_info "$(cat "${INSTALL_DIR}/.env")" - else - # Create new .env with XRAY_ENABLED - bash -c "cat > ${INSTALL_DIR}/.env" < ${INSTALL_DIR}/.env" </dev/null 2>&1; then - # Use getent if available (common in Linux) - if getent group "$1" >/dev/null 2>&1; then - return 0 - else - return 1 - fi - # Check using dscl (might be more reliable on macOS) - elif command -v dscl >/dev/null 2>&1; then - if dscl . -list /Groups | grep "$1" >/dev/null 2>&1; then - return 0 - else - return 1 - fi - fi -} - -function create_group() { - # Create group, takes group name as an argument $1 - if command -v groupadd; then - sudo groupadd "$1" - [[ $? -eq 0 ]] && return 0 || return 1 - elif command -v dscl; then - dscl . -create /Groups/"$1" - [[ $? -eq 0 ]] && return 0 || return 1 - fi -} - -#Create docker group and add user to the group -function add_user_to_group() { - # Add current user to docker group - if command -v usermod; then - if ! groups "$USER" | grep "$1"; then - sudo usermod -aG "$1" "$USER" # Use sudo and usermod for Linux - [[ $? -eq 0 ]] && return 0 || return 1 - fi - elif command -v dscl; then - if ! dscl . -read /Groups/"$1" | grep GroupMembership | grep "$USER"; then - dscl . -append /Groups/"$1" GroupMembership "$USER" # Use dscl for macOS - [[ $? -eq 0 ]] && return 0 || return 1 - fi - fi -} - -install_dependencies_docker_mode() { - log_info "=== Starting install_dependencies_docker_mode ===" - printf " → Checking Docker installation...\n" - if is_docker_installed; then - printf " ✓ Docker already installed\n" - sleep 2 - else - printf " → Installing Docker...\n" - if command -v apt-get > /dev/null; then - (sudo apt-get update -qq && sudo apt-get install -y containerd docker.io && sudo apt-get install socat-* -y && sudo apt-get install lsof -y >> "$LOG_FILE" 2>&1) & - elif command -v yum > /dev/null; then - (sudo yum install yum-utils -y && sudo yum install nmap-ncat.x86_64 -y && sudo yum install lsof socat -y && sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && yum install -y docker >> "$LOG_FILE" 2>&1 && sudo systemctl start docker && sudo systemctl enable docker >> "$LOG_FILE" 2>&1) & - elif command -v pacman > /dev/null; then - (sudo pacman -Sy --noconfirm docker socat >> "$LOG_FILE" 2>&1 && sudo systemctl start docker && sudo systemctl enable docker >> "$LOG_FILE" 2>&1) & - elif command -v dnf > /dev/null; then - printf " → Installing Docker on Fedora...\n" - (sudo dnf install dnf-plugins-core && dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && dnf install -y docker-ce docker-ce-cli containerd.io >> "$LOG_FILE" 2>&1) & - elif [[ "$OSTYPE" == "darwin"* ]]; then - printf " → Installing Docker on macOS...\n" - if ! command -v brew > /dev/null; then - printf " ✗ Homebrew not found. Please install Homebrew first.\n" - exit 1 - fi - (brew install --cask docker socat >> "$LOG_FILE" 2>&1 && open /Applications/Docker.app) & - printf " ✓ Docker installation complete\n" - else - printf " ✗ Unsupported Linux distribution.\n" - exit 1 - fi - printf " ✓ Docker installation complete\n" - fi - - if docker --version > /dev/null 2>&1; then - printf " → Configuring Docker group...\n" - # Created docker group if not exits - if ! group_exists "docker"; then - create_group "docker"; - fi - if add_user_to_group "docker"; then - if [[ $? -ne 0 ]]; then - printf " ✗ Failed to create group, docker configuration failed.\n" - exit 1 - fi - fi - printf " ✓ Docker configuration complete\n" - fi - log_info "=== Finished install_dependencies_docker_mode ===" -} - -function install_dependencies_binary_mode() { - log_info "=== Starting install_dependencies_binary_mode ===" - create_erebrus_folder - CURRENT_DIR=$(pwd) - - INSTALL_FAILED=false - - # Detect OS and install dependencies - if command -v apk > /dev/null; then - apk update >> "$LOG_FILE" 2>&1 - apk add --no-cache bash openresolv bind-tools wireguard-tools gettext inotify-tools iptables >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v apt-get > /dev/null; then - sudo apt-get update -qq >> "$LOG_FILE" 2>&1 - sudo apt-get install -y bash resolvconf dnsutils wireguard-tools gettext inotify-tools iptables systemd socat-* lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v yum > /dev/null; then - sudo yum install -y bash openresolv bind-utils wireguard-tools gettext inotify-tools iptables socat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v pacman > /dev/null; then - sudo pacman -Sy --noconfirm bash openresolv bind-tools wireguard-tools gettext inotify-tools iptables socat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v dnf > /dev/null; then - sudo dnf install -y bash openresolv bind-utils wireguard-tools gettext inotify-tools iptables socat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v brew > /dev/null; then - sudo -u "$SUDO_USER" brew install bash wireguard-tools gettext coreutils iproute2mac curl socat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - else - echo " ✗ Unsupported Linux distribution. Exiting." | tee -a "$LOG_FILE" - exit 1 - fi - - if [ "$INSTALL_FAILED" = true ]; then - log_error "Some dependencies failed to install." - fi - log_info "=== Finished install_dependencies_binary_mode ===" -} - -function download_xray_binary() { - log_info "=== Starting download_xray_binary ===" - XRAY_REPO="NetSepio/erebrus-xray" - DOWNLOAD_DIR="${INSTALL_DIR}" - - # Detect OS and ARCH (same logic as erebrus binary) - OS=$(uname | tr '[:upper:]' '[:lower:]') # "linux" or "darwin" - ARCH=$(uname -m) - - case "$ARCH" in - x86_64) ARCH="amd64" ;; - arm64 | aarch64) ARCH="arm64" ;; - *) - log_error "Unsupported architecture: $ARCH" - echo " ✗ Unsupported architecture: $ARCH" - return 1 - ;; - esac - - XRAY_BINARY_NAME="erebrus-xray-${OS}-${ARCH}" - XRAY_PATH="$DOWNLOAD_DIR/$XRAY_BINARY_NAME" - - log_info "Detected OS: $OS, Architecture: $ARCH" - log_info "Target binary: $XRAY_BINARY_NAME" - - # Check if binary already exists and is executable - if [[ -f "$XRAY_PATH" && -x "$XRAY_PATH" ]]; then - log_info "Erebrus-Xray binary already exists at $XRAY_PATH" - echo "$XRAY_PATH" > "${DOWNLOAD_DIR}/xray_binary_path" - log_success "Downloading latest Erebrus-Xray binary" - log_info "=== Finished download_xray_binary ===" - fi - - # Try to fetch latest release tag with better error handling - log_info "Fetching latest Xray release information..." - LATEST_XRAY_TAG=$(curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$XRAY_REPO/releases/latest 2>>"$LOG_FILE" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - - if [[ -z "$LATEST_XRAY_TAG" ]]; then - log_warning "Could not fetch latest release tag from GitHub API, trying fallback method..." - # Fallback: try to get the latest tag directly - LATEST_XRAY_TAG=$(curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/$XRAY_REPO/tags" 2>>"$LOG_FILE" | grep '"name":' | head -1 | sed -E 's/.*"([^"]+)".*/\1/') - - if [[ -z "$LATEST_XRAY_TAG" ]]; then - log_warning "GitHub API failed, using default tag 'latest'..." - LATEST_XRAY_TAG="latest" - fi - fi - - log_info "Using Xray release tag: $LATEST_XRAY_TAG" - XRAY_DOWNLOAD_URL="https://github.com/$XRAY_REPO/releases/download/$LATEST_XRAY_TAG/$XRAY_BINARY_NAME" - log_info "Download URL: $XRAY_DOWNLOAD_URL" - - # Remove existing file if present - if [[ -f "$XRAY_PATH" ]]; then - rm -f "$XRAY_PATH" - log_info "Removed existing Xray binary file" - fi - - # Download with better error handling - log_info "Downloading Xray binary..." - if curl -L --connect-timeout 10 --max-time 300 -o "$XRAY_PATH" "$XRAY_DOWNLOAD_URL" >> "$LOG_FILE" 2>&1; then - log_info "Download completed successfully" - chmod +x "$XRAY_PATH" - - if [[ -f "$XRAY_PATH" && -s "$XRAY_PATH" ]]; then - local file_size=$(stat -f%z "$XRAY_PATH" 2>/dev/null || stat -c%s "$XRAY_PATH" 2>/dev/null || echo "unknown") - echo "$XRAY_PATH" > "${DOWNLOAD_DIR}/xray_binary_path" - log_success "Erebrus-Xray binary downloaded successfully to $XRAY_PATH (size: $file_size bytes)" - else - log_error "Downloaded file is missing or empty" - return 1 - fi - else - log_error "Failed to download Erebrus-Xray binary from $XRAY_DOWNLOAD_URL" - return 1 - fi - - log_info "Finished download_xray_binary to $XRAY_PATH" - return 0 -} - -function download_erebrus_binary() { - log_info "=== Starting download_erebrus_binary ===" - REPO="NetSepio/erebrus" - DOWNLOAD_DIR="${INSTALL_DIR}" - #ERROR_LOG="$DOWNLOAD_DIR/erebrus_error.log" - - # Detect OS and ARCH - OS=$(uname | tr '[:upper:]' '[:lower:]') # "linux" or "darwin" - ARCH=$(uname -m) - - case "$ARCH" in - x86_64) ARCH="amd64" ;; - arm64 | aarch64) ARCH="arm64" ;; - *) echo " ✗ Unsupported architecture: $ARCH" | tee "$ERROR_LOG"; log_error "Unsupported architecture: $ARCH"; return 1 ;; - esac - - BINARY_NAME="erebrus-${OS}-${ARCH}" - BINARY_PATH="$DOWNLOAD_DIR/$BINARY_NAME" - - # Fetch latest release tag - LATEST_TAG=$(curl -s https://api.github.com/repos/$REPO/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - - if [[ -z "$LATEST_TAG" ]]; then - echo " ✗ Failed to fetch the latest release tag." | tee "$ERROR_LOG" - log_error "Failed to fetch the latest release tag." - return 1 - fi - - DOWNLOAD_URL="https://github.com/$REPO/releases/download/$LATEST_TAG/$BINARY_NAME" - - if [[ -f "$BINARY_PATH" ]]; then - rm -f "$BINARY_PATH" - fi - - curl -L -o "$BINARY_PATH" "$DOWNLOAD_URL" >> "$LOG_FILE" 2>&1 - - if [[ $? -ne 0 ]]; then - echo " ✗ Download failed!" | tee "$ERROR_LOG" - log_error "Download failed!" - return 1 - fi - - chmod +x "$BINARY_PATH" - - if [[ ! -f "$BINARY_PATH" ]]; then - echo " ✗ Error: $BINARY_NAME not found in $DOWNLOAD_DIR!" | tee "$ERROR_LOG" - log_error "Error: $BINARY_NAME not found in $DOWNLOAD_DIR!" - return 1 - fi - - echo "$BINARY_PATH" > "${DOWNLOAD_DIR}/erebrus_binary_path" - log_success "Erebrus binary downloaded successfully to $BINARY_PATH" - log_info "=== Finished download_erebrus_binary ===" - return 0 -} - -run_erebrus_container() { - log_info "=== Starting run_erebrus_container ===" - printf " → Starting Erebrus container...\n" - ENV_FILE="${INSTALL_DIR}/.env" - sleep 2 - if [ ! -f "$ENV_FILE" ]; then - printf " ✗ The .env file does not exist at path: %s\n" "$ENV_FILE" - printf " Make sure the .env file exists and try again.\n" - log_error "The .env file does not exist at path: $ENV_FILE" - exit 1 - fi - (sudo docker run -d -p 9080:9080/tcp -p 9002:9002/tcp -p 51820:51820/udp \ - --cap-add=NET_ADMIN --cap-add=SYS_MODULE \ - --sysctl="net.ipv4.conf.all.src_valid_mark=1" \ - --sysctl="net.ipv6.conf.all.forwarding=1" \ - --restart unless-stopped -v "${INSTALL_DIR}/wireguard:/etc/wireguard" \ - --name erebrus --env-file "${ENV_FILE}" ghcr.io/netsepio/erebrus:main >> "$LOG_FILE" 2>&1) & - wait $! - printf " ✓ Erebrus container started\n" - log_success "Erebrus container started" - log_info "=== Finished run_erebrus_container ===" -} - -run_erebrus_binary() { - log_info "=== Starting run_erebrus_binary ===" - local path="${INSTALL_DIR}/erebrus_binary_path" - - if [[ -f "$path" ]]; then - local binary=$(cat "$path") - # Change to the installation directory before running the binary - cd "${INSTALL_DIR}" || { - log_error "Failed to change to installation directory: ${INSTALL_DIR}" - return 1 - } - kill_port_erebrus - # Run the binary with sudo (should work now that we ensured credentials) - sudo "$binary" > "${INSTALL_DIR}/erebrus.log" 2>&1 & - EREBRUS_PID=$! - # Change back to original directory - cd - > /dev/null - - if kill -0 "$EREBRUS_PID" 2>/dev/null; then - log_success "Erebrus started with (PID: $EREBRUS_PID)" - return 0 - else - log_error "Erebrus binary failed to start" - return 1 - fi - else - log_error "Erebrus binary path not found" - return 1 - fi - log_info "=== Finished run_erebrus_binary ===" -} - -function run_xray_binary() { - log_info "=== Starting run_xray_binary ===" - kill_port_erebrus 8088 - - local path="${INSTALL_DIR}/xray_binary_path" - if [[ -f "$path" ]]; then - local binary=$(cat "$path") - local config_path="${INSTALL_DIR}/config.json" - "$binary" -c "$config_path" > "${INSTALL_DIR}/xray.log" 2>&1 & - XRAY_PID=$! - sleep 2 - - if kill -0 "$XRAY_PID" 2>/dev/null; then - log_success "Erebrus-Xray started (PID: $XRAY_PID) with config at $config_path" - return 0 - else - log_error "Erebrus-Xray process exited or failed to start" - return 1 - fi - else - log_error "Xray binary path not found" - return 1 - fi - - log_info "=== Finished run_xray_binary ===" - return 0 -} - -# Function to create the "erebrus" folder in the current directory -function create_erebrus_folder() { - CURRENT_DIR=$(pwd) - FOLDER_NAME="erebrus" - mkdir -p "$CURRENT_DIR/$FOLDER_NAME" - if ! [ -d "$CURRENT_DIR/$FOLDER_NAME" ]; then - return 1 - fi -} - -function kill_port_erebrus() { - local ports=() - - # If a port is provided as an argument, use it; otherwise, use default ports - if [[ -n "$1" && "$1" =~ ^[0-9]+$ ]]; then - ports=("$1") - log_info "Collecting and killing processes on specified port: $1" - else - ports=(9080 9002 8088) - log_info "Collecting and killing processes on default ports: ${ports[*]}" - fi - - for port in "${ports[@]}"; do - # Collect all PIDs listening on the port - local pids - pids=$(sudo lsof -t -i :$port 2>/dev/null) - - if [[ -n "$pids" ]]; then - # Log all collected PIDs - log_info "Collected PIDs on port $port: $pids" - - # Kill all collected PIDs in one command - echo "$pids" | xargs -r kill -9 2>/dev/null - if [[ $? -eq 0 ]]; then - log_success "Successfully killed PIDs ($pids) on port $port" - else - log_error "Failed to kill PIDs ($pids) on port $port" - fi - else - log_info "No processes found on port $port" - fi - done -} - -confirm_installation() { - read -p "Do you want to continue with installation? (default: y) (y/n): " confirm - - # Clear the prompt line immediately after user input - printf "\033[1A\033[2K" # Move up one line and clear it - - confirm=${confirm:-y} - if [[ "$confirm" != [Yy] ]]; then - echo "Installation cancelled." - exit 1 - fi - - if check_node_status; then - printf "\e[33mErebrus node is already installed and running.\e[0m\n" - printf "Refer \e[4mhttps://github.com/NetSepio/erebrus/blob/main/docs/docs.md\e[0m for API documentation.\n\n" - - while true; do - read -p "Do you want to reinstall the node? (y/n): " confirm_reinstallation - # Clear this prompt too - printf "\033[1A\033[2K" - - case "$confirm_reinstallation" in - [Yy]) - break - ;; - [Nn]) - printf "\e[31mInstallation aborted by user\e[0m\n" - exit 0 - ;; - *) - echo "Please select valid option" - ;; - esac - done - fi -} - -function create_xray_config() { - log_info "=== Starting create_xray_config ===" - # Create config.json file - local config_file="$INSTALL_DIR/config.json" - - cat > "$config_file" < /dev/null ;; - 2) declare -f install_dependencies > /dev/null ;; - 3) declare -f run_node > /dev/null ;; - *) return 1 ;; - esac -} - -# Function to check if previous stage was successful -check_previous_stage() { - local current_stage=$1 - local previous_stage=$((current_stage - 1)) - - if [[ $previous_stage -ge 0 ]]; then - local prev_status="${STAGE_STATUS[$previous_stage]}" - if [[ "$prev_status" != "✔ Complete" && "$prev_status" != "✘ Skipped" ]]; then - log_error "Stage $((current_stage + 1)) cannot run: Stage $((previous_stage + 1)) was not successful (Status: $prev_status)" - return 1 - fi - fi - return 0 -} - -create_manage_script() { - log_info "Installing node management script" - show_spinner $! "→ Installing node management script" - cat > ${INSTALL_DIR}/manage.sh <<'EOF' -#!/bin/bash -# Erebrus Node Management Script - -# Ensure script runs with sudo/root -if [[ "$EUID" -ne 0 ]]; then - exec sudo "$0" "$@" -fi - -DEBUG=false -ARGS=() -FOLLOW_LOGS=false -EREBRUS_AVAILABLE=false -XRAY_AVAILABLE=false -SERVICES_STARTED="" - -print_help() { - cat </dev/null) -XRAY_PATH=$(cat "$INSTALL_DIR/xray_binary_path" 2>/dev/null) - -if [[ -n "$EREBRUS_PATH" && -x "$EREBRUS_PATH" && $NODE_NAME ]]; then - EREBRUS_AVAILABLE=true - log_debug "Erebrus node binary found and executable at $EREBRUS_PATH" -else - log_debug "Erebrus node binary not found or not executable at $EREBRUS_PATH" -fi - -if [[ -n "$XRAY_PATH" && -x "$XRAY_PATH" && "$XRAY_ENABLED" == "true" ]]; then - XRAY_AVAILABLE=true - log_debug "Erebrus Xray binary found and executable at $XRAY_PATH" -else - log_debug "Erebrus Xray binary not found or not executable at $XRAY_PATH" -fi - -get_pids() { - local binary="$1" - local binary_name - binary_name=$(basename "$binary") - pgrep -f "$binary_name" | paste -sd ' ' - -} - -start_service() { - local name="$1" - local binary="$2" - - log_debug "Starting $name with binary: $binary" - - local pids - pids=$(get_pids "$binary") - if [[ -n "$pids" ]]; then - printf "\e[32m%s is already running (PIDs: %s)\e[0m\n" "$name" "$(echo "$pids" | paste -sd ',' -)" - return 1 - else - if [[ "$name" == "erebrus-node" ]]; then - "$binary" > "$INSTALL_DIR/erebrus.log" 2>&1 & - elif [[ "$name" == "erebrus-xray" ]]; then - "$binary" -c "$INSTALL_DIR/config.json" > "$INSTALL_DIR/xray.log" 2>&1 & - else - "$binary" > /dev/null 2>&1 & - fi - local pid=$! - log_debug "Started $name with PID: $pid" - sleep 1 # Give the process time to start or fail - if [[ -n "$(get_pids "$binary")" ]]; then - printf "\e[32m%s started (PID: %s)\e[0m\n" "$name" "$pid" - if [[ "$name" == "erebrus-node" ]]; then - SERVICES_STARTED="$SERVICES_STARTED node" - elif [[ "$name" == "erebrus-xray" ]]; then - SERVICES_STARTED="$SERVICES_STARTED xray" - fi - return 0 - else - printf "\e[31mFailed to start %s\e[0m\n" "$name" - log_debug "No PIDs found for $name after start attempt" - return 1 - fi - fi -} - -stop_service() { - local name="$1" - local binary="$2" - - log_debug "Stopping $name with binary: $binary" - - local pids - pids=$(get_pids "$binary") - if [[ -n "$pids" ]]; then - echo "$pids" | xargs kill - printf "\e[31m%s stopped (PIDs: %s)\e[0m\n" "$name" "$pids" - else - printf "%s is not running\n" "$name" - fi -} - -status_service() { - local name="$1" - local binary="$2" - - log_debug "Checking status of $name with binary: $binary" - - local pids - pids=$(get_pids "$binary") - if [[ -n "$pids" ]]; then - printf "\e[32m%s is running (PIDs: %s)\e[0m\n" "$name" "$pids" - else - printf "%s is not running\n" "$name" - fi -} - -ACTION="$1" -SERVICE="$2" - -run_action() { - local action="$1" - local service="$2" - local binary name - - if [[ "$action" != "log" ]]; then - case "$service" in - node) - if ! $EREBRUS_AVAILABLE; then - printf "\e[31mErebrus node is disabled or not installed\e[0m\n" - return - fi - binary="$EREBRUS_PATH" - name="erebrus-node" - ;; - xray) - if ! $XRAY_AVAILABLE; then - printf "\e[31mErebrus Xray is disabled or not installed\e[0m\n" - return - fi - binary="$XRAY_PATH" - name="erebrus-xray" - ;; - *) - printf "\e[31mUnknown service: %s\e[0m\n" "$service" - exit 1 - ;; - esac - fi - - case "$action" in - start) start_service "$name" "$binary" ;; - stop) stop_service "$name" "$binary" ;; - status) status_service "$name" "$binary" ;; - restart) - stop_service "$name" "$binary" - sleep 1 - start_service "$name" "$binary" - ;; - log) show_logs "$service" ;; - *) - printf "\e[31mInvalid action: %s\e[0m\n" "$action" - exit 1 - ;; - esac -} - -if [[ -z "$ACTION" ]]; then - print_help - exit 1 -fi - -if [[ -z "$SERVICE" ]]; then - run_action "$ACTION" node - run_action "$ACTION" xray -else - run_action "$ACTION" "$SERVICE" -fi - -# Print additional info message -if [[ "$ACTION" == "start" || "$ACTION" == "restart" ]]; then - printf "\nSee logs, Try: " - if [[ -z "$SERVICE" ]]; then - # No service specified, applies to both (or available) services - printf " \e[36merebrus log\e[0m\n" - else - printf " \e[1merebrus log ${SERVICE}\e[0m\n" - fi -fi -EOF - - chmod +x ${INSTALL_DIR}/manage.sh - sudo ln -sf ${INSTALL_DIR}/manage.sh /usr/local/bin/erebrus >> "$LOG_FILE" 2>&1 - log_info "manage.sh script created and made executable." - return $? -} -# Run stage1 -# For each run_stage function, change the order of operations: -run_stage_1() { - if declare -f configure_node > /dev/null; then - STAGE_STATUS[0]="In Progress" - display_header # Update header BEFORE running the function - - if [[ "$INSTALL_XRAY_ONLY" == true ]]; then - XRAY_ENABLED="true" - log_info "Stage 1: Configuring Xray" - # Create installation directory (using default) - if ! configure_node; then - log_error "Failed to configure erebrus xray" - echo "❌ Failed to configure erebrus xray" - STAGE_STATUS[0]="✘ Failed" - display_header - exit 1 - fi - - # # Create Xray configuration with spinner - # create_xray_config & - # show_spinner $! "→ Creating Xray configuration" - # if [ $? -eq 0 ]; then - # log_success "Xray configuration created successfully at $INSTALL_DIR/config.json" - # else - # log_error "Failed to create Xray configuration" - # echo "❌ Failed to create Xray configuration" - # STAGE_STATUS[0]="✘ Failed" - # display_header - # exit 1 - # fi - STAGE_STATUS[0]="✔ Complete" - else - if configure_node; then - if test_ip_reachability; then - if enable_ip_forwarding; then - STAGE_STATUS[0]="✔ Complete" - display_header # Update header AFTER status change - echo "✅ Stage 1: Node configuration completed, IP test succeded and IP forwarding enabled" - log_success "Stage 1: Node configuration completed, IP test succeded and IP forwarding enabled!" - else - STAGE_STATUS[0]="✘ Failed" - display_header # Update header AFTER status change - echo "❌ Stage 1: Failed to enable IP forwarding!" - log_error "Stage 1: Failed to enable IP forwarding!" - fi - else - STAGE_STATUS[0]="✘ Failed" - display_header # Update header AFTER status change - echo "❌ Stage 1: IP and port accessability check failed!" - log_error "Stage 1: IP and port accessability check failed!" - fi - else - STAGE_STATUS[0]="✘ Failed" - display_header # Update header AFTER status change - echo "❌ Stage 1: Node configuration failed!" - log_error "Stage 1: Node configuration failed!" - fi - sleep 3 - fi - else - STAGE_STATUS[0]="✘ Skipped" - display_header - echo "⏭️ Stage 1: Configuration skipped" - log_info "Stage 1: Configuration skipped" - sleep 3 - fi -} - -# Run stage2 -run_stage_2() { - # Check if previous stage was successful - if ! check_previous_stage 1; then - STAGE_STATUS[1]="✘ Blocked" - display_header - echo "🚫 Stage 2: Dependencies installation blocked due to previous stage failure" - log_error "Stage 2: Dependencies installation blocked due to previous stage failure" - sleep 2 - return 1 - fi - - if declare -f install_dependencies > /dev/null; then - STAGE_STATUS[1]="In Progress" - display_header - if [[ "$INSTALL_XRAY_ONLY" == true ]]; then - log_info "Stage 2: Downloading Xray binary" - - # Download Xray binary with spinner - download_xray_binary & - show_spinner $! "→ Downloading Xray binary" - local xray_binary_download_status=$? - if [ $xray_binary_download_status -ne 0 ]; then - log_error "Failed to download Xray binary" - echo "❌ Failed to download Xray binary" - STAGE_STATUS[1]="✘ Failed" - display_header - exit 1 - fi - log_success "Xray binary downloaded successfully to $INSTALL_DIR" - sleep 3 - if create_manage_script; then - log_success "Node management script installed successfully" - else - log_error "Failed to install node management script" - echo "❌ Failed to install node management script" - STAGE_STATUS[1]="✘ Failed" - display_header - exit 1 - fi - sleep 3 - STAGE_STATUS[1]="✔ Complete" - display_header - else - if install_dependencies; then - if create_manage_script; then - STAGE_STATUS[1]="✔ Complete" - display_header - echo "✅ Stage 2: Dependencies installed successfully & node management script installed!" - log_success "Stage 2: Dependencies installed successfully and node management script installed!" - else - STAGE_STATUS[2]="✘ Failed" - display_header - echo "❌ Stage 2: Node management script installation failed" - log_error "Stage 2: Installing dependencies completed, but Node management script installation failed" - fi - else - STAGE_STATUS[1]="✘ Failed" - echo "❌ Stage 2: Dependencies installation failed!" - log_error "Stage 2: Dependencies installation failed!" - fi - sleep 3 - fi - else - STAGE_STATUS[1]="✘ Skipped" - display_header - echo "⏭️ Stage 2: Dependencies installation skipped" - log_info "Stage 2: Dependencies installation skipped" - sleep 3 - # clear_subprocess_output # Add this line - fi -} - -# Run stage3 -run_stage_3() { - # Check if previous stage was successful - if ! check_previous_stage 2; then - STAGE_STATUS[2]="✘ Blocked" - display_header - echo "🚫 Stage 3: Node startup blocked due to previous stage failure" - log_error "Stage 3: Node startup blocked due to previous stage failure" - sleep 3 - return 1 - fi - - if declare -f run_node > /dev/null; then - STAGE_STATUS[2]="In Progress" - display_header - - if [[ "$INSTALL_XRAY_ONLY" == true ]]; then - log_info "Stage 3: Running Xray binary" - - # Run Xray binary with spinner - run_xray_binary & - show_spinner $! "→ Starting Erebrus-Xray" - if [ $? -eq 0 ]; then - STAGE_STATUS[2]="✔ Complete" - else - STAGE_STATUS[2]="✘ Failed" - fi - else - if run_node; then - if validate_post_install; then - STAGE_STATUS[2]="✔ Complete" - display_header - echo "✅ Stage 3: Node started and validated successfully!" - log_success "Stage 3: Node started and validated successfully!" - else - STAGE_STATUS[2]="✘ Failed" - display_header - echo "❌ Stage 3: Node started but validation failed" - log_error "Stage 3: Node started but validation failed" - fi - else - STAGE_STATUS[2]="✘ Failed" - display_header - echo "❌ Stage 3: Failed to start node" - log_error "Stage 3: Failed to start node" - fi - sleep 3 - fi - else - STAGE_STATUS[2]="✘ Skipped" - display_header - echo "⏭️ Stage 3: Node startup skipped" - log_info "Stage 3: Node startup skipped" - sleep 3 - fi -} - -# Set ownership of all the installation and log files to SUDO_USER and its Primary Group -set_all_file_ownership() { - log_info "Setting ownership for $LOG_DIR/erebrus-install-*.log and $INSTALL_DIR" - if [[ -n "$SUDO_USER" ]]; then - # Get the primary group of SUDO_USER - primary_group=$(id -gn "$SUDO_USER" 2>>"$LOG_FILE") - if [[ -z "$primary_group" ]]; then - log_error "Failed to determine primary group for $SUDO_USER; falling back to $SUDO_USER" - primary_group="$SUDO_USER" - else - log_info "Primary group for $SUDO_USER is $primary_group" - fi - - # Set ownership for log files in /tmp matching erebrus-install-*.log - for log_file in "$LOG_DIR"/erebrus-install-*.log; do - if [[ -f "$log_file" ]]; then - sudo chown "$SUDO_USER:$primary_group" "$log_file" 2>>"$LOG_FILE" - if [[ $? -eq 0 ]]; then - log_info "Set ownership of $log_file to $SUDO_USER:$primary_group" - else - log_error "Failed to set ownership of $log_file to $SUDO_USER:$primary_group" - fi - fi - done - # Set ownership for INSTALL_DIR recursively - if [[ -d "$INSTALL_DIR" ]]; then - sudo chown -R "$SUDO_USER:$primary_group" "$INSTALL_DIR" 2>>"$LOG_FILE" - if [[ $? -eq 0 ]]; then - log_info "Set ownership of $INSTALL_DIR to $SUDO_USER:$primary_group" - else - log_error "Failed to set ownership of $INSTALL_DIR to $SUDO_USER:$primary_group" - fi - else - log_warning "INSTALL_DIR $INSTALL_DIR does not exist; skipping ownership change" - fi - else - log_warning "SUDO_USER not set; skipping ownership changes" - fi -} - -# Cleanup function to restore terminal on exit -cleanup() { - tput cnorm # Show cursor -} - -##################################################################################################################### -# Main script execution starts here -##################################################################################################################### -# Ensure script runs with sudo/root -if [[ "$EUID" -ne 0 ]]; then - exec sudo "$0" "$@" -fi - -STAGE_STATUS=("Pending" "Pending" "Pending") -INSTALLATION_MODE="binary" #valid options "binary" , "container" -XRAY_ENABLED="false" -INSTALL_XRAY_ONLY=false - -# Set default directories -BASE_DIR=$(pwd) -INSTALL_DIR="$BASE_DIR/erebrus" - -while [[ $# -gt 0 ]]; do - case "$1" in - --xray-only) - INSTALL_XRAY_ONLY=true - shift - ;; - -h|--help) - print_help - ;; - *) - echo "Unknown option: $1" - echo "Use --help for usage information." - exit 1 - ;; - esac -done - - -init_logging -display_header # Show header once -confirm_installation -mark_disabled_stages # Mark disabled stages as skipped before running any stages - -# Only update header once after marking disabled stages -display_header - -# Run Stages -run_stage_1 -run_stage_2 -run_stage_3 - -# Final status update -for i in {0..2}; do - if [[ "${STAGE_STATUS[$i]}" == "Pending" ]]; then - STAGE_STATUS[$i]="✘ Skipped" - fi -done -display_header - -# Print final message -echo "" -# Print final message -if [[ "$INSTALL_XRAY_ONLY" == true ]]; then - set_all_file_ownership - if [[ "${STAGE_STATUS[0]}" == "✔ Complete" && "${STAGE_STATUS[1]}" == "✔ Complete" && "${STAGE_STATUS[2]}" == "✔ Complete" ]]; then - printf "\e[32m ✅ Erebrus xray installation is finished.\e[0m\n" - printf "Refer \e[4mhttps://github.com/NetSepio/erebrus/blob/main/docs/docs.md\e[0m for API documentation.\n" - printf "\nYou can manage the erebrus node using the \e[1merebrus\e[0m command. Try:\n" - printf " \e[36merebrus status xray\e[0m\n" - log_success "Installation completed successfully - Erebrus Xray is running" - else - log_error "Xray installation failed" - echo "❌ Xray installation failed" - tput cnorm - exit 1 - fi -else - set_all_file_ownership - print_final_message -fi - - -if [ -n "$BASH_VERSION" ]; then - hash -r -elif [ -n "$ZSH_VERSION" ]; then - rehash -fi - -# Show cursor again -tput cnorm - -# Set trap to cleanup on exit -trap cleanup EXIT diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..772f033 --- /dev/null +++ b/install.sh @@ -0,0 +1,714 @@ +#!/usr/bin/env bash +# +# Erebrus v2 node installer — curl -fsSL https://erebrus.io/install.sh | bash +# +# Deploy mode (how the node runs): +# container (default) — Docker compose; WireGuard + stealth carriers in a container. +# host — bare metal via systemd; supports App-Hosting + wildcard DNS. +# +# Access mode (who can use the node — independent of deploy): +# private (default) | shared | public +# All nodes register with the gateway using their access type. +# +# Linux only (x86_64 / arm64). A node needs a STATIC, internet-routable public +# IP, real bandwidth, and open ports — the installer verifies all three. +# +set -euo pipefail + +# --------------------------------------------------------------------------- +# Constants / defaults (override via env) +# --------------------------------------------------------------------------- +REPO_URL="${EREBRUS_REPO_URL:-https://github.com/NetSepio/erebrus}" +BRANCH="${EREBRUS_BRANCH:-v2}" +INSTALL_DIR="${INSTALL_DIR:-/opt/erebrus}" +STATE_DIR="${STATE_DIR:-/var/lib/erebrus}" +ENV_DIR="/etc/erebrus" +GO_VERSION="${GO_VERSION:-1.23.4}" +BUILD_TAGS="with_reality_server" + +# Ports +HTTP_PORT="${HTTP_PORT:-9080}" # tcp REST API +WG_PORT="${WG_ENDPOINT_PORT:-51820}" # udp WireGuard +STEALTH_TCP_PORT="${STEALTH_TCP_PORT:-8443}" # tcp VLESS+REALITY (gateway prod: 443) +STEALTH_UDP_PORT="${STEALTH_UDP_PORT:-4443}" # udp Hysteria2 (gateway prod: 443) +# (host + app-hosting also needs 80/tcp + 443/tcp for Caddy) + +# Minimum acceptable throughput for an exit node (Mbps) +MIN_DOWN_MBPS="${MIN_DOWN_MBPS:-50}" +MIN_UP_MBPS="${MIN_UP_MBPS:-20}" + +# Behaviour toggles +DEPLOY="${EREBRUS_DEPLOY:-}" +ACCESS="${EREBRUS_ACCESS:-}" +ASSUME_YES="${ASSUME_YES:-false}" +SKIP_CHECKS="${SKIP_CHECKS:-false}" + +LOG_FILE="/tmp/erebrus-install-$(date +%s).log" + +# --------------------------------------------------------------------------- +# Output helpers +# --------------------------------------------------------------------------- +if [[ -t 1 ]]; then + C_RESET='\033[0m'; C_R='\033[31m'; C_G='\033[32m'; C_Y='\033[33m'; C_B='\033[34m'; C_BOLD='\033[1m' +else + C_RESET=''; C_R=''; C_G=''; C_Y=''; C_B=''; C_BOLD='' +fi +log() { echo -e "$*"; echo "$(date '+%F %T') $*" >>"$LOG_FILE"; } +info() { log "${C_B}•${C_RESET} $*"; } +ok() { log "${C_G}✔${C_RESET} $*"; } +warn() { log "${C_Y}!${C_RESET} $*"; } +err() { log "${C_R}✘${C_RESET} $*"; } +die() { err "$*"; echo " See $LOG_FILE for details." >&2; exit 1; } + +# Prompts read from the controlling terminal so they work even when the script +# is piped in (curl … | bash, where stdin is the pipe, not the keyboard). With +# no tty we fall back to defaults — pair with --yes / env vars for unattended use. +TTY="/dev/tty"; [[ -e "$TTY" ]] || TTY="" + +confirm() { # confirm "question" [default y/n] + local q="$1" def="${2:-y}" ans + $ASSUME_YES && return 0 + [[ -z "$TTY" ]] && { [[ "$def" == "y" ]]; return; } + local hint="[Y/n]"; [[ "$def" == "n" ]] && hint="[y/N]" + read -rp "$(echo -e "${C_BOLD}?${C_RESET} $q $hint ") " ans <"$TTY" || true + ans="${ans:-$def}" + [[ "$ans" =~ ^[Yy]$ ]] +} +ask() { # ask VARNAME "prompt" "default" + local __var="$1" __prompt="$2" __def="${3:-}" __in + if $ASSUME_YES || [[ -z "$TTY" ]]; then printf -v "$__var" '%s' "$__def"; return; fi + read -rp "$(echo -e "${C_BOLD}?${C_RESET} $__prompt ${__def:+(default: $__def) }") " __in <"$TTY" || true + printf -v "$__var" '%s' "${__in:-$__def}" +} + +banner() { + echo -e "${C_B}${C_BOLD}" + cat <<'EOF' + ____ ____ ____ ____ ____ _ _ ____ + |___ |__/ |___ |__] |__/ | | [__ + |___ | \ |___ |__] | \ |__| ___] v2 node installer +EOF + echo -e "${C_RESET}" +} + +# --------------------------------------------------------------------------- +# Arg parsing +# --------------------------------------------------------------------------- +while [[ $# -gt 0 ]]; do + case "$1" in + --mode|--deploy) DEPLOY="${2:-}"; shift 2 ;; + --access) ACCESS="${2:-}"; shift 2 ;; + --docker|--container) DEPLOY="container"; shift ;; + --host) DEPLOY="host"; shift ;; + -y|--yes) ASSUME_YES=true; shift ;; + --skip-checks) SKIP_CHECKS=true; shift ;; + --branch) BRANCH="${2:-}"; shift 2 ;; + -h|--help) + cat <<'USAGE' +Erebrus v2 node installer + +Usage: install.sh [options] + --mode container|host Deploy mode (container = Docker; host = bare metal) + --deploy container|host Alias for --mode + --access private|public Gateway visibility (default: public) + --container | --docker Shorthand for --mode container + --host Shorthand for --mode host + -y, --yes Non-interactive; accept defaults (pair with env vars) + --skip-checks Skip static-IP / bandwidth / port preflight + --branch Source branch to build from (default: v2) + -h, --help This help + +Key env overrides: EREBRUS_ACCESS, EREBRUS_DEPLOY, MNEMONIC, WG_ENDPOINT_HOST, + NODE_NAME, REGION, GATEWAY_URL, NODE_API_TOKEN, ENABLE_STEALTH, + REALITY_SERVER_NAMES, HYSTERIA2_OBFS_PASSWORD, ENABLE_APP_HOSTING, + APP_WILDCARD_DOMAIN, INSTALL_DIR, MIN_DOWN_MBPS, MIN_UP_MBPS +Linux only (x86_64/arm64). Needs a static public IP, bandwidth, and open ports. +USAGE + exit 0 ;; + *) die "unknown option: $1 (try --help)" ;; + esac +done + +# --------------------------------------------------------------------------- +# Privilege + platform +# --------------------------------------------------------------------------- +SUDO="" +require_root() { + if [[ $EUID -ne 0 ]]; then + command -v sudo >/dev/null 2>&1 || die "run as root (sudo not found)" + SUDO="sudo" + info "Using sudo for privileged steps." + fi +} +run() { $SUDO "$@"; } + +PKG="" +detect_platform() { + [[ "$(uname -s)" == "Linux" ]] || die "Erebrus nodes are Linux-only. Detected: $(uname -s)." + case "$(uname -m)" in + x86_64|amd64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) die "unsupported architecture: $(uname -m)" ;; + esac + if command -v apt-get >/dev/null 2>&1; then PKG="apt" + elif command -v dnf >/dev/null 2>&1; then PKG="dnf" + elif command -v yum >/dev/null 2>&1; then PKG="yum" + elif command -v pacman >/dev/null 2>&1; then PKG="pacman" + else die "no supported package manager (apt/dnf/yum/pacman) found"; fi + ok "Linux/$ARCH detected, package manager: $PKG" +} + +pkg_install() { + info "Installing packages: $*" + case "$PKG" in + apt) run apt-get update -qq >>"$LOG_FILE" 2>&1; run apt-get install -y "$@" >>"$LOG_FILE" 2>&1 ;; + dnf) run dnf install -y "$@" >>"$LOG_FILE" 2>&1 ;; + yum) run yum install -y "$@" >>"$LOG_FILE" 2>&1 ;; + pacman) run pacman -Sy --noconfirm "$@" >>"$LOG_FILE" 2>&1 ;; + esac +} + +ensure_tool() { command -v "$1" >/dev/null 2>&1 || pkg_install "${2:-$1}"; } + +# --------------------------------------------------------------------------- +# Preflight checks +# --------------------------------------------------------------------------- +PUBLIC_IP="" +detect_public_ip() { + local svc + for svc in "https://api.ipify.org" "https://ifconfig.me/ip" "https://icanhazip.com" "https://ipinfo.io/ip"; do + PUBLIC_IP="$(curl -fsS --max-time 8 "$svc" 2>/dev/null | tr -d '[:space:]' || true)" + [[ "$PUBLIC_IP" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] && break + PUBLIC_IP="" + done + [[ -n "$PUBLIC_IP" ]] || die "could not determine public IP (no internet?)" + ok "Public IP: $PUBLIC_IP" +} + +check_static_ip() { + # Heuristic: a directly-attached public IP appears on a local interface. + # If it doesn't, the host is behind NAT and needs port-forwarding / a static + # mapping for inbound traffic to reach the node. + local local_ips + local_ips="$(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1 || true)" + if echo "$local_ips" | grep -qx "$PUBLIC_IP"; then + ok "Public IP is bound directly to a local interface (not behind NAT)." + else + warn "Public IP $PUBLIC_IP is NOT on a local interface — host appears to be behind NAT." + warn "Inbound traffic will only reach this node if that IP is STATIC and ports" + warn "$HTTP_PORT/tcp, $WG_PORT/udp, $STEALTH_TCP_PORT/tcp, $STEALTH_UDP_PORT/udp are forwarded here." + confirm "Continue anyway?" n || die "aborted: a static, routable public IP is required" + fi + warn "Note: the installer cannot prove the IP is permanent — make sure it is STATIC," + warn " otherwise the node will drop off the network when the lease changes." +} + +# Measure throughput against Cloudflare's speedtest endpoints (no account needed). +check_bandwidth() { + command -v curl >/dev/null 2>&1 || ensure_tool curl + info "Measuring bandwidth (this takes a few seconds)…" + + local down_bytes=50000000 up_bytes=20000000 t mbps + # Download + t="$(curl -fsS --max-time 30 -o /dev/null -w '%{time_total}' \ + "https://speed.cloudflare.com/__down?bytes=${down_bytes}" 2>/dev/null || echo 0)" + if [[ "$t" != "0" ]] && awk "BEGIN{exit !($t>0)}"; then + mbps="$(awk "BEGIN{printf \"%.0f\", ($down_bytes*8)/($t*1000000)}")" + if (( mbps < MIN_DOWN_MBPS )); then + warn "Download ~${mbps} Mbps (recommended ≥ ${MIN_DOWN_MBPS} Mbps)." + else + ok "Download ~${mbps} Mbps" + fi + else + warn "Could not measure download bandwidth." + fi + # Upload (critical for an exit node serving clients) + t="$(head -c "$up_bytes" /dev/zero | curl -fsS --max-time 30 -o /dev/null -w '%{time_total}' \ + --data-binary @- "https://speed.cloudflare.com/__up" 2>/dev/null || echo 0)" + if [[ "$t" != "0" ]] && awk "BEGIN{exit !($t>0)}"; then + mbps="$(awk "BEGIN{printf \"%.0f\", ($up_bytes*8)/($t*1000000)}")" + if (( mbps < MIN_UP_MBPS )); then + warn "Upload ~${mbps} Mbps (recommended ≥ ${MIN_UP_MBPS} Mbps for an exit node)." + else + ok "Upload ~${mbps} Mbps" + fi + else + warn "Could not measure upload bandwidth." + fi +} + +# Actively verify a TCP port is reachable FROM THE INTERNET: bind a temporary +# listener, then ask check-host.net (multiple external probes) to connect. +check_inbound_tcp() { + local port="$1" label="$2" + command -v python3 >/dev/null 2>&1 || { warn "python3 missing; skipping $label inbound check"; return; } + if ss -lnt 2>/dev/null | awk '{print $4}' | grep -qE "[:.]${port}\$"; then + warn "Port $port already in use locally; skipping inbound reachability check." + return + fi + + python3 - "$port" >>"$LOG_FILE" 2>&1 <<'PY' & +import socket, sys, time +p=int(sys.argv[1]); s=socket.socket(); s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) +s.bind(("0.0.0.0",p)); s.listen(8); s.settimeout(25); t=time.time() +while time.time()-t < 25: + try: + c,_=s.accept(); c.close() + except Exception: + break +PY + local lpid=$! + sleep 1 + + local rid res + rid="$(curl -fsS --max-time 10 -H 'Accept: application/json' \ + "https://check-host.net/check-tcp?host=${PUBLIC_IP}:${port}&max_nodes=3" 2>/dev/null \ + | python3 -c 'import sys,json;print(json.load(sys.stdin).get("request_id",""))' 2>/dev/null || true)" + if [[ -z "$rid" ]]; then + warn "$label ($port/tcp): external prober unavailable — could not auto-verify. Ensure the port is open." + kill "$lpid" 2>/dev/null || true; wait "$lpid" 2>/dev/null || true; return + fi + sleep 7 + res="$(curl -fsS --max-time 10 -H 'Accept: application/json' \ + "https://check-host.net/check-result/${rid}" 2>/dev/null || true)" + kill "$lpid" 2>/dev/null || true; wait "$lpid" 2>/dev/null || true + + local connected + connected="$(echo "$res" | python3 -c ' +import sys,json +try: d=json.load(sys.stdin) +except Exception: print("err"); sys.exit() +hit=0 +for v in (d or {}).values(): + if isinstance(v,list) and v and isinstance(v[0],list) and v[0] and v[0][0]==1: hit+=1 +print(hit) +' 2>/dev/null || echo err)" + if [[ "$connected" == "err" || -z "$connected" ]]; then + warn "$label ($port/tcp): inbound check inconclusive — verify the port is open." + elif (( connected > 0 )); then + ok "$label ($port/tcp) reachable from the internet ($connected probes)." + else + err "$label ($port/tcp) NOT reachable from the internet." + warn "Open it in your firewall/security-group (and NAT it if applicable)." + confirm "Continue anyway?" n || die "aborted: required port $port not reachable" + fi +} + +run_preflight() { + echo; info "${C_BOLD}Preflight checks${C_RESET}" + detect_public_ip + if $SKIP_CHECKS; then warn "--skip-checks set: skipping static-IP / bandwidth / port checks."; return; fi + ensure_tool curl + command -v ip >/dev/null 2>&1 || pkg_install iproute2 || pkg_install iproute || true + check_static_ip + check_bandwidth + info "Checking inbound port reachability…" + check_inbound_tcp "$HTTP_PORT" "REST API" + check_inbound_tcp "$STEALTH_TCP_PORT" "VLESS+REALITY carrier" + warn "UDP ports $WG_PORT (WireGuard) and $STEALTH_UDP_PORT (Hysteria2) can't be probed reliably —" + warn "make sure they're open; the installer will add firewall rules where it can." +} + +# --------------------------------------------------------------------------- +# Mode selection + configuration +# --------------------------------------------------------------------------- +normalize_deploy() { + case "$1" in + docker|container) echo "container" ;; + host) echo "host" ;; + *) echo "$1" ;; + esac +} + +choose_deploy() { + DEPLOY="$(normalize_deploy "$DEPLOY")" + [[ -n "$DEPLOY" ]] && { ok "Deploy mode: $DEPLOY"; return; } + echo + echo -e "${C_BOLD}Choose deploy mode:${C_RESET}" + echo " 1) container — Docker compose (VPN + stealth). Recommended." + echo " 2) host — bare-metal systemd. Adds App-Hosting (wildcard DNS)." + local c; ask c "Selection [1/2]" "1" + case "$c" in + 1|docker|container) DEPLOY="container" ;; + 2|host) DEPLOY="host" ;; + *) die "invalid selection: $c" ;; + esac + ok "Deploy mode: $DEPLOY" +} + +choose_access() { + ACCESS="${ACCESS:-private}" + ACCESS="$(echo "$ACCESS" | tr '[:upper:]' '[:lower:]')" + [[ -n "$ACCESS" && "$ASSUME_YES" == "true" ]] && { ok "Access mode: $ACCESS"; return; } + if [[ -n "$ACCESS" && "$ACCESS" != "private" ]]; then + ok "Access mode: $ACCESS" + return + fi + if $ASSUME_YES; then + ACCESS="private" + ok "Access mode: $ACCESS" + return + fi + echo + echo -e "${C_BOLD}Choose access mode:${C_RESET}" + echo " 1) private — your devices only (default)" + echo " 2) shared — friends via wallet allowlist on gateway" + echo " 3) public — open to entitled users on the network" + local c; ask c "Selection [1/2/3]" "1" + case "$c" in + 1|private) ACCESS="private" ;; + 2|shared) ACCESS="shared" ;; + 3|public) ACCESS="public" ;; + *) die "invalid selection: $c" ;; + esac + ok "Access mode: $ACCESS" +} + +# config values +NODE_NAME=""; REGION=""; WG_ENDPOINT_HOST=""; MNEMONIC="${MNEMONIC:-}" +NODE_API_TOKEN="${NODE_API_TOKEN:-}"; GATEWAY_URL="${GATEWAY_URL:-https://gateway.erebrus.io}" +ENABLE_STEALTH="${ENABLE_STEALTH:-true}"; REALITY_SERVER_NAMES="${REALITY_SERVER_NAMES:-www.microsoft.com}" +HYSTERIA2_OBFS_PASSWORD="${HYSTERIA2_OBFS_PASSWORD:-}" +ENABLE_APP_HOSTING="${ENABLE_APP_HOSTING:-false}"; APP_WILDCARD_DOMAIN="${APP_WILDCARD_DOMAIN:-}" +PUBLIC_DOMAIN="${PUBLIC_DOMAIN:-}"; WILDCARD_DOMAIN="${WILDCARD_DOMAIN:-}" +PUBLIC_GATEWAY_ENABLED="${PUBLIC_GATEWAY_ENABLED:-false}" +EREBRUS_BIN="" # path/way to invoke binary for genmnemonic + +rand_token() { head -c 24 /dev/urandom | base64 | tr -d '/+=' | head -c 32; } + +gather_config() { + echo; info "${C_BOLD}Node configuration${C_RESET}" + REGION="${REGION:-$(curl -fsS --max-time 6 https://ipinfo.io/country 2>/dev/null | tr -d '[:space:]' || echo unknown)}" + ask NODE_NAME "Node name" "${NODE_NAME:-erebrus-$(hostname -s 2>/dev/null || echo node)}" + ask WG_ENDPOINT_HOST "Public endpoint host (IP or domain clients dial)" "${WG_ENDPOINT_HOST:-$PUBLIC_IP}" + ask GATEWAY_URL "Gateway URL" "$GATEWAY_URL" + [[ -n "$NODE_API_TOKEN" ]] || NODE_API_TOKEN="$(rand_token)" + + case "$DEPLOY" in + container) + EREBRUS_MODE=container + EREBRUS_NETWORK_PROFILE=bridge + ;; + host) + EREBRUS_MODE=host + EREBRUS_NETWORK_PROFILE=host-network + ;; + *) die "invalid deploy mode: $DEPLOY (use container or host)" ;; + esac + EREBRUS_ACCESS="${ACCESS:-private}" + + if [[ "$EREBRUS_ACCESS" == "public" ]]; then + STEALTH_TCP_PORT=443 + STEALTH_UDP_PORT=443 + info "Public access: stealth carriers on 443/tcp and 443/udp for reachability." + fi + + if [[ "$DEPLOY" == "host" ]]; then + if confirm "Enable App-Hosting (expose VPN-connected apps to the internet)?" n; then + ENABLE_APP_HOSTING="true" + PUBLIC_GATEWAY_ENABLED="true" + echo " App-Hosting needs a WILDCARD DNS record you control, e.g.:" + echo -e " ${C_BOLD}*.apps.example.com A ${WG_ENDPOINT_HOST}${C_RESET}" + echo " The gateway then mints per-app CNAMEs under it and routes traffic in." + ask APP_WILDCARD_DOMAIN "Wildcard base domain (e.g. apps.example.com)" "$APP_WILDCARD_DOMAIN" + [[ -n "$APP_WILDCARD_DOMAIN" ]] || die "App-Hosting requires a wildcard domain" + PUBLIC_DOMAIN="$APP_WILDCARD_DOMAIN" + WILDCARD_DOMAIN="*.${APP_WILDCARD_DOMAIN}" + fi + fi +} + +# Invoke the node CLI regardless of install mode (built image vs host binary). +erebrus_cli() { + if [[ "$DEPLOY" == "container" ]]; then + run docker run --rm erebrus:v2 "$@" + else + /usr/local/bin/erebrus "$@" + fi +} + +# Generate a mnemonic using the freshly built binary/image if the operator +# didn't supply one. Called after the binary/image is available. +ensure_mnemonic() { + [[ -n "$MNEMONIC" ]] && { ok "Using supplied mnemonic."; return; } + info "Generating node identity mnemonic…" + MNEMONIC="$(erebrus_cli genmnemonic | tr -d '\r')" || die "failed to generate mnemonic" + [[ -n "$MNEMONIC" ]] || die "empty mnemonic generated" + ok "Node identity generated (12-word recovery phrase). It is saved securely — BACK IT UP." +} + +write_env_file() { + local f="$1" + run mkdir -p "$(dirname "$f")" + run tee "$f" >/dev/null </dev/null | awk '/default/{print $5; exit}' || echo eth0; } + +# --------------------------------------------------------------------------- +# Firewall +# --------------------------------------------------------------------------- +open_firewall() { + local extra_tcp=() + [[ "$ENABLE_APP_HOSTING" == "true" ]] && extra_tcp=(80 443) + if command -v ufw >/dev/null 2>&1 && run ufw status >/dev/null 2>&1; then + info "Opening ports via ufw…" + run ufw allow "${HTTP_PORT}/tcp" >>"$LOG_FILE" 2>&1 || true + run ufw allow "${STEALTH_TCP_PORT}/tcp" >>"$LOG_FILE" 2>&1 || true + run ufw allow "${WG_PORT}/udp" >>"$LOG_FILE" 2>&1 || true + run ufw allow "${STEALTH_UDP_PORT}/udp" >>"$LOG_FILE" 2>&1 || true + for p in "${extra_tcp[@]}"; do run ufw allow "${p}/tcp" >>"$LOG_FILE" 2>&1 || true; done + ok "ufw rules added." + elif command -v firewall-cmd >/dev/null 2>&1; then + info "Opening ports via firewalld…" + run firewall-cmd --permanent --add-port="${HTTP_PORT}/tcp" >>"$LOG_FILE" 2>&1 || true + run firewall-cmd --permanent --add-port="${STEALTH_TCP_PORT}/tcp" >>"$LOG_FILE" 2>&1 || true + run firewall-cmd --permanent --add-port="${WG_PORT}/udp" >>"$LOG_FILE" 2>&1 || true + run firewall-cmd --permanent --add-port="${STEALTH_UDP_PORT}/udp" >>"$LOG_FILE" 2>&1 || true + for p in "${extra_tcp[@]}"; do run firewall-cmd --permanent --add-port="${p}/tcp" >>"$LOG_FILE" 2>&1 || true; done + run firewall-cmd --reload >>"$LOG_FILE" 2>&1 || true + ok "firewalld rules added." + else + warn "No ufw/firewalld detected. Ensure these are open in your cloud security group:" + warn " ${HTTP_PORT}/tcp, ${STEALTH_TCP_PORT}/tcp, ${WG_PORT}/udp, ${STEALTH_UDP_PORT}/udp ${extra_tcp:+(+ 80/tcp 443/tcp)}" + fi +} + +enable_ip_forward() { + echo 'net.ipv4.ip_forward=1' | run tee /etc/sysctl.d/99-erebrus.conf >/dev/null + run sysctl -p /etc/sysctl.d/99-erebrus.conf >>"$LOG_FILE" 2>&1 || true +} + +# --------------------------------------------------------------------------- +# Docker install path +# --------------------------------------------------------------------------- +install_docker_mode() { + if ! command -v docker >/dev/null 2>&1; then + info "Installing Docker…" + curl -fsSL https://get.docker.com | run sh >>"$LOG_FILE" 2>&1 || die "Docker install failed" + fi + run systemctl enable --now docker >>"$LOG_FILE" 2>&1 || true + ensure_tool git + + info "Fetching source ($BRANCH) for image build…" + if [[ -d "$INSTALL_DIR/.git" ]]; then + run git -C "$INSTALL_DIR" fetch --depth 1 origin "$BRANCH" >>"$LOG_FILE" 2>&1 + run git -C "$INSTALL_DIR" checkout -f "$BRANCH" >>"$LOG_FILE" 2>&1 + run git -C "$INSTALL_DIR" reset --hard "origin/$BRANCH" >>"$LOG_FILE" 2>&1 + else + run mkdir -p "$INSTALL_DIR" + run git clone --depth 1 -b "$BRANCH" "$REPO_URL" "$INSTALL_DIR" >>"$LOG_FILE" 2>&1 + fi + + info "Building node image (includes -tags ${BUILD_TAGS})…" + ( cd "$INSTALL_DIR" && run docker build -t erebrus:v2 . >>"$LOG_FILE" 2>&1 ) || die "image build failed" + + ensure_mnemonic + write_env_file "$INSTALL_DIR/.env" + + info "Starting node via docker compose…" + local compose="docker compose" + docker compose version >/dev/null 2>&1 || compose="docker-compose" + ( cd "$INSTALL_DIR" && run $compose --env-file .env up -d >>"$LOG_FILE" 2>&1 ) || die "docker compose up failed" + open_firewall + ok "Docker node started." +} + +# --------------------------------------------------------------------------- +# Host (bare-metal) install path +# --------------------------------------------------------------------------- +ensure_go() { + if command -v go >/dev/null 2>&1; then return; fi + info "Installing Go ${GO_VERSION}…" + local tgz="go${GO_VERSION}.linux-${ARCH}.tar.gz" + curl -fsSL "https://go.dev/dl/${tgz}" -o "/tmp/${tgz}" >>"$LOG_FILE" 2>&1 || die "Go download failed" + run rm -rf /usr/local/go && run tar -C /usr/local -xzf "/tmp/${tgz}" + export PATH="$PATH:/usr/local/go/bin" +} + +build_host_binary() { + ensure_tool git + ensure_go + info "Fetching source ($BRANCH)…" + if [[ -d "$INSTALL_DIR/src/.git" ]]; then + run git -C "$INSTALL_DIR/src" fetch --depth 1 origin "$BRANCH" >>"$LOG_FILE" 2>&1 + run git -C "$INSTALL_DIR/src" reset --hard "origin/$BRANCH" >>"$LOG_FILE" 2>&1 + else + run mkdir -p "$INSTALL_DIR/src" + run git clone --depth 1 -b "$BRANCH" "$REPO_URL" "$INSTALL_DIR/src" >>"$LOG_FILE" 2>&1 + fi + info "Building erebrus (-tags ${BUILD_TAGS}); this can take a couple of minutes…" + ( cd "$INSTALL_DIR/src" && run env PATH="$PATH:/usr/local/go/bin" \ + go build -tags "$BUILD_TAGS" \ + -ldflags "-X github.com/NetSepio/erebrus/internal/config.Version=2.0.0" \ + -o /usr/local/bin/erebrus ./cmd/erebrus >>"$LOG_FILE" 2>&1 ) || die "build failed" + run chmod +x /usr/local/bin/erebrus + ok "Installed /usr/local/bin/erebrus" +} + +install_host_mode() { + pkg_install wireguard-tools iptables ca-certificates curl + command -v modprobe >/dev/null 2>&1 && run modprobe wireguard >>"$LOG_FILE" 2>&1 || \ + warn "Could not load the wireguard kernel module; ensure it is available on this host." + enable_ip_forward + build_host_binary + ensure_mnemonic + run mkdir -p "$STATE_DIR" /etc/wireguard + write_env_file "$ENV_DIR/erebrus.env" + + if [[ "$ENABLE_APP_HOSTING" == "true" ]]; then + info "Installing Caddy for app ingress…" + if [[ "$PKG" == "apt" ]]; then + run bash -c 'apt-get install -y debian-keyring debian-archive-keyring apt-transport-https >/dev/null 2>&1; \ + curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/gpg.key | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg; \ + curl -1sLf https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt | tee /etc/apt/sources.list.d/caddy-stable.list >/dev/null; \ + apt-get update -qq' >>"$LOG_FILE" 2>&1 || true + pkg_install caddy || warn "Caddy install failed; install it manually for app hosting." + else + pkg_install caddy || warn "Caddy not packaged here; install it manually for app hosting." + fi + fi + + info "Installing systemd service…" + run tee /etc/systemd/system/erebrus.service >/dev/null <>"$LOG_FILE" 2>&1 || die "failed to start erebrus service" + open_firewall + ok "Host node started (systemd: erebrus.service)." +} + +# --------------------------------------------------------------------------- +# Post-install +# --------------------------------------------------------------------------- +validate_and_summary() { + info "Validating node…" + local out="" i + for i in $(seq 1 15); do + out="$(curl -fsS --max-time 4 "http://127.0.0.1:${HTTP_PORT}/api/v2/status" 2>/dev/null || true)" + [[ -n "$out" ]] && break + sleep 2 + done + echo + if [[ -n "$out" ]]; then + ok "Node is up. Run: erebrus status (or curl /api/v2/status)" + echo "$out" | python3 -m json.tool 2>/dev/null || echo "$out" + if [[ -n "${GATEWAY_URL:-}" ]]; then + if curl -fsS --max-time 6 "${GATEWAY_URL%/}/healthz" >/dev/null 2>&1; then + ok "Gateway reachable at ${GATEWAY_URL}" + else + warn "Gateway not reachable at ${GATEWAY_URL} — control plane may stay offline" + fi + fi + else + warn "Node did not answer on :${HTTP_PORT} yet. Check logs:" + [[ "$DEPLOY" == "container" ]] && echo " cd $INSTALL_DIR && docker compose logs -f" \ + || echo " journalctl -u erebrus -f" + fi + + echo + echo -e "${C_BOLD}${C_G}Erebrus node installed (deploy=${DEPLOY}, access=${EREBRUS_ACCESS}).${C_RESET}" + echo " REST API : http://${WG_ENDPOINT_HOST}:${HTTP_PORT}/api/v2/status" + echo " WireGuard: ${WG_ENDPOINT_HOST}:${WG_PORT}/udp" + echo " Stealth : VLESS+REALITY :${STEALTH_TCP_PORT}/tcp · Hysteria2 :${STEALTH_UDP_PORT}/udp" + echo " Node API key: ${NODE_API_TOKEN}" + echo " Verify : erebrus status" + if [[ "$DEPLOY" == "container" ]]; then + echo " Manage : cd $INSTALL_DIR && docker compose [logs -f|restart|down]" + echo " Config : $INSTALL_DIR/.env" + else + echo " Manage : systemctl [status|restart|stop] erebrus ; journalctl -u erebrus -f" + echo " Config : $ENV_DIR/erebrus.env" + fi + if [[ "$ENABLE_APP_HOSTING" == "true" ]]; then + echo + echo -e "${C_BOLD}App-Hosting:${C_RESET} create this DNS record so the gateway can route apps:" + echo -e " ${C_BOLD}*.${APP_WILDCARD_DOMAIN} A ${WG_ENDPOINT_HOST}${C_RESET}" + fi + echo + echo -e "${C_Y}Back up your node identity (12-word phrase) — it cannot be recovered.${C_RESET}" +} + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +main() { + banner + require_root + detect_platform + choose_deploy + choose_access + run_preflight + gather_config + case "$DEPLOY" in + container) install_docker_mode ;; + host) install_host_mode ;; + *) die "invalid deploy mode: $DEPLOY" ;; + esac + validate_and_summary +} +main "$@" diff --git a/internal/api/middleware.go b/internal/api/middleware.go new file mode 100644 index 0000000..b618329 --- /dev/null +++ b/internal/api/middleware.go @@ -0,0 +1,60 @@ +package api + +import ( + "crypto/subtle" + "log/slog" + "net/http" + "strings" + "sync" + + "github.com/NetSepio/erebrus/internal/gatewayauth" + "github.com/gin-gonic/gin" +) + +var warnOnce sync.Once + +// gatewayAuth guards peer-management APIs. Production requires a gateway-issued +// short-lived PASETO (Authorization) plus the per-node key (X-Erebrus-Node-Key). +// Debug mode still accepts the legacy bearer node key in Authorization. +func (s *Server) gatewayAuth() gin.HandlerFunc { + nodeKey := s.cfg.EffectiveNodeKey() + gwPub := s.cfg.GatewayPublicKey + debug := s.cfg.RunType == "debug" + return func(c *gin.Context) { + if nodeKey == "" { + if debug { + warnOnce.Do(func() { + slog.Warn("NODE_KEY not set — peer API is UNAUTHENTICATED (debug only)") + }) + c.Next() + return + } + warnOnce.Do(func() { + slog.Error("NODE_KEY not set in release mode — peer API is DISABLED until configured") + }) + c.AbortWithStatusJSON(http.StatusServiceUnavailable, + gin.H{"error": "node API disabled: NODE_KEY not configured"}) + return + } + + bearer := strings.TrimSpace(strings.TrimPrefix(c.GetHeader("Authorization"), "Bearer ")) + headerKey := strings.TrimSpace(c.GetHeader("X-Erebrus-Node-Key")) + + if gwPub != "" && bearer != "" && headerKey != "" { + if _, err := gatewayauth.VerifyGatewayCall(bearer, gwPub, s.cfg.NodeID); err == nil { + if subtle.ConstantTimeCompare([]byte(headerKey), []byte(nodeKey)) == 1 { + c.Next() + return + } + } + } + + // Debug fallback: legacy single bearer (NODE_API_TOKEN style). + if debug && bearer != "" && subtle.ConstantTimeCompare([]byte(bearer), []byte(nodeKey)) == 1 { + c.Next() + return + } + + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"}) + } +} \ No newline at end of file diff --git a/internal/api/peers.go b/internal/api/peers.go new file mode 100644 index 0000000..7f370a9 --- /dev/null +++ b/internal/api/peers.go @@ -0,0 +1,73 @@ +package api + +import ( + "errors" + "log/slog" + "net/http" + + "github.com/NetSepio/erebrus/internal/store" + "github.com/gin-gonic/gin" +) + +func (s *Server) handlePutPeer(c *gin.Context) { + id := c.Param("id") + var req PeerRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid JSON body"}) + return + } + if req.Name == "" || req.WGPublicKey == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "name and wg_public_key are required"}) + return + } + if s.status == "draining" { + c.JSON(http.StatusConflict, gin.H{"error": "node is draining"}) + return + } + bundle, err := s.prov.UpsertPeer(c.Request.Context(), id, req) + if err != nil { + if errors.Is(err, store.ErrSubnetExhausted) { + c.JSON(http.StatusConflict, gin.H{"error": "address pool exhausted"}) + return + } + // Bad WireGuard key is a client error; everything else is internal. + // Detail is logged, never returned, to avoid leaking internals. + slog.Warn("provision peer failed", "peer", id, "err", err) + c.JSON(http.StatusBadRequest, gin.H{"error": "could not provision peer"}) + return + } + c.JSON(http.StatusOK, bundle) +} + +func (s *Server) handleDeletePeer(c *gin.Context) { + if err := s.prov.DeletePeer(c.Request.Context(), c.Param("id")); err != nil { + slog.Error("delete peer failed", "peer", c.Param("id"), "err", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) + return + } + c.Status(http.StatusNoContent) +} + +func (s *Server) handleCredentials(c *gin.Context) { + bundle, err := s.prov.Credentials(c.Request.Context(), c.Param("id")) + if err != nil { + if errors.Is(err, store.ErrNotFound) { + c.JSON(http.StatusNotFound, gin.H{"error": "unknown peer"}) + return + } + slog.Error("fetch credentials failed", "peer", c.Param("id"), "err", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) + return + } + c.JSON(http.StatusOK, bundle) +} + +func (s *Server) handleListPeers(c *gin.Context) { + peers, err := s.prov.ListPeers(c.Request.Context()) + if err != nil { + slog.Error("list peers failed", "err", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) + return + } + c.JSON(http.StatusOK, peers) +} diff --git a/internal/api/server.go b/internal/api/server.go new file mode 100644 index 0000000..3b56a68 --- /dev/null +++ b/internal/api/server.go @@ -0,0 +1,185 @@ +// Package api serves the node's HTTP REST surface (Gin) under /api/v2. It +// replaces the v1 api/v1 tree and the deleted gRPC server. Provisioning logic +// lives in the Provisioner so Phase 2 can extend it with sing-box credentials +// without touching the handlers. +package api + +import ( + "context" + _ "embed" + "net/http" + "strconv" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/readiness" + "github.com/NetSepio/erebrus/internal/wallet" + "github.com/gin-gonic/gin" + "github.com/prometheus/client_golang/prometheus/promhttp" +) + +// indexHTML is the local dashboard served at "/". +// +//go:embed web/index.html +var indexHTML []byte + +// Provisioner turns a peer request into a stored peer and a credential bundle. +// Implemented by node.Service; abstracted so handlers stay transport-only. +type Provisioner interface { + UpsertPeer(ctx context.Context, id string, req PeerRequest) (*CredentialBundle, error) + DeletePeer(ctx context.Context, id string) error + Credentials(ctx context.Context, id string) (*CredentialBundle, error) + ListPeers(ctx context.Context) ([]PeerInfo, error) + Stats(ctx context.Context) (*NodeStats, error) +} + +// Identity supplies the node's stable identifiers for the status endpoint. +type Identity struct { + PeerID string + DID string +} + +// Server wires the Gin engine. +type Server struct { + cfg *config.Config + prov Provisioner + id Identity + // status reflects drain state ("online" | "draining"); Phase 2 toggles it. + status string + readinessFn func() readiness.Input + wireGuardPublicKey func() string +} + +// NewServer builds the API server. +func NewServer(cfg *config.Config, prov Provisioner, id Identity) *Server { + return &Server{cfg: cfg, prov: prov, id: id, status: "online"} +} + +// SetReadinessProvider supplies live signals for readiness evaluation. +func (s *Server) SetReadinessProvider(fn func() readiness.Input) { + s.readinessFn = fn +} + +// SetWireGuardPublicKeyProvider supplies the node's WireGuard server public key. +func (s *Server) SetWireGuardPublicKeyProvider(fn func() string) { + s.wireGuardPublicKey = fn +} + +// SetStatus updates the public status field (online | draining). +func (s *Server) SetStatus(status string) { + if status == "" { + status = "online" + } + s.status = status +} + +// Router returns the configured Gin engine. +func (s *Server) Router() *gin.Engine { + if s.cfg.RunType == "debug" { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + r := gin.New() + r.Use(gin.Recovery()) + + // Local dashboard (intro, docs, live stats). + r.GET("/", func(c *gin.Context) { c.Data(http.StatusOK, "text/html; charset=utf-8", indexHTML) }) + + r.GET("/metrics", gin.WrapH(promhttp.Handler())) + r.GET("/healthz", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"status": "ok"}) }) + + v2 := r.Group("/api/v2") + v2.GET("/status", s.handleStatus) + v2.GET("/stats", s.handleStats) // coarse public aggregates for the dashboard + + authed := v2.Group("") + authed.Use(s.gatewayAuth()) + { + authed.GET("/peers", s.handleListPeers) + authed.PUT("/peers/:id", s.handlePutPeer) + authed.DELETE("/peers/:id", s.handleDeletePeer) + authed.GET("/peers/:id/credentials", s.handleCredentials) + } + return r +} + +func (s *Server) handleStatus(c *gin.Context) { + protocols := []string{"wireguard"} + if s.cfg.EnableStealth { + protocols = append(protocols, "vless-reality", "hysteria2") + } + in := readiness.Input{Cfg: s.cfg, IdentityConfigured: s.id.PeerID != ""} + if s.readinessFn != nil { + in = s.readinessFn() + in.Cfg = s.cfg + if in.IdentityConfigured == false && s.id.PeerID != "" { + in.IdentityConfigured = true + } + } + rep := readiness.Evaluate(in) + chain := s.cfg.WalletChain + if chain == "" { + chain = wallet.ChainSOL + } + idStatus := IdentityStatus{ + Configured: in.IdentityConfigured && s.cfg.Mnemonic != "", + PeerID: s.id.PeerID, + DID: s.id.DID, + WalletChain: chain, + WalletLabel: wallet.ChainLabel(chain), + } + if s.cfg.Mnemonic != "" { + if addr, err := wallet.AddressFromMnemonic(s.cfg.Mnemonic, chain); err == nil { + idStatus.WalletAddress = addr + } + } + wgPort, _ := strconv.Atoi(s.cfg.WGEndpointPort) + if wgPort == 0 { + wgPort = 51820 + } + wgPub := "" + if s.wireGuardPublicKey != nil { + wgPub = s.wireGuardPublicKey() + } + wgHost := s.cfg.WGEndpointHost + c.JSON(http.StatusOK, StatusResponse{ + Version: s.cfg.Version, + NodeName: s.cfg.NodeName, + Region: s.cfg.Region, + Status: s.status, + AccessMode: string(s.cfg.Mode.RuntimeMode), + PeerID: s.id.PeerID, + DID: s.id.DID, + Identity: idStatus, + Endpoints: EndpointsStatus{ + WireGuard: WireGuardEndpointStatus{ + Port: wgPort, + PublicKey: wgPub, + Endpoint: wgHost + ":" + strconv.Itoa(wgPort), + }, + }, + Capabilities: map[string]any{ + "access_mode": s.cfg.Mode.RuntimeMode, + "access_label": readiness.AccessModeLabel(s.cfg.Mode.RuntimeMode), + "access_hint": readiness.AccessModeHint(s.cfg.Mode.RuntimeMode), + "region_label": readiness.RegionLabel(s.cfg.Region), + "network_profile": s.cfg.Mode.NetworkProfile, + "app_hosting": s.cfg.EnableAppHosting, + "wildcard_domain": s.cfg.AppWildcardDomain, + "public_domain": s.cfg.PublicDomain, + "stealth": s.cfg.EnableStealth, + "public_api_url": readiness.PublicAPIURL(s.cfg), + }, + Protocols: protocols, + Readiness: rep, + }) +} + +func (s *Server) handleStats(c *gin.Context) { + stats, err := s.prov.Stats(c.Request.Context()) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read stats"}) + return + } + c.JSON(http.StatusOK, stats) +} diff --git a/internal/api/types.go b/internal/api/types.go new file mode 100644 index 0000000..fcef5f4 --- /dev/null +++ b/internal/api/types.go @@ -0,0 +1,104 @@ +package api + +const BundleVersion = 2 + +// TransportEntry describes one carrier in a v2 credential bundle. +type TransportEntry struct { + Kind string `json:"kind"` + URI string `json:"uri,omitempty"` +} + +// CredentialBundle is the unified response for peer provisioning and re-fetch. +type CredentialBundle struct { + BundleVersion int `json:"bundle_version"` + NodeID string `json:"node_id,omitempty"` + ID string `json:"id"` + IssuedAt int64 `json:"issued_at"` + ExpiresAt int64 `json:"expires_at,omitempty"` + WireGuard WireGuardBundle `json:"wireguard"` + Transports []TransportEntry `json:"transports,omitempty"` + VLESSURI string `json:"vless_uri,omitempty"` + Hysteria2URI string `json:"hysteria2_uri,omitempty"` + SingboxProfile any `json:"singbox_profile,omitempty"` + ServiceDiscovery map[string]any `json:"service_discovery,omitempty"` +} + +// WireGuardBundle holds everything a client needs for the WireGuard fast path. +type WireGuardBundle struct { + ClientConf string `json:"client_conf"` + ServerPublicKey string `json:"server_public_key"` + Endpoint string `json:"endpoint"` + Address string `json:"address"` + DNS string `json:"dns"` +} + +// PeerRequest is the body of PUT /api/v2/peers/{id}. +type PeerRequest struct { + Name string `json:"name"` + Wallet string `json:"wallet"` + WGPublicKey string `json:"wg_public_key"` + WGPresharedKey string `json:"wg_preshared_key"` + ExpiresAt int64 `json:"expires_at"` +} + +// PeerInfo is the metadata-only listing item (no credentials). +type PeerInfo struct { + ID string `json:"id"` + Name string `json:"name"` + WGAllowedIP string `json:"wg_allowed_ip"` + Enabled bool `json:"enabled"` + CreatedAt int64 `json:"created_at"` + ExpiresAt int64 `json:"expires_at"` +} + +// NodeStats is the coarse, public operational snapshot powering the local +// dashboard. It deliberately exposes only aggregates — never per-client data. +type NodeStats struct { + Status string `json:"status"` + Version string `json:"version"` + Region string `json:"region"` + Protocols []string `json:"protocols"` + TotalPeers int `json:"total_peers"` // provisioned in the store + ConnectedPeers int `json:"connected_peers"` // handshake in the last 3m + RxBytes int64 `json:"rx_bytes"` // cumulative since interface up + TxBytes int64 `json:"tx_bytes"` + UptimeSec int64 `json:"uptime_sec"` +} + +// WireGuardEndpointStatus is the node's WireGuard listen endpoint (server key + port). +type WireGuardEndpointStatus struct { + Port int `json:"port"` + PublicKey string `json:"public_key"` + Endpoint string `json:"endpoint"` // host:port clients dial +} + +// EndpointsStatus mirrors the gateway discovery projection for this node. +type EndpointsStatus struct { + WireGuard WireGuardEndpointStatus `json:"wireguard"` +} + +// IdentityStatus summarizes the node's cryptographic identity (never includes secrets). +type IdentityStatus struct { + Configured bool `json:"configured"` + PeerID string `json:"peer_id"` + DID string `json:"did"` + WalletChain string `json:"wallet_chain,omitempty"` + WalletLabel string `json:"wallet_chain_label,omitempty"` + WalletAddress string `json:"wallet_address,omitempty"` +} + +// StatusResponse is the public node status. +type StatusResponse struct { + Version string `json:"version"` + NodeName string `json:"node_name"` + Region string `json:"region"` + Status string `json:"status"` + AccessMode string `json:"access_mode"` + PeerID string `json:"peer_id"` // deprecated: use identity.peer_id + DID string `json:"did"` // deprecated: use identity.did + Identity IdentityStatus `json:"identity"` + Endpoints EndpointsStatus `json:"endpoints"` + Capabilities map[string]any `json:"capabilities"` + Protocols []string `json:"protocols"` + Readiness any `json:"readiness"` +} diff --git a/internal/api/web/index.html b/internal/api/web/index.html new file mode 100644 index 0000000..cd6b903 --- /dev/null +++ b/internal/api/web/index.html @@ -0,0 +1,327 @@ + + + + + +Erebrus Node + + + + + + + +
+
+

Erebrus Node

+

VPN exit node for the Erebrus network. Shows live stats only — no traffic logs are kept.

+

+
+ Status + Access + Region +
+
+
+ +
+
Connected
peers
+
Download
/s
+
Upload
/s
+
Total transfer
+
Uptime
+
Version
+
+ +

Node identity

+
+
+
+
libp2p Peer ID
+
+ +
+
+
DID
+
+ +
+ + + +
+
+ +

Readiness

+
+ +

Public API

+
+
GET/api/v2/status
identity, access, readiness
+
GET/api/v2/stats
bandwidth, uptime
+
GET/metrics
Prometheus
+
GET/healthz
liveness
+
+ +

Authenticated API · Bearer NODE_API_TOKEN

+
+
GET/api/v2/peers
list peers
+
PUT/api/v2/peers/:id
provision client
+
DEL/api/v2/peers/:id
remove client
+
GET/api/v2/peers/:id/credentials
credential bundle
+
+ +
+
Erebrus DePIN · Source · erebrus.io
+
+ + + + \ No newline at end of file diff --git a/internal/carriers/rotate.go b/internal/carriers/rotate.go new file mode 100644 index 0000000..c3d9ba9 --- /dev/null +++ b/internal/carriers/rotate.go @@ -0,0 +1,116 @@ +// Package carriers manages stealth carrier credential rotation with grace periods. +package carriers + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + "log/slog" + "time" + + "github.com/NetSepio/erebrus/internal/stealth" + "github.com/NetSepio/erebrus/internal/store" +) + +// Rotator rotates node-wide carrier secrets. +type Rotator struct { + St *store.Store + Stealth *stealth.Manager +} + +// Options configures a rotation run. +type Options struct { + GracePeriod time.Duration + PeerID string // optional scope label for audit +} + +// Rotate generates new carrier credentials, archives hashes of the previous +// secrets with a grace expiry, and restarts stealth listeners. +func (r *Rotator) Rotate(ctx context.Context, opt Options) error { + if r.St == nil || r.Stealth == nil { + return fmt.Errorf("rotator not configured") + } + if opt.GracePeriod <= 0 { + opt.GracePeriod = 24 * time.Hour + } + now := time.Now().Unix() + expires := time.Now().Add(opt.GracePeriod).Unix() + + scope := "node" + peer := opt.PeerID + if peer != "" { + scope = "peer" + } + + // Archive current secret hashes before rotation. + if err := r.archiveCurrent(ctx, scope, peer, expires); err != nil { + return err + } + + if err := r.Stealth.RotateAllSecrets(ctx); err != nil { + return fmt.Errorf("rotate secrets: %w", err) + } + + // Record new active credentials (hashes only). + for _, item := range []struct{ transport, material string }{ + {"vless_reality", r.Stealth.Params().VLESSUUID}, + {"hysteria2", r.Stealth.Params().Hysteria2Password}, + {"reality_short_id", r.Stealth.Params().RealityShortID}, + } { + if item.material == "" { + continue + } + if err := r.St.InsertCarrierCredential(ctx, store.CarrierCredential{ + Transport: item.transport, + SecretHash: hashSecret(item.material), + CreatedAt: now, + Active: true, + Scope: scope, + PeerID: peer, + }); err != nil { + return err + } + } + + n, _ := r.St.DeactivateExpiredCarrierCredentials(ctx, now) + if n > 0 { + slog.Info("carrier credentials expired", "count", n) + } + slog.Info("carrier rotation complete", "grace_period", opt.GracePeriod.String(), "scope", scope) + return nil +} + +func (r *Rotator) archiveCurrent(ctx context.Context, scope, peerID string, expiresAt int64) error { + p := r.Stealth.Params() + if !p.Enabled { + return nil + } + now := time.Now().Unix() + for _, item := range []struct{ transport, material string }{ + {"vless_reality", p.VLESSUUID}, + {"hysteria2", p.Hysteria2Password}, + {"reality_short_id", p.RealityShortID}, + } { + if item.material == "" { + continue + } + if err := r.St.InsertCarrierCredential(ctx, store.CarrierCredential{ + Transport: item.transport, + SecretHash: hashSecret(item.material), + CreatedAt: now, + ExpiresAt: expiresAt, + Active: true, + Scope: scope, + PeerID: peerID, + }); err != nil { + return err + } + } + return nil +} + +func hashSecret(s string) string { + sum := sha256.Sum256([]byte(s)) + return hex.EncodeToString(sum[:]) +} diff --git a/internal/carriers/rotate_test.go b/internal/carriers/rotate_test.go new file mode 100644 index 0000000..1d93a69 --- /dev/null +++ b/internal/carriers/rotate_test.go @@ -0,0 +1,13 @@ +package carriers + +import ( + "testing" +) + +func TestHashSecretStable(t *testing.T) { + a := hashSecret("test-secret") + b := hashSecret("test-secret") + if a != b || len(a) != 64 { + t.Fatalf("hash = %q", a) + } +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 0000000..7ef7eaf --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,324 @@ +// Package config centralizes all environment-derived configuration for the +// Erebrus v2 node. It replaces the scattered os.Getenv calls of v1. +package config + +import ( + "fmt" + "os" + "strconv" + "strings" +) + +// Config holds the full node configuration. +type Config struct { + // app + RunType string // debug | release + BindAddr string // SERVER / API_BIND_ADDR + HTTPPort string + NodeName string + Region string + Version string + + // runtime model (v2.1+) + Mode ModeSettings + UnsafePublicAPI bool + PublicDomain string + WildcardDomain string + PublicGatewayEnabled bool + PublicHTTPPort string + PublicHTTPSPort string + AutoTLS bool + + // identity + Mnemonic string + + // gateway + GatewayURL string + GatewayPeerMultiaddr string + P2PListenPort string + NodeID string // gateway-assigned; persisted in SQLite when registered + NodeToken string // gateway-issued PASETO for WS control plane + WalletChain string // sol | evm — signs gateway machine enrollment challenge + OrgEnrollmentSecret string // EREBRUS_ORG_ENROLLMENT_SECRET — org workspace credential + APIPublicURL string // URL gateway uses for peer provisioning (api_base_url) + GatewayAutoRegister bool + GatewayPublicKey string // gateway Ed25519 public key (hex) for verifying API calls + + // NodeKey is the per-node bearer (NODE_KEY). NODE_API_TOKEN is a legacy alias. + NodeKey string + NodeAPIToken string // deprecated alias for NodeKey + + // wireguard + WGConfDir string + WGInterface string // e.g. "wg0" + WGEndpointHost string + WGEndpointPort string // WG_PORT alias + StealthTCPPort string // STEALTH_TCP_PORT — VLESS+REALITY + StealthUDPPort string // STEALTH_UDP_PORT — Hysteria2/QUIC + WGIPv4Subnet string // e.g. "10.0.0.1/16" + WGDNS string + WGPostUp string + WGPostDown string + WGPreUp string + WGPreDown string + + // stealth protocols — sing-box carriers for when WireGuard's UDP is + // throttled or DPI-blocked. VLESS+REALITY presents as ordinary TLS to a + // borrowed SNI; Hysteria2 presents as QUIC/HTTP3. Both wrap the same + // WireGuard tunnel (WG stays the endpoint). + EnableStealth bool + VLESSPort string + Hysteria2Port string + RealityServerNames []string // SNIs the REALITY handshake borrows; first is the dial target + RealityHandshakeServer string // host:port the node proxies the real TLS handshake to + Hysteria2ObfsPassword string + EnableTUIC bool + + // node-local state + StateDir string + + // app hosting (Phase 5) + EnableAppHosting bool + AppWildcardDomain string + + // registrar + ChainRegistration string // off | solana + + // private DNS (Phase 2) + PrivateDNSEnabled bool + PrivateDNSDomain string + PrivateDNSAddr string + UpstreamDNS string + DNSQueryLogs bool +} + +// Load reads configuration from the environment, applying sane defaults. +func Load() *Config { + bindAddr := env("API_BIND_ADDR", "") + if bindAddr == "" { + bindAddr = env("SERVER", "0.0.0.0") + } + c := &Config{ + RunType: env("RUNTYPE", "release"), + BindAddr: bindAddr, + HTTPPort: env("HTTP_PORT", "9080"), + UnsafePublicAPI: boolEnv("UNSAFE_PUBLIC_API", false), + PublicDomain: firstEnv("EREBRUS_DOMAIN", "PUBLIC_DOMAIN", ""), + WildcardDomain: env("WILDCARD_DOMAIN", os.Getenv("EREBRUS_WILDCARD_DOMAIN")), + PublicGatewayEnabled: boolEnv("PUBLIC_GATEWAY_ENABLED", boolEnv("EREBRUS_PUBLIC_GATEWAY", false)), + PublicHTTPPort: env("PUBLIC_HTTP_PORT", "80"), + PublicHTTPSPort: env("PUBLIC_HTTPS_PORT", "443"), + AutoTLS: boolEnv("AUTO_TLS", true), + NodeName: env("NODE_NAME", hostnameOr("erebrus-node")), + Region: env("REGION", "unknown"), + Version: Version, + Mnemonic: os.Getenv("MNEMONIC"), + GatewayURL: env("GATEWAY_URL", ""), + GatewayPeerMultiaddr: env("GATEWAY_PEER_MULTIADDR", ""), + P2PListenPort: env("P2P_LISTEN_PORT", "9002"), + NodeID: os.Getenv("NODE_ID"), + NodeToken: os.Getenv("NODE_TOKEN"), + WalletChain: env("WALLET_CHAIN", "sol"), + OrgEnrollmentSecret: firstEnv("EREBRUS_ORG_ENROLLMENT_SECRET", "ORG_ENROLLMENT_SECRET", ""), + APIPublicURL: os.Getenv("API_PUBLIC_URL"), + GatewayAutoRegister: boolEnv("GATEWAY_AUTO_REGISTER", true), + GatewayPublicKey: os.Getenv("GATEWAY_PUBLIC_KEY"), + NodeKey: firstEnv("NODE_KEY", "NODE_API_TOKEN", ""), + NodeAPIToken: firstEnv("NODE_KEY", "NODE_API_TOKEN", ""), + WGConfDir: env("WG_CONF_DIR", "/etc/wireguard"), + WGInterface: normalizeInterface(env("WG_INTERFACE_NAME", "wg0")), + WGEndpointHost: os.Getenv("WG_ENDPOINT_HOST"), + WGEndpointPort: firstEnv("WG_PORT", "WG_ENDPOINT_PORT", "51820"), + StealthTCPPort: firstEnv("STEALTH_TCP_PORT", "VLESS_PORT", "8443"), + StealthUDPPort: firstEnv("STEALTH_UDP_PORT", "HYSTERIA2_PORT", "4443"), + WGIPv4Subnet: env("WG_IPv4_SUBNET", "10.0.0.1/16"), + WGDNS: env("WG_DNS", "1.1.1.1"), + WGPostUp: os.Getenv("WG_POST_UP"), + WGPostDown: os.Getenv("WG_POST_DOWN"), + WGPreUp: os.Getenv("WG_PRE_UP"), + WGPreDown: os.Getenv("WG_PRE_DOWN"), + EnableStealth: boolEnv("ENABLE_STEALTH", true), + VLESSPort: "", // synced from StealthTCPPort below + Hysteria2Port: "", // synced from StealthUDPPort below + RealityServerNames: splitCSV(env("REALITY_SERVER_NAMES", "www.microsoft.com")), + RealityHandshakeServer: env("REALITY_HANDSHAKE_SERVER", ""), + Hysteria2ObfsPassword: os.Getenv("HYSTERIA2_OBFS_PASSWORD"), + EnableTUIC: boolEnv("ENABLE_TUIC", false), + StateDir: env("STATE_DIR", "/var/lib/erebrus"), + EnableAppHosting: boolEnv("ENABLE_APP_HOSTING", false), + AppWildcardDomain: os.Getenv("APP_WILDCARD_DOMAIN"), + ChainRegistration: env("CHAIN_REGISTRATION", "off"), + PrivateDNSEnabled: boolEnv("PRIVATE_DNS_ENABLED", false), + PrivateDNSDomain: env("PRIVATE_DNS_DOMAIN", "ere"), + PrivateDNSAddr: os.Getenv("PRIVATE_DNS_ADDR"), + UpstreamDNS: env("UPSTREAM_DNS", "1.1.1.1"), + DNSQueryLogs: boolEnv("DNS_QUERY_LOGS", false), + } + if mode, err := ParseModeSettingsFromEnv(); err == nil { + c.Mode = mode + } + c.VLESSPort = c.StealthTCPPort + c.Hysteria2Port = c.StealthUDPPort + // The management peer API shares the HTTP listener. When it is bound to a + // non-loopback address it is reachable off-host (token-gated, fail-closed), + // so always surface that as a conscious decision — not just under the + // UNSAFE_PUBLIC_API flag. + if !isLoopbackAddr(c.BindAddr) { + c.Mode.Warnings = append(c.Mode.Warnings, fmt.Sprintf( + "WARNING: management API bound to %s:%s — the token-gated peer API is reachable off-host. "+ + "Firewall this port to the gateway/trusted sources, or set API_BIND_ADDR=127.0.0.1.", + c.BindAddr, c.HTTPPort)) + } + return c +} + +// isLoopbackAddr reports whether the bind address is loopback-only. +func isLoopbackAddr(addr string) bool { + switch addr { + case "127.0.0.1", "::1", "localhost": + return true + } + return strings.HasPrefix(addr, "127.") +} + +// Validate returns an error if required fields are missing or invalid. +func (c *Config) Validate() error { + mode, err := ParseModeSettingsFromEnv() + if err != nil { + return err + } + c.Mode = mode + var missing []string + if c.Mnemonic == "" { + missing = append(missing, "MNEMONIC") + } + if c.WGEndpointHost == "" { + missing = append(missing, "WG_ENDPOINT_HOST") + } + if len(missing) > 0 { + return fmt.Errorf("missing required config: %s", strings.Join(missing, ", ")) + } + if c.Mode.IsPublic() && c.PublicDomain == "" && c.EnableAppHosting { + c.Mode.Warnings = append(c.Mode.Warnings, + "WARNING: Public access mode with ENABLE_APP_HOSTING but no PUBLIC_DOMAIN set; public edge routing may be incomplete.") + } + if c.Mode.IsPublic() && (c.StealthTCPPort != "443" || c.StealthUDPPort != "443") { + c.Mode.Warnings = append(c.Mode.Warnings, + "WARNING: Public access mode production should expose stealth on 443/tcp and 443/udp (STEALTH_TCP_PORT/STEALTH_UDP_PORT) for best reachability.") + } + return nil +} + +// DBPath is the SQLite file path. +func (c *Config) DBPath() string { return c.StateDir + "/erebrus.db" } + +// PublicAPIBaseURL returns the URL the gateway should use for peer provisioning. +func (c *Config) PublicAPIBaseURL() string { + if c.APIPublicURL != "" { + return strings.TrimRight(c.APIPublicURL, "/") + } + host := c.WGEndpointHost + if host == "" { + host = "127.0.0.1" + } + return fmt.Sprintf("http://%s:%s", host, c.HTTPPort) +} + +// GatewayEnabled reports whether the node should connect to the gateway control plane. +func (c *Config) GatewayEnabled() bool { return strings.TrimSpace(c.GatewayURL) != "" } + +// EffectiveNodeKey returns the per-node API bearer (NODE_KEY legacy: NODE_API_TOKEN). +func (c *Config) EffectiveNodeKey() string { + if k := strings.TrimSpace(c.NodeKey); k != "" { + return k + } + return strings.TrimSpace(c.NodeAPIToken) +} + +// WGEndpointPortInt parses the endpoint port. +func (c *Config) WGEndpointPortInt() int { + n, _ := strconv.Atoi(c.WGEndpointPort) + return n +} + +// VLESSPortInt parses the VLESS+REALITY listen port. +func (c *Config) VLESSPortInt() int { n, _ := strconv.Atoi(c.VLESSPort); return n } + +// Hysteria2PortInt parses the Hysteria2 listen port. +func (c *Config) Hysteria2PortInt() int { n, _ := strconv.Atoi(c.Hysteria2Port); return n } + +// RealitySNI returns the primary SNI the REALITY handshake borrows. +func (c *Config) RealitySNI() string { + if len(c.RealityServerNames) > 0 { + return c.RealityServerNames[0] + } + return "www.microsoft.com" +} + +// RealityHandshakeTarget returns host:port the node proxies the real TLS +// handshake to. Defaults to the primary SNI on :443. +func (c *Config) RealityHandshakeTarget() string { + if c.RealityHandshakeServer != "" { + return c.RealityHandshakeServer + } + return c.RealitySNI() + ":443" +} + +func firstEnv(keys ...string) string { + if len(keys) == 0 { + return "" + } + def := keys[len(keys)-1] + keys = keys[:len(keys)-1] + for _, k := range keys { + if v := os.Getenv(k); v != "" { + return v + } + } + return def +} + +func env(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +func boolEnv(key string, def bool) bool { + v := os.Getenv(key) + if v == "" { + return def + } + b, err := strconv.ParseBool(v) + if err != nil { + return def + } + return b +} + +func splitCSV(s string) []string { + if s == "" { + return nil + } + parts := strings.Split(s, ",") + out := make([]string, 0, len(parts)) + for _, p := range parts { + if t := strings.TrimSpace(p); t != "" { + out = append(out, t) + } + } + return out +} + +// normalizeInterface accepts "wg0" or "wg0.conf" and returns "wg0". +func normalizeInterface(s string) string { + return strings.TrimSuffix(s, ".conf") +} + +func hostnameOr(def string) string { + if h, err := os.Hostname(); err == nil && h != "" { + return h + } + return def +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..b5e3594 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,149 @@ +package config + +import ( + "strings" + "testing" +) + +func TestParseModeDefaults(t *testing.T) { + m, err := ParseModeSettings("", "", "") + if err != nil { + t.Fatal(err) + } + if m.RuntimeMode != ModePublic || m.Deploy != DeployContainer || m.NetworkProfile != NetworkBridge { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } + if m.GatewayAccessMode() != "public" { + t.Fatalf("gateway access = %q, want public", m.GatewayAccessMode()) + } +} + +func TestParseAccessSharedDeprecated(t *testing.T) { + m, err := ParseModeSettings("shared", "container", "bridge") + if err != nil { + t.Fatal(err) + } + if !m.IsPrivate() || m.Deploy != DeployContainer || m.NetworkProfile != NetworkBridge { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } + if len(m.Warnings) == 0 || !strings.Contains(m.Warnings[0], "deprecated") { + t.Fatalf("expected shared deprecation warning, got %v", m.Warnings) + } + if m.GatewayAccessMode() != "private" { + t.Fatalf("gateway access = %q, want private", m.GatewayAccessMode()) + } +} + +func TestParsePublicHost(t *testing.T) { + m, err := ParseModeSettings("public", "host", "host-network") + if err != nil { + t.Fatal(err) + } + if !m.IsPublic() || m.Deploy != DeployHost || m.NetworkProfile != NetworkHostNetwork { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } +} + +func TestPublicContainer(t *testing.T) { + m, err := ParseModeSettings("public", "container", "") + if err != nil { + t.Fatal(err) + } + if !m.IsPublic() || m.Deploy != DeployContainer || m.NetworkProfile != NetworkBridge { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } +} + +func TestLegacyAccessInModeEnv(t *testing.T) { + m, err := ParseModeSettings("", "gateway", "host-network") + if err != nil { + t.Fatal(err) + } + if !m.IsPublic() || m.Deploy != DeployContainer { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } + if len(m.Warnings) == 0 || !strings.Contains(m.Warnings[0], "deprecated") { + t.Fatalf("expected deprecation warning, got %v", m.Warnings) + } +} + +func TestLegacyDockerDeployAlias(t *testing.T) { + m, err := ParseModeSettings("", "docker", "") + if err != nil { + t.Fatal(err) + } + if m.RuntimeMode != ModePublic || m.Deploy != DeployContainer || m.NetworkProfile != NetworkBridge { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } + if len(m.Warnings) == 0 || !strings.Contains(m.Warnings[0], "deprecated") { + t.Fatalf("expected deprecation warning, got %v", m.Warnings) + } +} + +func TestLegacyHostDeployDecoupledFromAccess(t *testing.T) { + m, err := ParseModeSettings("", "host", "") + if err != nil { + t.Fatal(err) + } + if m.RuntimeMode != ModePublic || m.Deploy != DeployHost || m.NetworkProfile != NetworkHostNetwork { + t.Fatalf("got access=%s deploy=%s profile=%s", m.RuntimeMode, m.Deploy, m.NetworkProfile) + } +} + +func TestPublicBridgeWarning(t *testing.T) { + m, err := ParseModeSettings("public", "container", "bridge") + if err != nil { + t.Fatal(err) + } + found := false + for _, w := range m.Warnings { + if strings.Contains(w, "host-network is recommended") { + found = true + break + } + } + if !found { + t.Fatalf("expected public+bridge warning, got %v", m.Warnings) + } +} + +func TestInvalidAccess(t *testing.T) { + if _, err := ParseModeSettings("astro", "container", "bridge"); err == nil { + t.Fatal("expected error for invalid access") + } +} + +func TestInvalidDeploy(t *testing.T) { + if _, err := ParseModeSettings("private", "vm", "bridge"); err == nil { + t.Fatal("expected error for invalid deploy") + } +} + +func TestInvalidProfile(t *testing.T) { + if _, err := ParseModeSettings("private", "container", "container"); err == nil { + t.Fatal("expected error for invalid profile") + } +} + +func TestLoadAPIBindDefault(t *testing.T) { + t.Setenv("SERVER", "") + t.Setenv("API_BIND_ADDR", "") + t.Setenv("UNSAFE_PUBLIC_API", "") + t.Setenv("MNEMONIC", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about") + t.Setenv("WG_ENDPOINT_HOST", "203.0.113.1") + c := Load() + if c.BindAddr != "0.0.0.0" { + t.Fatalf("bind addr = %q, want 0.0.0.0 default during testing", c.BindAddr) + } +} + +func TestLoadAPIBindOverride(t *testing.T) { + t.Setenv("API_BIND_ADDR", "127.0.0.1") + t.Setenv("SERVER", "0.0.0.0") + t.Setenv("MNEMONIC", "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about") + t.Setenv("WG_ENDPOINT_HOST", "203.0.113.1") + c := Load() + if c.BindAddr != "127.0.0.1" { + t.Fatalf("bind addr = %q, want 127.0.0.1 from API_BIND_ADDR", c.BindAddr) + } +} \ No newline at end of file diff --git a/internal/config/mode.go b/internal/config/mode.go new file mode 100644 index 0000000..20f761e --- /dev/null +++ b/internal/config/mode.go @@ -0,0 +1,206 @@ +package config + +import ( + "fmt" + "os" + "strings" +) + +// RuntimeMode is who may discover and use the node (access policy). +type RuntimeMode string + +const ( + ModePrivate RuntimeMode = "private" // operator devices only + ModeShared RuntimeMode = "shared" // wallet allowlist on gateway + ModePublic RuntimeMode = "public" // open directory; host earnings (future) +) + +// DeployMode is how the node process is run on the machine. +type DeployMode string + +const ( + DeployContainer DeployMode = "container" // Docker / compose (bridge networking) + DeployHost DeployMode = "host" // bare metal / systemd (host-network) +) + +// NetworkProfile describes container vs host networking (advanced override). +type NetworkProfile string + +const ( + NetworkBridge NetworkProfile = "bridge" + NetworkHostNetwork NetworkProfile = "host-network" + NetworkNative NetworkProfile = "native" +) + +// Legacy env/install aliases (deprecated). +const ( + legacyModeGateway = "gateway" + legacyModeDocker = "docker" +) + +// ModeSettings holds parsed access, deploy, and network profile. +type ModeSettings struct { + RuntimeMode RuntimeMode // access: private | shared | public + Deploy DeployMode // container | host + NetworkProfile NetworkProfile + Warnings []string +} + +// ParseModeSettings reads EREBRUS_ACCESS, EREBRUS_MODE (deploy), and +// EREBRUS_NETWORK_PROFILE with legacy fallbacks. +func ParseModeSettings(accessRaw, deployRaw, profileRaw string) (ModeSettings, error) { + accessRaw = strings.ToLower(strings.TrimSpace(accessRaw)) + deployRaw = strings.ToLower(strings.TrimSpace(deployRaw)) + profileRaw = strings.ToLower(strings.TrimSpace(profileRaw)) + + var warnings []string + + // Legacy: EREBRUS_MODE used to mean access (private/shared/public/gateway). + if accessRaw == "" && isAccessToken(deployRaw) { + warnings = append(warnings, fmt.Sprintf( + "WARNING: EREBRUS_MODE=%q is deprecated for access. Use EREBRUS_ACCESS=%s and EREBRUS_MODE=container|host for deploy.", + deployRaw, normalizeAccessToken(deployRaw))) + accessRaw = deployRaw + deployRaw = "" + } + + access, accessWarnings, err := parseAccess(accessRaw) + if err != nil { + return ModeSettings{}, err + } + warnings = append(warnings, accessWarnings...) + + deploy, deployWarnings, err := parseDeploy(deployRaw) + if err != nil { + return ModeSettings{}, err + } + warnings = append(warnings, deployWarnings...) + + profile, profileWarnings, err := parseNetworkProfile(profileRaw, deploy) + if err != nil { + return ModeSettings{}, err + } + warnings = append(warnings, profileWarnings...) + + if access == ModePublic && profile == NetworkBridge { + warnings = append(warnings, + "WARNING: Public access with bridge networking may work, but host-network is recommended for production nodes because WireGuard routing, 443 binding, reverse proxying, and debugging are simpler.") + } + if profile == NetworkNative { + warnings = append(warnings, + "WARNING: EREBRUS_NETWORK_PROFILE=native is experimental; container deployment is recommended.") + } + + return ModeSettings{ + RuntimeMode: access, + Deploy: deploy, + NetworkProfile: profile, + Warnings: warnings, + }, nil +} + +// ParseModeSettingsFromEnv is the env-backed entry point. +func ParseModeSettingsFromEnv() (ModeSettings, error) { + return ParseModeSettings( + os.Getenv("EREBRUS_ACCESS"), + os.Getenv("EREBRUS_MODE"), + os.Getenv("EREBRUS_NETWORK_PROFILE"), + ) +} + +func isAccessToken(s string) bool { + switch s { + case "", string(ModePrivate), string(ModeShared), string(ModePublic), legacyModeGateway: + return s != "" + default: + return false + } +} + +func normalizeAccessToken(s string) string { + if s == legacyModeGateway { + return string(ModePublic) + } + return s +} + +func parseAccess(raw string) (RuntimeMode, []string, error) { + var warnings []string + switch raw { + case "": + return ModePublic, warnings, nil + case string(ModePrivate): + return ModePrivate, warnings, nil + case string(ModeShared): + warnings = append(warnings, + "WARNING: EREBRUS_ACCESS=shared is deprecated; org membership now controls private node access. Treating as private.") + return ModePrivate, warnings, nil + case string(ModePublic): + return ModePublic, warnings, nil + case legacyModeGateway: + warnings = append(warnings, fmt.Sprintf( + "WARNING: access %q is deprecated. Use EREBRUS_ACCESS=%s.", legacyModeGateway, ModePublic)) + return ModePublic, warnings, nil + default: + return "", nil, fmt.Errorf("EREBRUS_ACCESS must be private or public (got %q)", raw) + } +} + +func parseDeploy(raw string) (DeployMode, []string, error) { + var warnings []string + switch raw { + case "", legacyModeDocker: + if raw == legacyModeDocker { + warnings = append(warnings, fmt.Sprintf( + "WARNING: EREBRUS_MODE=%q is deprecated. Use EREBRUS_MODE=%s.", legacyModeDocker, DeployContainer)) + } + return DeployContainer, warnings, nil + case string(DeployContainer): + return DeployContainer, warnings, nil + case string(DeployHost): + return DeployHost, warnings, nil + default: + return "", nil, fmt.Errorf("EREBRUS_MODE must be container or host (got %q)", raw) + } +} + +func parseNetworkProfile(raw string, deploy DeployMode) (NetworkProfile, []string, error) { + var warnings []string + switch raw { + case "": + if deploy == DeployHost { + return NetworkHostNetwork, warnings, nil + } + return NetworkBridge, warnings, nil + case string(NetworkBridge), string(NetworkHostNetwork), string(NetworkNative): + return NetworkProfile(raw), warnings, nil + default: + return "", nil, fmt.Errorf("EREBRUS_NETWORK_PROFILE must be bridge, host-network, or native (got %q)", raw) + } +} + +// IsPrivate reports whether only the operator and their devices may use the node. +func (m ModeSettings) IsPrivate() bool { return m.RuntimeMode == ModePrivate } + +// IsShared reports whether access is limited to a gateway wallet allowlist. +func (m ModeSettings) IsShared() bool { return m.RuntimeMode == ModeShared } + +// IsPublic reports whether the node is open to entitled network users. +func (m ModeSettings) IsPublic() bool { return m.RuntimeMode == ModePublic } + +// IsGateway is deprecated; use IsPublic. +func (m ModeSettings) IsGateway() bool { return m.IsPublic() } + +// IsContainer reports Docker/compose deployment. +func (m ModeSettings) IsContainer() bool { return m.Deploy == DeployContainer } + +// IsHostDeploy reports bare-metal/systemd deployment. +func (m ModeSettings) IsHostDeploy() bool { return m.Deploy == DeployHost } + +// GatewayAccessMode maps local access policy to gateway public|private. +func (m ModeSettings) GatewayAccessMode() string { + if m.RuntimeMode == ModePublic { + return "public" + } + return "private" +} \ No newline at end of file diff --git a/internal/config/version.go b/internal/config/version.go new file mode 100644 index 0000000..ef166ed --- /dev/null +++ b/internal/config/version.go @@ -0,0 +1,5 @@ +package config + +// Version is the Erebrus node version. Overridable at build time via +// -ldflags "-X github.com/NetSepio/erebrus/internal/config.Version=x.y.z". +var Version = "2.0.0" diff --git a/internal/dns/resolver.go b/internal/dns/resolver.go new file mode 100644 index 0000000..ea8954d --- /dev/null +++ b/internal/dns/resolver.go @@ -0,0 +1,142 @@ +// Package dns provides a private VPN DNS resolver backed by the service registry. +package dns + +import ( + "context" + "fmt" + "log/slog" + "net" + "strings" + "time" + + "github.com/NetSepio/erebrus/internal/services" + "github.com/miekg/dns" +) + +// Config drives the private resolver. +type Config struct { + Enabled bool + Domain string // e.g. "ere" + ListenAddr string // e.g. "10.66.0.1:53" + Upstream string // e.g. "1.1.1.1:53" + QueryLogs bool +} + +// Server resolves . from the registry and forwards other queries. +type Server struct { + cfg Config + reg *services.Registry + srv *dns.Server +} + +// New constructs a DNS server (call Start to listen). +func New(cfg Config, reg *services.Registry) *Server { + return &Server{cfg: cfg, reg: reg} +} + +// Start listens until ctx is cancelled. +func (s *Server) Start(ctx context.Context) error { + if !s.cfg.Enabled { + return nil + } + mux := dns.NewServeMux() + mux.HandleFunc(".", s.handle) + s.srv = &dns.Server{Addr: s.cfg.ListenAddr, Net: "udp", Handler: mux} + go func() { + <-ctx.Done() + _ = s.srv.Shutdown() + }() + slog.Info("private DNS listening", "addr", s.cfg.ListenAddr, "domain", s.cfg.Domain) + return s.srv.ListenAndServe() +} + +func (s *Server) handle(w dns.ResponseWriter, r *dns.Msg) { + m := new(dns.Msg) + m.SetReply(r) + m.Authoritative = true + + for _, q := range r.Question { + if q.Qtype != dns.TypeA { + continue + } + name := strings.TrimSuffix(strings.ToLower(q.Name), ".") + suffix := "." + strings.ToLower(s.cfg.Domain) + if !strings.HasSuffix(name, suffix) { + continue + } + svcName := strings.TrimSuffix(name, suffix) + // Support ollama.local.ere -> ollama + if i := strings.Index(svcName, "."); i >= 0 { + svcName = svcName[:i] + } + svc, err := s.reg.FindByName(context.Background(), svcName) + if err != nil { + m.Rcode = dns.RcodeNameError + continue + } + host, _, err := net.SplitHostPort(svc.InternalAddr) + if err != nil { + host = strings.Split(svc.InternalAddr, ":")[0] + } + ip := net.ParseIP(host) + if ip == nil || ip.To4() == nil { + m.Rcode = dns.RcodeNameError + continue + } + rr := &dns.A{ + Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: 60}, + A: ip.To4(), + } + m.Answer = append(m.Answer, rr) + if s.cfg.QueryLogs { + slog.Debug("dns query", "name", q.Name, "answer", ip.String()) + } + } + + if len(m.Answer) == 0 && len(r.Question) > 0 { + if fwd, err := s.forward(r); err == nil { + _ = w.WriteMsg(fwd) + return + } + } + _ = w.WriteMsg(m) +} + +func (s *Server) forward(r *dns.Msg) (*dns.Msg, error) { + up := s.cfg.Upstream + if !strings.Contains(up, ":") { + up = net.JoinHostPort(up, "53") + } + c := &dns.Client{Timeout: 2 * time.Second} + msg, _, err := c.Exchange(r, up) + return msg, err +} + +// DefaultListenAddr derives host:53 from a CIDR subnet gateway IP override. +func DefaultListenAddr(subnet, override string) string { + if override != "" { + if !strings.Contains(override, ":") { + return net.JoinHostPort(override, "53") + } + return override + } + ip, _, err := net.ParseCIDR(subnet) + if err != nil { + return "127.0.0.1:53" + } + return net.JoinHostPort(ip.String(), "53") +} + +// Validate checks resolver config. +func (c Config) Validate() error { + if !c.Enabled { + return nil + } + if c.Domain == "" { + return fmt.Errorf("PRIVATE_DNS_DOMAIN is required when private DNS is enabled") + } + if c.ListenAddr == "" { + return fmt.Errorf("PRIVATE_DNS_ADDR is required when private DNS is enabled") + } + return nil +} diff --git a/internal/edge/caddy.go b/internal/edge/caddy.go new file mode 100644 index 0000000..adf0e04 --- /dev/null +++ b/internal/edge/caddy.go @@ -0,0 +1,57 @@ +// Package edge implements the programmable public gateway (reverse proxy). +package edge + +import ( + "fmt" + "strings" + + "github.com/NetSepio/erebrus/internal/services" +) + +// CaddyOptions configures generated Caddy config. +type CaddyOptions struct { + PublicDomain string + WildcardDomain string + HTTPPort int + HTTPSPort int + AutoTLS bool +} + +// GenerateCaddyfile renders a Caddyfile routing public hostnames to internal services. +func GenerateCaddyfile(svcs []services.Service, opt CaddyOptions) string { + if opt.HTTPPort == 0 { + opt.HTTPPort = 80 + } + if opt.HTTPSPort == 0 { + opt.HTTPSPort = 443 + } + var b strings.Builder + b.WriteString("# Generated by Erebrus — do not edit by hand\n") + b.WriteString(fmt.Sprintf("{\n auto_https %s\n}\n\n", autoHTTPS(opt.AutoTLS))) + for _, s := range svcs { + if !s.Public || s.PublicHost == "" { + continue + } + target := s.InternalAddr + if !strings.HasPrefix(target, "http") { + target = "http://" + target + } + b.WriteString(s.PublicHost + " {\n") + b.WriteString(fmt.Sprintf(" reverse_proxy %s\n", target)) + b.WriteString("}\n\n") + } + if opt.WildcardDomain != "" { + b.WriteString(opt.WildcardDomain + " {\n") + b.WriteString(" @svc host {labels.3}.{labels.2}.{labels.1}.{labels.0}\n") + b.WriteString(" reverse_proxy @svc localhost:9081\n") + b.WriteString("}\n\n") + } + return b.String() +} + +func autoHTTPS(on bool) string { + if on { + return "on" + } + return "off" +} diff --git a/internal/edge/caddy_test.go b/internal/edge/caddy_test.go new file mode 100644 index 0000000..79ecb57 --- /dev/null +++ b/internal/edge/caddy_test.go @@ -0,0 +1,20 @@ +package edge + +import ( + "strings" + "testing" + + "github.com/NetSepio/erebrus/internal/services" +) + +func TestGenerateCaddyfile(t *testing.T) { + out := GenerateCaddyfile([]services.Service{ + {Name: "dashboard", Public: true, PublicHost: "dashboard.apps.example.com", InternalAddr: "127.0.0.1:3000"}, + }, CaddyOptions{AutoTLS: true}) + if !strings.Contains(out, "dashboard.apps.example.com") { + t.Fatalf("missing host: %s", out) + } + if !strings.Contains(out, "reverse_proxy") { + t.Fatal("missing reverse_proxy") + } +} diff --git a/internal/edge/proxy.go b/internal/edge/proxy.go new file mode 100644 index 0000000..fd444c5 --- /dev/null +++ b/internal/edge/proxy.go @@ -0,0 +1,66 @@ +package edge + +import ( + "context" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "strings" + + "github.com/NetSepio/erebrus/internal/services" +) + +// Proxy routes public HTTP requests to registered services by hostname. +type Proxy struct { + Reg *services.Registry + St interface { + GetServiceByDomain(ctx context.Context, domain string) (string, error) + } + WildcardDomain string +} + +// Handler returns an http.Handler for the public edge. +func (p *Proxy) Handler() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + host := strings.Split(r.Host, ":")[0] + svc, err := p.lookup(r.Context(), host) + if err != nil || svc == nil { + http.NotFound(w, r) + return + } + target, err := url.Parse("http://" + svc.InternalAddr) + if err != nil { + http.Error(w, "bad target", http.StatusBadGateway) + return + } + rp := httputil.NewSingleHostReverseProxy(target) + rp.ServeHTTP(w, r) + }) +} + +func (p *Proxy) lookup(ctx context.Context, host string) (*services.Service, error) { + items, err := p.Reg.List(ctx) + if err != nil { + return nil, err + } + for _, s := range items { + if s.Public && strings.EqualFold(s.PublicHost, host) { + return &s, nil + } + } + if p.St != nil { + if id, err := p.St.GetServiceByDomain(ctx, host); err == nil && id != "" { + return p.Reg.Get(ctx, id) + } + } + if p.WildcardDomain != "" { + base := strings.TrimPrefix(p.WildcardDomain, "*.") + if strings.HasSuffix(host, base) { + prefix := strings.TrimSuffix(host, "."+base) + name := strings.Split(prefix, ".")[0] + return p.Reg.FindByName(ctx, name) + } + } + return nil, fmt.Errorf("no service for host %s", host) +} diff --git a/internal/gatewayauth/verify.go b/internal/gatewayauth/verify.go new file mode 100644 index 0000000..8a45f91 --- /dev/null +++ b/internal/gatewayauth/verify.go @@ -0,0 +1,48 @@ +// Package gatewayauth verifies gateway-issued PASETO tokens on node private APIs. +package gatewayauth + +import ( + "crypto/ed25519" + "encoding/hex" + "fmt" + "strings" + + "github.com/vk-rv/pvx" +) + +const roleGatewayCall = "gateway_call" + +// Claims is the gateway call token payload. +type Claims struct { + Role string `json:"role,omitempty"` + NodeID string `json:"node_id,omitempty"` + PeerID string `json:"peer_id,omitempty"` + Purpose string `json:"purpose,omitempty"` + pvx.RegisteredClaims +} + +// VerifyGatewayCall parses a gateway PASETO and checks it is a valid short-lived +// gateway→node call for this node. +func VerifyGatewayCall(token, gatewayPublicKeyHex, expectNodeID string) (*Claims, error) { + raw, err := hex.DecodeString(strings.TrimPrefix(gatewayPublicKeyHex, "0x")) + if err != nil { + return nil, fmt.Errorf("decode gateway public key: %w", err) + } + if len(raw) != ed25519.PublicKeySize { + return nil, fmt.Errorf("gateway public key must be %d bytes", ed25519.PublicKeySize) + } + pk := pvx.NewAsymmetricPublicKey(ed25519.PublicKey(raw), pvx.Version4) + pv4 := pvx.NewPV4Public() + + var c Claims + if err := pv4.Verify(token, pk).ScanClaims(&c); err != nil { + return nil, err + } + if c.Role != roleGatewayCall { + return nil, fmt.Errorf("unexpected role %q", c.Role) + } + if expectNodeID != "" && c.NodeID != "" && c.NodeID != expectNodeID { + return nil, fmt.Errorf("node_id mismatch") + } + return &c, nil +} \ No newline at end of file diff --git a/internal/gatewayclient/client.go b/internal/gatewayclient/client.go new file mode 100644 index 0000000..445ab91 --- /dev/null +++ b/internal/gatewayclient/client.go @@ -0,0 +1,263 @@ +package gatewayclient + +import ( + "context" + "encoding/json" + "fmt" + "log/slog" + "math/rand" + "net/http" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/gorilla/websocket" +) + +const ( + defaultHeartbeatSec = 30 + usageInterval = 60 * time.Second + writeWait = 10 * time.Second + pongWait = 95 * time.Second + pingPeriod = 27 * time.Second +) + +// SnapshotProvider supplies protocol payloads the gateway expects. +type SnapshotProvider interface { + BuildHello(nodeID string) Hello + BuildHeartbeat(status string) Heartbeat + BuildUsageReport() UsageReport +} + +// CommandHandler executes gateway control commands. +type CommandHandler interface { + HandleCommand(ctx context.Context, cmd Command) CommandResult +} + +// Client maintains the node→gateway WebSocket control plane. +type Client struct { + gatewayURL string + nodeToken string + nodeID string + log *slog.Logger + + snap SnapshotProvider + cmds CommandHandler + status func() string + + mu sync.Mutex + heartbeatSec int + lastUsage map[string]peerCounters + onReconnect func() + connected atomic.Bool +} + +type peerCounters struct { + rx int64 + tx int64 +} + +// New constructs a gateway WebSocket client. +func New(gatewayURL, nodeID, nodeToken string, snap SnapshotProvider, cmds CommandHandler, status func() string) *Client { + return &Client{ + gatewayURL: strings.TrimRight(gatewayURL, "/"), + nodeID: nodeID, + nodeToken: nodeToken, + snap: snap, + cmds: cmds, + status: status, + heartbeatSec: defaultHeartbeatSec, + lastUsage: map[string]peerCounters{}, + log: slog.Default(), + } +} + +// SetLogger overrides the default logger. +func (c *Client) SetLogger(log *slog.Logger) { + if log != nil { + c.log = log + } +} + +// SetOnReconnect is called after each successful reconnect (e.g. to re-send hello). +func (c *Client) SetOnReconnect(fn func()) { c.onReconnect = fn } + +// Connected reports whether the gateway WebSocket session is active. +func (c *Client) Connected() bool { return c.connected.Load() } + +// Run dials the gateway and maintains the connection until ctx is cancelled. +func (c *Client) Run(ctx context.Context) { + backoff := time.Second + for { + if ctx.Err() != nil { + return + } + err := c.session(ctx) + if ctx.Err() != nil { + return + } + c.log.Warn("gateway disconnected", "err", err) + jitter := time.Duration(rand.Int63n(int64(backoff / 5))) + sleep := backoff + jitter - backoff/10 + select { + case <-ctx.Done(): + return + case <-time.After(sleep): + } + if backoff < 60*time.Second { + backoff *= 2 + } + } +} + +func (c *Client) session(ctx context.Context) error { + wsURL := strings.Replace(c.gatewayURL, "https://", "wss://", 1) + wsURL = strings.Replace(wsURL, "http://", "ws://", 1) + wsURL += "/api/v2/nodes/ws" + + header := http.Header{} + header.Set("Authorization", "Bearer "+c.nodeToken) + dialer := websocket.Dialer{HandshakeTimeout: 10 * time.Second} + ws, _, err := dialer.DialContext(ctx, wsURL, header) + if err != nil { + return fmt.Errorf("ws dial: %w", err) + } + defer ws.Close() + + c.log.Info("gateway connected", "url", wsURL) + c.connected.Store(true) + defer c.connected.Store(false) + + if err := c.sendHello(ws); err != nil { + return err + } + + errCh := make(chan error, 2) + go func() { errCh <- c.readPump(ctx, ws) }() + go func() { errCh <- c.writePump(ctx, ws) }() + + select { + case <-ctx.Done(): + _ = ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) + return ctx.Err() + case err := <-errCh: + return err + } +} + +func (c *Client) sendHello(ws *websocket.Conn) error { + hello := c.snap.BuildHello(c.nodeID) + frame, err := wrap(TypeHello, hello) + if err != nil { + return err + } + _ = ws.SetWriteDeadline(time.Now().Add(writeWait)) + return ws.WriteMessage(websocket.TextMessage, frame) +} + +func (c *Client) readPump(ctx context.Context, ws *websocket.Conn) error { + ws.SetReadLimit(1 << 20) + _ = ws.SetReadDeadline(time.Now().Add(pongWait)) + ws.SetPongHandler(func(string) error { + return ws.SetReadDeadline(time.Now().Add(pongWait)) + }) + for { + _, raw, err := ws.ReadMessage() + if err != nil { + return err + } + _ = ws.SetReadDeadline(time.Now().Add(pongWait)) + if err := c.handleFrame(ctx, ws, raw); err != nil { + c.log.Warn("handle gateway frame", "err", err) + } + } +} + +func (c *Client) handleFrame(ctx context.Context, ws *websocket.Conn, raw []byte) error { + var env Envelope + if err := json.Unmarshal(raw, &env); err != nil { + return err + } + switch env.Type { + case TypeHelloAck: + var ack HelloAck + if err := json.Unmarshal(env.Data, &ack); err != nil { + return err + } + if ack.HeartbeatIntervalSec > 0 { + c.mu.Lock() + c.heartbeatSec = ack.HeartbeatIntervalSec + c.mu.Unlock() + } + if c.onReconnect != nil { + c.onReconnect() + } + case TypeCommand: + var cmd Command + if err := json.Unmarshal(env.Data, &cmd); err != nil { + return err + } + res := c.cmds.HandleCommand(ctx, cmd) + frame, err := wrap(TypeCommandResult, res) + if err != nil { + return err + } + _ = ws.SetWriteDeadline(time.Now().Add(writeWait)) + return ws.WriteMessage(websocket.TextMessage, frame) + default: + c.log.Debug("ignore gateway message", "type", env.Type) + } + return nil +} + +func (c *Client) writePump(ctx context.Context, ws *websocket.Conn) error { + c.mu.Lock() + hbSec := c.heartbeatSec + c.mu.Unlock() + if hbSec <= 0 { + hbSec = defaultHeartbeatSec + } + hbTicker := time.NewTicker(time.Duration(hbSec) * time.Second) + usageTicker := time.NewTicker(usageInterval) + pingTicker := time.NewTicker(pingPeriod) + defer hbTicker.Stop() + defer usageTicker.Stop() + defer pingTicker.Stop() + + for { + select { + case <-ctx.Done(): + return ctx.Err() + case <-hbTicker.C: + status := "online" + if c.status != nil { + status = c.status() + } + hb := c.snap.BuildHeartbeat(status) + frame, err := wrap(TypeHeartbeat, hb) + if err != nil { + return err + } + _ = ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.TextMessage, frame); err != nil { + return err + } + case <-usageTicker.C: + ur := c.snap.BuildUsageReport() + frame, err := wrap(TypeUsageReport, ur) + if err != nil { + return err + } + _ = ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.TextMessage, frame); err != nil { + return err + } + case <-pingTicker.C: + _ = ws.SetWriteDeadline(time.Now().Add(writeWait)) + if err := ws.WriteMessage(websocket.PingMessage, nil); err != nil { + return err + } + } + } +} diff --git a/internal/gatewayclient/messages.go b/internal/gatewayclient/messages.go new file mode 100644 index 0000000..ed75a19 --- /dev/null +++ b/internal/gatewayclient/messages.go @@ -0,0 +1,158 @@ +// Package gatewayclient implements the node side of the node↔gateway control +// plane: HTTPS registration and the WebSocket client. +// +// Message structs are a hand-mirrored copy of docs/v2/ws-protocol.md (FROZEN +// v2.0) and erebrus-gateway/internal/gw/nodehub/messages.go. Change +// ws-protocol.md first, then both repos. +package gatewayclient + +import "encoding/json" + +// Message types. +const ( + TypeHello = "hello" + TypeHelloAck = "hello_ack" + TypeHeartbeat = "heartbeat" + TypeUsageReport = "usage_report" + TypeCommand = "command" + TypeCommandResult = "command_result" +) + +// Command actions (v2.0). +const ( + ActionDrain = "drain" + ActionUndrain = "undrain" + ActionRotateReality = "rotate_reality" + ActionResyncPeers = "resync_peers" + ActionSyncApps = "sync_apps" +) + +// Envelope wraps every WebSocket frame: {"type": "...", "data": {...}}. +type Envelope struct { + Type string `json:"type"` + Data json.RawMessage `json:"data"` +} + +// Identity is the node's stable identity anchor. +type Identity struct { + PeerID string `json:"peer_id"` + DID string `json:"did"` + IPHash string `json:"ip_hash"` +} + +// Spec is coarse node hardware/placement. +type Spec struct { + CPU string `json:"cpu"` + MemMB int `json:"mem_mb"` + Region string `json:"region"` + IP string `json:"ip"` +} + +// Capabilities advertises optional node features. +type Capabilities struct { + AccessMode string `json:"access_mode,omitempty"` // private | shared | public + AppHosting bool `json:"app_hosting"` + WildcardDomain string `json:"wildcard_domain"` +} + +// Endpoints describes the connection endpoints clients dial. +type Endpoints struct { + WireGuard WireGuardEndpoint `json:"wireguard"` + VLESSReality VLESSEndpoint `json:"vless_reality"` + Hysteria2 Hysteria2Endpoint `json:"hysteria2"` +} + +type WireGuardEndpoint struct { + Port int `json:"port"` + PublicKey string `json:"public_key"` +} + +type VLESSEndpoint struct { + Port int `json:"port"` + PublicKey string `json:"public_key"` + ShortIDs []string `json:"short_ids"` + SNI string `json:"sni"` +} + +type Hysteria2Endpoint struct { + Port int `json:"port"` + Obfs string `json:"obfs"` +} + +// Hello is sent by the node on every (re)connect. +type Hello struct { + NodeID string `json:"node_id"` + Version string `json:"version"` + Identity Identity `json:"identity"` + Spec Spec `json:"spec"` + Capabilities Capabilities `json:"capabilities"` + Endpoints Endpoints `json:"endpoints"` +} + +// HelloAck is the gateway's response to hello. +type HelloAck struct { + HeartbeatIntervalSec int `json:"heartbeat_interval_sec"` +} + +// Load is the node's coarse load snapshot. +type Load struct { + WGPeers int `json:"wg_peers"` + ProxySessions int `json:"proxy_sessions"` + CPUPct float64 `json:"cpu_pct"` + MemPct float64 `json:"mem_pct"` + RxBytes int64 `json:"rx_bytes"` + TxBytes int64 `json:"tx_bytes"` +} + +// Speedtest is the node's most recent self-measured throughput. +type Speedtest struct { + DownloadMbps float64 `json:"download_mbps"` + UploadMbps float64 `json:"upload_mbps"` + LatencyMs float64 `json:"latency_ms"` + MeasuredAt int64 `json:"measured_at"` +} + +// Heartbeat is sent every heartbeat_interval_sec. +type Heartbeat struct { + TS int64 `json:"ts"` + Status string `json:"status"` // online | draining + Load Load `json:"load"` + Speedtest Speedtest `json:"speedtest"` + Versions map[string]string `json:"versions"` +} + +// PeerUsage is one client's traffic delta in a usage_report. +type PeerUsage struct { + PeerID string `json:"peer_id"` + RxBytesDelta int64 `json:"rx_bytes_delta"` + TxBytesDelta int64 `json:"tx_bytes_delta"` + LastHandshake int64 `json:"last_handshake"` +} + +// UsageReport is sent every 60s with per-client deltas. +type UsageReport struct { + TS int64 `json:"ts"` + Peers []PeerUsage `json:"peers"` +} + +// Command is gateway → node. +type Command struct { + Action string `json:"action"` + RequestID string `json:"request_id"` + Args json.RawMessage `json:"args,omitempty"` +} + +// CommandResult is node → gateway. +type CommandResult struct { + RequestID string `json:"request_id"` + OK bool `json:"ok"` + Error string `json:"error"` +} + +func wrap(msgType string, payload any) ([]byte, error) { + data, err := json.Marshal(payload) + if err != nil { + return nil, err + } + return json.Marshal(Envelope{Type: msgType, Data: data}) +} diff --git a/internal/gatewayclient/messages_test.go b/internal/gatewayclient/messages_test.go new file mode 100644 index 0000000..77eae79 --- /dev/null +++ b/internal/gatewayclient/messages_test.go @@ -0,0 +1,90 @@ +package gatewayclient + +import ( + "encoding/json" + "testing" +) + +const canonicalHello = `{ + "type": "hello", + "data": { + "node_id": "9d3b0d5e-3a3c-4b9e-9a31-0c5a9f0e6c11", + "version": "2.0.0", + "identity": { + "peer_id": "12D3KooWQYhTNQdmr3ArTeo5gCtJ8m1bbb73Bb4Q4xxK9zMrf1nK", + "did": "did:erebrus:12D3KooWQYhTNQdmr3ArTeo5gCtJ8m1bbb73Bb4Q4xxK9zMrf1nK", + "ip_hash": "f1820f54e0e51b8a1a47b0ec96265d6021b3a0b6c6c61563b1d62fa4a4b0d3c2" + }, + "spec": { "cpu": "4 vCPU", "mem_mb": 8192, "region": "SG", "ip": "203.0.113.10" }, + "capabilities": { "app_hosting": false, "wildcard_domain": "" }, + "endpoints": { + "wireguard": { "port": 51820, "public_key": "wOLuwnTGzkkCC1WiV2t5HpJ56FftZyXTK0WnWxSDFkI=" }, + "vless_reality": { "port": 8443, "public_key": "SRYxyiZ1Tr3w0aV3PXAhd1NSjpvm8wOCnnlLWWBd7Vc", "short_ids": ["6ba85179e30d4fc2"], "sni": "www.microsoft.com" }, + "hysteria2": { "port": 4443, "obfs": "" } + } + } +}` + +func TestParseCanonicalHello(t *testing.T) { + var env Envelope + if err := json.Unmarshal([]byte(canonicalHello), &env); err != nil { + t.Fatalf("unmarshal envelope: %v", err) + } + if env.Type != TypeHello { + t.Fatalf("type = %q, want %q", env.Type, TypeHello) + } + var h Hello + if err := json.Unmarshal(env.Data, &h); err != nil { + t.Fatalf("unmarshal hello: %v", err) + } + if h.NodeID != "9d3b0d5e-3a3c-4b9e-9a31-0c5a9f0e6c11" { + t.Errorf("node_id = %q", h.NodeID) + } + if h.Identity.DID != "did:erebrus:"+"12D3KooWQYhTNQdmr3ArTeo5gCtJ8m1bbb73Bb4Q4xxK9zMrf1nK" { + t.Errorf("did = %q", h.Identity.DID) + } + if h.Spec.MemMB != 8192 || h.Spec.Region != "SG" { + t.Errorf("spec = %+v", h.Spec) + } + if h.Endpoints.WireGuard.Port != 51820 || h.Endpoints.Hysteria2.Port != 4443 { + t.Errorf("endpoints ports wrong: %+v", h.Endpoints) + } + if len(h.Endpoints.VLESSReality.ShortIDs) != 1 || h.Endpoints.VLESSReality.SNI != "www.microsoft.com" { + t.Errorf("vless endpoint = %+v", h.Endpoints.VLESSReality) + } +} + +func TestHeartbeatAndUsageRoundTrip(t *testing.T) { + hb := Heartbeat{ + TS: 1765584000, Status: "online", + Load: Load{WGPeers: 42, ProxySessions: 7, CPUPct: 23.5, MemPct: 41.2, RxBytes: 123456789, TxBytes: 987654321}, + Speedtest: Speedtest{DownloadMbps: 940.2, UploadMbps: 870.1, LatencyMs: 3.2, MeasuredAt: 1765580400}, + Versions: map[string]string{"node": "2.0.0", "singbox": "1.11.4"}, + } + frame, err := wrap(TypeHeartbeat, hb) + if err != nil { + t.Fatalf("wrap: %v", err) + } + var env Envelope + if err := json.Unmarshal(frame, &env); err != nil || env.Type != TypeHeartbeat { + t.Fatalf("envelope: %v type=%s", err, env.Type) + } + var got Heartbeat + if err := json.Unmarshal(env.Data, &got); err != nil { + t.Fatalf("unmarshal heartbeat: %v", err) + } + if got.Load.RxBytes != 123456789 || got.Load.TxBytes != 987654321 { + t.Errorf("byte counters lost: %+v", got.Load) + } + + ur := UsageReport{TS: 1765584000, Peers: []PeerUsage{{PeerID: "c0a4f1de", RxBytesDelta: 1048576, TxBytesDelta: 8388608, LastHandshake: 1765583970}}} + frame, _ = wrap(TypeUsageReport, ur) + _ = json.Unmarshal(frame, &env) + var gotUR UsageReport + if err := json.Unmarshal(env.Data, &gotUR); err != nil { + t.Fatalf("unmarshal usage: %v", err) + } + if len(gotUR.Peers) != 1 || gotUR.Peers[0].TxBytesDelta != 8388608 { + t.Errorf("usage peers wrong: %+v", gotUR.Peers) + } +} diff --git a/internal/gatewayclient/register.go b/internal/gatewayclient/register.go new file mode 100644 index 0000000..7875c3d --- /dev/null +++ b/internal/gatewayclient/register.go @@ -0,0 +1,232 @@ +package gatewayclient + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/NetSepio/erebrus/internal/wallet" +) + +const ( + settingNodeID = "gateway_node_id" + settingNodeToken = "gateway_node_token" + settingNodeKey = "gateway_node_key" + settingGatewayPublicKey = "gateway_public_key" +) + +// SettingsStore persists gateway registration credentials. +type SettingsStore interface { + GetSetting(ctx context.Context, key string) (string, error) + SetSetting(ctx context.Context, key, value string) error +} + +// RegistrationInput is the node identity payload sent to the gateway. +type RegistrationInput struct { + GatewayURL string + OrgEnrollmentSecret string + WalletChain string + Mnemonic string + PeerID string + DID string + Name string + Region string + APIBaseURL string + NodeKey string // optional; gateway mints if empty + AccessMode string // public | private +} + +// RegistrationResult holds the gateway-issued node credentials. +type RegistrationResult struct { + NodeID string + NodeToken string + NodeKey string + GatewayPublicKey string +} + +// Credentials is the persisted gateway registration state. +type Credentials struct { + NodeID string + NodeToken string + NodeKey string + GatewayPublicKey string +} + +// LoadCredentials reads persisted gateway credentials from the store. +func LoadCredentials(ctx context.Context, st SettingsStore) (*Credentials, error) { + nodeID, err := st.GetSetting(ctx, settingNodeID) + if err != nil { + return nil, err + } + nodeToken, err := st.GetSetting(ctx, settingNodeToken) + if err != nil { + return nil, err + } + nodeKey, _ := st.GetSetting(ctx, settingNodeKey) + gwPub, _ := st.GetSetting(ctx, settingGatewayPublicKey) + return &Credentials{ + NodeID: nodeID, NodeToken: nodeToken, NodeKey: nodeKey, GatewayPublicKey: gwPub, + }, nil +} + +// SaveCredentials persists gateway credentials. +func SaveCredentials(ctx context.Context, st SettingsStore, cred *Credentials) error { + if cred == nil { + return fmt.Errorf("nil credentials") + } + if err := st.SetSetting(ctx, settingNodeID, cred.NodeID); err != nil { + return err + } + if err := st.SetSetting(ctx, settingNodeToken, cred.NodeToken); err != nil { + return err + } + if cred.NodeKey != "" { + if err := st.SetSetting(ctx, settingNodeKey, cred.NodeKey); err != nil { + return err + } + } + if cred.GatewayPublicKey != "" { + if err := st.SetSetting(ctx, settingGatewayPublicKey, cred.GatewayPublicKey); err != nil { + return err + } + } + return nil +} + +// Register performs the two-step org enrollment flow and returns node credentials. +func Register(ctx context.Context, in RegistrationInput) (*RegistrationResult, error) { + base := strings.TrimRight(strings.TrimSpace(in.GatewayURL), "/") + secret := strings.TrimSpace(in.OrgEnrollmentSecret) + if base == "" { + return nil, fmt.Errorf("gateway URL is empty") + } + if secret == "" { + return nil, fmt.Errorf("org enrollment secret is empty") + } + if in.PeerID == "" { + return nil, fmt.Errorf("peer_id is empty") + } + + walletAddr, err := wallet.AddressFromMnemonic(in.Mnemonic, in.WalletChain) + if err != nil { + return nil, fmt.Errorf("wallet address: %w", err) + } + pubKey, err := wallet.PublicKeyFromMnemonic(in.Mnemonic, in.WalletChain) + if err != nil { + return nil, fmt.Errorf("wallet public key: %w", err) + } + + client := &http.Client{Timeout: 15 * time.Second} + + // Step 1: machine challenge (gated by enrollment_secret). + step1, _ := json.Marshal(map[string]string{ + "enrollment_secret": secret, + "peer_id": in.PeerID, + }) + raw, status, err := postJSON(ctx, client, base+"/api/v2/nodes/register", step1) + if err != nil { + return nil, err + } + if status != http.StatusOK { + return nil, fmt.Errorf("register step1: %d: %s", status, truncate(raw)) + } + var challenge struct { + FlowID string `json:"flow_id"` + Message string `json:"message"` + GatewayPublicKey string `json:"gateway_public_key"` + } + if err := json.Unmarshal(raw, &challenge); err != nil { + return nil, fmt.Errorf("parse challenge: %w", err) + } + if challenge.FlowID == "" || challenge.Message == "" { + return nil, fmt.Errorf("gateway returned empty challenge") + } + + _, _, signature, err := wallet.SignChallengeWithMnemonic(in.Mnemonic, in.WalletChain, challenge.Message) + if err != nil { + return nil, fmt.Errorf("sign challenge: %w", err) + } + + access := in.AccessMode + if access != "private" { + access = "public" + } + + // Step 2: signed machine registration. + step2, _ := json.Marshal(map[string]string{ + "flow_id": challenge.FlowID, + "enrollment_secret": secret, + "signature": signature, + "public_key": pubKey, + "wallet_address": walletAddr, + "chain": normalizeChain(in.WalletChain), + "peer_id": in.PeerID, + "did": in.DID, + "name": in.Name, + "region": in.Region, + "api_base_url": in.APIBaseURL, + "node_key": in.NodeKey, + "access_mode": access, + }) + raw, status, err = postJSON(ctx, client, base+"/api/v2/nodes/register", step2) + if err != nil { + return nil, err + } + if status != http.StatusOK { + return nil, fmt.Errorf("register step2: %d: %s", status, truncate(raw)) + } + var out struct { + NodeID string `json:"node_id"` + NodeToken string `json:"node_token"` + NodeKey string `json:"node_key"` + GatewayPublicKey string `json:"gateway_public_key"` + } + if err := json.Unmarshal(raw, &out); err != nil { + return nil, fmt.Errorf("parse registration response: %w", err) + } + if out.NodeID == "" || out.NodeToken == "" || out.NodeKey == "" { + return nil, fmt.Errorf("gateway returned incomplete registration response") + } + gwPub := out.GatewayPublicKey + if gwPub == "" { + gwPub = challenge.GatewayPublicKey + } + return &RegistrationResult{ + NodeID: out.NodeID, NodeToken: out.NodeToken, NodeKey: out.NodeKey, GatewayPublicKey: gwPub, + }, nil +} + +func normalizeChain(chain string) string { + chain = strings.ToLower(strings.TrimSpace(chain)) + if chain == "" { + return wallet.ChainSOL + } + return chain +} + +func postJSON(ctx context.Context, client *http.Client, url string, body []byte) (json.RawMessage, int, error) { + req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return nil, 0, err + } + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) + if err != nil { + return nil, 0, err + } + defer resp.Body.Close() + raw, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) + return raw, resp.StatusCode, nil +} + +func truncate(b []byte) string { + if len(b) > 200 { + return string(b[:200]) + } + return string(b) +} \ No newline at end of file diff --git a/internal/initcfg/initcfg.go b/internal/initcfg/initcfg.go new file mode 100644 index 0000000..cc08682 --- /dev/null +++ b/internal/initcfg/initcfg.go @@ -0,0 +1,168 @@ +// Package initcfg writes the internal operator env file for bare-metal installs. +package initcfg + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "github.com/NetSepio/erebrus/internal/config" +) + +// Options is the input for generating an env file. +type Options struct { + AccessMode config.RuntimeMode + NetworkProfile config.NetworkProfile + NodeName string + Region string + Mnemonic string + NodeAPIToken string + GatewayURL string + PublicAddress string + HTTPPort string + EnableStealth bool + StealthTCPPort string + StealthUDPPort string + EnableAppHosting bool + AppWildcardDomain string + PublicDomain string + WildcardDomain string + PublicGatewayEnabled bool + StateDir string + DefaultIface string +} + +// DefaultEnvPath is the standard bare-metal env file location. +const DefaultEnvPath = "/etc/erebrus/erebrus.env" + +// ApplyModeDefaults sets ports and profiles from access mode when unset. +func ApplyModeDefaults(o *Options) { + if o.AccessMode == "" { + o.AccessMode = config.ModePublic + } + if o.NetworkProfile == "" { + if o.AccessMode == config.ModePublic { + o.NetworkProfile = config.NetworkHostNetwork + } else { + o.NetworkProfile = config.NetworkBridge + } + } + if o.StealthTCPPort == "" { + if o.AccessMode == config.ModePublic { + o.StealthTCPPort = "443" + } else { + o.StealthTCPPort = "8443" + } + } + if o.StealthUDPPort == "" { + if o.AccessMode == config.ModePublic { + o.StealthUDPPort = "443" + } else { + o.StealthUDPPort = "4443" + } + } + if o.HTTPPort == "" { + o.HTTPPort = "9080" + } + if o.StateDir == "" { + o.StateDir = "/var/lib/erebrus" + } + if o.DefaultIface == "" { + o.DefaultIface = "eth0" + } +} + +// Render returns the env file contents. +func Render(o Options) string { + ApplyModeDefaults(&o) + iface := o.DefaultIface + return fmt.Sprintf(`# Erebrus v2 node — generated %s +# Internal configuration — use "erebrus status" to verify readiness. +RUNTYPE=release +EREBRUS_ACCESS=%s +EREBRUS_MODE=%s +EREBRUS_NETWORK_PROFILE=%s +SERVER=0.0.0.0 +HTTP_PORT=%s +NODE_NAME=%s +REGION=%s +MNEMONIC=%s +NODE_API_TOKEN=%s +GATEWAY_URL=%s +GATEWAY_AUTO_REGISTER=true +WALLET_CHAIN=sol +API_PUBLIC_URL=http://%s:%s + +WG_CONF_DIR=/etc/wireguard +WG_INTERFACE_NAME=wg0 +WG_ENDPOINT_HOST=%s +WG_ENDPOINT_PORT=51820 +WG_IPv4_SUBNET=10.0.0.1/16 +WG_DNS=1.1.1.1 +WG_POST_UP=iptables -A FORWARD -i %%i -j ACCEPT; iptables -A FORWARD -o %%i -j ACCEPT; iptables -t nat -A POSTROUTING -o %s -j MASQUERADE +WG_POST_DOWN=iptables -D FORWARD -i %%i -j ACCEPT; iptables -D FORWARD -o %%i -j ACCEPT; iptables -t nat -D POSTROUTING -o %s -j MASQUERADE + +ENABLE_STEALTH=%t +STEALTH_TCP_PORT=%s +STEALTH_UDP_PORT=%s +REALITY_SERVER_NAMES=www.microsoft.com +HYSTERIA2_OBFS_PASSWORD= + +ENABLE_APP_HOSTING=%t +APP_WILDCARD_DOMAIN=%s +PUBLIC_DOMAIN=%s +WILDCARD_DOMAIN=%s +PUBLIC_GATEWAY_ENABLED=%t + +STATE_DIR=%s +CHAIN_REGISTRATION=off +`, + time.Now().Format("2006-01-02 15:04:05"), + o.AccessMode, deployModeFor(o), o.NetworkProfile, + o.HTTPPort, o.NodeName, o.Region, + o.Mnemonic, o.NodeAPIToken, o.GatewayURL, + o.PublicAddress, o.HTTPPort, + o.PublicAddress, iface, iface, + o.EnableStealth, o.StealthTCPPort, o.StealthUDPPort, + o.EnableAppHosting, o.AppWildcardDomain, o.PublicDomain, o.WildcardDomain, o.PublicGatewayEnabled, + o.StateDir, + ) +} + +// WriteFile writes the env file with restrictive permissions. +func WriteFile(path string, o Options) error { + if path == "" { + path = DefaultEnvPath + } + content := Render(o) + if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { + return err + } + return os.WriteFile(path, []byte(content), 0o600) +} + +func deployModeFor(o Options) config.DeployMode { + if o.NetworkProfile == config.NetworkHostNetwork { + return config.DeployHost + } + return config.DeployContainer +} + +// ParseAccessMode normalizes user input. +func ParseAccessMode(s string) (config.RuntimeMode, error) { + s = strings.ToLower(strings.TrimSpace(s)) + switch s { + case "": + return config.ModePublic, nil + case "private": + return config.ModePrivate, nil + case "shared": + return config.ModePrivate, nil + case "public", "gateway": + return config.ModePublic, nil + default: + return "", fmt.Errorf("access mode must be private or public (got %q)", s) + } +} \ No newline at end of file diff --git a/internal/node/gateway.go b/internal/node/gateway.go new file mode 100644 index 0000000..afa2552 --- /dev/null +++ b/internal/node/gateway.go @@ -0,0 +1,236 @@ +package node + +import ( + "context" + "encoding/json" + "fmt" + "runtime" + "sync" + "time" + + "github.com/NetSepio/erebrus/internal/gatewayclient" + "github.com/NetSepio/erebrus/internal/registrar" +) + +// GatewayBridge implements gatewayclient.SnapshotProvider and CommandHandler. +type GatewayBridge struct { + svc *Service + peerID string + did string + nodeID string + + mu sync.RWMutex + status string + + lastUsage map[string]usageCounters +} + +type usageCounters struct { + rx int64 + tx int64 +} + +// NewGatewayBridge wires the node service to the gateway control plane. +func NewGatewayBridge(svc *Service, peerID, did, nodeID string) *GatewayBridge { + return &GatewayBridge{ + svc: svc, + peerID: peerID, + did: did, + nodeID: nodeID, + status: "online", + lastUsage: map[string]usageCounters{}, + } +} + +// Status returns the node's operational status for heartbeats. +func (g *GatewayBridge) Status() string { + g.mu.RLock() + defer g.mu.RUnlock() + return g.status +} + +// SetStatus sets online/draining and updates the public API status mirror. +func (g *GatewayBridge) SetStatus(status string) { + g.mu.Lock() + g.status = status + g.mu.Unlock() + if g.svc.apiStatus != nil { + g.svc.apiStatus(status) + } +} + +func (g *GatewayBridge) BuildHello(nodeID string) gatewayclient.Hello { + cfg := g.svc.cfg + eps := gatewayclient.Endpoints{ + WireGuard: gatewayclient.WireGuardEndpoint{ + Port: cfg.WGEndpointPortInt(), + PublicKey: g.svc.wg.ServerPublicKey(), + }, + } + if g.svc.stealth != nil && g.svc.stealth.Enabled() { + p := g.svc.stealth.Params() + obfs := "" + if cfg.Hysteria2ObfsPassword != "" { + obfs = "salamander" + } + eps.VLESSReality = gatewayclient.VLESSEndpoint{ + Port: cfg.VLESSPortInt(), + PublicKey: p.RealityPublicKey, + ShortIDs: []string{p.RealityShortID}, + SNI: p.SNI, + } + eps.Hysteria2 = gatewayclient.Hysteria2Endpoint{ + Port: cfg.Hysteria2PortInt(), + Obfs: obfs, + } + } + return gatewayclient.Hello{ + NodeID: nodeID, + Version: cfg.Version, + Identity: gatewayclient.Identity{ + PeerID: g.peerID, + DID: g.did, + IPHash: registrar.HashIP(cfg.WGEndpointHost), + }, + Spec: gatewayclient.Spec{ + CPU: fmt.Sprintf("%d CPU", runtime.NumCPU()), + MemMB: hostMemMB(), + Region: cfg.Region, + IP: cfg.WGEndpointHost, + }, + Capabilities: gatewayclient.Capabilities{ + AccessMode: cfg.Mode.GatewayAccessMode(), + AppHosting: cfg.EnableAppHosting, + WildcardDomain: cfg.AppWildcardDomain, + }, + Endpoints: eps, + } +} + +func (g *GatewayBridge) BuildHeartbeat(status string) gatewayclient.Heartbeat { + live := g.svc.wg.Stats() + peers, _ := g.svc.st.ListPeers(context.Background()) + return gatewayclient.Heartbeat{ + TS: time.Now().Unix(), + Status: status, + Load: gatewayclient.Load{ + WGPeers: len(peers), + ProxySessions: 0, + CPUPct: 0, + MemPct: memUsedPct(), + RxBytes: live.RxBytes, + TxBytes: live.TxBytes, + }, + Speedtest: gatewayclient.Speedtest{}, + Versions: map[string]string{ + "node": g.svc.cfg.Version, + "singbox": "1.11.15", + }, + } +} + +func (g *GatewayBridge) BuildUsageReport() gatewayclient.UsageReport { + ctx := context.Background() + peers, err := g.svc.st.ListPeers(ctx) + if err != nil { + return gatewayclient.UsageReport{TS: time.Now().Unix()} + } + byKey := map[string]string{} + for _, p := range peers { + byKey[p.WGPublicKey] = p.ID + } + transfers := g.svc.wg.PeerTransfers() + out := make([]gatewayclient.PeerUsage, 0) + g.mu.Lock() + defer g.mu.Unlock() + for _, tr := range transfers { + peerID, ok := byKey[tr.WGPublicKey] + if !ok { + continue + } + prev := g.lastUsage[peerID] + dRx := tr.RxBytes - prev.rx + dTx := tr.TxBytes - prev.tx + if dRx < 0 { + dRx = tr.RxBytes + } + if dTx < 0 { + dTx = tr.TxBytes + } + g.lastUsage[peerID] = usageCounters{rx: tr.RxBytes, tx: tr.TxBytes} + if dRx == 0 && dTx == 0 { + continue + } + out = append(out, gatewayclient.PeerUsage{ + PeerID: peerID, + RxBytesDelta: dRx, + TxBytesDelta: dTx, + LastHandshake: tr.LastHandshake, + }) + } + return gatewayclient.UsageReport{TS: time.Now().Unix(), Peers: out} +} + +func (g *GatewayBridge) HandleCommand(ctx context.Context, cmd gatewayclient.Command) gatewayclient.CommandResult { + res := gatewayclient.CommandResult{RequestID: cmd.RequestID, OK: true} + switch cmd.Action { + case gatewayclient.ActionDrain: + g.SetStatus("draining") + case gatewayclient.ActionUndrain: + g.SetStatus("online") + case gatewayclient.ActionRotateReality: + if g.svc.stealth == nil { + res.OK = false + res.Error = "stealth not enabled" + return res + } + if _, err := g.svc.stealth.RotateReality(ctx); err != nil { + res.OK = false + res.Error = err.Error() + } + case gatewayclient.ActionResyncPeers: + var args struct { + PeerIDs []string `json:"peer_ids"` + } + if err := json.Unmarshal(cmd.Args, &args); err != nil { + res.OK = false + res.Error = "invalid args" + return res + } + missing, err := g.svc.ResyncPeers(ctx, args.PeerIDs) + if err != nil { + res.OK = false + res.Error = err.Error() + return res + } + if len(missing) > 0 { + b, _ := json.Marshal(map[string]any{"missing_on_node": missing}) + res.Error = string(b) + } + case gatewayclient.ActionSyncApps: + // Phase 5 — acknowledge without effect in v2.0. + default: + res.OK = false + res.Error = "unknown action" + } + return res +} + +func hostMemMB() int { + var m runtime.MemStats + runtime.ReadMemStats(&m) + // Coarse placeholder until host memory detection is added. + if m.Sys > 0 { + return int(m.Sys / (1024 * 1024)) + } + return 0 +} + +func memUsedPct() float64 { + var m runtime.MemStats + runtime.ReadMemStats(&m) + if m.Sys == 0 { + return 0 + } + return float64(m.Alloc) / float64(m.Sys) * 100 +} diff --git a/internal/node/service.go b/internal/node/service.go new file mode 100644 index 0000000..54ab000 --- /dev/null +++ b/internal/node/service.go @@ -0,0 +1,221 @@ +// Package node is the node's core service: it ties the SQLite store and the +// WireGuard manager together to provision peers and build credential bundles. +// Phase 2 extends Service with sing-box (VLESS/Hysteria2) provisioning; the +// api.Provisioner interface it satisfies stays unchanged. +package node + +import ( + "context" + "crypto/rand" + "encoding/base64" + "time" + + "github.com/NetSepio/erebrus/internal/api" + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/stealth" + "github.com/NetSepio/erebrus/internal/store" + "github.com/NetSepio/erebrus/internal/telemetry" + "github.com/NetSepio/erebrus/internal/wg" + "github.com/google/uuid" +) + +// Service provisions peers across all protocols and renders credential bundles. +type Service struct { + cfg *config.Config + st *store.Store + wg *wg.Manager + stealth *stealth.Manager + metrics *telemetry.Metrics + startedAt time.Time + apiStatus func(string) +} + +// New constructs the node service. stealthMgr may be nil when the stealth +// carriers are not in use. +func New(cfg *config.Config, st *store.Store, wgm *wg.Manager, stealthMgr *stealth.Manager, m *telemetry.Metrics) *Service { + return &Service{cfg: cfg, st: st, wg: wgm, stealth: stealthMgr, metrics: m, startedAt: time.Now()} +} + +// SetAPIStatusHook mirrors drain/online state to the HTTP /api/v2/status field. +func (s *Service) SetAPIStatusHook(fn func(string)) { s.apiStatus = fn } + +// ResyncPeers deletes local peers not present in the authoritative peer_ids list +// from the gateway. Returns peer ids the gateway listed that are missing locally. +func (s *Service) ResyncPeers(ctx context.Context, keep []string) ([]string, error) { + want := map[string]struct{}{} + for _, id := range keep { + want[id] = struct{}{} + } + peers, err := s.st.ListPeers(ctx) + if err != nil { + return nil, err + } + have := map[string]struct{}{} + for _, p := range peers { + have[p.ID] = struct{}{} + if _, ok := want[p.ID]; !ok { + if err := s.DeletePeer(ctx, p.ID); err != nil { + return nil, err + } + } + } + var missing []string + for id := range want { + if _, ok := have[id]; !ok { + missing = append(missing, id) + } + } + return missing, nil +} + +// Stats returns coarse public aggregates for the local dashboard. It exposes +// only totals — never per-client rows. +func (s *Service) Stats(ctx context.Context) (*api.NodeStats, error) { + peers, err := s.st.ListPeers(ctx) + if err != nil { + return nil, err + } + live := s.wg.Stats() + protocols := []string{"wireguard"} + if s.cfg.EnableStealth { + protocols = append(protocols, "vless-reality", "hysteria2") + } + return &api.NodeStats{ + Status: "online", + Version: s.cfg.Version, + Region: s.cfg.Region, + Protocols: protocols, + TotalPeers: len(peers), + ConnectedPeers: live.Connected, + RxBytes: live.RxBytes, + TxBytes: live.TxBytes, + UptimeSec: int64(time.Since(s.startedAt).Seconds()), + }, nil +} + +// UpsertPeer creates or updates a peer and returns its credential bundle. The +// store allocates the WireGuard IP and persists generated proxy credentials +// atomically; the WireGuard interface is then synced live. +func (s *Service) UpsertPeer(ctx context.Context, id string, req api.PeerRequest) (*api.CredentialBundle, error) { + if id == "" { + id = uuid.NewString() + } + gen := store.GeneratedCreds{ + ProxyUUID: uuid.NewString(), + ProxyPassword: randomToken(24), + } + in := &store.Peer{ + ID: id, + Name: req.Name, + Wallet: req.Wallet, + WGPublicKey: req.WGPublicKey, + WGPresharedKey: req.WGPresharedKey, + Enabled: true, + ExpiresAt: req.ExpiresAt, + } + peer, err := s.st.UpsertPeer(ctx, in, s.wg.Subnet(), gen) + if err != nil { + return nil, err + } + if err := s.wg.Apply(ctx); err != nil { + return nil, err + } + if s.metrics != nil { + s.metrics.PeerProvisioned.Inc() + s.updatePeerGauge(ctx) + } + return s.buildBundle(peer) +} + +// DeletePeer removes a peer and re-syncs WireGuard. Idempotent. +func (s *Service) DeletePeer(ctx context.Context, id string) error { + if err := s.st.DeletePeer(ctx, id); err != nil { + return err + } + if err := s.wg.Apply(ctx); err != nil { + return err + } + if s.metrics != nil { + s.metrics.PeerDeprovisioned.Inc() + s.updatePeerGauge(ctx) + } + return nil +} + +// Credentials re-fetches the bundle for an existing peer. +func (s *Service) Credentials(ctx context.Context, id string) (*api.CredentialBundle, error) { + peer, err := s.st.GetPeer(ctx, id) + if err != nil { + return nil, err + } + return s.buildBundle(peer) +} + +// ListPeers returns metadata-only peer info. +func (s *Service) ListPeers(ctx context.Context) ([]api.PeerInfo, error) { + peers, err := s.st.ListPeers(ctx) + if err != nil { + return nil, err + } + out := make([]api.PeerInfo, 0, len(peers)) + for _, p := range peers { + out = append(out, api.PeerInfo{ + ID: p.ID, Name: p.Name, WGAllowedIP: p.WGAllowedIP, + Enabled: p.Enabled, CreatedAt: p.CreatedAt, ExpiresAt: p.ExpiresAt, + }) + } + return out, nil +} + +func (s *Service) buildBundle(p *store.Peer) (*api.CredentialBundle, error) { + conf, err := s.wg.ClientConfig(p) + if err != nil { + return nil, err + } + bundle := &api.CredentialBundle{ + BundleVersion: api.BundleVersion, + NodeID: s.cfg.NodeID, + ID: p.ID, + IssuedAt: time.Now().Unix(), + ExpiresAt: p.ExpiresAt, + WireGuard: api.WireGuardBundle{ + ClientConf: conf, + ServerPublicKey: s.wg.ServerPublicKey(), + Endpoint: s.wg.Endpoint(), + Address: p.WGAllowedIP, + DNS: s.cfg.WGDNS, + }, + } + // Stealth carriers (when enabled): the same WireGuard tunnel, wrapped in a + // DPI-resistant transport for clients whose UDP is blocked. + if s.stealth != nil && s.stealth.Enabled() { + label := p.Name + if label == "" { + label = s.cfg.NodeName + } + ps := s.stealth.BuildPeer(label, s.wg.ServerPublicKey(), p.WGAllowedIP, p.WGPresharedKey) + bundle.VLESSURI = ps.VLESSURI + bundle.Hysteria2URI = ps.Hysteria2URI + bundle.SingboxProfile = ps.SingboxProfile + bundle.Transports = []api.TransportEntry{ + {Kind: "direct_wireguard_udp", URI: s.wg.Endpoint()}, + {Kind: "vless_reality_tcp", URI: ps.VLESSURI}, + {Kind: "hysteria2_quic_udp", URI: ps.Hysteria2URI}, + } + } + return bundle, nil +} + +func (s *Service) updatePeerGauge(ctx context.Context) { + peers, err := s.st.ListPeers(ctx) + if err != nil { + return + } + s.metrics.WGPeers.Set(float64(len(peers))) +} + +func randomToken(n int) string { + b := make([]byte, n) + _, _ = rand.Read(b) + return base64.RawURLEncoding.EncodeToString(b) +} diff --git a/internal/p2p/identity.go b/internal/p2p/identity.go new file mode 100644 index 0000000..8a0626f --- /dev/null +++ b/internal/p2p/identity.go @@ -0,0 +1,78 @@ +// Package p2p provides the node's libp2p identity (a deterministic PeerID +// derived from the mnemonic), its DID, and DHT advertisement so the gateway +// and future IPFS/DHT use cases can discover it. It deliberately carries NO +// status/heartbeat logic — that moved to HTTPS + WebSocket (see +// internal/gatewayclient). The deterministic derivation matches v1 exactly so +// existing node mnemonics keep their PeerIDs. +package p2p + +import ( + "crypto/sha256" + "fmt" + + "github.com/libp2p/go-libp2p/core/crypto" + "github.com/libp2p/go-libp2p/core/peer" + bip32 "github.com/tyler-smith/go-bip32" + bip39 "github.com/tyler-smith/go-bip39" +) + +// DIDPrefix is the Erebrus DID method prefix. +const DIDPrefix = "did:erebrus:" + +// deterministicReader yields the same fixed seed bytes on every Read, used to +// make libp2p key generation deterministic from the mnemonic-derived seed. +type deterministicReader struct{ seed []byte } + +func (r *deterministicReader) Read(p []byte) (int, error) { + copy(p, r.seed) + return len(r.seed), nil +} + +// DeriveIdentity converts a BIP39 mnemonic into a libp2p Ed25519 private key. +// The derivation path (master → first hardened child → sha256) is identical to +// v1 so PeerIDs are stable across the v2 migration. +func DeriveIdentity(mnemonic string) (crypto.PrivKey, error) { + if mnemonic == "" { + return nil, fmt.Errorf("mnemonic is empty") + } + seed := bip39.NewSeed(mnemonic, "") + masterKey, err := bip32.NewMasterKey(seed) + if err != nil { + return nil, fmt.Errorf("master key: %w", err) + } + childKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild) + if err != nil { + return nil, fmt.Errorf("child key: %w", err) + } + hashed := sha256.Sum256(childKey.Key) + priv, _, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 256, &deterministicReader{seed: hashed[:]}) + if err != nil { + return nil, fmt.Errorf("libp2p key: %w", err) + } + return priv, nil +} + +// GenerateMnemonic returns a fresh 12-word BIP39 mnemonic (128 bits entropy), +// used by the installer to provision a node identity when the operator does not +// supply one. +func GenerateMnemonic() (string, error) { + entropy, err := bip39.NewEntropy(128) + if err != nil { + return "", fmt.Errorf("entropy: %w", err) + } + return bip39.NewMnemonic(entropy) +} + +// PeerIDFromMnemonic returns the PeerID and DID derived from a mnemonic without +// starting a host. Useful for registration payloads and tests. +func PeerIDFromMnemonic(mnemonic string) (peerID string, did string, err error) { + priv, err := DeriveIdentity(mnemonic) + if err != nil { + return "", "", err + } + id, err := peer.IDFromPrivateKey(priv) + if err != nil { + return "", "", err + } + return id.String(), DIDPrefix + id.String(), nil +} diff --git a/internal/p2p/p2p.go b/internal/p2p/p2p.go new file mode 100644 index 0000000..57e0d7f --- /dev/null +++ b/internal/p2p/p2p.go @@ -0,0 +1,98 @@ +package p2p + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/libp2p/go-libp2p" + dht "github.com/libp2p/go-libp2p-kad-dht" + "github.com/libp2p/go-libp2p/core/host" + "github.com/libp2p/go-libp2p/core/peer" + drouting "github.com/libp2p/go-libp2p/p2p/discovery/routing" + dutil "github.com/libp2p/go-libp2p/p2p/discovery/util" + "github.com/multiformats/go-multiaddr" +) + +// rendezvous is the DHT advertisement tag shared by all Erebrus nodes. +const rendezvous = "erebrus" + +// Node is the running libp2p host plus its DHT. +type Node struct { + Host host.Host + DHT *dht.IpfsDHT + did string +} + +// Start brings up the libp2p host with a deterministic identity, connects to +// the gateway bootstrap peer (if configured), and advertises on the DHT. It +// returns a started Node; call Close to stop it. +func Start(ctx context.Context, mnemonic, listenPort, gatewayMultiaddr string) (*Node, error) { + priv, err := DeriveIdentity(mnemonic) + if err != nil { + return nil, err + } + + h, err := libp2p.New( + libp2p.ListenAddrStrings(fmt.Sprintf("/ip4/0.0.0.0/tcp/%s", listenPort)), + libp2p.Identity(priv), + libp2p.DisableRelay(), + ) + if err != nil { + return nil, fmt.Errorf("libp2p host: %w", err) + } + + kad, err := dht.New(ctx, h, dht.Mode(dht.ModeAuto)) + if err != nil { + _ = h.Close() + return nil, fmt.Errorf("dht: %w", err) + } + if err := kad.Bootstrap(ctx); err != nil { + slog.Warn("dht bootstrap", "err", err) + } + + n := &Node{Host: h, DHT: kad, did: DIDPrefix + h.ID().String()} + + if gatewayMultiaddr != "" { + if err := n.connectBootstrap(ctx, gatewayMultiaddr); err != nil { + slog.Warn("gateway bootstrap connect failed", "err", err) + } + } + + // Advertise ourselves under the shared rendezvous tag. + rd := drouting.NewRoutingDiscovery(kad) + dutil.Advertise(ctx, rd, rendezvous) + + slog.Info("libp2p host started", + "peer_id", h.ID().String(), "did", n.did, "addrs", h.Addrs()) + return n, nil +} + +func (n *Node) connectBootstrap(ctx context.Context, addr string) error { + ma, err := multiaddr.NewMultiaddr(addr) + if err != nil { + return err + } + pi, err := peer.AddrInfoFromP2pAddr(ma) + if err != nil { + return err + } + cctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + return n.Host.Connect(cctx, *pi) +} + +// PeerID returns the node's PeerID string. +func (n *Node) PeerID() string { return n.Host.ID().String() } + +// DID returns the node's did:erebrus identifier. +func (n *Node) DID() string { return n.did } + +// Close shuts down the DHT and host. +func (n *Node) Close() error { + if n.DHT != nil { + _ = n.DHT.Close() + } + return n.Host.Close() +} diff --git a/internal/readiness/readiness.go b/internal/readiness/readiness.go new file mode 100644 index 0000000..8fca47f --- /dev/null +++ b/internal/readiness/readiness.go @@ -0,0 +1,258 @@ +// Package readiness evaluates whether a node is correctly configured and operational. +package readiness + +import ( + "fmt" + "strings" + + "github.com/NetSepio/erebrus/internal/config" +) + +// Check is one readiness predicate. +type Check struct { + ID string `json:"id"` + OK bool `json:"ok"` + Detail string `json:"detail,omitempty"` + Optional bool `json:"optional,omitempty"` +} + +// Report is the aggregate readiness result exposed on /api/v2/status. +type Report struct { + OK bool `json:"ok"` + Checks []Check `json:"checks"` + Warnings []string `json:"warnings,omitempty"` +} + +// Input carries live signals the evaluator cannot infer from config alone. +type Input struct { + Cfg *config.Config + IdentityConfigured bool + GatewayRegistered bool + GatewayConnected bool + WireGuardOK bool + StealthListening bool +} + +// Evaluate builds a readiness report from config and runtime signals. +func Evaluate(in Input) Report { + cfg := in.Cfg + if cfg == nil { + return Report{Checks: []Check{{ID: "config", OK: false, Detail: "configuration not loaded"}}} + } + + checks := []Check{ + { + ID: "identity", + OK: in.IdentityConfigured && cfg.Mnemonic != "", + Detail: identityDetail(in.IdentityConfigured, cfg.Mnemonic != ""), + }, + { + ID: "public_address", + OK: cfg.WGEndpointHost != "", + Detail: cfg.WGEndpointHost, + }, + apiKeyCheck(cfg), + { + ID: "wireguard", + OK: in.WireGuardOK, + Detail: wireguardDetail(in.WireGuardOK), + }, + } + checks = append(checks, stealthCheck(cfg, in.StealthListening)) + checks = append(checks, controlPlaneCheck(cfg, in.GatewayRegistered, in.GatewayConnected)) + + warnings := append([]string{}, cfg.Mode.Warnings...) + + ok := true + for _, c := range checks { + if c.Optional { + continue + } + if !c.OK { + ok = false + break + } + } + + return Report{OK: ok, Checks: checks, Warnings: warnings} +} + +func identityDetail(configured, hasMnemonic bool) string { + if configured && hasMnemonic { + return "node identity configured" + } + if !hasMnemonic { + return "node identity (recovery phrase) not set" + } + return "node identity pending" +} + +func apiKeyCheck(cfg *config.Config) Check { + if cfg.RunType == "debug" && cfg.EffectiveNodeKey() == "" { + return Check{ + ID: "node_api_key", + OK: true, + Optional: true, + Detail: "not set — peer API open in debug mode", + } + } + return Check{ + ID: "node_api_key", + OK: cfg.EffectiveNodeKey() != "", + Detail: apiKeyDetail(cfg.EffectiveNodeKey() != ""), + } +} + +func apiKeyDetail(ok bool) string { + if ok { + return "configured" + } + return "required in release mode" +} + +func wireguardDetail(ok bool) string { + if ok { + return "interface ready" + } + return "interface not up — check NET_ADMIN and wireguard-tools" +} + +func stealthCheck(cfg *config.Config, listening bool) Check { + if !cfg.EnableStealth { + return Check{ID: "stealth", OK: true, Optional: true, Detail: "disabled"} + } + return Check{ + ID: "stealth", + OK: listening, + Detail: stealthDetail(listening), + } +} + +func stealthDetail(listening bool) string { + if listening { + return "vless-reality and hysteria2 listening" + } + return "carriers enabled but not listening" +} + +func controlPlaneCheck(cfg *config.Config, registered, connected bool) Check { + if !cfg.GatewayEnabled() { + return Check{ + ID: "control_plane", + OK: true, + Optional: true, + Detail: "gateway URL not configured", + } + } + if !registered { + return Check{ + ID: "control_plane", + OK: false, + Detail: "not registered with gateway", + } + } + if connected { + return Check{ + ID: "control_plane", + OK: true, + Detail: "registered, control channel connected", + } + } + return Check{ + ID: "control_plane", + OK: true, + Detail: "registered, control channel reconnecting", + } +} + +// Preboot evaluates config-only checks before the node process is running. +func Preboot(cfg *config.Config) Report { + if cfg == nil { + return Report{Checks: []Check{{ID: "config", OK: false, Detail: "configuration not loaded"}}} + } + checks := []Check{ + {ID: "identity", OK: cfg.Mnemonic != "", Detail: identityDetail(cfg.Mnemonic != "", cfg.Mnemonic != "")}, + {ID: "public_address", OK: cfg.WGEndpointHost != "", Detail: cfg.WGEndpointHost}, + apiKeyCheck(cfg), + {ID: "wireguard", OK: true, Optional: true, Detail: "checked after node start"}, + } + if cfg.EnableStealth { + checks = append(checks, Check{ID: "stealth", OK: true, Optional: true, Detail: "checked after node start"}) + } + checks = append(checks, Check{ID: "control_plane", OK: true, Optional: true, Detail: "checked after node start"}) + + ok := true + for _, c := range checks { + if !c.Optional && !c.OK { + ok = false + break + } + } + return Report{OK: ok, Checks: checks, Warnings: append([]string{}, cfg.Mode.Warnings...)} +} + +// AccessModeLabel returns the access mode name for display (Private, Shared, Public). +func AccessModeLabel(mode config.RuntimeMode) string { + switch mode { + case config.ModePrivate: + return "Private" + case config.ModeShared: + return "Shared" + case config.ModePublic: + return "Public" + default: + return string(mode) + } +} + +// AccessModeHint is a one-line explanation shown in docs or expanded UI. +func AccessModeHint(mode config.RuntimeMode) string { + switch mode { + case config.ModePrivate: + return "Only your own devices can use this node." + case config.ModeShared: + return "Only wallets you invite can connect." + case config.ModePublic: + return "Listed on the network for users to connect." + default: + return "" + } +} + +// RegionLabel turns an ISO 3166-1 alpha-2 code (or custom REGION value) into a +// friendly label. Custom values like "EU-WEST" pass through unchanged. +func RegionLabel(code string) string { + code = strings.TrimSpace(code) + if code == "" || strings.EqualFold(code, "unknown") { + return "Not set" + } + if name, ok := regionNames[strings.ToUpper(code)]; ok { + return name + } + // Custom operator-defined region (not a 2-letter country code). + if len(code) > 2 || strings.ContainsAny(code, "-_") { + return code + } + return code + " (country code)" +} + +// PublicAPIURL returns the URL operators should allow for gateway provisioning. +func PublicAPIURL(cfg *config.Config) string { + if cfg == nil { + return "" + } + return cfg.PublicAPIBaseURL() +} + +// SummaryLine returns one line suitable for CLI output. +func SummaryLine(r Report) string { + if r.OK { + return "ready" + } + for _, c := range r.Checks { + if !c.Optional && !c.OK { + return fmt.Sprintf("not ready: %s", c.ID) + } + } + return "not ready" +} \ No newline at end of file diff --git a/internal/readiness/readiness_test.go b/internal/readiness/readiness_test.go new file mode 100644 index 0000000..aac5619 --- /dev/null +++ b/internal/readiness/readiness_test.go @@ -0,0 +1,64 @@ +package readiness + +import ( + "testing" + + "github.com/NetSepio/erebrus/internal/config" +) + +func TestEvaluateReadyPrivate(t *testing.T) { + cfg := config.Load() + cfg.Mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + cfg.WGEndpointHost = "203.0.113.1" + cfg.NodeAPIToken = "secret" + cfg.RunType = "release" + + r := Evaluate(Input{ + Cfg: cfg, + IdentityConfigured: true, + WireGuardOK: true, + StealthListening: true, + GatewayRegistered: true, + GatewayConnected: true, + }) + if !r.OK { + t.Fatalf("expected ready, got %+v", r) + } +} + +func TestEvaluateMissingPublicAddress(t *testing.T) { + cfg := config.Load() + cfg.Mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + cfg.NodeAPIToken = "secret" + + r := Evaluate(Input{Cfg: cfg, IdentityConfigured: true, WireGuardOK: true}) + if r.OK { + t.Fatal("expected not ready") + } +} + +func TestPreboot(t *testing.T) { + cfg := config.Load() + cfg.Mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + cfg.WGEndpointHost = "203.0.113.1" + cfg.NodeAPIToken = "secret" + r := Preboot(cfg) + if !r.OK { + t.Fatalf("preboot should pass config checks: %+v", r) + } +} + +func TestAccessModeLabel(t *testing.T) { + if AccessModeLabel(config.ModePublic) != "Public" { + t.Fatalf("public label = %q", AccessModeLabel(config.ModePublic)) + } +} + +func TestRegionLabel(t *testing.T) { + if RegionLabel("NO") != "Norway" { + t.Fatalf("NO = %q", RegionLabel("NO")) + } + if RegionLabel("EU-WEST") != "EU-WEST" { + t.Fatalf("custom region = %q", RegionLabel("EU-WEST")) + } +} \ No newline at end of file diff --git a/internal/readiness/regions.go b/internal/readiness/regions.go new file mode 100644 index 0000000..cfa58c7 --- /dev/null +++ b/internal/readiness/regions.go @@ -0,0 +1,20 @@ +package readiness + +// regionNames maps ISO 3166-1 alpha-2 codes to English country/territory names. +var regionNames = map[string]string{ + "AD": "Andorra", "AE": "United Arab Emirates", "AF": "Afghanistan", "AL": "Albania", + "AM": "Armenia", "AR": "Argentina", "AT": "Austria", "AU": "Australia", "AZ": "Azerbaijan", + "BA": "Bosnia and Herzegovina", "BD": "Bangladesh", "BE": "Belgium", "BG": "Bulgaria", + "BR": "Brazil", "BY": "Belarus", "CA": "Canada", "CH": "Switzerland", "CL": "Chile", + "CN": "China", "CO": "Colombia", "CY": "Cyprus", "CZ": "Czechia", "DE": "Germany", + "DK": "Denmark", "EE": "Estonia", "EG": "Egypt", "ES": "Spain", "FI": "Finland", + "FR": "France", "GB": "United Kingdom", "GE": "Georgia", "GR": "Greece", "HK": "Hong Kong", + "HR": "Croatia", "HU": "Hungary", "ID": "Indonesia", "IE": "Ireland", "IL": "Israel", + "IN": "India", "IS": "Iceland", "IT": "Italy", "JP": "Japan", "KR": "South Korea", + "KZ": "Kazakhstan", "LT": "Lithuania", "LU": "Luxembourg", "LV": "Latvia", "MD": "Moldova", + "MX": "Mexico", "MY": "Malaysia", "NG": "Nigeria", "NL": "Netherlands", "NO": "Norway", + "NZ": "New Zealand", "PH": "Philippines", "PK": "Pakistan", "PL": "Poland", "PT": "Portugal", + "RO": "Romania", "RS": "Serbia", "RU": "Russia", "SE": "Sweden", "SG": "Singapore", + "SI": "Slovenia", "SK": "Slovakia", "TH": "Thailand", "TR": "Turkey", "TW": "Taiwan", + "UA": "Ukraine", "US": "United States", "VN": "Vietnam", "ZA": "South Africa", +} \ No newline at end of file diff --git a/internal/registrar/registrar.go b/internal/registrar/registrar.go new file mode 100644 index 0000000..042a7ed --- /dev/null +++ b/internal/registrar/registrar.go @@ -0,0 +1,63 @@ +// Package registrar abstracts on-chain node registration. v2.0 ships only a +// no-op implementation; a Solana implementation will register the node's +// PeerID, DID and IP-hash on-chain later. The NodeIdentity shape is frozen so +// the future on-chain payload is known now (see docs/v2/identity.md in the +// gateway repo). +package registrar + +import ( + "context" + "encoding/hex" + "log/slog" + + "golang.org/x/crypto/sha3" +) + +// NodeIdentity is the registration payload. +type NodeIdentity struct { + PeerID string + DID string + IPHash string // sha3-256 hex of the public IPv4 + Region string + Spec string + Wallet string + Version string +} + +// Registrar registers nodes and reports status to an external system. +type Registrar interface { + Register(ctx context.Context, id NodeIdentity) error + UpdateStatus(ctx context.Context, peerID, status string) error +} + +// New returns a Registrar for the given mode. Only "off"/"noop" are supported +// in v2.0; unknown modes fall back to no-op with a warning. +func New(mode string) Registrar { + switch mode { + case "", "off", "noop": + return noop{} + default: + slog.Warn("unsupported chain registration mode, using noop", "mode", mode) + return noop{} + } +} + +// HashIP returns the lowercase hex SHA3-256 of an IP string, used to obfuscate +// node IPs anywhere they leave the operational trust boundary. +func HashIP(ip string) string { + sum := sha3.Sum256([]byte(ip)) + return hex.EncodeToString(sum[:]) +} + +type noop struct{} + +func (noop) Register(_ context.Context, id NodeIdentity) error { + slog.Info("registrar noop: skipping on-chain registration", + "peer_id", id.PeerID, "did", id.DID, "region", id.Region) + return nil +} + +func (noop) UpdateStatus(_ context.Context, peerID, status string) error { + slog.Debug("registrar noop: skipping status update", "peer_id", peerID, "status", status) + return nil +} diff --git a/internal/services/acl.go b/internal/services/acl.go new file mode 100644 index 0000000..5579249 --- /dev/null +++ b/internal/services/acl.go @@ -0,0 +1,92 @@ +package services + +import ( + "context" + "fmt" + "strings" + + "github.com/NetSepio/erebrus/internal/store" +) + +// ACLAction constants. +const ( + ActionConnect = "connect" + ActionPublish = "publish" + ActionManage = "manage" +) + +// ACLChecker evaluates service access policies. +type ACLChecker struct { + St *store.Store +} + +// AllowConnect returns whether subject may connect to the service. +func (a *ACLChecker) AllowConnect(ctx context.Context, svc *Service, subject string) (bool, error) { + if svc == nil { + return false, fmt.Errorf("service is nil") + } + switch svc.AuthMode { + case "public": + return true, nil + case "vpn-peer": + if subject == "" { + return false, nil + } + if svc.OwnerPeerID != "" && subject == svc.OwnerPeerID { + return true, nil + } + acls, err := a.St.ListServiceACLs(ctx, svc.ID) + if err != nil { + return false, err + } + for _, acl := range acls { + if acl.Action != ActionConnect && acl.Action != "" { + continue + } + if matchSubject(acl.Subject, subject) { + return true, nil + } + } + return svc.Visibility != "private" || svc.OwnerPeerID == subject, nil + case "token": + acls, err := a.St.ListServiceACLs(ctx, svc.ID) + if err != nil { + return false, err + } + for _, acl := range acls { + if matchSubject(acl.Subject, subject) { + return true, nil + } + } + return false, nil + default: + return svc.Visibility == "public", nil + } +} + +func matchSubject(rule, subject string) bool { + rule = strings.TrimSpace(rule) + subject = strings.TrimSpace(subject) + if rule == "public" { + return true + } + if strings.HasPrefix(rule, "peer:") { + return subject == strings.TrimPrefix(rule, "peer:") + } + if strings.HasPrefix(rule, "did:") { + return subject == strings.TrimPrefix(rule, "did:") + } + if strings.HasPrefix(rule, "wallet:") { + return strings.EqualFold(subject, strings.TrimPrefix(rule, "wallet:")) + } + return rule == subject +} + +// Grant adds an ACL rule. +func (a *ACLChecker) Grant(ctx context.Context, serviceID, subject, action string) error { + return a.St.InsertServiceACL(ctx, store.ServiceACL{ + ServiceID: serviceID, + Subject: subject, + Action: action, + }) +} diff --git a/internal/services/acl_test.go b/internal/services/acl_test.go new file mode 100644 index 0000000..e1b6dc5 --- /dev/null +++ b/internal/services/acl_test.go @@ -0,0 +1,38 @@ +package services + +import ( + "context" + "path/filepath" + "testing" + + "github.com/NetSepio/erebrus/internal/store" +) + +func TestACLVpnPeer(t *testing.T) { + st, err := store.Open(filepath.Join(t.TempDir(), "acl.db")) + if err != nil { + t.Fatal(err) + } + defer st.Close() + reg := &Registry{St: st} + acl := &ACLChecker{St: st} + ctx := context.Background() + + svc, err := reg.Publish(ctx, Service{Name: "api", Port: 8080, OwnerPeerID: "peer-owner", AuthMode: "vpn-peer"}) + if err != nil { + t.Fatal(err) + } + ok, err := acl.AllowConnect(ctx, svc, "peer-owner") + if err != nil || !ok { + t.Fatalf("owner should connect: ok=%v err=%v", ok, err) + } + ok, _ = acl.AllowConnect(ctx, svc, "peer-other") + if ok { + t.Fatal("other peer should not connect to private service") + } + _ = acl.Grant(ctx, svc.ID, "peer:peer-other", ActionConnect) + ok, _ = acl.AllowConnect(ctx, svc, "peer-other") + if !ok { + t.Fatal("granted peer should connect") + } +} diff --git a/internal/services/registry.go b/internal/services/registry.go new file mode 100644 index 0000000..6cbf996 --- /dev/null +++ b/internal/services/registry.go @@ -0,0 +1,129 @@ +// Package services implements the private service registry. +package services + +import ( + "context" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/NetSepio/erebrus/internal/store" + "github.com/google/uuid" +) + +// Service is a published private (or public) service on the VPN. +type Service struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Protocol string `json:"protocol"` + InternalAddr string `json:"internal_addr"` + Port int `json:"port"` + OwnerPeerID string `json:"owner_peer_id"` + OwnerDID string `json:"owner_did"` + Visibility string `json:"visibility"` + AuthMode string `json:"auth_mode"` + Tags []string `json:"tags"` + Public bool `json:"public"` + PublicHost string `json:"public_hostname,omitempty"` + CreatedAt int64 `json:"created_at"` + UpdatedAt int64 `json:"updated_at"` +} + +// Registry persists and queries services. +type Registry struct { + St *store.Store +} + +// Publish registers or updates a service. +func (r *Registry) Publish(ctx context.Context, s Service) (*Service, error) { + if s.Name == "" || s.Port <= 0 { + return nil, fmt.Errorf("name and port are required") + } + now := time.Now().Unix() + if s.ID == "" { + s.ID = "svc_" + strings.ReplaceAll(s.Name, " ", "-") + "_" + uuid.NewString()[:8] + } + if s.Protocol == "" { + s.Protocol = "http" + } + if s.Visibility == "" { + s.Visibility = "private" + } + if s.AuthMode == "" { + s.AuthMode = "vpn-peer" + } + if s.InternalAddr == "" { + s.InternalAddr = fmt.Sprintf("127.0.0.1:%d", s.Port) + } + s.CreatedAt = now + s.UpdatedAt = now + tags, _ := json.Marshal(s.Tags) + pub := 0 + if s.Public { + pub = 1 + } + if err := r.St.UpsertService(ctx, store.ServiceRow{ + ID: s.ID, Name: s.Name, Type: s.Type, Protocol: s.Protocol, + InternalAddr: s.InternalAddr, Port: s.Port, + OwnerPeerID: s.OwnerPeerID, OwnerDID: s.OwnerDID, + Visibility: s.Visibility, AuthMode: s.AuthMode, Tags: string(tags), + Public: pub, PublicHost: s.PublicHost, + CreatedAt: s.CreatedAt, UpdatedAt: s.UpdatedAt, + }); err != nil { + return nil, err + } + return &s, nil +} + +// List returns all services. +func (r *Registry) List(ctx context.Context) ([]Service, error) { + rows, err := r.St.ListServices(ctx) + if err != nil { + return nil, err + } + out := make([]Service, 0, len(rows)) + for _, row := range rows { + out = append(out, rowToService(row)) + } + return out, nil +} + +// Get returns one service by id. +func (r *Registry) Get(ctx context.Context, id string) (*Service, error) { + row, err := r.St.GetService(ctx, id) + if err != nil { + return nil, err + } + s := rowToService(*row) + return &s, nil +} + +// Remove deletes a service. +func (r *Registry) Remove(ctx context.Context, id string) error { + return r.St.DeleteService(ctx, id) +} + +// FindByName resolves a service for DNS (first match on name). +func (r *Registry) FindByName(ctx context.Context, name string) (*Service, error) { + row, err := r.St.GetServiceByName(ctx, name) + if err != nil { + return nil, err + } + s := rowToService(*row) + return &s, nil +} + +func rowToService(row store.ServiceRow) Service { + var tags []string + _ = json.Unmarshal([]byte(row.Tags), &tags) + return Service{ + ID: row.ID, Name: row.Name, Type: row.Type, Protocol: row.Protocol, + InternalAddr: row.InternalAddr, Port: row.Port, + OwnerPeerID: row.OwnerPeerID, OwnerDID: row.OwnerDID, + Visibility: row.Visibility, AuthMode: row.AuthMode, Tags: tags, + Public: row.Public == 1, PublicHost: row.PublicHost, + CreatedAt: row.CreatedAt, UpdatedAt: row.UpdatedAt, + } +} diff --git a/internal/services/registry_test.go b/internal/services/registry_test.go new file mode 100644 index 0000000..0eec776 --- /dev/null +++ b/internal/services/registry_test.go @@ -0,0 +1,42 @@ +package services + +import ( + "context" + "os" + "path/filepath" + "testing" + + "github.com/NetSepio/erebrus/internal/store" +) + +func TestRegistryCRUD(t *testing.T) { + dir := t.TempDir() + st, err := store.Open(filepath.Join(dir, "test.db")) + if err != nil { + t.Fatal(err) + } + defer st.Close() + reg := &Registry{St: st} + ctx := context.Background() + + svc, err := reg.Publish(ctx, Service{Name: "ollama", Port: 11434, Type: "ai.llm"}) + if err != nil { + t.Fatal(err) + } + list, err := reg.List(ctx) + if err != nil || len(list) != 1 { + t.Fatalf("list = %v err=%v", list, err) + } + got, err := reg.Get(ctx, svc.ID) + if err != nil || got.Name != "ollama" { + t.Fatalf("get = %+v err=%v", got, err) + } + if err := reg.Remove(ctx, svc.ID); err != nil { + t.Fatal(err) + } + list, _ = reg.List(ctx) + if len(list) != 0 { + t.Fatalf("expected empty list, got %d", len(list)) + } + _ = os.RemoveAll(dir) +} diff --git a/internal/stealth/profile.go b/internal/stealth/profile.go new file mode 100644 index 0000000..11ba825 --- /dev/null +++ b/internal/stealth/profile.go @@ -0,0 +1,170 @@ +package stealth + +import ( + "fmt" + "net/url" +) + +// ClientPrivateKeyPlaceholder marks where the client substitutes its own +// WireGuard private key in the generated sing-box profile. The node never sees +// client private keys. It avoids angle brackets so it survives JSON HTML-escaping +// unchanged. +const ClientPrivateKeyPlaceholder = "REPLACE_WITH_CLIENT_PRIVATE_KEY" + +// Params are the node-wide carrier parameters a client needs to reach the +// stealth transports. They contain no per-client data. +type Params struct { + Enabled bool `json:"enabled"` + Host string `json:"host"` + VLESSPort int `json:"vless_port"` + Hysteria2Port int `json:"hysteria2_port"` + SNI string `json:"sni"` + VLESSUUID string `json:"vless_uuid"` + VLESSFlow string `json:"vless_flow"` + RealityPublicKey string `json:"reality_public_key"` + RealityShortID string `json:"reality_short_id"` + Hysteria2Password string `json:"hysteria2_password"` + Hysteria2Obfs string `json:"hysteria2_obfs,omitempty"` // salamander password, "" = none +} + +// Params returns the carrier parameters. Returns Enabled=false (and no secrets) +// when stealth is off or Init has not run. +func (m *Manager) Params() Params { + if !m.cfg.EnableStealth || m.secrets == nil { + return Params{Enabled: false} + } + return Params{ + Enabled: true, + Host: m.cfg.WGEndpointHost, + VLESSPort: m.cfg.VLESSPortInt(), + Hysteria2Port: m.cfg.Hysteria2PortInt(), + SNI: m.cfg.RealitySNI(), + VLESSUUID: m.secrets.VLESSUUID, + VLESSFlow: vlessFlowVision, + RealityPublicKey: m.secrets.RealityPublicKey, + RealityShortID: m.secrets.RealityShortID, + Hysteria2Password: m.secrets.Hysteria2Password, + Hysteria2Obfs: m.cfg.Hysteria2ObfsPassword, + } +} + +// PeerStealth is the per-client stealth section of a credential bundle. +type PeerStealth struct { + VLESSURI string `json:"vless_uri"` + Hysteria2URI string `json:"hysteria2_uri"` + SingboxProfile any `json:"singbox_profile"` +} + +// BuildPeer renders the per-client stealth artifacts: standard vless:// and +// hysteria2:// carrier share links plus a complete sing-box client profile that +// tunnels WireGuard through the VLESS+REALITY carrier (Topology A — WireGuard +// is the endpoint). clientAddrCIDR is the peer's tunnel address (e.g. +// "10.0.0.7/32"); serverWGPub is the node's WireGuard public key (base64); psk +// is the optional WireGuard preshared key. +func (m *Manager) BuildPeer(label, serverWGPub, clientAddrCIDR, psk string) PeerStealth { + p := m.Params() + return PeerStealth{ + VLESSURI: p.vlessURI(label), + Hysteria2URI: p.hysteria2URI(label), + SingboxProfile: m.singboxProfile(p, serverWGPub, clientAddrCIDR, psk), + } +} + +func (p Params) vlessURI(label string) string { + q := url.Values{} + q.Set("encryption", "none") + q.Set("flow", p.VLESSFlow) + q.Set("security", "reality") + q.Set("sni", p.SNI) + q.Set("fp", "chrome") + q.Set("pbk", p.RealityPublicKey) + q.Set("sid", p.RealityShortID) + q.Set("type", "tcp") + return fmt.Sprintf("vless://%s@%s:%d?%s#%s", + p.VLESSUUID, p.Host, p.VLESSPort, q.Encode(), url.PathEscape(label)) +} + +func (p Params) hysteria2URI(label string) string { + q := url.Values{} + q.Set("sni", p.SNI) + q.Set("insecure", "1") + q.Set("alpn", "h3") + if p.Hysteria2Obfs != "" { + q.Set("obfs", "salamander") + q.Set("obfs-password", p.Hysteria2Obfs) + } + return fmt.Sprintf("hysteria2://%s@%s:%d?%s#%s", + url.QueryEscape(p.Hysteria2Password), p.Host, p.Hysteria2Port, q.Encode(), url.PathEscape(label)) +} + +// singboxProfile builds a full client config (as a JSON-serializable map) that +// runs WireGuard over the VLESS+REALITY carrier. The Hysteria2 carrier is also +// included as an outbound; a client switches by repointing the WireGuard +// endpoint's "detour" to "carrier-hysteria2". The WG peer endpoint is the node +// loopback because the node's direct outbound delivers carrier traffic straight +// to its local WireGuard listener. +func (m *Manager) singboxProfile(p Params, serverWGPub, clientAddrCIDR, psk string) map[string]any { + wgPeer := map[string]any{ + "address": "127.0.0.1", + "port": m.cfg.WGEndpointPortInt(), + "public_key": serverWGPub, + "allowed_ips": []string{"0.0.0.0/0", "::/0"}, + "persistent_keepalive_interval": 25, + } + if psk != "" { + wgPeer["pre_shared_key"] = psk + } + + vlessTLS := map[string]any{ + "enabled": true, + "server_name": p.SNI, + "utls": map[string]any{"enabled": true, "fingerprint": "chrome"}, + "reality": map[string]any{ + "enabled": true, + "public_key": p.RealityPublicKey, + "short_id": p.RealityShortID, + }, + } + hy2TLS := map[string]any{ + "enabled": true, + "server_name": p.SNI, + "insecure": true, + "alpn": []string{"h3"}, + } + hy2Out := map[string]any{ + "type": "hysteria2", + "tag": "carrier-hysteria2", + "server": p.Host, + "server_port": p.Hysteria2Port, + "password": p.Hysteria2Password, + "tls": hy2TLS, + } + if p.Hysteria2Obfs != "" { + hy2Out["obfs"] = map[string]any{"type": "salamander", "password": p.Hysteria2Obfs} + } + + return map[string]any{ + "log": map[string]any{"level": "warn"}, + "endpoints": []map[string]any{{ + "type": "wireguard", + "tag": "wg-out", + "address": []string{clientAddrCIDR}, + "private_key": ClientPrivateKeyPlaceholder, + "peers": []map[string]any{wgPeer}, + "detour": "carrier-vless", + }}, + "outbounds": []map[string]any{ + { + "type": "vless", + "tag": "carrier-vless", + "server": p.Host, + "server_port": p.VLESSPort, + "uuid": p.VLESSUUID, + "flow": p.VLESSFlow, + "tls": vlessTLS, + }, + hy2Out, + }, + "route": map[string]any{"final": "wg-out"}, + } +} diff --git a/internal/stealth/registry.go b/internal/stealth/registry.go new file mode 100644 index 0000000..50db68c --- /dev/null +++ b/internal/stealth/registry.go @@ -0,0 +1,33 @@ +package stealth + +import ( + "github.com/sagernet/sing-box/adapter/endpoint" + "github.com/sagernet/sing-box/adapter/inbound" + "github.com/sagernet/sing-box/adapter/outbound" + "github.com/sagernet/sing-box/protocol/direct" + "github.com/sagernet/sing-box/protocol/hysteria2" + "github.com/sagernet/sing-box/protocol/vless" +) + +// Minimal sing-box protocol registries. We deliberately avoid sing-box's +// include.*Registry() helpers because they pull in every protocol (tor, +// shadowsocks, shadowtls, naive, v2ray transports, TUN/gvisor …). The node only +// needs two carrier inbounds and a direct outbound, so registering just those +// keeps the dependency surface and binary size small. + +func inboundRegistry() *inbound.Registry { + r := inbound.NewRegistry() + vless.RegisterInbound(r) + hysteria2.RegisterInbound(r) + return r +} + +func outboundRegistry() *outbound.Registry { + r := outbound.NewRegistry() + direct.RegisterOutbound(r) + return r +} + +func endpointRegistry() *endpoint.Registry { + return endpoint.NewRegistry() +} diff --git a/internal/stealth/secrets.go b/internal/stealth/secrets.go new file mode 100644 index 0000000..fd879c9 --- /dev/null +++ b/internal/stealth/secrets.go @@ -0,0 +1,109 @@ +package stealth + +import ( + "context" + "crypto/rand" + "encoding/base64" + "encoding/hex" + + "github.com/google/uuid" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +// settings keys for the node-wide stealth carrier secrets. +const ( + keyRealityPrivate = "stealth_reality_private_key" + keyRealityPublic = "stealth_reality_public_key" + keyRealityShortID = "stealth_reality_short_id" + keyVLESSUUID = "stealth_vless_uuid" + keyHysteria2Pass = "stealth_hysteria2_password" +) + +// SettingsStore is the subset of the node store the stealth manager needs. +type SettingsStore interface { + GetSetting(ctx context.Context, key string) (string, error) + SetSetting(ctx context.Context, key, value string) error +} + +// Secrets are the node-wide credentials shared by every client of the stealth +// carriers. Per-client authentication still happens inside WireGuard, so these +// secrets only gate access to the obfuscated transport, not to the VPN itself. +type Secrets struct { + RealityPrivateKey string // base64 RawURL, x25519 + RealityPublicKey string // base64 RawURL, x25519 + RealityShortID string // 8 hex chars + VLESSUUID string + Hysteria2Password string +} + +// loadOrCreateSecrets reads the stealth secrets from the store, generating and +// persisting any that are missing. +func loadOrCreateSecrets(ctx context.Context, st SettingsStore) (*Secrets, error) { + s := &Secrets{} + + priv, err := st.GetSetting(ctx, keyRealityPrivate) + if err != nil { + return nil, err + } + if priv == "" { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return nil, err + } + pub := key.PublicKey() + priv = base64.RawURLEncoding.EncodeToString(key[:]) + pubStr := base64.RawURLEncoding.EncodeToString(pub[:]) + if err := st.SetSetting(ctx, keyRealityPrivate, priv); err != nil { + return nil, err + } + if err := st.SetSetting(ctx, keyRealityPublic, pubStr); err != nil { + return nil, err + } + } + s.RealityPrivateKey = priv + if s.RealityPublicKey, err = st.GetSetting(ctx, keyRealityPublic); err != nil { + return nil, err + } + + if s.RealityShortID, err = getOrSet(ctx, st, keyRealityShortID, randHex(4)); err != nil { + return nil, err + } + if s.VLESSUUID, err = getOrSet(ctx, st, keyVLESSUUID, uuid.NewString()); err != nil { + return nil, err + } + + // Hysteria2 auth password is always node-generated and persisted; the + // optional Salamander obfs password is operator-supplied (see config). + if s.Hysteria2Password, err = getOrSet(ctx, st, keyHysteria2Pass, randToken(24)); err != nil { + return nil, err + } + + return s, nil +} + +// getOrSet returns the stored value for key, persisting def first if absent. +func getOrSet(ctx context.Context, st SettingsStore, key, def string) (string, error) { + v, err := st.GetSetting(ctx, key) + if err != nil { + return "", err + } + if v != "" { + return v, nil + } + if err := st.SetSetting(ctx, key, def); err != nil { + return "", err + } + return def, nil +} + +func randHex(n int) string { + b := make([]byte, n) + _, _ = rand.Read(b) + return hex.EncodeToString(b) +} + +func randToken(n int) string { + b := make([]byte, n) + _, _ = rand.Read(b) + return base64.RawURLEncoding.EncodeToString(b) +} diff --git a/internal/stealth/stealth.go b/internal/stealth/stealth.go new file mode 100644 index 0000000..53e600a --- /dev/null +++ b/internal/stealth/stealth.go @@ -0,0 +1,274 @@ +// Package stealth runs the node's DPI-resistant carrier transports via an +// embedded sing-box instance. When a client's WireGuard UDP is throttled or +// blocked, it wraps the same WireGuard tunnel inside one of two carriers that +// look like ordinary internet traffic: +// +// - VLESS + REALITY on tcp/:8443 — indistinguishable from a real TLS session +// to a borrowed SNI (no fake cert; the handshake is proxied to a real site). +// - Hysteria2 on udp/:4443 — QUIC/HTTP3 with optional Salamander obfuscation. +// +// Both carriers terminate on a single node-wide credential and route to a +// direct outbound; per-client authentication stays in the inner WireGuard +// tunnel, so the sing-box instance never restarts on peer churn (Topology A — +// "WireGuard as the endpoint"). +package stealth + +import ( + "context" + "fmt" + "net/netip" + "strconv" + "strings" + "sync" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/google/uuid" + box "github.com/sagernet/sing-box" + C "github.com/sagernet/sing-box/constant" + "github.com/sagernet/sing-box/option" + "github.com/sagernet/sing/common/json/badoption" +) + +const vlessFlowVision = "xtls-rprx-vision" + +// Manager owns the embedded sing-box instance and the node-wide carrier secrets. +type Manager struct { + cfg *config.Config + st SettingsStore + secrets *Secrets + certPEM string + keyPEM string + + mu sync.Mutex + instance *box.Box + running bool +} + +// New constructs a Manager. Call Init before Start or Params. +func New(cfg *config.Config, st SettingsStore) *Manager { + return &Manager{cfg: cfg, st: st} +} + +// Enabled reports whether the stealth carriers are turned on. +func (m *Manager) Enabled() bool { return m.cfg.EnableStealth } + +// Init loads (creating on first run) the node-wide carrier secrets and the +// Hysteria2 self-signed certificate. Safe to call even when stealth is disabled +// — it makes Params usable without starting the listeners. +func (m *Manager) Init(ctx context.Context) error { + secrets, err := loadOrCreateSecrets(ctx, m.st) + if err != nil { + return fmt.Errorf("stealth secrets: %w", err) + } + certPEM, keyPEM, err := loadOrCreateCert(ctx, m.st, m.cfg.RealitySNI()) + if err != nil { + return fmt.Errorf("stealth cert: %w", err) + } + m.secrets = secrets + m.certPEM = certPEM + m.keyPEM = keyPEM + return nil +} + +// Start builds and starts the embedded sing-box instance. No-op when stealth is +// disabled. Init must have been called first. +func (m *Manager) Start(ctx context.Context) error { + if !m.cfg.EnableStealth { + return nil + } + if m.secrets == nil { + return fmt.Errorf("stealth: Init not called") + } + + opts := m.serverOptions() + boxCtx := box.Context(ctx, inboundRegistry(), outboundRegistry(), endpointRegistry()) + instance, err := box.New(box.Options{Context: boxCtx, Options: opts}) + if err != nil { + return fmt.Errorf("stealth: build sing-box: %w", err) + } + if err := instance.Start(); err != nil { + _ = instance.Close() + return fmt.Errorf("stealth: start sing-box: %w", err) + } + + m.mu.Lock() + m.instance = instance + m.running = true + m.mu.Unlock() + return nil +} + +// RotateAllSecrets regenerates VLESS UUID, REALITY short-id, and Hysteria2 +// password, then restarts sing-box if it was running. +func (m *Manager) RotateAllSecrets(ctx context.Context) error { + if m.st == nil { + return fmt.Errorf("stealth: not initialized") + } + if m.secrets == nil { + if err := m.Init(ctx); err != nil { + return err + } + } + if err := m.st.SetSetting(ctx, keyVLESSUUID, uuid.NewString()); err != nil { + return err + } + if err := m.st.SetSetting(ctx, keyRealityShortID, randHex(4)); err != nil { + return err + } + if err := m.st.SetSetting(ctx, keyHysteria2Pass, randToken(24)); err != nil { + return err + } + secrets, err := loadOrCreateSecrets(ctx, m.st) + if err != nil { + return err + } + m.secrets = secrets + wasRunning := m.running + if wasRunning { + _ = m.Close() + } + if wasRunning && m.cfg.EnableStealth { + return m.Start(ctx) + } + return nil +} + +// RotateReality regenerates the REALITY short-id, restarts sing-box, and returns +// the new short-id. The keypair is kept per ws-protocol rotate_reality semantics. +func (m *Manager) RotateReality(ctx context.Context) (string, error) { + if m.st == nil || m.secrets == nil { + return "", fmt.Errorf("stealth: not initialized") + } + shortID := randHex(4) + if err := m.st.SetSetting(ctx, keyRealityShortID, shortID); err != nil { + return "", err + } + m.secrets.RealityShortID = shortID + if m.running { + _ = m.Close() + if err := m.Start(ctx); err != nil { + return "", err + } + } + return shortID, nil +} + +// Close stops the embedded sing-box instance. Idempotent. +func (m *Manager) Close() error { + m.mu.Lock() + inst := m.instance + m.instance = nil + m.running = false + m.mu.Unlock() + if inst == nil { + return nil + } + return inst.Close() +} + +// serverOptions renders the sing-box configuration the node runs: the two +// carrier inbounds plus a single direct outbound. +func (m *Manager) serverOptions() option.Options { + logLevel := "warn" + if m.cfg.RunType == "debug" { + logLevel = "info" + } + + inbounds := []option.Inbound{m.vlessInbound()} + if h2, ok := m.hysteria2Inbound(); ok { + inbounds = append(inbounds, h2) + } + + // The direct outbound is pinned to the node's local WireGuard listener: + // every connection the carriers accept is forced to 127.0.0.1:, + // regardless of the inner destination. This keeps the node from acting as + // an open proxy for anyone holding the (shared) carrier secret — the + // carriers can only ever deliver packets to WireGuard, where the real + // per-client authentication happens. + return option.Options{ + Log: &option.LogOptions{Level: logLevel, Timestamp: true}, + Inbounds: inbounds, + Outbounds: []option.Outbound{{ + Type: C.TypeDirect, + Tag: "direct", + Options: &option.DirectOutboundOptions{ + OverrideAddress: "127.0.0.1", + OverridePort: uint16(m.cfg.WGEndpointPortInt()), + }, + }}, + Route: &option.RouteOptions{Final: "direct"}, + } +} + +func (m *Manager) vlessInbound() option.Inbound { + host, port := splitHostPort(m.cfg.RealityHandshakeTarget(), 443) + return option.Inbound{ + Type: C.TypeVLESS, + Tag: "vless-reality", + Options: &option.VLESSInboundOptions{ + ListenOptions: listenOn(m.cfg.VLESSPortInt()), + Users: []option.VLESSUser{{ + Name: "erebrus", + UUID: m.secrets.VLESSUUID, + Flow: vlessFlowVision, + }}, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{ + TLS: &option.InboundTLSOptions{ + Enabled: true, + ServerName: m.cfg.RealitySNI(), + Reality: &option.InboundRealityOptions{ + Enabled: true, + Handshake: option.InboundRealityHandshakeOptions{ + ServerOptions: option.ServerOptions{Server: host, ServerPort: port}, + }, + PrivateKey: m.secrets.RealityPrivateKey, + ShortID: badoption.Listable[string]{m.secrets.RealityShortID}, + }, + }, + }, + }, + } +} + +func (m *Manager) hysteria2Inbound() (option.Inbound, bool) { + tls := &option.InboundTLSOptions{ + Enabled: true, + ServerName: m.cfg.RealitySNI(), + ALPN: badoption.Listable[string]{"h3"}, + Certificate: badoption.Listable[string]{m.certPEM}, + Key: badoption.Listable[string]{m.keyPEM}, + } + in := &option.Hysteria2InboundOptions{ + ListenOptions: listenOn(m.cfg.Hysteria2PortInt()), + IgnoreClientBandwidth: true, + Users: []option.Hysteria2User{{ + Name: "erebrus", + Password: m.secrets.Hysteria2Password, + }}, + InboundTLSOptionsContainer: option.InboundTLSOptionsContainer{TLS: tls}, + } + if m.cfg.Hysteria2ObfsPassword != "" { + in.Obfs = &option.Hysteria2Obfs{Type: "salamander", Password: m.cfg.Hysteria2ObfsPassword} + } + return option.Inbound{Type: C.TypeHysteria2, Tag: "hysteria2", Options: in}, true +} + +// listenOn builds ListenOptions bound to all interfaces on the given port. +func listenOn(port int) option.ListenOptions { + addr := badoption.Addr(netip.IPv4Unspecified()) + return option.ListenOptions{Listen: &addr, ListenPort: uint16(port)} +} + +// splitHostPort splits "host:port"; missing/invalid port falls back to def. +func splitHostPort(s string, def uint16) (string, uint16) { + i := strings.LastIndex(s, ":") + if i < 0 { + return s, def + } + host := s[:i] + p, err := strconv.Atoi(s[i+1:]) + if err != nil || p <= 0 || p > 65535 { + return host, def + } + return host, uint16(p) +} diff --git a/internal/stealth/stealth_test.go b/internal/stealth/stealth_test.go new file mode 100644 index 0000000..1ca447c --- /dev/null +++ b/internal/stealth/stealth_test.go @@ -0,0 +1,177 @@ +package stealth + +import ( + "context" + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + "testing" + "time" + + "github.com/NetSepio/erebrus/internal/config" +) + +// memStore is an in-memory SettingsStore for tests. +type memStore struct{ m map[string]string } + +func newMemStore() *memStore { return &memStore{m: map[string]string{}} } + +func (s *memStore) GetSetting(_ context.Context, k string) (string, error) { return s.m[k], nil } +func (s *memStore) SetSetting(_ context.Context, k, v string) error { s.m[k] = v; return nil } + +func testConfig(vlessPort, hy2Port int) *config.Config { + return &config.Config{ + RunType: "release", + NodeName: "test-node", + EnableStealth: true, + WGEndpointHost: "127.0.0.1", + WGEndpointPort: "51820", + VLESSPort: strconv.Itoa(vlessPort), + Hysteria2Port: strconv.Itoa(hy2Port), + RealityServerNames: []string{"www.microsoft.com"}, + } +} + +func freePort(t *testing.T) int { + t.Helper() + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatalf("free port: %v", err) + } + defer l.Close() + return l.Addr().(*net.TCPAddr).Port +} + +func TestSecretsStableAndValid(t *testing.T) { + ctx := context.Background() + st := newMemStore() + + s1, err := loadOrCreateSecrets(ctx, st) + if err != nil { + t.Fatalf("first load: %v", err) + } + s2, err := loadOrCreateSecrets(ctx, st) + if err != nil { + t.Fatalf("second load: %v", err) + } + if *s1 != *s2 { + t.Fatalf("secrets not stable across loads:\n%+v\n%+v", s1, s2) + } + + // REALITY keys must be 32-byte x25519 values in base64 RawURL. + for name, key := range map[string]string{"private": s1.RealityPrivateKey, "public": s1.RealityPublicKey} { + raw, err := base64.RawURLEncoding.DecodeString(key) + if err != nil { + t.Fatalf("reality %s key not base64 RawURL: %v", name, err) + } + if len(raw) != 32 { + t.Fatalf("reality %s key wrong length: %d", name, len(raw)) + } + } + if len(s1.RealityShortID) != 8 { + t.Fatalf("short id should be 8 hex chars, got %q", s1.RealityShortID) + } + if s1.VLESSUUID == "" || s1.Hysteria2Password == "" { + t.Fatal("vless uuid / hysteria2 password must be set") + } +} + +func TestCertStableAndUsable(t *testing.T) { + ctx := context.Background() + st := newMemStore() + + cert, key, err := loadOrCreateCert(ctx, st, "www.example.com") + if err != nil { + t.Fatalf("create cert: %v", err) + } + if _, err := tls.X509KeyPair([]byte(cert), []byte(key)); err != nil { + t.Fatalf("cert/key not a valid pair: %v", err) + } + cert2, key2, err := loadOrCreateCert(ctx, st, "www.example.com") + if err != nil { + t.Fatalf("reload cert: %v", err) + } + if cert != cert2 || key != key2 { + t.Fatal("cert not persisted/stable across calls") + } +} + +func TestParamsDisabled(t *testing.T) { + cfg := testConfig(8443, 4443) + cfg.EnableStealth = false + m := New(cfg, newMemStore()) + if err := m.Init(context.Background()); err != nil { + t.Fatalf("init: %v", err) + } + if p := m.Params(); p.Enabled { + t.Fatal("params should report disabled when stealth is off") + } +} + +func TestStartListensAndClose(t *testing.T) { + ctx := context.Background() + vp, hp := freePort(t), freePort(t) + m := New(testConfig(vp, hp), newMemStore()) + if err := m.Init(ctx); err != nil { + t.Fatalf("init: %v", err) + } + if err := m.Start(ctx); err != nil { + t.Fatalf("start: %v", err) + } + defer m.Close() + + // The VLESS+REALITY carrier listens on TCP; a bare connect should succeed. + addr := fmt.Sprintf("127.0.0.1:%d", vp) + var conn net.Conn + var err error + for i := 0; i < 50; i++ { + conn, err = net.DialTimeout("tcp", addr, 500*time.Millisecond) + if err == nil { + break + } + time.Sleep(20 * time.Millisecond) + } + if err != nil { + t.Fatalf("VLESS carrier not listening on %s: %v", addr, err) + } + conn.Close() +} + +func TestBuildPeerArtifacts(t *testing.T) { + ctx := context.Background() + m := New(testConfig(8443, 4443), newMemStore()) + if err := m.Init(ctx); err != nil { + t.Fatalf("init: %v", err) + } + + ps := m.BuildPeer("alice", "c2VydmVycHVibGlja2V5MDAwMDAwMDAwMDAwMDAwMD0=", "10.0.0.7/32", "") + + if !strings.HasPrefix(ps.VLESSURI, "vless://") { + t.Fatalf("bad vless uri: %s", ps.VLESSURI) + } + for _, want := range []string{"security=reality", "flow=xtls-rprx-vision", "sni=www.microsoft.com", ":8443"} { + if !strings.Contains(ps.VLESSURI, want) { + t.Fatalf("vless uri missing %q: %s", want, ps.VLESSURI) + } + } + if !strings.HasPrefix(ps.Hysteria2URI, "hysteria2://") || !strings.Contains(ps.Hysteria2URI, ":4443") { + t.Fatalf("bad hysteria2 uri: %s", ps.Hysteria2URI) + } + + // The sing-box profile must serialize and carry a WG endpoint detoured + // through the VLESS carrier, with the WG peer pinned to the node loopback. + raw, err := json.Marshal(ps.SingboxProfile) + if err != nil { + t.Fatalf("marshal profile: %v", err) + } + s := string(raw) + for _, want := range []string{`"type":"wireguard"`, `"detour":"carrier-vless"`, `"carrier-hysteria2"`, `"127.0.0.1"`, ClientPrivateKeyPlaceholder} { + if !strings.Contains(s, want) { + t.Fatalf("profile missing %q: %s", want, s) + } + } +} diff --git a/internal/stealth/tlscert.go b/internal/stealth/tlscert.go new file mode 100644 index 0000000..36c5353 --- /dev/null +++ b/internal/stealth/tlscert.go @@ -0,0 +1,82 @@ +package stealth + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "time" +) + +// settings keys for the node's self-signed Hysteria2 TLS certificate. +const ( + keyHysteria2Cert = "stealth_hysteria2_cert_pem" + keyHysteria2Key = "stealth_hysteria2_key_pem" +) + +// loadOrCreateCert returns the PEM cert/key pair the Hysteria2 (QUIC) carrier +// presents. Hysteria2 runs over real TLS 1.3, so unlike the VLESS carrier it +// cannot use REALITY; a long-lived self-signed cert is generated once and +// persisted. Clients pin nothing and connect with insecure verification — the +// actual VPN authentication lives in the inner WireGuard tunnel. +func loadOrCreateCert(ctx context.Context, st SettingsStore, sni string) (certPEM, keyPEM string, err error) { + certPEM, err = st.GetSetting(ctx, keyHysteria2Cert) + if err != nil { + return "", "", err + } + keyPEM, err = st.GetSetting(ctx, keyHysteria2Key) + if err != nil { + return "", "", err + } + if certPEM != "" && keyPEM != "" { + return certPEM, keyPEM, nil + } + + certPEM, keyPEM, err = generateSelfSigned(sni) + if err != nil { + return "", "", err + } + if err = st.SetSetting(ctx, keyHysteria2Cert, certPEM); err != nil { + return "", "", err + } + if err = st.SetSetting(ctx, keyHysteria2Key, keyPEM); err != nil { + return "", "", err + } + return certPEM, keyPEM, nil +} + +func generateSelfSigned(sni string) (certPEM, keyPEM string, err error) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return "", "", err + } + serial, err := rand.Int(rand.Reader, new(big.Int).Lsh(big.NewInt(1), 128)) + if err != nil { + return "", "", err + } + tmpl := &x509.Certificate{ + SerialNumber: serial, + Subject: pkix.Name{CommonName: sni}, + DNSNames: []string{sni}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(10 * 365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) + if err != nil { + return "", "", err + } + keyDER, err := x509.MarshalPKCS8PrivateKey(key) + if err != nil { + return "", "", err + } + certPEM = string(pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})) + keyPEM = string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyDER})) + return certPEM, keyPEM, nil +} diff --git a/internal/store/acls.go b/internal/store/acls.go new file mode 100644 index 0000000..2ecc3ce --- /dev/null +++ b/internal/store/acls.go @@ -0,0 +1,50 @@ +package store + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +// ServiceACL is an access rule for a service. +type ServiceACL struct { + ID string + ServiceID string + Subject string + Action string + CreatedAt int64 +} + +// InsertServiceACL adds an ACL row. +func (s *Store) InsertServiceACL(ctx context.Context, acl ServiceACL) error { + if acl.ID == "" { + acl.ID = uuid.NewString() + } + if acl.CreatedAt == 0 { + acl.CreatedAt = time.Now().Unix() + } + _, err := s.db.ExecContext(ctx, + `INSERT INTO service_acls(id,service_id,subject,action,created_at) VALUES(?,?,?,?,?)`, + acl.ID, acl.ServiceID, acl.Subject, acl.Action, acl.CreatedAt) + return err +} + +// ListServiceACLs returns ACLs for a service. +func (s *Store) ListServiceACLs(ctx context.Context, serviceID string) ([]ServiceACL, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT id,service_id,subject,action,created_at FROM service_acls WHERE service_id=?`, serviceID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []ServiceACL + for rows.Next() { + var a ServiceACL + if err := rows.Scan(&a.ID, &a.ServiceID, &a.Subject, &a.Action, &a.CreatedAt); err != nil { + return nil, err + } + out = append(out, a) + } + return out, rows.Err() +} diff --git a/internal/store/carriers.go b/internal/store/carriers.go new file mode 100644 index 0000000..c9baef1 --- /dev/null +++ b/internal/store/carriers.go @@ -0,0 +1,72 @@ +package store + +import ( + "context" + "time" + + "github.com/google/uuid" +) + +// CarrierCredential is a rotated carrier secret record (hash only). +type CarrierCredential struct { + ID string + Transport string + SecretHash string + CreatedAt int64 + ExpiresAt int64 // 0 = no expiry + Active bool + Scope string + PeerID string +} + +// InsertCarrierCredential records a carrier credential hash. +func (s *Store) InsertCarrierCredential(ctx context.Context, c CarrierCredential) error { + if c.ID == "" { + c.ID = uuid.NewString() + } + now := time.Now().Unix() + if c.CreatedAt == 0 { + c.CreatedAt = now + } + active := 0 + if c.Active { + active = 1 + } + _, err := s.db.ExecContext(ctx, + `INSERT INTO carrier_credentials(id,transport,secret_hash,created_at,expires_at,active,scope,peer_id) + VALUES(?,?,?,?,?,?,?,?)`, + c.ID, c.Transport, c.SecretHash, c.CreatedAt, c.ExpiresAt, active, c.Scope, c.PeerID) + return err +} + +// DeactivateExpiredCarrierCredentials marks expired rows inactive. +func (s *Store) DeactivateExpiredCarrierCredentials(ctx context.Context, now int64) (int64, error) { + res, err := s.db.ExecContext(ctx, + `UPDATE carrier_credentials SET active=0 WHERE active=1 AND expires_at > 0 AND expires_at <= ?`, now) + if err != nil { + return 0, err + } + return res.RowsAffected() +} + +// ListActiveCarrierCredentials returns active credential records. +func (s *Store) ListActiveCarrierCredentials(ctx context.Context) ([]CarrierCredential, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT id,transport,secret_hash,created_at,expires_at,active,scope,peer_id + FROM carrier_credentials WHERE active=1 ORDER BY created_at DESC`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []CarrierCredential + for rows.Next() { + var c CarrierCredential + var active int + if err := rows.Scan(&c.ID, &c.Transport, &c.SecretHash, &c.CreatedAt, &c.ExpiresAt, &active, &c.Scope, &c.PeerID); err != nil { + return nil, err + } + c.Active = active == 1 + out = append(out, c) + } + return out, rows.Err() +} diff --git a/internal/store/domains.go b/internal/store/domains.go new file mode 100644 index 0000000..27a86bd --- /dev/null +++ b/internal/store/domains.go @@ -0,0 +1,49 @@ +package store + +import ( + "context" + "time" +) + +// AddServiceDomain maps a custom domain to a service. +func (s *Store) AddServiceDomain(ctx context.Context, serviceID, domain string) error { + _, err := s.db.ExecContext(ctx, + `INSERT INTO service_domains(service_id,domain,created_at) VALUES(?,?,?) + ON CONFLICT(service_id,domain) DO NOTHING`, + serviceID, domain, time.Now().Unix()) + return err +} + +// RemoveServiceDomain removes a custom domain mapping. +func (s *Store) RemoveServiceDomain(ctx context.Context, serviceID, domain string) error { + _, err := s.db.ExecContext(ctx, + `DELETE FROM service_domains WHERE service_id=? AND domain=?`, serviceID, domain) + return err +} + +// GetServiceByDomain finds a service id for a custom domain. +func (s *Store) GetServiceByDomain(ctx context.Context, domain string) (string, error) { + var id string + err := s.db.QueryRowContext(ctx, + `SELECT service_id FROM service_domains WHERE domain=?`, domain).Scan(&id) + return id, err +} + +// ListServiceDomains returns custom domains for a service. +func (s *Store) ListServiceDomains(ctx context.Context, serviceID string) ([]string, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT domain FROM service_domains WHERE service_id=? ORDER BY domain`, serviceID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var d string + if err := rows.Scan(&d); err != nil { + return nil, err + } + out = append(out, d) + } + return out, rows.Err() +} diff --git a/internal/store/ipalloc.go b/internal/store/ipalloc.go new file mode 100644 index 0000000..304eebe --- /dev/null +++ b/internal/store/ipalloc.go @@ -0,0 +1,86 @@ +package store + +import ( + "context" + "database/sql" + "errors" + "net" +) + +// ErrSubnetExhausted is returned when no free address remains. +var ErrSubnetExhausted = errors.New("wireguard subnet exhausted") + +// txAllocateIP finds the lowest free host address in subnet (CIDR with the +// server address as host bits, e.g. "10.0.0.1/16") not already assigned to a +// peer, and returns it as a /32 CIDR. Runs inside the caller's transaction so +// the read-then-insert is atomic against concurrent provisioning. +// +// The subnet's own host address (the server, e.g. 10.0.0.1), the network +// address, and the broadcast address are reserved. +func txAllocateIP(ctx context.Context, tx *sql.Tx, subnet string) (string, error) { + serverIP, ipnet, err := net.ParseCIDR(subnet) + if err != nil { + return "", err + } + + reserved := map[string]struct{}{} + // network + broadcast + server host + reserved[ipnet.IP.String()] = struct{}{} + reserved[broadcastAddr(ipnet).String()] = struct{}{} + reserved[serverIP.String()] = struct{}{} + + rows, err := tx.QueryContext(ctx, `SELECT wg_allowed_ip FROM peers`) + if err != nil { + return "", err + } + defer rows.Close() + for rows.Next() { + var cidr string + if err := rows.Scan(&cidr); err != nil { + return "", err + } + if ip, _, err := net.ParseCIDR(cidr); err == nil { + reserved[ip.String()] = struct{}{} + } + } + if err := rows.Err(); err != nil { + return "", err + } + + for ip := cloneIP(ipnet.IP.Mask(ipnet.Mask)); ipnet.Contains(ip); incIP(ip) { + addr := ip.String() + if _, taken := reserved[addr]; taken { + continue + } + return addr + "/32", nil + } + return "", ErrSubnetExhausted +} + +func cloneIP(ip net.IP) net.IP { + out := make(net.IP, len(ip)) + copy(out, ip) + return out +} + +func incIP(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +func broadcastAddr(n *net.IPNet) net.IP { + var b net.IP + if len(n.IP) == 4 { + b = net.ParseIP("0.0.0.0").To4() + } else { + b = net.ParseIP("::") + } + for i := 0; i < len(n.IP); i++ { + b[i] = n.IP[i] | ^n.Mask[i] + } + return b +} diff --git a/internal/store/migrations/0001_init.sql b/internal/store/migrations/0001_init.sql new file mode 100644 index 0000000..8ece54e --- /dev/null +++ b/internal/store/migrations/0001_init.sql @@ -0,0 +1,23 @@ +-- Erebrus node v2 local state. +CREATE TABLE IF NOT EXISTS peers ( + id TEXT PRIMARY KEY, -- gateway-issued VPN client UUID + name TEXT NOT NULL, + wallet TEXT NOT NULL DEFAULT '', + wg_public_key TEXT NOT NULL UNIQUE, + wg_allowed_ip TEXT NOT NULL UNIQUE, -- e.g. 10.0.0.7/32 + wg_preshared_key TEXT NOT NULL DEFAULT '', + proxy_uuid TEXT NOT NULL UNIQUE, -- VLESS user id (Phase 2) + proxy_password TEXT NOT NULL DEFAULT '', -- Hysteria2 password (Phase 2) + enabled INTEGER NOT NULL DEFAULT 1, + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0 -- unix seconds; 0 = never +); + +CREATE INDEX IF NOT EXISTS idx_peers_enabled ON peers(enabled); + +-- Key/value for node-level settings: WG server keypair, REALITY keys, ports. +CREATE TABLE IF NOT EXISTS node_settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); diff --git a/internal/store/migrations/0002_carrier_credentials.sql b/internal/store/migrations/0002_carrier_credentials.sql new file mode 100644 index 0000000..4aa8fbd --- /dev/null +++ b/internal/store/migrations/0002_carrier_credentials.sql @@ -0,0 +1,13 @@ +-- Carrier credential rotation audit trail (hashes only, no plaintext history). +CREATE TABLE IF NOT EXISTS carrier_credentials ( + id TEXT PRIMARY KEY, + transport TEXT NOT NULL, + secret_hash TEXT NOT NULL, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL DEFAULT 0, + active INTEGER NOT NULL DEFAULT 1, + scope TEXT NOT NULL DEFAULT 'node', + peer_id TEXT NOT NULL DEFAULT '' +); + +CREATE INDEX IF NOT EXISTS idx_carrier_credentials_active ON carrier_credentials(active, expires_at); \ No newline at end of file diff --git a/internal/store/migrations/0003_services.sql b/internal/store/migrations/0003_services.sql new file mode 100644 index 0000000..8c30eeb --- /dev/null +++ b/internal/store/migrations/0003_services.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS services ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + type TEXT NOT NULL DEFAULT '', + protocol TEXT NOT NULL DEFAULT 'http', + internal_addr TEXT NOT NULL, + port INTEGER NOT NULL, + owner_peer_id TEXT NOT NULL DEFAULT '', + owner_did TEXT NOT NULL DEFAULT '', + visibility TEXT NOT NULL DEFAULT 'private', + auth_mode TEXT NOT NULL DEFAULT 'vpn-peer', + tags TEXT NOT NULL DEFAULT '', + public INTEGER NOT NULL DEFAULT 0, + public_hostname TEXT NOT NULL DEFAULT '', + created_at INTEGER NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE INDEX IF NOT EXISTS idx_services_name ON services(name); +CREATE INDEX IF NOT EXISTS idx_services_visibility ON services(visibility); \ No newline at end of file diff --git a/internal/store/migrations/0004_service_acls.sql b/internal/store/migrations/0004_service_acls.sql new file mode 100644 index 0000000..141d2e4 --- /dev/null +++ b/internal/store/migrations/0004_service_acls.sql @@ -0,0 +1,10 @@ +CREATE TABLE IF NOT EXISTS service_acls ( + id TEXT PRIMARY KEY, + service_id TEXT NOT NULL, + subject TEXT NOT NULL, + action TEXT NOT NULL DEFAULT 'connect', + created_at INTEGER NOT NULL, + FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_service_acls_service ON service_acls(service_id); \ No newline at end of file diff --git a/internal/store/migrations/0005_service_domains.sql b/internal/store/migrations/0005_service_domains.sql new file mode 100644 index 0000000..d2deac0 --- /dev/null +++ b/internal/store/migrations/0005_service_domains.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS service_domains ( + service_id TEXT NOT NULL, + domain TEXT NOT NULL, + created_at INTEGER NOT NULL, + PRIMARY KEY (service_id, domain), + FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE +); + +CREATE INDEX IF NOT EXISTS idx_service_domains_domain ON service_domains(domain); \ No newline at end of file diff --git a/internal/store/services.go b/internal/store/services.go new file mode 100644 index 0000000..3dce7f1 --- /dev/null +++ b/internal/store/services.go @@ -0,0 +1,141 @@ +package store + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" +) + +// ServiceRow is the DB representation of a registered service. +type ServiceRow struct { + ID string + Name string + Type string + Protocol string + InternalAddr string + Port int + OwnerPeerID string + OwnerDID string + Visibility string + AuthMode string + Tags string + Public int + PublicHost string + CreatedAt int64 + UpdatedAt int64 +} + +// UpsertService inserts or replaces a service by id. +func (s *Store) UpsertService(ctx context.Context, row ServiceRow) error { + _, err := s.db.ExecContext(ctx, + `INSERT INTO services(id,name,type,protocol,internal_addr,port,owner_peer_id,owner_did, + visibility,auth_mode,tags,public,public_hostname,created_at,updated_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + ON CONFLICT(id) DO UPDATE SET + name=excluded.name, type=excluded.type, protocol=excluded.protocol, + internal_addr=excluded.internal_addr, port=excluded.port, + visibility=excluded.visibility, auth_mode=excluded.auth_mode, tags=excluded.tags, + public=excluded.public, public_hostname=excluded.public_hostname, updated_at=excluded.updated_at`, + row.ID, row.Name, row.Type, row.Protocol, row.InternalAddr, row.Port, + row.OwnerPeerID, row.OwnerDID, row.Visibility, row.AuthMode, row.Tags, + row.Public, row.PublicHost, row.CreatedAt, row.UpdatedAt) + return err +} + +// ListServices returns all registered services. +func (s *Store) ListServices(ctx context.Context) ([]ServiceRow, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT id,name,type,protocol,internal_addr,port,owner_peer_id,owner_did, + visibility,auth_mode,tags,public,public_hostname,created_at,updated_at + FROM services ORDER BY name`) + if err != nil { + return nil, err + } + defer rows.Close() + return scanServices(rows) +} + +// GetService fetches a service by id. +func (s *Store) GetService(ctx context.Context, id string) (*ServiceRow, error) { + row := s.db.QueryRowContext(ctx, + `SELECT id,name,type,protocol,internal_addr,port,owner_peer_id,owner_did, + visibility,auth_mode,tags,public,public_hostname,created_at,updated_at + FROM services WHERE id=?`, id) + out, err := scanService(row.Scan) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("%w: service", ErrNotFound) + } + return out, err +} + +// GetServiceByName fetches the first service matching name. +func (s *Store) GetServiceByName(ctx context.Context, name string) (*ServiceRow, error) { + row := s.db.QueryRowContext(ctx, + `SELECT id,name,type,protocol,internal_addr,port,owner_peer_id,owner_did, + visibility,auth_mode,tags,public,public_hostname,created_at,updated_at + FROM services WHERE name=? LIMIT 1`, name) + out, err := scanService(row.Scan) + if errors.Is(err, sql.ErrNoRows) { + return nil, fmt.Errorf("%w: service", ErrNotFound) + } + return out, err +} + +// DeleteService removes a service by id. +func (s *Store) DeleteService(ctx context.Context, id string) error { + res, err := s.db.ExecContext(ctx, `DELETE FROM services WHERE id=?`, id) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("%w: service", ErrNotFound) + } + return nil +} + +// SetServicePublic updates public exposure fields. +func (s *Store) SetServicePublic(ctx context.Context, id, hostname string, public bool) error { + pub := 0 + if public { + pub = 1 + } + res, err := s.db.ExecContext(ctx, + `UPDATE services SET public=?, public_hostname=?, updated_at=? WHERE id=?`, + pub, hostname, time.Now().Unix(), id) + if err != nil { + return err + } + n, _ := res.RowsAffected() + if n == 0 { + return fmt.Errorf("%w: service", ErrNotFound) + } + return nil +} + +func scanServices(rows *sql.Rows) ([]ServiceRow, error) { + var out []ServiceRow + for rows.Next() { + var r ServiceRow + if err := rows.Scan(&r.ID, &r.Name, &r.Type, &r.Protocol, &r.InternalAddr, &r.Port, + &r.OwnerPeerID, &r.OwnerDID, &r.Visibility, &r.AuthMode, &r.Tags, + &r.Public, &r.PublicHost, &r.CreatedAt, &r.UpdatedAt); err != nil { + return nil, err + } + out = append(out, r) + } + return out, rows.Err() +} + +func scanService(scan func(dest ...any) error) (*ServiceRow, error) { + var r ServiceRow + err := scan(&r.ID, &r.Name, &r.Type, &r.Protocol, &r.InternalAddr, &r.Port, + &r.OwnerPeerID, &r.OwnerDID, &r.Visibility, &r.AuthMode, &r.Tags, + &r.Public, &r.PublicHost, &r.CreatedAt, &r.UpdatedAt) + if err != nil { + return nil, err + } + return &r, nil +} diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100644 index 0000000..9995d5a --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,264 @@ +// Package store is the node-local persistence layer, backed by SQLite +// (modernc.org/sqlite, pure Go — CGO-free). It replaces the v1 per-UUID JSON +// files and gives us atomic multi-protocol provisioning and race-free IP +// allocation. +package store + +import ( + "context" + "database/sql" + "embed" + "errors" + "fmt" + "os" + "sort" + "time" + + _ "modernc.org/sqlite" +) + +//go:embed migrations/*.sql +var migrationsFS embed.FS + +// ErrNotFound is returned when a peer does not exist. +var ErrNotFound = errors.New("not found") + +// Store wraps the SQLite database. +type Store struct { + db *sql.DB +} + +// Peer is a provisioned VPN client on this node. +type Peer struct { + ID string + Name string + Wallet string + WGPublicKey string + WGAllowedIP string // CIDR, e.g. 10.0.0.7/32 + WGPresharedKey string + ProxyUUID string + ProxyPassword string + Enabled bool + CreatedAt int64 + UpdatedAt int64 + ExpiresAt int64 +} + +// Open opens (creating if necessary) the SQLite database at path and applies +// migrations. Busy timeout + WAL keep concurrent reads smooth. +func Open(path string) (*Store, error) { + dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=journal_mode(WAL)&_pragma=foreign_keys(1)", path) + db, err := sql.Open("sqlite", dsn) + if err != nil { + return nil, err + } + // SQLite is single-writer; cap connections to avoid lock churn. + db.SetMaxOpenConns(1) + s := &Store{db: db} + if err := s.migrate(); err != nil { + _ = db.Close() + return nil, err + } + // The DB holds private key material (WG server key, REALITY key, per-peer + // PSKs). Restrict it to the owner; default SQLite creation is 0644. + restrictPerms(path) + return s, nil +} + +// restrictPerms tightens the DB file (and its WAL/SHM sidecars) to 0600. +func restrictPerms(path string) { + for _, p := range []string{path, path + "-wal", path + "-shm"} { + _ = os.Chmod(p, 0o600) + } +} + +// Close closes the database. +func (s *Store) Close() error { return s.db.Close() } + +func (s *Store) migrate() error { + entries, err := migrationsFS.ReadDir("migrations") + if err != nil { + return err + } + names := make([]string, 0, len(entries)) + for _, e := range entries { + names = append(names, e.Name()) + } + sort.Strings(names) + for _, name := range names { + b, err := migrationsFS.ReadFile("migrations/" + name) + if err != nil { + return err + } + if _, err := s.db.Exec(string(b)); err != nil { + return fmt.Errorf("migration %s: %w", name, err) + } + } + return nil +} + +// --- node_settings --- + +// GetSetting returns a setting value; ("", nil) if absent. +func (s *Store) GetSetting(ctx context.Context, key string) (string, error) { + var v string + err := s.db.QueryRowContext(ctx, `SELECT value FROM node_settings WHERE key = ?`, key).Scan(&v) + if errors.Is(err, sql.ErrNoRows) { + return "", nil + } + return v, err +} + +// SetSetting upserts a setting. +func (s *Store) SetSetting(ctx context.Context, key, value string) error { + _, err := s.db.ExecContext(ctx, + `INSERT INTO node_settings(key, value) VALUES(?, ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value`, key, value) + return err +} + +// --- peers --- + +// GetPeer returns a peer by id. +func (s *Store) GetPeer(ctx context.Context, id string) (*Peer, error) { + row := s.db.QueryRowContext(ctx, selectCols+` WHERE id = ?`, id) + p, err := scanPeer(row) + if errors.Is(err, sql.ErrNoRows) { + return nil, ErrNotFound + } + return p, err +} + +// ListPeers returns all peers ordered by creation time. +func (s *Store) ListPeers(ctx context.Context) ([]*Peer, error) { + rows, err := s.db.QueryContext(ctx, selectCols+` ORDER BY created_at ASC`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []*Peer + for rows.Next() { + p, err := scanPeer(rows) + if err != nil { + return nil, err + } + out = append(out, p) + } + return out, rows.Err() +} + +// DeletePeer removes a peer. Idempotent: deleting a missing peer is not an error. +func (s *Store) DeletePeer(ctx context.Context, id string) error { + _, err := s.db.ExecContext(ctx, `DELETE FROM peers WHERE id = ?`, id) + return err +} + +// UpsertPeer creates or updates a peer, allocating a WireGuard IP from subnet +// on first creation. The whole operation runs in one immediate transaction so +// IP allocation is race-free even under concurrent calls. On update, the +// allocated IP and generated proxy credentials are preserved. +// +// gen supplies freshly generated values used only when creating a new peer. +func (s *Store) UpsertPeer(ctx context.Context, in *Peer, subnet string, gen GeneratedCreds) (*Peer, error) { + tx, err := s.db.BeginTx(ctx, &sql.TxOptions{}) + if err != nil { + return nil, err + } + defer tx.Rollback() //nolint:errcheck + + existing, err := txGetPeer(ctx, tx, in.ID) + if err != nil && !errors.Is(err, sql.ErrNoRows) { + return nil, err + } + + now := time.Now().Unix() + if existing != nil { + // Update: preserve IP and proxy credentials, refresh mutable fields. + existing.Name = in.Name + existing.Wallet = in.Wallet + existing.WGPublicKey = in.WGPublicKey + existing.WGPresharedKey = in.WGPresharedKey + existing.Enabled = in.Enabled + existing.ExpiresAt = in.ExpiresAt + existing.UpdatedAt = now + if _, err := tx.ExecContext(ctx, + `UPDATE peers SET name=?, wallet=?, wg_public_key=?, wg_preshared_key=?, + enabled=?, updated_at=?, expires_at=? WHERE id=?`, + existing.Name, existing.Wallet, existing.WGPublicKey, existing.WGPresharedKey, + boolToInt(existing.Enabled), existing.UpdatedAt, existing.ExpiresAt, existing.ID); err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + return existing, nil + } + + // Create: allocate the next free IP within the transaction. + allocated, err := txAllocateIP(ctx, tx, subnet) + if err != nil { + return nil, err + } + p := &Peer{ + ID: in.ID, + Name: in.Name, + Wallet: in.Wallet, + WGPublicKey: in.WGPublicKey, + WGAllowedIP: allocated, + WGPresharedKey: in.WGPresharedKey, + ProxyUUID: gen.ProxyUUID, + ProxyPassword: gen.ProxyPassword, + Enabled: in.Enabled, + CreatedAt: now, + UpdatedAt: now, + ExpiresAt: in.ExpiresAt, + } + if _, err := tx.ExecContext(ctx, + `INSERT INTO peers(id,name,wallet,wg_public_key,wg_allowed_ip,wg_preshared_key, + proxy_uuid,proxy_password,enabled,created_at,updated_at,expires_at) + VALUES(?,?,?,?,?,?,?,?,?,?,?,?)`, + p.ID, p.Name, p.Wallet, p.WGPublicKey, p.WGAllowedIP, p.WGPresharedKey, + p.ProxyUUID, p.ProxyPassword, boolToInt(p.Enabled), p.CreatedAt, p.UpdatedAt, p.ExpiresAt); err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + return p, nil +} + +// GeneratedCreds carries freshly minted credentials for a new peer. +type GeneratedCreds struct { + ProxyUUID string + ProxyPassword string +} + +const selectCols = `SELECT id,name,wallet,wg_public_key,wg_allowed_ip,wg_preshared_key, + proxy_uuid,proxy_password,enabled,created_at,updated_at,expires_at FROM peers` + +type scanner interface { + Scan(dest ...any) error +} + +func scanPeer(sc scanner) (*Peer, error) { + var p Peer + var enabled int + err := sc.Scan(&p.ID, &p.Name, &p.Wallet, &p.WGPublicKey, &p.WGAllowedIP, &p.WGPresharedKey, + &p.ProxyUUID, &p.ProxyPassword, &enabled, &p.CreatedAt, &p.UpdatedAt, &p.ExpiresAt) + if err != nil { + return nil, err + } + p.Enabled = enabled != 0 + return &p, nil +} + +func txGetPeer(ctx context.Context, tx *sql.Tx, id string) (*Peer, error) { + return scanPeer(tx.QueryRowContext(ctx, selectCols+` WHERE id = ?`, id)) +} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} diff --git a/internal/telemetry/telemetry.go b/internal/telemetry/telemetry.go new file mode 100644 index 0000000..021c322 --- /dev/null +++ b/internal/telemetry/telemetry.go @@ -0,0 +1,52 @@ +// Package telemetry sets up structured logging (slog JSON) and Prometheus +// metrics for the node. +package telemetry + +import ( + "log/slog" + "os" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promauto" +) + +// InitLogger installs a JSON slog logger as the default. debug=true lowers the +// level to Debug. +func InitLogger(debug bool) { + level := slog.LevelInfo + if debug { + level = slog.LevelDebug + } + h := slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: level}) + slog.SetDefault(slog.New(h)) +} + +// Metrics holds the node's Prometheus collectors. +type Metrics struct { + WGPeers prometheus.Gauge + ProxySessions prometheus.Gauge + SingboxRebuilds prometheus.Counter + PeerProvisioned prometheus.Counter + PeerDeprovisioned prometheus.Counter +} + +// NewMetrics registers and returns the node metrics on the default registry. +func NewMetrics() *Metrics { + return &Metrics{ + WGPeers: promauto.NewGauge(prometheus.GaugeOpts{ + Name: "erebrus_wg_peers", Help: "Number of configured WireGuard peers.", + }), + ProxySessions: promauto.NewGauge(prometheus.GaugeOpts{ + Name: "erebrus_proxy_sessions", Help: "Active sing-box proxy sessions.", + }), + SingboxRebuilds: promauto.NewCounter(prometheus.CounterOpts{ + Name: "erebrus_singbox_rebuilds_total", Help: "sing-box configuration rebuilds.", + }), + PeerProvisioned: promauto.NewCounter(prometheus.CounterOpts{ + Name: "erebrus_peer_provisioned_total", Help: "Peers provisioned.", + }), + PeerDeprovisioned: promauto.NewCounter(prometheus.CounterOpts{ + Name: "erebrus_peer_deprovisioned_total", Help: "Peers removed.", + }), + } +} diff --git a/internal/templates/templates.go b/internal/templates/templates.go new file mode 100644 index 0000000..e85547c --- /dev/null +++ b/internal/templates/templates.go @@ -0,0 +1,60 @@ +// Package templates provides one-command service setup presets. +package templates + +import ( + "context" + "fmt" + + "github.com/NetSepio/erebrus/internal/services" +) + +// Template describes a built-in service preset. +type Template struct { + Name string `json:"name"` + Type string `json:"type"` + Ports []int `json:"ports"` + Protocol string `json:"protocol"` + DefaultVisibility string `json:"default_visibility"` + DefaultAuth string `json:"default_auth"` + Description string `json:"description"` +} + +// Catalog returns built-in templates. +func Catalog() []Template { + return []Template{ + {Name: "drop-room", Type: "webdav", Ports: []int{8787}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "WebDAV/file sharing"}, + {Name: "ollama", Type: "ai.llm", Ports: []int{11434}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "Local Ollama LLM API"}, + {Name: "openai-compatible-llm", Type: "ai.llm", Ports: []int{8080}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "OpenAI-compatible local endpoint"}, + {Name: "nextcloud", Type: "web", Ports: []int{8080}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "Nextcloud"}, + {Name: "home-assistant", Type: "web", Ports: []int{8123}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "Home Assistant"}, + {Name: "dashboard", Type: "web", Ports: []int{3000}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "Local web dashboard"}, + {Name: "static-site", Type: "web", Ports: []int{8080}, Protocol: "http", DefaultVisibility: "private", DefaultAuth: "vpn-peer", Description: "Simple static site"}, + } +} + +// Find returns a template by name. +func Find(name string) (*Template, error) { + for _, t := range Catalog() { + if t.Name == name { + return &t, nil + } + } + return nil, fmt.Errorf("unknown template %q", name) +} + +// Install registers a service from a template. +func Install(ctx context.Context, reg *services.Registry, name string) (*services.Service, error) { + tpl, err := Find(name) + if err != nil { + return nil, err + } + port := tpl.Ports[0] + return reg.Publish(ctx, services.Service{ + Name: tpl.Name, + Type: tpl.Type, + Port: port, + Protocol: tpl.Protocol, + Visibility: tpl.DefaultVisibility, + AuthMode: tpl.DefaultAuth, + }) +} diff --git a/internal/templates/templates_test.go b/internal/templates/templates_test.go new file mode 100644 index 0000000..cbb1041 --- /dev/null +++ b/internal/templates/templates_test.go @@ -0,0 +1,26 @@ +package templates + +import ( + "context" + "path/filepath" + "testing" + + "github.com/NetSepio/erebrus/internal/services" + "github.com/NetSepio/erebrus/internal/store" +) + +func TestInstallOllama(t *testing.T) { + st, err := store.Open(filepath.Join(t.TempDir(), "tpl.db")) + if err != nil { + t.Fatal(err) + } + defer st.Close() + reg := &services.Registry{St: st} + svc, err := Install(context.Background(), reg, "ollama") + if err != nil { + t.Fatal(err) + } + if svc.Port != 11434 || svc.Type != "ai.llm" { + t.Fatalf("svc = %+v", svc) + } +} diff --git a/internal/transport/probe/probe.go b/internal/transport/probe/probe.go new file mode 100644 index 0000000..e00781e --- /dev/null +++ b/internal/transport/probe/probe.go @@ -0,0 +1,63 @@ +// Package probe defines the transport probing interface. Concrete network +// probes are added incrementally; the node ships with a local evaluator that +// scores configured listeners. +package probe + +import ( + "context" + + "github.com/NetSepio/erebrus/internal/transport" +) + +// Prober checks transport reachability from the node's perspective. +type Prober interface { + Probe(ctx context.Context, kinds []transport.Kind) []transport.ProbeResult +} + +// LocalProber assumes transports are available when the node has them configured. +type LocalProber struct { + StealthEnabled bool + WGPort int + VLESSPort int + Hysteria2Port int +} + +// Probe returns synthetic success for implemented local listeners. +func (p *LocalProber) Probe(_ context.Context, kinds []transport.Kind) []transport.ProbeResult { + out := make([]transport.ProbeResult, 0, len(kinds)) + for _, k := range kinds { + r := transport.ProbeResult{Kind: k, LatencyMs: 1} + switch k { + case transport.KindDirectWG: + if p.WGPort > 0 { + r.Success = true + } else { + r.Error = "wireguard port not configured" + } + case transport.KindHysteria2: + if p.StealthEnabled && p.Hysteria2Port > 0 { + r.Success = true + } else { + r.Error = "hysteria2 not enabled" + } + case transport.KindVLESSReality: + if p.StealthEnabled && p.VLESSPort > 0 { + r.Success = true + } else { + r.Error = "vless+reality not enabled" + } + default: + r.Error = "not implemented" + } + transport.Score(r, false, false) + out = append(out, r) + } + return out +} + +// Select runs the ladder and returns the best transport. +func Select(ctx context.Context, prober Prober, stealthEnabled bool) (transport.ProbeResult, bool) { + kinds := transport.ImplementedLadder(stealthEnabled) + results := prober.Probe(ctx, kinds) + return transport.SelectBest(results) +} diff --git a/internal/transport/transport.go b/internal/transport/transport.go new file mode 100644 index 0000000..c5b29fe --- /dev/null +++ b/internal/transport/transport.go @@ -0,0 +1,116 @@ +// Package transport models the stealth transport ladder: preferred order, +// probing, scoring, and selection. Clients use this to pick the best working +// path when WireGuard UDP is blocked. +package transport + +import "sort" + +// Kind identifies a transport in the ladder. +type Kind string + +const ( + KindDirectWG Kind = "direct_wireguard_udp" + KindHysteria2 Kind = "hysteria2_quic_udp" + KindVLESSReality Kind = "vless_reality_tcp" + KindWebSocketTLS Kind = "websocket_tls_tcp" + KindHTTPSConnect Kind = "https_connect_tcp" +) + +// DefaultLadder is the preferred transport order for v2.1. +var DefaultLadder = []Kind{ + KindDirectWG, + KindHysteria2, + KindVLESSReality, + KindWebSocketTLS, + KindHTTPSConnect, +} + +// ImplementedLadder returns transports the node can offer today. +func ImplementedLadder(stealthEnabled bool) []Kind { + if !stealthEnabled { + return []Kind{KindDirectWG} + } + return []Kind{KindDirectWG, KindHysteria2, KindVLESSReality} +} + +// ProbeResult is the outcome of probing one transport. +type ProbeResult struct { + Kind Kind + Success bool + LatencyMs int + PacketLossPct float64 + Error string + Score int +} + +// Score computes the transport ranking per the v2 upgrade plan. +func Score(r ProbeResult, survived60s bool, wasLastSuccess bool) int { + if !r.Success { + return 0 + } + s := 100 - r.LatencyMs/10 - int(r.PacketLossPct*2) + if survived60s { + s += 20 + } + if wasLastSuccess { + s += 10 + } + if s < 0 { + s = 0 + } + r.Score = s + return s +} + +// SelectBest picks the highest-scoring successful probe. +func SelectBest(results []ProbeResult) (ProbeResult, bool) { + var best *ProbeResult + var bestScore int + for i := range results { + if !results[i].Success { + continue + } + s := Score(results[i], false, false) + if best == nil || s > bestScore { + cp := results[i] + cp.Score = s + best = &cp + bestScore = s + } + } + if best == nil { + return ProbeResult{}, false + } + return *best, true +} + +// SortByLadder orders kinds according to the preferred ladder. +func SortByLadder(kinds []Kind) []Kind { + order := map[Kind]int{} + for i, k := range DefaultLadder { + order[k] = i + } + out := append([]Kind(nil), kinds...) + sort.Slice(out, func(i, j int) bool { + oi, oki := order[out[i]] + oj, okj := order[out[j]] + if !oki { + oi = len(DefaultLadder) + } + if !okj { + oj = len(DefaultLadder) + } + return oi < oj + }) + return out +} + +// IsImplemented reports whether a kind has a node-side implementation. +func IsImplemented(k Kind, stealthEnabled bool) bool { + for _, x := range ImplementedLadder(stealthEnabled) { + if x == k { + return true + } + } + return false +} diff --git a/internal/transport/transport_test.go b/internal/transport/transport_test.go new file mode 100644 index 0000000..5a49b3a --- /dev/null +++ b/internal/transport/transport_test.go @@ -0,0 +1,42 @@ +package transport + +import "testing" + +func TestDefaultLadderOrder(t *testing.T) { + if DefaultLadder[0] != KindDirectWG { + t.Fatalf("first = %s", DefaultLadder[0]) + } + if DefaultLadder[1] != KindHysteria2 { + t.Fatalf("second = %s", DefaultLadder[1]) + } +} + +func TestScore(t *testing.T) { + r := ProbeResult{Kind: KindDirectWG, Success: true, LatencyMs: 50, PacketLossPct: 1} + s := Score(r, true, true) + // 100 - 5 - 2 + 20 + 10 = 123 + if s != 123 { + t.Fatalf("score = %d", s) + } +} + +func TestSelectBest(t *testing.T) { + results := []ProbeResult{ + {Kind: KindDirectWG, Success: true, LatencyMs: 10}, + {Kind: KindHysteria2, Success: true, LatencyMs: 5}, + {Kind: KindVLESSReality, Success: false}, + } + best, ok := SelectBest(results) + if !ok || best.Kind != KindHysteria2 { + t.Fatalf("best = %+v ok=%v", best, ok) + } +} + +func TestImplementedLadder(t *testing.T) { + if len(ImplementedLadder(false)) != 1 { + t.Fatal("expected wg only without stealth") + } + if len(ImplementedLadder(true)) != 3 { + t.Fatal("expected 3 with stealth") + } +} diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go new file mode 100644 index 0000000..b7cc469 --- /dev/null +++ b/internal/wallet/wallet.go @@ -0,0 +1,214 @@ +// Package wallet derives node wallet keys from the BIP39 mnemonic and signs +// gateway registration challenges. Solana (chain "sol") is the default; EVM +// (chain "evm") is also supported to match erebrus-gateway/internal/gw/wallet. +package wallet + +import ( + "crypto/ecdsa" + "crypto/ed25519" + "encoding/hex" + "fmt" + "strings" + + "github.com/blocto/solana-go-sdk/pkg/hdwallet" + "github.com/blocto/solana-go-sdk/types" + ethcrypto "github.com/ethereum/go-ethereum/crypto" + "github.com/mr-tron/base58" + bip32 "github.com/tyler-smith/go-bip32" + bip39 "github.com/tyler-smith/go-bip39" + "golang.org/x/crypto/sha3" +) + +const ( + ChainEVM = "evm" + ChainSOL = "sol" +) + +// ChainLabel returns a user-facing name for a chain code. +func ChainLabel(chain string) string { + switch strings.ToLower(strings.TrimSpace(chain)) { + case ChainSOL, "solana": + return "Solana" + case ChainEVM, "ethereum": + return "EVM" + default: + if chain == "" { + return "Solana" + } + return chain + } +} + +// Identity is a mnemonic-derived wallet used for gateway registration. +type Identity struct { + Chain string + Address string + PubKey string // base58 (sol) or hex pubkey (evm/apt/sui) +} + +// AddressFromMnemonic returns the wallet address for the given chain. +func AddressFromMnemonic(mnemonic, chain string) (string, error) { + id, err := Derive(mnemonic, chain) + if err != nil { + return "", err + } + return id.Address, nil +} + +// PublicKeyFromMnemonic returns the signing public key for gateway registration. +func PublicKeyFromMnemonic(mnemonic, chain string) (string, error) { + chain = strings.ToLower(strings.TrimSpace(chain)) + if chain == "" { + chain = ChainSOL + } + switch chain { + case ChainSOL: + seed := bip39.NewSeed(mnemonic, "") + derived, err := hdwallet.Derived(`m/44'/501'/0'/0'`, seed) + if err != nil { + return "", err + } + account, err := types.AccountFromSeed(derived.PrivateKey) + if err != nil { + return "", err + } + return account.PublicKey.ToBase58(), nil + case ChainEVM: + _, key, err := evmKeypair(mnemonic) + if err != nil { + return "", err + } + pub := key.Public().(*ecdsa.PublicKey) + return hex.EncodeToString(ethcrypto.FromECDSAPub(pub)), nil + default: + return "", fmt.Errorf("unsupported chain %q", chain) + } +} + +// Derive returns the wallet identity for the given chain. +func Derive(mnemonic, chain string) (*Identity, error) { + chain = strings.ToLower(strings.TrimSpace(chain)) + if chain == "" { + chain = ChainSOL + } + if !bip39.IsMnemonicValid(mnemonic) { + return nil, fmt.Errorf("invalid mnemonic") + } + switch chain { + case ChainSOL: + return deriveSolana(mnemonic) + case ChainEVM: + return deriveEVM(mnemonic) + default: + return nil, fmt.Errorf("unsupported wallet chain %q (use sol or evm)", chain) + } +} + +func deriveSolana(mnemonic string) (*Identity, error) { + seed := bip39.NewSeed(mnemonic, "") + derived, err := hdwallet.Derived(`m/44'/501'/0'/0'`, seed) + if err != nil { + return nil, fmt.Errorf("derive solana key: %w", err) + } + account, err := types.AccountFromSeed(derived.PrivateKey) + if err != nil { + return nil, fmt.Errorf("solana account: %w", err) + } + return &Identity{ + Chain: ChainSOL, + Address: account.PublicKey.ToBase58(), + PubKey: account.PublicKey.ToBase58(), + }, nil +} + +// SignChallengeWithMnemonic signs a challenge using the mnemonic directly. +func SignChallengeWithMnemonic(mnemonic, chain, message string) (address, publicKey, signature string, err error) { + chain = strings.ToLower(strings.TrimSpace(chain)) + if chain == "" { + chain = ChainSOL + } + switch chain { + case ChainSOL: + seed := bip39.NewSeed(mnemonic, "") + derived, err := hdwallet.Derived(`m/44'/501'/0'/0'`, seed) + if err != nil { + return "", "", "", err + } + account, err := types.AccountFromSeed(derived.PrivateKey) + if err != nil { + return "", "", "", err + } + sig := ed25519.Sign(account.PrivateKey, []byte(message)) + return account.PublicKey.ToBase58(), account.PublicKey.ToBase58(), base58.Encode(sig), nil + case ChainEVM: + addr, key, err := evmKeypair(mnemonic) + if err != nil { + return "", "", "", err + } + prefixed := fmt.Sprintf("\x19Ethereum Signed Message:\n%d%s", len(message), message) + hash := ethcrypto.Keccak256Hash([]byte(prefixed)) + sig, err := ethcrypto.Sign(hash.Bytes(), key) + if err != nil { + return "", "", "", err + } + if sig[64] < 27 { + sig[64] += 27 + } + pub := key.Public().(*ecdsa.PublicKey) + return addr, hex.EncodeToString(ethcrypto.FromECDSAPub(pub)), "0x" + hex.EncodeToString(sig), nil + default: + return "", "", "", fmt.Errorf("unsupported chain %q", chain) + } +} + +func deriveEVM(mnemonic string) (*Identity, error) { + addr, _, err := evmKeypair(mnemonic) + if err != nil { + return nil, err + } + return &Identity{Chain: ChainEVM, Address: addr, PubKey: ""}, nil +} + +func evmKeypair(mnemonic string) (address string, key *ecdsa.PrivateKey, err error) { + seed := bip39.NewSeed(mnemonic, "") + master, err := bip32.NewMasterKey(seed) + if err != nil { + return "", nil, err + } + child := master + for _, idx := range []uint32{bip32.FirstHardenedChild + 44, bip32.FirstHardenedChild + 60, bip32.FirstHardenedChild + 0, 0, 0} { + child, err = child.NewChildKey(idx) + if err != nil { + return "", nil, err + } + } + key, err = ethcrypto.ToECDSA(child.Key) + if err != nil { + return "", nil, err + } + pub := key.Public().(*ecdsa.PublicKey) + pubBytes := ethcrypto.FromECDSAPub(pub) + keccak := sha3.NewLegacyKeccak256() + keccak.Write(pubBytes[1:]) + addrBytes := keccak.Sum(nil)[12:] + return toChecksumAddress(hex.EncodeToString(addrBytes)), key, nil +} + +func toChecksumAddress(address string) string { + address = strings.ToLower(address) + keccak := sha3.NewLegacyKeccak256() + keccak.Write([]byte(address)) + hash := keccak.Sum(nil) + var b strings.Builder + b.WriteString("0x") + for i, c := range address { + if c >= '0' && c <= '9' { + b.WriteRune(c) + } else if hash[i/2]>>(4*(1-i%2))&0xF >= 8 { + b.WriteRune(c - 'a' + 'A') + } else { + b.WriteRune(c) + } + } + return b.String() +} diff --git a/internal/wallet/wallet_test.go b/internal/wallet/wallet_test.go new file mode 100644 index 0000000..1185205 --- /dev/null +++ b/internal/wallet/wallet_test.go @@ -0,0 +1,39 @@ +package wallet + +import "testing" + +func TestSolanaDerivationStable(t *testing.T) { + mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + addr1, err := AddressFromMnemonic(mnemonic, ChainSOL) + if err != nil { + t.Fatal(err) + } + addr2, err := AddressFromMnemonic(mnemonic, ChainSOL) + if err != nil { + t.Fatal(err) + } + if addr1 != addr2 || addr1 == "" { + t.Fatalf("address = %q", addr1) + } +} + +func TestChainLabel(t *testing.T) { + if ChainLabel(ChainSOL) != "Solana" { + t.Fatalf("sol label = %q", ChainLabel(ChainSOL)) + } + if ChainLabel("") != "Solana" { + t.Fatalf("empty label = %q", ChainLabel("")) + } +} + +func TestSignChallengeSolana(t *testing.T) { + mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" + msg := "I accept the Erebrus Terms of Service https://erebrus.network/terms. Challenge: test-flow" + addr, pub, sig, err := SignChallengeWithMnemonic(mnemonic, ChainSOL, msg) + if err != nil { + t.Fatal(err) + } + if addr == "" || pub == "" || sig == "" { + t.Fatalf("empty sign output addr=%q pub=%q sig=%q", addr, pub, sig) + } +} diff --git a/internal/wg/controller.go b/internal/wg/controller.go new file mode 100644 index 0000000..4ba5a50 --- /dev/null +++ b/internal/wg/controller.go @@ -0,0 +1,147 @@ +package wg + +import ( + "fmt" + "net" + "os/exec" + "strings" + "time" + + "github.com/NetSepio/erebrus/internal/store" + "golang.zx2c4.com/wireguard/wgctrl" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +// DeviceStats is a coarse, live snapshot of the WireGuard interface. +type DeviceStats struct { + RxBytes int64 // cumulative bytes received from peers + TxBytes int64 // cumulative bytes sent to peers + Connected int // peers with a handshake in the last 3 minutes +} + +// PeerTransfer is live transfer counters for one WireGuard peer. +type PeerTransfer struct { + WGPublicKey string + RxBytes int64 + TxBytes int64 + LastHandshake int64 // unix seconds; 0 if never +} + +// Controller abstracts the host's WireGuard plumbing so the Manager can be +// unit-tested with a fake. The real implementation uses wg-quick for the +// interface lifecycle (addresses + PostUp/Down rules) and wgctrl for live +// peer changes (no interface bounce, sessions survive). +type Controller interface { + // BringUp (re)creates the interface from the rendered conf file. + BringUp(iface, confPath string) error + // SyncPeers replaces the live peer set on iface to match peers. + SyncPeers(iface string, peers []*store.Peer) error + // Stats reads live transfer counters and active-peer count from the device. + Stats(iface string) (DeviceStats, error) + // PeerTransfers returns per-peer transfer counters keyed by WG public key. + PeerTransfers(iface string) ([]PeerTransfer, error) +} + +// realController talks to the kernel via wg-quick and wgctrl. +type realController struct{} + +// NewController returns the production WireGuard controller. +func NewController() Controller { return &realController{} } + +func (r *realController) BringUp(iface, confPath string) error { + // Idempotent: tear down a stale interface first, ignore its error. + _ = exec.Command("wg-quick", "down", confPath).Run() + out, err := exec.Command("wg-quick", "up", confPath).CombinedOutput() + if err != nil { + return fmt.Errorf("wg-quick up: %v: %s", err, strings.TrimSpace(string(out))) + } + return nil +} + +func (r *realController) SyncPeers(iface string, peers []*store.Peer) error { + cl, err := wgctrl.New() + if err != nil { + return err + } + defer cl.Close() + + cfgs := make([]wgtypes.PeerConfig, 0, len(peers)) + for _, p := range peers { + if !p.Enabled { + continue + } + pub, err := wgtypes.ParseKey(p.WGPublicKey) + if err != nil { + return fmt.Errorf("peer %s bad public key: %w", p.ID, err) + } + pc := wgtypes.PeerConfig{ + PublicKey: pub, + ReplaceAllowedIPs: true, + } + if p.WGPresharedKey != "" { + psk, err := wgtypes.ParseKey(p.WGPresharedKey) + if err != nil { + return fmt.Errorf("peer %s bad preshared key: %w", p.ID, err) + } + pc.PresharedKey = &psk + } + _, ipnet, err := net.ParseCIDR(p.WGAllowedIP) + if err != nil { + return fmt.Errorf("peer %s bad allowed ip: %w", p.ID, err) + } + pc.AllowedIPs = []net.IPNet{*ipnet} + cfgs = append(cfgs, pc) + } + + return cl.ConfigureDevice(iface, wgtypes.Config{ + ReplacePeers: true, + Peers: cfgs, + }) +} + +func (r *realController) Stats(iface string) (DeviceStats, error) { + cl, err := wgctrl.New() + if err != nil { + return DeviceStats{}, err + } + defer cl.Close() + d, err := cl.Device(iface) + if err != nil { + return DeviceStats{}, err + } + var st DeviceStats + cutoff := time.Now().Add(-3 * time.Minute) + for _, p := range d.Peers { + st.RxBytes += p.ReceiveBytes + st.TxBytes += p.TransmitBytes + if !p.LastHandshakeTime.IsZero() && p.LastHandshakeTime.After(cutoff) { + st.Connected++ + } + } + return st, nil +} + +func (r *realController) PeerTransfers(iface string) ([]PeerTransfer, error) { + cl, err := wgctrl.New() + if err != nil { + return nil, err + } + defer cl.Close() + d, err := cl.Device(iface) + if err != nil { + return nil, err + } + out := make([]PeerTransfer, 0, len(d.Peers)) + for _, p := range d.Peers { + pt := PeerTransfer{ + WGPublicKey: p.PublicKey.String(), + RxBytes: p.ReceiveBytes, + TxBytes: p.TransmitBytes, + } + if !p.LastHandshakeTime.IsZero() { + pt.LastHandshake = p.LastHandshakeTime.Unix() + } + out = append(out, pt) + } + return out, nil +} diff --git a/internal/wg/template.go b/internal/wg/template.go new file mode 100644 index 0000000..6969ce7 --- /dev/null +++ b/internal/wg/template.go @@ -0,0 +1,99 @@ +package wg + +import ( + "bytes" + "strings" + "text/template" + + "github.com/NetSepio/erebrus/internal/store" +) + +// PrivateKeyPlaceholder is emitted in client configs in place of the client's +// private key. The node never sees the client private key; the client (which +// generated the keypair) substitutes its own key locally. +const PrivateKeyPlaceholder = "REPLACE_WITH_PRIVATE_KEY" + +var serverTpl = template.Must(template.New("server"). + Funcs(template.FuncMap{"join": strings.Join}). + Parse(`# Erebrus wg0 — generated, do not edit by hand +[Interface] +Address = {{ .Address }} +ListenPort = {{ .ListenPort }} +PrivateKey = {{ .PrivateKey }} +{{- if .MTU }} +MTU = {{ .MTU }} +{{- end }} +{{- if .PreUp }} +PreUp = {{ .PreUp }} +{{- end }} +{{- if .PostUp }} +PostUp = {{ .PostUp }} +{{- end }} +{{- if .PreDown }} +PreDown = {{ .PreDown }} +{{- end }} +{{- if .PostDown }} +PostDown = {{ .PostDown }} +{{- end }} +{{ range .Peers }}{{ if .Enabled }} +# {{ .Name }} / {{ .Wallet }} / id={{ .ID }} +[Peer] +PublicKey = {{ .WGPublicKey }} +{{- if .WGPresharedKey }} +PresharedKey = {{ .WGPresharedKey }} +{{- end }} +AllowedIPs = {{ .WGAllowedIP }} +{{ end }}{{ end }}`)) + +var clientTpl = template.Must(template.New("client").Parse(`[Interface] +Address = {{ .Address }} +PrivateKey = ` + PrivateKeyPlaceholder + ` +{{- if .DNS }} +DNS = {{ .DNS }} +{{- end }} + +[Peer] +PublicKey = {{ .ServerPublicKey }} +{{- if .PresharedKey }} +PresharedKey = {{ .PresharedKey }} +{{- end }} +AllowedIPs = 0.0.0.0/0, ::/0 +Endpoint = {{ .Endpoint }} +PersistentKeepalive = 16 +`)) + +type serverTplData struct { + Address string + ListenPort int + PrivateKey string + MTU int + PreUp string + PostUp string + PreDown string + PostDown string + Peers []*store.Peer +} + +type clientTplData struct { + Address string + DNS string + ServerPublicKey string + PresharedKey string + Endpoint string +} + +func renderServer(d serverTplData) ([]byte, error) { + var buf bytes.Buffer + if err := serverTpl.Execute(&buf, d); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + +func renderClient(d clientTplData) (string, error) { + var buf bytes.Buffer + if err := clientTpl.Execute(&buf, d); err != nil { + return "", err + } + return buf.String(), nil +} diff --git a/internal/wg/wg.go b/internal/wg/wg.go new file mode 100644 index 0000000..f2ea4c1 --- /dev/null +++ b/internal/wg/wg.go @@ -0,0 +1,190 @@ +// Package wg manages the node's WireGuard server: the server keypair (stored +// in SQLite node_settings), rendering the interface config and per-client +// configs, and applying peer changes live. It carries forward the v1 template +// and wgctrl logic but sources state from the store instead of JSON files. +package wg + +import ( + "context" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "sync" + + "github.com/NetSepio/erebrus/internal/config" + "github.com/NetSepio/erebrus/internal/store" + "golang.zx2c4.com/wireguard/wgctrl/wgtypes" +) + +const ( + settingServerPrivateKey = "wg_server_private_key" + settingServerPublicKey = "wg_server_public_key" +) + +// Manager owns the node's WireGuard state. +type Manager struct { + cfg *config.Config + st *store.Store + ctrl Controller + + mu sync.RWMutex + privateKey string + publicKey string +} + +// New constructs a Manager. Call Init before use. +func New(cfg *config.Config, st *store.Store, ctrl Controller) *Manager { + return &Manager{cfg: cfg, st: st, ctrl: ctrl} +} + +// Init loads or generates the server keypair, writes the interface config, and +// brings the interface up. A failure to bring the interface up (e.g. running +// without NET_ADMIN in local dev) is logged by the caller but not fatal — the +// conf file is still written for later activation. +func (m *Manager) Init(ctx context.Context) error { + if err := m.loadOrCreateKeys(ctx); err != nil { + return err + } + if err := os.MkdirAll(m.cfg.WGConfDir, 0o700); err != nil { + return err + } + if err := m.writeServerConf(ctx); err != nil { + return err + } + return m.ctrl.BringUp(m.cfg.WGInterface, m.confPath()) +} + +// ServerPublicKey returns the node's WireGuard public key. +func (m *Manager) ServerPublicKey() string { + m.mu.RLock() + defer m.mu.RUnlock() + return m.publicKey +} + +// Endpoint returns host:port clients should dial. +func (m *Manager) Endpoint() string { + return fmt.Sprintf("%s:%s", m.cfg.WGEndpointHost, m.cfg.WGEndpointPort) +} + +// Subnet returns the configured IPv4 subnet (server host CIDR). +func (m *Manager) Subnet() string { return m.cfg.WGIPv4Subnet } + +// Stats returns a live device snapshot (transfer counters, active peers). +// Returns a zero value when the interface is not up (e.g. dev without NET_ADMIN). +func (m *Manager) Stats() DeviceStats { + st, err := m.ctrl.Stats(m.cfg.WGInterface) + if err != nil { + return DeviceStats{} + } + return st +} + +// PeerTransfers returns per-peer transfer counters keyed by WG public key. +func (m *Manager) PeerTransfers() []PeerTransfer { + pt, err := m.ctrl.PeerTransfers(m.cfg.WGInterface) + if err != nil { + return nil + } + return pt +} + +// Apply re-renders the interface config from the current peer set and syncs the +// live peer list. Call after any peer add/update/remove. +func (m *Manager) Apply(ctx context.Context) error { + if err := m.writeServerConf(ctx); err != nil { + return err + } + peers, err := m.st.ListPeers(ctx) + if err != nil { + return err + } + return m.ctrl.SyncPeers(m.cfg.WGInterface, peers) +} + +// ClientConfig renders a wg-quick config for a peer, with the private key left +// as a placeholder for the client to fill in. +func (m *Manager) ClientConfig(p *store.Peer) (string, error) { + return renderClient(clientTplData{ + Address: p.WGAllowedIP, + DNS: m.cfg.WGDNS, + ServerPublicKey: m.ServerPublicKey(), + PresharedKey: p.WGPresharedKey, + Endpoint: m.Endpoint(), + }) +} + +func (m *Manager) loadOrCreateKeys(ctx context.Context) error { + priv, err := m.st.GetSetting(ctx, settingServerPrivateKey) + if err != nil { + return err + } + if priv == "" { + key, err := wgtypes.GeneratePrivateKey() + if err != nil { + return err + } + priv = key.String() + pub := key.PublicKey().String() + if err := m.st.SetSetting(ctx, settingServerPrivateKey, priv); err != nil { + return err + } + if err := m.st.SetSetting(ctx, settingServerPublicKey, pub); err != nil { + return err + } + } + pub, err := m.st.GetSetting(ctx, settingServerPublicKey) + if err != nil { + return err + } + m.mu.Lock() + m.privateKey = priv + m.publicKey = pub + m.mu.Unlock() + return nil +} + +func (m *Manager) writeServerConf(ctx context.Context) error { + peers, err := m.st.ListPeers(ctx) + if err != nil { + return err + } + m.mu.RLock() + priv := m.privateKey + m.mu.RUnlock() + + data, err := renderServer(serverTplData{ + Address: m.serverAddress(), + ListenPort: m.cfg.WGEndpointPortInt(), + PrivateKey: priv, + PreUp: m.cfg.WGPreUp, + PostUp: m.cfg.WGPostUp, + PreDown: m.cfg.WGPreDown, + PostDown: m.cfg.WGPostDown, + Peers: peers, + }) + if err != nil { + return err + } + return os.WriteFile(m.confPath(), data, 0o600) +} + +// serverAddress returns the server's own address inside the subnet as a CIDR, +// e.g. "10.0.0.1/16". +func (m *Manager) serverAddress() string { + ip, ipnet, err := net.ParseCIDR(m.cfg.WGIPv4Subnet) + if err != nil { + return m.cfg.WGIPv4Subnet + } + ones, _ := ipnet.Mask.Size() + return fmt.Sprintf("%s/%d", ip.String(), ones) +} + +func (m *Manager) confPath() string { + name := m.cfg.WGInterface + if !strings.HasSuffix(name, ".conf") { + name += ".conf" + } + return filepath.Join(m.cfg.WGConfDir, name) +} diff --git a/main.go b/main.go deleted file mode 100644 index f03f9f1..0000000 --- a/main.go +++ /dev/null @@ -1,199 +0,0 @@ -package main - -import ( - "fmt" - "net" - "os" - "path/filepath" - "sync" - "time" - - "github.com/NetSepio/erebrus/api" - "github.com/NetSepio/erebrus/core" - grpc "github.com/NetSepio/erebrus/gRPC" - "github.com/NetSepio/erebrus/p2p" - "github.com/NetSepio/erebrus/util" - "github.com/NetSepio/erebrus/util/pkg/auth" - "github.com/NetSepio/erebrus/util/pkg/node" - "github.com/gin-contrib/static" - - helmet "github.com/danielkov/gin-helmet" - "github.com/gin-contrib/cors" - "github.com/gin-gonic/gin" - "github.com/joho/godotenv" - "github.com/patrickmn/go-cache" - log "github.com/sirupsen/logrus" -) - -var wg sync.WaitGroup - -func init() { - log.SetFormatter(&log.JSONFormatter{}) - log.SetOutput(os.Stderr) - log.SetLevel(log.DebugLevel) - node.Init() - - // Get Hostname for updating Log StandardFields - HostName, err := os.Hostname() - if err != nil { - log.Infof("Error in getting the Hostname: %v", err) - } else { - util.StandardFields = log.Fields{ - "hostname": HostName, - "appname": "Erebrus", - } - } - // Check if loading environment variables from .env file is required - if os.Getenv("LOAD_CONFIG_FILE") == "" { - - // Load environment variables from .env file - err = godotenv.Load() - if err != nil { - log.WithFields(util.StandardFields).Fatalf("Error in reading the config file: %v", err) - - } - } - - core.GetIPInfo() - - auth.Init() - // agents.EnsureDockerAndCaddy() - -} - -func RungRPCServer() { - grpc_server := grpc.Initialize() - - port := os.Getenv("GRPC_PORT") - - log.WithFields(util.StandardFields).Info("Starting gRPC Api, Listening on Port :", port) - - listener, err := net.Listen("tcp", ":"+port) - if err != nil { - wg.Done() - log.Fatal("Unable to listen on port", port) - - } - - //Server GRPC - if err := grpc_server.Serve(listener); err != nil { - wg.Done() - log.Fatal("Faied to create GRPC server!") - - } - wg.Done() - -} - -func main() { - if len(os.Args) > 1 { - core.Execute() - return - } - - log.WithFields(util.StandardFields).Infof("Starting NetSepio - Erebrus Version: %s", util.Version) - - // check directories or create it - if !util.DirectoryExists(filepath.Join(os.Getenv("WG_CONF_DIR"))) { - err := os.Mkdir(os.Getenv("WG_CONF_DIR"), 0755) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - "dir": filepath.Join(os.Getenv("WG_CONF_DIR")), - }).Fatal("failed to create wireguard configuration directory") - } - } - - // check directories or create it - fmt.Println(os.Getenv("WG_CLIENTS_DIR")) - if !util.DirectoryExists(filepath.Join(os.Getenv("WG_CLIENTS_DIR"))) { - err := os.Mkdir(os.Getenv("WG_CLIENTS_DIR"), 0755) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - "dir": filepath.Join(os.Getenv("WG_CLIENTS_DIR")), - }).Fatal("failed to create wireguard clients directory") - } - } - - // check if server.json exists otherwise create it with default values - if !util.FileExists(filepath.Join(os.Getenv("WG_CONF_DIR"), "server.json")) { - _, err := core.ReadServer() - if err != nil { - log.WithFields(util.StandardFields).Fatal("server.json does not exist and unable to open") - } - } - - if os.Getenv("RUNTYPE") == "debug" { - // set gin release debug - gin.SetMode(gin.DebugMode) - } else { - // set gin release mode - gin.SetMode(gin.ReleaseMode) - // disable console color - gin.DisableConsoleColor() - // log level info - log.SetLevel(log.InfoLevel) - } - - // dump wg config file - err := core.UpdateServerConfigWg() - util.CheckError("Error while creating WireGuard config file: ", err) - // Call the function to generate the wallet address and store it in the global variable - - core.LoadNodeDetails() - - // Register node on Peaq or Monad if configured - if err := core.RegisterNodeOnChain(); err != nil { - log.WithFields(util.StandardFields).Errorf("Failed to register node on %s: %v", os.Getenv("CHAIN_NAME"), err) - } - - go p2p.Init() - //running updater - wg.Add(1) - - if os.Getenv("GRPC_PORT") != "" { - //Add gRPC routine to wait group - wg.Add(1) - //run gRPC server - go RungRPCServer() - } - - if os.Getenv("HTTP_PORT") != "" { - // creates a gin router with default middleware: logger and recovery (crash-free) middleware - ginApp := gin.Default() - // cors middleware - config := cors.DefaultConfig() - config.AllowOrigins = []string{os.Getenv("GATEWAY_DOMAIN")} - ginApp.Use(cors.New(config)) - - // protection middleware - ginApp.Use(helmet.Default()) - - // add cache storage to gin ginApp - ginApp.Use(func(ctx *gin.Context) { - ctx.Set("cache", cache.New(60*time.Minute, 10*time.Minute)) - ctx.Next() - }) - // serve static files - ginApp.Use(static.Serve("/", static.LocalFile("./webapp", false))) - //ginApp.Use(static.Serve("/docs", static.LocalFile("./docs", false))) - - /*opt := openapimiddleware.RedocOpts{SpecURL: "/docs/swagger.yml"} - handler := openapimiddleware.Redoc(opt, nil) - */ - //ginApp.Static("docs", "./docs") - // no route redirect to frontend app - ginApp.NoRoute(func(c *gin.Context) { - c.JSON(404, gin.H{"status": 404, "message": "Invalid Endpoint Request"}) - }) - - // Apply API Routes - api.ApplyRoutes(ginApp) - err = ginApp.Run(fmt.Sprintf("%s:%s", os.Getenv("SERVER"), os.Getenv("HTTP_PORT"))) - util.CheckError("Failed to Start HTTP Server: ", err) - } - //wait untill all servers are stopped - wg.Wait() - -} diff --git a/model/agents.go b/model/agents.go deleted file mode 100644 index 32e7a92..0000000 --- a/model/agents.go +++ /dev/null @@ -1,29 +0,0 @@ -package model - -type Agent struct { - ID string `json:"id"` - Name string `json:"name"` - Clients []string `json:"clients"` - Port int `json:"port"` - Domain string `json:"domain"` - Status string `json:"status"` - AvatarImg string `json:"avatar_img"` - CoverImg string `json:"cover_img"` - VoiceModel string `json:"voice_model"` - Organization string `json:"organization"` -} - -type AgentResponse struct { - ID string `json:"id"` - Name string `json:"name"` - Clients []string `json:"clients"` - Status string `json:"status"` - AvatarImg string `json:"avatar_img"` - CoverImg string `json:"cover_img"` - VoiceModel string `json:"voice_model"` - Organization string `json:"organization"` -} - -type CharacterFile struct { - Name string `json:"name"` -} diff --git a/model/client.go b/model/client.go deleted file mode 100644 index 016144f..0000000 --- a/model/client.go +++ /dev/null @@ -1,68 +0,0 @@ -package model - -import ( - "fmt" - - "github.com/NetSepio/erebrus/util" -) - -// Client structure -/*type Client struct { - UUID string `json:"uuid"` - Name string `json:"name"` - Tags []string `json:"tags"` - WalletAddress string `json:"walletAddress"` - Enable bool `json:"enable"` - IgnorePersistentKeepalive bool `json:"ignorePersistentKeepalive"` - PresharedKey string `json:"presharedKey"` - AllowedIPs []string `json:"allowedIPs"` - Address []string `json:"address"` - PrivateKey string `json:"privateKey"` - PublicKey string `json:"publicKey"` - CreatedBy string `json:"createdBy"` - UpdatedBy string `json:"updatedBy"` - Created int64 `json:"created"` - Updated int64 `json:"updated"` -}*/ - -// IsValid check if model is valid -func (a Client) IsValid() []error { - errs := make([]error, 0) - - // check if the name is empty - if a.Name == "" { - errs = append(errs, fmt.Errorf("name is required")) - } - // check the name field is between 3 to 40 chars - if len(a.Name) < 2 || len(a.Name) > 40 { - errs = append(errs, fmt.Errorf("name field must be between 2-40 chars")) - } - // email is not required, but if provided must match regex - if a.WalletAddress != "" { - if !util.RegexpWalletEth.MatchString(a.WalletAddress) { - errs = append(errs, fmt.Errorf("wallet address %s is invalid", a.WalletAddress)) - } - } - // check if the allowedIPs empty - if len(a.AllowedIPs) == 0 { - errs = append(errs, fmt.Errorf("allowedIPs field is required")) - } - // check if the allowedIPs are valid - for _, allowedIP := range a.AllowedIPs { - if !util.IsValidCidr(allowedIP) { - errs = append(errs, fmt.Errorf("allowedIP %s is invalid", allowedIP)) - } - } - // check if the address empty - if len(a.Address) == 0 { - errs = append(errs, fmt.Errorf("address field is required")) - } - // check if the address are valid - for _, address := range a.Address { - if !util.IsValidCidr(address) { - errs = append(errs, fmt.Errorf("address %s is invalid", address)) - } - } - - return errs -} diff --git a/model/model.pb.go b/model/model.pb.go deleted file mode 100644 index 4cbfaf3..0000000 --- a/model/model.pb.go +++ /dev/null @@ -1,797 +0,0 @@ -// Code generated by protoc-gen-go. DO NOT EDIT. -// versions: -// protoc-gen-go v1.31.0 -// protoc v4.25.1 -// source: model.proto - -package model - -import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - reflect "reflect" - sync "sync" -) - -const ( - // Verify that this generated code is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) - // Verify that runtime/protoimpl is sufficiently up-to-date. - _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) -) - -type Response struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Status int64 `protobuf:"varint,1,opt,name=status,proto3" json:"status,omitempty"` - Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"` - Message string `protobuf:"bytes,3,opt,name=message,proto3" json:"message,omitempty"` - Error string `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` - Client *Client `protobuf:"bytes,5,opt,name=client,proto3" json:"client,omitempty"` - Server *Server `protobuf:"bytes,6,opt,name=server,proto3" json:"server,omitempty"` - Clients []*Client `protobuf:"bytes,7,rep,name=clients,proto3" json:"clients,omitempty"` -} - -func (x *Response) Reset() { - *x = Response{} - if protoimpl.UnsafeEnabled { - mi := &file_model_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Response) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Response) ProtoMessage() {} - -func (x *Response) ProtoReflect() protoreflect.Message { - mi := &file_model_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Response.ProtoReflect.Descriptor instead. -func (*Response) Descriptor() ([]byte, []int) { - return file_model_proto_rawDescGZIP(), []int{0} -} - -func (x *Response) GetStatus() int64 { - if x != nil { - return x.Status - } - return 0 -} - -func (x *Response) GetSuccess() bool { - if x != nil { - return x.Success - } - return false -} - -func (x *Response) GetMessage() string { - if x != nil { - return x.Message - } - return "" -} - -func (x *Response) GetError() string { - if x != nil { - return x.Error - } - return "" -} - -func (x *Response) GetClient() *Client { - if x != nil { - return x.Client - } - return nil -} - -func (x *Response) GetServer() *Server { - if x != nil { - return x.Server - } - return nil -} - -func (x *Response) GetClients() []*Client { - if x != nil { - return x.Clients - } - return nil -} - -type Client struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - UUID string `protobuf:"bytes,1,opt,name=UUID,proto3" json:"UUID,omitempty"` - Name string `protobuf:"bytes,2,opt,name=Name,proto3" json:"Name,omitempty"` - Tags []string `protobuf:"bytes,3,rep,name=Tags,proto3" json:"Tags,omitempty"` - WalletAddress string `protobuf:"bytes,4,opt,name=WalletAddress,proto3" json:"WalletAddress,omitempty"` - Enable bool `protobuf:"varint,5,opt,name=Enable,proto3" json:"Enable,omitempty"` - IgnorePersistentKeepalive bool `protobuf:"varint,6,opt,name=IgnorePersistentKeepalive,proto3" json:"IgnorePersistentKeepalive,omitempty"` - PublicKey string `protobuf:"bytes,7,opt,name=PublicKey,proto3" json:"PublicKey,omitempty"` - PresharedKey string `protobuf:"bytes,8,opt,name=PresharedKey,proto3" json:"PresharedKey,omitempty"` - AllowedIPs []string `protobuf:"bytes,9,rep,name=AllowedIPs,proto3" json:"AllowedIPs,omitempty"` - Address []string `protobuf:"bytes,10,rep,name=Address,proto3" json:"Address,omitempty"` - CreatedBy string `protobuf:"bytes,11,opt,name=CreatedBy,proto3" json:"CreatedBy,omitempty"` - UpdatedBy string `protobuf:"bytes,12,opt,name=UpdatedBy,proto3" json:"UpdatedBy,omitempty"` - CreatedAt int64 `protobuf:"varint,13,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` - UpdatedAt int64 `protobuf:"varint,14,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` - ReceiveBytes int64 `protobuf:"varint,15,opt,name=ReceiveBytes,proto3" json:"ReceiveBytes"` - TransmitBytes int64 `protobuf:"varint,16,opt,name=TransmitBytes,proto3" json:"TransmitBytes"` -} - -func (x *Client) Reset() { - *x = Client{} - if protoimpl.UnsafeEnabled { - mi := &file_model_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Client) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Client) ProtoMessage() {} - -func (x *Client) ProtoReflect() protoreflect.Message { - mi := &file_model_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Client.ProtoReflect.Descriptor instead. -func (*Client) Descriptor() ([]byte, []int) { - return file_model_proto_rawDescGZIP(), []int{1} -} - -func (x *Client) GetUUID() string { - if x != nil { - return x.UUID - } - return "" -} - -func (x *Client) GetName() string { - if x != nil { - return x.Name - } - return "" -} - -func (x *Client) GetTags() []string { - if x != nil { - return x.Tags - } - return nil -} - -func (x *Client) GetWalletAddress() string { - if x != nil { - return x.WalletAddress - } - return "" -} - -func (x *Client) GetEnable() bool { - if x != nil { - return x.Enable - } - return false -} - -func (x *Client) GetIgnorePersistentKeepalive() bool { - if x != nil { - return x.IgnorePersistentKeepalive - } - return false -} - -func (x *Client) GetPublicKey() string { - if x != nil { - return x.PublicKey - } - return "" -} - -func (x *Client) GetPresharedKey() string { - if x != nil { - return x.PresharedKey - } - return "" -} - -func (x *Client) GetAllowedIPs() []string { - if x != nil { - return x.AllowedIPs - } - return nil -} - -func (x *Client) GetAddress() []string { - if x != nil { - return x.Address - } - return nil -} - -func (x *Client) GetCreatedBy() string { - if x != nil { - return x.CreatedBy - } - return "" -} - -func (x *Client) GetUpdatedBy() string { - if x != nil { - return x.UpdatedBy - } - return "" -} - -func (x *Client) GetCreatedAt() int64 { - if x != nil { - return x.CreatedAt - } - return 0 -} - -func (x *Client) GetUpdatedAt() int64 { - if x != nil { - return x.UpdatedAt - } - return 0 -} - -func (x *Client) GetReceiveBytes() int64 { - if x != nil { - return x.ReceiveBytes - } - return 0 -} - -func (x *Client) GetTransmitBytes() int64 { - if x != nil { - return x.TransmitBytes - } - return 0 -} - -type Server struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Address []string `protobuf:"bytes,1,rep,name=Address,proto3" json:"Address,omitempty"` - ListenPort int64 `protobuf:"varint,2,opt,name=ListenPort,proto3" json:"ListenPort,omitempty"` - Mtu int64 `protobuf:"varint,3,opt,name=Mtu,proto3" json:"Mtu,omitempty"` - PrivateKey string `protobuf:"bytes,4,opt,name=PrivateKey,proto3" json:"PrivateKey,omitempty"` - PublicKey string `protobuf:"bytes,5,opt,name=PublicKey,proto3" json:"PublicKey,omitempty"` - Endpoint string `protobuf:"bytes,6,opt,name=Endpoint,proto3" json:"Endpoint,omitempty"` - PersistentKeepalive int64 `protobuf:"varint,7,opt,name=PersistentKeepalive,proto3" json:"PersistentKeepalive,omitempty"` - DNS []string `protobuf:"bytes,8,rep,name=DNS,proto3" json:"DNS,omitempty"` - AllowedIPs []string `protobuf:"bytes,9,rep,name=AllowedIPs,proto3" json:"AllowedIPs,omitempty"` - PreUp string `protobuf:"bytes,10,opt,name=PreUp,proto3" json:"PreUp,omitempty"` - PostUp string `protobuf:"bytes,11,opt,name=PostUp,proto3" json:"PostUp,omitempty"` - PreDown string `protobuf:"bytes,12,opt,name=PreDown,proto3" json:"PreDown,omitempty"` - PostDown string `protobuf:"bytes,13,opt,name=PostDown,proto3" json:"PostDown,omitempty"` - UpdatedBy string `protobuf:"bytes,14,opt,name=UpdatedBy,proto3" json:"UpdatedBy,omitempty"` - CreatedAt int64 `protobuf:"varint,15,opt,name=CreatedAt,proto3" json:"CreatedAt,omitempty"` - UpdatedAt int64 `protobuf:"varint,16,opt,name=UpdatedAt,proto3" json:"UpdatedAt,omitempty"` -} - -func (x *Server) Reset() { - *x = Server{} - if protoimpl.UnsafeEnabled { - mi := &file_model_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Server) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Server) ProtoMessage() {} - -func (x *Server) ProtoReflect() protoreflect.Message { - mi := &file_model_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Server.ProtoReflect.Descriptor instead. -func (*Server) Descriptor() ([]byte, []int) { - return file_model_proto_rawDescGZIP(), []int{2} -} - -func (x *Server) GetAddress() []string { - if x != nil { - return x.Address - } - return nil -} - -func (x *Server) GetListenPort() int64 { - if x != nil { - return x.ListenPort - } - return 0 -} - -func (x *Server) GetMtu() int64 { - if x != nil { - return x.Mtu - } - return 0 -} - -func (x *Server) GetPrivateKey() string { - if x != nil { - return x.PrivateKey - } - return "" -} - -func (x *Server) GetPublicKey() string { - if x != nil { - return x.PublicKey - } - return "" -} - -func (x *Server) GetEndpoint() string { - if x != nil { - return x.Endpoint - } - return "" -} - -func (x *Server) GetPersistentKeepalive() int64 { - if x != nil { - return x.PersistentKeepalive - } - return 0 -} - -func (x *Server) GetDNS() []string { - if x != nil { - return x.DNS - } - return nil -} - -func (x *Server) GetAllowedIPs() []string { - if x != nil { - return x.AllowedIPs - } - return nil -} - -func (x *Server) GetPreUp() string { - if x != nil { - return x.PreUp - } - return "" -} - -func (x *Server) GetPostUp() string { - if x != nil { - return x.PostUp - } - return "" -} - -func (x *Server) GetPreDown() string { - if x != nil { - return x.PreDown - } - return "" -} - -func (x *Server) GetPostDown() string { - if x != nil { - return x.PostDown - } - return "" -} - -func (x *Server) GetUpdatedBy() string { - if x != nil { - return x.UpdatedBy - } - return "" -} - -func (x *Server) GetCreatedAt() int64 { - if x != nil { - return x.CreatedAt - } - return 0 -} - -func (x *Server) GetUpdatedAt() int64 { - if x != nil { - return x.UpdatedAt - } - return 0 -} - -type Status struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - - Version string `protobuf:"bytes,1,opt,name=Version,proto3" json:"Version,omitempty"` - Hostname string `protobuf:"bytes,2,opt,name=Hostname,proto3" json:"Hostname,omitempty"` - Domain string `protobuf:"bytes,3,opt,name=Domain,proto3" json:"Domain,omitempty"` - PublicIP string `protobuf:"bytes,4,opt,name=PublicIP,proto3" json:"PublicIP,omitempty"` - GRPCPort string `protobuf:"bytes,5,opt,name=gRPCPort,proto3" json:"gRPCPort,omitempty"` - PrivateIP string `protobuf:"bytes,6,opt,name=PrivateIP,proto3" json:"PrivateIP,omitempty"` - HttpPort string `protobuf:"bytes,7,opt,name=HttpPort,proto3" json:"HttpPort,omitempty"` - Region string `protobuf:"bytes,8,opt,name=Region,proto3" json:"Region,omitempty"` - VPNPort string `protobuf:"bytes,9,opt,name=VPNPort,proto3" json:"VPNPort,omitempty"` - PublicKey string `protobuf:"bytes,10,opt,name=PublicKey,proto3" json:"PublicKey,omitempty"` - PersistentKeepalive int64 `protobuf:"varint,11,opt,name=PersistentKeepalive,proto3" json:"PersistentKeepalive,omitempty"` - DNS []string `protobuf:"bytes,12,rep,name=DNS,proto3" json:"DNS,omitempty"` -} - -func (x *Status) Reset() { - *x = Status{} - if protoimpl.UnsafeEnabled { - mi := &file_model_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } -} - -func (x *Status) String() string { - return protoimpl.X.MessageStringOf(x) -} - -func (*Status) ProtoMessage() {} - -func (x *Status) ProtoReflect() protoreflect.Message { - mi := &file_model_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - if ms.LoadMessageInfo() == nil { - ms.StoreMessageInfo(mi) - } - return ms - } - return mi.MessageOf(x) -} - -// Deprecated: Use Status.ProtoReflect.Descriptor instead. -func (*Status) Descriptor() ([]byte, []int) { - return file_model_proto_rawDescGZIP(), []int{3} -} - -func (x *Status) GetVersion() string { - if x != nil { - return x.Version - } - return "" -} - -func (x *Status) GetHostname() string { - if x != nil { - return x.Hostname - } - return "" -} - -func (x *Status) GetDomain() string { - if x != nil { - return x.Domain - } - return "" -} - -func (x *Status) GetPublicIP() string { - if x != nil { - return x.PublicIP - } - return "" -} - -func (x *Status) GetGRPCPort() string { - if x != nil { - return x.GRPCPort - } - return "" -} - -func (x *Status) GetPrivateIP() string { - if x != nil { - return x.PrivateIP - } - return "" -} - -func (x *Status) GetHttpPort() string { - if x != nil { - return x.HttpPort - } - return "" -} - -func (x *Status) GetRegion() string { - if x != nil { - return x.Region - } - return "" -} - -func (x *Status) GetVPNPort() string { - if x != nil { - return x.VPNPort - } - return "" -} - -func (x *Status) GetPublicKey() string { - if x != nil { - return x.PublicKey - } - return "" -} - -func (x *Status) GetPersistentKeepalive() int64 { - if x != nil { - return x.PersistentKeepalive - } - return 0 -} - -func (x *Status) GetDNS() []string { - if x != nil { - return x.DNS - } - return nil -} - -var File_model_proto protoreflect.FileDescriptor - -var file_model_proto_rawDesc = []byte{ - 0x0a, 0x0b, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x05, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x22, 0xe3, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x73, 0x75, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x14, 0x0a, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x65, 0x72, - 0x72, 0x6f, 0x72, 0x12, 0x25, 0x0a, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6c, 0x69, 0x65, - 0x6e, 0x74, 0x52, 0x06, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x25, 0x0a, 0x06, 0x73, 0x65, - 0x72, 0x76, 0x65, 0x72, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6d, 0x6f, 0x64, - 0x65, 0x6c, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x12, 0x27, 0x0a, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x07, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x43, 0x6c, 0x69, 0x65, 0x6e, - 0x74, 0x52, 0x07, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x73, 0x22, 0xfe, 0x03, 0x0a, 0x06, 0x43, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x55, 0x55, 0x49, 0x44, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x55, 0x55, 0x49, 0x44, 0x12, 0x12, 0x0a, 0x04, 0x4e, 0x61, 0x6d, - 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x54, 0x61, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x54, 0x61, 0x67, - 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x57, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x45, 0x6e, 0x61, 0x62, 0x6c, - 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, - 0x3c, 0x0a, 0x19, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, - 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x65, 0x70, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x19, 0x49, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, - 0x74, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x65, 0x70, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x1c, 0x0a, - 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x0c, 0x50, - 0x72, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0c, 0x50, 0x72, 0x65, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4b, 0x65, 0x79, 0x12, - 0x1e, 0x0a, 0x0a, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x50, 0x73, 0x18, 0x09, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x0a, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x50, 0x73, 0x12, - 0x18, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x72, 0x65, - 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x43, 0x72, - 0x65, 0x61, 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, - 0x65, 0x64, 0x42, 0x79, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, - 0x74, 0x65, 0x64, 0x42, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x64, 0x41, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, - 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x42, 0x79, 0x74, 0x65, - 0x73, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0c, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, - 0x42, 0x79, 0x74, 0x65, 0x73, 0x12, 0x24, 0x0a, 0x0d, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x6d, 0x69, - 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x18, 0x10, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0d, 0x54, 0x72, - 0x61, 0x6e, 0x73, 0x6d, 0x69, 0x74, 0x42, 0x79, 0x74, 0x65, 0x73, 0x22, 0xd0, 0x03, 0x0a, 0x06, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x1e, 0x0a, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x4c, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x72, 0x74, - 0x12, 0x10, 0x0a, 0x03, 0x4d, 0x74, 0x75, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x03, 0x4d, - 0x74, 0x75, 0x12, 0x1e, 0x0a, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, 0x65, 0x79, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4b, - 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, - 0x12, 0x1a, 0x0a, 0x08, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x08, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x30, 0x0a, 0x13, - 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x65, 0x70, 0x61, 0x6c, - 0x69, 0x76, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x50, 0x65, 0x72, 0x73, 0x69, - 0x73, 0x74, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x65, 0x70, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x10, - 0x0a, 0x03, 0x44, 0x4e, 0x53, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x44, 0x4e, 0x53, - 0x12, 0x1e, 0x0a, 0x0a, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x50, 0x73, 0x18, 0x09, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x49, 0x50, 0x73, - 0x12, 0x14, 0x0a, 0x05, 0x50, 0x72, 0x65, 0x55, 0x70, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x50, 0x72, 0x65, 0x55, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x50, 0x6f, 0x73, 0x74, 0x55, 0x70, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x50, 0x6f, 0x73, 0x74, 0x55, 0x70, 0x12, 0x18, - 0x0a, 0x07, 0x50, 0x72, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x50, 0x72, 0x65, 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x6f, 0x73, 0x74, - 0x44, 0x6f, 0x77, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x6f, 0x73, 0x74, - 0x44, 0x6f, 0x77, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x42, - 0x79, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, - 0x42, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, - 0x12, 0x1c, 0x0a, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x03, 0x52, 0x09, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x22, 0xdc, - 0x02, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x56, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x48, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, - 0x16, 0x0a, 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x06, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x49, 0x50, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x50, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x49, 0x50, 0x12, 0x1a, 0x0a, 0x08, 0x67, 0x52, 0x50, 0x43, 0x50, 0x6f, 0x72, 0x74, 0x18, - 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x67, 0x52, 0x50, 0x43, 0x50, 0x6f, 0x72, 0x74, 0x12, - 0x1c, 0x0a, 0x09, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x50, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x09, 0x50, 0x72, 0x69, 0x76, 0x61, 0x74, 0x65, 0x49, 0x50, 0x12, 0x1a, 0x0a, - 0x08, 0x48, 0x74, 0x74, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x08, 0x48, 0x74, 0x74, 0x70, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x52, 0x65, 0x67, - 0x69, 0x6f, 0x6e, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x52, 0x65, 0x67, 0x69, 0x6f, - 0x6e, 0x12, 0x18, 0x0a, 0x07, 0x56, 0x50, 0x4e, 0x50, 0x6f, 0x72, 0x74, 0x18, 0x09, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x56, 0x50, 0x4e, 0x50, 0x6f, 0x72, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x50, - 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x13, 0x50, 0x65, 0x72, - 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, 0x74, 0x4b, 0x65, 0x65, 0x70, 0x61, 0x6c, 0x69, 0x76, 0x65, - 0x18, 0x0b, 0x20, 0x01, 0x28, 0x03, 0x52, 0x13, 0x50, 0x65, 0x72, 0x73, 0x69, 0x73, 0x74, 0x65, - 0x6e, 0x74, 0x4b, 0x65, 0x65, 0x70, 0x61, 0x6c, 0x69, 0x76, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x44, - 0x4e, 0x53, 0x18, 0x0c, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x44, 0x4e, 0x53, 0x42, 0x21, 0x5a, - 0x1f, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x4e, 0x65, 0x74, 0x53, - 0x65, 0x70, 0x69, 0x6f, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x3b, 0x6d, 0x6f, 0x64, 0x65, 0x6c, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} - -var ( - file_model_proto_rawDescOnce sync.Once - file_model_proto_rawDescData = file_model_proto_rawDesc -) - -func file_model_proto_rawDescGZIP() []byte { - file_model_proto_rawDescOnce.Do(func() { - file_model_proto_rawDescData = protoimpl.X.CompressGZIP(file_model_proto_rawDescData) - }) - return file_model_proto_rawDescData -} - -var file_model_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_model_proto_goTypes = []interface{}{ - (*Response)(nil), // 0: model.Response - (*Client)(nil), // 1: model.Client - (*Server)(nil), // 2: model.Server - (*Status)(nil), // 3: model.Status -} -var file_model_proto_depIdxs = []int32{ - 1, // 0: model.Response.client:type_name -> model.Client - 2, // 1: model.Response.server:type_name -> model.Server - 1, // 2: model.Response.clients:type_name -> model.Client - 3, // [3:3] is the sub-list for method output_type - 3, // [3:3] is the sub-list for method input_type - 3, // [3:3] is the sub-list for extension type_name - 3, // [3:3] is the sub-list for extension extendee - 0, // [0:3] is the sub-list for field type_name -} - -func init() { file_model_proto_init() } -func file_model_proto_init() { - if File_model_proto != nil { - return - } - if !protoimpl.UnsafeEnabled { - file_model_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Response); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_model_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Client); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_model_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Server); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_model_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Status); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } - type x struct{} - out := protoimpl.TypeBuilder{ - File: protoimpl.DescBuilder{ - GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_model_proto_rawDesc, - NumEnums: 0, - NumMessages: 4, - NumExtensions: 0, - NumServices: 0, - }, - GoTypes: file_model_proto_goTypes, - DependencyIndexes: file_model_proto_depIdxs, - MessageInfos: file_model_proto_msgTypes, - }.Build() - File_model_proto = out.File - file_model_proto_rawDesc = nil - file_model_proto_goTypes = nil - file_model_proto_depIdxs = nil -} diff --git a/model/model.proto b/model/model.proto deleted file mode 100644 index cb20d37..0000000 --- a/model/model.proto +++ /dev/null @@ -1,67 +0,0 @@ -syntax="proto3"; - -package model; -option go_package = "github.com/NetSepio/model;model"; - -message Response{ - int64 status=1; - bool success=2; - string message=3; - string error=4; - Client client=5; - Server server=6; - repeated Client clients=7; -} - -message Client{ - string UUID=1; - string Name=2; - repeated string Tags=3; - string WalletAddress=4; - bool Enable=5; - bool IgnorePersistentKeepalive=6; - string PublicKey=7; - string PresharedKey=8; - repeated string AllowedIPs=9; - repeated string Address=10; - string CreatedBy=11; - string UpdatedBy=12; - int64 CreatedAt=13; - int64 UpdatedAt=14; - int64 ReceiveBytes=15; - int64 TransmitBytes=16; -} - -message Server{ - repeated string Address=1; - int64 ListenPort=2; - int64 Mtu=3; - string PrivateKey=4; - string PublicKey=5; - string Endpoint=6; - int64 PersistentKeepalive=7; - repeated string DNS=8; - repeated string AllowedIPs=9; - string PreUp=10; - string PostUp=11; - string PreDown=12; - string PostDown=13; - string UpdatedBy=14; - int64 CreatedAt=15; - int64 UpdatedAt=16; -} - -message Status{ - string Version=1; - string Hostname=2; - string Domain=3; - string PublicIP=4; - string gRPCPort=5; - string PrivateIP=6; - string HttpPort=7; - string Region=8; - string VPNPort=9; - string PublicKey=10; - int64 PersistentKeepalive=11; - repeated string DNS=12; -} \ No newline at end of file diff --git a/model/server.go b/model/server.go deleted file mode 100644 index 3a8646a..0000000 --- a/model/server.go +++ /dev/null @@ -1,149 +0,0 @@ -package model - -import ( - "fmt" - - "github.com/NetSepio/erebrus/util" - - "golang.zx2c4.com/wireguard/wgctrl" - // "golang.zx2c4.com/wireguard/wgctrl/wgtypes" -) - -// Server structure -/*type Server struct { - Address []string `json:"address"` - ListenPort int64 `json:"listenPort"` - Mtu int64 `json:"mtu"` - PrivateKey string `json:"privateKey"` - PublicKey string `json:"publicKey"` - Endpoint string `json:"endpoint"` - PersistentKeepalive int64 `json:"persistentKeepalive"` - DNS []string `json:"dns"` - AllowedIPs []string `json:"allowedips"` - PreUp string `json:"preUp"` - PostUp string `json:"postUp"` - PreDown string `json:"preDown"` - PostDown string `json:"postDown"` - UpdatedBy string `json:"updatedBy"` - Created time.Time `json:"created"` - Updated time.Time `json:"updated"` -}*/ - -/*type Status struct { - Version string `json:"version"` - HostName string`json:"hostname"` - Domain string `json:"domain"` - PublicIP string `json:"publicIP"` - gRPCPort string`json:"grpcport"` - PrivateIP string`json:"private"` - HttpPort string`json:"httpport"` - Region string - VPNPort string -}*/ - -// WireGuardServer supports both Kernel and Userland implementations of WireGuard. -type WireGuardServer struct { - wg *wgctrl.Client - deviceName string -} - -// NewServer initializes a Server with a WireGuard client. -func NewServer(wg *wgctrl.Client, deviceName string) (*WireGuardServer, error) { - return &WireGuardServer{wg: wg, deviceName: deviceName}, nil -} - -// ListPeers retrieves information about all Peers known to the current -// WireGuard interface, including allowed IP addresses and usage stats, -// optionally with pagination. -// func (wgServer *WireGuardServer) ListPeers(ctx context.Context, req *Client) (*Client, error) { -// if err := validateListPeersRequest(req); err != nil { -// return nil, err -// } - -// dev, err := wgServer.wg.Device(s.deviceName) -// if err != nil { -// return nil, fmt.Errorf("could not get WireGuard device: %w", err) -// } - -// var peers []*client.Peer - -// for _, peer := range dev.Peers { -// peers = append(peers, peer2rpc(peer)) -// } - -// //TODO(jc): pagination - -// return &Client{ -// Peers: peers, -// }, nil -// } - -// func peer2rpc(peer wgtypes.Peer) *client.Peer { -// var keepAlive string -// if peer.PersistentKeepaliveInterval > 0 { -// keepAlive = peer.PersistentKeepaliveInterval.String() -// } - -// var allowedIPs []string -// for _, allowedIP := range peer.AllowedIPs { -// allowedIPs = append(allowedIPs, allowedIP.String()) -// } - -// return &client.Peer{ -// PublicKey: peer.PublicKey.String(), -// HasPresharedKey: peer.PresharedKey != wgtypes.Key{}, -// Endpoint: peer.Endpoint.String(), -// PersistentKeepAlive: keepAlive, -// LastHandshake: peer.LastHandshakeTime, -// ReceiveBytes: peer.ReceiveBytes, -// TransmitBytes: peer.TransmitBytes, -// AllowedIPs: allowedIPs, -// ProtocolVersion: peer.ProtocolVersion, -// } -// } - -// IsValid check if model is valid -func (a Server) IsValid() []error { - errs := make([]error, 0) - - // check if the address empty - if len(a.Address) == 0 { - errs = append(errs, fmt.Errorf("address is required")) - } - // check if the address are valid - for _, address := range a.Address { - if !util.IsValidCidr(address) { - errs = append(errs, fmt.Errorf("address %s is invalid", address)) - } - } - // check if the listenPort is valid - if a.ListenPort < 0 || a.ListenPort > 65535 { - errs = append(errs, fmt.Errorf("listenPort %d is invalid", a.ListenPort)) - } - // check if the endpoint empty - if a.Endpoint == "" { - errs = append(errs, fmt.Errorf("endpoint is required")) - } - // check if the persistentKeepalive is valid - if a.PersistentKeepalive < 0 { - errs = append(errs, fmt.Errorf("persistentKeepalive %d is invalid", a.PersistentKeepalive)) - } - // check if the mtu is valid - if a.Mtu < 0 { - errs = append(errs, fmt.Errorf("MTU %d is invalid", a.PersistentKeepalive)) - } - // check if the address are valid - for _, dns := range a.DNS { - if !util.IsValidIP(dns) { - errs = append(errs, fmt.Errorf("dns %s is invalid", dns)) - } - } - // check if the allowedIPs are valid - for _, allowedIP := range a.AllowedIPs { - if !util.IsValidCidr(allowedIP) { - errs = append(errs, fmt.Errorf("allowedIP %s is invalid", allowedIP)) - } - } - - return errs -} diff --git a/model/tunnel.go b/model/tunnel.go deleted file mode 100644 index 28bd39e..0000000 --- a/model/tunnel.go +++ /dev/null @@ -1,17 +0,0 @@ -package model - -// struct name service -type Service struct { // name app - Name string `json:"name"` - Type string `json:"type"` - IpAddress string `json:"ipAddress,omitempty"` - Port string `json:"port"` - Domain string `json:"domain"` - Status string `json:"status,omitempty"` - CreatedAt string `json:"createdAt"` -} - -// type name services -type ServicesList struct { - Services []Service `json:"services"` -} diff --git a/model/update.model.go b/model/update.model.go deleted file mode 100644 index 2188f2e..0000000 --- a/model/update.model.go +++ /dev/null @@ -1,8 +0,0 @@ -package model - -type RegionEndpoint struct { - Name string `json:"name"` - Code string `json:"code"` - ServiceType string `json:"service_type"` - Endpoint string `json:"endpoint"` -} diff --git a/old-install-node.sh b/old-install-node.sh deleted file mode 100644 index f3809da..0000000 --- a/old-install-node.sh +++ /dev/null @@ -1,1631 +0,0 @@ -#!/usr/bin/env bash -# Initialize logging -init_logging() { - LOG_DIR="/tmp" - LOG_FILE="${LOG_DIR}/erebrus_install.log" - - # Clear previous log and start fresh - > "$LOG_FILE" - log_info "=== Erebrus Node Installation Started ===" - log_info "Installation directory: $INSTALL_DIR" - log_info "Installation mode: ${INSTALLATION_MODE:-binary}" - log_info "Timestamp: $(date)" -} - -# Centralized logging functions -log_info() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [INFO] $message" >> "$LOG_FILE" -} - -log_error() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR] $message" >> "$LOG_FILE" -} - -log_success() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS] $message" >> "$LOG_FILE" -} - -log_warning() { - local message="$1" - echo "[$(date '+%Y-%m-%d %H:%M:%S')] [WARNING] $message" >> "$LOG_FILE" -} - -format_status() { - local status="$1" - case "$status" in - "✔ Complete") echo "[\033[32m$status\033[0m]" ;; - "✘ Skipped") echo "[\033[33m$status\033[0m]" ;; - "✘ Failed") echo "[\033[31m$status\033[0m]" ;; - "In Progress")echo "[\033[34m$status\033[0m]" ;; - "Pending") echo "[$status]" ;; - *) echo "[$status]" ;; - esac -} - -display_header() { - # clear everything including scrollback buffer - printf '\033[2J\033[3J\033[H' - local header_buffer="" - - # Add the logo to buffer - header_buffer+="$(tput clear)$(tput civis)" - header_buffer+="\e[94m" - header_buffer+=$(cat << "EOF" -/$$$$$$$$ /$$ -| $$_____/ | $$ -| $$ /$$$$$$ /$$$$$$ | $$$$$$$ /$$$$$$ /$$ /$$ /$$$$$$$ -| $$$$$ /$$__ $$ /$$__ $$| $$__ $$ /$$__ $$| $$ | $$ /$$_____/ -| $$__/ | $$ \__/| $$$$$$$$| $$ \ $$| $$ \__/| $$ | $$| $$$$$$ -| $$ | $$ | $$_____/| $$ | $$| $$ | $$ | $$ \____ $$ -| $$$$$$$$| $$ | $$$$$$$| $$$$$$$/| $$ | $$$$$$/ /$$$$$$$/ -|________/|__/ \_______/|_______/ |__/ \______/ |_______/ -EOF -) - header_buffer+="\e[0m\n\n" - header_buffer+="\033[1m\033[4mErebrus Node Software Installer v1.1\033[0m\n" - printf '─%.0s' {1..80} - - # Add separator and requirements - header_buffer+=$(printf '─%.0s' {1..100}) - header_buffer+="\n\e[1mRequirements:\e[0m\n" - header_buffer+="→ Erebrus node needs static public IP that is routable from internet & controlled by you.\n" - header_buffer+="→ Ports 9080, 9002, 9003, 51820, and 8088 must be open on your firewall and/or host system.\n" - header_buffer+=$(printf '─%.0s' {1..100}) - header_buffer+="\n" - - # Add status lines - header_buffer+="\033[1m🔧 Configure Node: \033[0m$(format_status "${STAGE_STATUS[0]}")\n" - header_buffer+="\033[1m📦 Install Packages: \033[0m$(format_status "${STAGE_STATUS[1]}")\n" - header_buffer+="\033[1m🚀 Run Node: \033[0m$(format_status "${STAGE_STATUS[2]}")\n" - - # Add final separator - header_buffer+=$(printf '─%.0s' {1..100}) - header_buffer+="\n" - - # Print the entire buffer at once - echo -e "$header_buffer" - - # Save cursor position after printing everything - tput sc - log_info "Header displayed successfully" -} - -# Function to clear all subprocess output -function clear_subprocess_output() { - # Go to saved position after header (status lines) - tput rc - - # Move down 4 lines (3 status lines + 1 separator line) - # tput cud - - # Clear everything from current position to end of screen - tput ed - log_info "Subprocess output cleared" -} - -# Function to show spinner -show_spinner() { - local pid=$1 - local msg=$2 - local delay=0.2 - local spinstr='|/-\' - - log_info "Starting subprocess: $msg" - - # Disable keyboard input echoing and save terminal settings - stty -echo - local old_tty_settings=$(stty -g) - - # Print the initial message with brackets and spinner placeholder - printf "\n%s [ ]" "$msg" - printf "\b\b" # Move cursor back inside the brackets - - # Start the spinner - while kill -0 $pid 2>/dev/null; do - local temp=${spinstr#?} - printf "%c\b" "$spinstr" # Print spinner char and move back - local spinstr=$temp${spinstr%"$temp"} - sleep $delay - - # Clear any input to prevent line breaks - read -t 0.1 -n 10000 discard 2>/dev/null || true - done - - # Get the exit status of the process - wait $pid - local exit_status=$? - - # Update with Done/Failed in brackets and add newline - if [ $exit_status -eq 0 ]; then - printf "\033[32mSuccess\033[0m]\n" - log_success "Subprocess completed: $msg" - else - printf "\033[31mFailed\033[0m]\n" - log_error "Subprocess failed: $msg (exit code: $exit_status)" - fi - - # Restore terminal settings - stty "$old_tty_settings" - stty echo - - return $exit_status -} - -# Function to check and create installation directory -create_install_directory() { - local base_dir="$1" - - if [[ -z "$base_dir" ]]; then - log_error "create_install_directory: base_dir parameter is required" - return 1 - fi - - # Set installation directory to base_dir/erebrus - INSTALL_DIR="${base_dir}/erebrus" - log_info "Setting installation directory to: $INSTALL_DIR" - - # Check if directory already exists - if [ -d "$INSTALL_DIR" ]; then - log_info "Installation directory already exists: $INSTALL_DIR" - return 0 - fi - - log_info "Creating installation directory: $INSTALL_DIR" - - # Try to create without sudo first - if mkdir -p "$INSTALL_DIR/wireguard" 2>/dev/null && chown -R $(id -u -n):$(id -g -n) "$INSTALL_DIR" 2>/dev/null; then - log_success "Installation directory created successfully: $INSTALL_DIR" - return 0 - else - # Try with sudo - printf "Creating directory '%s' requires elevated permissions.\n" "$INSTALL_DIR" - if sudo mkdir -p "$INSTALL_DIR/wireguard" && sudo chown -R $(whoami):$(whoami) "$INSTALL_DIR"; then - printf "Directory '%s' created successfully.\n" "$INSTALL_DIR" - log_success "Installation directory created with sudo: $INSTALL_DIR" - return 0 - else - printf "Error: Failed to create directory '%s'.\n" "$INSTALL_DIR" - log_error "Failed to create installation directory: $INSTALL_DIR" - return 1 - fi - fi -} - -# Function to get the public IP address -get_public_ip() { - log_info "Attempting to get public IP address" - local ip=$(curl -s ifconfig.io 2>>"$LOG_FILE") - if [[ -n "$ip" ]]; then - log_success "Public IP detected: $ip" - echo "$ip" - else - log_error "Failed to detect public IP address" - echo "" - fi -} - -# Function to get region -get_region() { - log_info "Attempting to get region" - local region=$(curl -s ifconfig.io/country_code 2>>"$LOG_FILE") - if [[ -n "$region" ]]; then - log_success "Region detected: $region" - echo "$region" - else - log_error "Failed to detect region" - echo "US" - fi -} - -# Function to check if Docker is installed -is_docker_installed() { - log_info "Checking if Docker is installed" - if command -v docker > /dev/null && command -v docker-compose > /dev/null; then - log_success "Docker is already installed" - return 0 - else - log_info "Docker is not installed" - return 1 - fi -} - -# Function to test if the IP is directly reachable from the internet -test_ip_reachability() { - local host_ip=$1 - local port=9080 - local max_retries=2 - local retry=0 - local user_retry_choice="" - local listener_pid="" - - log_info "Testing IP reachability for $host_ip:$port" - - # This function does the actual test and returns success/failure. It doesn't print any messages - do_ip_test() { - local host_ip=$1 - local port=$2 - local listener_pid="" - - # Check if port is already in use - if sudo lsof -i :$port > /dev/null 2>&1; then - log_warning "Port $port is already in use, skipping reachability test" - return 0 # Consider this a success to continue installation - fi - - # Start a netcat listener in the background - nc -l $port > /dev/null 2>&1 & - listener_pid=$! - - # Verify the listener started successfully - sleep 1 - if ! kill -0 "$listener_pid" 2>/dev/null; then - log_error "Failed to start netcat listener on port $port" - return 1 - fi - - sleep 1 # Give the listener more time to bind to the port - - # Try to connect to the listener using netcat - if echo "test" | nc -w 3 $host_ip $port > /dev/null 2>&1; then - # Kill the listener if it's still running - if [ -n "$listener_pid" ] && kill -0 "$listener_pid" 2>/dev/null; then - kill "$listener_pid" > /dev/null 2>&1 - fi - log_success "IP reachability test passed for $host_ip:$port" - return 0 - else - # Kill the listener if it's still running - if [ -n "$listener_pid" ] && kill -0 "$listener_pid" 2>/dev/null; then - kill "$listener_pid" > /dev/null 2>&1 - fi - log_error "IP reachability test failed for $host_ip:$port" - return 1 - fi - } - - while [ $retry -le $max_retries ]; do - # Run the test with spinner - do_ip_test "$host_ip" "$port" & - show_spinner $! "→ Verifying IP reachability" - local test_result=$? - - if [ $test_result -eq 0 ]; then - return 0 # Success - else - # If we have retries left, ask the user if they want to retry - if [ $retry -lt $max_retries ]; then - printf "\nThe IP address %s is not reachable from internet. IP reachability test failed.\n" "$host_ip" - printf "Make sure port 9002 and 9080 are open on your firewall and/or host system and try again.\n" - - read -p "Would you like to retry? (y/n): " user_retry_choice - if [ "$user_retry_choice" != "y" ]; then - log_error "User chose not to retry IP reachability test" - return 1 - fi - else - printf "\nYou do not have a public IP that is routable and reachable from internet.\n" - log_error "IP reachability test failed after $max_retries attempts" - return 1 - fi - fi - - ((retry++)) - log_warning "IP reachability test failed for $host_ip:$port - retry $retry" - done - - log_error "IP reachability test failed completely" - return 1 -} - -# Docker check_node_status() has been deprecated. See bottom of script if needed. -check_node_status() { - log_info "Checking node status" - local container_running=0 - local port_9080_listening=0 - local port_9002_listening=0 - local port_8088_listening=0 - local service_responding=0 - - # Check if container 'erebrus' is running (more precise check) - if [ "$INSTALLATION_MODE" = "container" ]; then - if sudo docker ps --format "table {{.Names}}" | grep -q "^erebrus$"; then - container_running=1 - log_info "Erebrus container is running" - else - log_info "Erebrus container is not running" - fi - fi - - # Check specific ports more efficiently - if sudo lsof -i :9080 -sTCP:LISTEN >/dev/null 2>&1; then - port_9080_listening=1 - log_info "Port 9080 is listening" - else - log_info "Port 9080 is not listening" - fi - - if sudo lsof -i :9002 -sTCP:LISTEN >/dev/null 2>&1; then - port_9002_listening=1 - log_info "Port 9002 is listening" - else - log_info "Port 9002 is not listening" - fi - - if sudo lsof -i :8088 -sTCP:LISTEN >/dev/null 2>&1; then - port_8088_listening=1 - log_info "Xray Port 8088 is listening" - else - log_info "Xray Port 8088 is not listening" - fi - - # HTTP health check to verify service is actually responding - if [ "$port_9080_listening" -eq 1 ]; then - if curl -s --connect-timeout 5 --max-time 10 "http://localhost:9080" >/dev/null 2>&1 || \ - curl -s --connect-timeout 5 --max-time 10 "http://localhost:9080/health" >/dev/null 2>&1 || \ - curl -s --connect-timeout 5 --max-time 10 "http://localhost:9080/api" >/dev/null 2>&1; then - service_responding=1 - log_info "Erebrus service is responding to HTTP requests" - else - log_warning "Port 9080 is listening but service is not responding to HTTP requests" - fi - fi - - # Determine overall status - local status_ok=0 - if [ "$INSTALLATION_MODE" = "container" ]; then - if [[ "$container_running" -eq 1 && "$port_9080_listening" -eq 1 && "$port_9002_listening" -eq 1 ]]; then - status_ok=1 - fi - else - if [[ "$port_9080_listening" -eq 1 && "$port_9002_listening" -eq 1 && "$port_8088_listening" -eq 1 ]]; then - status_ok=1 - fi - fi - - # Check if service is also responding - if [[ "$status_ok" -eq 1 && "$service_responding" -eq 1 ]]; then - log_success "Node status check passed - Service is fully operational" - return 0 - elif [[ "$status_ok" -eq 1 ]]; then - log_success "Node status check passed - Ports are listening" - return 0 - else - log_error "Node status check failed" - return 1 - fi -} - -validate_post_install() { - echo "🔍 Preparing to validate installation..." - (sleep 5 && check_node_status) & - show_spinner $! "→ Validating installation" - return $? -} - -check_mnemonic_format() { - log_info "Validating mnemonic format" - local mnemonic="$1" - # Split the mnemonic into an array of words - IFS=' ' read -r -a words <<< "$mnemonic" - - # Define the required number of words in the mnemonic (12, 15, 18, 21, or 24 typically for BIP39) - local required_words=(12 15 18 21 24) - - # Check if the mnemonic has the correct number of words - local num_words=${#words[@]} - if ! [[ " ${required_words[*]} " =~ " $num_words " ]]; then - log_error "Invalid mnemonic: wrong number of words ($num_words). Expected: 12, 15, 18, 21, or 24" - return 1 - fi - - # Check if each word in the mnemonic is valid - for word in "${words[@]}"; do - if [[ ! "$word" =~ ^[a-zA-Z]+$ ]]; then - log_error "Invalid mnemonic: word '$word' contains non-alphabetic characters" - return 1 - fi - done - log_success "Mnemonic format validation passed ($num_words words)" - return 0 -} - -print_final_message() { - log_info "Generating final installation message" - - # Check if any enabled stages failed - local has_failures=false - local enabled_stages=0 - local completed_stages=0 - - for i in {0..2}; do - local status="${STAGE_STATUS[$i]}" - if [[ "$status" == "✘ Failed" || "$status" == "✘ Blocked" ]]; then - has_failures=true - fi - if [[ "$status" != "✘ Skipped" ]]; then - enabled_stages=$((enabled_stages + 1)) - if [[ "$status" == "✔ Complete" ]]; then - completed_stages=$((completed_stages + 1)) - fi - fi - done - - if [[ "$has_failures" == true ]]; then - printf "\e[31mInstallation failed due to stage failures.\e[0m\n" - printf "See $LOG_FILE for details.\n" - log_error "Installation failed - One or more stages failed" - elif [[ $enabled_stages -eq 0 ]]; then - printf "\e[33mNo stages were enabled to run.\e[0m\n" - log_warning "No stages were enabled to run" - elif [[ $completed_stages -eq $enabled_stages ]]; then - printf "\e[32mErebrus node installation is finished.\e[0m\n" - printf "Erebrus Node API is accessible at http://${HOST_IP}:9080\n" - printf "Refer \e[4mhttps://github.com/NetSepio/erebrus/blob/main/docs/docs.md\e[0m for API documentation.\n" - printf "\nYou can now manage the node using the \e[1merebrus\e[0m command. Try:\n" - printf " \e[36merebrus status\e[0m\n" - printf "\n\e[32mAll stages completed successfully!\e[0m\n\n" - log_success "Installation completed successfully - Node is running" - else - printf "\e[33mSome enabled stages did not complete successfully.\e[0m\n" - printf "See $LOG_FILE for details.\n" - log_warning "Some enabled stages did not complete successfully" - fi -} - -# Stage #1 - Configure Node environment variables -configure_node() { - log_info "=== Starting Stage 1: Configure Node ===" - echo "📋 Configuring node..." - - # Prompt for installation directory and validate input - read -p "Enter installation directory (default: current directory): " INSTALL_DIR_INPUT - # Set base directory from input or use current default - BASE_DIR=${INSTALL_DIR_INPUT:-$(pwd)} - echo "Installation directory set to "$BASE_DIR"" - log_info "User input for installation directory: $INSTALL_DIR_INPUT" - - # Create the installation directory - if ! create_install_directory "$BASE_DIR"; then - log_error "Failed to create installation directory" - return 1 - fi - - DEFAULT_HOST_IP=$(get_public_ip) - - # Prompt for Public IP - printf "\nAutomatically detected public IP: ${DEFAULT_HOST_IP}\n" - read -p "Do you want to use this public IP? (default: y) (y/n): " use_default_host_ip - log_info "User choice for public IP: $use_default_host_ip" - if [ "$use_default_host_ip" = "n" ]; then - read -p "Enter your public IP (default: ${DEFAULT_HOST_IP}): " HOST_IP - HOST_IP=${HOST_IP:-$DEFAULT_HOST_IP} - log_info "User provided custom IP: $HOST_IP" - else - HOST_IP=${DEFAULT_HOST_IP} - log_info "Using detected IP: $HOST_IP" - fi - - DEFAULT_DOMAIN="http://${HOST_IP}:9080" - - # Prompt for Node Details - while [[ -z "$NODE_NAME" ]]; do - read -p "Enter your node name: " NODE_NAME - if [[ -z "$NODE_NAME" ]]; then - echo "❌ Node name cannot be empty. Please try again." - fi - done - log_info "Node name set: $NODE_NAME" - printf "Select a configuration type from the list below:\n" - PS3="Select a config type (e.g. 1): " - options=("ASTRO - Coming soon" "BEACON" "TITAN - Coming soon" "NEXUS" "ZENETH") - - while true; do - select choice in "${options[@]}"; do - case "$choice" in - "ASTRO - Coming soon"|"TITAN - Coming soon") - echo "This configuration will be in upcoming updates. Please choose another option." - break # Restart the select prompt - ;; - "BEACON"|"NEXUS"|"ZENETH") - CONFIG="$choice" - echo "You selected: $CONFIG" - log_info "Configuration type selected: $CONFIG" - break 2 # Exit both select and while loops - ;; - *) - echo "Invalid choice. Please select a valid config type." - ;; - esac - done - done - - read -p "Enable Xray (default: n) (y/n): " enable_xray - enable_xray=${enable_xray:-n} # default to 'n' if empty - log_info "Xray enable choice: $enable_xray" - - if [[ "$enable_xray" =~ ^[yY]$ ]]; then - XRAY_ENABLED="true" - printf "\033[0;32mXray will be enabled on this node.\033[0m\n" - log_info "Xray enabled" - else - XRAY_ENABLED="false" - printf "\033[0;31mXray will be disabled on this node.\033[0m\n" - log_info "Xray disabled" - fi - - # Prompt for Chain - printf "Select valid chain from list below:\n" - PS3="Select a chain (e.g. 1): " - options=("SOLANA" "PEAQ") - select CHAIN in "${options[@]}"; do - if [ -n "$CHAIN" ]; then - log_info "Chain selected: $CHAIN" - break - else - echo "Invalid choice. Please select a valid chain." - fi - done - - while true; do - read -p "Enter your wallet mnemonic: " WALLET_MNEMONIC - if check_mnemonic_format "$WALLET_MNEMONIC"; then - break - else - printf "Wrong mnemonic, try again with correct mnemonic.\n" - fi - done - - # Prompt for Config Type - printf "Select an access type from list below:\n" - PS3="Select an access type (e.g. 1): " - options=("public" "private") - select ACCESS in "${options[@]}"; do - if [ -n "$ACCESS" ]; then - log_info "Access type selected: $ACCESS" - break - else - echo "Invalid choice. Please select a valid access type." - fi - done - - # Write environment variables to .env file - bash -c "cat > ${INSTALL_DIR}/.env" </dev/null 2>&1; then - # Use getent if available (common in Linux) - if getent group "$1" >/dev/null 2>&1; then - return 0 - else - return 1 - fi - # Check using dscl (might be more reliable on macOS) - elif command -v dscl >/dev/null 2>&1; then - if dscl . -list /Groups | grep "$1" >/dev/null 2>&1; then - return 0 - else - return 1 - fi - fi -} - -function create_group() { - # Create group, takes group name as an argument $1 - if command -v groupadd; then - sudo groupadd "$1" - [[ $? -eq 0 ]] && return 0 || return 1 - elif command -v dscl; then - dscl . -create /Groups/"$1" - [[ $? -eq 0 ]] && return 0 || return 1 - fi -} - -#Create docker group and add user to the group -function add_user_to_group() { - # Add current user to docker group - if command -v usermod; then - if ! groups "$USER" | grep "$1"; then - sudo usermod -aG "$1" "$USER" # Use sudo and usermod for Linux - [[ $? -eq 0 ]] && return 0 || return 1 - fi - elif command -v dscl; then - if ! dscl . -read /Groups/"$1" | grep GroupMembership | grep "$USER"; then - dscl . -append /Groups/"$1" GroupMembership "$USER" # Use dscl for macOS - [[ $? -eq 0 ]] && return 0 || return 1 - fi - fi -} - -install_dependencies_docker_mode() { - log_info "=== Starting install_dependencies_docker_mode ===" - printf " → Checking Docker installation...\n" - if is_docker_installed; then - printf " ✓ Docker already installed\n" - sleep 2 - else - printf " → Installing Docker...\n" - if command -v apt-get > /dev/null; then - (sudo apt-get update -qq && sudo apt-get install -y containerd docker.io && sudo apt-get install netcat-* -y && sudo apt-get install lsof -y >> "$LOG_FILE" 2>&1) & - elif command -v yum > /dev/null; then - (sudo yum install yum-utils -y && sudo yum install nmap-ncat.x86_64 -y && sudo yum install lsof -y && sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo && yum install -y docker >> "$LOG_FILE" 2>&1 && sudo systemctl start docker && sudo systemctl enable docker >> "$LOG_FILE" 2>&1) & - elif command -v pacman > /dev/null; then - (sudo pacman -Sy --noconfirm docker >> "$LOG_FILE" 2>&1 && sudo systemctl start docker && sudo systemctl enable docker >> "$LOG_FILE" 2>&1) & - elif command -v dnf > /dev/null; then - printf " → Installing Docker on Fedora...\n" - (sudo dnf install dnf-plugins-core && dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && dnf install -y docker-ce docker-ce-cli containerd.io >> "$LOG_FILE" 2>&1) & - elif [[ "$OSTYPE" == "darwin"* ]]; then - printf " → Installing Docker on macOS...\n" - if ! command -v brew > /dev/null; then - printf " ✗ Homebrew not found. Please install Homebrew first.\n" - exit 1 - fi - (brew install --cask docker >> "$LOG_FILE" 2>&1 && open /Applications/Docker.app) & - printf " ✓ Docker installation complete\n" - else - printf " ✗ Unsupported Linux distribution.\n" - exit 1 - fi - printf " ✓ Docker installation complete\n" - fi - - if docker --version > /dev/null 2>&1; then - printf " → Configuring Docker group...\n" - # Created docker group if not exits - if ! group_exists "docker"; then - create_group "docker"; - fi - if add_user_to_group "docker"; then - if [[ $? -ne 0 ]]; then - printf " ✗ Failed to create group, docker configuration failed.\n" - exit 1 - fi - fi - printf " ✓ Docker configuration complete\n" - fi - log_info "=== Finished install_dependencies_docker_mode ===" -} - -function install_dependencies_binary_mode() { - log_info "=== Starting install_dependencies_binary_mode ===" - create_erebrus_folder - CURRENT_DIR=$(pwd) - - INSTALL_FAILED=false - - # Detect OS and install dependencies - if command -v apk > /dev/null; then - apk update >> "$LOG_FILE" 2>&1 - apk add --no-cache bash openresolv bind-tools wireguard-tools gettext inotify-tools iptables >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v apt-get > /dev/null; then - sudo apt-get update -qq >> "$LOG_FILE" 2>&1 - sudo apt-get install -y bash resolvconf dnsutils wireguard-tools gettext inotify-tools iptables systemd netcat-* lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v yum > /dev/null; then - sudo yum install -y bash openresolv bind-utils wireguard-tools gettext inotify-tools iptables nmap-ncat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v pacman > /dev/null; then - sudo pacman -Sy --noconfirm bash openresolv bind-tools wireguard-tools gettext inotify-tools iptables netcat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v dnf > /dev/null; then - sudo dnf install -y bash openresolv bind-utils wireguard-tools gettext inotify-tools iptables nmap-ncat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - elif command -v brew > /dev/null; then - brew install bash wireguard-tools gettext coreutils iproute2mac curl netcat lsof >> "$LOG_FILE" 2>&1 || INSTALL_FAILED=true - else - echo " ✗ Unsupported Linux distribution. Exiting." | tee -a "$LOG_FILE" - exit 1 - fi - - if [ "$INSTALL_FAILED" = true ]; then - log_error "Some dependencies failed to install." - fi - log_info "=== Finished install_dependencies_binary_mode ===" -} - -function download_xray_binary() { - log_info "=== Starting download_xray_binary ===" - XRAY_REPO="NetSepio/erebrus-xray" - DOWNLOAD_DIR="${INSTALL_DIR}" - - # Detect OS and ARCH (same logic as erebrus binary) - OS=$(uname | tr '[:upper:]' '[:lower:]') # "linux" or "darwin" - ARCH=$(uname -m) - - case "$ARCH" in - x86_64) ARCH="amd64" ;; - arm64 | aarch64) ARCH="arm64" ;; - *) - log_error "Unsupported architecture: $ARCH" - echo " ✗ Unsupported architecture: $ARCH" - return 1 - ;; - esac - - XRAY_BINARY_NAME="erebrus-xray-${OS}-${ARCH}" - XRAY_PATH="$DOWNLOAD_DIR/$XRAY_BINARY_NAME" - - log_info "Detected OS: $OS, Architecture: $ARCH" - log_info "Target binary: $XRAY_BINARY_NAME" - - # Check if binary already exists and is executable - if [[ -f "$XRAY_PATH" && -x "$XRAY_PATH" ]]; then - log_info "Erebrus-Xray binary already exists at $XRAY_PATH" - echo "$XRAY_PATH" > "${DOWNLOAD_DIR}/xray_binary_path" - log_success "Downloading latest Erebrus-Xray binary" - log_info "=== Finished download_xray_binary ===" - fi - - # Try to fetch latest release tag with better error handling - log_info "Fetching latest Xray release information..." - LATEST_XRAY_TAG=$(curl -s --connect-timeout 10 --max-time 30 https://api.github.com/repos/$XRAY_REPO/releases/latest 2>>"$LOG_FILE" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - - if [[ -z "$LATEST_XRAY_TAG" ]]; then - log_warning "Could not fetch latest release tag from GitHub API, trying fallback method..." - # Fallback: try to get the latest tag directly - LATEST_XRAY_TAG=$(curl -s --connect-timeout 10 --max-time 30 "https://api.github.com/repos/$XRAY_REPO/tags" 2>>"$LOG_FILE" | grep '"name":' | head -1 | sed -E 's/.*"([^"]+)".*/\1/') - - if [[ -z "$LATEST_XRAY_TAG" ]]; then - log_warning "GitHub API failed, using default tag 'latest'..." - LATEST_XRAY_TAG="latest" - fi - fi - - log_info "Using Xray release tag: $LATEST_XRAY_TAG" - XRAY_DOWNLOAD_URL="https://github.com/$XRAY_REPO/releases/download/$LATEST_XRAY_TAG/$XRAY_BINARY_NAME" - log_info "Download URL: $XRAY_DOWNLOAD_URL" - - # Remove existing file if present - if [[ -f "$XRAY_PATH" ]]; then - rm -f "$XRAY_PATH" - log_info "Removed existing Xray binary file" - fi - - # Download with better error handling - log_info "Downloading Xray binary..." - if curl -L --connect-timeout 10 --max-time 300 -o "$XRAY_PATH" "$XRAY_DOWNLOAD_URL" >> "$LOG_FILE" 2>&1; then - log_info "Download completed successfully" - chmod +x "$XRAY_PATH" - - if [[ -f "$XRAY_PATH" && -s "$XRAY_PATH" ]]; then - local file_size=$(stat -f%z "$XRAY_PATH" 2>/dev/null || stat -c%s "$XRAY_PATH" 2>/dev/null || echo "unknown") - echo "$XRAY_PATH" > "${DOWNLOAD_DIR}/xray_binary_path" - log_success "Erebrus-Xray binary downloaded successfully to $XRAY_PATH (size: $file_size bytes)" - else - log_error "Downloaded file is missing or empty" - return 1 - fi - else - log_error "Failed to download Erebrus-Xray binary from $XRAY_DOWNLOAD_URL" - return 1 - fi - - log_info "=== Finished download_xray_binary ===" - return 0 -} - -function download_erebrus_binary() { - log_info "=== Starting download_erebrus_binary ===" - kill_port_erebrus - REPO="NetSepio/erebrus" - DOWNLOAD_DIR="${INSTALL_DIR}" - #ERROR_LOG="$DOWNLOAD_DIR/erebrus_error.log" - - # Detect OS and ARCH - OS=$(uname | tr '[:upper:]' '[:lower:]') # "linux" or "darwin" - ARCH=$(uname -m) - - case "$ARCH" in - x86_64) ARCH="amd64" ;; - arm64 | aarch64) ARCH="arm64" ;; - *) echo " ✗ Unsupported architecture: $ARCH" | tee "$ERROR_LOG"; log_error "Unsupported architecture: $ARCH"; return 1 ;; - esac - - BINARY_NAME="erebrus-${OS}-${ARCH}" - BINARY_PATH="$DOWNLOAD_DIR/$BINARY_NAME" - - # Fetch latest release tag - LATEST_TAG=$(curl -s https://api.github.com/repos/$REPO/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - - if [[ -z "$LATEST_TAG" ]]; then - echo " ✗ Failed to fetch the latest release tag." | tee "$ERROR_LOG" - log_error "Failed to fetch the latest release tag." - return 1 - fi - - DOWNLOAD_URL="https://github.com/$REPO/releases/download/$LATEST_TAG/$BINARY_NAME" - - if [[ -f "$BINARY_PATH" ]]; then - rm -f "$BINARY_PATH" - fi - - curl -L -o "$BINARY_PATH" "$DOWNLOAD_URL" >> "$LOG_FILE" 2>&1 - - if [[ $? -ne 0 ]]; then - echo " ✗ Download failed!" | tee "$ERROR_LOG" - log_error "Download failed!" - return 1 - fi - - chmod +x "$BINARY_PATH" - - if [[ ! -f "$BINARY_PATH" ]]; then - echo " ✗ Error: $BINARY_NAME not found in $DOWNLOAD_DIR!" | tee "$ERROR_LOG" - log_error "Error: $BINARY_NAME not found in $DOWNLOAD_DIR!" - return 1 - fi - - echo "$BINARY_PATH" > "${DOWNLOAD_DIR}/erebrus_binary_path" - log_success "Erebrus binary downloaded successfully to $BINARY_PATH" - log_info "=== Finished download_erebrus_binary ===" - return 0 -} - -run_erebrus_container() { - log_info "=== Starting run_erebrus_container ===" - printf " → Starting Erebrus container...\n" - ENV_FILE="${INSTALL_DIR}/.env" - sleep 2 - if [ ! -f "$ENV_FILE" ]; then - printf " ✗ The .env file does not exist at path: %s\n" "$ENV_FILE" - printf " Make sure the .env file exists and try again.\n" - log_error "The .env file does not exist at path: $ENV_FILE" - exit 1 - fi - (sudo docker run -d -p 9080:9080/tcp -p 9002:9002/tcp -p 51820:51820/udp \ - --cap-add=NET_ADMIN --cap-add=SYS_MODULE \ - --sysctl="net.ipv4.conf.all.src_valid_mark=1" \ - --sysctl="net.ipv6.conf.all.forwarding=1" \ - --restart unless-stopped -v "${INSTALL_DIR}/wireguard:/etc/wireguard" \ - --name erebrus --env-file "${ENV_FILE}" ghcr.io/netsepio/erebrus:main >> "$LOG_FILE" 2>&1) & - wait $! - printf " ✓ Erebrus container started\n" - log_success "Erebrus container started" - log_info "=== Finished run_erebrus_container ===" -} - -run_erebrus_binary() { - log_info "=== Starting run_erebrus_binary ===" - local path="${INSTALL_DIR}/erebrus_binary_path" - - if [[ -f "$path" ]]; then - local binary=$(cat "$path") - # Change to the installation directory before running the binary - cd "${INSTALL_DIR}" || { - log_error "Failed to change to installation directory: ${INSTALL_DIR}" - return 1 - } - - # Run the binary with sudo (should work now that we ensured credentials) - sudo "$binary" > "${INSTALL_DIR}/erebrus.log" 2>&1 & - EREBRUS_PID=$! - # Change back to original directory - cd - > /dev/null - - if kill -0 "$EREBRUS_PID" 2>/dev/null; then - log_success "Erebrus started with (PID: $EREBRUS_PID)" - return 0 - else - log_error "Erebrus binary failed to start" - return 1 - fi - else - log_error "Erebrus binary path not found" - return 1 - fi - log_info "=== Finished run_erebrus_binary ===" -} - -function run_xray_binary() { - log_info "=== Starting run_xray_binary ===" - - # Create config file first - create_xray_config || { - log_error "Failed to create Xray configuration" - return 1 - } - - local path="${INSTALL_DIR}/xray_binary_path" - if [[ -f "$path" ]]; then - local binary=$(cat "$path") - local config_path="${INSTALL_DIR}/config.json" - "$binary" -c "$config_path" > "${INSTALL_DIR}/xray.log" 2>&1 & - XRAY_PID=$! - sleep 2 - - if kill -0 "$XRAY_PID" 2>/dev/null; then - log_success "Erebrus-Xray started (PID: $XRAY_PID) with config at $config_path" - return 0 - else - log_error "Erebrus-Xray process exited or failed to start" - return 1 - fi - else - log_error "Xray binary path not found" - return 1 - fi - - log_info "=== Finished run_xray_binary ===" - return 0 -} - -# Function to create the "erebrus" folder in the current directory -function create_erebrus_folder() { - CURRENT_DIR=$(pwd) - FOLDER_NAME="erebrus" - mkdir -p "$CURRENT_DIR/$FOLDER_NAME" - if ! [ -d "$CURRENT_DIR/$FOLDER_NAME" ]; then - return 1 - fi -} - -function kill_port_erebrus() { - local ports=(9080 9002 8088) - - for port in "${ports[@]}"; do - local pids - pids=$(sudo lsof -t -i :$port 2>/dev/null) - - if [[ -n "$pids" ]]; then - log_info "Killing processes on port $port: $pids" - echo "$pids" | xargs kill -9 2>/dev/null - fi - done -} - -confirm_installation() { - read -p "Do you want to continue with installation? (default: y) (y/n): " confirm - - # Clear the prompt line immediately after user input - printf "\033[1A\033[2K" # Move up one line and clear it - - confirm=${confirm:-y} - if [[ "$confirm" != [Yy] ]]; then - echo "Installation cancelled." - exit 1 - fi - - if check_node_status; then - printf "\e[33mErebrus node is already installed and running.\e[0m\n" - printf "Refer \e[4mhttps://github.com/NetSepio/erebrus/blob/main/docs/docs.md\e[0m for API documentation.\n\n" - - while true; do - read -p "Do you want to reinstall the node? (y/n): " confirm_reinstallation - # Clear this prompt too - printf "\033[1A\033[2K" - - case "$confirm_reinstallation" in - [Yy]) - break - ;; - [Nn]) - printf "\e[31mInstallation aborted by user\e[0m\n" - exit 0 - ;; - *) - echo "Please select valid option" - ;; - esac - done - fi -} - -function create_xray_config() { - log_info "=== Starting create_xray_config ===" - # Create config.json file - local config_file="$INSTALL_DIR/config.json" - - cat > "$config_file" < /dev/null ;; - 2) declare -f install_dependencies > /dev/null ;; - 3) declare -f run_node > /dev/null ;; - *) return 1 ;; - esac -} - -# Function to check if previous stage was successful -check_previous_stage() { - local current_stage=$1 - local previous_stage=$((current_stage - 1)) - - if [[ $previous_stage -ge 0 ]]; then - local prev_status="${STAGE_STATUS[$previous_stage]}" - if [[ "$prev_status" != "✔ Complete" && "$prev_status" != "✘ Skipped" ]]; then - log_error "Stage $((current_stage + 1)) cannot run: Stage $((previous_stage + 1)) was not successful (Status: $prev_status)" - return 1 - fi - fi - return 0 -} - -create_manage_script() { - log_info "Installing node management script" - show_spinner $! "→ Installing node management script" - cat > ${INSTALL_DIR}/manage.sh <<'EOF' -#!/bin/bash -#Erebrus Node Management Script - -# Ensure script runs with sudo/root -if [[ "$EUID" -ne 0 ]]; then - exec sudo "$0" "$@" -fi - -DEBUG=false -ARGS=() -FOLLOW_LOGS=false - - -print_help() { - cat </dev/null) -XRAY_PATH=$(cat $INSTALL_DIR/xray_binary_path 2>/dev/null) -if [[ ! -x "$EREBRUS_PATH" ]]; then - echo "Error: erebrus_binary_path is missing or not executable" - exit 1 -fi - -if [[ "$XRAY_ENABLED" == "true" && ! -x "$XRAY_PATH" ]]; then - echo "Error: xray_binary_path is missing or not executable" - exit 1 -fi - -get_pids() { - local binary="$1" - local binary_name - binary_name=$(basename "$binary") - pgrep -f "$binary_name" | paste -sd ' ' - -} - -start_service() { - local name="$1" - local binary="$2" - - log_debug "Starting $name with binary: $binary" - - local pids - pids=$(get_pids "$binary") - if [[ -n "$pids" ]]; then - printf "\e[32m%s is already running (PIDs: %s)\e[0m\n" "$name" "$(echo "$pids" | paste -sd ',' -)" - else - if [[ "$name" == "erebrus-node" ]]; then - "$binary" > "$INSTALL_DIR/erebrus.log" 2>&1 & - elif [[ "$name" == "erebrus-xray" ]]; then - "$binary" -c "$INSTALL_DIR/config.json" > "$INSTALL_DIR/xray.log" 2>&1 & - else - "$binary" > /dev/null 2>&1 & - fi - printf "\e[32m%s started (PID: %s)\e[0m\n" "$name" "$!" - fi -} - -stop_service() { - local name="$1" - local binary="$2" - - log_debug "Stopping $name with binary: $binary" - - local pids - pids=$(get_pids "$binary") - if [[ -n "$pids" ]]; then - echo "$pids" | xargs kill - printf "\e[31m%s stopped (PIDs: %s)\e[0m\n" "$name" "$pids" - else - printf "\e[31m%s is not running\e[0m\n" "$name" - fi -} - -status_service() { - local name="$1" - local binary="$2" - - log_debug "Checking status of $name with binary: $binary" - - local pids - pids=$(get_pids "$binary") - if [[ -n "$pids" ]]; then - printf "\e[32m%s is running (PIDs: %s)\e[0m\n" "$name" "$pids" - else - printf "\e[31m%s is not running\e[0m\n" "$name" - fi -} - -ACTION="$1" -SERVICE="$2" - -run_action() { - local action="$1" - local service="$2" - local binary name - - if [[ "$action" != "log" ]]; then - case "$service" in - node) - binary="$EREBRUS_PATH" - name="erebrus-node" - ;; - xray) - if [[ "$XRAY_ENABLED" != "true" ]]; then - printf "\e[31mXray is not installed on this node\e[0m\n" - return - fi - binary="$XRAY_PATH" - name="erebrus-xray" - ;; - *) - printf "\e[31mUnknown service: %s\e[0m\n" "$service" - exit 1 - ;; - esac - fi - - case "$action" in - start) start_service "$name" "$binary" ;; - stop) stop_service "$name" "$binary" ;; - status) status_service "$name" "$binary" ;; - restart) - stop_service "$name" "$binary" - sleep 1 - start_service "$name" "$binary" - ;; - log) show_logs "$service" ;; - *) - printf "\e[31mInvalid action: %s\e[0m\n" "$action" - exit 1 - ;; - esac -} - -if [[ -z "$ACTION" ]]; then - print_help - exit 1 -fi - -if [[ -z "$SERVICE" ]]; then - run_action "$ACTION" node - run_action "$ACTION" xray -else - run_action "$ACTION" "$SERVICE" -fi -EOF - - chmod +x ${INSTALL_DIR}/manage.sh - sudo ln -s ${INSTALL_DIR}/manage.sh /usr/local/bin/erebrus - log_info "manage.sh script created and made executable." - return $? -} - -# Run stage1 -# For each run_stage function, change the order of operations: -run_stage_1() { - if declare -f configure_node > /dev/null; then - STAGE_STATUS[0]="In Progress" - display_header # Update header BEFORE running the function - - if configure_node; then - STAGE_STATUS[0]="✔ Complete" - display_header # Update header AFTER status change - echo "✅ Stage 1: Node configuration completed!" - log_success "Stage 1: Node configuration completed!" - else - STAGE_STATUS[0]="✘ Failed" - display_header # Update header AFTER status change - echo "❌ Stage 1: Node configuration failed!" - log_error "Stage 1: Node configuration failed!" - fi - - sleep 3 - else - STAGE_STATUS[0]="✘ Skipped" - display_header - echo "⏭️ Stage 1: Configuration skipped" - log_info "Stage 1: Configuration skipped" - sleep 3 - fi -} - -# Run stage2 -run_stage_2() { - # Check if previous stage was successful - if ! check_previous_stage 1; then - STAGE_STATUS[1]="✘ Blocked" - display_header - echo "🚫 Stage 2: Dependencies installation blocked due to previous stage failure" - log_error "Stage 2: Dependencies installation blocked due to previous stage failure" - sleep 2 - return 1 - fi - - if declare -f install_dependencies > /dev/null; then - STAGE_STATUS[1]="In Progress" - display_header - if install_dependencies; then - if create_manage_script; then - STAGE_STATUS[1]="✔ Complete" - display_header - echo "✅ Stage 2: Dependencies installed successfully & node management script installed!" - log_success "Stage 2: Dependencies installed successfully and node management script installed!" - else - STAGE_STATUS[2]="✘ Failed" - display_header - echo "❌ Stage 2: Node management script installation failed" - log_error "Stage 2: Installing dependencies completed, but Node management script installation failed" - fi - else - STAGE_STATUS[1]="✘ Failed" - echo "❌ Stage 2: Dependencies installation failed!" - log_error "Stage 2: Dependencies installation failed!" - fi - - sleep 3 - # clear_subprocess_output # Add this line - else - STAGE_STATUS[1]="✘ Skipped" - display_header - echo "⏭️ Stage 2: Dependencies installation skipped" - log_info "Stage 2: Dependencies installation skipped" - sleep 3 - # clear_subprocess_output # Add this line - fi -} - -# Run stage3 -run_stage_3() { - # Check if previous stage was successful - if ! check_previous_stage 2; then - STAGE_STATUS[2]="✘ Blocked" - display_header - echo "🚫 Stage 3: Node startup blocked due to previous stage failure" - log_error "Stage 3: Node startup blocked due to previous stage failure" - sleep 3 - return 1 - fi - - if declare -f run_node > /dev/null; then - STAGE_STATUS[2]="In Progress" - display_header - if run_node; then - if validate_post_install; then - STAGE_STATUS[2]="✔ Complete" - display_header - echo "✅ Stage 3: Node started and validated successfully!" - log_success "Stage 3: Node started and validated successfully!" - else - STAGE_STATUS[2]="✘ Failed" - display_header - echo "❌ Stage 3: Node started but validation failed" - log_error "Stage 3: Node started but validation failed" - fi - else - STAGE_STATUS[2]="✘ Failed" - display_header - echo "❌ Stage 3: Failed to start node" - log_error "Stage 3: Failed to start node" - fi - - sleep 3 - else - STAGE_STATUS[2]="✘ Skipped" - display_header - echo "⏭️ Stage 3: Node startup skipped" - log_info "Stage 3: Node startup skipped" - sleep 3 - fi -} - -# Cleanup function to restore terminal on exit -cleanup() { - tput cnorm # Show cursor -} - -# Set trap to cleanup on exit -trap cleanup EXIT - -##################################################################################################################### -# Main script execution starts here -##################################################################################################################### -STAGE_STATUS=("Pending" "Pending" "Pending") -INSTALLATION_MODE="binary" #valid options "binary" , "container" -XRAY_ENABLED="false" - -# Set default directories -BASE_DIR=$(pwd) -INSTALL_DIR="$BASE_DIR/erebrus" - -init_logging -display_header # Show header once -confirm_installation -mark_disabled_stages # Mark disabled stages as skipped before running any stages - -# Only update header once after marking disabled stages -display_header - -# Run Stages -run_stage_1 -run_stage_2 -run_stage_3 - -# Final status update -for i in {0..2}; do - if [[ "${STAGE_STATUS[$i]}" == "Pending" ]]; then - STAGE_STATUS[$i]="✘ Skipped" - fi -done -display_header - -# Print final message -echo "" -print_final_message - -# Show cursor again -tput cnorm -if [ -n "$BASH_VERSION" ]; then - hash -r -elif [ -n "$ZSH_VERSION" ]; then - rehash -fi diff --git a/p2p/discovery.go b/p2p/discovery.go deleted file mode 100644 index 3f4e69e..0000000 --- a/p2p/discovery.go +++ /dev/null @@ -1,56 +0,0 @@ -package p2p - -import ( - "context" - "fmt" - "sync" - - dht "github.com/libp2p/go-libp2p-kad-dht" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/libp2p/go-libp2p/p2p/discovery/routing" - discovery "github.com/libp2p/go-libp2p/p2p/discovery/util" - "github.com/multiformats/go-multiaddr" -) - -// NewDHT attempts to connect to a bunch of bootstrap peers and returns a new DHT. -// If you don't have any bootstrapPeers, you can use dht.DefaultBootstrapPeers -// or an empty list. -func NewDHT(ctx context.Context, host host.Host, bootstrapPeers []multiaddr.Multiaddr) (*dht.IpfsDHT, error) { - kdht, err := dht.New(ctx, host) - if err != nil { - return nil, err - } - - if err = kdht.Bootstrap(ctx); err != nil { - return nil, err - } - - var wg sync.WaitGroup - // loop through bootstrapPeers (if any), and attempt to connect to them - for _, peerAddr := range bootstrapPeers { - peerinfo, _ := peer.AddrInfoFromP2pAddr(peerAddr) - - wg.Add(1) - go func() { - defer wg.Done() - if err := host.Connect(ctx, *peerinfo); err != nil { - fmt.Printf("Error while connecting to node %q: %-v", peerinfo, err) - fmt.Println() - } else { - fmt.Printf("Connection established with bootstrap node: %q", *peerinfo) - fmt.Println() - } - }() - } - wg.Wait() - return kdht, nil -} - -// Search the DHT for peers, then connect to them. -func Discover(ctx context.Context, h host.Host, dht *dht.IpfsDHT, rendezvous string) { - var routingDiscovery = routing.NewRoutingDiscovery(dht) - - // Advertise our addresses on rendezvous - discovery.Advertise(ctx, routingDiscovery, rendezvous) -} diff --git a/p2p/host.go b/p2p/host.go deleted file mode 100644 index bfc989d..0000000 --- a/p2p/host.go +++ /dev/null @@ -1,136 +0,0 @@ -package p2p - -import ( - "crypto/sha256" - "fmt" - "os" - - "github.com/NetSepio/erebrus/types" - "github.com/libp2p/go-libp2p" - "github.com/libp2p/go-libp2p/core/crypto" - "github.com/libp2p/go-libp2p/core/host" - "github.com/libp2p/go-libp2p/core/peer" - "github.com/multiformats/go-multiaddr" - bip39 "github.com/tyler-smith/go-bip39" - bip32 "github.com/tyler-smith/go-bip32" - log "github.com/sirupsen/logrus" -) - -// Custom reader for deterministic key generation -type reader struct { - seed []byte - pos int -} - -func (r *reader) Read(p []byte) (n int, err error) { - copy(p, r.seed) - return len(r.seed), nil -} - -func bytesReader(seed []byte) *reader { - return &reader{seed: seed} -} - -// Add this variable to store the host instance -var Host host.Host - -// makeBasicHost creates a LibP2P host with a deterministic peer ID using mnemonics -func makeBasicHost() (host.Host, error) { - // Get mnemonic from environment variable or use default - mnemonic := os.Getenv("MNEMONIC") - if mnemonic == "" { - log.Warn("MNEMONIC not set, using default mnemonic") - mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" - } - - // Convert mnemonic to a BIP-32 seed - seed := bip39.NewSeed(mnemonic, "") - - // Derive a master key from the seed - masterKey, err := bip32.NewMasterKey(seed) - if err != nil { - return nil, fmt.Errorf("failed to create master key: %v", err) - } - - // Derive a child key (hardened path example: m/44'/60'/0'/0) - childKey, err := masterKey.NewChildKey(bip32.FirstHardenedChild) - if err != nil { - return nil, fmt.Errorf("failed to derive child key: %v", err) - } - - // Convert the private key to an Ed25519 key (libp2p format) - hashedKey := sha256.Sum256(childKey.Key) // Hashing to get a fixed-length key - priv, _, err := crypto.GenerateKeyPairWithReader(crypto.Ed25519, 256, bytesReader(hashedKey[:])) - if err != nil { - return nil, fmt.Errorf("failed to generate libp2p key: %v", err) - } - - // Log the peer ID being generated (for debugging) - peerID, err := peer.IDFromPrivateKey(priv) - if err != nil { - log.Warnf("Failed to generate peer ID from private key: %v", err) - } else { - log.WithFields(log.Fields{ - "peerID": peerID.String(), - }).Info("Generated deterministic peer ID") - } - - opts := []libp2p.Option{ - libp2p.ListenAddrStrings("/ip4/0.0.0.0/tcp/9002"), - libp2p.Identity(priv), - libp2p.DisableRelay(), - } - - host, err := libp2p.New(opts...) - if err != nil { - return nil, fmt.Errorf("failed to create libp2p host: %v", err) - } - - // Set the host in types package - types.SetHost(host) - - // Log the host addresses - log.WithFields(log.Fields{ - "addresses": host.Addrs(), - }).Info("LibP2P host created with addresses") - - return host, nil -} - -func getHostAddress(ha host.Host) string { - // Build host multiaddress - hostAddr, _ := multiaddr.NewMultiaddr(fmt.Sprintf("/p2p/%s", ha.ID().String())) - - // Now we can build a full multiaddress to reach this host - // by encapsulating both addresses: - addr := ha.Addrs()[0] - fullAddr := addr.Encapsulate(hostAddr).String() - - log.WithFields(log.Fields{ - "address": fullAddr, - }).Info("Generated host address") - - return fullAddr -} - -// Add a function to get the Host -func GetHost() host.Host { - return Host -} - -// InitHost initializes the LibP2P host -func InitHost() error { - _, err := makeBasicHost() - if err != nil { - return fmt.Errorf("failed to initialize LibP2P host: %v", err) - } - - if host := types.GetHost(); host != nil { - log.WithFields(log.Fields{ - "peerID": host.ID().String(), - "addresses": host.Addrs(), - }).Info("LibP2P host initialized successfully") - } - - return nil -} diff --git a/p2p/p2p.go b/p2p/p2p.go deleted file mode 100644 index 31d05b6..0000000 --- a/p2p/p2p.go +++ /dev/null @@ -1,174 +0,0 @@ -package p2p - -import ( - "context" - "encoding/json" - "fmt" - "log" - "os" - "time" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/util/pkg/node" - "github.com/docker/docker/pkg/namesgenerator" - pubsub "github.com/libp2p/go-libp2p-pubsub" - "github.com/multiformats/go-multiaddr" - "github.com/sirupsen/logrus" -) - -// DiscoveryInterval is how often we search for other peers via the DHT. -const DiscoveryInterval = time.Second * 10 - -// DiscoveryServiceTag is used in our DHT advertisements to discover -// other peers. -const DiscoveryServiceTag = "erebrus" - -var StartTimeStamp int64 - -func Init() { - - var name string - - if os.Getenv("NODE_NAME") != "" { - name = os.Getenv("NODE_NAME") - } else { - name = namesgenerator.GetRandomName(0) - - } - StartTimeStamp = time.Now().Unix() - ctx := context.Background() - - // create a new libp2p Host - ha, err := makeBasicHost() - if err != nil { - log.Fatal(err) - } - - fullAddr := getHostAddress(ha) - log.Printf("I am %s\n", fullAddr) - - remoteAddr := "/ip4/" + os.Getenv("HOST_IP") + "/tcp/" + os.Getenv("LIBP2P_PORT") + "/p2p/" + ha.ID().String() - // Create a new PubSub service using the GossipSub router. - ps, err := pubsub.NewGossipSub(ctx, ha) - if err != nil { - panic(err) - } - - // Setup DHT with empty discovery peers so this will be a discovery peer for other - // peers. This peer should run with a public ip address, otherwise change "nil" to - // a list of peers to bootstrap with. - bootstrapPeer, err := multiaddr.NewMultiaddr(os.Getenv("GATEWAY_PEERID")) - if err != nil { - panic(err) - } - dht, err := NewDHT(ctx, ha, []multiaddr.Multiaddr{bootstrapPeer}) - if err != nil { - panic(err) - } - - // Setup global peer discovery over DiscoveryServiceTag. - go Discover(ctx, ha, dht, DiscoveryServiceTag) - - //Topic 1 - topicString := "status" // Change "UniversalPeer" to whatever you want! - topic, err := ps.Join(DiscoveryServiceTag + "/" + topicString) - if err != nil { - panic(err) - } - go func() { - time.Sleep(5 * time.Second) - fmt.Println("sending status") - node_data := node.CreateNodeStatus(remoteAddr, ha.ID().String(), StartTimeStamp, name) - msgBytes, err := json.Marshal(node_data) - log.Println("node data", node_data) - if err != nil { - panic(err) - } - if err := topic.Publish(ctx, msgBytes); err != nil { - panic(err) - } - }() - //Subscribe to the topic. - sub, err := topic.Subscribe() - if err != nil { - panic(err) - } - - go func() { - for { - // Block until we recieve a new message. - msg, err := sub.Next(ctx) - if err != nil { - panic(err) - } - if msg.ReceivedFrom == ha.ID() { - continue - } - fmt.Printf("[%s] %s", msg.ReceivedFrom, string(msg.Data)) - fmt.Println() - } - }() - - //Topic 2 - ClientTopicString := "client" // Change "UniversalPeer" to whatever you want! - ClientTopic, err := ps.Join(DiscoveryServiceTag + "/" + ClientTopicString) - if err != nil { - panic(err) - } - go func() { - time.Sleep(5 * time.Second) - fmt.Println("sending clients") - clients, err := core.ReadClients() - if err != nil { - logrus.WithFields(logrus.Fields{ - "err": err, - }).Error("failed to list clients") - return - } - - msgBytes, err := json.Marshal(clients) - if err != nil { - panic(err) - } - if err := topic.Publish(ctx, msgBytes); err != nil { - panic(err) - } - }() - //Subscribe to the topic. - ClientSub, err := ClientTopic.Subscribe() - if err != nil { - panic(err) - } - go func() { - for { - // Block until we recieve a new message. - msg, err := ClientSub.Next(ctx) - if err != nil { - panic(err) - } - if msg.ReceivedFrom == ha.ID() { - continue - } - fmt.Printf("[%s] %s", msg.ReceivedFrom, string(msg.Data)) - fmt.Println() - } - }() - -} - -type status struct { - Status string -} - -func sendStatusMsg(msg string, topic *pubsub.Topic, ctx context.Context) { - m := status{ - Status: msg, - } - msgBytes, err := json.Marshal(m) - if err != nil { - panic(err) - } - if err := topic.Publish(ctx, msgBytes); err != nil { - panic(err) - } -} diff --git a/storage/file.go b/storage/file.go deleted file mode 100644 index 04b77d5..0000000 --- a/storage/file.go +++ /dev/null @@ -1,61 +0,0 @@ -package storage - -import ( - "encoding/json" - "os" - "path/filepath" - - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" -) - -// Serialize write interface to disk -func Serialize(id string, c interface{}) error { - b, err := json.MarshalIndent(c, "", " ") - if err != nil { - return err - } - - //If the file is not server.json write in clients directory - if id != "server.json" { - return util.WriteFile(filepath.Join(os.Getenv("WG_CLIENTS_DIR"), id), b) - } - - //If the file is server.json write in wg_conf directory - return util.WriteFile(filepath.Join(os.Getenv("WG_CONF_DIR"), id), b) -} - -// Deserialize read interface from disk -func Deserialize(id string) (interface{}, error) { - var path string - - //if the id is for client use client directory otherwise use WG_CONF directory - if id != "server.json" { - path = filepath.Join(os.Getenv("WG_CLIENTS_DIR"), id) - } else { - path = filepath.Join(os.Getenv("WG_CONF_DIR"), id) - } - - data, err := util.ReadFile(path) - if err != nil { - return nil, err - } - - if id == "server.json" { - var s *model.Server - err = json.Unmarshal(data, &s) - if err != nil { - return nil, err - } - return s, nil - } - - // if not the server, must be client - var c *model.Client - err = json.Unmarshal(data, &c) - if err != nil { - return nil, err - } - - return c, nil -} diff --git a/template/template.go b/template/template.go deleted file mode 100644 index b4fd21c..0000000 --- a/template/template.go +++ /dev/null @@ -1,118 +0,0 @@ -package template - -import ( - "bytes" - "os" - "path/filepath" - "strings" - "text/template" - "time" - - "github.com/NetSepio/erebrus/model" - "github.com/NetSepio/erebrus/util" -) - -var ( - wgTpl = `# Updated: {{ .Server.UpdatedAt }} / Created: {{ .Server.CreatedAt }} -[Interface] -{{- range .Server.Address }} -Address = {{ . }} -{{- end }} -ListenPort = {{ .Server.ListenPort }} -PrivateKey = {{ .Server.PrivateKey }} -{{ if ne .Server.Mtu 0 -}} -MTU = {{.Server.Mtu}} -{{- end}} -PreUp = {{ .Server.PreUp }} -PostUp = {{ .Server.PostUp }} -PreDown = {{ .Server.PreDown }} -PostDown = {{ .Server.PostDown }} -{{- range .Clients }} -{{ if .Enable -}} -# {{.Name}} / {{.WalletAddress}} / Updated: {{.UpdatedAt}} / Created: {{.CreatedAt}} -# friendly_name = {{.Name}} -[Peer] -PublicKey = {{ .PublicKey }} -PresharedKey = {{ .PresharedKey }} -AllowedIPs = {{ StringsJoin .Address ", " }} -{{- end }} -{{ end }}` - - clientTpl = `[Interface] -Address = {{ StringsJoin .Client.Address ", " }} -PrivateKey = {{ .Server.PrivateKey }} -{{ if ne (len .Server.DNS) 0 -}} -DNS = {{ StringsJoin .Server.DNS ", " }} -{{- end }} -{{ if ne .Server.Mtu 0 -}} -MTU = {{.Server.Mtu}} -{{- end}} -[Peer] -PublicKey = {{ .Server.PublicKey }} -PresharedKey = {{ .Client.PresharedKey }} -AllowedIPs = {{ StringsJoin .Client.AllowedIPs ", " }} -Endpoint = {{ .Server.Endpoint }}:{{ .Server.ListenPort }} -{{ if and (ne .Server.PersistentKeepalive 0) (not .Client.IgnorePersistentKeepalive) -}} -PersistentKeepalive = {{.Server.PersistentKeepalive}} -{{- end}} -` -) - -// DumpServerWg dump server wg config with go template, write it to file and return bytes -func DumpServerWg(clients []*model.Client, server *model.Server) ([]byte, error) { - t, err := template.New("server").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(wgTpl) - if err != nil { - return nil, err - } - - configDataWg, err := dump(t, struct { - Clients []*model.Client - Server *model.Server - }{ - Clients: clients, - Server: server, - }) - if err != nil { - return nil, err - } - - err = util.WriteFile(filepath.Join(os.Getenv("WG_CONF_DIR"), os.Getenv("WG_INTERFACE_NAME")), configDataWg) - if err != nil { - return nil, err - } - - return configDataWg, nil -} - -func dump(tpl *template.Template, data interface{}) ([]byte, error) { - var tplBuff bytes.Buffer - - err := tpl.Execute(&tplBuff, data) - if err != nil { - return nil, err - } - - return tplBuff.Bytes(), nil -} - -// DumpClientWg dump client wg config with go template -func DumpClientWg(client *model.Client, server *model.Server) ([]byte, error) { - t, err := template.New("client").Funcs(template.FuncMap{"StringsJoin": strings.Join}).Parse(clientTpl) - if err != nil { - return nil, err - } - - return dump(t, struct { - Client *model.Client - Server *model.Server - }{ - Client: client, - Server: server, - }) -} - -func FormatTime(t int64) string { - result := time.Unix(0, t*int64(time.Millisecond)) - return result.Format("Monday, 02 January 06 15:04:05 MST") - -} diff --git a/types/host.go b/types/host.go deleted file mode 100644 index bf426af..0000000 --- a/types/host.go +++ /dev/null @@ -1,15 +0,0 @@ -package types - -import ( - "github.com/libp2p/go-libp2p/core/host" -) - -var LibP2PHost host.Host - -func SetHost(h host.Host) { - LibP2PHost = h -} - -func GetHost() host.Host { - return LibP2PHost -} \ No newline at end of file diff --git a/util/pkg/auth/auth.go b/util/pkg/auth/auth.go deleted file mode 100644 index b764ea4..0000000 --- a/util/pkg/auth/auth.go +++ /dev/null @@ -1,53 +0,0 @@ -package auth - -import ( - "encoding/json" - "fmt" - "os" - "strconv" - "time" - - gopaseto "aidanwoods.dev/go-paseto" - "github.com/NetSepio/erebrus/util/pkg/claims" - log "github.com/sirupsen/logrus" -) - -var PublicKey gopaseto.V4AsymmetricPublicKey -var secretKey gopaseto.V4AsymmetricSecretKey - -func Init() { - secretKey = gopaseto.NewV4AsymmetricSecretKey() - PublicKey = secretKey.Public() -} -func GenerateTokenPaseto(claim claims.CustomClaims) (string, error) { - footer := os.Getenv("FOOTER") - claimbyte, _ := json.Marshal(claim) - fmt.Println("claim value", claimbyte) - token, err := gopaseto.NewTokenFromClaimsJSON(claimbyte, []byte(footer)) - if err != nil { - return "", err - } - pasetoExpirationInHours, ok := os.LookupEnv("PASETO_EXPIRATION_IN_HOURS") - pasetoExpirationInHoursInt := time.Duration(24) - if ok { - res, err := strconv.Atoi(pasetoExpirationInHours) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - - } else { - pasetoExpirationInHoursInt = time.Duration(res) - } - } - pasetoExpirationHours := pasetoExpirationInHoursInt * time.Hour - expiration := time.Now().Add(pasetoExpirationHours) - token.SetExpiration(expiration) - signed := token.V4Sign(secretKey, nil) - return signed, nil -} - -func Getpublickey() gopaseto.V4AsymmetricPublicKey { - publickey := PublicKey - return publickey -} diff --git a/util/pkg/claims/Claim.go b/util/pkg/claims/Claim.go deleted file mode 100644 index 5ddb642..0000000 --- a/util/pkg/claims/Claim.go +++ /dev/null @@ -1,45 +0,0 @@ -package claims - -import ( - "fmt" - "os" - "strconv" - "time" - - log "github.com/sirupsen/logrus" -) - -type CustomClaims struct { - WalletAddress string `json:"walletAddress"` - SignedBy string `json:"signedBy"` - Expiration time.Time `json:"expiryTime"` -} - -func (c CustomClaims) Valid() error { - // Fetch the Key iinterface pair for custom claim and get the expiration time alog with wallet address - return nil -} - -func New(walletAddress string) CustomClaims { - pasetoExpirationInHours, ok := os.LookupEnv("PASETO_EXPIRATION_IN_HOURS") - pasetoExpirationInHoursInt := time.Duration(24) - fmt.Println("ok value walletaddress", ok) - if ok { - res, err := strconv.Atoi(pasetoExpirationInHours) - if err != nil { - log.WithFields(log.Fields{ - "err": err, - }).Error("failed to bind") - } else { - pasetoExpirationInHoursInt = time.Duration(res) - } - } - pasetoExpirationHours := pasetoExpirationInHoursInt * time.Hour - expiration := time.Now().Add(pasetoExpirationHours) - signedBy := os.Getenv("SIGNED_BY") - return CustomClaims{ - walletAddress, - signedBy, - expiration, - } -} diff --git a/util/pkg/cryptosign/checksign.go b/util/pkg/cryptosign/checksign.go deleted file mode 100644 index 704bff6..0000000 --- a/util/pkg/cryptosign/checksign.go +++ /dev/null @@ -1,51 +0,0 @@ -package cryptosign - -import ( - "errors" - "fmt" - "strings" - "time" - - "github.com/NetSepio/erebrus/api/v1/authenticate/challengeid" - "github.com/ethereum/go-ethereum/common/hexutil" - "github.com/ethereum/go-ethereum/crypto" -) - -var ( - ErrFlowIdNotFound = errors.New("flow id not found") -) - -func CheckSign(signature string, flowId string, message string) (string, bool, error) { - // get flowid from the local db file - newMsg := fmt.Sprintf("\x19Ethereum Signed Message:\n%v%v", len(message), message) - newMsgHash := crypto.Keccak256Hash([]byte(newMsg)) - signatureInBytes, err := hexutil.Decode(signature) - if err != nil { - return "", false, err - } - if signatureInBytes[64] == 27 || signatureInBytes[64] == 28 { - signatureInBytes[64] -= 27 - } - pubKey, err := crypto.SigToPub(newMsgHash.Bytes(), signatureInBytes) - - if err != nil { - return "", false, err - } - - //Get address from public key - walletAddress := crypto.PubkeyToAddress(*pubKey) - - localData, exists := challengeid.Data[flowId] - if !exists { - return "", false, ErrFlowIdNotFound - } - if time.Since(localData.Timestamp) > 1*time.Hour { - return "", false, errors.New("challenge id expired for the request") - } - if strings.EqualFold(localData.WalletAddress, walletAddress.String()) { - return localData.WalletAddress, true, nil - } else { - return "", false, nil - } //equate the wallet address from the flow id and the reeived wallet address - -} diff --git a/util/pkg/node/file.go b/util/pkg/node/file.go deleted file mode 100644 index d911f29..0000000 --- a/util/pkg/node/file.go +++ /dev/null @@ -1,69 +0,0 @@ -package node - -import ( - "fmt" - "net" - "os" - "runtime" - - "github.com/NetSepio/erebrus/core" -) - -var ( - osInfo OSInfo - ipInfo IPInfo - ipGeoData IpGeoAddress -) - -func Init() { - osInfo = OSInfo{ - Name: runtime.GOOS, - Architecture: runtime.GOARCH, - NumCPU: runtime.NumCPU(), - } - - ipGeoData = IpGeoAddress{ - IpInfoIP: core.GlobalIPInfo.IP, - IpInfoCity: core.GlobalIPInfo.City, - IpInfoCountry: core.GlobalIPInfo.Country, - IpInfoLocation: core.GlobalIPInfo.Location, - IpInfoOrg: core.GlobalIPInfo.Org, - IpInfoPostal: core.GlobalIPInfo.Postal, - IpInfoTimezone: core.GlobalIPInfo.Timezone, - } - - hostname, err := os.Hostname() - if err != nil { - fmt.Println("Error:", err) - return - } - osInfo.Hostname = hostname - - addrs, err := net.InterfaceAddrs() - if err != nil { - fmt.Println("Error:", err) - return - } - - for _, addr := range addrs { - if ipNet, ok := addr.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { - if ipNet.IP.To4() != nil { - ipInfo.IPv4Addresses = append(ipInfo.IPv4Addresses, ipNet.IP.String()) - } else if ipNet.IP.To16() != nil { - ipInfo.IPv6Addresses = append(ipInfo.IPv6Addresses, ipNet.IP.String()) - } - } - } -} - -func GetOSInfo() OSInfo { - return osInfo -} - -func GetIPInfo() IPInfo { - return ipInfo -} - -func GetIpData() IpGeoAddress { - return ipGeoData -} diff --git a/util/pkg/node/node.go b/util/pkg/node/node.go deleted file mode 100644 index cb15f0b..0000000 --- a/util/pkg/node/node.go +++ /dev/null @@ -1,127 +0,0 @@ -package node - -import ( - "encoding/json" - "fmt" - "os" - "unicode" - - "github.com/NetSepio/erebrus/core" - "github.com/NetSepio/erebrus/util/pkg/speedtest" - "github.com/sirupsen/logrus" -) - -type NodeStatus struct { - PeerId string `json:"peerId" gorm:"primaryKey"` - Name string `json:"name"` - HttpPort string `json:"httpPort"` - Host string `json:"host"` //domain - PeerAddress string `json:"peerAddress"` - Region string `json:"region"` - Status string `json:"status"` // offline 1, online 2, maintainance 3,block 4 - DownloadSpeed float64 `json:"downloadSpeed"` - UploadSpeed float64 `json:"uploadSpeed"` - RegistrationTime int64 `json:"registrationTime"` //StartTimeStamp - LastPing int64 `json:"lastPing"` - Chain string `json:"chainName"` - WalletAddress string `json:"walletAddress"` - Version string `json:"version"` - CodeHash string `json:"codeHash"` - SystemInfo string `json:"systemInfo" gorm:"type:jsonb"` - IpInfo string `json:"ipinfo" gorm:"type:jsonb"` - IpGeoData string `json:"ipGeoData" gorm:"type:jsonb"` - NodeType string `json:"nodeType"` - NodeConfig string `json:"nodeConfig"` -} - -func ToJSON(data interface{}) string { - bytes, err := json.Marshal(data) - if err != nil { - panic(err) - } - return string(bytes) -} - -// Helper function to convert JSON string to struct -func FromJSON(data string, v interface{}) error { - return json.Unmarshal([]byte(data), v) -} - -type OSInfo struct { - Name string // Name of the operating system - Hostname string // Hostname of the system - Architecture string // Architecture of the system - NumCPU int // Number of CPUs -} - -type IPInfo struct { - IPv4Addresses []string - IPv6Addresses []string -} - -type IpGeoAddress struct { - IpInfoIP string - IpInfoCity string - IpInfoCountry string - IpInfoLocation string - IpInfoOrg string - IpInfoPostal string - IpInfoTimezone string -} - -func CreateNodeStatus(address string, id string, startTimeStamp int64, name string) *NodeStatus { - - fmt.Println("Printing GetIpData : ") - fmt.Printf("%+v\n", core.GlobalIPInfo) - fmt.Println() - speedtestResult, err := speedtest.GetSpeedtestResults() - if err != nil { - logrus.Error("failed to fetch network speed: ", err.Error()) - } - IpGeoAddress := IpGeoAddress{IpInfoIP: core.GlobalIPInfo.IP, - IpInfoCity: core.GlobalIPInfo.City, - IpInfoCountry: core.GlobalIPInfo.Country, - IpInfoLocation: core.GlobalIPInfo.Location, - IpInfoOrg: core.GlobalIPInfo.Org, - IpInfoPostal: core.GlobalIPInfo.Postal, - IpInfoTimezone: core.GlobalIPInfo.Timezone} - fmt.Println("Ip Geo : ", IpGeoAddress) - - nodeStatus := &NodeStatus{ - HttpPort: os.Getenv("HTTP_PORT"), - Host: os.Getenv("DOMAIN"), - PeerAddress: address, - Region: core.GlobalIPInfo.Country, - PeerId: id, - DownloadSpeed: speedtestResult.DownloadSpeed, - UploadSpeed: speedtestResult.UploadSpeed, - RegistrationTime: startTimeStamp, - Name: name, - WalletAddress: core.WalletAddress, - Chain: core.ChainName, - Version: core.Version, - CodeHash: core.CodeHash, - SystemInfo: ToJSON(GetOSInfo()), - IpInfo: ToJSON(GetIPInfo()), - IpGeoData: ToJSON(IpGeoAddress), - NodeType: core.NodeType, - NodeConfig: core.NodeConfig, - } - - fmt.Printf("%+v\n", nodeStatus) - - return nodeStatus -} - -func MakeItString(str string) string { - - result := "" - for _, char := range str { - if unicode.IsLetter(char) { - result += string(unicode.ToLower(char)) - } else { - result += string(char) - } - } - return result -} diff --git a/util/pkg/speedtest/speedtest.go b/util/pkg/speedtest/speedtest.go deleted file mode 100644 index 0d14bbf..0000000 --- a/util/pkg/speedtest/speedtest.go +++ /dev/null @@ -1,37 +0,0 @@ -package speedtest - -import ( - "fmt" - - "github.com/showwin/speedtest-go/speedtest" -) - -type SpeedtestResult struct { - Latency string `json:"latency"` - DownloadSpeed float64 `json:"downloadSpeed"` - UploadSpeed float64 `json:"uploadSpeed"` -} - -func GetSpeedtestResults() (res *SpeedtestResult, err error) { - var speedtestClient = speedtest.New() - - serverList, _ := speedtestClient.FetchServers() - targets, _ := serverList.FindServer([]int{}) - var response *SpeedtestResult - for _, s := range targets { - // Please make sure your host can access this test server, - // otherwise you will get an error. - // It is recommended to replace a server at this time - s.PingTest(nil) - s.DownloadTest() - s.UploadTest() - fmt.Printf("Latency: %s, Download: %f, Upload: %f\n", s.Latency, s.DLSpeed, s.ULSpeed) - s.Context.Reset() // reset counter - response = &SpeedtestResult{ - Latency: s.Latency.String(), - DownloadSpeed: s.DLSpeed.Mbps(), - UploadSpeed: s.ULSpeed.Mbps(), - } - } - return response, nil -} diff --git a/util/pkg/stats/stats.go b/util/pkg/stats/stats.go deleted file mode 100644 index 63c6584..0000000 --- a/util/pkg/stats/stats.go +++ /dev/null @@ -1,75 +0,0 @@ -package stats - -import ( - "fmt" - "os/exec" - "strings" -) - -func GetWireGuardStats() (map[string]map[string]int64, error) { - cmd := exec.Command("wg", "show", "all", "transfer") - - output, err := cmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("failed to execute 'sudo wg show all transfer': %v", err) - } - - stats := make(map[string]map[string]int64) - lines := strings.Split(string(output), "\n") - for _, line := range lines { - fields := strings.Fields(line) - if len(fields) >= 4 { - peerPublicKey := fields[1] - receivedBytes := fields[2] - transmittedBytes := fields[3] - - if _, ok := stats[peerPublicKey]; !ok { - stats[peerPublicKey] = make(map[string]int64) - } - - stats[peerPublicKey]["ReceivedBytes"] = parseBytes(receivedBytes) - stats[peerPublicKey]["TransmittedBytes"] = parseBytes(transmittedBytes) - } - } - - return stats, nil -} - -type WireGuardStats struct { - Interface string - PeerPublicKey string - ReceivedBytes int64 - TransmittedBytes int64 -} - -func GetWireGuardStatsForPeer(publicKey string) (*WireGuardStats, error) { - cmd := exec.Command("wg", "show", "all", "transfer") - - output, err := cmd.CombinedOutput() - if err != nil { - return nil, fmt.Errorf("failed to execute 'sudo wg show all transfer': %v", err) - } - - lines := strings.Split(string(output), "\n") - for _, line := range lines { - fields := strings.Fields(line) - if len(fields) >= 4 && fields[1] == publicKey { - return &WireGuardStats{ - Interface: fields[0], - PeerPublicKey: fields[1], - ReceivedBytes: parseBytes(fields[2]), - TransmittedBytes: parseBytes(fields[3]), - }, nil - } - } - - return nil, fmt.Errorf("stats not found for public key: %s", publicKey) -} - -func parseBytes(bytesStr string) int64 { - var unit int64 - - fmt.Sscanf(bytesStr, "%d", &unit) - - return unit -} diff --git a/util/util.go b/util/util.go deleted file mode 100644 index 806b275..0000000 --- a/util/util.go +++ /dev/null @@ -1,197 +0,0 @@ -package util - -import ( - "crypto/rand" - "encoding/base64" - "errors" - "io/ioutil" - "net" - "os" - "regexp" - - log "github.com/sirupsen/logrus" -) - -// Erebrus Version -var Version = "1.0" - -// Hostname -var hostname, _ = os.Hostname() - -var ( - // RegexpWalletEth check valid Eth Wallet Address - RegexpWalletEth = regexp.MustCompile("^0x[a-fA-F0-9]{40}$") -) - -//add wallet regex - -// StandardFields for logger -var StandardFields = log.Fields{ - "hostname": "host-server", - "appname": "erebrus", -} - -var StandardFieldsGRPC = log.Fields{ - "hostname": hostname, - "appname": "erebrus", - "service": "gRPC", -} - -// CheckError for checking any errors -func CheckError(message string, err error) { - if err != nil { - log.WithFields(StandardFields).Fatalf("%s %+v", message, err.Error()) - } -} - -// LogError for logging any errors -func LogError(message string, err error) { - if err != nil { - log.WithFields(StandardFields).Warnf("%s %+v", message, err) - } -} - -// ReadFile file content -func ReadFile(path string) (bytes []byte, err error) { - bytes, err = ioutil.ReadFile(path) - if err != nil { - return nil, err - } - - return bytes, nil -} - -// WriteFile content to file -func WriteFile(path string, bytes []byte) (err error) { - err = ioutil.WriteFile(path, bytes, 0644) - if err != nil { - return err - } - - return nil -} - -// FileExists check if file exists -func FileExists(name string) bool { - info, err := os.Stat(name) - if os.IsNotExist(err) { - return false - } - return !info.IsDir() -} - -// DirectoryExists check if directory exists -func DirectoryExists(name string) bool { - info, err := os.Stat(name) - if os.IsNotExist(err) { - return false - } - return info.IsDir() -} - -// GetAvailableIP search for an available ip in cidr against a list of reserved ips -func GetAvailableIP(cidr string, reserved []string) (string, error) { - ip, ipnet, err := net.ParseCIDR(cidr) - if err != nil { - return "", err - } - - // this two addresses are not usable - broadcastAddr := BroadcastAddr(ipnet).String() - networkAddr := ipnet.IP.String() - - for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) { - ok := true - address := ip.String() - for _, r := range reserved { - if address == r { - ok = false - break - } - } - if ok && address != networkAddr && address != broadcastAddr { - return address, nil - } - } - - return "", errors.New("no more available address from cidr") -} - -// IsIPv6 check if given ip is IPv6 -func IsIPv6(address string) bool { - ip := net.ParseIP(address) - if ip == nil { - return false - } - return ip.To4() == nil -} - -// IsValidIP check if ip is valid -func IsValidIP(ip string) bool { - return net.ParseIP(ip) != nil -} - -// IsValidCidr check if CIDR is valid -func IsValidCidr(cidr string) bool { - _, _, err := net.ParseCIDR(cidr) - return err == nil -} - -// GetIPFromCidr get ip from cidr -func GetIPFromCidr(cidr string) (string, error) { - ip, _, err := net.ParseCIDR(cidr) - if err != nil { - return "", err - } - return ip.String(), nil -} - -// http://play.golang.org/p/m8TNTtygK0 -func inc(ip net.IP) { - for j := len(ip) - 1; j >= 0; j-- { - ip[j]++ - if ip[j] > 0 { - break - } - } -} - -// BroadcastAddr returns the last address in the given network, or the broadcast address. -func BroadcastAddr(n *net.IPNet) net.IP { - // The golang net package doesn't make it easy to calculate the broadcast address. :( - var broadcast net.IP - if len(n.IP) == 4 { - broadcast = net.ParseIP("0.0.0.0").To4() - } else { - broadcast = net.ParseIP("::") - } - for i := 0; i < len(n.IP); i++ { - broadcast[i] = n.IP[i] | ^n.Mask[i] - } - return broadcast -} - -// GenerateRandomBytes returns securely generated random bytes. -// It will return an error if the system's secure random -// number generator fails to function correctly, in which -// case the caller should not continue. -func GenerateRandomBytes(n int) ([]byte, error) { - b := make([]byte, n) - _, err := rand.Read(b) - // Note that err == nil only if we read len(b) bytes. - if err != nil { - return nil, err - } - - return b, nil -} - -// GenerateRandomString returns a URL-safe, base64 encoded -// securely generated random string. -// It will return an error if the system's secure random -// number generator fails to function correctly, in which -// case the caller should not continue. -func GenerateRandomString(s int) (string, error) { - b, err := GenerateRandomBytes(s) - return base64.URLEncoding.EncodeToString(b), err -} diff --git a/webapp/README.md b/webapp/README.md deleted file mode 100644 index f5436cf..0000000 --- a/webapp/README.md +++ /dev/null @@ -1,2 +0,0 @@ -# Erebrus Webapp -Webapp to Interact with the Anonymous Virtual Private Network Service \ No newline at end of file diff --git a/webapp/assets/css/styles.css b/webapp/assets/css/styles.css deleted file mode 100644 index f181231..0000000 --- a/webapp/assets/css/styles.css +++ /dev/null @@ -1,8 +0,0 @@ -body { - background-color: lightblue; -} - -h1 { - color: navy; - margin-left: 20px; -} \ No newline at end of file diff --git a/webapp/assets/js/index.js b/webapp/assets/js/index.js deleted file mode 100644 index f1fa67c..0000000 --- a/webapp/assets/js/index.js +++ /dev/null @@ -1 +0,0 @@ -// JavaScript Here \ No newline at end of file diff --git a/webapp/docs/index.html b/webapp/docs/index.html deleted file mode 100644 index 90f2f26..0000000 --- a/webapp/docs/index.html +++ /dev/null @@ -1,498 +0,0 @@ - - - - - - Erebrus - - - - - - - - - -

Erebrus (1.0.0)

Download OpenAPI specification:Download

Sambath Kumar: sachinmugu@gmail.com License: GPL-3.0

Erebrus is an open source VPN solution from The NetSepio, that helps to deploy your own VPN solution in -minutes.The vision of Erebrus is to deliver Cyber security to everyone .

-

Features of Erebrus were, Easy Client and Server management, Supports REST and gRPC, Email VPN configuration to clients easily.

-

This documentation guides you, How to use Erebrus endpoints and It's Request and Response briefly.

-

Client

Read All Clients

Get all clients in the server.

-

Responses

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 201,
  • "Sucess": true,
  • "clients": [
    ]
}

Create client

Create client based on the given client model.

-
Request Body schema:

Requestbody used for create and update client operations.

-
address
required
Array of strings

Address range client must will assigned

-
allowedIPs
required
Array of strings

IP addresses allowed to connect

-
createdBy
required
string

Denoting person creates the client

-
email
required
string

Email that the client device belongs

-
enable
required
boolean

Status signal for client

-
name
required
string
tags
required
Array of strings

Tags for client device

-
updatedBy
required
string

Denoting person updates the client

-

Responses

Request samples

Content type
{
  • "address": [
    ],
  • "allowedIPs": [
    ],
  • "createdBy": "jonsnow@mail.com",
  • "email": "jonsnow@mail.com",
  • "enable": true,
  • "name": "jon snow",
  • "tags": [
    ],
  • "updatedBy": "jonsnow@mail.com"
}

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 201,
  • "Sucess": true,
  • "client": {
    }
}

Delete client

Delete client based on the given uuid.

-
path Parameters
id
required
string

The Identifier of the Client

-

Responses

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 200,
  • "Sucess": true
}

Read client

Return client based on the given uuid.

-
path Parameters
id
required
string

The Identifier of the Client

-

Responses

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 201,
  • "Sucess": true,
  • "client": {
    }
}

Update client

Update client based on the given uuid and client model.

-
path Parameters
id
required
string

The Identifier of the Client

-
Request Body schema:

Requestbody used for create and update client operations.

-
address
required
Array of strings

IP addresses allowed to connect

-
allowedIPs
required
Array of strings

IP addresses allowed to connect

-
created
integer <int64>

Time the client is created

-
createdBy
string

Denoting person creates the client

-
email
required
string

Email that the client device belongs

-
enable
required
boolean

Status signal for client

-
ignorePersistentKeepalive
boolean
name
required
string

Name of the client

-
presharedKey
string

Preshared key for the client

-
privateKey
string

Private key for the client

-
publicKey
string

Public key for the client

-
tags
required
Array of strings

Tags for client device

-
updated
integer <int64>

Time the client is last updated

-
updatedBy
required
string

Denoting person updates the client

-
uuid
required
string

Client identifier

-

Responses

Request samples

Content type
{
  • "address": [
    ],
  • "allowedIPs": [
    ],
  • "created": 1642409076544,
  • "createdBy": "jonsnow@mail.com",
  • "email": "jonsnow@mail.com",
  • "enable": true,
  • "ignorePersistentKeepalive": true,
  • "name": "jon snow",
  • "presharedKey": "twDZk0lehYtst3Zclb+SRniVfoHnug9N6gjxuaipcvc=",
  • "privateKey": "KFOyCoR9Eq+LpqT9VzJCilXYmFwhMFw7UDkdRRxoWVg=",
  • "publicKey": "YeT/lG9L4AeYOHNrkohnmXfljx3/JgThulskllayxi4=",
  • "tags": [
    ],
  • "updated": 1642409076544,
  • "updatedBy": "jonsnow@mail.com",
  • "uuid": "6c8ff96f-ce8a-4c64-a76d-07e9af0b75ab"
}

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 201,
  • "Sucess": true,
  • "client": {
    }
}

Get client configuration

Return client configuration file in byte format based on the given uuid.

-
path Parameters
id
required
string

The Identifier of the Client

-

Responses

Response samples

Content type
{
  • "content": "File Download"
}

Email client Configuration

Email the configuration file of the client to the email associated with client.

-
path Parameters
id
required
string

The Identifier of the Client

-

Responses

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 200,
  • "Sucess": true
}

Server

Read Server

Retrieves the server details.

-

Responses

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 201,
  • "Sucess": true,
  • "server": {
    }
}

Update Server

Update the server with given details.

-
Request Body schema:

Requestbody used for update server operations.

-
address
Array of strings

Server address

-
allowedips
Array of strings

IP addresses allowed to connect

-
created
integer <int64>

Time when server is created

-
dns
Array of strings

DNS of the VPN server

-
endpoint
string

Endpoint of the server

-
listenPort
integer <int64>

Port the server listens

-
mtu
integer <int64>
persistentKeepalive
integer <int64>

Persistent keep alive for server

-
postDown
string

Post down command

-
postUp
string

Post up command

-
preDown
string

Pre down command

-
preUp
string

Pre up command

-
privateKey
string

Private key for the server

-
publicKey
string

Public key for the server

-
updated
integer <int64>

Time when server is created

-
updatedBy
string

Updater email address

-

Responses

Request samples

Content type
{
  • "address": [
    ],
  • "allowedips": [
    ],
  • "created": 26103870,
  • "dns": [
    ],
  • "endpoint": "region.example.com",
  • "listenPort": 51280,
  • "mtu": 0,
  • "persistentKeepalive": 16,
  • "postDown": "iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE",
  • "postUp": "iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE",
  • "preDown": "echo WireGuard PreDown",
  • "preUp": "echo WireGuard PreUp",
  • "privateKey": "UFWsgb/Ax5B8zZGx0YtHBAuQVRrOHrxKz2zS2p1LuUE=",
  • "publicKey": "T5ZMOnik3YuaRhZgAhcxXrmn2+C0B7qFaqnCypMMcks=",
  • "updated": 26103870,
  • "updatedBy": "admin@mail.com"
}

Response samples

Content type
{
  • "Message": "sucess message",
  • "Status": 201,
  • "Sucess": true,
  • "server": {
    }
}

configServer

Get Server Configuration -Retrieves the server configuration details.

-

Responses

Response samples

Content type
{
  • "content": "File Download"
}

Get Server status

Retrieves the server status details.

-

Responses

Response samples

Content type
{
  • "Domain": "vpn.example.com",
  • "Hostname": "ubuntu",
  • "HttpPort": "4000",
  • "PrivateIP": "10.0.1.5",
  • "PublicIP": "14.10.35.65",
  • "Region": "India/Banglore",
  • "VPNPort": "5128",
  • "Version": "1.0",
  • "gRPCPort": "5000"
}
- - - - \ No newline at end of file diff --git a/webapp/favicon.png b/webapp/favicon.png deleted file mode 100644 index 2844ea6..0000000 Binary files a/webapp/favicon.png and /dev/null differ diff --git a/webapp/index.html b/webapp/index.html deleted file mode 100644 index 1b07d16..0000000 --- a/webapp/index.html +++ /dev/null @@ -1,27 +0,0 @@ - - - - Erebrus | NetSepio - - - - - - - - - - - -
-

NetSepio - Erebrus

-

- Anonymous Virtual Private Network for accessing internet in stealth mode - bypassing filewalls and filters -

-
- - diff --git a/wg-watcher.path b/wg-watcher.path deleted file mode 100644 index 68a86ab..0000000 --- a/wg-watcher.path +++ /dev/null @@ -1,9 +0,0 @@ -# /etc/systemd/system/wg-watcher.path -[Unit] -Description=Watch /etc/wireguard for changes - -[Path] -PathModified=/etc/wireguard - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/wg-watcher.service b/wg-watcher.service deleted file mode 100644 index f6101b2..0000000 --- a/wg-watcher.service +++ /dev/null @@ -1,11 +0,0 @@ -# /etc/systemd/system/wg-watcher.service -[Unit] -Description=WireGuard directory watcher -After=network.target - -[Service] -Type=oneshot -ExecStart=/bin/systemctl restart wg-quick@wg0.service - -[Install] -WantedBy=multi-user.target \ No newline at end of file diff --git a/wg-watcher.sh b/wg-watcher.sh deleted file mode 100644 index 45a4947..0000000 --- a/wg-watcher.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -while inotifywait -e modify -e create /etc/wireguard; do - wg-quick down wg0 - wg-quick up wg0 -done