Compare commits

...

9 Commits

Author SHA1 Message Date
Sasha Koshka
1978e263d6 sahd;ljasl;dkj 2023-06-01 03:44:33 -04:00
Sasha Koshka
7726d732d4 we got some dhrek we got some doneky we got some fienona 2023-06-01 03:37:08 -04:00
Sasha Koshka
32bc44c90f Oops lol 2023-06-01 03:00:15 -04:00
Sasha Koshka
805f42d828 Changed name of router to hn-router 2023-06-01 02:59:18 -04:00
Sasha Koshka
e8349360cc hnctl prints line breaks after errors 2023-06-01 01:01:08 -04:00
Sasha Koshka
4e58df9c9b Fix hnctl 2023-06-01 00:59:03 -04:00
Sasha Koshka
f90421e5db Add gitignore 2023-05-31 22:35:25 -04:00
Sasha Koshka
c9f2c56d65 Services no longer print out errors when they shut down 2023-05-31 22:00:21 -04:00
Sasha Koshka
92b645f34c Routine manager now recovers from panicking goroutines 2023-05-31 18:49:00 -04:00
10 changed files with 164 additions and 92 deletions

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
/hnctl
/router
/wrench

View File

@@ -5,7 +5,10 @@ package cli
import "os" import "os"
import "fmt" import "fmt"
import "flag" import "flag"
import "os/user"
import "strings" import "strings"
import "strconv"
import "path/filepath"
// Sayf is like Printf, but prints the program name before the message. This is // Sayf is like Printf, but prints the program name before the message. This is
// used for printing messages and errors. // used for printing messages and errors.
@@ -25,6 +28,16 @@ func ServiceUser (service string) string {
return "hn-" + strings.ToLower(service) return "hn-" + strings.ToLower(service)
} }
// DataDir returns the standard Hnakra data directory.
func DataDir () string {
return "/var/hnakra"
}
// ServiceDir returns the standard data directory of a service.
func ServiceDir (service string) string {
return filepath.Join(DataDir(), "services", ServiceUser(service))
}
// NeedRoot halts the program and displays an error if it is not being run as // NeedRoot halts the program and displays an error if it is not being run as
// root. This should be called whenever an operation takes place that requires // root. This should be called whenever an operation takes place that requires
// root privelages. // root privelages.
@@ -35,3 +48,34 @@ func NeedRoot() {
os.Exit(1) os.Exit(1)
} }
} }
// MkdirFor makes a directory makes the specified directory (if it doesnt
// already exist) and gives ownership of it to the specified uid and gid.
func MkdirFor (directory string, uid, gid int) error {
err := os.MkdirAll(directory, 0755)
if err != nil { return err }
err = os.Chmod(directory, 0770)
if err != nil { return err }
err = os.Chown(directory, uid, gid)
if err != nil { return err }
return nil
}
// LookupUID returns the uid and gid of the given username, if it exists.
func LookupUID (name string) (uid, gid uint32, err error) {
user, err := user.Lookup(name)
if err != nil {
return 0, 0, err
}
puid, err := strconv.Atoi(user.Uid)
if err != nil {
return 0, 0, err
}
pgid, err := strconv.Atoi(user.Gid)
if err != nil {
return 0, 0, err
}
return uint32(puid), uint32(pgid), nil
}

View File

@@ -10,8 +10,8 @@ import "hnakra/daemon"
import "hnakra/routines" import "hnakra/routines"
import "hnakra/router/rcon" import "hnakra/router/rcon"
import "hnakra/router/config" import "hnakra/router/config"
import "hnakra/cmd/router/srvhttps" import "hnakra/cmd/hn-router/srvhttps"
import "hnakra/cmd/router/srvhnakra" import "hnakra/cmd/hn-router/srvhnakra"
const banner = "\n" + const banner = "\n" +
" -=\\\n" + " -=\\\n" +

View File

