diff --git a/build/entrypoint.sh b/build/entrypoint.sh index 545fab3..1bb481b 100755 --- a/build/entrypoint.sh +++ b/build/entrypoint.sh @@ -21,6 +21,6 @@ for target in $targets; do rm $output done -echo "----> Build is complete. List of files at $release_path:" +echo "----> Build is complete. List of files at build/:" cd build/ ls -l gokapi-* diff --git a/build/go.sum b/build/go.sum index ccc62e5..251d0a4 100644 --- a/build/go.sum +++ b/build/go.sum @@ -60,6 +60,7 @@ github.com/aws/aws-sdk-go v1.48.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3Tju github.com/aws/aws-sdk-go v1.48.2/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go v1.49.22/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/aws/aws-sdk-go v1.51.7/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.51.25/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= @@ -399,6 +400,7 @@ golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -415,6 +417,7 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQz golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa/go.mod h1:zk2irFbV9DP96SEBUUAy67IdHUaZuSnrz1n472HUCLE= golang.org/x/exp v0.0.0-20240112132812-db7319d0e0e3/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -445,6 +448,7 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -497,6 +501,7 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -515,6 +520,7 @@ golang.org/x/oauth2 v0.13.0/go.mod h1:/JMhi4ZRXAf4HG9LiNmxvk+45+96RUlVThiH8FzNBn golang.org/x/oauth2 v0.14.0/go.mod h1:lAtNWgaWfL4cm7j2OV8TxGi9Qb7ECORx8DktCY74OwM= golang.org/x/oauth2 v0.16.0/go.mod h1:hqZ+0LWXsiVoZpeld6jVt06P3adbS2Uu911W1SsJv2o= golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8= +golang.org/x/oauth2 v0.19.0/go.mod h1:vYi7skDa1x015PmRRYZ7+s1cWyPgrPiSYRe4rnsexc8= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -531,6 +537,7 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -590,6 +597,7 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -601,6 +609,7 @@ golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -679,6 +688,7 @@ golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc= +golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -821,21 +831,26 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= modernc.org/cc/v4 v4.19.5/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/cc/v4 v4.20.0/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= modernc.org/ccgo/v3 v3.17.0/go.mod h1:Sg3fwVpmLvCUTaqEUjiBDAvshIaKDB0RXaf+zgqFu8I= modernc.org/ccgo/v4 v4.13.1/go.mod h1:Td6RI9W9G2ZpKHaJ7UeGEiB2aIpoDqLBnm4wtkbJTbQ= +modernc.org/ccgo/v4 v4.16.0/go.mod h1:dkNyWIjFrVIZ68DTo36vHK+6/ShBn4ysU61So6PIqCI= modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= modernc.org/gc/v3 v3.0.0-20240304020402-f0dba7c97c2b/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4= modernc.org/libc v1.34.9/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/libc v1.40.5/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE= modernc.org/libc v1.47.0/go.mod h1:gzCncw0a74aCiVqHeWAYHHaW//fkSHHS/3S/gfhLlCI= +modernc.org/libc v1.49.3/go.mod h1:yMZuGkn7pXbKfoT/M35gFJOAEdSKdxL0q64sF7KqCDo= modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0= modernc.org/sqlite v1.29.5/go.mod h1:S02dvcmm7TnTRvGhv8IGYyLnIt7AS2KPaB1F/71p75U= +modernc.org/sqlite v1.29.8/go.mod h1:lQPm27iqa4UNZpmr4Aor0MH0HkCLbt1huYDfWylLZFk= modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/internal/helper/systemd/Systemd.go b/internal/helper/systemd/Systemd.go index d1e44d6..8caa928 100644 --- a/internal/helper/systemd/Systemd.go +++ b/internal/helper/systemd/Systemd.go @@ -1,158 +1,24 @@ +//go:build !linux + package systemd import ( "fmt" "os" - "os/exec" - "os/user" - "path/filepath" - "syscall" ) // InstallService installs Gokapi as a systemd service func InstallService() { - checkRunAsRoot() - checkSystemdOs() - - fmt.Println("Installing Gokapi as a service...") - - // Check if the service file already exists - if _, err := os.Stat("/usr/lib/systemd/system/gokapi.service"); err == nil { - fmt.Println("Service file already exists. Reinstalling it") - } - - // Find the path to the current executable and it's directory - executablePath, err := os.Executable() - if err != nil { - fmt.Println("Error getting executable path: ", err) - os.Exit(6) - } - executableDir := filepath.Dir(executablePath) - - username := getUserInvokingSudo(executablePath) - fmt.Println("Running service as user", username) - - // Create the service file - serviceFileContents := createSystemdFileContent(executablePath, executableDir, username) - - err = os.WriteFile("/usr/lib/systemd/system/gokapi.service", serviceFileContents, 0644) - if err != nil { - fmt.Println("Error writing service data to file: ", err) - os.Exit(3) - } - - systemctlCmd("daemon-reload") - systemctlCmd("enable", "gokapi.service") - systemctlCmd("start", "gokapi.service") - - fmt.Println("Service installed and started successfully.") - fmt.Println("The Gokapi executable found at " + executablePath + " will now run on startup in the background.") - fmt.Println("Please do not remove the executable file from that location or the service will not start.") - - // Exit the program - os.Exit(0) - + invalidOS() } // UninstallService uninstalls Gokapi as a systemd service func UninstallService() { - checkRunAsRoot() - checkSystemdOs() - - fmt.Println("Uninstalling Gokapi systemd service...") - - // Check if the service file exists - if _, err := os.Stat("/usr/lib/systemd/system/gokapi.service"); os.IsNotExist(err) { - fmt.Println("Service does not exist in systemd. Nothing to uninstall.") - os.Exit(3) - } - systemctlCmd("stop", "gokapi.service") - systemctlCmd("disable", "gokapi.service") - // Remove the service file - fmt.Println("Removing the service file...") - err := os.Remove("/usr/lib/systemd/system/gokapi.service") - if err != nil { - fmt.Println("Error removing service file: ", err) - os.Exit(4) - } - - systemctlCmd("daemon-reload") - fmt.Println("Service uninstalled successfully.") - - // Exit the program - os.Exit(0) + invalidOS() } -// checkRunAsRoot displays an error message and exits the program if not run as root -func checkRunAsRoot() { - if os.Geteuid() != 0 { - fmt.Println("This feature requires root privileges.") - os.Exit(1) - } -} - -// checkSystemdOs displays an error message and exits the program if the OS is not systemd based -func checkSystemdOs() { - if _, err := os.Stat("/usr/lib/systemd/system"); os.IsNotExist(err) { - fmt.Println("This feature is only supported on systems using systemd.") - os.Exit(2) - } -} - -// systemctlCmd runs the command systemctl with the provided arguments. It displays an error message and exits the program -// if an error is encountered -func systemctlCmd(arg ...string) { - err := exec.Command("systemctl", arg...).Run() - if err != nil { - fmt.Println("Error executing systemctl "+arg[0]+": ", err) - os.Exit(4) - } -} - -func getUserInvokingSudo(executablePath string) string { - username := os.Getenv("SUDO_USER") - if username == "root" || username == "" { - fmt.Println("WARNING! Could not determine user invoking sudo.") - usernameFromExecutable, err := getUsernameOfFileOwner(executablePath) - if err != nil { - fmt.Println("Could not determine username from file owner:", err) - os.Exit(6) - } - username = usernameFromExecutable - } - if username == "root" { - fmt.Println("Could not determine username other than root. Not running service as root.") - os.Exit(6) - } - return username -} - -func getUsernameOfFileOwner(filename string) (string, error) { - fileInfo, err := os.Stat(filename) - if err != nil { - return "", err - } - - fileUid := fileInfo.Sys().(*syscall.Stat_t).Uid - fileUser, err := user.LookupId(fmt.Sprintf("%d", fileUid)) - if err != nil { - return "", err - } - return fileUser.Username, nil -} - -// createSystemdFileContent returns a byte array with the content of the systemd file to be written -func createSystemdFileContent(executablePath, executableDir, username string) []byte { - return []byte(`[Unit] -Description=Gokapi -After=network.target - -[Service] -ExecStart=` + executablePath + ` -WorkingDirectory=` + executableDir + ` -User=` + username + ` -Restart=always - -[Install] -WantedBy=multi-user.target`) +// invalidOS displays an error message and exits the program, as systemd is not supported on Windows +func invalidOS() { + fmt.Println("This feature is only supported on systems using systemd.") + os.Exit(2) } diff --git a/internal/helper/systemd/Systemd_linux.go b/internal/helper/systemd/Systemd_linux.go new file mode 100644 index 0000000..ea17c14 --- /dev/null +++ b/internal/helper/systemd/Systemd_linux.go @@ -0,0 +1,160 @@ +//go:build linux + +package systemd + +import ( + "fmt" + "os" + "os/exec" + "os/user" + "path/filepath" + "syscall" +) + +// InstallService installs Gokapi as a systemd service +func InstallService() { + checkRunAsRoot() + checkSystemdOs() + + fmt.Println("Installing Gokapi as a service...") + + // Check if the service file already exists + if _, err := os.Stat("/usr/lib/systemd/system/gokapi.service"); err == nil { + fmt.Println("Service file already exists. Reinstalling it") + } + + // Find the path to the current executable and it's directory + executablePath, err := os.Executable() + if err != nil { + fmt.Println("Error getting executable path: ", err) + os.Exit(6) + } + executableDir := filepath.Dir(executablePath) + + username := getUserInvokingSudo(executablePath) + fmt.Println("Running service as user", username) + + // Create the service file + serviceFileContents := createSystemdFileContent(executablePath, executableDir, username) + + err = os.WriteFile("/usr/lib/systemd/system/gokapi.service", serviceFileContents, 0644) + if err != nil { + fmt.Println("Error writing service data to file: ", err) + os.Exit(3) + } + + systemctlCmd("daemon-reload") + systemctlCmd("enable", "gokapi.service") + systemctlCmd("start", "gokapi.service") + + fmt.Println("Service installed and started successfully.") + fmt.Println("The Gokapi executable found at " + executablePath + " will now run on startup in the background.") + fmt.Println("Please do not remove the executable file from that location or the service will not start.") + + // Exit the program + os.Exit(0) + +} + +// UninstallService uninstalls Gokapi as a systemd service +func UninstallService() { + checkRunAsRoot() + checkSystemdOs() + + fmt.Println("Uninstalling Gokapi systemd service...") + + // Check if the service file exists + if _, err := os.Stat("/usr/lib/systemd/system/gokapi.service"); os.IsNotExist(err) { + fmt.Println("Service does not exist in systemd. Nothing to uninstall.") + os.Exit(3) + } + systemctlCmd("stop", "gokapi.service") + systemctlCmd("disable", "gokapi.service") + // Remove the service file + fmt.Println("Removing the service file...") + err := os.Remove("/usr/lib/systemd/system/gokapi.service") + if err != nil { + fmt.Println("Error removing service file: ", err) + os.Exit(4) + } + + systemctlCmd("daemon-reload") + fmt.Println("Service uninstalled successfully.") + + // Exit the program + os.Exit(0) +} + +// checkRunAsRoot displays an error message and exits the program if not run as root +func checkRunAsRoot() { + if os.Geteuid() != 0 { + fmt.Println("This feature requires root privileges.") + os.Exit(1) + } +} + +// checkSystemdOs displays an error message and exits the program if the OS is not systemd based +func checkSystemdOs() { + if _, err := os.Stat("/usr/lib/systemd/system"); os.IsNotExist(err) { + fmt.Println("This feature is only supported on systems using systemd.") + os.Exit(2) + } +} + +// systemctlCmd runs the command systemctl with the provided arguments. It displays an error message and exits the program +// if an error is encountered +func systemctlCmd(arg ...string) { + err := exec.Command("systemctl", arg...).Run() + if err != nil { + fmt.Println("Error executing systemctl "+arg[0]+": ", err) + os.Exit(4) + } +} + +func getUserInvokingSudo(executablePath string) string { + username := os.Getenv("SUDO_USER") + if username == "root" || username == "" { + fmt.Println("WARNING! Could not determine user invoking sudo.") + usernameFromExecutable, err := getUsernameOfFileOwner(executablePath) + if err != nil { + fmt.Println("Could not determine username from file owner:", err) + os.Exit(6) + } + username = usernameFromExecutable + } + if username == "root" { + fmt.Println("Could not determine username other than root. Not running service as root.") + os.Exit(6) + } + return username +} + +func getUsernameOfFileOwner(filename string) (string, error) { + fileInfo, err := os.Stat(filename) + if err != nil { + return "", err + } + + fileUid := fileInfo.Sys().(*syscall.Stat_t).Uid + fileUser, err := user.LookupId(fmt.Sprintf("%d", fileUid)) + if err != nil { + return "", err + } + return fileUser.Username, nil +} + +// createSystemdFileContent returns a byte array with the content of the systemd file to be written +func createSystemdFileContent(executablePath, executableDir, username string) []byte { + return []byte(`[Unit] +Description=Gokapi +After=network.target + +[Service] +ExecStart=` + executablePath + ` +WorkingDirectory=` + executableDir + ` +User=` + username + ` +Restart=always + +[Install] +WantedBy=multi-user.target`) +}