diff --git a/Dockerfile b/Dockerfile index ca427af2..17f7a730 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,12 +34,31 @@ RUN apk add --no-cache \ iptables-legacy \ wireguard-tools +# Download and instll Go +RUN wget https://go.dev/dl/go1.22.5.linux-amd64.tar.gz &&\ + tar -C /usr/local -xzf go1.22.5.linux-amd64.tar.gz &&\ + rm go1.22.5.linux-amd64.tar.gz + + +# Configure Go +RUN echo 'export PATH=$PATH:/usr/local/go/bin' >> /etc/profile &&\ + echo 'export GOPATH=$HOME/goproject' >> /etc/profile &&\ + echo 'export PATH=$PATH:$GOPATH/bin' >> /etc/profile &&\ + source /etc/profile &&\ + mkdir $HOME/goproject + +# Set Go Environment +ENV PATH=$PATH:/usr/local/go/bin +ENV GOPATH=/root/goproject +ENV PATH=$PATH:$GOPATH/bin + # Use iptables-legacy RUN update-alternatives --install /sbin/iptables iptables /sbin/iptables-legacy 10 --slave /sbin/iptables-restore iptables-restore /sbin/iptables-legacy-restore --slave /sbin/iptables-save iptables-save /sbin/iptables-legacy-save -# Set Environment +# Set WG Environment ENV DEBUG=Server,WireGuard -# Run Web UI +# Start cron in the background and then start the main application WORKDIR /app -CMD ["/usr/bin/dumb-init", "node", "server.js"] +CMD ["/usr/bin/dumb-init",\ + "/bin/sh", "-c", "exec node server.js & watch -n 86400 go run ./services/ClientSwitcher.go ${WG_HOST} ${PORT} ${PASSWORD}"] diff --git a/docker-compose.yml b/docker-compose.yml index 7a6a4f49..611a9510 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -29,7 +29,7 @@ services: # - UI_TRAFFIC_STATS=true # - UI_CHART_TYPE=0 # (0 Charts disabled, 1 # Line chart, 2 # Area chart, 3 # Bar chart) - image: ghcr.io/wg-easy/wg-easy + image: ghcr.io/amarseillaise/wg-easy-amars container_name: wg-easy volumes: - etc_wireguard:/etc/wireguard diff --git a/src/lib/Server.js b/src/lib/Server.js index 40341ee5..3d0bc0e2 100644 --- a/src/lib/Server.js +++ b/src/lib/Server.js @@ -223,6 +223,15 @@ module.exports = class Server { await WireGuard.disableClient({ clientId }); return { success: true }; })) + .put('/api/wireguard/client/:clientId/enableduntil', defineEventHandler(async (event) => { + const clientId = getRouterParam(event, 'clientId'); + if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { + throw createError({ status: 403 }); + } + const { date } = await readBody(event); + await WireGuard.changeEnabledUntilDate({ clientId, date }); + return { success: true }; + })) .put('/api/wireguard/client/:clientId/name', defineEventHandler(async (event) => { const clientId = getRouterParam(event, 'clientId'); if (clientId === '__proto__' || clientId === 'constructor' || clientId === 'prototype') { diff --git a/src/lib/WireGuard.js b/src/lib/WireGuard.js index d4f32c5f..d501b519 100644 --- a/src/lib/WireGuard.js +++ b/src/lib/WireGuard.js @@ -136,6 +136,9 @@ ${client.preSharedKey ? `PresharedKey = ${client.preSharedKey}\n` : '' id: clientId, name: client.name, enabled: client.enabled, + enabledUntil: client.enabledUntil !== null + ? new Date(client.enabledUntil) + : null, address: client.address, publicKey: client.publicKey, createdAt: new Date(client.createdAt), @@ -261,6 +264,7 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; updatedAt: new Date(), enabled: true, + enabledUntil: null }; config.clients[id] = client; @@ -297,6 +301,14 @@ Endpoint = ${WG_HOST}:${WG_CONFIG_PORT}`; await this.saveConfig(); } + async changeEnabledUntilDate({ clientId, date }) { + const client = await this.getClient({ clientId }); + const dateObj = new Date(date); + client.enabledUntil = dateObj; + + await this.saveConfig(); + } + async updateClientName({ clientId, name }) { const client = await this.getClient({ clientId }); diff --git a/src/services/ClientSwitcher.go b/src/services/ClientSwitcher.go new file mode 100644 index 00000000..7cdf7221 --- /dev/null +++ b/src/services/ClientSwitcher.go @@ -0,0 +1,114 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/cookiejar" + "os" + "strings" + "time" +) + +type Client struct { + ID string `json:"id"` + Enabled bool `json:"enabled"` + EnabledUntil time.Time `json:"enabledUntil"` +} + +const ( + Api = "api" + Wireguard = "wireguard" + ClientApi = "client" + Session = "session" +) + +func makeRequest(client *http.Client, url string, method string) *http.Response { + request, _ := http.NewRequest(method, url, nil) + response, err := client.Do(request) + if err != nil || response.StatusCode != 200 { + fmt.Println(response) + panic(response) + } + return response +} + +func initClients(resp *http.Response, clients *[]Client) error { + defer resp.Body.Close() + return json.NewDecoder(resp.Body).Decode(clients) +} + +func switchClientsStatus(httpClient *http.Client, clients []Client) { + today := time.Now() + for _, client := range clients { + if client.EnabledUntil.Before(today) { + switchStatus(httpClient, client.ID, "disable") + } else if client.EnabledUntil.After(today) { + switchStatus(httpClient, client.ID, "enable") + } + } +} + +func switchStatus(client *http.Client, clientID, status string) { + url := strings.Join([]string{getHost(), Api, Wireguard, ClientApi, clientID, status}, "/") + makeRequest(client, url, "POST") +} + +func checkAuth(client *http.Client) bool { + type AuthChecker struct { + RequiresPassword bool `json:"requiresPassword"` + Authenticated bool `json:"authenticated"` + } + var auth AuthChecker + + url := strings.Join([]string{getHost(), Api, Session}, "/") + response := makeRequest(client, url, "GET") + json.NewDecoder(response.Body).Decode(&auth) + result := (auth.RequiresPassword && auth.Authenticated) || (!auth.RequiresPassword && auth.Authenticated) + return result +} + +func authenticate(client *http.Client) { + args := os.Args[1:] + type Password struct { + Password string `json:"password"` + } + + password := Password{ + Password: args[2], + } + url := strings.Join([]string{getHost(), Api, Session}, "/") + passwdJson, _ := json.Marshal(password) + + req, _ := http.NewRequest("POST", url, bytes.NewBuffer(passwdJson)) + req.Header.Set("Content-Type", "application/json") + + resp, err := client.Do(req) + if err != nil || resp.StatusCode != 200 { + fmt.Println(resp) + panic(resp) + } + defer resp.Body.Close() +} + +func getHost() string { + args := os.Args[1:] + host := args[0] + ":" + args[1] + return host +} + +func main() { + getClientsUrl := strings.Join([]string{getHost(), Api, Wireguard, ClientApi}, "/") + var clients []Client + jar, _ := cookiejar.New(nil) + client := &http.Client{Jar: jar} + if !checkAuth(client) { + authenticate(client) + } + + resp := makeRequest(client, getClientsUrl, "GET") + + initClients(resp, &clients) + switchClientsStatus(client, clients) +} diff --git a/src/www/css/app.css b/src/www/css/app.css index ead64396..2c80cfd4 100644 --- a/src/www/css/app.css +++ b/src/www/css/app.css @@ -1686,6 +1686,10 @@ video { font-size: 1rem; line-height: 1.5rem; } + .md\:text-base-small { + font-size: 0.9rem; + line-height: 1.1rem; + } } .dark\:border-neutral-500:where(.dark, .dark *) { diff --git a/src/www/index.html b/src/www/index.html index 72044728..6444bad8 100644 --- a/src/www/index.html +++ b/src/www/index.html @@ -271,7 +271,13 @@