├── .env ├── .github ├── dependabot.yml └── workflows │ ├── build-manager-dev.yml │ ├── build-manager.yml │ └── build-worker.yml ├── .gitignore ├── .vscode ├── config.json └── settings.json ├── LICENSE ├── README.md ├── certs ├── ca-cert.pem ├── ca-cert.srl ├── ca-key.pem ├── manager │ ├── ca-cert.pem │ ├── cert.pem │ ├── csr.pem │ └── key.pem └── worker │ ├── ca-cert.pem │ ├── cert.pem │ ├── csr.pem │ └── key.pem ├── docker-compose-test.yml ├── docker-compose.yml ├── docs ├── docs.go ├── swagger.json └── swagger.yaml ├── examples ├── scriptExample.sh ├── scriptExample1command.sh └── scriptExampleAdvanced.sh ├── generateCert.sh ├── globalstructs └── struct.go ├── go.mod ├── go.sum ├── main.go ├── manager.conf ├── manager ├── Dockerfile ├── api │ ├── API.go │ ├── APItask.go │ └── APIworkers.go ├── database │ ├── DB.go │ ├── DBtask.go │ └── DBworkers.go ├── manager.go ├── sshTunnel │ └── sshTunnel.go ├── utils │ ├── disk.go │ ├── manageTasks.go │ ├── stats.go │ ├── structs.go │ ├── userRequest.go │ └── workerRequest.go └── websockets │ └── websockets.go ├── output └── .gitkeep ├── resources ├── nTask-diagram.drawio ├── nTask-diagram.png ├── nTask-small.png ├── nTask-swagger-addTask.png ├── nTask-swagger-functions.png ├── nTask-swagger-getTasks.png ├── nTask-swagger-status.png └── nTask.png ├── ssh.conf ├── worker.conf └── worker ├── Dockerfile ├── managerrequest └── managerRequest.go ├── modules ├── module1.py ├── modules.go └── nmapIPs.sh ├── process └── processTask.go ├── utils ├── structs.go └── utils.go ├── websockets └── websockets.go └── worker.go /.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/.env -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/build-manager-dev.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build and Publish Manager DEV 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'dev' 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Set up QEMU 13 | uses: docker/setup-qemu-action@v1 14 | 15 | - name: Set up Docker Buildx 16 | uses: docker/setup-buildx-action@v1 17 | 18 | - name: Checkout 19 | uses: actions/checkout@v3 20 | 21 | # manager docker 22 | - name: Extract metadata (tags, labels) for Docker 23 | id: meta 24 | uses: docker/metadata-action@v5 25 | with: 26 | images: ${{ github.actor }}/ntask-manager 27 | 28 | - name: Login to DockerHub 29 | uses: docker/login-action@v1 30 | with: 31 | username: ${{ github.actor }} 32 | password: ${{ secrets.DOCKERHUB_TOKEN }} 33 | - name: Build and push docker 34 | id: docker_build 35 | uses: docker/build-push-action@v2 36 | with: 37 | context: . 38 | file: ./manager/Dockerfile 39 | platforms: linux/amd64 40 | push: true 41 | tags: ${{ secrets.DOCKERHUB_USERNAME }}/ntask-manager:dev 42 | 43 | - name: Login to GitHub Container Registry 44 | run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin 45 | 46 | - name: Build and push docker 47 | id: docker_build_ghcr 48 | uses: docker/build-push-action@v5 49 | with: 50 | context: . 51 | file: ./manager/Dockerfile 52 | platforms: linux/amd64 53 | push: true 54 | tags: ghcr.io/${{ github.actor }}/ntask-manager:dev 55 | 56 | -------------------------------------------------------------------------------- /.github/workflows/build-manager.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build and Publish Manager 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v1 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | # manager docker 24 | - name: Extract metadata (tags, labels) for Docker 25 | id: meta 26 | uses: docker/metadata-action@v5 27 | with: 28 | images: ${{ github.actor }}/ntask-manager 29 | 30 | - name: Login to DockerHub 31 | uses: docker/login-action@v1 32 | with: 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.DOCKERHUB_TOKEN }} 35 | - name: Build and push docker 36 | id: docker_build 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: . 40 | file: ./manager/Dockerfile 41 | platforms: linux/amd64 42 | push: true 43 | tags: ${{ steps.meta.outputs.tags }} 44 | labels: ${{ steps.meta.outputs.labels }} 45 | 46 | - name: Login to GitHub Container Registry 47 | run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin 48 | 49 | - name: Build and push docker 50 | id: docker_build_ghcr 51 | uses: docker/build-push-action@v5 52 | with: 53 | context: . 54 | file: ./manager/Dockerfile 55 | platforms: linux/amd64 56 | push: true 57 | tags: ghcr.io/${{ github.actor }}/ntask-manager:latest, ghcr.io/${{ steps.meta.outputs.tags }} 58 | labels: ${{ steps.meta.outputs.labels }} 59 | -------------------------------------------------------------------------------- /.github/workflows/build-worker.yml: -------------------------------------------------------------------------------- 1 | name: Docker Build and Publish Worker 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | tags: 8 | - 'v*' 9 | 10 | jobs: 11 | docker: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v1 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v3 22 | 23 | # worker docker 24 | - name: Extract metadata (tags, labels) for Docker 25 | id: meta 26 | uses: docker/metadata-action@v5 27 | with: 28 | images: ${{ github.actor }}/ntask-worker 29 | 30 | - name: Login to DockerHub 31 | uses: docker/login-action@v1 32 | with: 33 | username: ${{ github.actor }} 34 | password: ${{ secrets.DOCKERHUB_TOKEN }} 35 | - name: Build and push docker 36 | id: docker_build 37 | uses: docker/build-push-action@v2 38 | with: 39 | context: . 40 | file: ./worker/Dockerfile 41 | platforms: linux/amd64 42 | push: true 43 | tags: ${{ steps.meta.outputs.tags }} 44 | labels: ${{ steps.meta.outputs.labels }} 45 | 46 | - name: Login to GitHub Container Registry 47 | run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin 48 | 49 | - name: Build and push docker 50 | id: docker_build_ghcr 51 | uses: docker/build-push-action@v5 52 | with: 53 | context: . 54 | file: ./worker/Dockerfile 55 | platforms: linux/amd64 56 | push: true 57 | tags: ghcr.io/${{ github.actor }}/ntask-worker:latest, ghcr.io/${{ steps.meta.outputs.tags }} 58 | labels: ${{ steps.meta.outputs.labels }} 59 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | db/* 23 | output/* 24 | -------------------------------------------------------------------------------- /.vscode/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.useLanguageServer": true, 3 | "go.lintTool": "golangci-lint", 4 | "go.lintOnSave": "package", 5 | "editor.codeActionsOnSave": { 6 | "source.organizeImports": true, 7 | "source.fixAll": true 8 | }, 9 | "go.formatTool": "goimports", 10 | "go.gopath": "${workspaceFolder}", 11 | "go.goroot": "", 12 | "go.toolsEnvVars": {"GO111MODULE": "on"}, 13 | "go.testFlags": ["-v"], 14 | "go.toolsManagement.autoUpdate": true, 15 | "go.autocompleteUnimportedPackages": true, 16 | "go.inferGopath": true, 17 | "go.buildOnSave": "workspace" 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "go.useLanguageServer": true, 3 | "go.lintTool": "golangci-lint", 4 | "go.lintOnSave": "package", 5 | "editor.codeActionsOnSave": { 6 | "source.organizeImports": true, 7 | "source.fixAll": true 8 | }, 9 | "go.formatTool": "goimports", 10 | "go.gopath": "${workspaceFolder}", 11 | "go.goroot": "", 12 | "go.toolsEnvVars": {"GO111MODULE": "on"}, 13 | "go.testFlags": ["-v"], 14 | "go.toolsManagement.autoUpdate": true, 15 | "go.autocompleteUnimportedPackages": true, 16 | "go.inferGopath": true, 17 | "go.buildOnSave": "workspace" 18 | } 19 | -------------------------------------------------------------------------------- /certs/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDIzCCAgugAwIBAgIUc3Jh+/9Xusdd/DpNTBkSNFjpWkkwDQYJKoZIhvcNAQEL 3 | BQAwITEOMAwGA1UEAwwFblRhc2sxDzANBgNVBAoMBnI0dWxjbDAeFw0yNDAxMzAx 4 | OTM0MDNaFw0yNDAyMjkxOTM0MDNaMCExDjAMBgNVBAMMBW5UYXNrMQ8wDQYDVQQK 5 | DAZyNHVsY2wwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHD7yHomPx 6 | V3ASuaGT+5s75Yza2HmnpbZ4VvOGI32NHSn7OkLJWSr6IHjzlqrBrk4JnJce6uyk 7 | /cN5EdzU+f8fgYMqFIf6JZRc+/H6ZPvYH/rH9nyuQhHs/oIQgxZTbvitYZ5g1kpC 8 | U2dh8Yu7x2EAW57Qq3q35/06iUYS1oueW8WQ6OILQZS3Vv6GuBfoXNXa8hFKIBHj 9 | 503HK2fuyUEKU6uKX5bKRVhhEiVplNTRxFE3il+uOOdWi6naG/Bseid1VP8j2ihB 10 | feK4r9n/HCNmmTJBdW+MObpG2OmmiiPyrgUaRiK3I0nqh2ucsPYzgSCbTb3YcewL 11 | 5L66qQ5haqY1AgMBAAGjUzBRMB0GA1UdDgQWBBTeuWAvF4bTXyok6li/DtuOZorb 12 | KTAfBgNVHSMEGDAWgBTeuWAvF4bTXyok6li/DtuOZorbKTAPBgNVHRMBAf8EBTAD 13 | AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCqdGO4Q9d+Bns/S94bMJ/5ybhZ3UjDqkM8 14 | O20Afqxjv53mPVVEU91Sz+xBCmpkCChnnGY3RdEhXX383GGNRq/2jUYkbk+gILVd 15 | mkVnBRUBySvcHm7Asoj6xVAsml9NJS7xmP9OYXnXJCJ5jUGpqGM/XzvGiMzqhumG 16 | U0C0Pjfs7eUBKnUDz7mVS+ucagATd1tSIxupqpIiRXAnlDe0pkBkHVhhEz55SZ7r 17 | l78Jxf9y5AenyXngf/2f5+8npzoDU73pt3N2gSsa6P3d3E9Rfpyh1az69/uCu8Kx 18 | CLhMAJLGOL3jAtQ9pESQVAT+wTod47NMN7d6oKqYvlwuKRnaynVl 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /certs/ca-cert.srl: -------------------------------------------------------------------------------- 1 | 360BEDFD841BFB41D34056C05D30992B5F9823D7 2 | -------------------------------------------------------------------------------- /certs/ca-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDHD7yHomPxV3AS 3 | uaGT+5s75Yza2HmnpbZ4VvOGI32NHSn7OkLJWSr6IHjzlqrBrk4JnJce6uyk/cN5 4 | EdzU+f8fgYMqFIf6JZRc+/H6ZPvYH/rH9nyuQhHs/oIQgxZTbvitYZ5g1kpCU2dh 5 | 8Yu7x2EAW57Qq3q35/06iUYS1oueW8WQ6OILQZS3Vv6GuBfoXNXa8hFKIBHj503H 6 | K2fuyUEKU6uKX5bKRVhhEiVplNTRxFE3il+uOOdWi6naG/Bseid1VP8j2ihBfeK4 7 | r9n/HCNmmTJBdW+MObpG2OmmiiPyrgUaRiK3I0nqh2ucsPYzgSCbTb3YcewL5L66 8 | qQ5haqY1AgMBAAECggEALwl65RnsP9UHeIVAtvUXQ1oEpJnOdVzk9x6kwKeWPUgM 9 | 6X5k1asqSpxtuDF7+/QyIHdOBlJAxOPp0qvz4KeKL2mtEr5zOxqyKh6mmSJPmExG 10 | OAX4hDy8e6HQHhK7rc9lF6Mfh4ZbWbzXiv9Go4KDW1BLAMfkYZyB69kQI9dqemqN 11 | wVqqjR/0NNSaUIPommCfjl5fxgO+T62cqQxIT1QG73Mml4gXcoAE5xBGeKf+hFxF 12 | 9OckYVRL7KVrN+I8SFu7qbHUV8WQfUC0EtFYrvbsfH5dmddlTj73JprZdtap3JEQ 13 | zvnt/NCYF/SY3NKEkO0eU8Ni+lHdmAXvYwX5xcWOwQKBgQDKu+OeQ53YgDAxPWBL 14 | AvywfNsWBGAPmUFuerVF6qYMp+BTtBa8CHcEA0gSYjaGuagce/hppjMFLivNM9WP 15 | kN9PuvSKfElQz8i4Fl7RXHv8h6zGXnu5gVnJ/r8wcG6kA6Kl4qiKiNYCU1GV199B 16 | UcO8NI0507B4uhC2GQkAfVxdWQKBgQD7XNUxqk4diY/jMC3TeHtS+hP4dKxU4+zZ 17 | 3ZatNtDxFCh066GkqFth52hRPH3TsT2o3EiTM2wftCwF0skabPqOoxwEN4URMLAW 18 | C1TYGEzYKNYaWh1mNBDQe6TyafrSooX6A25uk4/Ap7JmSv7Ysn/PT0hbDzjwOuyK 19 | 4Vixa5aoPQKBgEQ0Mb9swA22EoB+RYb22kwFtS8TCb41sO2aGqIK7xIS6EVAsOVR 20 | c7jF4dlNcUqh6wyqKEhiwYdcoR/H8HD8LCSGoP52EbQ+Myi7XerRUmUCv/18i+M8 21 | wRhTu75wFMjY8D8eodT5dAYUQb5HgbRX7aHDjD+IGDaFYlng0kZ35jsBAoGBAMxG 22 | aUvvZ4RBoxmysctGApMwgMJNry9d+8Iifq1N+wewpiA+ziKOX5V1BiXezzMWu1Fb 23 | k+9svtYVCiHBZ4V+QzFgBQi4Rf/uXWvM0aq8NNcGeNj5myLP9Uo48Ze/4QME6XSB 24 | DWH3sb+TiTvwfqOEjLHhcJ/wAwnYGRvUfsvQ76LRAoGAEiARGYR70ciGCYfKVVSI 25 | 25bu5yKmfL4xDRKKwslww+vwxbZXD4W92ymxGRTNTD1zEP8LRnZUzT0FX0DiLJlu 26 | 2mZ4w6WWAsNodY/rDXaWHXQ08839i5x0fdIVdYhwTsnKJ8/+NTZxV27bGasrj5Cy 27 | 5U0ohG7Xv5iEq4T+or4hVWQ= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /certs/manager/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDIzCCAgugAwIBAgIUc3Jh+/9Xusdd/DpNTBkSNFjpWkkwDQYJKoZIhvcNAQEL 3 | BQAwITEOMAwGA1UEAwwFblRhc2sxDzANBgNVBAoMBnI0dWxjbDAeFw0yNDAxMzAx 4 | OTM0MDNaFw0yNDAyMjkxOTM0MDNaMCExDjAMBgNVBAMMBW5UYXNrMQ8wDQYDVQQK 5 | DAZyNHVsY2wwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHD7yHomPx 6 | V3ASuaGT+5s75Yza2HmnpbZ4VvOGI32NHSn7OkLJWSr6IHjzlqrBrk4JnJce6uyk 7 | /cN5EdzU+f8fgYMqFIf6JZRc+/H6ZPvYH/rH9nyuQhHs/oIQgxZTbvitYZ5g1kpC 8 | U2dh8Yu7x2EAW57Qq3q35/06iUYS1oueW8WQ6OILQZS3Vv6GuBfoXNXa8hFKIBHj 9 | 503HK2fuyUEKU6uKX5bKRVhhEiVplNTRxFE3il+uOOdWi6naG/Bseid1VP8j2ihB 10 | feK4r9n/HCNmmTJBdW+MObpG2OmmiiPyrgUaRiK3I0nqh2ucsPYzgSCbTb3YcewL 11 | 5L66qQ5haqY1AgMBAAGjUzBRMB0GA1UdDgQWBBTeuWAvF4bTXyok6li/DtuOZorb 12 | KTAfBgNVHSMEGDAWgBTeuWAvF4bTXyok6li/DtuOZorbKTAPBgNVHRMBAf8EBTAD 13 | AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCqdGO4Q9d+Bns/S94bMJ/5ybhZ3UjDqkM8 14 | O20Afqxjv53mPVVEU91Sz+xBCmpkCChnnGY3RdEhXX383GGNRq/2jUYkbk+gILVd 15 | mkVnBRUBySvcHm7Asoj6xVAsml9NJS7xmP9OYXnXJCJ5jUGpqGM/XzvGiMzqhumG 16 | U0C0Pjfs7eUBKnUDz7mVS+ucagATd1tSIxupqpIiRXAnlDe0pkBkHVhhEz55SZ7r 17 | l78Jxf9y5AenyXngf/2f5+8npzoDU73pt3N2gSsa6P3d3E9Rfpyh1az69/uCu8Kx 18 | CLhMAJLGOL3jAtQ9pESQVAT+wTod47NMN7d6oKqYvlwuKRnaynVl 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /certs/manager/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDNDCCAhygAwIBAgIUE0IqD/pf7nIBEpWUjkdfS+QQjrowDQYJKoZIhvcNAQEL 3 | BQAwITEOMAwGA1UEAwwFblRhc2sxDzANBgNVBAoMBnI0dWxjbDAeFw0yNDAxMzAx 4 | OTM0MDNaFw0yNTAxMjkxOTM0MDNaMCMxEDAOBgNVBAMMB01hbmFnZXIxDzANBgNV 5 | BAoMBnI0dWxjbDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALzpBMjG 6 | KO7CNOSBJr+JYjZF5tNGU2GWiGwg8YTpxiu/wd9hM7laroZhIXGtiBQecqC5JFyR 7 | BGji5s7FjTI0mOGkettwX3YZGLrokIJu4tG7qBYmeSctvPtybcMgdTQSXsOpgcKP 8 | MEWfEUrL5Hl4RWiPOM57o79wbh15A+AMgOjCp15lJXZI6DxC3F5/Nh5X9SQ6Fz5H 9 | uKvG7cRll2j5f8eq2Zup2m6/vaRt3AgqQKuWoJJBYO4NIyKKyuWlyEnNmsraNSXw 10 | GQp/+GWJgUnEZUso9hPZWCIOfNFQW0WvbDd7x9BoLL1sa1j3g1h9m5UJO1J6mgPU 11 | ltwi23jxNyM4RuUCAwEAAaNiMGAwHgYDVR0RBBcwFYcEfwAAAYINbWFuYWdlci5s 12 | b2NhbDAdBgNVHQ4EFgQUri9jEXH/otROYllQJE+VXpgQbZMwHwYDVR0jBBgwFoAU 13 | 3rlgLxeG018qJOpYvw7bjmaK2ykwDQYJKoZIhvcNAQELBQADggEBAFVP9Q/vCaND 14 | JAO5qW5Lqv30H/3xERe2noLR+WnAZTjvdU2OMeDKRKbIKFJfan89+rsGc7IwjhZH 15 | u36sMs8Ql9zKgfdRx+a5z/LkuInID121A8C/nukXnJij2Cm3yNyHo531UYmZfT0p 16 | IwsxljLq60lBDvGipHsfVlbIPfIVYW1Aj68xfOdHxdn0WnCwdTsgg9m1mnRdJOG/ 17 | tbWw1QdECgMOeIfDXTyScdlzrOWiw0liFxcfrtvzwYAHrVPld1Jd5Js+0bms7SWp 18 | zCzFbzt/Kf0t6iDc488tpZHlFj+0svWs7MbvOwHi0bNe8l8OgApCFsheeTjGO8IX 19 | V/gY6zSWf+M= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /certs/manager/csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICmTCCAYECAQAwIzEQMA4GA1UEAwwHTWFuYWdlcjEPMA0GA1UECgwGcjR1bGNs 3 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvOkEyMYo7sI05IEmv4li 4 | NkXm00ZTYZaIbCDxhOnGK7/B32EzuVquhmEhca2IFB5yoLkkXJEEaOLmzsWNMjSY 5 | 4aR623BfdhkYuuiQgm7i0buoFiZ5Jy28+3JtwyB1NBJew6mBwo8wRZ8RSsvkeXhF 6 | aI84znujv3BuHXkD4AyA6MKnXmUldkjoPELcXn82Hlf1JDoXPke4q8btxGWXaPl/ 7 | x6rZm6nabr+9pG3cCCpAq5agkkFg7g0jIorK5aXISc2ayto1JfAZCn/4ZYmBScRl 8 | Syj2E9lYIg580VBbRa9sN3vH0GgsvWxrWPeDWH2blQk7UnqaA9SW3CLbePE3IzhG 9 | 5QIDAQABoDEwLwYJKoZIhvcNAQkOMSIwIDAeBgNVHREEFzAVhwR/AAABgg1tYW5h 10 | Z2VyLmxvY2FsMA0GCSqGSIb3DQEBCwUAA4IBAQADKhPXSckLeTPlb5/FqxTUno2+ 11 | FzR0hwcIkkgwdmi8DCTOua8Lm+7W0xY7LaFXbxsXgAMEVcqS/qwm7GsRv4UegQQ8 12 | J/SgRzvKhsBLs9okKd1dZuzwZJPvSJOzSNaJdQNws+yt1mHKHw3A/Dm0ripaEpbM 13 | +7uXs0w4bMpU9Vx4VLQSQRDLSmAFbS1xXsRqaLyIvTkbZ4XAntj+9s8jV2bS8Z8r 14 | /Fpo5HeScfBtq2Wt8klh1K45Iy6fxnyeeEUBAP/Odk7WCnWt2k3HMtVMhWy6IJrA 15 | ovwMvEMM2AlmNBXGZfu4AIk1V6KkYXAEMJ3WDAVPvoNWmpwola7NJc2ndSr3 16 | -----END CERTIFICATE REQUEST----- 17 | -------------------------------------------------------------------------------- /certs/manager/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC86QTIxijuwjTk 3 | gSa/iWI2RebTRlNhlohsIPGE6cYrv8HfYTO5Wq6GYSFxrYgUHnKguSRckQRo4ubO 4 | xY0yNJjhpHrbcF92GRi66JCCbuLRu6gWJnknLbz7cm3DIHU0El7DqYHCjzBFnxFK 5 | y+R5eEVojzjOe6O/cG4deQPgDIDowqdeZSV2SOg8QtxefzYeV/UkOhc+R7irxu3E 6 | ZZdo+X/Hqtmbqdpuv72kbdwIKkCrlqCSQWDuDSMiisrlpchJzZrK2jUl8BkKf/hl 7 | iYFJxGVLKPYT2VgiDnzRUFtFr2w3e8fQaCy9bGtY94NYfZuVCTtSepoD1JbcItt4 8 | 8TcjOEblAgMBAAECggEAKrXAKChtHrX3wWXVvd4wvzaEnmW6+khPZar5D+TOshtz 9 | mK5gRFrVNHqHVi02o93SarYRG4CJ77DFICCX8K7llbZbNHuuiYPZDIECEwtA6swz 10 | j3Z5U7tKi8ruN/yBoxk4JaKQPM2ky0jQXwnECRaBEse2vzBC8EhaDP3MO354MqdT 11 | XRqF0u7uipxTaulnQQvQ3ZFpjlTG/Syrv5Ks4P3NbLVtvtbGBbtHd4hzM472op/G 12 | FklenEKRPedRwMlUm4J+ABClAo6kNHZ/vCPBQDsyOMpb7OTyLaywYFcKFpYYUba7 13 | pFH2DL7XBTeAOV0G57+j4HRatFBiq1crhle1GtGWbQKBgQD7ZX0skJFfhtFR6w1C 14 | N83IfZ956IFpSD805ivjxis+m009jxVcGVLQEzhUWGyb+WyLXZ0fJ4ifc8Sp2gh2 15 | u3qZg58BkGEiaCYC73bM8JnleuWxi9ACLcLRQljsw/XzCMVxAYXq2SYTCMMKFpdh 16 | fFiaHjaDnc+CMvqBlO3muZ0/gwKBgQDAXppqPy8NHSC3Zxyz8u0s/V3Uk1QVVl7h 17 | LBH9hSVTZGYjBBZ9zoAr8Stki0PnL8yHyeTF0O7QWhZjhvTxaOHdCa2GbRVZ/rjG 18 | KWv809QEjnFi2VQ/4VgyB0MiUdQHyzpQviqFx6XgRMIftfKXy8LmAFfeD/5t3nYa 19 | HFnHr9xrdwKBgGx9+Q5ReZtrOEyNDxTDtnhO1pMq5yaDelue9dP/wsvrA+OMK2Cq 20 | wRVxJf8ohf6uHszqYpN+YTTHJllS8hIjiJ5Vsjpfj7vkjHr50yBQuWnSpuv/dY5r 21 | J0ddxbiwPSVcZLEHQj7+5bKTNnDVHRGCM06XuVkFsvbyfy+LETxgYF93AoGAT2S8 22 | Bi2dlaP35LnBtuMD0BWhrCJCCaxj7DrsEd3p0ckV/k2pmrKnY3tdlVmE5N1tZH2G 23 | 1b2tUoBbzSfd3+SRk1BzNY+/yCzAxchCsU4bquW/FjTr+JFgfQVSR8/N2omdv8U8 24 | d4o2g6DdHYlSXiPShGqP2S5wq5es1ons4+VI63ECgYEAuP2rBbC0sRaCud6+HAY7 25 | weyBlhx01ZNA7LSNWXVW+dj+vgUx0zi1B29ADkg24q+/wNtFQ6dkKLPuu2mugNkb 26 | jfhsZ4igP8RWBmZ1hS++GwPhNsFL7Ua1CzJfAbd4Ra9rTidc13iKCGBu6TjQE/Lk 27 | oU1YINWaJzo+s1V4p3XaW4o= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /certs/worker/ca-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDIzCCAgugAwIBAgIUSAHevOpPcSFqn+BgDiXI1BloPgYwDQYJKoZIhvcNAQEL 3 | BQAwITEOMAwGA1UEAwwFblRhc2sxDzANBgNVBAoMBnI0dWxjbDAeFw0yMzEyMTIy 4 | MzU5MzdaFw0yNDAxMTEyMzU5MzdaMCExDjAMBgNVBAMMBW5UYXNrMQ8wDQYDVQQK 5 | DAZyNHVsY2wwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMaCepvcm+ 6 | u6JoV8iiiUoCqUuERvB0TP4eqG+QrYPpys71krZdMsGGGqkxptv+6BKfLvQLw6oy 7 | 0KJZctqiATVTwXPd7No8w3dX3WHpcMqzETNbrBrKH6f8wkXmN4g7PaV5pq4MpWpM 8 | 7tQWxOszo7/7VTdR3bRZidtaSeZh/sDtsgXATFjm1EDZ0fImiAkLhTWfhnADfa+P 9 | 3893GfYKWbnx/EpQZUxgp8MpyLinCKiOeK58MZGpbPEQ1u8xLjPyUWkc21hb53HU 10 | l7sZZOqmKNuKqG7P1Ir5U7pOQ13luEC33gPePaylPjxlbxRqgyErXzuSwSYaxjSC 11 | +Q/Itygrw6inAgMBAAGjUzBRMB0GA1UdDgQWBBQQsJifjPjWNfFUew2+704mL38b 12 | HjAfBgNVHSMEGDAWgBQQsJifjPjWNfFUew2+704mL38bHjAPBgNVHRMBAf8EBTAD 13 | AQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBISN1S2REUJRwGrxvIamN7hn0T1ipx3WKt 14 | Yb+eTbqKG8YG9U5J7weMiOy+7JEXeh4f1gOxbhm1bJtfoo1Hn+IGYXWJmJry+ylw 15 | AT52vkJ1874UWR14F8m8rmeeSvPcywGYqMzIuaWk6AGrw7Bi6LPUNepL0sYyB2om 16 | j4fh26mG9UVAK+lDJeSpjEBtiixVVDn7+TmynsKzQTfnzeS4h41haNH5QpzBZGZ4 17 | pRI1HSrttawXhnJrqTqI0WXf+ARVsYVp0atBGmdLOUaELPVifgC+wojQfmOX0mLo 18 | cVnIwz5/Hj/fSDP1/MipD5x+3H8Uu9k6XoYUyto2MnhwwjI5L2zD 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /certs/worker/cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDPjCCAiagAwIBAgIUNgvt/YQb+0HTQFbAXTCZK1+YI9YwDQYJKoZIhvcNAQEL 3 | BQAwITEOMAwGA1UEAwwFblRhc2sxDzANBgNVBAoMBnI0dWxjbDAeFw0yMzEyMTIy 4 | MzU5MzdaFw0yNDEyMTEyMzU5MzdaMCIxDzANBgNVBAMMBldvcmtlcjEPMA0GA1UE 5 | CgwGcjR1bGNsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAstyDpiAy 6 | Fzt8e0c2KsT255qZyn1yWkHC/MERFbMyimiXwT3mX7jMLm198/6CY2Ti0Liv2QQT 7 | s34Z74yzgiRuZZzT1zFO67/AolBfdX9eQ3LQD/PxExmP3B3gkPZpuL4c7plJYYmN 8 | 9T5GQuMJ3xy4we0J3GK0wd/cYLWyChrLvwkHFRhe47byAyaofQYUtG/bOh+LubUw 9 | HKmCvLiOOWZwH1+RbCgIIo9SrNE9LgRfSQoe9I6MOAoYhGESQLgJK+3T9T6La0RG 10 | zTdxHX8pWGBuOnJmCLsvi4pSdPCQfKmR0FbI734vnW0L61DYJs32cD7OINdF0d7n 11 | fsiJS+itCl2HBwIDAQABo20wazApBgNVHREEIjAghxAAAAAAAAAAAAAAAAAAAAAB 12 | ggx3b3JrZXIubG9jYWwwHQYDVR0OBBYEFDwTQbO5D+0I/9WdBZ4rcSDn1su+MB8G 13 | A1UdIwQYMBaAFBCwmJ+M+NY18VR7Db7vTiYvfxseMA0GCSqGSIb3DQEBCwUAA4IB 14 | AQCTCfQoFbDK7pa/fXEtwVFusNKJTD0a91axoeHZ1QyagBg5sim+WL95rfEAqmsq 15 | v9LWnl9dATIZH1JepRhkg4KTJj1m/krnhJ+/X7i876aLUAIh9HCMKVMC+qLe5yXc 16 | c/lGoYf9IYrVY2mSH3iFL86MZXcDBFqJtcPU82UdNFkOxva8xfv50WxdhiPaUmHk 17 | PVUcK4DqbWP3yE/DfOAwTO2+aguDnUQsYUOXU5S5YbQ1BrlfUj8ur5B6iRJL9WSr 18 | TfqfsQgbeCgQLIRvPvKEa2CRdeuynNLsFu0i3Liwjc46mK7/xGF2sBGAXUntwr6E 19 | Bc9FVNQTtWQPv9ynJlWgpQ2T 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /certs/worker/csr.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICozCCAYsCAQAwIjEPMA0GA1UEAwwGV29ya2VyMQ8wDQYDVQQKDAZyNHVsY2ww 3 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCy3IOmIDIXO3x7RzYqxPbn 4 | mpnKfXJaQcL8wREVszKKaJfBPeZfuMwubX3z/oJjZOLQuK/ZBBOzfhnvjLOCJG5l 5 | nNPXMU7rv8CiUF91f15DctAP8/ETGY/cHeCQ9mm4vhzumUlhiY31PkZC4wnfHLjB 6 | 7QncYrTB39xgtbIKGsu/CQcVGF7jtvIDJqh9BhS0b9s6H4u5tTAcqYK8uI45ZnAf 7 | X5FsKAgij1Ks0T0uBF9JCh70jow4ChiEYRJAuAkr7dP1PotrREbNN3EdfylYYG46 8 | cmYIuy+LilJ08JB8qZHQVsjvfi+dbQvrUNgmzfZwPs4g10XR3ud+yIlL6K0KXYcH 9 | AgMBAAGgPDA6BgkqhkiG9w0BCQ4xLTArMCkGA1UdEQQiMCCHEAAAAAAAAAAAAAAA 10 | AAAAAAGCDHdvcmtlci5sb2NhbDANBgkqhkiG9w0BAQsFAAOCAQEAE7eeuuGWzq2Z 11 | 3q796FquirlBusHbEHVN/FBDAnIID2k75wYfM4D5JN9Fm3947Qjp2rlyiEP5cy8W 12 | TwABed7Bb5whnrqjl5/uHkUyYY8SkguHkOMo8fvvQLohHjbIMlC0aF+Dc32VoQVZ 13 | 8JH275Lhej1ybTJuMm4ehfCWMIt04PIFRlETqkJdZJywn3AoGM3qYp7fkL+xYgv0 14 | wD7MHT+sOZizJGdosgvB6pOMt2tNC9n18r3TcRye1EU9ia0M+1sAL29rS+e6UE/P 15 | KNJhcRYtbpB/3I+hGjd2Tjhzr9pFwzbwEdGtoxRYHQ6KdER/PHToWZon4vWUzotZ 16 | 81m7R+H+4A== 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /certs/worker/key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCy3IOmIDIXO3x7 3 | RzYqxPbnmpnKfXJaQcL8wREVszKKaJfBPeZfuMwubX3z/oJjZOLQuK/ZBBOzfhnv 4 | jLOCJG5lnNPXMU7rv8CiUF91f15DctAP8/ETGY/cHeCQ9mm4vhzumUlhiY31PkZC 5 | 4wnfHLjB7QncYrTB39xgtbIKGsu/CQcVGF7jtvIDJqh9BhS0b9s6H4u5tTAcqYK8 6 | uI45ZnAfX5FsKAgij1Ks0T0uBF9JCh70jow4ChiEYRJAuAkr7dP1PotrREbNN3Ed 7 | fylYYG46cmYIuy+LilJ08JB8qZHQVsjvfi+dbQvrUNgmzfZwPs4g10XR3ud+yIlL 8 | 6K0KXYcHAgMBAAECggEAE8JYqNmVrwtbTSMo0MDBpgRmSQBiyaXKLLIHeSY6xx+W 9 | 5Do6YQjBRb/C0lmjfed/Rx+gDZFtu76KjvQ0QIRVzdPtczB3T4P3e/b1FL3lY/4j 10 | DiAc2cdRJTgHvvv54fbeq10zxhq0HAutJ2Z9iLBbmNDe7gHZhjF83xIzgPpE1+2v 11 | 6iMyGlccY6n15HbkfsEbRs4gNKKIaCL9Oi+4yEkP5AU6hcHDgDInizQApZleYJuL 12 | aafh5vxhIW2EyKG2AjDM5fNYoU6ustTb7hJ9dxFSUVb3SaH3D8a13ZsLA6FU9pdK 13 | HIIK96nAi8gHRjwTAQ1GKYhuc/vAICY6i9xFlVGKAQKBgQDqJGG1jU1DEsZCLtoa 14 | PZw/Hd5mu05AKVgEHTs1p23+L5Vcw1652gyaWsKmXbJ9KYXHm+Fxasmw9lvMFp4/ 15 | n+MLFogAm4lsgRy3Jr+1sMZ2xZOuboBVO9BMomKjNKkpHxzgnv8aBmqAQ8DmjuXY 16 | vivFMNwA54G5N0Zd9Q99yilx5wKBgQDDjwMri9U4US1tbtDb/0GTWCyHnmlODYFk 17 | stU26HbiDrmRVgkwy5BOnpcUorS/ivDSfr9dmYvtbmL338IlD1ZYDD84VLRYsB2Z 18 | VG9GDpwuPWGqSUiwYnmccO5dWImU0rzaUcOO4c3se/rC+xOidvGvBqk8jlo07i3H 19 | INyHKwTd4QKBgFQTXlaf96dnHXP0ePLFPs7XPd4r0gxuDCHmETXR+kg08/BMYz5f 20 | Uvk2MAEnUglpmJU1nz1nuCboP8xILujv08pOe+Wj9DPXJOWLb3mhkZJingYbvc9d 21 | XA24nay3IysV9fuefEHvHd0S9ziTBWvP1c3IHqfgeY91jrn8XmSv7DPvAoGBAJ2I 22 | Ady9Wvhv5DFIWHUv7Wo9WDmzcU7P8FDnAnylzMaTaAbckLC++rNqa3fTDdlSmmZm 23 | dc93llGgX5cfp+xkq4Bn9TFygokC7gULEEwWDtfs2FTtjeGQojhUfJ+zZ7j7/ee8 24 | f9UErvZ2dO1Ghm2UdWIx8nh2j9Idn8DOBvfXvqbhAoGAXMK+XKwjo3Y6UI8NHRkX 25 | KkfkV8PMf/d7w0emYM0Uxqck6RkU3nl+Z9xAVnFRH5XPal1wvtIC8CRKM7Gq+kYK 26 | weHm8QTEymXeOhoTe5a6C7Eg/pRqrk4+lreNcQuWWC/1JzShZa5XHbtBWmF2b66o 27 | TMExXN3x8jqy09saL3xAESE= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /docker-compose-test.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | manager: 4 | build: 5 | context: . 6 | dockerfile: ./manager/Dockerfile 7 | restart: unless-stopped 8 | container_name: nTask_manager 9 | ports: 10 | - 8080:8080 11 | depends_on: 12 | - db 13 | env_file: .env 14 | command: manager --swagger --verbose --debug 15 | volumes: 16 | - ./manager.conf:/config/manager.conf 17 | - ./output/:/config/output/ 18 | - ./certs/:/config/certs/ 19 | 20 | worker: 21 | build: 22 | context: . 23 | dockerfile: ./worker/Dockerfile 24 | restart: unless-stopped 25 | container_name: nTask-worker 26 | depends_on: 27 | - manager 28 | env_file: .env 29 | command: worker --verbose --debug 30 | volumes: 31 | - ./worker.conf:/config/worker.conf 32 | - ./certs/:/config/certs/ 33 | 34 | worker2: 35 | # build: 36 | # context: . 37 | # dockerfile: ./worker/Dockerfile 38 | image: ntask-worker 39 | restart: unless-stopped 40 | container_name: nTask-worker2 41 | depends_on: 42 | - manager 43 | env_file: .env 44 | command: worker --verbose --debug 45 | volumes: 46 | - ./worker.conf:/config/worker.conf 47 | - ./certs/:/config/certs/ 48 | 49 | worker3: 50 | # build: 51 | # context: . 52 | # dockerfile: ./worker/Dockerfile 53 | image: ntask-worker 54 | restart: unless-stopped 55 | container_name: nTask-worker3 56 | depends_on: 57 | - manager 58 | env_file: .env 59 | command: worker --verbose --debug 60 | volumes: 61 | - ./worker.conf:/config/worker.conf 62 | - ./certs/:/config/certs/ 63 | 64 | worker4: 65 | # build: 66 | # context: . 67 | # dockerfile: ./worker/Dockerfile 68 | image: ntask-worker 69 | restart: unless-stopped 70 | container_name: nTask-worker4 71 | depends_on: 72 | - manager 73 | env_file: .env 74 | command: worker --verbose --debug 75 | volumes: 76 | - ./worker.conf:/config/worker.conf 77 | - ./certs/:/config/certs/ 78 | 79 | worker5: 80 | # build: 81 | # context: . 82 | # dockerfile: ./worker/Dockerfile 83 | image: ntask-worker 84 | restart: unless-stopped 85 | container_name: nTask-worker5 86 | depends_on: 87 | - manager 88 | env_file: .env 89 | command: worker --verbose --debug 90 | volumes: 91 | - ./worker.conf:/config/worker.conf 92 | - ./certs/:/config/certs/ 93 | 94 | worker6: 95 | # build: 96 | # context: . 97 | # dockerfile: ./worker/Dockerfile 98 | image: ntask-worker 99 | restart: unless-stopped 100 | container_name: nTask-worker6 101 | depends_on: 102 | - manager 103 | env_file: .env 104 | command: worker --verbose --debug 105 | volumes: 106 | - ./worker.conf:/config/worker.conf 107 | - ./certs/:/config/certs/ 108 | 109 | worker7: 110 | # build: 111 | # context: . 112 | # dockerfile: ./worker/Dockerfile 113 | image: ntask-worker 114 | restart: unless-stopped 115 | container_name: nTask-worker7 116 | depends_on: 117 | - manager 118 | env_file: .env 119 | command: worker --verbose --debug 120 | volumes: 121 | - ./worker.conf:/config/worker.conf 122 | - ./certs/:/config/certs/ 123 | 124 | worker8: 125 | # build: 126 | # context: . 127 | # dockerfile: ./worker/Dockerfile 128 | image: ntask-worker 129 | restart: unless-stopped 130 | container_name: nTask-worker8 131 | depends_on: 132 | - manager 133 | env_file: .env 134 | command: worker --verbose --debug 135 | volumes: 136 | - ./worker.conf:/config/worker.conf 137 | - ./certs/:/config/certs/ 138 | 139 | worker9: 140 | # build: 141 | # context: . 142 | # dockerfile: ./worker/Dockerfile 143 | image: ntask-worker 144 | restart: unless-stopped 145 | container_name: nTask-worker9 146 | depends_on: 147 | - manager 148 | env_file: .env 149 | command: worker --verbose --debug 150 | volumes: 151 | - ./worker.conf:/config/worker.conf 152 | - ./certs/:/config/certs/ 153 | 154 | worker10: 155 | # build: 156 | # context: . 157 | # dockerfile: ./worker/Dockerfile 158 | image: ntask-worker 159 | restart: unless-stopped 160 | container_name: nTask-worker10 161 | depends_on: 162 | - manager 163 | env_file: .env 164 | command: worker --verbose --debug 165 | volumes: 166 | - ./worker.conf:/config/worker.conf 167 | - ./certs/:/config/certs/ 168 | 169 | worker11: 170 | # build: 171 | # context: . 172 | # dockerfile: ./worker/Dockerfile 173 | image: ntask-worker 174 | restart: unless-stopped 175 | container_name: nTask-worker11 176 | depends_on: 177 | - manager 178 | env_file: .env 179 | command: worker --verbose --debug 180 | volumes: 181 | - ./worker.conf:/config/worker.conf 182 | - ./certs/:/config/certs/ 183 | 184 | worker12: 185 | # build: 186 | # context: . 187 | # dockerfile: ./worker/Dockerfile 188 | image: ntask-worker 189 | restart: unless-stopped 190 | container_name: nTask-worker12 191 | depends_on: 192 | - manager 193 | env_file: .env 194 | command: worker --verbose --debug 195 | volumes: 196 | - ./worker.conf:/config/worker.conf 197 | - ./certs/:/config/certs/ 198 | 199 | worker13: 200 | # build: 201 | # context: . 202 | # dockerfile: ./worker/Dockerfile 203 | image: ntask-worker 204 | restart: unless-stopped 205 | container_name: nTask-worker13 206 | depends_on: 207 | - manager 208 | env_file: .env 209 | command: worker --verbose --debug 210 | volumes: 211 | - ./worker.conf:/config/worker.conf 212 | - ./certs/:/config/certs/ 213 | 214 | worker14: 215 | # build: 216 | # context: . 217 | # dockerfile: ./worker/Dockerfile 218 | image: ntask-worker 219 | restart: unless-stopped 220 | container_name: nTask-worker14 221 | depends_on: 222 | - manager 223 | env_file: .env 224 | command: worker --verbose --debug 225 | volumes: 226 | - ./worker.conf:/config/worker.conf 227 | - ./certs/:/config/certs/ 228 | 229 | worker15: 230 | # build: 231 | # context: . 232 | # dockerfile: ./worker/Dockerfile 233 | image: ntask-worker 234 | restart: unless-stopped 235 | container_name: nTask-worker15 236 | depends_on: 237 | - manager 238 | env_file: .env 239 | command: worker --verbose --debug 240 | volumes: 241 | - ./worker.conf:/config/worker.conf 242 | - ./certs/:/config/certs/ 243 | 244 | worker16: 245 | # build: 246 | # context: . 247 | # dockerfile: ./worker/Dockerfile 248 | image: ntask-worker 249 | restart: unless-stopped 250 | container_name: nTask-worker16 251 | depends_on: 252 | - manager 253 | env_file: .env 254 | command: worker --verbose --debug 255 | volumes: 256 | - ./worker.conf:/config/worker.conf 257 | - ./certs/:/config/certs/ 258 | 259 | worker17: 260 | # build: 261 | # context: . 262 | # dockerfile: ./worker/Dockerfile 263 | image: ntask-worker 264 | restart: unless-stopped 265 | container_name: nTask-worker17 266 | depends_on: 267 | - manager 268 | env_file: .env 269 | command: worker --verbose --debug 270 | volumes: 271 | - ./worker.conf:/config/worker.conf 272 | - ./certs/:/config/certs/ 273 | 274 | worker18: 275 | # build: 276 | # context: . 277 | # dockerfile: ./worker/Dockerfile 278 | image: ntask-worker 279 | restart: unless-stopped 280 | container_name: nTask-worker18 281 | depends_on: 282 | - manager 283 | env_file: .env 284 | command: worker --verbose --debug 285 | volumes: 286 | - ./worker.conf:/config/worker.conf 287 | - ./certs/:/config/certs/ 288 | 289 | worker19: 290 | # build: 291 | # context: . 292 | # dockerfile: ./worker/Dockerfile 293 | image: ntask-worker 294 | restart: unless-stopped 295 | container_name: nTask-worker19 296 | depends_on: 297 | - manager 298 | env_file: .env 299 | command: worker --verbose --debug 300 | volumes: 301 | - ./worker.conf:/config/worker.conf 302 | - ./certs/:/config/certs/ 303 | 304 | worker20: 305 | # build: 306 | # context: . 307 | # dockerfile: ./worker/Dockerfile 308 | image: ntask-worker 309 | restart: unless-stopped 310 | container_name: nTask-worker20 311 | depends_on: 312 | - manager 313 | env_file: .env 314 | command: worker --verbose --debug 315 | volumes: 316 | - ./worker.conf:/config/worker.conf 317 | - ./certs/:/config/certs/ 318 | 319 | db: 320 | image: mysql 321 | command: --default-authentication-plugin=caching_sha2_password 322 | restart: unless-stopped 323 | # ports: 324 | # - 3306:3306 325 | environment: 326 | MYSQL_ROOT_PASSWORD: your_password_root 327 | MYSQL_USER: your_username 328 | MYSQL_PASSWORD: your_password 329 | MYSQL_DATABASE: manager 330 | volumes: 331 | - ./db:/var/lib/mysql 332 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | services: 3 | manager: 4 | build: 5 | context: . 6 | dockerfile: ./manager/Dockerfile 7 | restart: unless-stopped 8 | container_name: nTask_manager 9 | ports: 10 | - 8080:8080 11 | depends_on: 12 | - db 13 | env_file: .env 14 | command: manager --swagger --verbose --debug #--configSSHFile ./ssh.conf 15 | volumes: 16 | - ./manager.conf:/config/manager.conf 17 | - ./output/:/config/output/ 18 | - ./certs/:/config/certs/ 19 | # - ./ssh.conf:/config/ssh.conf 20 | # - ./ssh_key:/config/ssh_key 21 | 22 | db: 23 | image: mysql 24 | command: --default-authentication-plugin=caching_sha2_password 25 | restart: unless-stopped 26 | # ports: 27 | # - 3306:3306 28 | environment: 29 | MYSQL_ROOT_PASSWORD: your_password_root 30 | MYSQL_USER: your_username 31 | MYSQL_PASSWORD: your_password 32 | MYSQL_DATABASE: manager 33 | volumes: 34 | - ./db:/var/lib/mysql 35 | -------------------------------------------------------------------------------- /docs/swagger.yaml: -------------------------------------------------------------------------------- 1 | basePath: / 2 | definitions: 3 | globalstructs.Command: 4 | properties: 5 | args: 6 | type: string 7 | fileContent: 8 | type: string 9 | module: 10 | type: string 11 | output: 12 | type: string 13 | remoteFilePath: 14 | type: string 15 | type: object 16 | globalstructs.CommandSwagger: 17 | properties: 18 | args: 19 | type: string 20 | fileContent: 21 | type: string 22 | module: 23 | type: string 24 | remoteFilePath: 25 | type: string 26 | type: object 27 | globalstructs.Error: 28 | properties: 29 | error: 30 | type: string 31 | type: object 32 | globalstructs.Task: 33 | properties: 34 | callbackToken: 35 | type: string 36 | callbackURL: 37 | type: string 38 | command: 39 | items: 40 | $ref: '#/definitions/globalstructs.Command' 41 | type: array 42 | createdAt: 43 | type: string 44 | executedAt: 45 | type: string 46 | id: 47 | type: string 48 | name: 49 | type: string 50 | priority: 51 | type: integer 52 | status: 53 | description: pending, running, done, failed, deleted 54 | type: string 55 | updatedAt: 56 | type: string 57 | username: 58 | type: string 59 | workerName: 60 | type: string 61 | type: object 62 | globalstructs.TaskSwagger: 63 | properties: 64 | command: 65 | items: 66 | $ref: '#/definitions/globalstructs.CommandSwagger' 67 | type: array 68 | name: 69 | type: string 70 | priority: 71 | type: integer 72 | type: object 73 | globalstructs.Worker: 74 | properties: 75 | IddleThreads: 76 | type: integer 77 | downCount: 78 | type: integer 79 | name: 80 | description: Workers name (unique) 81 | type: string 82 | up: 83 | type: boolean 84 | type: object 85 | info: 86 | contact: 87 | email: me@r4ulcl.com 88 | name: r4ulcl 89 | url: https://r4ulcl.com 90 | description: nTask API documentation 91 | license: 92 | name: GPL-3.0 93 | url: https://github.com/r4ulcl/nTask/blob/main/LICENSE 94 | title: nTask API 95 | version: "1.0" 96 | paths: 97 | /status: 98 | get: 99 | consumes: 100 | - application/json 101 | description: Get status summary from Manager 102 | produces: 103 | - application/json 104 | responses: 105 | "200": 106 | description: OK 107 | "400": 108 | description: Bad Request 109 | schema: 110 | $ref: '#/definitions/globalstructs.Error' 111 | "403": 112 | description: Forbidden 113 | schema: 114 | $ref: '#/definitions/globalstructs.Error' 115 | security: 116 | - ApiKeyAuth: [] 117 | summary: Get status summary from Manager 118 | tags: 119 | - status 120 | /task: 121 | get: 122 | consumes: 123 | - application/json 124 | description: Get status of tasks 125 | parameters: 126 | - description: Task ID 127 | in: query 128 | name: ID 129 | type: string 130 | - description: Task command 131 | in: query 132 | name: command 133 | type: string 134 | - description: Task name 135 | in: query 136 | name: name 137 | type: string 138 | - description: Task createdAt 139 | in: query 140 | name: createdAt 141 | type: string 142 | - description: Task updatedAt 143 | in: query 144 | name: updatedAt 145 | type: string 146 | - description: Task executedAt 147 | in: query 148 | name: executedAt 149 | type: string 150 | - description: Task status 151 | enum: 152 | - pending 153 | - running 154 | - done 155 | - failed 156 | - deleted 157 | in: query 158 | name: status 159 | type: string 160 | - description: Task workerName 161 | in: query 162 | name: workerName 163 | type: string 164 | - description: Task username 165 | in: query 166 | name: username 167 | type: string 168 | - description: Task priority 169 | in: query 170 | name: priority 171 | type: string 172 | - description: Task callbackURL 173 | in: query 174 | name: callbackURL 175 | type: string 176 | - description: Task callbackToken 177 | in: query 178 | name: callbackToken 179 | type: string 180 | - description: limit output DB 181 | in: query 182 | name: limit 183 | type: integer 184 | - description: page output DB 185 | in: query 186 | name: page 187 | type: integer 188 | produces: 189 | - application/json 190 | responses: 191 | "200": 192 | description: OK 193 | schema: 194 | items: 195 | $ref: '#/definitions/globalstructs.Task' 196 | type: array 197 | "400": 198 | description: Bad Request 199 | schema: 200 | $ref: '#/definitions/globalstructs.Error' 201 | "403": 202 | description: Forbidden 203 | schema: 204 | $ref: '#/definitions/globalstructs.Error' 205 | security: 206 | - ApiKeyAuth: [] 207 | summary: Get all tasks 208 | tags: 209 | - task 210 | post: 211 | consumes: 212 | - application/json 213 | description: Add a new tasks 214 | parameters: 215 | - description: Task object to create 216 | in: body 217 | name: task 218 | required: true 219 | schema: 220 | $ref: '#/definitions/globalstructs.TaskSwagger' 221 | produces: 222 | - application/json 223 | responses: 224 | "200": 225 | description: OK 226 | schema: 227 | $ref: '#/definitions/globalstructs.Task' 228 | "400": 229 | description: Bad Request 230 | schema: 231 | $ref: '#/definitions/globalstructs.Error' 232 | "403": 233 | description: Forbidden 234 | schema: 235 | $ref: '#/definitions/globalstructs.Error' 236 | security: 237 | - ApiKeyAuth: [] 238 | summary: Add a new tasks 239 | tags: 240 | - task 241 | /task/{ID}: 242 | delete: 243 | consumes: 244 | - application/json 245 | description: Delete a tasks 246 | parameters: 247 | - description: task ID 248 | in: path 249 | name: ID 250 | required: true 251 | type: string 252 | produces: 253 | - application/json 254 | responses: 255 | "200": 256 | description: OK 257 | schema: 258 | $ref: '#/definitions/globalstructs.Task' 259 | "400": 260 | description: Bad Request 261 | schema: 262 | $ref: '#/definitions/globalstructs.Error' 263 | "403": 264 | description: Forbidden 265 | schema: 266 | $ref: '#/definitions/globalstructs.Error' 267 | security: 268 | - ApiKeyAuth: [] 269 | summary: Delete a tasks 270 | tags: 271 | - task 272 | get: 273 | consumes: 274 | - application/json 275 | description: Get status of a task 276 | parameters: 277 | - description: task ID 278 | in: path 279 | name: ID 280 | required: true 281 | type: string 282 | produces: 283 | - application/json 284 | responses: 285 | "200": 286 | description: OK 287 | schema: 288 | items: 289 | $ref: '#/definitions/globalstructs.Task' 290 | type: array 291 | "400": 292 | description: Bad Request 293 | schema: 294 | $ref: '#/definitions/globalstructs.Error' 295 | "403": 296 | description: Forbidden 297 | schema: 298 | $ref: '#/definitions/globalstructs.Error' 299 | security: 300 | - ApiKeyAuth: [] 301 | summary: Get status of a task 302 | tags: 303 | - task 304 | /worker: 305 | get: 306 | consumes: 307 | - application/json 308 | description: Handle worker request 309 | produces: 310 | - application/json 311 | responses: 312 | "200": 313 | description: OK 314 | schema: 315 | items: 316 | $ref: '#/definitions/globalstructs.Worker' 317 | type: array 318 | "400": 319 | description: Bad Request 320 | schema: 321 | $ref: '#/definitions/globalstructs.Error' 322 | "403": 323 | description: Forbidden 324 | schema: 325 | $ref: '#/definitions/globalstructs.Error' 326 | security: 327 | - ApiKeyAuth: [] 328 | summary: Get workers 329 | tags: 330 | - worker 331 | post: 332 | consumes: 333 | - application/json 334 | description: Add a worker, normally done by the worker 335 | parameters: 336 | - description: Worker object to create 337 | in: body 338 | name: worker 339 | required: true 340 | schema: 341 | $ref: '#/definitions/globalstructs.Worker' 342 | produces: 343 | - application/json 344 | responses: 345 | "200": 346 | description: OK 347 | schema: 348 | items: 349 | $ref: '#/definitions/globalstructs.Worker' 350 | type: array 351 | "400": 352 | description: Bad Request 353 | schema: 354 | $ref: '#/definitions/globalstructs.Error' 355 | "403": 356 | description: Forbidden 357 | schema: 358 | $ref: '#/definitions/globalstructs.Error' 359 | security: 360 | - ApiKeyAuth: [] 361 | summary: Add a worker 362 | tags: 363 | - worker 364 | /worker/{NAME}: 365 | delete: 366 | consumes: 367 | - application/json 368 | description: Remove a worker from the system 369 | parameters: 370 | - description: Worker NAME 371 | in: path 372 | name: NAME 373 | required: true 374 | type: string 375 | produces: 376 | - application/json 377 | responses: 378 | "200": 379 | description: OK 380 | schema: 381 | items: 382 | type: string 383 | type: array 384 | "400": 385 | description: Bad Request 386 | schema: 387 | $ref: '#/definitions/globalstructs.Error' 388 | "403": 389 | description: Forbidden 390 | schema: 391 | $ref: '#/definitions/globalstructs.Error' 392 | security: 393 | - ApiKeyAuth: [] 394 | summary: Remove a worker 395 | tags: 396 | - worker 397 | get: 398 | consumes: 399 | - application/json 400 | description: Get status of worker 401 | parameters: 402 | - description: Worker NAME 403 | in: path 404 | name: NAME 405 | required: true 406 | type: string 407 | produces: 408 | - application/json 409 | responses: 410 | "200": 411 | description: OK 412 | schema: 413 | $ref: '#/definitions/globalstructs.Worker' 414 | "400": 415 | description: Bad Request 416 | schema: 417 | $ref: '#/definitions/globalstructs.Error' 418 | "403": 419 | description: Forbidden 420 | schema: 421 | $ref: '#/definitions/globalstructs.Error' 422 | security: 423 | - ApiKeyAuth: [] 424 | summary: Get status of worker 425 | tags: 426 | - worker 427 | schemes: 428 | - https 429 | - http 430 | security: 431 | - ApiKeyAuth: [] 432 | securityDefinitions: 433 | ApiKeyAuth: 434 | description: ApiKeyAuth to login 435 | in: header 436 | name: Authorization 437 | type: apiKey 438 | swagger: "2.0" 439 | -------------------------------------------------------------------------------- /examples/scriptExample.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Function to send a POST request and get the task ID 4 | function send_post_request() { 5 | local url="$1" 6 | local oauthToken="$2" 7 | local data="$3" 8 | 9 | # Send POST request and capture the task ID 10 | task_id=$(curl -s -k -X POST -H "Authorization: $oauthToken" -H "Content-Type: application/json" -d "$data" "$url" | jq -r '.id') 11 | echo "$task_id" 12 | } 13 | 14 | # Function to check the status of a task using a GET request 15 | function get_task() { 16 | local url="$1" 17 | local oauthToken="$2" 18 | 19 | # Send GET request to check task status 20 | task=$(curl -s -k -H "Authorization: $oauthToken" "$url") 21 | echo "$task" 22 | } 23 | 24 | function wait_task(){ 25 | local url="$1" 26 | local oauthToken="$2" 27 | local task_id="$3" 28 | # Wait for task done 29 | while true; do 30 | task=$(get_task "$url/task/$task_id" "$oauthToken" ) 31 | status=$(echo $task | jq -r '.status') 32 | # Check if the status is not equal to "working" 33 | if [ "$status" == "done" ]; then 34 | #echo "Task completed successfully. Status: $status" 35 | 36 | echo $task 37 | break # Exit the loop 38 | else 39 | #echo "Task still in progress. Status: $status" 40 | sleep 1 # Adjust the sleep duration as needed 41 | fi 42 | done 43 | } 44 | 45 | function wait_tasks(){ 46 | local url="$1" 47 | local oauthToken="$2" 48 | shift 2 49 | local task_ids=("$@") 50 | 51 | output_array=() 52 | pids=() 53 | 54 | echo -n '{"result":[' 55 | 56 | # Loop ids 57 | array_length=${#task_ids[@]} 58 | for ((i=0; i 0 THEN IddleThreads - 1 "+ 226 | "ELSE 0 END WHERE name = ?", worker) 227 | if err != nil { 228 | if debug { 229 | log.Println("DB Error DBworkers: ", err) 230 | } 231 | return err 232 | } 233 | return nil 234 | } 235 | 236 | // GetWorkerIddle retrieves all workers that are iddle. 237 | func GetWorkerIddle(db *sql.DB, verbose, debug bool) ([]globalstructs.Worker, error) { 238 | sql := "SELECT name, IddleThreads, up, downCount FROM worker WHERE up = true AND IddleThreads > 0 ORDER BY RAND();" 239 | return GetWorkerSQL(sql, db, verbose, debug) 240 | } 241 | 242 | // GetWorkerUP retrieves all workers that are up. 243 | func GetWorkerUP(db *sql.DB, verbose, debug bool) ([]globalstructs.Worker, error) { 244 | sql := "SELECT name, IddleThreads, up, downCount FROM worker WHERE up = true;" 245 | return GetWorkerSQL(sql, db, verbose, debug) 246 | } 247 | 248 | // GetWorkerSQL retrieves workers information based on a SQL statement. 249 | func GetWorkerSQL(sql string, db *sql.DB, verbose, debug bool) ([]globalstructs.Worker, error) { 250 | // Slice to store all workers 251 | var workers []globalstructs.Worker 252 | 253 | // Query all workers from the worker table 254 | rows, err := db.Query(sql) 255 | if err != nil { 256 | if debug { 257 | log.Println("DB Error DBworkers: ", err) 258 | } 259 | return workers, err 260 | } 261 | defer rows.Close() 262 | 263 | // Iterate over the rows 264 | for rows.Next() { 265 | // Declare variables to store JSON data 266 | var name string 267 | var IddleThreads int 268 | var up bool 269 | var downCount int 270 | 271 | // Scan the values from the row into variables 272 | err := rows.Scan(&name, &IddleThreads, &up, &downCount) 273 | if err != nil { 274 | if debug { 275 | log.Println("DB Error DBworkers: ", err) 276 | } 277 | return workers, err 278 | } 279 | 280 | // Data into a Worker struct 281 | var worker globalstructs.Worker 282 | worker.Name = name 283 | 284 | worker.IddleThreads = IddleThreads 285 | worker.UP = up 286 | worker.DownCount = downCount 287 | 288 | // Append the worker to the slice 289 | workers = append(workers, worker) 290 | } 291 | 292 | // Check for errors from iterating over rows 293 | if err := rows.Err(); err != nil { 294 | if debug { 295 | log.Println("DB Error DBworkers: ", err) 296 | } 297 | return workers, err 298 | } 299 | 300 | return workers, nil 301 | } 302 | 303 | // GetWorkerCount get workers downCount by name (used to downCount until 3 to set down) 304 | func GetWorkerDownCount(db *sql.DB, worker *globalstructs.Worker, verbose, debug bool) (int, error) { 305 | var countS string 306 | err := db.QueryRow("SELECT downCount FROM worker WHERE name = ?", 307 | worker.Name).Scan(&countS) 308 | if err != nil { 309 | if debug { 310 | log.Println("DB Error DBworkers: ", err) 311 | } 312 | return -1, err 313 | } 314 | downCount, err := strconv.Atoi(countS) 315 | if err != nil { 316 | return -1, err 317 | } 318 | 319 | if debug { 320 | log.Println("DB count worker:", worker.Name, "downCount:", downCount) 321 | } 322 | return downCount, nil 323 | } 324 | 325 | // SetWorkerCount set worker downCount to downCount int 326 | func SetWorkerDownCount(count int, db *sql.DB, worker *globalstructs.Worker, verbose, debug bool, wg *sync.WaitGroup) error { 327 | // Add to the WaitGroup when the goroutine starts and done when exits 328 | defer wg.Done() 329 | wg.Add(1) 330 | _, err := db.Exec("UPDATE worker SET downCount = ? WHERE name = ?", 331 | count, worker.Name) 332 | if err != nil { 333 | if debug { 334 | log.Println("DB Error DBworkers: ", err) 335 | } 336 | return err 337 | } 338 | 339 | return nil 340 | } 341 | 342 | // AddWorkerCount add 1 to worker downCount 343 | func AddWorkerDownCount(db *sql.DB, worker *globalstructs.Worker, verbose, debug bool, wg *sync.WaitGroup) error { 344 | // Add to the WaitGroup when the goroutine starts and done when exits 345 | defer wg.Done() 346 | wg.Add(1) 347 | _, err := db.Exec("UPDATE worker SET downCount = downCount + 1 WHERE name = ?", 348 | worker.Name) 349 | if err != nil { 350 | if debug { 351 | log.Println("DB Error DBworkers: ", err) 352 | } 353 | return err 354 | } 355 | 356 | return nil 357 | } 358 | 359 | func GetUpCount(db *sql.DB, verbose, debug bool) (int, error) { 360 | // Prepare the SQL query 361 | query := "SELECT COUNT(*) FROM worker where up = true" 362 | 363 | // Execute the query 364 | var count int 365 | err := db.QueryRow(query).Scan(&count) 366 | if err != nil { 367 | return 0, err 368 | } 369 | 370 | return count, nil 371 | } 372 | 373 | func GetDownCount(db *sql.DB, verbose, debug bool) (int, error) { 374 | // Prepare the SQL query 375 | query := "SELECT COUNT(*) FROM worker where up = false" 376 | 377 | // Execute the query 378 | var count int 379 | err := db.QueryRow(query).Scan(&count) 380 | if err != nil { 381 | return 0, err 382 | } 383 | 384 | return count, nil 385 | } 386 | -------------------------------------------------------------------------------- /manager/manager.go: -------------------------------------------------------------------------------- 1 | // manager.go 2 | package manager 3 | 4 | import ( 5 | "context" 6 | "database/sql" 7 | "encoding/json" 8 | "errors" 9 | "fmt" 10 | "log" 11 | "net/http" 12 | "os" 13 | "strings" 14 | "sync" 15 | "time" 16 | 17 | "github.com/gorilla/mux" 18 | "github.com/gorilla/websocket" 19 | "github.com/r4ulcl/nTask/manager/api" 20 | "github.com/r4ulcl/nTask/manager/database" 21 | sshtunnel "github.com/r4ulcl/nTask/manager/sshTunnel" 22 | "github.com/r4ulcl/nTask/manager/utils" 23 | httpSwagger "github.com/swaggo/http-swagger" 24 | ) 25 | 26 | func loadManagerConfig(filename string, verbose, debug bool) (*utils.ManagerConfig, error) { 27 | var config utils.ManagerConfig 28 | if debug { 29 | log.Println("Manager Loading manager config from file", filename) 30 | } 31 | 32 | // Validate filename 33 | if filename == "" { 34 | return nil, errors.New("filename cannot be empty") 35 | } 36 | 37 | // Check if file exists 38 | if _, err := os.Stat(filename); os.IsNotExist(err) { 39 | return nil, fmt.Errorf("config file does not exist") 40 | } 41 | 42 | content, err := os.ReadFile(filename) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | // Use specific error message for json.Unmarshal failure 48 | err = json.Unmarshal(content, &config) 49 | if err != nil { 50 | return nil, fmt.Errorf("error unmarshaling JSON: %w", err) 51 | } 52 | 53 | // init WebSockets map 54 | config.WebSockets = make(map[string]*websocket.Conn) 55 | 56 | // Return nil instead of &config when error occurs 57 | return &config, nil 58 | } 59 | 60 | func loadManagerSSHConfig(filename string, verbose, debug bool) (*utils.ManagerSSHConfig, error) { 61 | var configSSH utils.ManagerSSHConfig 62 | if debug { 63 | log.Println("Manager Loading manager config from file", filename) 64 | } 65 | 66 | // Validate filename 67 | if filename == "" { 68 | return nil, errors.New("filename cannot be empty") 69 | } 70 | 71 | // Check if file exists 72 | if _, err := os.Stat(filename); os.IsNotExist(err) { 73 | return nil, fmt.Errorf("config file does not exist") 74 | } 75 | 76 | content, err := os.ReadFile(filename) 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | // Use specific error message for json.Unmarshal failure 82 | err = json.Unmarshal(content, &configSSH) 83 | if err != nil { 84 | return nil, fmt.Errorf("error unmarshaling JSON: %w", err) 85 | } 86 | 87 | return &configSSH, nil 88 | } 89 | 90 | func addHandleWorker(workers *mux.Router, config *utils.ManagerConfig, db *sql.DB, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) { 91 | // worker 92 | workers.HandleFunc("", func(w http.ResponseWriter, r *http.Request) { 93 | api.HandleWorkerGet(w, r, config, db, verbose, debug) 94 | }).Methods("GET") // get workers 95 | 96 | workers.HandleFunc("", func(w http.ResponseWriter, r *http.Request) { 97 | api.HandleWorkerPost(w, r, config, db, verbose, debug, wg) 98 | }).Methods("POST") // add worker 99 | 100 | workers.HandleFunc("/websocket", func(w http.ResponseWriter, r *http.Request) { 101 | api.HandleWorkerPostWebsocket(w, r, config, db, verbose, debug, wg, writeLock) 102 | }) 103 | 104 | workers.HandleFunc("/{NAME}", func(w http.ResponseWriter, r *http.Request) { 105 | api.HandleWorkerDeleteName(w, r, config, db, verbose, debug, wg) 106 | }).Methods("DELETE") // delete worker 107 | 108 | workers.HandleFunc("/{NAME}", func(w http.ResponseWriter, r *http.Request) { 109 | api.HandleWorkerStatus(w, r, config, db, verbose, debug) 110 | }).Methods("GET") // check status 1 worker 111 | 112 | } 113 | 114 | func addHandleTask(task *mux.Router, config *utils.ManagerConfig, db *sql.DB, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) { 115 | // task 116 | task.HandleFunc("", func(w http.ResponseWriter, r *http.Request) { 117 | api.HandleTaskGet(w, r, config, db, verbose, debug) 118 | }).Methods("GET") // check tasks 119 | 120 | task.HandleFunc("", func(w http.ResponseWriter, r *http.Request) { 121 | api.HandleTaskPost(w, r, config, db, verbose, debug, wg) 122 | }).Methods("POST") // Add task 123 | 124 | task.HandleFunc("/{ID}", func(w http.ResponseWriter, r *http.Request) { 125 | api.HandleTaskDelete(w, r, config, db, verbose, debug, wg, writeLock) 126 | }).Methods("DELETE") // Delete task 127 | 128 | task.HandleFunc("/{ID}", func(w http.ResponseWriter, r *http.Request) { 129 | api.HandleTaskStatus(w, r, config, db, verbose, debug) 130 | }).Methods("GET") // get status task 131 | 132 | } 133 | 134 | func startSwaggerWeb(router *mux.Router, verbose, debug bool) { 135 | // Serve Swagger UI at /swagger 136 | //swagger := router.PathPrefix("/swagger").Subrouter() 137 | router.PathPrefix("/swagger").Handler(httpSwagger.Handler( 138 | httpSwagger.URL("/docs/swagger.json"), // URL to the swagger.json file 139 | )) 140 | 141 | // Serve Swagger JSON at /swagger/doc.json 142 | router.HandleFunc("/docs/swagger.json", func(w http.ResponseWriter, r *http.Request) { 143 | http.ServeFile(w, r, "docs/swagger.json") 144 | }).Methods("GET") 145 | 146 | if verbose { 147 | log.Println("Manager Configure swagger docs in /swagger/") 148 | } 149 | } 150 | 151 | func StartManager(swagger bool, configFile, configSSHFile string, verifyAltName, verbose, debug bool) { 152 | log.Println("Manager Running as manager...") 153 | 154 | // if config file empty set default 155 | if configFile == "" { 156 | configFile = "manager.conf" 157 | } 158 | 159 | config, err := loadManagerConfig(configFile, verbose, debug) 160 | if err != nil { 161 | log.Fatal("Error loading config file: ", err) 162 | } 163 | 164 | var configSSH *utils.ManagerSSHConfig 165 | if configSSHFile != "" { 166 | configSSH, err = loadManagerSSHConfig(configSSHFile, verbose, debug) 167 | if err != nil { 168 | log.Fatal("Error loading config SSH file: ", err) 169 | } 170 | } 171 | 172 | // create waitGroups for DB 173 | var wg sync.WaitGroup 174 | var writeLock sync.Mutex 175 | 176 | // Start DB 177 | var db *sql.DB 178 | for { 179 | if debug { 180 | log.Println("Manager Trying to connect to DB") 181 | } 182 | db, err = database.ConnectDB(config.DBUsername, config.DBPassword, config.DBHost, config.DBPort, config.DBDatabase, verbose, debug) 183 | if err != nil { 184 | log.Println("Error manager ConnectDB: ", err) 185 | if db != nil { 186 | defer db.Close() 187 | } 188 | time.Sleep(time.Second * 5) 189 | } else { 190 | break 191 | } 192 | } 193 | 194 | // if running set to failed 195 | if debug { 196 | log.Println("Manager Set task running to failed") 197 | } 198 | err = database.SetTasksStatusIfRunning(db, "failed", verbose, debug, &wg) 199 | if err != nil { 200 | log.Println("Error SetTasksStatusIfRunning:", err) 201 | return 202 | } 203 | // Create an HTTP client with the custom TLS configuration 204 | if config.CertFolder != "" { 205 | clientHTTP, err := utils.CreateTLSClientWithCACert(config.CertFolder+"/ca-cert.pem", verifyAltName, verbose, debug) 206 | if err != nil { 207 | log.Println("Error creating HTTP client:", err) 208 | return 209 | } 210 | config.ClientHTTP = clientHTTP 211 | 212 | } else { 213 | config.ClientHTTP = &http.Client{} 214 | } 215 | config.ClientHTTP.Timeout = 5 * time.Second 216 | 217 | // verify status workers infinite 218 | go utils.VerifyWorkersLoop(db, config, verbose, debug, &wg, &writeLock) 219 | 220 | // manage task, routine to send task to iddle workers 221 | go utils.ManageTasks(config, db, verbose, debug, &wg, &writeLock) 222 | 223 | if configSSHFile != "" { 224 | go sshtunnel.StartSSH(configSSH, config.Port, verbose, debug) 225 | } 226 | 227 | router := mux.NewRouter() 228 | 229 | amw := authenticationMiddleware{tokenUsers: make(map[string]string), tokenWorkers: make(map[string]string)} 230 | amw.Populate(config) 231 | 232 | if swagger { 233 | // Start swagger endpoint 234 | startSwaggerWeb(router, verbose, debug) 235 | } 236 | 237 | // r.HandleFunc("/send/{recipient}", handleSendMessage).Methods("POST") 238 | 239 | // Status 240 | status := router.PathPrefix("/status").Subrouter() 241 | status.Use(amw.Middleware) 242 | status.HandleFunc("", func(w http.ResponseWriter, r *http.Request) { 243 | api.HandleStatus(w, r, config, db, verbose, debug) 244 | }).Methods("GET") // get callback info from task 245 | 246 | // Worker 247 | workers := router.PathPrefix("/worker").Subrouter() 248 | workers.Use(amw.Middleware) 249 | addHandleWorker(workers, config, db, verbose, debug, &wg, &writeLock) 250 | 251 | // Task 252 | task := router.PathPrefix("/task").Subrouter() 253 | task.Use(amw.Middleware) 254 | addHandleTask(task, config, db, verbose, debug, &wg, &writeLock) 255 | 256 | // Middleware to modify server response headers 257 | router.Use(func(next http.Handler) http.Handler { 258 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 259 | // Modify the server response headers here 260 | w.Header().Set("Server", "Apache") 261 | 262 | // Call the next handler 263 | next.ServeHTTP(w, r) 264 | }) 265 | }) 266 | //router.Use(amw.Middleware) 267 | 268 | http.Handle("/", router) 269 | 270 | // Set string for the port 271 | addr := fmt.Sprintf(":%s", config.Port) 272 | if verbose { 273 | log.Println("Manager Port", config.Port) 274 | } 275 | 276 | // if there is cert is HTTPS 277 | if config.CertFolder != "" { 278 | log.Fatal(http.ListenAndServeTLS(addr, config.CertFolder+"/cert.pem", config.CertFolder+"/key.pem", router)) 279 | } 280 | 281 | err = http.ListenAndServe(addr, nil) 282 | if err != nil { 283 | log.Println("Manager Error manager CertFolder: ", err) 284 | } 285 | 286 | /* 287 | err = http.ListenAndServe(":"+config.Port, allowCORS(http.DefaultServeMux)) 288 | if err != nil { 289 | log.Println("Manager Error manager: ",err) 290 | } 291 | */ 292 | 293 | } 294 | 295 | // Define our struct 296 | type authenticationMiddleware struct { 297 | tokenUsers map[string]string 298 | tokenWorkers map[string]string 299 | } 300 | 301 | // Initialize it somewhere 302 | func (amw *authenticationMiddleware) Populate(config *utils.ManagerConfig) { 303 | // the key is the token instead of user 304 | for k, v := range config.Users { 305 | amw.tokenUsers[v] = k 306 | } 307 | for k, v := range config.Workers { 308 | amw.tokenWorkers[v] = k 309 | } 310 | } 311 | 312 | // Middleware function, which will be called for each request 313 | func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler { 314 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 315 | if !strings.Contains(r.URL.Path, "/swagger/") && r.URL.Path != "/docs/swagger.json" { 316 | token := r.Header.Get("Authorization") 317 | user, foundUser := amw.tokenUsers[token] 318 | worker, foundWorker := amw.tokenWorkers[token] 319 | if foundUser { 320 | // We found the token in our map 321 | // Add the username to the request context 322 | ctx := context.WithValue(r.Context(), "username", user) 323 | 324 | // Pass down the request with the updated context to the next middleware (or final handler) 325 | next.ServeHTTP(w, r.WithContext(ctx)) 326 | } else if foundWorker { 327 | // We found the token in our map 328 | // Add the username to the request context 329 | ctx := context.WithValue(r.Context(), "worker", worker) 330 | 331 | // Pass down the request with the updated context to the next middleware (or final handler) 332 | next.ServeHTTP(w, r.WithContext(ctx)) 333 | 334 | } else { 335 | // Write an error and stop the handler chain 336 | http.Error(w, "{ \"error\" : \"Forbidden\" }", http.StatusForbidden) 337 | 338 | } 339 | } 340 | }) 341 | } 342 | -------------------------------------------------------------------------------- /manager/sshTunnel/sshTunnel.go: -------------------------------------------------------------------------------- 1 | package sshtunnel 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net" 9 | "os" 10 | 11 | "github.com/r4ulcl/nTask/manager/utils" 12 | "golang.org/x/crypto/ssh" 13 | ) 14 | 15 | func forwardData(src, dest net.Conn) { 16 | _, err := io.Copy(src, dest) 17 | if err != nil { 18 | log.Printf("Error forwarding data: %v", err) 19 | } 20 | 21 | src.Close() 22 | dest.Close() 23 | } 24 | 25 | func publicKeyFile(file string) (ssh.AuthMethod, error) { 26 | buffer, err := os.ReadFile(file) 27 | if err != nil { 28 | return nil, err 29 | } 30 | 31 | key, err := ssh.ParsePrivateKey(buffer) 32 | if err != nil { 33 | return nil, err 34 | } 35 | return ssh.PublicKeys(key), nil 36 | } 37 | 38 | func StartSSH(config *utils.ManagerSSHConfig, portAPI string, verbose, debug bool) { 39 | log.Println("SSH StartSSH") 40 | 41 | for ip, port := range config.IPPort { 42 | go func(ip, port string) { 43 | log.Println("SSH connecction", ip, port) 44 | 45 | if !checkFileExists(config.PrivateKeyPath) { 46 | log.Fatal("File ", config.PrivateKeyPath, " not found") 47 | } 48 | 49 | auth, err := publicKeyFile(config.PrivateKeyPath) 50 | if err != nil { 51 | log.Fatal("Error loading file ", config.PrivateKeyPath, err) 52 | } 53 | 54 | // SSH connection configuration 55 | sshConfig := &ssh.ClientConfig{ 56 | User: config.SSHUsername, 57 | Auth: []ssh.AuthMethod{ 58 | auth, 59 | }, 60 | HostKeyCallback: ssh.InsecureIgnoreHostKey(), 61 | } 62 | 63 | // If a password is provided, add it as an additional authentication method 64 | if config.PrivateKeyPassword != "" { 65 | sshConfig.Auth = append(sshConfig.Auth, ssh.Password(config.PrivateKeyPassword)) 66 | } 67 | 68 | // Connect to the SSH server 69 | sshClient, err := ssh.Dial("tcp", ip+":"+port, sshConfig) 70 | if err != nil { 71 | log.Fatalf("Failed to dial: %s", err) 72 | } 73 | 74 | // Remote port to forward 75 | remoteAddr := "127.0.0.1:" + portAPI 76 | // Local address to forward to 77 | localAddr := "127.0.0.1:" + portAPI 78 | 79 | if debug { 80 | log.Println("SSH remoteAddr", remoteAddr) 81 | } 82 | 83 | // Request remote port forwarding 84 | remoteListener, err := sshClient.Listen("tcp", remoteAddr) 85 | if err != nil { 86 | log.Fatalf("Failed to request remote port forwarding: %v", err) 87 | } 88 | defer remoteListener.Close() 89 | 90 | fmt.Printf("Remote port forwarding %s to %s via SSH...\n", remoteAddr, localAddr) 91 | 92 | for { 93 | // Wait for a connection on the remote port 94 | remoteConn, err := remoteListener.Accept() 95 | if err != nil { 96 | log.Fatalf("Failed to accept connection on remote port: %v", err) 97 | } 98 | 99 | // Connect to the local server 100 | localConn, err := net.Dial("tcp", localAddr) 101 | if err != nil { 102 | log.Printf("Failed to connect to local server: %v", err) 103 | remoteConn.Close() 104 | continue 105 | } 106 | 107 | // Start forwarding data between local and remote connections 108 | go forwardData(remoteConn, localConn) 109 | go forwardData(localConn, remoteConn) 110 | } 111 | }(ip, port) 112 | } 113 | } 114 | 115 | // ssh-keygen -t rsa -b 2048 116 | func checkFileExists(filePath string) bool { 117 | _, error := os.Stat(filePath) 118 | //return !os.IsNotExist(err) 119 | return !errors.Is(error, os.ErrNotExist) 120 | } 121 | -------------------------------------------------------------------------------- /manager/utils/disk.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "os" 7 | 8 | "github.com/r4ulcl/nTask/globalstructs" 9 | ) 10 | 11 | func SaveTaskToDisk(task globalstructs.Task, path string, verbose, debug bool) error { 12 | // Convert the struct to JSON format 13 | jsonData, err := json.MarshalIndent(task, "", " ") 14 | if err != nil { 15 | if verbose { 16 | log.Println("Utils Error marshaling JSON:", err) 17 | } 18 | return err 19 | } 20 | 21 | // Get date and time 22 | //currentTime := time.Now() 23 | // Specify the file path 24 | // filePath := path + "/" + task.ID + ".json" 25 | //filePath := fmt.Sprintf("%s/%s_%s.json", path, currentTime.Format("2006-01-02_15-04-05"), task.ID) 26 | filePath := path 27 | 28 | // Open the file for writing 29 | file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) 30 | if err != nil { 31 | if verbose { 32 | log.Println("Error creating file:", err) 33 | } 34 | return err 35 | } 36 | defer file.Close() 37 | 38 | // Write the JSON data to the file 39 | _, err = file.Write(jsonData) 40 | if err != nil { 41 | if verbose { 42 | log.Println("Error writing to file:", err) 43 | } 44 | return err 45 | } 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /manager/utils/manageTasks.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "database/sql" 5 | "log" 6 | "sync" 7 | "time" 8 | 9 | "github.com/r4ulcl/nTask/manager/database" 10 | ) 11 | 12 | func ManageTasks(config *ManagerConfig, db *sql.DB, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) { 13 | // infinite loop eecuted with go routine 14 | for { 15 | // Get all tasks in order and if priority 16 | tasks, err := database.GetTasksPending(100, db, verbose, debug) 17 | if err != nil { 18 | log.Println(err.Error()) 19 | } 20 | 21 | // Get iddle workers 22 | workers, err := database.GetWorkerIddle(db, verbose, debug) 23 | if err != nil { 24 | log.Println(err.Error()) 25 | } 26 | 27 | if debug { 28 | log.Println("Utils tasks", len(tasks)) 29 | log.Println("Utils workers", len(workers)) 30 | } 31 | 32 | // if there are tasks 33 | if len(tasks) > 0 && len(workers) > 0 { 34 | for _, task := range tasks { 35 | for _, worker := range workers { 36 | // if WorkerName not send or set this worker, just sendAddTask 37 | if task.WorkerName == "" || task.WorkerName == worker.Name { 38 | err = SendAddTask(db, config, &worker, &task, verbose, debug, wg, writeLock) 39 | if err != nil { 40 | log.Println("Utils Error SendAddTask", err.Error()) 41 | //time.Sleep(time.Second * 1) 42 | break 43 | } 44 | } 45 | } 46 | // Update iddle workers after loop all 47 | workers, err = database.GetWorkerIddle(db, verbose, debug) 48 | if err != nil { 49 | log.Println(err.Error()) 50 | } 51 | // If no workers just start again 52 | if len(workers) == 0 { 53 | break 54 | } 55 | } 56 | } else { 57 | if len(tasks) == 0 { 58 | time.Sleep(time.Second * 1) 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /manager/utils/stats.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "database/sql" 5 | 6 | "github.com/r4ulcl/nTask/manager/database" 7 | ) 8 | 9 | func GetStatusTask(db *sql.DB, verbose, debug bool) (StatusTask, error) { 10 | task := StatusTask{ 11 | Pending: 0, 12 | Running: 0, 13 | Done: 0, 14 | Failed: 0, 15 | Deleted: 0, 16 | } 17 | 18 | pending, err := database.GetPendingCount(db, verbose, debug) 19 | if err != nil { 20 | return task, err 21 | } 22 | task.Pending = pending 23 | 24 | running, err := database.GetRunningCount(db, verbose, debug) 25 | if err != nil { 26 | return task, err 27 | } 28 | task.Running = running 29 | 30 | done, err := database.GetDoneCount(db, verbose, debug) 31 | if err != nil { 32 | return task, err 33 | } 34 | task.Done = done 35 | 36 | failed, err := database.GetFailedCount(db, verbose, debug) 37 | if err != nil { 38 | return task, err 39 | } 40 | task.Failed = failed 41 | 42 | deleted, err := database.GetDeletedCount(db, verbose, debug) 43 | if err != nil { 44 | return task, err 45 | } 46 | task.Deleted = deleted 47 | 48 | return task, nil 49 | } 50 | 51 | func GetStatusWorker(db *sql.DB, verbose, debug bool) (StatusWorker, error) { 52 | worker := StatusWorker{ 53 | Up: 0, 54 | Down: 0, 55 | } 56 | 57 | up, err := database.GetUpCount(db, verbose, debug) 58 | if err != nil { 59 | return worker, err 60 | } 61 | worker.Up = up 62 | 63 | down, err := database.GetDownCount(db, verbose, debug) 64 | if err != nil { 65 | return worker, err 66 | } 67 | worker.Down = down 68 | return worker, nil 69 | } 70 | -------------------------------------------------------------------------------- /manager/utils/structs.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gorilla/websocket" 7 | ) 8 | 9 | type ManagerConfig struct { 10 | Users map[string]string `json:"users"` 11 | Workers map[string]string `json:"workers"` 12 | Port string `json:"port"` 13 | DBUsername string `json:"dbUsername"` 14 | DBPassword string `json:"dbPassword"` 15 | DBHost string `json:"dbHost"` 16 | DBPort string `json:"dbPort"` 17 | DBDatabase string `json:"dbDatabase"` 18 | StatusCheckSeconds int `json:"statusCheckSeconds"` 19 | StatusCheckDown int `json:"statusCheckDown"` 20 | DiskPath string `json:"diskPath"` 21 | CertFolder string `json:"certFolder"` 22 | ClientHTTP *http.Client `json:"clientHTTP"` 23 | WebSockets map[string]*websocket.Conn `json:"webSockets"` 24 | } 25 | 26 | type ManagerSSHConfig struct { 27 | IPPort map[string]string `json:"ipPort"` 28 | SSHUsername string `json:"sshUsername"` 29 | PrivateKeyPath string `json:"privateKeyPath"` 30 | PrivateKeyPassword string `json:"privateKeyPassword"` 31 | } 32 | 33 | type Status struct { 34 | Task StatusTask `json:"task"` 35 | Worker StatusWorker `json:"worker"` 36 | } 37 | 38 | type StatusTask struct { 39 | Pending int `json:"pending"` 40 | Running int `json:"running"` 41 | Done int `json:"done"` 42 | Failed int `json:"failed"` 43 | Deleted int `json:"deleted"` 44 | } 45 | 46 | type StatusWorker struct { 47 | Up int `json:"up"` 48 | Down int `json:"down"` 49 | } 50 | -------------------------------------------------------------------------------- /manager/utils/userRequest.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "net/http" 8 | 9 | globalstructs "github.com/r4ulcl/nTask/globalstructs" 10 | ) 11 | 12 | // CallbackUserTaskMessage is a function that sends a task message as a callback to a specified URL 13 | func CallbackUserTaskMessage(config *ManagerConfig, task *globalstructs.Task, verbose, debug bool) { 14 | url := task.CallbackURL 15 | 16 | // Convert the task to a JSON payload 17 | payload, _ := json.Marshal(task) 18 | 19 | // Create a new request with the POST method and the payload 20 | req, err := http.NewRequest("POST", url, bytes.NewBuffer(payload)) 21 | if err != nil { 22 | log.Println("Utils Error creating request:", err) 23 | return 24 | } 25 | 26 | // Add custom headers, including the OAuth header 27 | req.Header.Set("Content-Type", "application/json") 28 | if task.CallbackToken != "" { 29 | req.Header.Set("Authorization", task.CallbackToken) 30 | } 31 | 32 | // Create an HTTP client and make the request 33 | resp, err := config.ClientHTTP.Do(req) 34 | if err != nil { 35 | if verbose { 36 | log.Println("Utils config.ClientHTTP.Do(req)", err) 37 | } 38 | return 39 | } 40 | defer resp.Body.Close() 41 | 42 | if debug { 43 | log.Println("Utils Status Code:", resp.Status) 44 | } 45 | // Handle the response body as needed 46 | } 47 | -------------------------------------------------------------------------------- /manager/utils/workerRequest.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "database/sql" 7 | "encoding/json" 8 | "fmt" 9 | "log" 10 | "net/http" 11 | "os" 12 | "sync" 13 | "time" 14 | 15 | "github.com/gorilla/websocket" 16 | globalstructs "github.com/r4ulcl/nTask/globalstructs" 17 | "github.com/r4ulcl/nTask/manager/database" 18 | ) 19 | 20 | func SendMessage(conn *websocket.Conn, message []byte, verbose, debug bool, writeLock *sync.Mutex) error { 21 | writeLock.Lock() 22 | defer writeLock.Unlock() 23 | if debug { 24 | log.Println("Utils SendMessage", string(message)) 25 | } 26 | // check if websocket alive 27 | err := conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(5*time.Second)) 28 | if err != nil { 29 | log.Println("Utils Error in websocket", string(message)) 30 | return err 31 | } 32 | 33 | err = conn.WriteMessage(websocket.TextMessage, message) 34 | if err != nil { 35 | return err 36 | } 37 | if debug { 38 | log.Println("Utils SendMessage OK", string(message)) 39 | } 40 | return nil 41 | } 42 | 43 | // VerifyWorkersLoop checks and sets if the workers are UP infinitely. 44 | func VerifyWorkersLoop(db *sql.DB, config *ManagerConfig, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) { 45 | for { 46 | go verifyWorkers(db, config, verbose, debug, wg, writeLock) 47 | time.Sleep(time.Duration(config.StatusCheckSeconds) * time.Second) 48 | } 49 | } 50 | 51 | // verifyWorkers checks and sets if the workers are UP. 52 | func verifyWorkers(db *sql.DB, config *ManagerConfig, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) { 53 | // Get all UP workers from the database 54 | workers, err := database.GetWorkers(db, verbose, debug) 55 | if err != nil { 56 | log.Print("GetWorkerUP", err) 57 | } 58 | 59 | // Verify each worker 60 | for _, worker := range workers { 61 | err := verifyWorker(db, config, &worker, verbose, debug, wg, writeLock) 62 | if err != nil { 63 | log.Print("verifyWorker ", err) 64 | } 65 | } 66 | } 67 | 68 | // verifyWorker checks and sets if the worker is UP. 69 | func verifyWorker(db *sql.DB, config *ManagerConfig, worker *globalstructs.Worker, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) error { 70 | if debug { 71 | log.Println("Utils verifyWorker", worker.Name) 72 | } 73 | conn := config.WebSockets[worker.Name] 74 | if conn == nil { 75 | if debug { 76 | log.Println("Utils Error: The worker doesnt have a websocket", worker.Name) 77 | } 78 | 79 | delete(config.WebSockets, worker.Name) 80 | 81 | err := database.SetWorkerUPto(false, db, worker, verbose, debug, wg) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | downCount, err := database.GetWorkerDownCount(db, worker, verbose, debug) 87 | if err != nil { 88 | return err 89 | } 90 | 91 | if downCount >= config.StatusCheckDown { 92 | if debug { 93 | log.Println("Utils downCount", downCount, " >= config.StatusCheckDown", config.StatusCheckDown) 94 | } 95 | err = database.RmWorkerName(db, worker.Name, verbose, debug, wg) 96 | if err != nil { 97 | return err 98 | } 99 | } else { 100 | err = database.AddWorkerDownCount(db, worker, verbose, debug, wg) 101 | if err != nil { 102 | return err 103 | } 104 | } 105 | 106 | // Set as 'pending' all workers tasks to REDO 107 | err = database.SetTasksWorkerPending(db, worker.Name, verbose, debug, wg) 108 | if err != nil { 109 | return err 110 | } 111 | return nil 112 | } 113 | 114 | msg := globalstructs.WebsocketMessage{ 115 | Type: "status", 116 | JSON: "{}", 117 | } 118 | 119 | jsonData, err := json.Marshal(msg) 120 | if err != nil { 121 | if debug { 122 | log.Println("Utils Error: json.Marshal(msg):", err) 123 | } 124 | return err 125 | } 126 | 127 | err = SendMessage(conn, jsonData, verbose, debug, writeLock) 128 | if err != nil { 129 | if debug { 130 | log.Println("Utils Can't send message, error:", err) 131 | } 132 | err = WorkerDisconnected(db, config, worker, verbose, debug, wg) 133 | if err != nil { 134 | return err 135 | } 136 | return err 137 | } 138 | 139 | err = database.SetWorkerDownCount(0, db, worker, verbose, debug, wg) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | return nil 145 | 146 | } 147 | 148 | // SendAddTask sends a request to a worker to add a task. 149 | func SendAddTask(db *sql.DB, config *ManagerConfig, worker *globalstructs.Worker, task *globalstructs.Task, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) error { 150 | if debug { 151 | log.Println("Utils SendAddTask") 152 | } 153 | //Sustract 1 Iddle Thread in worker 154 | err := database.SubtractWorkerIddleThreads1(db, worker.Name, verbose, debug, wg) 155 | if err != nil { 156 | return err 157 | } 158 | // add 1 on callback 159 | 160 | conn := config.WebSockets[worker.Name] 161 | if conn == nil { 162 | return fmt.Errorf("Error, websocket not found") 163 | } 164 | 165 | // Set workerName in DB and in object 166 | task.WorkerName = worker.Name 167 | 168 | // Tast to json 169 | // Convert the struct to JSON 170 | jsonDataTask, err := json.Marshal(task) 171 | if err != nil { 172 | return err 173 | } 174 | 175 | msg := globalstructs.WebsocketMessage{ 176 | Type: "addTask", 177 | JSON: string(jsonDataTask), 178 | } 179 | 180 | jsonData, err := json.Marshal(msg) 181 | if err != nil { 182 | return err 183 | } 184 | 185 | err = SendMessage(conn, jsonData, verbose, debug, writeLock) 186 | if err != nil { 187 | if debug { 188 | log.Println("Utils Can't send message, error:", err) 189 | } 190 | err = WorkerDisconnected(db, config, worker, verbose, debug, wg) 191 | if err != nil { 192 | return err 193 | } 194 | return err 195 | } 196 | 197 | // Set task as running 198 | err = database.SetTaskStatus(db, task.ID, "running", verbose, debug, wg) 199 | if err != nil { 200 | log.Println("Utils Error SetTaskStatus in request:", err) 201 | } 202 | 203 | // Set task as executed 204 | err = database.SetTaskExecutedAtNow(db, task.ID, verbose, debug, wg) 205 | if err != nil { 206 | return fmt.Errorf("Error SetTaskExecutedAt in request: %s", err) 207 | } 208 | 209 | // Set workerName in DB and in object 210 | err = database.SetTaskWorkerName(db, task.ID, worker.Name, verbose, debug, wg) 211 | if err != nil { 212 | return fmt.Errorf("Error SetWorkerNameTask in request: %s", err) 213 | } 214 | 215 | if verbose { 216 | log.Println("Utils Task send successfully") 217 | } 218 | 219 | return nil 220 | } 221 | 222 | // SendDeleteTask sends a request to a worker to stop and delete a task. 223 | func SendDeleteTask(db *sql.DB, config *ManagerConfig, worker *globalstructs.Worker, task *globalstructs.Task, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) error { 224 | conn := config.WebSockets[worker.Name] 225 | if conn == nil { 226 | return fmt.Errorf("Error, websocket not found") 227 | } 228 | 229 | // Tast to json 230 | // Convert the struct to JSON 231 | jsonDataTask, err := json.Marshal(task) 232 | if err != nil { 233 | return err 234 | } 235 | 236 | msg := globalstructs.WebsocketMessage{ 237 | Type: "deleteTask", 238 | JSON: string(jsonDataTask), 239 | } 240 | 241 | jsonData, err := json.Marshal(msg) 242 | if err != nil { 243 | return err 244 | } 245 | 246 | err = SendMessage(conn, jsonData, verbose, debug, writeLock) 247 | if err != nil { 248 | if debug { 249 | log.Println("Utils Can't send message, error:", err) 250 | } 251 | err = WorkerDisconnected(db, config, worker, verbose, debug, wg) 252 | if err != nil { 253 | return err 254 | } 255 | return err 256 | } 257 | 258 | // Set the task and worker as not working 259 | err = database.SetTaskStatus(db, task.ID, "deleted", verbose, debug, wg) 260 | if err != nil { 261 | return err 262 | } 263 | err = database.SubtractWorkerIddleThreads1(db, worker.Name, verbose, debug, wg) 264 | if err != nil { 265 | return err 266 | } 267 | 268 | if verbose { 269 | log.Println("Utils Delete Task send successfully") 270 | } 271 | 272 | return nil 273 | } 274 | 275 | // CreateTLSClientWithCACert from cert.pem 276 | func CreateTLSClientWithCACert(caCertPath string, verifyAltName, verbose, debug bool) (*http.Client, error) { 277 | // Load CA certificate from file 278 | caCert, err := os.ReadFile(caCertPath) 279 | if err != nil { 280 | fmt.Printf("Failed to read CA certificate file: %v\n", err) 281 | return nil, err 282 | } 283 | 284 | // Create a certificate pool and add the CA certificate 285 | certPool := x509.NewCertPool() 286 | certPool.AppendCertsFromPEM(caCert) 287 | 288 | // Replace 'cert' with the expected certificate that the server should present 289 | //var cert *x509.Certificate 290 | 291 | var tlsConfig *tls.Config 292 | 293 | // Create a TLS configuration with the custom VerifyPeerCertificate function 294 | if !verifyAltName { 295 | tlsConfig = &tls.Config{ 296 | InsecureSkipVerify: true, // Enable server verification 297 | RootCAs: certPool, 298 | VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 299 | if len(rawCerts) == 0 { 300 | return fmt.Errorf("no certificates provided by the server") 301 | } 302 | 303 | serverCert, err := x509.ParseCertificate(rawCerts[0]) 304 | if err != nil { 305 | return fmt.Errorf("failed to parse server certificate: %v", err) 306 | } 307 | 308 | // Verify the server certificate against the CA certificate 309 | opts := x509.VerifyOptions{ 310 | Roots: certPool, 311 | Intermediates: x509.NewCertPool(), 312 | } 313 | _, err = serverCert.Verify(opts) 314 | if err != nil { 315 | return fmt.Errorf("failed to verify server certificate: %v", err) 316 | } 317 | 318 | return nil 319 | }, 320 | } 321 | } else { 322 | log.Println("Utils verifyAltName YES", !verifyAltName) 323 | 324 | tlsConfig = &tls.Config{ 325 | InsecureSkipVerify: false, // Ensure that server verification is enabled 326 | RootCAs: certPool, 327 | MinVersion: tls.VersionTLS12, // Set the desired minimum TLS version 328 | } 329 | } 330 | 331 | // Create HTTP client with TLS 332 | client := &http.Client{ 333 | Transport: &http.Transport{ 334 | TLSClientConfig: tlsConfig, 335 | }, 336 | } 337 | 338 | return client, nil 339 | } 340 | 341 | func WorkerDisconnected(db *sql.DB, config *ManagerConfig, worker *globalstructs.Worker, verbose, debug bool, wg *sync.WaitGroup) error { 342 | if debug { 343 | log.Println("Utils Error: WriteControl cant connect", worker.Name) 344 | } 345 | // Close connection 346 | config.WebSockets[worker.Name].Close() 347 | 348 | delete(config.WebSockets, worker.Name) 349 | 350 | err := database.SetWorkerUPto(false, db, worker, verbose, debug, wg) 351 | if err != nil { 352 | return err 353 | } 354 | 355 | // Set as 'pending' all workers tasks to REDO 356 | err = database.SetTasksWorkerPending(db, worker.Name, verbose, debug, wg) 357 | if err != nil { 358 | return err 359 | } 360 | 361 | return nil 362 | } 363 | -------------------------------------------------------------------------------- /manager/websockets/websockets.go: -------------------------------------------------------------------------------- 1 | package websockets 2 | 3 | import ( 4 | "database/sql" 5 | "encoding/json" 6 | "fmt" 7 | "log" 8 | "sync" 9 | 10 | "github.com/go-sql-driver/mysql" 11 | "github.com/gorilla/websocket" 12 | "github.com/r4ulcl/nTask/globalstructs" 13 | "github.com/r4ulcl/nTask/manager/database" 14 | "github.com/r4ulcl/nTask/manager/utils" 15 | ) 16 | 17 | func GetWorkerMessage(conn *websocket.Conn, config *utils.ManagerConfig, db *sql.DB, verbose, debug bool, wg *sync.WaitGroup, writeLock *sync.Mutex) { 18 | var worker globalstructs.Worker 19 | for { 20 | response := globalstructs.WebsocketMessage{ 21 | Type: "", 22 | JSON: "", 23 | } 24 | 25 | _, p, err := conn.ReadMessage() 26 | if err != nil { 27 | // if the clients conexion is down, this is the first error 28 | if debug { 29 | log.Println("WebSockets client conexion down error: ", err) 30 | } 31 | // check if worker not init 32 | if worker != (globalstructs.Worker{}) { 33 | err = utils.WorkerDisconnected(db, config, &worker, verbose, debug, wg) 34 | if err != nil { 35 | if debug { 36 | log.Println("WebSockets WorkerDisconnected error: ", err) 37 | } 38 | } 39 | } else { 40 | if debug { 41 | log.Println("WebSockets Worker empty") 42 | } 43 | } 44 | return 45 | } 46 | 47 | var msg globalstructs.WebsocketMessage 48 | err = json.Unmarshal(p, &msg) 49 | if err != nil { 50 | if debug { 51 | log.Println("WebSockets Error decoding JSON:", err) 52 | } 53 | continue 54 | } 55 | 56 | switch msg.Type { 57 | case "addWorker": 58 | if debug { 59 | log.Println("WebSockets msg.Type", msg.Type) 60 | log.Println("WebSockets msg.JSON", msg.JSON) 61 | } 62 | 63 | err = json.Unmarshal([]byte(msg.JSON), &worker) 64 | if err != nil { 65 | log.Println("WebSockets addWorker Unmarshal error: ", err) 66 | } 67 | // add con to worker 68 | err = addWorker(worker, db, verbose, debug, wg) 69 | if err != nil { 70 | log.Println("WebSockets addWorker error: ", err) 71 | response.Type = "FAILED" 72 | } else { 73 | response.Type = "OK" 74 | config.WebSockets[worker.Name] = conn 75 | } 76 | 77 | case "deleteWorker": 78 | if debug { 79 | log.Println(msg.Type) 80 | } 81 | err = json.Unmarshal([]byte(msg.JSON), &worker) 82 | if err != nil { 83 | log.Println("WebSockets deleteWorker Unmarshal error: ", err) 84 | } 85 | 86 | err = database.RmWorkerName(db, worker.Name, verbose, debug, wg) 87 | if err != nil { 88 | log.Println("WebSockets RmWorkerName error: ", err) 89 | response.Type = "FAILED" 90 | } else { 91 | response.Type = "OK" 92 | config.WebSockets[worker.Name].Close() 93 | delete(config.WebSockets, worker.Name) 94 | } 95 | 96 | // Set the tasks as failed 97 | err := database.SetTasksWorkerPending(db, worker.Name, verbose, debug, wg) 98 | if err != nil { 99 | log.Println("WebSockets SetTasksWorkerFailed error: ", err) 100 | } 101 | case "callbackTask": 102 | if debug { 103 | log.Println("WebSockets msg.Type", msg.Type) 104 | log.Println("WebSockets msg.JSON", msg.JSON) 105 | } 106 | 107 | var result globalstructs.Task 108 | err = json.Unmarshal([]byte(msg.JSON), &result) 109 | if err != nil { 110 | log.Println("WebSockets addWorker Unmarshal error: ", err) 111 | } 112 | 113 | err = callback(result, config, db, verbose, debug, wg) 114 | 115 | if err != nil { 116 | log.Println("WebSockets callbackTask error: ", err) 117 | } 118 | 119 | //Responses 120 | 121 | case "OK;addTask": 122 | if debug { 123 | log.Println("WebSockets msg.Type", msg.Type) 124 | log.Println("WebSockets msg.JSON", msg.JSON) 125 | } 126 | var result globalstructs.Task 127 | err = json.Unmarshal([]byte(msg.JSON), &result) 128 | if err != nil { 129 | log.Println("WebSockets addWorker Unmarshal error: ", err) 130 | } 131 | 132 | // Set task as executed 133 | err = database.SetTaskExecutedAtNow(db, result.ID, verbose, debug, wg) 134 | if err != nil { 135 | log.Println("WebSockets Error SetTaskExecutedAt in request:", err) 136 | } 137 | 138 | // Set workerName in DB and in object 139 | err = database.SetTaskWorkerName(db, result.ID, result.WorkerName, verbose, debug, wg) 140 | if err != nil { 141 | log.Println("WebSockets Error SetWorkerNameTask in request:", err) 142 | } 143 | 144 | if verbose { 145 | log.Println("WebSockets Task send successfully") 146 | } 147 | case "FAILED;addTask": 148 | if debug { 149 | log.Println("WebSockets msg.Type", msg.Type) 150 | log.Println("WebSockets msg.JSON", msg.JSON) 151 | } 152 | 153 | var result globalstructs.Task 154 | err = json.Unmarshal([]byte(msg.JSON), &result) 155 | if err != nil { 156 | log.Println("WebSockets addWorker Unmarshal error: ", err) 157 | } 158 | 159 | // Set the task as pending because the worker return error in add, so its not been procesed 160 | err = database.SetTaskStatus(db, result.ID, "pending", verbose, debug, wg) 161 | if err != nil { 162 | if verbose { 163 | log.Println("WebSockets HandleCallback { \"error\" : \"Error SetTaskStatus: " + err.Error() + "\"}") 164 | } 165 | log.Println("WebSockets Error SetTaskStatus in request:", err) 166 | } 167 | case "OK;deleteTask": 168 | if debug { 169 | log.Println("WebSockets msg.Type", msg.Type) 170 | log.Println("WebSockets msg.JSON", msg.JSON) 171 | } 172 | case "FAILED;deleteTask": 173 | if debug { 174 | log.Println("WebSockets msg.Type", msg.Type) 175 | log.Println("WebSockets msg.JSON", msg.JSON) 176 | } 177 | log.Println("WebSockets ------------------ TODO FAILED;deleteTask") 178 | case "status": 179 | if debug { 180 | log.Println("WebSockets msg.Type", msg.Type) 181 | log.Println("WebSockets msg.JSON", msg.JSON) 182 | } 183 | if msg.Type == "status" { 184 | // Unmarshal the JSON into a WorkerStatus struct 185 | var status globalstructs.WorkerStatus 186 | err = json.Unmarshal([]byte(msg.JSON), &status) 187 | if err != nil { 188 | log.Println("WebSockets status Unmarshal error: ", err) 189 | } 190 | 191 | if verbose { 192 | log.Println("WebSockets Response status from worker", status.Name, msg.JSON) 193 | } 194 | worker, err := database.GetWorker(db, status.Name, verbose, debug) 195 | if err != nil { 196 | log.Println("WebSockets GetWorker error: ", err) 197 | } 198 | // If there is no error in making the request, assume worker is online 199 | err = database.SetWorkerUPto(true, db, &worker, verbose, debug, wg) 200 | if err != nil { 201 | log.Println("WebSockets status error: ", err) 202 | } 203 | 204 | // If worker IddleThreads is not the same as stored in the DB, update the DB 205 | if status.IddleThreads != worker.IddleThreads { 206 | err := database.SetIddleThreadsTo(status.IddleThreads, db, worker.Name, verbose, debug, wg) 207 | if err != nil { 208 | log.Println("WebSockets status SetIddleThreadsTo error: ", err) 209 | } 210 | } 211 | } 212 | } 213 | 214 | if debug { 215 | fmt.Printf("Received message type: %s\n", msg.Type) 216 | fmt.Printf("Received message json: %s\n", msg.JSON) 217 | } 218 | 219 | if response.Type != "" { 220 | jsonData, err := json.Marshal(response) 221 | if err != nil { 222 | log.Println("WebSockets Marshal error: ", err) 223 | } 224 | err = utils.SendMessage(conn, jsonData, verbose, debug, writeLock) 225 | if err != nil { 226 | log.Println("WebSockets SendMessage error: ", err) 227 | } 228 | } 229 | } 230 | } 231 | 232 | func addWorker(worker globalstructs.Worker, db *sql.DB, verbose, debug bool, wg *sync.WaitGroup) error { 233 | 234 | if debug { 235 | log.Println("WebSockets worker.Name", worker.Name) 236 | } 237 | 238 | err := database.AddWorker(db, &worker, verbose, debug, wg) 239 | if err != nil { 240 | if mysqlErr, ok := err.(*mysql.MySQLError); ok { 241 | if mysqlErr.Number == 1062 { // MySQL error number for duplicate entry 242 | // Set as 'pending' all workers tasks to REDO 243 | err = database.SetTasksWorkerPending(db, worker.Name, verbose, debug, wg) 244 | if err != nil { 245 | return err 246 | } 247 | 248 | // set worker up 249 | err = database.SetWorkerUPto(true, db, &worker, verbose, debug, wg) 250 | if err != nil { 251 | return err 252 | } 253 | 254 | // reset down count 255 | err = database.SetWorkerDownCount(0, db, &worker, verbose, debug, wg) 256 | if err != nil { 257 | return err 258 | } 259 | } 260 | return err 261 | 262 | } 263 | 264 | } 265 | 266 | return nil 267 | } 268 | 269 | func callback(result globalstructs.Task, config *utils.ManagerConfig, db *sql.DB, verbose, debug bool, wg *sync.WaitGroup) error { 270 | 271 | if debug { 272 | log.Println("WebSockets result: ", result) 273 | log.Println("WebSockets Received result (ID: ", result.ID, " from : ", result.WorkerName, " with command: ", result.Commands) 274 | } 275 | 276 | // Update task with the worker one 277 | err := database.UpdateTask(db, result, verbose, debug, wg) 278 | if err != nil { 279 | if verbose { 280 | log.Println("WebSockets HandleCallback { \"error\" : \"Error UpdateTask: " + err.Error() + "\"}") 281 | } 282 | 283 | return err 284 | } 285 | 286 | // force set task to status receive 287 | // Set the task as done 288 | if result.Status == "failed" { 289 | err = database.SetTaskStatus(db, result.ID, result.Status, verbose, debug, wg) 290 | if err != nil { 291 | if verbose { 292 | log.Println("WebSockets HandleCallback { \"error\" : \"Error SetTaskStatus: " + err.Error() + "\"}") 293 | } 294 | return err 295 | } 296 | } else { 297 | err = database.SetTaskStatus(db, result.ID, "done", verbose, debug, wg) 298 | if err != nil { 299 | if verbose { 300 | log.Println("WebSockets HandleCallback { \"error\" : \"Error SetTaskStatus: " + err.Error() + "\"}") 301 | } 302 | return err 303 | } 304 | } 305 | 306 | // if callbackURL is not empty send the request to the client 307 | if result.CallbackURL != "" { 308 | utils.CallbackUserTaskMessage(config, &result, verbose, debug) 309 | } 310 | 311 | // if path not empty 312 | if config.DiskPath != "" { 313 | //get the task from DB to get updated 314 | task, err := database.GetTask(db, result.ID, verbose, debug) 315 | if err != nil { 316 | return err 317 | } 318 | err = utils.SaveTaskToDisk(task, config.DiskPath, verbose, debug) 319 | if err != nil { 320 | return err 321 | } 322 | } 323 | 324 | // Handle the result as needed 325 | 326 | //Add 1 to Iddle thread in worker 327 | // add 1 when finish 328 | err = database.AddWorkerIddleThreads1(db, result.WorkerName, verbose, debug, wg) 329 | if err != nil { 330 | return err 331 | } 332 | 333 | return nil 334 | } 335 | -------------------------------------------------------------------------------- /output/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/output/.gitkeep -------------------------------------------------------------------------------- /resources/nTask-diagram.drawio: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /resources/nTask-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask-diagram.png -------------------------------------------------------------------------------- /resources/nTask-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask-small.png -------------------------------------------------------------------------------- /resources/nTask-swagger-addTask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask-swagger-addTask.png -------------------------------------------------------------------------------- /resources/nTask-swagger-functions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask-swagger-functions.png -------------------------------------------------------------------------------- /resources/nTask-swagger-getTasks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask-swagger-getTasks.png -------------------------------------------------------------------------------- /resources/nTask-swagger-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask-swagger-status.png -------------------------------------------------------------------------------- /resources/nTask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/r4ulcl/nTask/d93108cd4fe92d987f21632b2d923fe6d56d3f98/resources/nTask.png -------------------------------------------------------------------------------- /ssh.conf: -------------------------------------------------------------------------------- 1 | { 2 | "ipPort": { 3 | "" : "22", 4 | "" : "22", 5 | "" : "22" 6 | }, 7 | "sshUsername": "root", 8 | "privateKeyPath": "~/.ssh/ssh_key", 9 | "privateKeyPassword": "" 10 | } -------------------------------------------------------------------------------- /worker.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "iddleThreads": 2, 4 | "managerIP" : "nTask_manager", 5 | "managerPort" : "8080", 6 | "managerOauthToken": "IeH0vpYFz2Yol6RdLvYZz62TFMv5FF", 7 | "CA": "./certs/ca-cert.pem", 8 | "insecureModules": false, 9 | "modules": { 10 | "sleep": "/usr/bin/sleep", 11 | "curl": "/usr/bin/curl", 12 | "cat": "/usr/bin/cat", 13 | "echo": "/usr/bin/echo", 14 | "grep": "/usr/bin/grep", 15 | "rm": "/usr/bin/rm", 16 | "nmap": "nmap", 17 | "nmapIPs": "bash ./worker/modules/nmapIPs.sh", 18 | "touch": "/usr/bin/touch", 19 | "exec": "" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /worker/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | # STEP 1 build executable binary 3 | FROM golang:alpine as builder 4 | 5 | # copy files for compile 6 | COPY ./certs $GOPATH/src/github.com/r4ulcl/nTask/certs 7 | COPY ./docs $GOPATH/src/github.com/r4ulcl/nTask/docs 8 | COPY ./globalstructs $GOPATH/src/github.com/r4ulcl/nTask/globalstructs 9 | COPY ./go.mod $GOPATH/src/github.com/r4ulcl/nTask/go.mod 10 | COPY ./go.sum $GOPATH/src/github.com/r4ulcl/nTask/go.sum 11 | COPY ./main.go $GOPATH/src/github.com/r4ulcl/nTask/main.go 12 | COPY ./manager $GOPATH/src/github.com/r4ulcl/nTask/manager 13 | COPY ./worker $GOPATH/src/github.com/r4ulcl/nTask/worker 14 | 15 | WORKDIR $GOPATH/src/github.com/r4ulcl/nTask 16 | #get dependancies 17 | #RUN apk -U add alpine-sdk 18 | #RUN go get -d -v 19 | #build the binary 20 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -installsuffix cgo -ldflags '-w -s' -o /go/bin/nTask 21 | 22 | #create config folder 23 | RUN mkdir /config 24 | 25 | # STEP 2 build a small image 26 | # start from kali for worker 27 | FROM kalilinux/kali-rolling 28 | #GOPATH doesn-t exists in scratch 29 | ENV GOPATH='/go' 30 | 31 | RUN apt-get update && apt-get install procps net-tools curl nmap -y 32 | RUN apt-get install -y kali-tools-fuzzing 33 | 34 | # Copy our static executable 35 | COPY --from=builder /$GOPATH/bin/nTask /$GOPATH/bin/nTask 36 | # Copy modules 37 | COPY --from=builder $GOPATH/src/github.com/r4ulcl/nTask/worker/modules/ /config/modules/ 38 | 39 | # Copy swagger 40 | COPY --from=builder $GOPATH/src/github.com/r4ulcl/nTask/docs/ /config/docs/ 41 | 42 | 43 | # Set config folder 44 | WORKDIR /config 45 | 46 | ENTRYPOINT ["/go/bin/nTask"] -------------------------------------------------------------------------------- /worker/managerrequest/managerRequest.go: -------------------------------------------------------------------------------- 1 | package managerrequest 2 | 3 | import ( 4 | "encoding/json" 5 | "log" 6 | "net/http" 7 | "sync" 8 | 9 | "github.com/gorilla/websocket" 10 | globalstructs "github.com/r4ulcl/nTask/globalstructs" 11 | "github.com/r4ulcl/nTask/worker/utils" 12 | ) 13 | 14 | func CreateWebsocket(config *utils.WorkerConfig, caCertPath string, 15 | verifyAltName, verbose, debug bool) (*websocket.Conn, error) { 16 | 17 | headers := make(http.Header) 18 | headers.Set("Authorization", config.ManagerOauthToken) 19 | 20 | var serverAddr string 21 | if transport, ok := config.ClientHTTP.Transport.(*http.Transport); ok { 22 | if transport.TLSClientConfig != nil { 23 | serverAddr = "wss://" + config.ManagerIP + ":" + config.ManagerPort + "/worker/websocket" 24 | } else { 25 | serverAddr = "ws://" + config.ManagerIP + ":" + config.ManagerPort + "/worker/websocket" 26 | } 27 | } else { 28 | serverAddr = "wss://" + config.ManagerIP + ":" + config.ManagerPort + "/worker/websocket" 29 | } 30 | 31 | if debug { 32 | log.Println("ManagerRequest serverAddr", serverAddr) 33 | } 34 | 35 | //tlsConfig := &tls.Config{InsecureSkipVerify: false} // InsecureSkipVerify is used for testing purposes only 36 | 37 | tlsConfig, err := utils.GenerateTLSConfig(caCertPath, verifyAltName, verbose, debug) 38 | if err != nil { 39 | if debug { 40 | log.Println("ManagerRequest Error reading worker config file: ", err) 41 | } 42 | return nil, err 43 | } 44 | 45 | dialer := websocket.Dialer{ 46 | TLSClientConfig: tlsConfig, 47 | Subprotocols: []string{"chat"}, 48 | } 49 | 50 | conn, _, err := dialer.Dial(serverAddr, headers) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | return conn, nil 56 | } 57 | 58 | func SendMessage(conn *websocket.Conn, message []byte, verbose, debug bool, writeLock *sync.Mutex) error { 59 | writeLock.Lock() 60 | defer writeLock.Unlock() 61 | if debug { 62 | log.Println("sendMessage", string(message)) 63 | } 64 | err := conn.WriteMessage(websocket.TextMessage, message) 65 | if err != nil { 66 | return err 67 | } 68 | return nil 69 | } 70 | 71 | // AddWorker sends a POST request to add a worker to the manager 72 | func AddWorker(config *utils.WorkerConfig, verbose, debug bool, writeLock *sync.Mutex) error { 73 | // Create a Worker object with the provided configuration 74 | worker := globalstructs.Worker{ 75 | Name: config.Name, 76 | IddleThreads: config.IddleThreads, 77 | UP: true, 78 | DownCount: 0, 79 | } 80 | 81 | // Marshal the worker object into JSON 82 | payload, _ := json.Marshal(worker) 83 | 84 | msg := globalstructs.WebsocketMessage{ 85 | Type: "addWorker", 86 | JSON: string(payload), 87 | } 88 | 89 | jsonData, err := json.Marshal(msg) 90 | if err != nil { 91 | log.Println("Error encoding JSON:", err) 92 | return err 93 | } 94 | 95 | err = SendMessage(config.Conn, jsonData, verbose, debug, writeLock) 96 | if err != nil { 97 | return err 98 | } 99 | 100 | return nil 101 | } 102 | 103 | // AddWorker sends a POST request to add a worker to the manager 104 | func DeleteWorker(config *utils.WorkerConfig, verbose, debug bool, writeLock *sync.Mutex) error { 105 | // Create a Worker object with the provided configuration 106 | worker := globalstructs.Worker{ 107 | Name: config.Name, 108 | IddleThreads: config.IddleThreads, 109 | UP: true, 110 | DownCount: 0, 111 | } 112 | 113 | // Marshal the worker object into JSON 114 | payload, _ := json.Marshal(worker) 115 | 116 | msg := globalstructs.WebsocketMessage{ 117 | Type: "deleteWorker", 118 | JSON: string(payload), 119 | } 120 | 121 | jsonData, err := json.Marshal(msg) 122 | if err != nil { 123 | log.Println("Error encoding JSON:", err) 124 | return err 125 | } 126 | 127 | err = SendMessage(config.Conn, jsonData, verbose, debug, writeLock) 128 | if err != nil { 129 | return err 130 | } 131 | 132 | return nil 133 | } 134 | 135 | // CallbackTaskMessage sends a POST request to the manager to callback with a task message 136 | func CallbackTaskMessage(config *utils.WorkerConfig, task *globalstructs.Task, verbose, debug bool, writeLock *sync.Mutex) error { 137 | // Marshal the task object into JSON 138 | payload, _ := json.Marshal(task) 139 | 140 | msg := globalstructs.WebsocketMessage{ 141 | Type: "callbackTask", 142 | JSON: string(payload), 143 | } 144 | 145 | if debug { 146 | log.Println("ManagerRequest msg callback:", msg) 147 | } 148 | 149 | jsonData, err := json.Marshal(msg) 150 | if err != nil { 151 | log.Println("Error encoding JSON:", err) 152 | return err 153 | } 154 | 155 | err = SendMessage(config.Conn, jsonData, verbose, debug, writeLock) 156 | if err != nil { 157 | return err 158 | } 159 | 160 | return nil 161 | } 162 | -------------------------------------------------------------------------------- /worker/modules/module1.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import time 3 | import random 4 | import sys 5 | 6 | def generate_random_data(): 7 | return [random.randint(1, 100), random.uniform(0.0, 1.0), random.choice(['A', 'B', 'C'])] 8 | 9 | def main(): 10 | 11 | print(sys.argv[1:]) 12 | 13 | header = ['h1', 'h2', 'h3'] 14 | 15 | # Generate random data 16 | data = generate_random_data() 17 | 18 | # Write to CSV 19 | print(header) 20 | print(data) 21 | 22 | # Sleep for a random time between 1 and 30 seconds 23 | sleep_time = random.uniform(5, 15) 24 | print(f"Sleeping for {sleep_time:.2f} seconds...") 25 | time.sleep(sleep_time) 26 | 27 | if __name__ == "__main__": 28 | main() 29 | -------------------------------------------------------------------------------- /worker/modules/modules.go: -------------------------------------------------------------------------------- 1 | package modules 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "runtime" 10 | "strings" 11 | "sync" 12 | "syscall" 13 | "time" 14 | 15 | "github.com/r4ulcl/nTask/globalstructs" 16 | "github.com/r4ulcl/nTask/worker/utils" 17 | ) 18 | 19 | var mutex sync.Mutex 20 | 21 | func runModule(config *utils.WorkerConfig, command string, arguments string, status *globalstructs.WorkerStatus, id string, verbose, debug bool) (string, error) { 22 | // if command is empty, like in the example "exec" to exec any binary 23 | // the first argument is the command 24 | var cmd *exec.Cmd 25 | if config.InsecureModules { 26 | cmdStr := command + " " + arguments 27 | if debug { 28 | log.Println("Modules cmdStr: ", cmdStr) 29 | } 30 | 31 | if runtime.GOOS == "windows" { 32 | cmd = exec.Command("cmd", "/c", cmdStr) 33 | } else if runtime.GOOS == "linux" { 34 | cmd = exec.Command("sh", "-c", cmdStr) 35 | } else { 36 | log.Fatal("Unsupported operating system") 37 | } 38 | 39 | } else { 40 | // Convert arguments to array 41 | argumentsArray := strings.Split(arguments, " ") 42 | if command == "" && len(arguments) > 0 { 43 | command = argumentsArray[0] 44 | argumentsArray = argumentsArray[1:] 45 | } 46 | 47 | // Check if module has space, to separate it in command and args 48 | if strings.Contains(command, " ") { 49 | parts := strings.SplitN(command, " ", 2) 50 | argumentsArray = append([]string{parts[1]}, argumentsArray...) 51 | 52 | // Update the inputString to contain only the first part 53 | command = parts[0] 54 | } 55 | 56 | if debug { 57 | log.Println("Modules command: ", command) 58 | log.Println("Modules argumentsArray: ", argumentsArray) 59 | } 60 | 61 | // Command to run the module 62 | cmd = exec.Command(command, argumentsArray...) 63 | } 64 | // Create a buffer to store the command output 65 | var stdout, stderr bytes.Buffer 66 | 67 | // Set the output and error streams to the buffers 68 | cmd.Stdout = &stdout 69 | cmd.Stderr = &stderr 70 | 71 | // Start the command 72 | err := cmd.Start() 73 | if err != nil { 74 | // Check if the error is an ExitError 75 | if exitError, ok := err.(*exec.ExitError); ok { 76 | // The command exited with a non-zero status 77 | fmt.Printf("Command exited with error: %v\n", exitError) 78 | 79 | // Print the captured standard error 80 | log.Println("Standard Error:") 81 | fmt.Print(stderr.String()) 82 | } else { 83 | // Some other error occurred 84 | fmt.Printf("Command finished with unexpected error: %v\n", err) 85 | } 86 | return err.Error(), err 87 | } 88 | 89 | mutex.Lock() 90 | status.WorkingIDs[id] = cmd.Process.Pid 91 | mutex.Unlock() 92 | 93 | defer func() { 94 | mutex.Lock() 95 | delete(status.WorkingIDs, id) 96 | mutex.Unlock() 97 | }() 98 | 99 | // Create a channel to signal when the process is done 100 | done := make(chan error, 1) 101 | 102 | // Monitor the process in a goroutine 103 | go func() { 104 | // Wait for the command to finish 105 | err := cmd.Wait() 106 | done <- err 107 | }() 108 | 109 | // Check every 30 minutes if the process is still running 110 | ticker := time.NewTicker(30 * time.Minute) 111 | defer ticker.Stop() 112 | 113 | for { 114 | select { 115 | case <-ticker.C: 116 | // Check if the process is still running 117 | if err := isProcessRunning(cmd.Process.Pid); err != nil { 118 | // Process is not running, break the loop 119 | return "", err 120 | } 121 | case err := <-done: 122 | // Process has finished 123 | if err != nil { 124 | if debug { 125 | log.Println("Modules Error waiting for command:", err) 126 | } 127 | return err.Error(), err 128 | } 129 | 130 | // Process completed successfully 131 | // Capture the output of the script 132 | output := stdout.String() + stderr.String() 133 | output = strings.TrimRight(output, "\n") 134 | return output, nil 135 | } 136 | } 137 | } 138 | 139 | // Function to check if a process with a given PID is still running 140 | func isProcessRunning(pid int) error { 141 | process, err := os.FindProcess(pid) 142 | if err != nil { 143 | return err 144 | } 145 | 146 | // Send a signal of 0 to check if the process exists 147 | err = process.Signal(syscall.Signal(0)) 148 | if err != nil { 149 | // Process does not exist 150 | return fmt.Errorf("Process with PID %d is not running", pid) 151 | } 152 | 153 | // Process is still running 154 | return nil 155 | } 156 | 157 | // ProcessModule processes a task by iterating through its commands and executing corresponding modules 158 | func ProcessModule(task *globalstructs.Task, config *utils.WorkerConfig, status *globalstructs.WorkerStatus, id string, verbose, debug bool) error { 159 | for num, command := range task.Commands { 160 | module := command.Module 161 | arguments := command.Args 162 | 163 | // Check if the module exists in the worker configuration 164 | commandAux, found := config.Modules[module] 165 | if !found { 166 | // Return an error if the module is not found 167 | return fmt.Errorf("unknown command: %s", module) 168 | } 169 | 170 | // If there is a file in the command, save to disk 171 | if command.FileContent != "" { 172 | if command.RemoteFilePath == "" { 173 | return fmt.Errorf("RemoteFilePath empty") 174 | } 175 | 176 | err := SaveStringToFile(command.RemoteFilePath, command.FileContent) 177 | if err != nil { 178 | return err 179 | } 180 | 181 | } 182 | 183 | if verbose { 184 | log.Println("Modules commandAux: ", commandAux) 185 | log.Println("Modules arguments: ", arguments) 186 | } 187 | 188 | // Execute the module and get the output and any error 189 | outputCommand, err := runModule(config, commandAux, arguments, status, id, verbose, debug) 190 | if err != nil { 191 | // Save the text error in the task output to review 192 | task.Commands[num].Output = outputCommand + err.Error() 193 | // Return an error if there is an issue running the module 194 | return fmt.Errorf("error running %s task: %v", commandAux, err) 195 | } 196 | 197 | // Store the output in the task struct for the current command 198 | task.Commands[num].Output = outputCommand 199 | } 200 | 201 | // Return nil if the task is processed successfully 202 | return nil 203 | } 204 | 205 | func stringList(list []string, verbose, debug bool) string { 206 | stringList := "" 207 | for _, item := range list { 208 | stringList += item + "\n" 209 | } 210 | 211 | return stringList 212 | } 213 | 214 | // SaveStringToFile saves a string to a file. 215 | func SaveStringToFile(filename string, content string) error { 216 | // Write the string content to the file 217 | err := os.WriteFile(filename, []byte(content), 0600) 218 | if err != nil { 219 | return fmt.Errorf("error saving string to file: %v", err) 220 | } 221 | 222 | fmt.Printf("String saved to file: %s\n", filename) 223 | return nil 224 | } 225 | -------------------------------------------------------------------------------- /worker/modules/nmapIPs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | scanRange=$1 4 | 5 | nmap -sn $scanRange | grep -E -o "([0-9]{1,3}\.){3}[0-9]{1,3}" -------------------------------------------------------------------------------- /worker/process/processTask.go: -------------------------------------------------------------------------------- 1 | package process 2 | 3 | import ( 4 | "log" 5 | "sync" 6 | "time" 7 | 8 | "github.com/r4ulcl/nTask/globalstructs" 9 | "github.com/r4ulcl/nTask/worker/managerrequest" 10 | "github.com/r4ulcl/nTask/worker/modules" 11 | "github.com/r4ulcl/nTask/worker/utils" 12 | ) 13 | 14 | var mutex sync.Mutex 15 | 16 | // processTask is a helper function that processes the given task in the background. 17 | // It sets the worker status to indicate that it is currently working on the task. 18 | // It calls the ProcessModule function to execute the task's module. 19 | // If an error occurs, it sets the task status to "failed". 20 | // Otherwise, it sets the task status to "done" and assigns the output of the module to the task. 21 | // Finally, it calls the CallbackTaskMessage function to send the task result to the configured callback endpoint. 22 | // After completing the task, it resets the worker status to indicate that it is no longer working. 23 | func Task(status *globalstructs.WorkerStatus, config *utils.WorkerConfig, task *globalstructs.Task, verbose, debug bool, writeLock *sync.Mutex) { 24 | //Remove one from working threads 25 | sustract1IddleThreads(status) 26 | 27 | //Add one from working threads 28 | defer add1IddleThreads(status) 29 | 30 | if verbose { 31 | log.Println("Process Start processing task", task.ID, " workCount: ", status.IddleThreads) 32 | } 33 | 34 | err := modules.ProcessModule(task, config, status, task.ID, verbose, debug) 35 | if err != nil { 36 | log.Println("Process Error ProcessModule:", err) 37 | task.Status = "failed" 38 | } else { 39 | task.Status = "done" 40 | } 41 | 42 | // While manager doesnt responds loop 43 | for { 44 | err = managerrequest.CallbackTaskMessage(config, task, verbose, debug, writeLock) 45 | if err == nil { 46 | break 47 | } 48 | time.Sleep(time.Second * 10) 49 | } 50 | 51 | } 52 | 53 | func add1IddleThreads(status *globalstructs.WorkerStatus) { 54 | modifyIddleThreads(true, status) 55 | } 56 | 57 | func sustract1IddleThreads(status *globalstructs.WorkerStatus) { 58 | modifyIddleThreads(false, status) 59 | } 60 | 61 | func modifyIddleThreads(add bool, status *globalstructs.WorkerStatus) { 62 | mutex.Lock() 63 | defer mutex.Unlock() 64 | 65 | if add { 66 | status.IddleThreads++ 67 | } else { 68 | status.IddleThreads-- 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /worker/utils/structs.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | 7 | "github.com/gorilla/websocket" 8 | ) 9 | 10 | type WorkerConfig struct { 11 | Name string `json:"name"` 12 | IddleThreads int `json:"iddleThreads"` 13 | ManagerIP string `json:"managerIP"` 14 | ManagerPort string `json:"managerPort"` 15 | ManagerOauthToken string `json:"managerOauthToken"` 16 | CA string `json:"ca"` 17 | InsecureModules bool `json:"insecureModules"` 18 | Modules map[string]string `json:"modules"` 19 | ClientHTTP *http.Client `json:"clientHTTP"` 20 | Conn *websocket.Conn `json:"Conn"` 21 | } 22 | 23 | type Task struct { 24 | ID string 25 | Module string 26 | Arguments []string 27 | CallbackURL string 28 | Status string 29 | Result string 30 | Goroutine *sync.WaitGroup 31 | } 32 | -------------------------------------------------------------------------------- /worker/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "encoding/json" 7 | "fmt" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | // CreateTLSClientWithCACert from cert.pem 14 | func CreateTLSClientWithCACert(caCertPath string, verifyAltName, verbose, debug bool) (*http.Client, error) { 15 | 16 | tlsConfig, err := GenerateTLSConfig(caCertPath, verifyAltName, verbose, debug) 17 | if err != nil { 18 | if debug { 19 | log.Println("Utils Error reading worker config file: ", err) 20 | } 21 | return nil, err 22 | } 23 | 24 | // Create HTTP client with TLS 25 | client := &http.Client{ 26 | Transport: &http.Transport{ 27 | TLSClientConfig: tlsConfig, 28 | }, 29 | } 30 | 31 | return client, nil 32 | } 33 | 34 | func LoadWorkerConfig(filename string, verbose, debug bool) (*WorkerConfig, error) { 35 | var config WorkerConfig 36 | content, err := os.ReadFile(filename) 37 | if err != nil { 38 | if debug { 39 | log.Println("Utils Error reading worker config file: ", err) 40 | } 41 | return &config, err 42 | } 43 | 44 | err = json.Unmarshal(content, &config) 45 | if err != nil { 46 | if debug { 47 | log.Println("Utils Error unmarshalling worker config: ", err) 48 | } 49 | return &config, err 50 | } 51 | 52 | // if Name is empty use hostname 53 | if config.Name == "" { 54 | hostname, err := os.Hostname() 55 | if err != nil { 56 | if debug { 57 | log.Println("Utils Error getting hostname:", err) 58 | } 59 | return &config, err 60 | } 61 | if debug { 62 | log.Println("Utils hostname:", hostname) 63 | } 64 | config.Name = hostname 65 | } 66 | 67 | // Print the values from the struct 68 | if debug { 69 | log.Println("Utils Name:", config.Name) 70 | log.Println("Utils Tasks:") 71 | 72 | for module, exec := range config.Modules { 73 | log.Printf(" Module: %s, Exec: %s\n", module, exec) 74 | } 75 | } 76 | 77 | if verbose { 78 | log.Println("Config loaded:", config) 79 | } 80 | 81 | return &config, nil 82 | } 83 | 84 | func GenerateTLSConfig(caCertPath string, verifyAltName, verbose, debug bool) (*tls.Config, error) { 85 | var tlsConfig *tls.Config 86 | 87 | // Load CA certificate from file 88 | caCert, err := os.ReadFile(caCertPath) 89 | if err != nil { 90 | fmt.Printf("Failed to read CA certificate file: %v\n", err) 91 | return nil, err 92 | } 93 | 94 | // Create a certificate pool and add the CA certificate 95 | certPool := x509.NewCertPool() 96 | certPool.AppendCertsFromPEM(caCert) 97 | 98 | //var cert *x509.Certificate 99 | // Create a TLS configuration with the custom VerifyPeerCertificate function 100 | if !verifyAltName { 101 | tlsConfig = &tls.Config{ 102 | InsecureSkipVerify: true, // Enable server verification 103 | RootCAs: certPool, 104 | VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { 105 | if len(rawCerts) == 0 { 106 | return fmt.Errorf("no certificates provided by the server") 107 | } 108 | 109 | serverCert, err := x509.ParseCertificate(rawCerts[0]) 110 | if err != nil { 111 | return fmt.Errorf("failed to parse server certificate: %v", err) 112 | } 113 | 114 | // Verify the server certificate against the CA certificate 115 | opts := x509.VerifyOptions{ 116 | Roots: certPool, 117 | Intermediates: x509.NewCertPool(), 118 | } 119 | _, err = serverCert.Verify(opts) 120 | if err != nil { 121 | return fmt.Errorf("failed to verify server certificate: %v", err) 122 | } 123 | 124 | return nil 125 | }, 126 | } 127 | } else { 128 | log.Println("Utils verifyAltName YES", !verifyAltName) 129 | 130 | tlsConfig = &tls.Config{ 131 | InsecureSkipVerify: false, // Ensure that server verification is enabled 132 | RootCAs: certPool, 133 | MinVersion: tls.VersionTLS12, // Set the desired minimum TLS version 134 | } 135 | } 136 | 137 | return tlsConfig, nil 138 | } 139 | -------------------------------------------------------------------------------- /worker/websockets/websockets.go: -------------------------------------------------------------------------------- 1 | package websockets 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "log" 7 | "os/exec" 8 | "strconv" 9 | "sync" 10 | "time" 11 | 12 | "github.com/gorilla/websocket" 13 | "github.com/r4ulcl/nTask/globalstructs" 14 | "github.com/r4ulcl/nTask/worker/managerrequest" 15 | "github.com/r4ulcl/nTask/worker/process" 16 | "github.com/r4ulcl/nTask/worker/utils" 17 | ) 18 | 19 | func GetMessage(config *utils.WorkerConfig, status *globalstructs.WorkerStatus, verbose, debug bool, writeLock *sync.Mutex) { 20 | for { 21 | 22 | response := globalstructs.WebsocketMessage{ 23 | Type: "", 24 | JSON: "", 25 | } 26 | 27 | _, p, err := config.Conn.ReadMessage() //messageType 28 | if err != nil { 29 | log.Println("WebSockets config.Conn.ReadMessage()", err) 30 | time.Sleep(time.Second * 5) 31 | 32 | continue 33 | } 34 | 35 | var msg globalstructs.WebsocketMessage 36 | err = json.Unmarshal(p, &msg) 37 | if err != nil { 38 | log.Println("WebSockets Error decoding JSON:", err) 39 | 40 | continue 41 | } 42 | 43 | switch msg.Type { 44 | 45 | case "status": 46 | if debug { 47 | if debug { 48 | log.Println("WebSockets msg.Type", msg.Type) 49 | } 50 | } 51 | jsonData, err := json.Marshal(status) 52 | if err != nil { 53 | response.Type = "FAILED" 54 | } else { 55 | response.Type = "status" 56 | response.JSON = string(jsonData) 57 | } 58 | 59 | if debug { 60 | // Print the JSON data 61 | log.Println(string(jsonData)) 62 | } 63 | 64 | case "addTask": 65 | if debug { 66 | log.Println("WebSockets msg.Type", msg.Type) 67 | } 68 | var requestTask globalstructs.Task 69 | err = json.Unmarshal([]byte(msg.JSON), &requestTask) 70 | if err != nil { 71 | log.Println("WebSockets addWorker Unmarshal error: ", err) 72 | } 73 | // if executing task skip and return error 74 | if status.IddleThreads <= 0 { 75 | response.Type = "FAILED;addTask" 76 | response.JSON = msg.JSON 77 | 78 | requestTask.Status = "failed" 79 | } else { 80 | // Process task in background 81 | if debug { 82 | log.Println("WebSockets Task") 83 | } 84 | go process.Task(status, config, &requestTask, verbose, debug, writeLock) 85 | response.Type = "OK;addTask" 86 | response.JSON = msg.JSON 87 | requestTask.Status = "running" 88 | } 89 | 90 | //return task 91 | jsonData, err := json.Marshal(requestTask) 92 | if err != nil { 93 | log.Println("WebSockets Marshal error: ", err) 94 | } 95 | response.JSON = string(jsonData) 96 | 97 | case "deleteTask": 98 | if debug { 99 | log.Println("WebSockets msg.Type", msg.Type) 100 | } 101 | 102 | var requestTask globalstructs.Task 103 | err = json.Unmarshal([]byte(msg.JSON), &requestTask) 104 | if err != nil { 105 | log.Println("WebSockets deleteTask Unmarshal error: ", err) 106 | } 107 | 108 | cmdID := status.WorkingIDs[requestTask.ID] 109 | 110 | if cmdID < 0 { 111 | log.Println("Invalid cmdID") 112 | continue 113 | } 114 | cmdIDString := strconv.Itoa(cmdID) 115 | 116 | // Kill the process using cmdID 117 | cmd := exec.Command("kill", "-9", cmdIDString) 118 | 119 | var stderr bytes.Buffer 120 | cmd.Stderr = &stderr 121 | 122 | err := cmd.Run() 123 | 124 | if err != nil { 125 | if debug { 126 | log.Println("WebSockets Error killing process:", err) 127 | log.Println("WebSockets Error details:", stderr.String()) 128 | } 129 | response.Type = "FAILED;deleteTask" 130 | response.JSON = msg.JSON 131 | } else { 132 | response.Type = "OK;deleteTask" 133 | response.JSON = msg.JSON 134 | } 135 | } 136 | if debug { 137 | log.Printf("Received message type: %s\n", msg.Type) 138 | log.Printf("Received message json: %s\n", msg.JSON) 139 | } 140 | 141 | if response.Type != "" { 142 | jsonData, err := json.Marshal(response) 143 | if err != nil { 144 | log.Println("WebSockets Marshal error: ", err) 145 | } 146 | err = managerrequest.SendMessage(config.Conn, jsonData, verbose, debug, writeLock) 147 | if err != nil { 148 | log.Println("WebSockets SendMessage error: ", err) 149 | } 150 | } 151 | } 152 | } 153 | 154 | func RecreateConnection(config *utils.WorkerConfig, verifyAltName, verbose, debug bool, writeLock *sync.Mutex) { 155 | for { 156 | time.Sleep(1 * time.Second) // Adjust the interval based on your requirements 157 | if err := config.Conn.WriteControl(websocket.PingMessage, nil, time.Now().Add(1*time.Second)); err != nil { 158 | conn, err := managerrequest.CreateWebsocket(config, config.CA, verifyAltName, verbose, debug) 159 | if err != nil { 160 | if verbose { 161 | log.Println("WebSockets Error CreateWebsocket: ", err) 162 | } 163 | } else { 164 | config.Conn = conn 165 | 166 | err = managerrequest.AddWorker(config, verbose, debug, writeLock) 167 | if err != nil { 168 | if verbose { 169 | log.Println("WebSockets Error worker RecreateConnection AddWorker: ", err) 170 | } 171 | } else { 172 | if verbose { 173 | log.Println("WebSockets Worker connected to manager. ") 174 | } 175 | 176 | continue 177 | } 178 | 179 | } 180 | } 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /worker/worker.go: -------------------------------------------------------------------------------- 1 | // workerouter.go 2 | package worker 3 | 4 | import ( 5 | "log" 6 | "net/http" 7 | "os" 8 | "os/signal" 9 | "sync" 10 | "syscall" 11 | "time" 12 | 13 | globalstructs "github.com/r4ulcl/nTask/globalstructs" 14 | "github.com/r4ulcl/nTask/worker/managerrequest" 15 | "github.com/r4ulcl/nTask/worker/utils" 16 | "github.com/r4ulcl/nTask/worker/websockets" 17 | ) 18 | 19 | func StartWorker(swagger bool, configFile string, verifyAltName, verbose, debug bool) { 20 | log.Println("Worker Running as worker router...") 21 | 22 | config, err := utils.LoadWorkerConfig(configFile, verbose, debug) 23 | if err != nil { 24 | log.Fatal("Error loading config file: ", err) 25 | } 26 | 27 | var writeLock sync.Mutex 28 | 29 | status := globalstructs.WorkerStatus{ 30 | Name: config.Name, 31 | IddleThreads: config.IddleThreads, 32 | WorkingIDs: make(map[string]int), 33 | } 34 | 35 | // Create a channel to receive signals for Ctrl+C 36 | sigChan := make(chan os.Signal, 1) 37 | // Notify the sigChan for interrupt signals (e.g., Ctrl+C) 38 | signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) 39 | // Create a goroutine to handle the signal 40 | go func(config *utils.WorkerConfig) { 41 | // Wait for a signal 42 | sig := <-sigChan 43 | log.Println("\nReceived signal:", sig) 44 | 45 | // Execute your function or cleanup here 46 | log.Println("Executing cleanup function...") 47 | 48 | //delete worker 49 | if config.Conn != nil { 50 | err := managerrequest.DeleteWorker(config, verbose, debug, &writeLock) 51 | if err != nil { 52 | log.Println("Worker Error worker DeleteWorker: ", err) 53 | } 54 | } 55 | // Exit the program gracefully 56 | os.Exit(0) 57 | }(config) 58 | 59 | if config.CA != "" { 60 | // Create an HTTP client with the custom TLS configuration 61 | clientHTTP, err := utils.CreateTLSClientWithCACert(config.CA, verifyAltName, verbose, debug) 62 | if err != nil { 63 | log.Println("Error creating HTTPS client:", err) 64 | return 65 | } 66 | 67 | config.ClientHTTP = clientHTTP 68 | } else { 69 | config.ClientHTTP = &http.Client{} 70 | } 71 | 72 | // Loop until connects 73 | for { 74 | if debug { 75 | log.Println("Worker Trying to conenct to manager") 76 | } 77 | conn, err := managerrequest.CreateWebsocket(config, config.CA, verifyAltName, verbose, debug) 78 | if err != nil { 79 | log.Println("Worker Error worker CreateWebsocket: ", err) 80 | } else { 81 | config.Conn = conn 82 | 83 | err = managerrequest.AddWorker(config, verbose, debug, &writeLock) 84 | if err != nil { 85 | if verbose { 86 | log.Println("Worker Error worker AddWorker: ", err) 87 | } 88 | } else { 89 | if verbose { 90 | log.Println("Worker connected to manager. ") 91 | } 92 | break 93 | } 94 | } 95 | time.Sleep(time.Second * 5) 96 | } 97 | 98 | go websockets.GetMessage(config, &status, verbose, debug, &writeLock) 99 | 100 | go websockets.RecreateConnection(config, verifyAltName, verbose, debug, &writeLock) 101 | 102 | mainloop() 103 | } 104 | 105 | func mainloop() { 106 | exitSignal := make(chan os.Signal, 1) // Use a buffered channel with capacity 1 107 | signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM) 108 | <-exitSignal 109 | } 110 | --------------------------------------------------------------------------------