Compare commits
No commits in common. "3cd53b3dd99ba30054a5c2534f2f583840041538" and "92b93abb13c14d6bf594a8fa9202832198e382d8" have entirely different histories.
3cd53b3dd9
...
92b93abb13
37
cli/cli.go
37
cli/cli.go
@ -1,37 +0,0 @@
|
|||||||
// Package cli provides utilities for writing command line utilities that
|
|
||||||
// interact with services.
|
|
||||||
package cli
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
import "fmt"
|
|
||||||
import "flag"
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// Sayf is like Printf, but prints the program name before the message. This is
|
|
||||||
// used for printing messages and errors.
|
|
||||||
func Sayf (format string, values ...any) {
|
|
||||||
Printf(os.Args[0] + ": " + format, values...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Printf prints to stderr.
|
|
||||||
func Printf (format string, values ...any) {
|
|
||||||
fmt.Fprintf(flag.CommandLine.Output(), format, values...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServiceUser returns the system user that corresponds to the given service
|
|
||||||
// name. This is not necissarily equivalent Hnakra user, although it is good
|
|
||||||
// practice to have a 1:1 correspondance between them.
|
|
||||||
func ServiceUser (service string) string {
|
|
||||||
return "hn-" + strings.ToLower(service)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 privelages.
|
|
||||||
func NeedRoot() {
|
|
||||||
uid := os.Getuid()
|
|
||||||
if uid != 0 {
|
|
||||||
Sayf("this utility must be run as root")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,151 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
import "fmt"
|
|
||||||
import "time"
|
|
||||||
import "flag"
|
|
||||||
import "os/exec"
|
|
||||||
import "hnakra/cli"
|
|
||||||
import "path/filepath"
|
|
||||||
import "hnakra/cmd/hnctl/spawn"
|
|
||||||
|
|
||||||
func main () {
|
|
||||||
flag.Usage = func () {
|
|
||||||
out := flag.CommandLine.Output()
|
|
||||||
fmt.Fprintf(out, "Usage of %s:\n", os.Args[0])
|
|
||||||
fmt.Fprintf(out, " start\n")
|
|
||||||
fmt.Fprintf(out, " Start a service\n")
|
|
||||||
fmt.Fprintf(out, " stop\n")
|
|
||||||
fmt.Fprintf(out, " Stop a service\n")
|
|
||||||
fmt.Fprintf(out, " restart\n")
|
|
||||||
fmt.Fprintf(out, " Start and then stop a service\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// define commands
|
|
||||||
startCommand := flag.NewFlagSet("start", flag.ExitOnError)
|
|
||||||
startService := startCommand.String("s", "router", "Service to start")
|
|
||||||
|
|
||||||
stopCommand := flag.NewFlagSet("stop", flag.ExitOnError)
|
|
||||||
stopService := stopCommand.String("s", "router", "Service to stop")
|
|
||||||
|
|
||||||
restartCommand := flag.NewFlagSet("restart", flag.ExitOnError)
|
|
||||||
restartService := stopCommand.String("s", "router", "Service to restart")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
// execute correct command
|
|
||||||
if len(os.Args) < 2 {
|
|
||||||
flag.Usage()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
subCommandArgs := os.Args[2:]
|
|
||||||
switch os.Args[1] {
|
|
||||||
case "start":
|
|
||||||
startCommand.Parse(subCommandArgs)
|
|
||||||
execStart(*startService)
|
|
||||||
case "stop":
|
|
||||||
stopCommand.Parse(subCommandArgs)
|
|
||||||
execStop(*stopService)
|
|
||||||
case "restart":
|
|
||||||
restartCommand.Parse(subCommandArgs)
|
|
||||||
execStop(*restartService)
|
|
||||||
execStart(*restartService)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func execStart (service string) {
|
|
||||||
fullName := cli.ServiceUser(service)
|
|
||||||
cli.NeedRoot()
|
|
||||||
|
|
||||||
pid, err := spawn.PidOf(fullName)
|
|
||||||
if err == nil && spawn.Running(pid) {
|
|
||||||
cli.Sayf("service is already running")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
uid, gid, err := spawn.LookupUID(fullName)
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("cannot start service: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
path, err := exec.LookPath(fullName)
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("cannot start service: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
logDir := filepath.Join("/var/log/", fullName)
|
|
||||||
env := append(os.Environ(), "HNAKRA_LOG_DIR=" + logDir)
|
|
||||||
err = ensureLogDir(logDir, int(uid), int(gid))
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("cannot start service: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepare pidfile. the service will be responsible for actually writing
|
|
||||||
// to it
|
|
||||||
err = ensurePidFile(spawn.PidFile(fullName), int(uid), int(gid))
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("cannot start service: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// spawn the service
|
|
||||||
pid, err = spawn.Spawn(path, uid, gid, env)
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("cannot start service: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func execStop (service string) {
|
|
||||||
fullName := cli.ServiceUser(service)
|
|
||||||
cli.NeedRoot()
|
|
||||||
|
|
||||||
pid, err := spawn.PidOf(fullName)
|
|
||||||
if err != nil || !spawn.Running(pid) {
|
|
||||||
cli.Sayf("service is not running")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
process, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("service is not running")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = spawn.KillAndWait(process, 16 * time.Second)
|
|
||||||
if err != nil {
|
|
||||||
cli.Sayf("could not stop service: %v", err)
|
|
||||||
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 {
|
|
||||||
pidFile, err := os.Create(file)
|
|
||||||
if err != nil { return err }
|
|
||||||
err = pidFile.Close()
|
|
||||||
if err != nil { return err }
|
|
||||||
|
|
||||||
err = os.Chmod(file, 0660)
|
|
||||||
if err != nil { return err }
|
|
||||||
err = os.Chown(file, uid, gid)
|
|
||||||
if err != nil { return err }
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,132 +0,0 @@
|
|||||||
// Package spawn provides utilities for daemonizing services.
|
|
||||||
package spawn
|
|
||||||
|
|
||||||
import "os"
|
|
||||||
import "fmt"
|
|
||||||
import "time"
|
|
||||||
import "errors"
|
|
||||||
import "syscall"
|
|
||||||
import "os/user"
|
|
||||||
import "strconv"
|
|
||||||
import "path/filepath"
|
|
||||||
|
|
||||||
// Spawn spawns a process in the background and returns its PID.
|
|
||||||
func Spawn (path string, uid, gid uint32, env []string, args ...string) (pid int, err error) {
|
|
||||||
cred := &syscall.Credential{
|
|
||||||
Uid: uid,
|
|
||||||
Gid: gid,
|
|
||||||
Groups: []uint32{},
|
|
||||||
NoSetGroups: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// the Noctty flag is used to detach the process from parent tty
|
|
||||||
sysproc := &syscall.SysProcAttr{
|
|
||||||
Credential: cred,
|
|
||||||
Noctty: true,
|
|
||||||
}
|
|
||||||
attr := os.ProcAttr{
|
|
||||||
Dir: ".",
|
|
||||||
Env: os.Environ(),
|
|
||||||
Files: []*os.File{
|
|
||||||
os.Stdin,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
},
|
|
||||||
Sys: sysproc,
|
|
||||||
}
|
|
||||||
|
|
||||||
process, err := os.StartProcess(path, args, &attr)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Release() is what actually detatches the process and places it under
|
|
||||||
// init
|
|
||||||
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
|
|
||||||
// specifically, it returns `/run/<name>.pid`.
|
|
||||||
func PidFile (name string) string {
|
|
||||||
return filepath.Join("/run/", name + ".pid")
|
|
||||||
}
|
|
||||||
|
|
||||||
// PidOf returns the PID stored in the pidfile of the given name as defined by
|
|
||||||
// PidFile.
|
|
||||||
func PidOf (name string) (pid int, err error) {
|
|
||||||
content, err := os.ReadFile(PidFile(name))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
pid, err = strconv.Atoi(string(content))
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return pid, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Running returns whether or not a process with the given PID is running.
|
|
||||||
func Running (pid int) bool {
|
|
||||||
directoryInfo, err := os.Stat("/proc/")
|
|
||||||
if os.IsNotExist(err) || !directoryInfo.IsDir() {
|
|
||||||
// if /proc/ does not exist, fallback to sending a signal
|
|
||||||
process, err := os.FindProcess(pid)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
err = process.Signal(syscall.Signal(0))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if /proc/ exists, see if the process's directory exists there
|
|
||||||
_, err = os.Stat("/proc/" + strconv.Itoa(pid))
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// KillAndWait kills a process and waits for it to finish, with a timeout. If
|
|
||||||
// the timeout is zero, it will wait indefinetly. This function will poll every
|
|
||||||
// 100 milliseconds to see if the process has finished.
|
|
||||||
func KillAndWait (process *os.Process, timeout time.Duration) error {
|
|
||||||
pid := process.Pid
|
|
||||||
err := process.Kill()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for the process to exit, with a timeout
|
|
||||||
timeoutPoint := time.Now()
|
|
||||||
for timeout == 0 || time.Since(timeoutPoint) < 16 * time.Second {
|
|
||||||
if !Running(pid) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors.New(fmt.Sprintf (
|
|
||||||
"timeout exceeded while waiting for process %d to finish", pid))
|
|
||||||
}
|
|
@ -4,9 +4,8 @@ import "io"
|
|||||||
import "os"
|
import "os"
|
||||||
import "log"
|
import "log"
|
||||||
import "time"
|
import "time"
|
||||||
import "hnakra/router"
|
|
||||||
import "hnakra/rotate"
|
import "hnakra/rotate"
|
||||||
import "hnakra/daemon"
|
import "hnakra/router"
|
||||||
import "hnakra/routines"
|
import "hnakra/routines"
|
||||||
import "hnakra/router/rcon"
|
import "hnakra/router/rcon"
|
||||||
import "hnakra/router/config"
|
import "hnakra/router/config"
|
||||||
@ -60,10 +59,10 @@ func main () {
|
|||||||
manager := routines.Manager { RestartDeadline: time.Second * 8 }
|
manager := routines.Manager { RestartDeadline: time.Second * 8 }
|
||||||
rout := router.New(conf)
|
rout := router.New(conf)
|
||||||
srvhnakra := &srvhnakra.Server { Config: conf, Router: rout }
|
srvhnakra := &srvhnakra.Server { Config: conf, Router: rout }
|
||||||
manager.Append(srvhnakra)
|
manager.Append(srvhnakra.ListenAndServe)
|
||||||
if conf.HTTPSEnable() {
|
if conf.HTTPSEnable() {
|
||||||
srvhttps := &srvhttps.Server { Config: conf, Handler: rout }
|
srvhttps := &srvhttps.Server { Config: conf, Handler: rout }
|
||||||
manager.Append(srvhttps)
|
manager.Append(srvhttps.ListenAndServe)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up rcon
|
// set up rcon
|
||||||
@ -75,19 +74,12 @@ func main () {
|
|||||||
log.SetOutput(originalWriter)
|
log.SetOutput(originalWriter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// be a daemon
|
|
||||||
daemon.ShutdownOnSigint(&manager)
|
|
||||||
pidfile := daemon.PidFile(os.Getenv("HNAKRA_PIDFILE"))
|
|
||||||
if !pidfile.Empty() {
|
|
||||||
err := pidfile.Start()
|
|
||||||
if err != nil { log.Println("!!! could not write pid:", err) }
|
|
||||||
defer func () {
|
|
||||||
err := pidfile.Close()
|
|
||||||
if err != nil { log.Println("!!! could not delete pidfile:", err) }
|
|
||||||
} ()
|
|
||||||
}
|
|
||||||
|
|
||||||
// run servers
|
// run servers
|
||||||
err = manager.Run()
|
err = manager.Run()
|
||||||
if err != nil { log.Println("XXX", err) }
|
if err != nil { log.Println("XXX", err) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func httpsRoutine (server *srvhttps.Server) {
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if err != nil { log.Println("XXX", err) }
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ type Server struct {
|
|||||||
Router *router.Router
|
Router *router.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Run () (err error) {
|
func (server *Server) ListenAndServe () (err error) {
|
||||||
server.underlying, err = tls.Listen (
|
server.underlying, err = tls.Listen (
|
||||||
"tcp", fmt.Sprint(":", server.Config.RouterPort()),
|
"tcp", fmt.Sprint(":", server.Config.RouterPort()),
|
||||||
config.TLSConfigFor(server.Config))
|
config.TLSConfigFor(server.Config))
|
||||||
@ -29,6 +29,6 @@ func (server *Server) Run () (err error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Shutdown () error {
|
func (server *Server) Close () error {
|
||||||
return server.underlying.Close()
|
return server.underlying.Close()
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ type Server struct {
|
|||||||
Handler http.Handler
|
Handler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Run () error {
|
func (server *Server) ListenAndServe () error {
|
||||||
server.underlying = &http.Server {
|
server.underlying = &http.Server {
|
||||||
Addr: fmt.Sprint(":", server.Config.HTTPSPort()),
|
Addr: fmt.Sprint(":", server.Config.HTTPSPort()),
|
||||||
// ReadHeaderTimeout: timeoutReadHeader * time.Second,
|
// ReadHeaderTimeout: timeoutReadHeader * time.Second,
|
||||||
@ -26,6 +26,6 @@ func (server *Server) Run () error {
|
|||||||
return server.underlying.ListenAndServeTLS("", "")
|
return server.underlying.ListenAndServeTLS("", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (server *Server) Shutdown () error {
|
func (server *Server) Close () error {
|
||||||
return server.underlying.Close()
|
return server.underlying.Close()
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,24 @@ import "flag"
|
|||||||
import "strconv"
|
import "strconv"
|
||||||
import "os/exec"
|
import "os/exec"
|
||||||
import "os/user"
|
import "os/user"
|
||||||
import "hnakra/cli"
|
|
||||||
import "path/filepath"
|
import "path/filepath"
|
||||||
import "golang.org/x/crypto/bcrypt"
|
import "golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
func printErr (format string, values ...any) {
|
||||||
|
fmt.Fprintf (
|
||||||
|
flag.CommandLine.Output(),
|
||||||
|
os.Args[0] + ": " + format + "\n",
|
||||||
|
values...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceUser (service string) string {
|
||||||
|
return "hn-" + service
|
||||||
|
}
|
||||||
|
|
||||||
func tryCommand (cmd *exec.Cmd, failReason string) {
|
func tryCommand (cmd *exec.Cmd, failReason string) {
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("%s: %s\n", failReason, string(output))
|
printErr("%s: %s", failReason, string(output))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -21,13 +31,13 @@ func tryCommand (cmd *exec.Cmd, failReason string) {
|
|||||||
func ownOne (path string, uid, gid int) {
|
func ownOne (path string, uid, gid int) {
|
||||||
file, err := os.Stat(path)
|
file, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not stat %s: %v\n", path, err)
|
printErr("could not stat %s: %v", path, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Chown(path, uid, gid)
|
err = os.Chown(path, uid, gid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not change ownership of %s: %v\n", path, err)
|
printErr("could not change ownership of %s: %v", path, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -37,7 +47,7 @@ func ownOne (path string, uid, gid int) {
|
|||||||
err = os.Chmod(path, 0660)
|
err = os.Chmod(path, 0660)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not change mode of %s: %v\n", path, err)
|
printErr("could not change mode of %s: %v", path, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,22 +55,23 @@ func ownOne (path string, uid, gid int) {
|
|||||||
func main () {
|
func main () {
|
||||||
user, err := user.Current()
|
user, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not get username %v\n", err)
|
printErr("could not get username %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
flag.Usage = func () {
|
flag.Usage = func () {
|
||||||
cli.Printf("Usage of %s:\n", os.Args[0])
|
out := flag.CommandLine.Output()
|
||||||
cli.Printf(" hash\n")
|
fmt.Fprintf(out, "Usage of %s:\n", os.Args[0])
|
||||||
cli.Printf(" Generate a bcrypt hash of a key\n")
|
fmt.Fprintf(out, " hash\n")
|
||||||
cli.Printf(" adduser\n")
|
fmt.Fprintf(out, " Generate a bcrypt hash of a key\n")
|
||||||
cli.Printf(" Add a system user to run a service as\n")
|
fmt.Fprintf(out, " adduser\n")
|
||||||
cli.Printf(" deluser\n")
|
fmt.Fprintf(out, " Add a system user to run a service as\n")
|
||||||
cli.Printf(" Remove a user added with adduser\n")
|
fmt.Fprintf(out, " deluser\n")
|
||||||
cli.Printf(" auth\n")
|
fmt.Fprintf(out, " Remove a user added with adduser\n")
|
||||||
cli.Printf(" Authorize a system user to access a service's files\n")
|
fmt.Fprintf(out, " auth\n")
|
||||||
cli.Printf(" own\n")
|
fmt.Fprintf(out, " Authorize a system user to access a service's files\n")
|
||||||
cli.Printf(" Give ownership of a file to a service\n")
|
fmt.Fprintf(out, " own\n")
|
||||||
|
fmt.Fprintf(out, " Give ownership of a file to a service\n")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,22 +131,22 @@ func main () {
|
|||||||
|
|
||||||
func execHash (cost int, key string) {
|
func execHash (cost int, key string) {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
cli.Sayf("please specify key text content\n")
|
printErr("please specify key text content")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if cost < bcrypt.MinCost {
|
if cost < bcrypt.MinCost {
|
||||||
cli.Sayf("cost is too low, must be at least %v\n", bcrypt.MinCost)
|
printErr("cost is too low, must be at least %v", bcrypt.MinCost)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
if cost > bcrypt.MaxCost {
|
if cost > bcrypt.MaxCost {
|
||||||
cli.Sayf("cost is too hight, can be at most %v\n", bcrypt.MaxCost)
|
printErr("cost is too hight, can be at most %v", bcrypt.MaxCost)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(key), cost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(key), cost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not hash key: %v\n", err)
|
printErr("could not hash key: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +154,7 @@ func execHash (cost int, key string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func execAdduser (service string) {
|
func execAdduser (service string) {
|
||||||
fullName := cli.ServiceUser(service)
|
fullName := serviceUser(service)
|
||||||
|
|
||||||
// BUSYBOX
|
// BUSYBOX
|
||||||
adduser, err := exec.LookPath("adduser")
|
adduser, err := exec.LookPath("adduser")
|
||||||
@ -165,12 +176,12 @@ func execAdduser (service string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.Sayf("could not add user: no command adduser or useradd\n")
|
printErr("could not add user: no command adduser or useradd")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execDeluser (service string) {
|
func execDeluser (service string) {
|
||||||
fullName := cli.ServiceUser(service)
|
fullName := serviceUser(service)
|
||||||
|
|
||||||
// BUSYBOX
|
// BUSYBOX
|
||||||
deluser, err := exec.LookPath("deluser")
|
deluser, err := exec.LookPath("deluser")
|
||||||
@ -191,12 +202,12 @@ func execDeluser (service string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.Sayf("could not delete user: no command deluser or userdel\n")
|
printErr("could not delete user: no command deluser or userdel")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execAuth (service, user string) {
|
func execAuth (service, user string) {
|
||||||
fullName := cli.ServiceUser(service)
|
fullName := serviceUser(service)
|
||||||
|
|
||||||
adduser, err := exec.LookPath("adduser")
|
adduser, err := exec.LookPath("adduser")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -213,19 +224,19 @@ func execAuth (service, user string) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cli.Sayf("could not auth user: no command adduser or usermod\n")
|
printErr("could not auth user: no command adduser or usermod")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func execOwn (service, file string, recurse bool) {
|
func execOwn (service, file string, recurse bool) {
|
||||||
fullName := cli.ServiceUser(service)
|
fullName := serviceUser(service)
|
||||||
|
|
||||||
userInfo, err := user.Lookup(fullName)
|
userInfo, err := user.Lookup(fullName)
|
||||||
uid, _ := strconv.Atoi(userInfo.Uid)
|
uid, _ := strconv.Atoi(userInfo.Uid)
|
||||||
gid, _ := strconv.Atoi(userInfo.Gid)
|
gid, _ := strconv.Atoi(userInfo.Gid)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not get user info: %v\n", err)
|
printErr("could not get user info: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,7 +251,7 @@ func execOwn (service, file string, recurse bool) {
|
|||||||
err error,
|
err error,
|
||||||
) error {
|
) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not traverse filesystem: %v\n", err)
|
printErr("could not traverse filesystem: %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,7 +260,7 @@ func execOwn (service, file string, recurse bool) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cli.Sayf("could not traverse filesystem: %v\n", err)
|
printErr("could not traverse filesystem: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,50 +0,0 @@
|
|||||||
// Package daemon provides utilities for daemons.
|
|
||||||
package daemon
|
|
||||||
|
|
||||||
import "io"
|
|
||||||
import "os"
|
|
||||||
import "syscall"
|
|
||||||
import "strconv"
|
|
||||||
import "os/signal"
|
|
||||||
|
|
||||||
// PidFile is a string that contains a path to a pidfile.
|
|
||||||
type PidFile string
|
|
||||||
|
|
||||||
// Start writes to the pidfile.
|
|
||||||
func (pidfile PidFile) Start () error {
|
|
||||||
return os.WriteFile(string(pidfile), []byte(strconv.Itoa(os.Getpid())), 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close deletes the pidfile.
|
|
||||||
func (pidfile PidFile) Close () error {
|
|
||||||
return os.Remove(string(pidfile))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Empty returns true if the object is zero value (an empty string).
|
|
||||||
func (pidfile PidFile) Empty () bool {
|
|
||||||
return pidfile == ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnSigint calls the specified function once sigint is recieved. This function
|
|
||||||
// does not block, and spawns a goroutine that waits. For this reason, the
|
|
||||||
// callback must be safe to call concurrently.
|
|
||||||
func OnSigint (callback func ()) {
|
|
||||||
go func () {
|
|
||||||
sigintNotify := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigintNotify, os.Interrupt, syscall.SIGTERM)
|
|
||||||
|
|
||||||
<-sigintNotify
|
|
||||||
callback()
|
|
||||||
} ()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseOnSigint is like OnSigint, but takes an io.Closer.
|
|
||||||
func CloseOnSigint (closer io.Closer) {
|
|
||||||
OnSigint(func () { closer.Close() })
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShutdownOnSigint is like OnSigint, but takes an object with a Shutdown()
|
|
||||||
// method.
|
|
||||||
func ShutdownOnSigint (shutdowner interface { Shutdown() error }) {
|
|
||||||
OnSigint(func () { shutdowner.Shutdown() })
|
|
||||||
}
|
|
@ -7,43 +7,9 @@ import "log"
|
|||||||
import "time"
|
import "time"
|
||||||
import "sync"
|
import "sync"
|
||||||
|
|
||||||
type routine struct {
|
// Routine is a long-running function that does not return until it is finished.
|
||||||
run, shutdown func () error
|
// An error is returned if the routine exited due to an error.
|
||||||
}
|
type Routine func () error
|
||||||
|
|
||||||
func (routine routine) Run () error {
|
|
||||||
if routine.run == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return routine.run()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (routine routine) Shutdown () error {
|
|
||||||
if routine.shutdown == nil {
|
|
||||||
return nil
|
|
||||||
} else {
|
|
||||||
return routine.shutdown()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// From creates a routine from a separate run and shutdown function.
|
|
||||||
func From (run, shutdown func () error) Routine {
|
|
||||||
return routine {
|
|
||||||
run: run,
|
|
||||||
shutdown: shutdown,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routine is an object that can be run and stopped.
|
|
||||||
type Routine interface {
|
|
||||||
// Run is a long-running function that does not return until it is
|
|
||||||
// finished. An error is returned if the routine exited due to an error.
|
|
||||||
Run () error
|
|
||||||
|
|
||||||
// Shutdown stops Run.
|
|
||||||
Shutdown () error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manager is a system capable of managing multiple routines, and restarting
|
// Manager is a system capable of managing multiple routines, and restarting
|
||||||
// them if they fail.
|
// them if they fail.
|
||||||
@ -61,9 +27,6 @@ type Manager struct {
|
|||||||
// is nil, messages will be written to the standard logger. To disable
|
// is nil, messages will be written to the standard logger. To disable
|
||||||
// logging altogether, this can be set to io.Discard.
|
// logging altogether, this can be set to io.Discard.
|
||||||
Logger io.Writer
|
Logger io.Writer
|
||||||
|
|
||||||
stoppingMutex sync.Mutex
|
|
||||||
stopping bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run spawns all routines in the Routines slice. If a routine exits with an
|
// Run spawns all routines in the Routines slice. If a routine exits with an
|
||||||
@ -84,21 +47,6 @@ func (manager *Manager) Run () error {
|
|||||||
return errExit
|
return errExit
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown shuts down all routines in the manager.
|
|
||||||
func (manager *Manager) Shutdown () (err error) {
|
|
||||||
manager.stoppingMutex.Lock()
|
|
||||||
manager.stopping = true
|
|
||||||
manager.stoppingMutex.Unlock()
|
|
||||||
|
|
||||||
for _, routine := range manager.Routines {
|
|
||||||
routineErr := routine.Shutdown()
|
|
||||||
if routineErr != nil {
|
|
||||||
err = routineErr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append adds one or more routines to the Routines slice. This has no effect if
|
// Append adds one or more routines to the Routines slice. This has no effect if
|
||||||
// the manager is already running.
|
// the manager is already running.
|
||||||
func (manager *Manager) Append (routines ...Routine) {
|
func (manager *Manager) Append (routines ...Routine) {
|
||||||
@ -118,15 +66,9 @@ func (manager *Manager) runRoutine (routine Routine, group *sync.WaitGroup, errE
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
for {
|
for {
|
||||||
stopping := false
|
|
||||||
manager.stoppingMutex.Lock()
|
|
||||||
stopping = manager.stopping
|
|
||||||
manager.stoppingMutex.Unlock()
|
|
||||||
if stopping { break }
|
|
||||||
|
|
||||||
// TODO: recover from panics
|
// TODO: recover from panics
|
||||||
lastStart := time.Now()
|
lastStart := time.Now()
|
||||||
err = routine.Run()
|
err = routine()
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
manager.log("(i) routine exited")
|
manager.log("(i) routine exited")
|
||||||
|
@ -5,7 +5,6 @@ import "os"
|
|||||||
import "log"
|
import "log"
|
||||||
import "time"
|
import "time"
|
||||||
import "hnakra/rotate"
|
import "hnakra/rotate"
|
||||||
import "hnakra/daemon"
|
|
||||||
import "hnakra/routines"
|
import "hnakra/routines"
|
||||||
|
|
||||||
// Service is capable of managing multiple mounts. It also sets up logging
|
// Service is capable of managing multiple mounts. It also sets up logging
|
||||||
@ -13,8 +12,6 @@ import "hnakra/routines"
|
|||||||
type Service struct {
|
type Service struct {
|
||||||
ServiceInfo
|
ServiceInfo
|
||||||
Mounts []Mount
|
Mounts []Mount
|
||||||
|
|
||||||
manager routines.Manager
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewService provides a shorthand for creating a new service, leaving most
|
// NewService provides a shorthand for creating a new service, leaving most
|
||||||
@ -32,8 +29,6 @@ func NewService (name, description string, mounts ...Mount) *Service {
|
|||||||
// Run runs the mounts within the service, and only exits when all of them have
|
// Run runs the mounts within the service, and only exits when all of them have
|
||||||
// exited. It will automatically start logging to the directory specified by
|
// exited. It will automatically start logging to the directory specified by
|
||||||
// $HNAKRA_LOG_DIR. If that variable is unset, it will just log to stdout.
|
// $HNAKRA_LOG_DIR. If that variable is unset, it will just log to stdout.
|
||||||
// Additionally, if $HNAKRA_PIDFILE is set, it will write the process PID to the
|
|
||||||
// file specified by it.
|
|
||||||
func (service *Service) Run () error {
|
func (service *Service) Run () error {
|
||||||
// set up logging
|
// set up logging
|
||||||
logDir := os.Getenv("HNAKRA_LOG_DIR")
|
logDir := os.Getenv("HNAKRA_LOG_DIR")
|
||||||
@ -46,28 +41,16 @@ func (service *Service) Run () error {
|
|||||||
log.Println("... starting service", service.Name)
|
log.Println("... starting service", service.Name)
|
||||||
|
|
||||||
// set up routine manager
|
// set up routine manager
|
||||||
service.manager = routines.Manager { RestartDeadline: time.Second * 8 }
|
manager := routines.Manager { RestartDeadline: time.Second * 8 }
|
||||||
service.manager.Routines = make([]routines.Routine, len(service.Mounts))
|
manager.Routines = make([]routines.Routine, len(service.Mounts))
|
||||||
for index, mount := range service.Mounts {
|
for index, mount := range service.Mounts {
|
||||||
service.manager.Routines[index] = routines.From (func () error {
|
manager.Routines[index] = func () error {
|
||||||
return mount.Run(service.ServiceInfo)
|
return mount.Run(service.ServiceInfo)
|
||||||
}, mount.Shutdown)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// be a daemon
|
|
||||||
daemon.ShutdownOnSigint(service)
|
|
||||||
pidfile := daemon.PidFile(os.Getenv("HNAKRA_PIDFILE"))
|
|
||||||
if !pidfile.Empty() {
|
|
||||||
err := pidfile.Start()
|
|
||||||
if err != nil { log.Println("!!! could not write pid:", err) }
|
|
||||||
defer func () {
|
|
||||||
err := pidfile.Close()
|
|
||||||
if err != nil { log.Println("!!! could not delete pidfile:", err) }
|
|
||||||
} ()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// send it
|
// send it
|
||||||
err := service.manager.Run()
|
err := manager.Run()
|
||||||
if err != nil { log.Println("XXX", err) }
|
if err != nil { log.Println("XXX", err) }
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -87,5 +70,11 @@ func (service *Service) Close () (err error) {
|
|||||||
// Shutdown gracefully shuts down each mount in the service. This will cause
|
// Shutdown gracefully shuts down each mount in the service. This will cause
|
||||||
// Run() to exit.
|
// Run() to exit.
|
||||||
func (service *Service) Shutdown () (err error) {
|
func (service *Service) Shutdown () (err error) {
|
||||||
return service.manager.Shutdown()
|
for _, mount := range service.Mounts {
|
||||||
|
singleErr := mount.Shutdown()
|
||||||
|
if singleErr != nil {
|
||||||
|
err = singleErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user