2020-07-29 21:11:28 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2020-08-05 15:58:24 +00:00
|
|
|
"context"
|
2020-07-29 21:11:28 +00:00
|
|
|
"crypto/rand"
|
|
|
|
"encoding/base64"
|
2020-07-31 15:09:30 +00:00
|
|
|
"encoding/json"
|
2020-07-29 21:11:28 +00:00
|
|
|
"fmt"
|
2021-01-31 23:12:50 +00:00
|
|
|
"io/fs"
|
2020-07-31 21:07:09 +00:00
|
|
|
"log"
|
2021-02-08 15:25:02 +00:00
|
|
|
"mime"
|
2020-09-08 22:08:50 +00:00
|
|
|
"net"
|
2020-08-05 15:58:24 +00:00
|
|
|
"net/http"
|
2020-07-29 21:11:28 +00:00
|
|
|
"os"
|
2020-09-08 22:08:50 +00:00
|
|
|
"os/exec"
|
2020-08-05 15:58:24 +00:00
|
|
|
"os/signal"
|
2020-07-29 21:11:28 +00:00
|
|
|
"path/filepath"
|
2020-09-05 20:52:23 +00:00
|
|
|
"runtime"
|
2020-09-16 10:55:35 +00:00
|
|
|
"strings"
|
2023-06-21 20:14:41 +00:00
|
|
|
"sync"
|
2023-06-11 18:48:03 +00:00
|
|
|
"syscall"
|
2020-08-01 14:22:30 +00:00
|
|
|
"time"
|
2020-08-16 12:36:54 +00:00
|
|
|
|
2021-02-19 16:12:14 +00:00
|
|
|
"github.com/fatih/color"
|
2020-11-02 00:53:08 +00:00
|
|
|
"github.com/hrfee/jfa-go/common"
|
2020-09-24 16:51:13 +00:00
|
|
|
_ "github.com/hrfee/jfa-go/docs"
|
2021-04-30 23:13:57 +00:00
|
|
|
"github.com/hrfee/jfa-go/logger"
|
2020-11-02 00:53:08 +00:00
|
|
|
"github.com/hrfee/jfa-go/ombi"
|
2021-03-29 20:49:46 +00:00
|
|
|
"github.com/hrfee/mediabrowser"
|
2020-08-16 12:36:54 +00:00
|
|
|
"github.com/lithammer/shortuuid/v3"
|
|
|
|
"gopkg.in/ini.v1"
|
2020-07-29 21:11:28 +00:00
|
|
|
)
|
|
|
|
|
2021-02-02 18:09:02 +00:00
|
|
|
var (
|
|
|
|
PLATFORM string = runtime.GOOS
|
|
|
|
SOCK string = "jfa-go.sock"
|
|
|
|
SRV *http.Server
|
|
|
|
RESTART chan bool
|
2021-06-01 18:54:13 +00:00
|
|
|
TRAYRESTART chan bool
|
2021-02-02 18:09:02 +00:00
|
|
|
DATA, CONFIG, HOST *string
|
|
|
|
PORT *int
|
|
|
|
DEBUG *bool
|
2021-03-23 21:59:04 +00:00
|
|
|
PPROF *bool
|
2021-02-02 18:09:02 +00:00
|
|
|
TEST bool
|
|
|
|
SWAGGER *bool
|
2021-05-16 15:23:28 +00:00
|
|
|
QUIT = false
|
|
|
|
RUNNING = false
|
2023-06-11 18:48:03 +00:00
|
|
|
// Used to know how many times to re-broadcast restart signal.
|
|
|
|
RESTARTLISTENERCOUNT = 0
|
|
|
|
warning = color.New(color.FgYellow).SprintfFunc()
|
|
|
|
info = color.New(color.FgMagenta).SprintfFunc()
|
|
|
|
hiwhite = color.New(color.FgHiWhite).SprintfFunc()
|
|
|
|
white = color.New(color.FgWhite).SprintfFunc()
|
|
|
|
version string
|
|
|
|
commit string
|
2021-02-02 18:09:02 +00:00
|
|
|
)
|
|
|
|
|
2021-03-07 15:23:44 +00:00
|
|
|
var temp = func() string {
|
|
|
|
temp := "/tmp"
|
|
|
|
if PLATFORM == "windows" {
|
|
|
|
temp = os.Getenv("TEMP")
|
|
|
|
}
|
|
|
|
return temp
|
|
|
|
}()
|
|
|
|
|
2021-01-09 20:38:13 +00:00
|
|
|
var serverTypes = map[string]string{
|
|
|
|
"jellyfin": "Jellyfin",
|
|
|
|
"emby": "Emby (experimental)",
|
|
|
|
}
|
2021-01-10 15:51:04 +00:00
|
|
|
var serverType = mediabrowser.JellyfinServer
|
|
|
|
var substituteStrings = ""
|
2021-01-09 20:38:13 +00:00
|
|
|
|
2020-11-22 16:36:43 +00:00
|
|
|
// User is used for auth purposes.
|
2020-07-29 21:11:28 +00:00
|
|
|
type User struct {
|
2020-07-31 21:07:09 +00:00
|
|
|
UserID string `json:"id"`
|
|
|
|
Username string `json:"username"`
|
|
|
|
Password string `json:"password"`
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|
|
|
|
|
2021-02-01 20:25:20 +00:00
|
|
|
// contains (almost) everything the application needs, essentially. This was a dumb design decision imo.
|
2020-07-29 21:11:28 +00:00
|
|
|
type appContext struct {
|
2020-08-15 21:07:48 +00:00
|
|
|
// defaults *Config
|
2021-01-08 23:47:26 +00:00
|
|
|
config *ini.File
|
|
|
|
configPath string
|
|
|
|
configBasePath string
|
|
|
|
configBase settings
|
|
|
|
dataPath string
|
2021-01-31 23:12:50 +00:00
|
|
|
webFS httpFS
|
2021-12-31 17:28:08 +00:00
|
|
|
cssClass string // Default theme, "light"|"dark".
|
2021-01-08 23:47:26 +00:00
|
|
|
jellyfinLogin bool
|
2023-06-15 20:32:18 +00:00
|
|
|
adminUsers []User
|
2021-01-08 23:47:26 +00:00
|
|
|
invalidTokens []string
|
|
|
|
// Keeping jf name because I can't think of a better one
|
2023-06-21 20:14:41 +00:00
|
|
|
jf *mediabrowser.MediaBrowser
|
|
|
|
authJf *mediabrowser.MediaBrowser
|
|
|
|
ombi *ombi.Ombi
|
|
|
|
datePattern string
|
|
|
|
timePattern string
|
|
|
|
storage Storage
|
|
|
|
validator Validator
|
|
|
|
email *Emailer
|
|
|
|
telegram *TelegramDaemon
|
|
|
|
discord *DiscordDaemon
|
|
|
|
matrix *MatrixDaemon
|
|
|
|
info, debug, err *logger.Logger
|
|
|
|
host string
|
|
|
|
port int
|
|
|
|
version string
|
|
|
|
URLBase string
|
|
|
|
updater *Updater
|
|
|
|
newUpdate bool // Whether whatever's in update is new.
|
|
|
|
tag Tag
|
|
|
|
update Update
|
|
|
|
internalPWRs map[string]InternalPWR
|
|
|
|
ConfirmationKeys map[string]map[string]newUserDTO // Map of invite code to jwt to request
|
|
|
|
confirmationKeysLock sync.Mutex
|
2020-11-03 21:11:43 +00:00
|
|
|
}
|
|
|
|
|
2020-11-22 16:36:43 +00:00
|
|
|
func generateSecret(length int) (string, error) {
|
2020-07-29 21:11:28 +00:00
|
|
|
bytes := make([]byte, length)
|
|
|
|
_, err := rand.Read(bytes)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
return base64.URLEncoding.EncodeToString(bytes), err
|
|
|
|
}
|
|
|
|
|
2020-09-16 10:55:35 +00:00
|
|
|
func test(app *appContext) {
|
|
|
|
fmt.Printf("\n\n----\n\n")
|
|
|
|
settings := map[string]interface{}{
|
2020-11-02 00:53:08 +00:00
|
|
|
"server": app.jf.Server,
|
|
|
|
"server version": app.jf.ServerInfo.Version,
|
|
|
|
"server name": app.jf.ServerInfo.Name,
|
|
|
|
"authenticated?": app.jf.Authenticated,
|
|
|
|
"access token": app.jf.AccessToken,
|
|
|
|
"username": app.jf.Username,
|
2020-09-16 10:55:35 +00:00
|
|
|
}
|
|
|
|
for n, v := range settings {
|
|
|
|
fmt.Println(n, ":", v)
|
|
|
|
}
|
2020-11-02 00:53:08 +00:00
|
|
|
users, status, err := app.jf.GetUsers(false)
|
|
|
|
fmt.Printf("GetUsers: code %d err %s maplength %d\n", status, err, len(users))
|
2020-09-16 10:55:35 +00:00
|
|
|
fmt.Printf("View output? [y/n]: ")
|
|
|
|
var choice string
|
|
|
|
fmt.Scanln(&choice)
|
|
|
|
if strings.Contains(choice, "y") {
|
|
|
|
out, err := json.MarshalIndent(users, "", " ")
|
|
|
|
fmt.Print(string(out), err)
|
|
|
|
}
|
2020-09-16 18:19:04 +00:00
|
|
|
fmt.Printf("Enter a user to grab: ")
|
|
|
|
var username string
|
|
|
|
fmt.Scanln(&username)
|
2020-11-02 00:53:08 +00:00
|
|
|
user, status, err := app.jf.UserByName(username, false)
|
|
|
|
fmt.Printf("UserByName (%s): code %d err %s", username, status, err)
|
2020-11-22 16:36:43 +00:00
|
|
|
out, _ := json.MarshalIndent(user, "", " ")
|
2020-09-16 18:19:04 +00:00
|
|
|
fmt.Print(string(out))
|
2020-09-16 10:55:35 +00:00
|
|
|
}
|
|
|
|
|
2020-09-08 22:08:50 +00:00
|
|
|
func start(asDaemon, firstCall bool) {
|
2023-06-11 18:48:03 +00:00
|
|
|
RESTARTLISTENERCOUNT = 0
|
2021-05-16 15:23:28 +00:00
|
|
|
RUNNING = true
|
|
|
|
defer func() { RUNNING = false }()
|
2021-08-16 19:41:07 +00:00
|
|
|
|
|
|
|
defer func() {
|
|
|
|
if r := recover(); r != nil {
|
|
|
|
Exit(r)
|
|
|
|
}
|
|
|
|
}()
|
2020-08-19 13:31:41 +00:00
|
|
|
// app encompasses essentially all useful functions.
|
2020-08-16 12:36:54 +00:00
|
|
|
app := new(appContext)
|
2020-08-19 13:31:41 +00:00
|
|
|
|
|
|
|
/*
|
2021-02-02 18:09:02 +00:00
|
|
|
set default config and data paths
|
|
|
|
data: Contains invites.json, emails.json, user_profile.json, etc.
|
|
|
|
config: config.ini. Usually in data, but can be changed via -config.
|
2021-03-20 18:00:01 +00:00
|
|
|
localFS: jfa-go's internal data. On internal builds, this is contained within the binary.
|
|
|
|
On external builds, the directory is named "data" and placed next to the executable.
|
2020-08-19 13:31:41 +00:00
|
|
|
*/
|
2020-08-01 20:20:02 +00:00
|
|
|
userConfigDir, _ := os.UserConfigDir()
|
2020-11-22 16:36:43 +00:00
|
|
|
app.dataPath = filepath.Join(userConfigDir, "jfa-go")
|
|
|
|
app.configPath = filepath.Join(app.dataPath, "config.ini")
|
2021-02-02 18:09:02 +00:00
|
|
|
// gin-static doesn't just take a plain http.FileSystem, so we implement it's ServeFileSystem. See static.go.
|
2021-01-31 23:12:50 +00:00
|
|
|
app.webFS = httpFS{
|
2021-02-01 18:41:45 +00:00
|
|
|
hfs: http.FS(localFS),
|
|
|
|
fs: localFS,
|
2021-01-31 23:12:50 +00:00
|
|
|
}
|
2020-08-01 20:20:02 +00:00
|
|
|
|
2021-04-30 23:13:57 +00:00
|
|
|
app.info = logger.NewLogger(os.Stdout, "[INFO] ", log.Ltime, color.FgHiWhite)
|
2021-06-11 22:14:16 +00:00
|
|
|
app.info.SetFatalFunc(Exit)
|
2021-04-30 23:13:57 +00:00
|
|
|
app.err = logger.NewLogger(os.Stdout, "[ERROR] ", log.Ltime|log.Lshortfile, color.FgRed)
|
2021-06-11 22:14:16 +00:00
|
|
|
app.err.SetFatalFunc(Exit)
|
2020-08-01 20:20:02 +00:00
|
|
|
|
2021-04-30 23:13:57 +00:00
|
|
|
app.loadArgs(firstCall)
|
2020-08-01 23:05:35 +00:00
|
|
|
|
|
|
|
var firstRun bool
|
2020-11-22 16:36:43 +00:00
|
|
|
if _, err := os.Stat(app.dataPath); os.IsNotExist(err) {
|
|
|
|
os.Mkdir(app.dataPath, 0700)
|
2020-08-01 20:20:02 +00:00
|
|
|
}
|
2020-11-22 16:36:43 +00:00
|
|
|
if _, err := os.Stat(app.configPath); os.IsNotExist(err) {
|
2020-08-01 23:05:35 +00:00
|
|
|
firstRun = true
|
2021-02-02 15:44:30 +00:00
|
|
|
dConfig, err := fs.ReadFile(localFS, "config-default.ini")
|
2020-08-01 20:20:02 +00:00
|
|
|
if err != nil {
|
2021-01-31 23:12:50 +00:00
|
|
|
app.err.Fatalf("Couldn't find default config file")
|
2020-08-01 20:20:02 +00:00
|
|
|
}
|
2020-11-22 16:36:43 +00:00
|
|
|
nConfig, err := os.Create(app.configPath)
|
2021-07-27 09:54:52 +00:00
|
|
|
if err != nil && os.IsNotExist(err) {
|
|
|
|
err = os.MkdirAll(filepath.Dir(app.configPath), 0760)
|
|
|
|
}
|
2020-08-01 20:20:02 +00:00
|
|
|
if err != nil {
|
2020-11-22 16:36:43 +00:00
|
|
|
app.err.Printf("Couldn't open config file for writing: \"%s\"", app.configPath)
|
2020-10-20 20:16:46 +00:00
|
|
|
app.err.Fatalf("Error: %s", err)
|
2020-08-01 20:20:02 +00:00
|
|
|
}
|
|
|
|
defer nConfig.Close()
|
2021-01-31 23:12:50 +00:00
|
|
|
_, err = nConfig.Write(dConfig)
|
2020-08-01 20:20:02 +00:00
|
|
|
if err != nil {
|
2021-01-31 23:12:50 +00:00
|
|
|
app.err.Fatalf("Couldn't copy default config.")
|
2020-08-01 20:20:02 +00:00
|
|
|
}
|
2020-11-22 16:36:43 +00:00
|
|
|
app.info.Printf("Copied default configuration to \"%s\"", app.configPath)
|
2021-06-01 18:54:13 +00:00
|
|
|
tempConfig, _ := ini.Load(app.configPath)
|
|
|
|
tempConfig.Section("").Key("first_run").SetValue("true")
|
|
|
|
tempConfig.SaveTo(app.configPath)
|
2020-08-01 20:20:02 +00:00
|
|
|
}
|
2020-08-19 13:31:41 +00:00
|
|
|
|
2020-08-01 23:05:35 +00:00
|
|
|
var debugMode bool
|
|
|
|
var address string
|
2021-04-30 23:13:57 +00:00
|
|
|
if err := app.loadConfig(); err != nil {
|
|
|
|
app.err.Fatalf("Failed to load config file \"%s\": %v", app.configPath, err)
|
2020-07-31 21:07:09 +00:00
|
|
|
}
|
2021-05-07 20:53:29 +00:00
|
|
|
|
2021-06-01 18:54:13 +00:00
|
|
|
if app.config.Section("").Key("first_run").MustBool(false) {
|
|
|
|
firstRun = true
|
|
|
|
}
|
|
|
|
|
2020-08-16 12:36:54 +00:00
|
|
|
app.version = app.config.Section("jellyfin").Key("version").String()
|
2020-08-19 13:31:41 +00:00
|
|
|
// read from config...
|
2020-08-19 13:09:48 +00:00
|
|
|
debugMode = app.config.Section("ui").Key("debug").MustBool(false)
|
2020-08-19 13:31:41 +00:00
|
|
|
// then from flag
|
2020-09-08 22:08:50 +00:00
|
|
|
if *DEBUG {
|
2020-08-19 13:09:48 +00:00
|
|
|
debugMode = true
|
|
|
|
}
|
2020-08-01 13:08:55 +00:00
|
|
|
if debugMode {
|
2021-04-30 23:13:57 +00:00
|
|
|
app.debug = logger.NewLogger(os.Stdout, "[DEBUG] ", log.Ltime|log.Lshortfile, color.FgYellow)
|
2020-07-31 21:07:09 +00:00
|
|
|
} else {
|
2021-09-18 12:43:11 +00:00
|
|
|
app.debug = logger.NewEmptyLogger()
|
2020-07-31 21:07:09 +00:00
|
|
|
}
|
2021-03-23 21:59:04 +00:00
|
|
|
if *PPROF {
|
|
|
|
app.info.Print(warning("\n\nWARNING: Don't use pprof in production.\n\n"))
|
|
|
|
}
|
2020-07-31 21:07:09 +00:00
|
|
|
|
2021-03-20 18:00:01 +00:00
|
|
|
// Starts listener to receive commands over a unix socket. Use with 'jfa-go start/stop'
|
2020-09-08 22:08:50 +00:00
|
|
|
if asDaemon {
|
|
|
|
go func() {
|
2021-04-30 23:13:57 +00:00
|
|
|
os.Remove(SOCK)
|
|
|
|
listener, err := net.Listen("unix", SOCK)
|
2020-09-08 22:08:50 +00:00
|
|
|
if err != nil {
|
|
|
|
app.err.Fatalf("Couldn't establish socket connection at %s\n", SOCK)
|
|
|
|
}
|
|
|
|
c := make(chan os.Signal, 1)
|
2023-06-11 18:48:03 +00:00
|
|
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
2020-09-08 22:08:50 +00:00
|
|
|
go func() {
|
|
|
|
<-c
|
2021-04-30 23:13:57 +00:00
|
|
|
os.Remove(SOCK)
|
2020-09-08 22:08:50 +00:00
|
|
|
os.Exit(1)
|
|
|
|
}()
|
|
|
|
defer func() {
|
|
|
|
listener.Close()
|
|
|
|
os.Remove(SOCK)
|
|
|
|
}()
|
|
|
|
for {
|
|
|
|
con, err := listener.Accept()
|
|
|
|
if err != nil {
|
2021-04-30 23:13:57 +00:00
|
|
|
app.err.Printf("Couldn't read message on %s: %s", SOCK, err)
|
2020-09-08 22:08:50 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
buf := make([]byte, 512)
|
|
|
|
nr, err := con.Read(buf)
|
|
|
|
if err != nil {
|
2021-04-30 23:13:57 +00:00
|
|
|
app.err.Printf("Couldn't read message on %s: %s", SOCK, err)
|
2020-09-08 22:08:50 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
command := string(buf[0:nr])
|
|
|
|
if command == "stop" {
|
|
|
|
app.shutdown()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
2021-02-02 15:19:43 +00:00
|
|
|
app.storage.lang.CommonPath = "common"
|
2023-06-16 13:43:37 +00:00
|
|
|
app.storage.lang.UserPath = "form"
|
2021-02-02 15:19:43 +00:00
|
|
|
app.storage.lang.AdminPath = "admin"
|
|
|
|
app.storage.lang.EmailPath = "email"
|
2021-05-07 00:08:12 +00:00
|
|
|
app.storage.lang.TelegramPath = "telegram"
|
2021-03-30 21:41:28 +00:00
|
|
|
app.storage.lang.PasswordResetPath = "pwreset"
|
2021-02-01 17:39:19 +00:00
|
|
|
externalLang := app.config.Section("files").Key("lang_files").MustString("")
|
|
|
|
var err error
|
|
|
|
if externalLang == "" {
|
2021-02-01 18:41:45 +00:00
|
|
|
err = app.storage.loadLang(langFS)
|
2021-02-01 17:39:19 +00:00
|
|
|
} else {
|
2021-02-01 18:41:45 +00:00
|
|
|
err = app.storage.loadLang(langFS, os.DirFS(externalLang))
|
2021-02-01 17:39:19 +00:00
|
|
|
}
|
2021-01-26 22:57:29 +00:00
|
|
|
if err != nil {
|
|
|
|
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
|
|
|
}
|
|
|
|
|
2020-08-01 23:05:35 +00:00
|
|
|
if !firstRun {
|
2020-08-16 12:36:54 +00:00
|
|
|
app.host = app.config.Section("ui").Key("host").String()
|
2021-01-15 14:41:44 +00:00
|
|
|
if app.config.Section("advanced").Key("tls").MustBool(false) {
|
|
|
|
app.info.Println("Using TLS/HTTP2")
|
|
|
|
app.port = app.config.Section("advanced").Key("tls_port").MustInt(8057)
|
|
|
|
} else {
|
|
|
|
app.port = app.config.Section("ui").Key("port").MustInt(8056)
|
|
|
|
}
|
2020-07-31 21:07:09 +00:00
|
|
|
|
2020-09-08 22:08:50 +00:00
|
|
|
if *HOST != app.host && *HOST != "" {
|
|
|
|
app.host = *HOST
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
2020-09-08 22:08:50 +00:00
|
|
|
if *PORT != app.port && *PORT > 0 {
|
|
|
|
app.port = *PORT
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
2020-08-01 20:20:02 +00:00
|
|
|
|
2020-08-01 23:05:35 +00:00
|
|
|
if h := os.Getenv("JFA_HOST"); h != "" {
|
2020-08-16 12:36:54 +00:00
|
|
|
app.host = h
|
2020-08-01 23:05:35 +00:00
|
|
|
if p := os.Getenv("JFA_PORT"); p != "" {
|
|
|
|
var port int
|
|
|
|
_, err := fmt.Sscan(p, &port)
|
|
|
|
if err == nil {
|
2020-08-16 12:36:54 +00:00
|
|
|
app.port = port
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-08-16 12:36:54 +00:00
|
|
|
address = fmt.Sprintf("%s:%d", app.host, app.port)
|
2020-07-29 21:11:28 +00:00
|
|
|
|
2020-11-22 16:36:43 +00:00
|
|
|
app.debug.Printf("Loaded config file \"%s\"", app.configPath)
|
2020-07-29 21:11:28 +00:00
|
|
|
|
2020-08-16 12:36:54 +00:00
|
|
|
app.debug.Println("Loading storage")
|
2020-08-01 23:05:35 +00:00
|
|
|
|
2020-09-05 16:32:49 +00:00
|
|
|
app.storage.invite_path = app.config.Section("files").Key("invites").String()
|
2021-04-06 12:44:52 +00:00
|
|
|
if err := app.storage.loadInvites(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Invites: %v", err)
|
|
|
|
}
|
2020-09-05 16:32:49 +00:00
|
|
|
app.storage.emails_path = app.config.Section("files").Key("emails").String()
|
2021-04-06 12:44:52 +00:00
|
|
|
if err := app.storage.loadEmails(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Emails: %v", err)
|
2021-06-01 13:18:49 +00:00
|
|
|
err := migrateEmailStorage(app)
|
2021-05-21 21:46:46 +00:00
|
|
|
if err != nil {
|
|
|
|
app.err.Printf("Failed to migrate Email storage: %v", err)
|
|
|
|
}
|
2021-04-06 12:44:52 +00:00
|
|
|
}
|
2020-09-05 16:32:49 +00:00
|
|
|
app.storage.policy_path = app.config.Section("files").Key("user_template").String()
|
2021-04-06 12:44:52 +00:00
|
|
|
if err := app.storage.loadPolicy(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Policy: %v", err)
|
|
|
|
}
|
2020-09-17 15:51:19 +00:00
|
|
|
app.storage.configuration_path = app.config.Section("files").Key("user_configuration").String()
|
2021-04-06 12:44:52 +00:00
|
|
|
if err := app.storage.loadConfiguration(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Configuration: %v", err)
|
|
|
|
}
|
2020-09-05 16:32:49 +00:00
|
|
|
app.storage.displayprefs_path = app.config.Section("files").Key("user_displayprefs").String()
|
2021-04-06 12:44:52 +00:00
|
|
|
if err := app.storage.loadDisplayprefs(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Displayprefs: %v", err)
|
|
|
|
}
|
2021-02-28 15:41:06 +00:00
|
|
|
app.storage.users_path = app.config.Section("files").Key("users").String()
|
2021-04-06 12:44:52 +00:00
|
|
|
if err := app.storage.loadUsers(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Users: %v", err)
|
|
|
|
}
|
2021-05-07 00:08:12 +00:00
|
|
|
app.storage.telegram_path = app.config.Section("files").Key("telegram_users").String()
|
|
|
|
if err := app.storage.loadTelegramUsers(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Telegram users: %v", err)
|
|
|
|
}
|
2021-05-17 22:42:33 +00:00
|
|
|
app.storage.discord_path = app.config.Section("files").Key("discord_users").String()
|
|
|
|
if err := app.storage.loadDiscordUsers(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Discord users: %v", err)
|
|
|
|
}
|
2021-05-29 18:50:16 +00:00
|
|
|
app.storage.matrix_path = app.config.Section("files").Key("matrix_users").String()
|
|
|
|
if err := app.storage.loadMatrixUsers(); err != nil {
|
|
|
|
app.err.Printf("Failed to load Matrix users: %v", err)
|
|
|
|
}
|
2021-07-10 15:43:27 +00:00
|
|
|
app.storage.announcements_path = app.config.Section("files").Key("announcements").String()
|
|
|
|
if err := app.storage.loadAnnouncements(); err != nil {
|
|
|
|
app.err.Printf("Failed to load announcement templates: %v", err)
|
|
|
|
}
|
2020-08-01 23:05:35 +00:00
|
|
|
|
2020-09-20 10:21:04 +00:00
|
|
|
app.storage.profiles_path = app.config.Section("files").Key("user_profiles").String()
|
|
|
|
app.storage.loadProfiles()
|
|
|
|
|
2020-09-05 16:32:49 +00:00
|
|
|
if app.config.Section("ombi").Key("enabled").MustBool(false) {
|
|
|
|
app.storage.ombi_path = app.config.Section("files").Key("ombi_template").String()
|
|
|
|
app.storage.loadOmbiTemplate()
|
2020-11-02 00:53:08 +00:00
|
|
|
ombiServer := app.config.Section("ombi").Key("server").String()
|
|
|
|
app.ombi = ombi.NewOmbi(
|
|
|
|
ombiServer,
|
2020-09-05 16:32:49 +00:00
|
|
|
app.config.Section("ombi").Key("api_key").String(),
|
2020-11-02 00:53:08 +00:00
|
|
|
common.NewTimeoutHandler("Ombi", ombiServer, true),
|
2020-09-05 16:32:49 +00:00
|
|
|
)
|
2020-11-02 00:53:08 +00:00
|
|
|
|
2020-09-05 16:32:49 +00:00
|
|
|
}
|
|
|
|
|
2021-03-20 18:00:01 +00:00
|
|
|
// Read config-base for settings on web.
|
2021-02-02 15:44:30 +00:00
|
|
|
app.configBasePath = "config-base.json"
|
2021-02-01 18:41:45 +00:00
|
|
|
configBase, _ := fs.ReadFile(localFS, app.configBasePath)
|
2020-11-02 00:53:08 +00:00
|
|
|
json.Unmarshal(configBase, &app.configBase)
|
2020-08-01 23:05:35 +00:00
|
|
|
|
2020-11-22 16:36:43 +00:00
|
|
|
secret, err := generateSecret(16)
|
2020-08-01 23:05:35 +00:00
|
|
|
if err != nil {
|
2020-08-16 12:36:54 +00:00
|
|
|
app.err.Fatal(err)
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
|
|
|
os.Setenv("JFA_SECRET", secret)
|
2020-07-31 11:48:37 +00:00
|
|
|
|
2021-03-20 18:00:01 +00:00
|
|
|
// Initialize jellyfin/emby connection
|
2020-08-16 12:36:54 +00:00
|
|
|
server := app.config.Section("jellyfin").Key("server").String()
|
2020-11-02 23:26:46 +00:00
|
|
|
cacheTimeout := int(app.config.Section("jellyfin").Key("cache_timeout").MustUint(30))
|
2021-01-09 20:38:13 +00:00
|
|
|
stringServerType := app.config.Section("jellyfin").Key("type").String()
|
2021-06-13 14:36:36 +00:00
|
|
|
timeoutHandler := mediabrowser.NewNamedTimeoutHandler("Jellyfin", "\""+server+"\"", true)
|
2021-01-09 20:38:13 +00:00
|
|
|
if stringServerType == "emby" {
|
|
|
|
serverType = mediabrowser.EmbyServer
|
2021-06-13 14:36:36 +00:00
|
|
|
timeoutHandler = mediabrowser.NewNamedTimeoutHandler("Emby", "\""+server+"\"", true)
|
2021-01-08 23:47:26 +00:00
|
|
|
app.info.Println("Using Emby server type")
|
2021-02-19 16:12:14 +00:00
|
|
|
fmt.Println(warning("WARNING: Emby compatibility is experimental, and support is limited.\nPassword resets are not available."))
|
2021-01-08 23:47:26 +00:00
|
|
|
} else {
|
|
|
|
app.info.Println("Using Jellyfin server type")
|
|
|
|
}
|
2021-01-09 20:38:13 +00:00
|
|
|
|
2021-06-01 18:54:13 +00:00
|
|
|
app.jf, err = mediabrowser.NewServer(
|
2021-01-09 20:38:13 +00:00
|
|
|
serverType,
|
|
|
|
server,
|
|
|
|
app.config.Section("jellyfin").Key("client").String(),
|
|
|
|
app.config.Section("jellyfin").Key("version").String(),
|
|
|
|
app.config.Section("jellyfin").Key("device").String(),
|
|
|
|
app.config.Section("jellyfin").Key("device_id").String(),
|
|
|
|
timeoutHandler,
|
|
|
|
cacheTimeout,
|
|
|
|
)
|
2021-06-01 18:54:13 +00:00
|
|
|
if err != nil {
|
2021-06-11 22:14:16 +00:00
|
|
|
app.err.Fatalf("Failed to authenticate with Jellyfin @ \"%s\": %v", server, err)
|
2021-06-01 18:54:13 +00:00
|
|
|
}
|
2021-04-02 21:13:04 +00:00
|
|
|
if debugMode {
|
|
|
|
app.jf.Verbose = true
|
|
|
|
}
|
2020-08-01 23:05:35 +00:00
|
|
|
var status int
|
2020-11-02 00:53:08 +00:00
|
|
|
_, status, err = app.jf.Authenticate(app.config.Section("jellyfin").Key("username").String(), app.config.Section("jellyfin").Key("password").String())
|
2020-08-01 23:05:35 +00:00
|
|
|
if status != 200 || err != nil {
|
2021-06-11 22:14:16 +00:00
|
|
|
app.err.Fatalf("Failed to authenticate with Jellyfin @ \"%s\" (%d): %v", server, status, err)
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
2021-08-22 12:54:39 +00:00
|
|
|
app.info.Printf("Authenticated with \"%s\"", server)
|
2021-06-01 13:18:49 +00:00
|
|
|
|
|
|
|
runMigrations(app)
|
2021-01-10 15:51:04 +00:00
|
|
|
|
2021-03-20 18:00:01 +00:00
|
|
|
// Auth (manual user/pass or jellyfin)
|
|
|
|
app.jellyfinLogin = true
|
|
|
|
if jfLogin, _ := app.config.Section("ui").Key("jellyfin_login").Bool(); !jfLogin {
|
|
|
|
app.jellyfinLogin = false
|
|
|
|
user := User{}
|
|
|
|
user.UserID = shortuuid.New()
|
|
|
|
user.Username = app.config.Section("ui").Key("username").String()
|
|
|
|
user.Password = app.config.Section("ui").Key("password").String()
|
2023-06-15 20:32:18 +00:00
|
|
|
app.adminUsers = append(app.adminUsers, user)
|
2021-03-20 18:00:01 +00:00
|
|
|
} else {
|
|
|
|
app.debug.Println("Using Jellyfin for authentication")
|
|
|
|
app.authJf, _ = mediabrowser.NewServer(serverType, server, "jfa-go", app.version, "auth", "auth", timeoutHandler, cacheTimeout)
|
2021-04-02 21:13:04 +00:00
|
|
|
if debugMode {
|
|
|
|
app.authJf.Verbose = true
|
|
|
|
}
|
2021-03-20 18:00:01 +00:00
|
|
|
}
|
|
|
|
|
2021-01-21 18:57:32 +00:00
|
|
|
// Since email depends on language, the email reload in loadConfig won't work first time.
|
|
|
|
app.email = NewEmailer(app)
|
2020-08-16 12:36:54 +00:00
|
|
|
app.loadStrftime()
|
2020-08-01 23:05:35 +00:00
|
|
|
|
2021-03-20 18:00:01 +00:00
|
|
|
var validatorConf ValidatorConf
|
|
|
|
|
2020-08-16 12:36:54 +00:00
|
|
|
if !app.config.Section("password_validation").Key("enabled").MustBool(false) {
|
2021-03-20 18:00:01 +00:00
|
|
|
validatorConf = ValidatorConf{}
|
|
|
|
} else {
|
|
|
|
validatorConf = ValidatorConf{
|
|
|
|
"length": app.config.Section("password_validation").Key("min_length").MustInt(0),
|
|
|
|
"uppercase": app.config.Section("password_validation").Key("upper").MustInt(0),
|
|
|
|
"lowercase": app.config.Section("password_validation").Key("lower").MustInt(0),
|
|
|
|
"number": app.config.Section("password_validation").Key("number").MustInt(0),
|
|
|
|
"special": app.config.Section("password_validation").Key("special").MustInt(0),
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-16 12:36:54 +00:00
|
|
|
app.validator.init(validatorConf)
|
2020-08-01 23:05:35 +00:00
|
|
|
|
2021-03-20 18:00:01 +00:00
|
|
|
// Test mode for testing connection to Jellyfin, accessed with 'jfa-go test'
|
2020-09-16 10:55:35 +00:00
|
|
|
if TEST {
|
|
|
|
test(app)
|
|
|
|
os.Exit(0)
|
|
|
|
}
|
|
|
|
|
2021-04-06 17:12:06 +00:00
|
|
|
invDaemon := newInviteDaemon(time.Duration(60*time.Second), app)
|
|
|
|
go invDaemon.run()
|
2021-06-01 13:18:49 +00:00
|
|
|
defer invDaemon.Shutdown()
|
2020-08-01 23:05:35 +00:00
|
|
|
|
2021-02-28 15:41:06 +00:00
|
|
|
userDaemon := newUserDaemon(time.Duration(60*time.Second), app)
|
|
|
|
go userDaemon.run()
|
2021-04-06 17:12:06 +00:00
|
|
|
defer userDaemon.shutdown()
|
2021-02-28 15:41:06 +00:00
|
|
|
|
2021-01-10 16:00:26 +00:00
|
|
|
if app.config.Section("password_resets").Key("enabled").MustBool(false) && serverType == mediabrowser.JellyfinServer {
|
2020-08-16 12:36:54 +00:00
|
|
|
go app.StartPWR()
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
2021-03-07 15:23:44 +00:00
|
|
|
|
|
|
|
if app.config.Section("updates").Key("enabled").MustBool(false) {
|
|
|
|
go app.checkForUpdates()
|
|
|
|
}
|
2021-05-07 00:08:12 +00:00
|
|
|
|
2021-05-07 20:53:29 +00:00
|
|
|
if telegramEnabled {
|
2021-05-07 00:08:12 +00:00
|
|
|
app.telegram, err = newTelegramDaemon(app)
|
|
|
|
if err != nil {
|
|
|
|
app.err.Printf("Failed to authenticate with Telegram: %v", err)
|
2021-05-23 13:48:36 +00:00
|
|
|
telegramEnabled = false
|
2021-05-07 00:08:12 +00:00
|
|
|
} else {
|
|
|
|
go app.telegram.run()
|
|
|
|
defer app.telegram.Shutdown()
|
|
|
|
}
|
|
|
|
}
|
2021-05-17 22:42:33 +00:00
|
|
|
if discordEnabled {
|
|
|
|
app.discord, err = newDiscordDaemon(app)
|
|
|
|
if err != nil {
|
|
|
|
app.err.Printf("Failed to authenticate with Discord: %v", err)
|
2021-05-23 13:48:36 +00:00
|
|
|
discordEnabled = false
|
2021-05-17 22:42:33 +00:00
|
|
|
} else {
|
|
|
|
go app.discord.run()
|
|
|
|
defer app.discord.Shutdown()
|
|
|
|
}
|
|
|
|
}
|
2021-05-29 16:43:11 +00:00
|
|
|
if matrixEnabled {
|
|
|
|
app.matrix, err = newMatrixDaemon(app)
|
|
|
|
if err != nil {
|
|
|
|
app.err.Printf("Failed to initialize Matrix daemon: %v", err)
|
|
|
|
matrixEnabled = false
|
|
|
|
} else {
|
|
|
|
go app.matrix.run()
|
|
|
|
defer app.matrix.Shutdown()
|
|
|
|
}
|
|
|
|
}
|
2020-08-01 23:05:35 +00:00
|
|
|
} else {
|
|
|
|
debugMode = false
|
2021-07-21 16:17:59 +00:00
|
|
|
if *PORT != app.port && *PORT > 0 {
|
|
|
|
app.port = *PORT
|
|
|
|
} else {
|
|
|
|
app.port = 8056
|
|
|
|
}
|
2021-07-21 16:24:06 +00:00
|
|
|
if *HOST != app.host && *HOST != "" {
|
|
|
|
app.host = *HOST
|
|
|
|
} else {
|
|
|
|
app.host = "0.0.0.0"
|
|
|
|
}
|
|
|
|
address = fmt.Sprintf("%s:%d", app.host, app.port)
|
2021-02-02 15:19:43 +00:00
|
|
|
app.storage.lang.SetupPath = "setup"
|
2021-02-01 18:41:45 +00:00
|
|
|
err := app.storage.loadLangSetup(langFS)
|
2021-01-25 21:26:54 +00:00
|
|
|
if err != nil {
|
|
|
|
app.info.Fatalf("Failed to load language files: %+v\n", err)
|
|
|
|
}
|
2020-08-01 15:31:08 +00:00
|
|
|
}
|
2021-03-20 18:00:01 +00:00
|
|
|
|
2021-02-05 13:10:47 +00:00
|
|
|
cssHeader = app.loadCSSHeader()
|
2021-02-11 23:06:51 +00:00
|
|
|
// workaround for potentially broken windows mime types
|
2021-02-08 15:25:02 +00:00
|
|
|
mime.AddExtensionType(".js", "application/javascript")
|
|
|
|
|
2021-02-02 18:09:02 +00:00
|
|
|
app.info.Println("Initializing router")
|
|
|
|
router := app.loadRouter(address, debugMode)
|
2020-08-16 12:36:54 +00:00
|
|
|
app.info.Println("Loading routes")
|
2020-08-01 23:05:35 +00:00
|
|
|
if !firstRun {
|
2021-02-02 18:09:02 +00:00
|
|
|
app.loadRoutes(router)
|
2020-08-01 23:05:35 +00:00
|
|
|
} else {
|
2021-02-02 18:09:02 +00:00
|
|
|
app.loadSetup(router)
|
2020-08-16 12:36:54 +00:00
|
|
|
app.info.Printf("Loading setup @ %s", address)
|
2020-08-01 23:05:35 +00:00
|
|
|
}
|
2020-08-05 15:58:24 +00:00
|
|
|
go func() {
|
2021-01-15 14:41:44 +00:00
|
|
|
if app.config.Section("advanced").Key("tls").MustBool(false) {
|
|
|
|
cert := app.config.Section("advanced").Key("tls_cert").MustString("")
|
|
|
|
key := app.config.Section("advanced").Key("tls_key").MustString("")
|
|
|
|
if err := SRV.ListenAndServeTLS(cert, key); err != nil {
|
|
|
|
app.err.Printf("Failure serving: %s", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if err := SRV.ListenAndServe(); err != nil {
|
|
|
|
app.err.Printf("Failure serving: %s", err)
|
|
|
|
}
|
2020-08-05 15:58:24 +00:00
|
|
|
}
|
|
|
|
}()
|
2021-05-23 21:12:47 +00:00
|
|
|
if firstRun {
|
|
|
|
app.info.Printf("Loaded, visit %s to start.", address)
|
|
|
|
} else {
|
|
|
|
app.info.Printf("Loaded @ %s", address)
|
|
|
|
}
|
2023-06-11 18:48:03 +00:00
|
|
|
|
|
|
|
waitForRestart()
|
|
|
|
|
|
|
|
app.info.Printf("Restart/Quit signal received, give me a second!")
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
|
|
defer cancel()
|
|
|
|
if err := SRV.Shutdown(ctx); err != nil {
|
|
|
|
app.err.Fatalf("Server shutdown error: %s", err)
|
2020-09-08 22:08:50 +00:00
|
|
|
}
|
2023-06-11 18:48:03 +00:00
|
|
|
app.info.Println("Server shut down.")
|
|
|
|
return
|
2020-09-08 22:08:50 +00:00
|
|
|
}
|
|
|
|
|
2023-06-11 18:48:03 +00:00
|
|
|
func shutdown() {
|
2021-05-16 15:23:28 +00:00
|
|
|
QUIT = true
|
|
|
|
RESTART <- true
|
2023-06-11 18:48:03 +00:00
|
|
|
// Safety Sleep (Ensure shutdown tasks get done)
|
|
|
|
time.Sleep(time.Second)
|
|
|
|
}
|
2023-06-11 15:35:41 +00:00
|
|
|
|
2023-06-11 18:48:03 +00:00
|
|
|
func (app *appContext) shutdown() {
|
|
|
|
app.info.Println("Shutting down...")
|
|
|
|
shutdown()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Receives a restart signal and re-broadcasts it for other components.
|
|
|
|
func waitForRestart() {
|
|
|
|
RESTARTLISTENERCOUNT++
|
|
|
|
<-RESTART
|
|
|
|
RESTARTLISTENERCOUNT--
|
|
|
|
if RESTARTLISTENERCOUNT > 0 {
|
|
|
|
RESTART <- true
|
2020-08-05 15:58:24 +00:00
|
|
|
}
|
2020-09-08 22:08:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func flagPassed(name string) (found bool) {
|
2023-06-11 15:05:31 +00:00
|
|
|
for i, f := range os.Args {
|
2020-09-08 22:08:50 +00:00
|
|
|
if f == name {
|
|
|
|
found = true
|
2023-06-11 15:05:31 +00:00
|
|
|
// Remove the flag, to avoid issues wit the flag library.
|
|
|
|
os.Args = append(os.Args[:i], os.Args[i+1:]...)
|
|
|
|
return
|
|
|
|
|
2020-09-08 22:08:50 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2020-09-24 16:51:13 +00:00
|
|
|
// @title jfa-go internal API
|
2022-01-30 14:27:00 +00:00
|
|
|
// @version 0.4.0
|
2020-09-24 16:51:13 +00:00
|
|
|
// @description API for the jfa-go frontend
|
|
|
|
// @contact.name Harvey Tindall
|
2021-05-16 14:00:13 +00:00
|
|
|
// @contact.email hrfee@hrfee.dev
|
2020-09-24 16:51:13 +00:00
|
|
|
// @license.name MIT
|
|
|
|
// @license.url https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE
|
|
|
|
// @BasePath /
|
|
|
|
|
2020-11-12 21:04:35 +00:00
|
|
|
// @securityDefinitions.apikey Bearer
|
|
|
|
// @in header
|
|
|
|
// @name Authorization
|
2020-09-24 17:50:03 +00:00
|
|
|
|
|
|
|
// @securityDefinitions.basic getTokenAuth
|
|
|
|
// @name getTokenAuth
|
|
|
|
|
2023-06-15 20:32:18 +00:00
|
|
|
// @securityDefinitions.basic getUserTokenAuth
|
|
|
|
// @name getUserTokenAuth
|
|
|
|
|
2020-09-24 17:50:03 +00:00
|
|
|
// @tag.name Auth
|
2021-11-15 00:17:39 +00:00
|
|
|
// @tag.description -Get a token here if running swagger UI locally.-
|
2020-09-24 17:50:03 +00:00
|
|
|
|
2023-06-17 11:48:28 +00:00
|
|
|
// @tag.name User Page
|
|
|
|
// @tag.description User-page related routes.
|
|
|
|
|
2020-09-24 17:50:03 +00:00
|
|
|
// @tag.name Users
|
|
|
|
// @tag.description Jellyfin user related operations.
|
|
|
|
|
|
|
|
// @tag.name Invites
|
|
|
|
// @tag.description Invite related operations.
|
|
|
|
|
|
|
|
// @tag.name Profiles & Settings
|
|
|
|
// @tag.description Profile and settings related operations.
|
|
|
|
|
|
|
|
// @tag.name Configuration
|
|
|
|
// @tag.description jfa-go settings.
|
|
|
|
|
|
|
|
// @tag.name Ombi
|
|
|
|
// @tag.description Ombi related operations.
|
|
|
|
|
|
|
|
// @tag.name Other
|
|
|
|
// @tag.description Things that dont fit elsewhere.
|
|
|
|
|
2020-09-24 22:57:42 +00:00
|
|
|
func printVersion() {
|
2021-05-24 14:58:43 +00:00
|
|
|
tray := ""
|
|
|
|
if TRAY {
|
|
|
|
tray = " TrayIcon"
|
|
|
|
}
|
|
|
|
fmt.Println(info("jfa-go version: %s (%s)%s\n", hiwhite(version), white(commit), tray))
|
2020-09-24 22:57:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
2021-12-20 19:05:18 +00:00
|
|
|
f, err := logOutput()
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Failed to start logging: %v\n", err)
|
|
|
|
}
|
2021-07-13 18:02:16 +00:00
|
|
|
defer f()
|
2020-09-24 22:57:42 +00:00
|
|
|
printVersion()
|
2021-03-07 15:23:44 +00:00
|
|
|
SOCK = filepath.Join(temp, SOCK)
|
2020-09-13 20:07:15 +00:00
|
|
|
fmt.Println("Socket:", SOCK)
|
2020-09-16 10:55:35 +00:00
|
|
|
if flagPassed("test") {
|
|
|
|
TEST = true
|
|
|
|
}
|
2021-02-02 18:09:02 +00:00
|
|
|
loadFilesystems()
|
2023-06-11 18:48:03 +00:00
|
|
|
|
|
|
|
quit := make(chan os.Signal, 0)
|
|
|
|
signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
|
|
|
|
// defer close(quit)
|
|
|
|
go func() {
|
|
|
|
<-quit
|
|
|
|
shutdown()
|
|
|
|
}()
|
|
|
|
|
2020-09-08 22:08:50 +00:00
|
|
|
if flagPassed("start") {
|
|
|
|
args := []string{}
|
|
|
|
for i, f := range os.Args {
|
|
|
|
if f == "start" {
|
|
|
|
args = append(args, "daemon")
|
|
|
|
} else if i != 0 {
|
|
|
|
args = append(args, f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cmd := exec.Command(os.Args[0], args...)
|
|
|
|
cmd.Start()
|
|
|
|
os.Exit(1)
|
|
|
|
} else if flagPassed("stop") {
|
|
|
|
con, err := net.Dial("unix", SOCK)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Couldn't dial socket %s, are you sure jfa-go is running?\n", SOCK)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
_, err = con.Write([]byte("stop"))
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Couldn't send command to socket %s, are you sure jfa-go is running?\n", SOCK)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
fmt.Println("Sent.")
|
|
|
|
} else if flagPassed("daemon") {
|
|
|
|
start(true, true)
|
2021-04-24 17:54:31 +00:00
|
|
|
} else if flagPassed("systemd") {
|
|
|
|
service, err := fs.ReadFile(localFS, "jfa-go.service")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Couldn't read jfa-go.service: %v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
absPath, err := filepath.Abs(os.Args[0])
|
|
|
|
if err != nil {
|
|
|
|
absPath = os.Args[0]
|
|
|
|
}
|
|
|
|
command := absPath
|
|
|
|
for i, v := range os.Args {
|
|
|
|
if i != 0 && v != "systemd" {
|
|
|
|
command += " " + v
|
|
|
|
}
|
|
|
|
}
|
|
|
|
service = []byte(strings.Replace(string(service), "{executable}", command, 1))
|
|
|
|
err = os.WriteFile("jfa-go.service", service, 0666)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("Couldn't write jfa-go.service: %v\n", err)
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2021-07-27 09:47:07 +00:00
|
|
|
fmt.Println(info(`If you want to execute jfa-go with special arguments, re-run this command with them.
|
2021-04-24 17:54:31 +00:00
|
|
|
Move the newly created "jfa-go.service" file to ~/.config/systemd/user (Creating it if necessary).
|
|
|
|
Then run "systemctl --user daemon-reload".
|
|
|
|
You can then run:
|
|
|
|
|
|
|
|
`))
|
2021-07-27 09:47:07 +00:00
|
|
|
// I have no idea why sleeps are necessary, but if not the lines print in the wrong order.
|
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
color.New(color.FgGreen).Print("To start: ")
|
|
|
|
time.Sleep(time.Millisecond)
|
2021-04-24 17:54:31 +00:00
|
|
|
fmt.Print(info("systemctl --user start jfa-go\n\n"))
|
2021-07-27 09:47:07 +00:00
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
color.New(color.FgRed).Print("To stop: ")
|
|
|
|
time.Sleep(time.Millisecond)
|
2021-04-24 17:54:31 +00:00
|
|
|
fmt.Print(info("systemctl --user stop jfa-go\n\n"))
|
2021-07-27 09:47:07 +00:00
|
|
|
time.Sleep(time.Millisecond)
|
|
|
|
color.New(color.FgYellow).Print("To restart: ")
|
|
|
|
time.Sleep(time.Millisecond)
|
2021-04-24 17:54:31 +00:00
|
|
|
fmt.Print(info("systemctl --user stop jfa-go\n"))
|
2021-05-23 21:12:47 +00:00
|
|
|
} else if TRAY {
|
2021-05-16 15:23:28 +00:00
|
|
|
RunTray()
|
2020-09-08 22:08:50 +00:00
|
|
|
} else {
|
|
|
|
RESTART = make(chan bool, 1)
|
|
|
|
start(false, true)
|
|
|
|
for {
|
2021-05-16 15:23:28 +00:00
|
|
|
if QUIT {
|
2023-06-11 18:48:03 +00:00
|
|
|
break
|
2021-05-16 15:23:28 +00:00
|
|
|
}
|
2020-09-24 22:57:42 +00:00
|
|
|
printVersion()
|
2020-09-08 22:08:50 +00:00
|
|
|
start(false, false)
|
|
|
|
}
|
|
|
|
}
|
2020-07-29 21:11:28 +00:00
|
|
|
}
|