diff --git a/dospin.go b/dospin.go index 8a94d59..2d128e8 100644 --- a/dospin.go +++ b/dospin.go @@ -18,14 +18,16 @@ const ( ) type cmdOptions struct { - config string - cmd string + config string + cmd string + varStateDir string } func parseCmdOptions() cmdOptions { var o cmdOptions flag.StringVar(&o.cmd, "cmd", CMD_SERVE, "Mode to run command in ("+CMD_SERVE+","+CMD_SPINDOWNALL+")") - flag.StringVar(&o.config, "config", "dospin.json", "Path to the dospin config file") + flag.StringVar(&o.config, "config", "/etc/dospin.json", "Path to the dospin config file") + flag.StringVar(&o.varStateDir, "varstate", "/var/lib/dospin", "Path to the var state directory") flag.Parse() return o } diff --git a/dospin.json b/dospin.json index 5d394f4..82399f2 100644 --- a/dospin.json +++ b/dospin.json @@ -1,12 +1,17 @@ { "ApiToken": "", + "GatewayInterface": "eth0:2", "Servers": { "minecraft": { "Ports": [25565], "UsePublicIP": false, - "InitialSize": "512mb", + "InitialSize": "4gb", "Size": "4gb", - "Region": "nyc3" + "Region": "nyc1", + "SshKeys": [513314, 1667247], + "ImageSlug": "ubuntu-16-04-x64", + "Volumes": ["volume-nyc1-01"], + "UserData": "#!/bin/bash\napt-get update\napt-get install -y docker.io\nmkdir -p /mnt/volume-nyc1-01\nmount -o discard,defaults /dev/disk/by-id/scsi-0DO_Volume_volume-nyc1-01 /mnt/volume-nyc1-01\necho /dev/disk/by-id/scsi-0DO_Volume_volume-nyc1-01 /mnt/volume-nyc1-01 ext4 defaults,nofail,discard 0 0 | tee -a /etc/fstab\ndocker run -d --restart=always -p 25565:25565 -v /mnt/volume-nyc1-01/minecraft-server:/minecraft-server -w /minecraft-server -t java:8-alpine sh start.sh" } } } diff --git a/droplethandler.go b/droplethandler.go index a2e2c90..a0bb4a4 100644 --- a/droplethandler.go +++ b/droplethandler.go @@ -28,6 +28,22 @@ func (t *tokenSource) Token() (*oauth2.Token, error) { return token, nil } +func sshKeys(ids []int) []godo.DropletCreateSSHKey { + var out []godo.DropletCreateSSHKey + for _, id := range ids { + out = append(out, godo.DropletCreateSSHKey{ID: id}) + } + return out +} + +func volumes(names []string) []godo.DropletCreateVolume { + var out []godo.DropletCreateVolume + for _, name := range names { + out = append(out, godo.DropletCreateVolume{Name: name}) + } + return out +} + type DropletHandler struct { client *godo.Client settings Settings @@ -58,9 +74,20 @@ func (me *DropletHandler) Spinup(name string) (string, error) { } } else { // create the droplet - image, err := me.getSnapshot(name) - if err != nil { - return "", err + var image godo.DropletCreateImage + + if vd.ImageSlug == "" { + snapshot, err := me.getSnapshot(name) + if err != nil { + return "", err + } + image = godo.DropletCreateImage{ + ID: snapshot.ID, + } + } else { + image = godo.DropletCreateImage{ + Slug: vd.ImageSlug, + } } // determine droplet size @@ -76,9 +103,10 @@ func (me *DropletHandler) Spinup(name string) (string, error) { Region: vd.Region, Size: size, PrivateNetworking: true, - Image: godo.DropletCreateImage{ - ID: image.ID, - }, + SSHKeys: sshKeys(vd.SshKeys), + Volumes: volumes(vd.Volumes), + UserData: vd.UserData, + Image: image, } log.Println("Spinup: Creating " + name) @@ -126,17 +154,27 @@ func (me *DropletHandler) Spinup(name string) (string, error) { // delete the image log.Println("Spinup: Deleting image " + name) - _, err = me.client.Images.Delete(image.ID) - if err != nil { - log.Println("Spinup: Could not delete image: ", err) + if image.ID > -1 { + _, err = me.client.Images.Delete(image.ID) + if err != nil { + log.Println("Spinup: Could not delete image: ", err) + } else { + log.Println("Spinup: Deleted image " + name) + } } - log.Println("Spinup: Deleted image " + name) // get the private IP and return it - if vd.UsePublicIP { - return droplet.PublicIPv4() + + // get new copy of droplet that has IP + droplet, _, err = me.client.Droplets.Get(droplet.ID) + if err == nil { + if vd.UsePublicIP { + return droplet.PublicIPv4() + } else { + return droplet.PrivateIPv4() + } } else { - return droplet.PrivateIPv4() + return "", err } } } diff --git a/list_keys.sh b/list_keys.sh new file mode 100755 index 0000000..1bad52c --- /dev/null +++ b/list_keys.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env sh + +echo Keys +curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer `cat dospin.json | jq -r .ApiToken`" "https://api.digitalocean.com/v2/account/keys" | jq . diff --git a/list_volumes.sh b/list_volumes.sh new file mode 100755 index 0000000..f09d88e --- /dev/null +++ b/list_volumes.sh @@ -0,0 +1,4 @@ +#! /usr/bin/env sh + +echo Volumes +curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer `cat dospin.json | jq -r .ApiToken`" "https://api.digitalocean.com/v2/volumes" | jq . diff --git a/net_linux.go b/net_linux.go index dcac07a..b713edb 100644 --- a/net_linux.go +++ b/net_linux.go @@ -8,29 +8,25 @@ package main import ( - "bytes" "log" "os/exec" - "strconv" ) -// just have this stub to allow building on Linux -func addPortForward(ruleName, ip, port string) { - log.Print("Port forwarding not currently implemented for Linux/iptables") +func addPortForward(ruleName, gatewayInt, localIp, targetIp, port string) { + log.Println("Setting up port", port, "to", targetIp) + cmdOut, err := exec.Command("iptables", "-A", "PREROUTING", "-t", "nat", "-i", "\""+gatewayInt+"\"", "-p", "tcp", "--dport", port, "-j", "DNAT", "--to", targetIp+":"+port).Output() + log.Println("iptables", "-A", "PREROUTING,", "-t", "nat", "-i", "\""+gatewayInt+"\"", "-p", "tcp", "--dport", port, "-j", "DNAT", "--to", targetIp+":"+port) + if err != nil { + log.Println("iptables error:", err) + } + + cmdOut, err = exec.Command("iptables", "-A", "FORWARD", "-p", "tcp", "-d", targetIp, "--dport", port, "-j", "ACCEPT").Output() + log.Println("iptables", "-A", "FORWARD", "-p", "tcp", "-d", targetIp, "--dport", port, "-j", "ACCEPT") + if err != nil { + log.Println("iptables error:", err) + } } func rmPortForward(ruleName string) { log.Print("Port forwarding not currently implemented for Linux/iptables") } - -func portUsageCount(ports ...int) int { - cmd := "sockstat -4c" - for _, v := range ports { - cmd += " -p " + strconv.Itoa(v) - } - out, err := exec.Command(cmd).Output() - if err != nil { - log.Println("Port Usage Check: Could not run ", cmd) - } - return bytes.Count(out, []byte{'\n'}) - 1 -} diff --git a/servermanager.go b/servermanager.go index 92cfe04..ec3950d 100644 --- a/servermanager.go +++ b/servermanager.go @@ -19,6 +19,11 @@ const ( SERVERMANAGER_STOP ) +type serverManagerEvent struct { + eventType int + tcpConn *net.TCPConn +} + type ServerHandler interface { // Takes snapshot name, and returns the IP to connect to. Spinup(name string) (string, error) @@ -28,7 +33,8 @@ type ServerHandler interface { type ServerManager struct { name string ports []int - in chan int + gatewayInt string + in chan serverManagerEvent done chan interface{} usageScore int // spin down server when this reaches 0 server ServerHandler @@ -39,7 +45,8 @@ func NewServerManager(name string, server ServerHandler, settings Settings) *Ser sm.name = name sm.ports = settings.Servers[name].Ports - sm.in = make(chan int) + sm.gatewayInt = settings.GatewayInterface + sm.in = make(chan serverManagerEvent) sm.done = make(chan interface{}) sm.usageScore = 5 sm.server = server @@ -68,32 +75,38 @@ func (me *ServerManager) Serve() { /* Sends the serve loop a spinup message. */ -func (me *ServerManager) Spinup() { - me.in <- SERVERMANAGER_SPINUP +func (me *ServerManager) Spinup(c *net.TCPConn) { + me.in <- serverManagerEvent{eventType: SERVERMANAGER_SPINUP, tcpConn: c} } /* Sends the serve loop a spindown message. */ -func (me *ServerManager) Spindown() { - me.in <- SERVERMANAGER_SPINDOWN +func (me *ServerManager) Spindown(c *net.TCPConn) { + me.in <- serverManagerEvent{eventType: SERVERMANAGER_SPINDOWN} } /* Sends the serve loop a quit message. */ func (me *ServerManager) Stop() { - me.in <- SERVERMANAGER_STOP + me.in <- serverManagerEvent{eventType: SERVERMANAGER_STOP} } func (me *ServerManager) Done() { <-me.done } -func (me *ServerManager) addPortForwards(ip string) { +func (me *ServerManager) addPortForwards(localIp, remoteIp string) { + log.Println("Ports:", me.ports) + for _, p := range me.ports { + port := strconv.Itoa(p) + addPortForward(me.name, me.gatewayInt, localIp, remoteIp, port) + } } func (me *ServerManager) rmPortForwards() { + rmPortForward(me.name) } func (me *ServerManager) setupListener(port int) { @@ -118,7 +131,7 @@ func (me *ServerManager) setupListener(port int) { // connection accepted // spinup machine - me.Spinup() + me.Spinup(conn) // close existing connection, not doing anything with it conn.Close() @@ -128,14 +141,15 @@ func (me *ServerManager) setupListener(port int) { }() } -func (me *ServerManager) serveAction(action int) bool { +func (me *ServerManager) serveAction(event serverManagerEvent) bool { running := true - switch action { + switch event.eventType { case SERVERMANAGER_SPINUP: - ip, err := me.server.Spinup(me.name) + targetIp, err := me.server.Spinup(me.name) if err == nil { - log.Println("ServerManager: Got IP for", me.name+":", ip) - me.addPortForwards(ip) + log.Println("ServerManager: Got IP for", me.name+":", targetIp) + localIp := event.tcpConn.LocalAddr().String() + me.addPortForwards(localIp, targetIp) } else { log.Println("ServerManager: Could not spin up "+me.name+":", err) } diff --git a/settings.go b/settings.go index 1b27ed8..2596ee4 100644 --- a/settings.go +++ b/settings.go @@ -13,8 +13,9 @@ import ( ) type Settings struct { - ApiToken string - Servers map[string]Server + ApiToken string + GatewayInterface string + Servers map[string]Server } type Server struct { @@ -23,6 +24,10 @@ type Server struct { InitialSize string Size string Region string + ImageSlug string + UserData string + SshKeys []int + Volumes []string } func loadSettings(path string) (Settings, error) { diff --git a/varstate.go b/varstate.go new file mode 100644 index 0000000..d6a86ee --- /dev/null +++ b/varstate.go @@ -0,0 +1,19 @@ +/* + Copyright 2016 gtalent2@gmail.com + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ +package main + +// Firewall forwarding rule managed by dospin. +type ForwardingRule struct { + destHost string // Destination IP + destPort int + srcPort int +} + +func LoadForwardingRules() []ForwardingRule { + return nil +}