Initial commit
This commit is contained in:
commit
7dec9e6fd2
|
@ -0,0 +1,4 @@
|
||||||
|
# Server generated files
|
||||||
|
/server.crt
|
||||||
|
/server.key
|
||||||
|
/token.txt
|
|
@ -0,0 +1,24 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
func openUrl(url string) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "linux":
|
||||||
|
err = exec.Command("xdg-open", url).Start()
|
||||||
|
case "windows":
|
||||||
|
err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start()
|
||||||
|
case "darwin":
|
||||||
|
err = exec.Command("open", url).Start()
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("unsupported platform")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"crypto/x509/pkix"
|
||||||
|
"encoding/pem"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func publicKey(priv interface{}) interface{} {
|
||||||
|
switch k := priv.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return &k.PublicKey
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
return &k.PublicKey
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func pemBlockForKey(priv interface{}) *pem.Block {
|
||||||
|
switch k := priv.(type) {
|
||||||
|
case *rsa.PrivateKey:
|
||||||
|
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}
|
||||||
|
case *ecdsa.PrivateKey:
|
||||||
|
b, err := x509.MarshalECPrivateKey(k)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to marshal ECDSA private key: %v", err)
|
||||||
|
os.Exit(2)
|
||||||
|
}
|
||||||
|
return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateCert(crtFile, keyFile string) error {
|
||||||
|
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
template := x509.Certificate{
|
||||||
|
SerialNumber: big.NewInt(1),
|
||||||
|
Subject: pkix.Name{
|
||||||
|
Organization: []string{"Acme Co"},
|
||||||
|
},
|
||||||
|
NotBefore: time.Now(),
|
||||||
|
NotAfter: time.Now().Add(time.Hour * 24 * 180),
|
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
|
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||||
|
BasicConstraintsValid: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, publicKey(priv), priv)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := os.Create(crtFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
|
||||||
|
f, err = os.Create(keyFile)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pem.Encode(f, pemBlockForKey(priv))
|
||||||
|
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
module meow.tf/streamdeck-remote-server
|
||||||
|
|
||||||
|
go 1.12
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/atotto/clipboard v0.1.2
|
||||||
|
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||||
|
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||||
|
github.com/getlantern/golog v0.0.0-20170508214112-cca714f7feb5 // indirect
|
||||||
|
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||||
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||||
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||||
|
github.com/getlantern/systray v0.0.0-20190131073753-26d5b920200d
|
||||||
|
github.com/go-stack/stack v1.8.0 // indirect
|
||||||
|
github.com/oxtoacart/bpool v0.0.0-20190227141107-8c4636f812cc // indirect
|
||||||
|
github.com/stretchr/testify v1.3.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af // indirect
|
||||||
|
)
|
|
@ -0,0 +1,29 @@
|
||||||
|
github.com/atotto/clipboard v0.1.2 h1:YZCtFu5Ie8qX2VmVTBnrqLSiU9XOWwqNRmdT3gIQzbY=
|
||||||
|
github.com/atotto/clipboard v0.1.2/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
||||||
|
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||||
|
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||||
|
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||||
|
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||||
|
github.com/getlantern/golog v0.0.0-20170508214112-cca714f7feb5 h1:Okd7vkn9CfIgDBj1ST/vtBTCfD/kxIhYD412K+FRKPc=
|
||||||
|
github.com/getlantern/golog v0.0.0-20170508214112-cca714f7feb5/go.mod h1:Vwx1Cg64gCdIalad44uvQsKZw6LsVczIKZrUBStEjVw=
|
||||||
|
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||||
|
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||||
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||||
|
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||||
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||||
|
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||||
|
github.com/getlantern/systray v0.0.0-20190131073753-26d5b920200d h1:4P2eDMAoQcQoWIIKCNIkuVbQb+paRmpMxVXVfbs7B4U=
|
||||||
|
github.com/getlantern/systray v0.0.0-20190131073753-26d5b920200d/go.mod h1:7Splj4WBQSps8jODnMgrIV6goKL0N1HR+mhCAEVWlA0=
|
||||||
|
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||||
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
|
github.com/oxtoacart/bpool v0.0.0-20190227141107-8c4636f812cc h1:uhnyuvDwdKbjemAXHKsiEZOPagHim2nRjMcazH1g26A=
|
||||||
|
github.com/oxtoacart/bpool v0.0.0-20190227141107-8c4636f812cc/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
|
||||||
|
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af h1:NXfmMfXz6JqGfG3ikSxcz2N93j6DgScr19Oo2uwFu88=
|
||||||
|
golang.org/x/sys v0.0.0-20190520201301-c432e742b0af/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,41 @@
|
||||||
|
@ECHO OFF
|
||||||
|
|
||||||
|
IF "%GOPATH%"=="" GOTO NOGO
|
||||||
|
IF NOT EXIST %GOPATH%\bin\2goarray.exe GOTO INSTALL
|
||||||
|
:POSTINSTALL
|
||||||
|
IF "%1"=="" GOTO NOICO
|
||||||
|
IF NOT EXIST %1 GOTO BADFILE
|
||||||
|
ECHO Creating iconwin.go
|
||||||
|
ECHO //+build windows > iconwin.go
|
||||||
|
ECHO. >> iconwin.go
|
||||||
|
TYPE %1 | %GOPATH%\bin\2goarray Data icon >> iconwin.go
|
||||||
|
GOTO DONE
|
||||||
|
|
||||||
|
:CREATEFAIL
|
||||||
|
ECHO Unable to create output file
|
||||||
|
GOTO DONE
|
||||||
|
|
||||||
|
:INSTALL
|
||||||
|
ECHO Installing 2goarray...
|
||||||
|
go get github.com/cratonica/2goarray
|
||||||
|
IF ERRORLEVEL 1 GOTO GETFAIL
|
||||||
|
GOTO POSTINSTALL
|
||||||
|
|
||||||
|
:GETFAIL
|
||||||
|
ECHO Failure running go get github.com/cratonica/2goarray. Ensure that go and git are in PATH
|
||||||
|
GOTO DONE
|
||||||
|
|
||||||
|
:NOGO
|
||||||
|
ECHO GOPATH environment variable not set
|
||||||
|
GOTO DONE
|
||||||
|
|
||||||
|
:NOICO
|
||||||
|
ECHO Please specify a .ico file
|
||||||
|
GOTO DONE
|
||||||
|
|
||||||
|
:BADFILE
|
||||||
|
ECHO %1 is not a valid file
|
||||||
|
GOTO DONE
|
||||||
|
|
||||||
|
:DONE
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
#/bin/sh
|
||||||
|
|
||||||
|
if [ -z "$GOPATH" ]; then
|
||||||
|
echo GOPATH environment variable not set
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -e "$GOPATH/bin/2goarray" ]; then
|
||||||
|
echo "Installing 2goarray..."
|
||||||
|
go get github.com/cratonica/2goarray
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo Failure executing go get github.com/cratonica/2goarray
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo Please specify a PNG file
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ ! -f "$1" ]; then
|
||||||
|
echo $1 is not a valid file
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
|
||||||
|
OUTPUT=iconunix.go
|
||||||
|
echo Generating $OUTPUT
|
||||||
|
echo "//+build linux darwin" > $OUTPUT
|
||||||
|
echo >> $OUTPUT
|
||||||
|
cat "$1" | $GOPATH/bin/2goarray Data icon >> $OUTPUT
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo Failure generating $OUTPUT
|
||||||
|
exit
|
||||||
|
fi
|
||||||
|
echo Finished
|
|
@ -0,0 +1,143 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/atotto/clipboard"
|
||||||
|
"github.com/getlantern/systray"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"meow.tf/streamdeck-remote-server/icon"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
systray.Run(onReady, onExit)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onReady() {
|
||||||
|
systray.SetIcon(icon.Data)
|
||||||
|
systray.SetTitle("StreamDeck Remote")
|
||||||
|
systray.SetTooltip("StreamDeck Remote Server (Running)")
|
||||||
|
|
||||||
|
mCopy := systray.AddMenuItem("Copy Token", "Copy the token to the clipboard")
|
||||||
|
mQuit := systray.AddMenuItem("Quit", "Close StreamDeck Remote Server")
|
||||||
|
|
||||||
|
go listenServer()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-mCopy.ClickedCh:
|
||||||
|
clipboard.WriteAll(token)
|
||||||
|
case <-mQuit.ClickedCh:
|
||||||
|
systray.Quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func onExit() {
|
||||||
|
// clean up here
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
token string
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadToken() {
|
||||||
|
if _, err := os.Stat("token.txt"); os.IsNotExist(err) {
|
||||||
|
token = RandStringRunes(32)
|
||||||
|
|
||||||
|
f, err := os.Create("token.txt")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to save token:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
f.WriteString(token)
|
||||||
|
} else {
|
||||||
|
f, err := os.Open("token.txt")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to open token file:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
b, err := ioutil.ReadAll(f)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
f.Close()
|
||||||
|
log.Fatalln("Unable to read token file:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token = string(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func listenServer() {
|
||||||
|
loadToken()
|
||||||
|
|
||||||
|
if _, err := os.Stat("server.crt"); os.IsNotExist(err) {
|
||||||
|
err = generateCert("server.crt", "server.key")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Unable to generate certificates:", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
|
mux.HandleFunc("/url/open", authenticationHandler(requirePost(urlOpenHandler)))
|
||||||
|
|
||||||
|
http.ListenAndServeTLS(":4443", "server.crt", "server.key", mux)
|
||||||
|
}
|
||||||
|
|
||||||
|
func authenticationHandler(next http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// Check authorization header
|
||||||
|
auth := strings.SplitN(r.Header.Get("Authorization"), " ", 2)
|
||||||
|
|
||||||
|
if len(auth) != 2 || auth[0] != "Bearer" || auth[1] != token {
|
||||||
|
log.Println("Authentication failed, got:", auth[1])
|
||||||
|
http.Error(w, "authorization failed", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
next(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func urlOpenHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
urlStr := r.Form.Get("url")
|
||||||
|
|
||||||
|
if urlStr == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("empty url")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(urlStr)
|
||||||
|
|
||||||
|
if err != nil || u.Scheme == "" || u.Host == "" {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
log.Println("invalid url scheme/host or err:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Opening URL", urlStr)
|
||||||
|
|
||||||
|
openUrl(urlStr)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func requireGet(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "GET" {
|
||||||
|
h(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, "get only", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requirePost(h http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "POST" {
|
||||||
|
h(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Error(w, "post only", http.StatusMethodNotAllowed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
|
||||||
|
func RandStringRunes(n int) string {
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
||||||
|
}
|
||||||
|
return string(b)
|
||||||
|
}
|
Loading…
Reference in New Issue