@@ -11,6 +11,8 @@ type Server struct {
underlying net.Listener underlying net.Listener
Config config.Config Config config.Config
Router *router.Router Router *router.Router
running bool
} }
func (server *Server) Run () (err error) { func (server *Server) Run () (err error) {
@@ -18,17 +20,25 @@ func (server *Server) Run () (err error) {
"tcp", fmt.Sprint(":", server.Config.RouterPort()), "tcp", fmt.Sprint(":", server.Config.RouterPort()),
config.TLSConfigFor(server.Config)) config.TLSConfigFor(server.Config))
if err != nil { return err } if err != nil { return err }
server.running = true
log.Println(".// router on", server.underlying.Addr()) log.Println(".// router on", server.underlying.Addr())
for { for {
conn, err := server.underlying.Accept() conn, err := server.underlying.Accept()
if err != nil { return err } if err != nil {
if server.running {
return err
} else {
return nil
}
}
log.Println("-=E incoming connection from", conn.RemoteAddr()) log.Println("-=E incoming connection from", conn.RemoteAddr())
server.Router.Accept(conn) server.Router.Accept(conn)
} }
} }
func (server *Server) Shutdown () error { func (server *Server) Shutdown () error {
server.running = false
return server.underlying.Close() return server.underlying.Close()
} }

View File

@@ -9,6 +9,8 @@ type Server struct {
underlying *http.Server underlying *http.Server
Config config.Config Config config.Config
Handler http.Handler Handler http.Handler
running bool
} }
func (server *Server) Run () error { func (server *Server) Run () error {
@@ -22,10 +24,17 @@ func (server *Server) Run () error {
Handler: server.Handler, Handler: server.Handler,
} }
server.running = true
log.Println(".// https on", server.underlying.Addr) log.Println(".// https on", server.underlying.Addr)
return server.underlying.ListenAndServeTLS("", "") err := server.underlying.ListenAndServeTLS("", "")
if server.running {
return err
} else {
return nil
}
} }
func (server *Server) Shutdown () error { func (server *Server) Shutdown () error {
server.running = false
return server.underlying.Close() return server.underlying.Close()
} }

View File

