2020-07-29 23:11:28 +02:00
package main
import (
2020-08-05 17:58:24 +02:00
"context"
2020-07-29 23:11:28 +02:00
"crypto/rand"
"encoding/base64"
2020-07-31 17:09:30 +02:00
"encoding/json"
2020-08-01 22:20:02 +02:00
"flag"
2020-07-29 23:11:28 +02:00
"fmt"
2020-08-01 22:20:02 +02:00
"io"
2020-07-31 17:09:30 +02:00
"io/ioutil"
2020-07-31 23:07:09 +02:00
"log"
2020-09-09 00:08:50 +02:00
"net"
2020-08-05 17:58:24 +02:00
"net/http"
2020-07-29 23:11:28 +02:00
"os"
2020-09-09 00:08:50 +02:00
"os/exec"
2020-08-05 17:58:24 +02:00
"os/signal"
2020-07-29 23:11:28 +02:00
"path/filepath"
2020-09-05 22:52:23 +02:00
"runtime"
2020-09-16 12:55:35 +02:00
"strings"
2020-08-01 16:22:30 +02:00
"time"
2020-08-16 14:36:54 +02:00
"github.com/gin-contrib/pprof"
"github.com/gin-contrib/static"
"github.com/gin-gonic/gin"
2020-09-24 18:51:13 +02:00
_ "github.com/hrfee/jfa-go/docs"
2020-08-16 14:36:54 +02:00
"github.com/lithammer/shortuuid/v3"
2020-09-24 18:51:13 +02:00
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
2020-08-16 14:36:54 +02:00
"gopkg.in/ini.v1"
2020-07-29 23:11:28 +02:00
)
// Username is JWT!
type User struct {
2020-07-31 23:07:09 +02:00
UserID string ` json:"id" `
Username string ` json:"username" `
Password string ` json:"password" `
2020-07-29 23:11:28 +02:00
}
type appContext struct {
2020-08-15 23:07:48 +02:00
// defaults *Config
2020-07-31 23:07:09 +02:00
config * ini . File
config_path string
configBase_path string
configBase map [ string ] interface { }
data_path string
local_path string
cssFile string
bsVersion int
jellyfinLogin bool
users [ ] User
2020-08-20 21:20:31 +02:00
invalidTokens [ ] string
2020-08-30 21:44:10 +02:00
jf * Jellyfin
authJf * Jellyfin
2020-09-05 18:32:49 +02:00
ombi * Ombi
2020-07-31 23:07:09 +02:00
datePattern string
timePattern string
storage Storage
validator Validator
2020-09-13 22:07:15 +02:00
email * Emailer
2020-07-31 23:07:09 +02:00
info , debug , err * log . Logger
2020-08-01 22:20:02 +02:00
host string
port int
2020-08-02 01:05:35 +02:00
version string
2020-08-05 17:58:24 +02:00
quit chan os . Signal
2020-07-29 23:11:28 +02:00
}
func GenerateSecret ( length int ) ( string , error ) {
bytes := make ( [ ] byte , length )
_ , err := rand . Read ( bytes )
if err != nil {
return "" , err
}
return base64 . URLEncoding . EncodeToString ( bytes ) , err
}
2020-08-01 15:08:55 +02:00
func setGinLogger ( router * gin . Engine , debugMode bool ) {
if debugMode {
router . Use ( gin . LoggerWithFormatter ( func ( param gin . LogFormatterParams ) string {
2020-08-01 22:20:02 +02:00
return fmt . Sprintf ( "[GIN/DEBUG] %s: %s(%s) => %d in %s; %s\n" ,
2020-08-01 15:08:55 +02:00
param . TimeStamp . Format ( "15:04:05" ) ,
param . Method ,
param . Path ,
param . StatusCode ,
param . Latency ,
2020-08-01 22:20:02 +02:00
func ( ) string {
if param . ErrorMessage != "" {
return "Error: " + param . ErrorMessage
}
return ""
} ( ) ,
2020-08-01 15:08:55 +02:00
)
} ) )
} else {
router . Use ( gin . LoggerWithFormatter ( func ( param gin . LogFormatterParams ) string {
return fmt . Sprintf ( "[GIN] %s(%s) => %d\n" ,
param . Method ,
param . Path ,
param . StatusCode ,
)
} ) )
}
}
2020-09-09 00:08:50 +02:00
var (
PLATFORM string = runtime . GOOS
SOCK string = "jfa-go.sock"
SRV * http . Server
RESTART chan bool
DATA , CONFIG , HOST * string
PORT * int
DEBUG * bool
2020-09-16 12:55:35 +02:00
TEST bool
2020-09-24 18:51:13 +02:00
SWAGGER * bool
2020-09-09 00:08:50 +02:00
)
2020-09-05 22:52:23 +02:00
2020-09-16 12:55:35 +02:00
func test ( app * appContext ) {
fmt . Printf ( "\n\n----\n\n" )
settings := map [ string ] interface { } {
"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 ,
}
for n , v := range settings {
fmt . Println ( n , ":" , v )
}
users , status , err := app . jf . getUsers ( false )
fmt . Printf ( "getUsers: code %d err %s maplength %d\n" , status , err , len ( users ) )
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 20:19:04 +02:00
fmt . Printf ( "Enter a user to grab: " )
var username string
fmt . Scanln ( & username )
user , status , err := app . jf . userByName ( username , false )
fmt . Printf ( "userByName (%s): code %d err %s" , username , status , err )
out , err := json . MarshalIndent ( user , "" , " " )
fmt . Print ( string ( out ) )
2020-09-16 12:55:35 +02:00
}
2020-09-09 00:08:50 +02:00
func start ( asDaemon , firstCall bool ) {
2020-08-19 15:31:41 +02:00
// app encompasses essentially all useful functions.
2020-08-16 14:36:54 +02:00
app := new ( appContext )
2020-08-19 15:31:41 +02:00
/ *
set default config , data and local paths
also , confusing naming here . data_path is not the internal ' data ' directory , rather the users . config / jfa - go folder .
local_path is the internal ' data ' directory .
* /
2020-08-01 22:20:02 +02:00
userConfigDir , _ := os . UserConfigDir ( )
2020-08-16 14:36:54 +02:00
app . data_path = filepath . Join ( userConfigDir , "jfa-go" )
app . config_path = filepath . Join ( app . data_path , "config.ini" )
2020-08-02 03:11:50 +02:00
executable , _ := os . Executable ( )
2020-08-16 14:36:54 +02:00
app . local_path = filepath . Join ( filepath . Dir ( executable ) , "data" )
2020-08-01 22:20:02 +02:00
2020-08-16 14:36:54 +02:00
app . info = log . New ( os . Stdout , "[INFO] " , log . Ltime )
app . err = log . New ( os . Stdout , "[ERROR] " , log . Ltime | log . Lshortfile )
2020-08-01 22:20:02 +02:00
2020-09-09 00:08:50 +02:00
if firstCall {
DATA = flag . String ( "data" , app . data_path , "alternate path to data directory." )
CONFIG = flag . String ( "config" , app . config_path , "alternate path to config file." )
HOST = flag . String ( "host" , "" , "alternate address to host web ui on." )
PORT = flag . Int ( "port" , 0 , "alternate port to host web ui on." )
DEBUG = flag . Bool ( "debug" , false , "Enables debug logging and exposes pprof." )
2020-09-24 18:51:13 +02:00
SWAGGER = flag . Bool ( "swagger" , false , "Enable swagger at /swagger/index.html" )
2020-08-01 22:20:02 +02:00
2020-09-09 00:08:50 +02:00
flag . Parse ( )
2020-09-24 22:05:23 +02:00
if * SWAGGER {
os . Setenv ( "SWAGGER" , "1" )
}
if * DEBUG {
os . Setenv ( "DEBUG" , "1" )
}
2020-09-09 00:08:50 +02:00
}
2020-08-19 15:31:41 +02:00
2020-09-24 22:05:23 +02:00
if os . Getenv ( "SWAGGER" ) == "1" {
* SWAGGER = true
}
if os . Getenv ( "DEBUG" ) == "1" {
* DEBUG = true
}
2020-08-19 15:31:41 +02:00
// attempt to apply command line flags correctly
2020-09-09 00:08:50 +02:00
if app . config_path == * CONFIG && app . data_path != * DATA {
app . data_path = * DATA
2020-08-16 21:11:16 +02:00
app . config_path = filepath . Join ( app . data_path , "config.ini" )
2020-09-09 00:08:50 +02:00
} else if app . config_path != * CONFIG && app . data_path == * DATA {
app . config_path = * CONFIG
2020-08-01 22:20:02 +02:00
} else {
2020-09-09 00:08:50 +02:00
app . config_path = * CONFIG
app . data_path = * DATA
2020-08-01 22:20:02 +02:00
}
2020-08-19 15:31:41 +02:00
// env variables are necessary because syscall.Exec for self-restarts doesn't doesn't work with arguments for some reason.
2020-08-02 01:05:35 +02:00
if v := os . Getenv ( "JFA_CONFIGPATH" ) ; v != "" {
2020-08-16 14:36:54 +02:00
app . config_path = v
2020-08-02 01:05:35 +02:00
}
if v := os . Getenv ( "JFA_DATAPATH" ) ; v != "" {
2020-08-16 14:36:54 +02:00
app . data_path = v
2020-08-02 01:05:35 +02:00
}
2020-08-16 14:36:54 +02:00
os . Setenv ( "JFA_CONFIGPATH" , app . config_path )
os . Setenv ( "JFA_DATAPATH" , app . data_path )
2020-08-02 01:05:35 +02:00
var firstRun bool
2020-08-16 14:36:54 +02:00
if _ , err := os . Stat ( app . data_path ) ; os . IsNotExist ( err ) {
os . Mkdir ( app . data_path , 0700 )
2020-08-01 22:20:02 +02:00
}
2020-08-16 14:36:54 +02:00
if _ , err := os . Stat ( app . config_path ) ; os . IsNotExist ( err ) {
2020-08-02 01:05:35 +02:00
firstRun = true
2020-08-16 14:36:54 +02:00
dConfigPath := filepath . Join ( app . local_path , "config-default.ini" )
2020-08-01 22:20:02 +02:00
var dConfig * os . File
dConfig , err = os . Open ( dConfigPath )
if err != nil {
2020-08-16 14:36:54 +02:00
app . err . Fatalf ( "Couldn't find default config file \"%s\"" , dConfigPath )
2020-08-01 22:20:02 +02:00
}
defer dConfig . Close ( )
var nConfig * os . File
2020-08-16 14:36:54 +02:00
nConfig , err := os . Create ( app . config_path )
2020-08-01 22:20:02 +02:00
if err != nil {
2020-08-16 21:11:16 +02:00
app . err . Fatalf ( "Couldn't open config file for writing: \"%s\"" , app . config_path )
2020-08-01 22:20:02 +02:00
}
defer nConfig . Close ( )
_ , err = io . Copy ( nConfig , dConfig )
if err != nil {
2020-08-16 14:36:54 +02:00
app . err . Fatalf ( "Couldn't copy default config. To do this manually, copy\n%s\nto\n%s" , dConfigPath , app . config_path )
2020-08-01 22:20:02 +02:00
}
2020-08-16 14:36:54 +02:00
app . info . Printf ( "Copied default configuration to \"%s\"" , app . config_path )
2020-08-01 22:20:02 +02:00
}
2020-08-19 15:31:41 +02:00
2020-08-02 01:05:35 +02:00
var debugMode bool
var address string
2020-08-16 14:36:54 +02:00
if app . loadConfig ( ) != nil {
app . err . Fatalf ( "Failed to load config file \"%s\"" , app . config_path )
2020-07-31 23:07:09 +02:00
}
2020-08-16 14:36:54 +02:00
app . version = app . config . Section ( "jellyfin" ) . Key ( "version" ) . String ( )
2020-08-19 15:31:41 +02:00
// read from config...
2020-08-19 15:09:48 +02:00
debugMode = app . config . Section ( "ui" ) . Key ( "debug" ) . MustBool ( false )
2020-08-19 15:31:41 +02:00
// then from flag
2020-09-09 00:08:50 +02:00
if * DEBUG {
2020-08-19 15:09:48 +02:00
debugMode = true
}
2020-08-01 15:08:55 +02:00
if debugMode {
2020-08-19 15:09:48 +02:00
app . info . Println ( "WARNING: Don't use debug mode in production, as it exposes pprof on the network." )
2020-08-16 14:36:54 +02:00
app . debug = log . New ( os . Stdout , "[DEBUG] " , log . Ltime | log . Lshortfile )
2020-07-31 23:07:09 +02:00
} else {
2020-08-16 14:36:54 +02:00
app . debug = log . New ( ioutil . Discard , "" , 0 )
2020-07-31 23:07:09 +02:00
}
2020-09-09 00:08:50 +02:00
if asDaemon {
go func ( ) {
socket := SOCK
os . Remove ( socket )
listener , err := net . Listen ( "unix" , socket )
if err != nil {
app . err . Fatalf ( "Couldn't establish socket connection at %s\n" , SOCK )
}
c := make ( chan os . Signal , 1 )
signal . Notify ( c , os . Interrupt )
go func ( ) {
<- c
os . Remove ( socket )
os . Exit ( 1 )
} ( )
defer func ( ) {
listener . Close ( )
os . Remove ( SOCK )
} ( )
for {
con , err := listener . Accept ( )
if err != nil {
app . err . Printf ( "Couldn't read message on %s: %s" , socket , err )
continue
}
buf := make ( [ ] byte , 512 )
nr , err := con . Read ( buf )
if err != nil {
app . err . Printf ( "Couldn't read message on %s: %s" , socket , err )
continue
}
command := string ( buf [ 0 : nr ] )
if command == "stop" {
app . shutdown ( )
}
}
} ( )
}
2020-08-02 01:05:35 +02:00
if ! firstRun {
2020-08-16 14:36:54 +02:00
app . host = app . config . Section ( "ui" ) . Key ( "host" ) . String ( )
app . port = app . config . Section ( "ui" ) . Key ( "port" ) . MustInt ( 8056 )
2020-07-31 23:07:09 +02:00
2020-09-09 00:08:50 +02:00
if * HOST != app . host && * HOST != "" {
app . host = * HOST
2020-08-02 01:05:35 +02:00
}
2020-09-09 00:08:50 +02:00
if * PORT != app . port && * PORT > 0 {
app . port = * PORT
2020-08-02 01:05:35 +02:00
}
2020-08-01 22:20:02 +02:00
2020-08-02 01:05:35 +02:00
if h := os . Getenv ( "JFA_HOST" ) ; h != "" {
2020-08-16 14:36:54 +02:00
app . host = h
2020-08-02 01:05:35 +02:00
if p := os . Getenv ( "JFA_PORT" ) ; p != "" {
var port int
_ , err := fmt . Sscan ( p , & port )
if err == nil {
2020-08-16 14:36:54 +02:00
app . port = port
2020-08-02 01:05:35 +02:00
}
}
}
2020-07-31 23:07:09 +02:00
2020-08-16 14:36:54 +02:00
address = fmt . Sprintf ( "%s:%d" , app . host , app . port )
2020-07-29 23:11:28 +02:00
2020-08-16 14:36:54 +02:00
app . debug . Printf ( "Loaded config file \"%s\"" , app . config_path )
2020-07-29 23:11:28 +02:00
2020-08-16 14:36:54 +02:00
if app . config . Section ( "ui" ) . Key ( "bs5" ) . MustBool ( false ) {
app . cssFile = "bs5-jf.css"
app . bsVersion = 5
2020-08-02 01:05:35 +02:00
} else {
2020-08-16 14:36:54 +02:00
app . cssFile = "bs4-jf.css"
app . bsVersion = 4
2020-07-29 23:11:28 +02:00
}
2020-08-16 14:36:54 +02:00
app . debug . Println ( "Loading storage" )
2020-08-02 01:05:35 +02:00
2020-09-05 18:32:49 +02:00
// app.storage.invite_path = filepath.Join(app.data_path, "invites.json")
app . storage . invite_path = app . config . Section ( "files" ) . Key ( "invites" ) . String ( )
2020-08-16 14:36:54 +02:00
app . storage . loadInvites ( )
2020-09-05 18:32:49 +02:00
// app.storage.emails_path = filepath.Join(app.data_path, "emails.json")
app . storage . emails_path = app . config . Section ( "files" ) . Key ( "emails" ) . String ( )
2020-08-16 14:36:54 +02:00
app . storage . loadEmails ( )
2020-09-05 18:32:49 +02:00
// app.storage.policy_path = filepath.Join(app.data_path, "user_template.json")
app . storage . policy_path = app . config . Section ( "files" ) . Key ( "user_template" ) . String ( )
2020-08-16 14:36:54 +02:00
app . storage . loadPolicy ( )
2020-09-05 18:32:49 +02:00
// app.storage.configuration_path = filepath.Join(app.data_path, "user_configuration.json")
2020-09-17 17:51:19 +02:00
app . storage . configuration_path = app . config . Section ( "files" ) . Key ( "user_configuration" ) . String ( )
2020-08-16 14:36:54 +02:00
app . storage . loadConfiguration ( )
2020-09-05 18:32:49 +02:00
// app.storage.displayprefs_path = filepath.Join(app.data_path, "user_displayprefs.json")
app . storage . displayprefs_path = app . config . Section ( "files" ) . Key ( "user_displayprefs" ) . String ( )
2020-08-16 14:36:54 +02:00
app . storage . loadDisplayprefs ( )
2020-08-02 01:05:35 +02:00
2020-09-20 12:21:04 +02:00
app . storage . profiles_path = app . config . Section ( "files" ) . Key ( "user_profiles" ) . String ( )
app . storage . loadProfiles ( )
if ! ( len ( app . storage . policy ) == 0 && len ( app . storage . configuration ) == 0 && len ( app . storage . displayprefs ) == 0 ) {
app . info . Println ( "Migrating user template files to new profile format" )
app . storage . migrateToProfile ( )
2020-09-22 01:34:11 +02:00
for _ , path := range [ 3 ] string { app . storage . policy_path , app . storage . configuration_path , app . storage . displayprefs_path } {
if _ , err := os . Stat ( path ) ; ! os . IsNotExist ( err ) {
dir , fname := filepath . Split ( path )
newFname := strings . Replace ( fname , ".json" , ".old.json" , 1 )
err := os . Rename ( path , filepath . Join ( dir , newFname ) )
if err != nil {
app . err . Fatalf ( "Failed to rename %s: %s" , fname , err )
}
}
}
app . info . Println ( "In case of a problem, your original files have been renamed to <file>.old.json" )
2020-09-20 12:21:04 +02:00
app . storage . storeProfiles ( )
}
2020-09-05 18:32:49 +02: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 ( )
app . ombi = newOmbi (
app . config . Section ( "ombi" ) . Key ( "server" ) . String ( ) ,
app . config . Section ( "ombi" ) . Key ( "api_key" ) . String ( ) ,
true ,
)
}
2020-08-16 14:36:54 +02:00
app . configBase_path = filepath . Join ( app . local_path , "config-base.json" )
config_base , _ := ioutil . ReadFile ( app . configBase_path )
json . Unmarshal ( config_base , & app . configBase )
2020-08-02 01:05:35 +02:00
themes := map [ string ] string {
2020-08-16 14:36:54 +02:00
"Jellyfin (Dark)" : fmt . Sprintf ( "bs%d-jf.css" , app . bsVersion ) ,
"Bootstrap (Light)" : fmt . Sprintf ( "bs%d.css" , app . bsVersion ) ,
2020-08-02 01:05:35 +02:00
"Custom CSS" : "" ,
}
2020-08-16 14:36:54 +02:00
if val , ok := themes [ app . config . Section ( "ui" ) . Key ( "theme" ) . String ( ) ] ; ok {
app . cssFile = val
2020-08-02 01:05:35 +02:00
}
2020-08-16 14:36:54 +02:00
app . debug . Printf ( "Using css file \"%s\"" , app . cssFile )
2020-08-02 01:05:35 +02:00
secret , err := GenerateSecret ( 16 )
if err != nil {
2020-08-16 14:36:54 +02:00
app . err . Fatal ( err )
2020-08-02 01:05:35 +02:00
}
os . Setenv ( "JFA_SECRET" , secret )
2020-08-16 14:36:54 +02:00
app . jellyfinLogin = true
if val , _ := app . config . Section ( "ui" ) . Key ( "jellyfin_login" ) . Bool ( ) ; ! val {
app . jellyfinLogin = false
2020-08-02 01:05:35 +02:00
user := User { }
user . UserID = shortuuid . New ( )
2020-08-16 14:36:54 +02:00
user . Username = app . config . Section ( "ui" ) . Key ( "username" ) . String ( )
user . Password = app . config . Section ( "ui" ) . Key ( "password" ) . String ( )
app . users = append ( app . users , user )
2020-08-02 01:05:35 +02:00
} else {
2020-08-16 14:36:54 +02:00
app . debug . Println ( "Using Jellyfin for authentication" )
2020-08-02 01:05:35 +02:00
}
2020-07-31 13:48:37 +02:00
2020-08-16 14:36:54 +02:00
server := app . config . Section ( "jellyfin" ) . Key ( "server" ) . String ( )
2020-08-30 21:44:10 +02:00
app . jf , _ = newJellyfin ( server , "jfa-go" , app . version , "hrfee-arch" , "hrfee-arch" )
2020-08-02 01:05:35 +02:00
var status int
2020-08-16 14:36:54 +02:00
_ , status , err = app . jf . authenticate ( app . config . Section ( "jellyfin" ) . Key ( "username" ) . String ( ) , app . config . Section ( "jellyfin" ) . Key ( "password" ) . String ( ) )
2020-08-02 01:05:35 +02:00
if status != 200 || err != nil {
2020-08-16 14:36:54 +02:00
app . err . Fatalf ( "Failed to authenticate with Jellyfin @ %s: Code %d" , server , status )
2020-08-02 01:05:35 +02:00
}
2020-08-16 14:36:54 +02:00
app . info . Printf ( "Authenticated with %s" , server )
2020-08-30 21:44:10 +02:00
app . authJf , _ = newJellyfin ( server , "jfa-go" , app . version , "auth" , "auth" )
2020-08-01 16:22:30 +02:00
2020-08-16 14:36:54 +02:00
app . loadStrftime ( )
2020-08-02 01:05:35 +02:00
validatorConf := ValidatorConf {
2020-08-16 14:36:54 +02:00
"characters" : app . config . Section ( "password_validation" ) . Key ( "min_length" ) . MustInt ( 0 ) ,
"uppercase characters" : app . config . Section ( "password_validation" ) . Key ( "upper" ) . MustInt ( 0 ) ,
"lowercase characters" : app . config . Section ( "password_validation" ) . Key ( "lower" ) . MustInt ( 0 ) ,
"numbers" : app . config . Section ( "password_validation" ) . Key ( "number" ) . MustInt ( 0 ) ,
"special characters" : app . config . Section ( "password_validation" ) . Key ( "special" ) . MustInt ( 0 ) ,
2020-08-02 01:05:35 +02:00
}
2020-08-16 14:36:54 +02:00
if ! app . config . Section ( "password_validation" ) . Key ( "enabled" ) . MustBool ( false ) {
2020-08-02 01:05:35 +02:00
for key := range validatorConf {
validatorConf [ key ] = 0
}
}
2020-08-16 14:36:54 +02:00
app . validator . init ( validatorConf )
2020-08-02 01:05:35 +02:00
2020-09-16 12:55:35 +02:00
if TEST {
test ( app )
os . Exit ( 0 )
}
2020-08-16 14:36:54 +02:00
inviteDaemon := NewRepeater ( time . Duration ( 60 * time . Second ) , app )
2020-08-02 01:05:35 +02:00
go inviteDaemon . Run ( )
2020-08-16 14:36:54 +02:00
if app . config . Section ( "password_resets" ) . Key ( "enabled" ) . MustBool ( false ) {
go app . StartPWR ( )
2020-08-02 01:05:35 +02:00
}
} else {
debugMode = false
address = "0.0.0.0:8056"
2020-08-01 17:31:08 +02:00
}
2020-08-16 14:36:54 +02:00
app . info . Println ( "Loading routes" )
2020-08-17 13:33:26 +02:00
if debugMode {
gin . SetMode ( gin . DebugMode )
} else {
gin . SetMode ( gin . ReleaseMode )
}
2020-08-01 15:08:55 +02:00
router := gin . New ( )
setGinLogger ( router , debugMode )
router . Use ( gin . Recovery ( ) )
2020-08-16 14:36:54 +02:00
router . Use ( static . Serve ( "/" , static . LocalFile ( filepath . Join ( app . local_path , "static" ) , false ) ) )
router . LoadHTMLGlob ( filepath . Join ( app . local_path , "templates" , "*" ) )
router . NoRoute ( app . NoRouteHandler )
2020-08-03 01:13:09 +02:00
if debugMode {
2020-08-16 14:36:54 +02:00
app . debug . Println ( "Loading pprof" )
2020-08-03 01:13:09 +02:00
pprof . Register ( router )
}
2020-08-02 01:05:35 +02:00
if ! firstRun {
2020-08-16 14:36:54 +02:00
router . GET ( "/" , app . AdminPage )
2020-08-23 15:59:07 +02:00
router . GET ( "/getToken" , app . getToken )
2020-08-16 14:36:54 +02:00
router . POST ( "/newUser" , app . NewUser )
router . Use ( static . Serve ( "/invite/" , static . LocalFile ( filepath . Join ( app . local_path , "static" ) , false ) ) )
router . GET ( "/invite/:invCode" , app . InviteProxy )
2020-09-24 18:51:13 +02:00
if * SWAGGER {
2020-09-24 19:50:03 +02:00
app . info . Print ( "\n\nSwagger should not be used on a public instance.\nTo test the api with it, you need an API token. See the jfa-go wiki for how to get one.\n\n" )
2020-09-24 18:51:13 +02:00
router . GET ( "/swagger/*any" , ginSwagger . WrapHandler ( swaggerFiles . Handler ) )
}
2020-08-16 14:36:54 +02:00
api := router . Group ( "/" , app . webAuth ( ) )
2020-08-20 21:20:31 +02:00
router . POST ( "/logout" , app . Logout )
2020-09-24 15:03:25 +02:00
api . DELETE ( "/users" , app . DeleteUser )
api . GET ( "/users" , app . GetUsers )
api . POST ( "/users" , app . NewUserAdmin )
api . POST ( "/invites" , app . GenerateInvite )
api . GET ( "/invites" , app . GetInvites )
api . DELETE ( "/invites" , app . DeleteInvite )
api . POST ( "/invites/profile" , app . SetProfile )
api . GET ( "/profiles" , app . GetProfiles )
api . POST ( "/profiles/default" , app . SetDefaultProfile )
api . POST ( "/profiles" , app . CreateProfile )
api . DELETE ( "/profiles" , app . DeleteProfile )
api . POST ( "/invites/notify" , app . SetNotify )
api . POST ( "/users/emails" , app . ModifyEmails )
2020-09-23 01:01:07 +02:00
// api.POST("/setDefaults", app.SetDefaults)
2020-09-24 15:03:25 +02:00
api . POST ( "/users/settings" , app . ApplySettings )
api . GET ( "/config" , app . GetConfig )
api . POST ( "/config" , app . ModifyConfig )
2020-09-05 18:32:49 +02:00
if app . config . Section ( "ombi" ) . Key ( "enabled" ) . MustBool ( false ) {
2020-09-24 15:03:25 +02:00
api . GET ( "/ombi/users" , app . OmbiUsers )
api . POST ( "/ombi/defaults" , app . SetOmbiDefaults )
2020-09-05 18:32:49 +02:00
}
2020-08-16 14:36:54 +02:00
app . info . Printf ( "Starting router @ %s" , address )
2020-08-02 01:05:35 +02:00
} else {
router . GET ( "/" , func ( gc * gin . Context ) {
2020-09-09 00:13:44 +02:00
gc . HTML ( 200 , "setup.html" , gin . H { } )
2020-08-02 01:05:35 +02:00
} )
2020-09-24 15:03:25 +02:00
router . POST ( "/jellyfin/test" , app . TestJF )
router . POST ( "/config" , app . ModifyConfig )
2020-08-16 14:36:54 +02:00
app . info . Printf ( "Loading setup @ %s" , address )
2020-08-02 01:05:35 +02:00
}
2020-08-15 23:07:48 +02:00
2020-09-09 00:08:50 +02:00
SRV = & http . Server {
2020-08-05 17:58:24 +02:00
Addr : address ,
Handler : router ,
}
go func ( ) {
2020-09-09 00:08:50 +02:00
if err := SRV . ListenAndServe ( ) ; err != nil {
2020-08-16 14:36:54 +02:00
app . err . Printf ( "Failure serving: %s" , err )
2020-08-05 17:58:24 +02:00
}
} ( )
2020-08-16 14:36:54 +02:00
app . quit = make ( chan os . Signal )
signal . Notify ( app . quit , os . Interrupt )
2020-09-09 00:08:50 +02:00
go func ( ) {
for range app . quit {
app . shutdown ( )
}
} ( )
for range RESTART {
cntx , cancel := context . WithTimeout ( context . Background ( ) , time . Second * 5 )
defer cancel ( )
if err := SRV . Shutdown ( cntx ) ; err != nil {
app . err . Fatalf ( "Server shutdown error: %s" , err )
}
return
}
}
func ( app * appContext ) shutdown ( ) {
2020-08-16 14:36:54 +02:00
app . info . Println ( "Shutting down..." )
2020-08-05 17:58:24 +02:00
cntx , cancel := context . WithTimeout ( context . Background ( ) , time . Second * 5 )
defer cancel ( )
2020-09-09 00:08:50 +02:00
if err := SRV . Shutdown ( cntx ) ; err != nil {
2020-08-16 14:36:54 +02:00
app . err . Fatalf ( "Server shutdown error: %s" , err )
2020-08-05 17:58:24 +02:00
}
2020-09-09 00:08:50 +02:00
os . Exit ( 1 )
}
func flagPassed ( name string ) ( found bool ) {
for _ , f := range os . Args {
if f == name {
found = true
}
}
return
}
2020-09-24 18:51:13 +02:00
// @title jfa-go internal API
// @version 0.2.0
// @description API for the jfa-go frontend
// @contact.name Harvey Tindall
// @contact.email hrfee@protonmail.ch
// @license.name MIT
// @license.url https://raw.githubusercontent.com/hrfee/jfa-go/main/LICENSE
// @BasePath /
2020-09-24 19:50:03 +02:00
// @securityDefinitions.basic ApiKeyBlankPassword
// @name ApiKeyBlankPassword
// @securityDefinitions.basic getTokenAuth
// @name getTokenAuth
// @tag.name Auth
// @tag.description --------Get a token here first!--------
// @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-09 00:08:50 +02:00
func main ( ) {
fmt . Printf ( "jfa-go version: %s (%s)\n" , VERSION , COMMIT )
folder := "/tmp"
if PLATFORM == "windows" {
folder = os . Getenv ( "TEMP" )
}
SOCK = filepath . Join ( folder , SOCK )
2020-09-13 22:07:15 +02:00
fmt . Println ( "Socket:" , SOCK )
2020-09-16 12:55:35 +02:00
if flagPassed ( "test" ) {
TEST = true
}
2020-09-09 00:08:50 +02: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 )
} else {
RESTART = make ( chan bool , 1 )
start ( false , true )
for {
fmt . Printf ( "jfa-go version: %s (%s)\n" , VERSION , COMMIT )
start ( false , false )
}
}
2020-07-29 23:11:28 +02:00
}