First commit
This commit is contained in:
commit
fbb6440c77
4
.env.example
Normal file
4
.env.example
Normal file
|
@ -0,0 +1,4 @@
|
|||
LOG_LEVEL=Verbose
|
||||
LOG_LOCATION=Europe/Stockholm
|
||||
PORT=4200
|
||||
URLS_CMDS_JSON_PATH=./urlstocmd.example.json
|
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
.env
|
||||
bin
|
27
README.md
Normal file
27
README.md
Normal file
|
@ -0,0 +1,27 @@
|
|||
# URL to CMD app
|
||||
|
||||
A tiny web server that runs a specific command on the server when a specific URL is reached. A use case can be to update production code on a server, but without giving arbitrary command access to the server to external parties.
|
||||
|
||||
## Dependencies and installation
|
||||
|
||||
To build a new binary, you need to have Go 1.21 or later installed and then run `./build.sh`. All binaries lands in the `bin` folder.
|
||||
|
||||
To "install" this app, simply copy the binary to a folder in your $path, or run it directly.
|
||||
|
||||
There is no dependencies except for a supported architecture for the binary. If your architecture is not present, please add it and make a PR.
|
||||
|
||||
## Running and configuration
|
||||
|
||||
Either copy `.env.example` to `.env` and modify the settings to your likings, or modify the ENV in your shell according to `.env.example`. Modify `urlstocmd.example.json` to your liking and rename it to match your ENV `URLS_CMDS_JSON_PATH`. Then run the application.
|
||||
|
||||
## Installing as a system.d service in compatible Linux systems (Debian etc)
|
||||
|
||||
1. Configure the app according to the above instructions somewhere on your filesystem.
|
||||
2. Copy `urltocmd.service` to `/etc/systemd/system/urltocmd.service`.
|
||||
3. Modify `/etc/systemd/system/urltocmd.service` to match your needs (especially check User, ExecStart and WorkingDirectory)
|
||||
4. `sudo systemctl daemon-reload && sudo systemctl enable urltocmd && sudo service urltocmd start`
|
||||
5. Profit!
|
||||
|
||||
You can watch the log from urltocmd like this: `sudo journalctl -u urltocmd -f`.
|
||||
|
||||
See more information about how to work with system.d services [here](https://wiki.ubuntu.com/SystemdForUpstartUsers).
|
5
build.sh
Executable file
5
build.sh
Executable file
|
@ -0,0 +1,5 @@
|
|||
#!/bin/sh
|
||||
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -o bin/darwin_arm64/urltocmd
|
||||
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -o bin/darwin_amd64/urltocmd
|
||||
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/linux_amd64/urltocmd
|
7
go.mod
Normal file
7
go.mod
Normal file
|
@ -0,0 +1,7 @@
|
|||
module tw/urltocmd
|
||||
|
||||
go 1.21.0
|
||||
|
||||
require gitea.larvit.se/pwrpln/go_log v0.3.0
|
||||
|
||||
require github.com/joho/godotenv v1.5.1
|
12
go.sum
Normal file
12
go.sum
Normal file
|
@ -0,0 +1,12 @@
|
|||
gitea.larvit.se/pwrpln/go_log v0.3.0 h1:zxCQ4vPU8gqsYT35DIrGm1qntd88oDbzd7SvzAmNi1s=
|
||||
gitea.larvit.se/pwrpln/go_log v0.3.0/go.mod h1:CVZH9ge+rbQyhc+HYSI+B1A3j54wnQxzAcFgFMqsfLw=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
157
main.go
Normal file
157
main.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"gitea.larvit.se/pwrpln/go_log"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Load ENV from .env file
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
tmpLog := go_log.GetLog()
|
||||
tmpLog.Info("Failed to load .env file!")
|
||||
}
|
||||
|
||||
// Setting up logger
|
||||
log := go_log.GetLog()
|
||||
log.MinLogLvl = go_log.LogLvlFromStr(os.Getenv("LOG_LEVEL"))
|
||||
loc, err := time.LoadLocation(os.Getenv("LOG_LOCATION"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
log.TimeLocation = loc
|
||||
|
||||
// Open the JSON file for reading
|
||||
log.Info("Reading URLs and CMDs from file", "urls.json path", os.Getenv("URLS_CMDS_JSON_PATH"))
|
||||
file, err := os.Open(os.Getenv("URLS_CMDS_JSON_PATH"))
|
||||
if err != nil {
|
||||
log.Error("Error opening URLs and CMDs JSON file", "err", err.Error())
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Read the JSON data from the file
|
||||
jsonStr, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
log.Error("Error reading JSON string from file", "err", err.Error())
|
||||
panic(err)
|
||||
}
|
||||
file.Close()
|
||||
|
||||
var urlsToCmds map[string]string
|
||||
err = json.Unmarshal([]byte(jsonStr), &urlsToCmds)
|
||||
if err != nil {
|
||||
log.Error("Error unmarshalling JSON", "err", err.Error)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
http.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) {
|
||||
cmdStr, exists := urlsToCmds[req.URL.Path]
|
||||
|
||||
if !exists {
|
||||
log.Verbose("Call to undefined URL", "URL", req.URL.Path)
|
||||
res.WriteHeader(404)
|
||||
res.Write([]byte("Not Found"))
|
||||
return
|
||||
}
|
||||
|
||||
log.Verbose("Executing command", "cmdStr", cmdStr)
|
||||
|
||||
// Set the response headers for SSE
|
||||
res.Header().Set("Content-Type", "text/event-stream")
|
||||
res.Header().Set("Cache-Control", "no-cache")
|
||||
res.Header().Set("Connection", "keep-alive")
|
||||
|
||||
// Create the command
|
||||
cmd := exec.Command("/bin/sh", "-c", cmdStr)
|
||||
|
||||
// Create a pipes to capture the command's output
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
http.Error(res, "Internal Server Error", 500)
|
||||
log.Error("Could not create stdout pipe from command", "err", err.Error())
|
||||
return
|
||||
}
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
http.Error(res, "Internal Server Error", 500)
|
||||
log.Error("Could not create stderr pipe from command", "err", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Start the command
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
http.Error(res, "Internal Server Error", 500)
|
||||
log.Error("Could not start the command", "err", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
defer cmd.Wait()
|
||||
defer stdoutPipe.Close()
|
||||
defer stderrPipe.Close()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := stdoutPipe.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("Error reading stdout from command", "err", err.Error())
|
||||
break
|
||||
}
|
||||
log.Debug("std", "out", string(buf[:n]))
|
||||
res.Write(buf[:n])
|
||||
flusher, ok := res.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
buf := make([]byte, 1024)
|
||||
for {
|
||||
n, err := stderrPipe.Read(buf)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
log.Error("Error reading stderr from command", "err", err.Error())
|
||||
break
|
||||
}
|
||||
log.Debug("std", "err", string(buf[:n]))
|
||||
res.Write([]byte("STDERR: "))
|
||||
res.Write(buf[:n])
|
||||
flusher, ok := res.(http.Flusher)
|
||||
if ok {
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
log.Info("Starting web server", "PORT", os.Getenv("PORT"))
|
||||
err = http.ListenAndServe(":"+os.Getenv("PORT"), nil)
|
||||
if err != nil {
|
||||
log.Error("Web server crashed", "err", err.Error())
|
||||
}
|
||||
}
|
5
urlstocmd.example.json
Normal file
5
urlstocmd.example.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"/docker": "docker ps",
|
||||
"/hello": "echo \"hello world\"",
|
||||
"/ls": "ls -l"
|
||||
}
|
13
urltocmd.service
Normal file
13
urltocmd.service
Normal file
|
@ -0,0 +1,13 @@
|
|||
[Unit]
|
||||
Description=A tiny web server launching local commands upon specific URLs being called
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
Restart=always
|
||||
RestartSec=1
|
||||
User=root
|
||||
ExecStart=/root/urltocmd/urltocmd
|
||||
WorkingDirectory=/root/urltocmd
|
||||
|
||||
[Install]
|
||||
WantedBy=mult-user.target
|
Loading…
Reference in New Issue
Block a user