@@ -30,7 +30,7 @@ func main () {
stopService := stopCommand.String("s", "router", "Service to stop") stopService := stopCommand.String("s", "router", "Service to stop")
restartCommand := flag.NewFlagSet("restart", flag.ExitOnError) restartCommand := flag.NewFlagSet("restart", flag.ExitOnError)
restartService := stopCommand.String("s", "router", "Service to restart") restartService := restartCommand.String("s", "router", "Service to restart")
flag.Parse() flag.Parse()
@@ -60,27 +60,27 @@ func execStart (service string) {
pid, err := spawn.PidOf(fullName) pid, err := spawn.PidOf(fullName)
if err == nil && spawn.Running(pid) { if err == nil && spawn.Running(pid) {
cli.Sayf("service is already running") cli.Sayf("service is already running\n")
return return
} }
uid, gid, err := spawn.LookupUID(fullName) uid, gid, err := cli.LookupUID(fullName)
if err != nil { if err != nil {
cli.Sayf("cannot start service: %v", err) cli.Sayf("cannot start service: %v\n", err)
os.Exit(1) os.Exit(1)
} }
path, err := exec.LookPath(fullName) path, err := exec.LookPath(fullName)
if err != nil { if err != nil {
cli.Sayf("cannot start service: %v", err) cli.Sayf("cannot start service: %v\n", err)
os.Exit(1) os.Exit(1)
} }
logDir := filepath.Join("/var/log/", fullName) logDir := filepath.Join("/var/log/", fullName)
env := append(os.Environ(), "HNAKRA_LOG_DIR=" + logDir) env := append(os.Environ(), "HNAKRA_LOG_DIR=" + logDir)
err = ensureLogDir(logDir, int(uid), int(gid)) err = cli.MkdirFor(logDir, int(uid), int(gid))
if err != nil { if err != nil {
cli.Sayf("cannot start service: %v", err) cli.Sayf("cannot start service: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@@ -88,14 +88,14 @@ func execStart (service string) {
// to it // to it
err = ensurePidFile(spawn.PidFile(fullName), int(uid), int(gid)) err = ensurePidFile(spawn.PidFile(fullName), int(uid), int(gid))
if err != nil { if err != nil {
cli.Sayf("cannot start service: %v", err) cli.Sayf("cannot start service: %v\n", err)
os.Exit(1) os.Exit(1)
} }
// spawn the service // spawn the service
pid, err = spawn.Spawn(path, uid, gid, env) pid, err = spawn.Spawn(path, uid, gid, env)
if err != nil { if err != nil {
cli.Sayf("cannot start service: %v", err) cli.Sayf("cannot start service: %v\n", err)
os.Exit(1) os.Exit(1)
} }
@@ -108,34 +108,23 @@ func execStop (service string) {
pid, err := spawn.PidOf(fullName) pid, err := spawn.PidOf(fullName)
if err != nil || !spawn.Running(pid) { if err != nil || !spawn.Running(pid) {
cli.Sayf("service is not running") cli.Sayf("service is not running\n")
return return
} }
process, err := os.FindProcess(pid) process, err := os.FindProcess(pid)
if err != nil { if err != nil {
cli.Sayf("service is not running") cli.Sayf("service is not running\n")
return return
} }
err = spawn.KillAndWait(process, 16 * time.Second) err = spawn.KillAndWait(process, 16 * time.Second)
if err != nil { if err != nil {
cli.Sayf("could not stop service: %v", err) cli.Sayf("could not stop service: %v\n", err)
os.Exit(1) os.Exit(1)
} }
} }
func ensureLogDir (directory string, uid, gid int) error {
err := os.MkdirAll(directory, 0755)
if err != nil { return err }
err = os.Chmod(directory, 0770)
if err != nil { return err }
err = os.Chown(directory, uid, gid)
if err != nil { return err }
return nil
}
func ensurePidFile (file string, uid, gid int) error { func ensurePidFile (file string, uid, gid int) error {
pidFile, err := os.Create(file) pidFile, err := os.Create(file)
if err != nil { return err } if err != nil { return err }

View File

@@ -6,7 +6,6 @@ import "fmt"
import "time" import "time"
import "errors" import "errors"
import "syscall" import "syscall"
import "os/user"
import "strconv" import "strconv"
import "path/filepath" import "path/filepath"
@@ -45,24 +44,6 @@ func Spawn (path string, uid, gid uint32, env []string, args ...string) (pid int
return process.Pid, process.Release() return process.Pid, process.Release()
} }
// LookupUID returns the uid and gid of the given username, if it exists.
func LookupUID (name string) (uid, gid uint32, err error) {
user, err := user.Lookup(name)
if err != nil {
return 0, 0, err
}
puid, err := strconv.Atoi(user.Uid)
if err != nil {
return 0, 0, err
}
pgid, err := strconv.Atoi(user.Gid)
if err != nil {
return 0, 0, err
}
return uint32(puid), uint32(pgid), nil
}
// PidFile returns the path of a pidfile under the specified name. More // PidFile returns the path of a pidfile under the specified name. More
// specifically, it returns `/run/<name>.pid`. // specifically, it returns `/run/<name>.pid`.
func PidFile (name string) string { func PidFile (name string) string {

View File

@@ -76,6 +76,8 @@ func main () {
delUserCommand := flag.NewFlagSet("deluser", flag.ExitOnError) delUserCommand := flag.NewFlagSet("deluser", flag.ExitOnError)
delUserService := delUserCommand.String ("s", "router", delUserService := delUserCommand.String ("s", "router",
"Service to delete the user for") "Service to delete the user for")
delUserRmData := delUserCommand.Bool ("D", false,
"Whether to remove the service's data directory")
authCommand := flag.NewFlagSet("auth", flag.ExitOnError) authCommand := flag.NewFlagSet("auth", flag.ExitOnError)
authService := authCommand.String ("s", "router", authService := authCommand.String ("s", "router",
@@ -108,7 +110,7 @@ func main () {
execAdduser(*addUserService) execAdduser(*addUserService)
case "deluser": case "deluser":
delUserCommand.Parse(subCommandArgs) delUserCommand.Parse(subCommandArgs)
execDeluser(*delUserService) execDeluser(*delUserService, *delUserRmData)
case "auth": case "auth":
authCommand.Parse(subCommandArgs) authCommand.Parse(subCommandArgs)
execAuth(*authService, *authUser) execAuth(*authService, *authUser)
@@ -144,55 +146,68 @@ func execHash (cost int, key string) {
func execAdduser (service string) { func execAdduser (service string) {
fullName := cli.ServiceUser(service) fullName := cli.ServiceUser(service)
dataDir := cli.ServiceDir(service)
// BUSYBOX if adduser, err := exec.LookPath("adduser"); err == nil {
adduser, err := exec.LookPath("adduser") // BUSYBOX
if err == nil {
addgroup, _ := exec.LookPath("addgroup") addgroup, _ := exec.LookPath("addgroup")
tryCommand (exec.Command(addgroup, fullName, "-S"), tryCommand (exec.Command(addgroup, fullName, "-S"),
"could not add group") "could not add group")
tryCommand (exec.Command(adduser, fullName, "-SHDG", fullName), tryCommand (exec.Command (
adduser, fullName, "-SHDG", fullName, "-h", dataDir),
"could not add user") "could not add user")
return } else if useradd, err := exec.LookPath("useradd"); err == nil {
} // GNU
// GNU
useradd, err := exec.LookPath("useradd")
if err == nil {
tryCommand (exec.Command ( tryCommand (exec.Command (
useradd, fullName, "-rUM", useradd, fullName, "-rUM",
"--shell", "/sbin/nologin"), "could not add user") "--shell", "/sbin/nologin",
return "-d", dataDir), "could not add user")
} else {
cli.Sayf("could not add user: no command adduser or useradd\n")
os.Exit(1)
} }
cli.Sayf("could not add user: no command adduser or useradd\n") // create data directory
os.Exit(1) uid, gid, err := cli.LookupUID(fullName)
if err != nil {
cli.Sayf("could not create data dir: %v\n", err)
os.Exit(1)
}
err = cli.MkdirFor(dataDir, int(uid), int(gid))
if err != nil {
cli.Sayf("could not create data dir: %v\n", err)
os.Exit(1)
}
} }
func execDeluser (service string) { func execDeluser (service string, rmData bool) {
fullName := cli.ServiceUser(service) fullName := cli.ServiceUser(service)
dataDir := cli.ServiceDir(service)
// BUSYBOX if deluser, err := exec.LookPath("deluser"); err == nil {
deluser, err := exec.LookPath("deluser") // BUSYBOX
if err == nil {
tryCommand (exec.Command(deluser, fullName, "--remove-home"), tryCommand (exec.Command(deluser, fullName, "--remove-home"),
"could not delete user") "could not delete user")
return } else if userdel, err := exec.LookPath("userdel"); err == nil {
} // GNU
// GNU
userdel, err := exec.LookPath("userdel")
if err == nil {
tryCommand (exec.Command(userdel, fullName, "-r"), tryCommand (exec.Command(userdel, fullName, "-r"),
"could not delete user") "could not delete user")
groupdel, _ := exec.LookPath("groupdel") groupdel, _ := exec.LookPath("groupdel")
tryCommand (exec.Command(groupdel, fullName), tryCommand (exec.Command(groupdel, fullName),
"could not delete group") "could not delete group")
return } else {
cli.Sayf("could not delete user: no command deluser or userdel\n")
os.Exit(1)
} }
cli.Sayf("could not delete user: no command deluser or userdel\n") // delete data directory
os.Exit(1) if rmData {
err := os.RemoveAll(dataDir)
if err != nil {
cli.Sayf("could not delete data dir: %v\n", err)
os.Exit(1)
}
}
} }
func execAuth (service, user string) { func execAuth (service, user string) {

View File

@@ -6,6 +6,7 @@ import "fmt"
import "log" import "log"
import "time" import "time"
import "sync" import "sync"
import "errors"
type routine struct { type routine struct {
run, shutdown func () error run, shutdown func () error
@@ -71,17 +72,16 @@ type Manager struct {
// Run returns only when all routines have exited. // Run returns only when all routines have exited.
func (manager *Manager) Run () error { func (manager *Manager) Run () error {
var waitGroup sync.WaitGroup var waitGroup sync.WaitGroup
var errExit error
for _, routine := range manager.Routines { for _, routine := range manager.Routines {
if routine != nil { if routine != nil {
waitGroup.Add(1) waitGroup.Add(1)
go manager.runRoutine(routine, &waitGroup, &errExit) go manager.runRoutine(routine, &waitGroup)
} }
} }
waitGroup.Wait() waitGroup.Wait()
return errExit return nil
} }
// Shutdown shuts down all routines in the manager. // Shutdown shuts down all routines in the manager.
@@ -113,21 +113,26 @@ func (manager *Manager) log (message ...any) {
} }
} }
func (manager *Manager) runRoutine (routine Routine, group *sync.WaitGroup, errExit *error) { func (manager *Manager) runRoutine (routine Routine, group *sync.WaitGroup) {
defer group.Done() defer group.Done()
var err error
for { for {
lastStart := time.Now()
err := panicWrap(routine.Run)
stopping := false stopping := false
manager.stoppingMutex.Lock() manager.stoppingMutex.Lock()
stopping = manager.stopping stopping = manager.stopping
manager.stoppingMutex.Unlock() manager.stoppingMutex.Unlock()
if stopping { break } if stopping {
if err == nil {
// TODO: recover from panics manager.log("(i) stopped routine")
lastStart := time.Now() } else {
err = routine.Run() manager.log("!!! stopped routine, with error:", err)
}
break
}
if err == nil { if err == nil {
manager.log("(i) routine exited") manager.log("(i) routine exited")
break break
@@ -142,8 +147,15 @@ func (manager *Manager) runRoutine (routine Routine, group *sync.WaitGroup, errE
manager.log("(i) routine is being restarted") manager.log("(i) routine is being restarted")
} }
} }
}
if err != nil {
*errExit = err func panicWrap (f func () error) (err error) {
} defer func () {
if pan := recover(); pan != nil {
err = errors.New(fmt.Sprint(pan))
}
} ()
err = f()
return
} }

View File

@@ -19,11 +19,13 @@ type HTTP struct {
Handler http.Handler Handler http.Handler
conn *Conn conn *Conn
running bool
requests requestManager requests requestManager
} }
// Close closes the mount abruptly, interrupting any active connections. // Close closes the mount abruptly, interrupting any active connections.
func (htmount *HTTP) Close () error { func (htmount *HTTP) Close () error {
htmount.running = false
return htmount.conn.Close() return htmount.conn.Close()
} }
@@ -44,12 +46,19 @@ func (htmount *HTTP) Run (service ServiceInfo) (err error) {
} }
htmount.conn, err = Dial(htmount.MountInfo, service) htmount.conn, err = Dial(htmount.MountInfo, service)
if err != nil { return } if err != nil { return }
htmount.running = true
htmount.requests.init() htmount.requests.init()
for { for {
message, err := htmount.conn.Receive() message, err := htmount.conn.Receive()
if err != nil { return err } if err != nil {
if htmount.running {
return err
} else {
return nil
}
}
switch message.(type) { switch message.(type) {
case protocol.MessageHTTPRequest: case protocol.MessageHTTPRequest: