2020-04-10 13:12:18 +00:00
package cmd
import (
"crypto/tls"
2021-01-08 23:14:56 +00:00
"errors"
2020-04-10 13:12:18 +00:00
"fmt"
2021-01-10 01:22:39 +00:00
log2 "log"
"net/http"
"os"
"path"
"path/filepath"
2021-01-15 04:11:01 +00:00
"strconv"
2021-01-10 01:22:39 +00:00
"strings"
2021-01-08 23:14:56 +00:00
"time"
2021-01-10 01:22:39 +00:00
2020-08-04 03:35:48 +00:00
"github.com/NYTimes/logrotate"
2020-12-16 05:56:53 +00:00
"github.com/apex/log"
2020-08-04 03:35:48 +00:00
"github.com/apex/log/handlers/multi"
2020-09-13 05:08:50 +00:00
"github.com/docker/docker/client"
2020-08-01 04:56:44 +00:00
"github.com/gammazero/workerpool"
2020-07-04 22:18:29 +00:00
"github.com/mitchellh/colorstring"
2020-04-10 21:39:07 +00:00
"github.com/pkg/profile"
2020-04-10 13:12:18 +00:00
"github.com/pterodactyl/wings/config"
"github.com/pterodactyl/wings/environment"
2020-12-16 05:56:53 +00:00
"github.com/pterodactyl/wings/loggers/cli"
2021-01-22 22:38:11 +00:00
"github.com/pterodactyl/wings/remote"
2020-04-10 13:12:18 +00:00
"github.com/pterodactyl/wings/router"
"github.com/pterodactyl/wings/server"
"github.com/pterodactyl/wings/sftp"
"github.com/pterodactyl/wings/system"
"github.com/spf13/cobra"
2020-12-16 05:56:53 +00:00
"golang.org/x/crypto/acme"
"golang.org/x/crypto/acme/autocert"
2020-04-10 13:12:18 +00:00
)
2020-11-08 19:59:04 +00:00
var (
2021-01-09 06:19:23 +00:00
configPath = config . DefaultLocation
debug = false
2020-11-08 19:59:04 +00:00
)
2020-04-10 13:12:18 +00:00
2021-01-09 06:19:23 +00:00
var rootCommand = & cobra . Command {
2020-04-10 13:12:18 +00:00
Use : "wings" ,
2021-01-09 06:19:23 +00:00
Short : "Runs the API server allowing programatic control of game servers for Pterodactyl Panel." ,
2020-07-01 04:34:47 +00:00
PreRun : func ( cmd * cobra . Command , args [ ] string ) {
2021-01-15 04:11:01 +00:00
initConfig ( )
initLogging ( )
2021-01-09 06:19:23 +00:00
if tls , _ := cmd . Flags ( ) . GetBool ( "auto-tls" ) ; tls {
if host , _ := cmd . Flags ( ) . GetString ( "tls-hostname" ) ; host == "" {
fmt . Println ( "A TLS hostname must be provided when running wings with automatic TLS, e.g.:\n\n ./wings --auto-tls --tls-hostname my.example.com" )
os . Exit ( 1 )
}
2020-07-01 04:34:47 +00:00
}
} ,
2020-07-04 22:18:29 +00:00
Run : rootCmdRun ,
2020-04-10 13:12:18 +00:00
}
2021-01-09 06:19:23 +00:00
var versionCommand = & cobra . Command {
Use : "version" ,
Short : "Prints the current executable version and exits." ,
Run : func ( cmd * cobra . Command , _ [ ] string ) {
fmt . Printf ( "wings v%s\nCopyright © 2018 - 2021 Dane Everitt & Contributors\n" , system . Version )
} ,
}
func Execute ( ) {
if err := rootCommand . Execute ( ) ; err != nil {
log2 . Fatalf ( "failed to execute command: %s" , err )
}
}
2020-04-10 13:12:18 +00:00
func init ( ) {
2021-01-09 06:19:23 +00:00
rootCommand . PersistentFlags ( ) . StringVar ( & configPath , "config" , config . DefaultLocation , "set the location for the configuration file" )
rootCommand . PersistentFlags ( ) . BoolVar ( & debug , "debug" , false , "pass in order to run wings in debug mode" )
// Flags specifically used when running the API.
rootCommand . Flags ( ) . String ( "profiler" , "" , "the profiler to run for this instance" )
rootCommand . Flags ( ) . Bool ( "auto-tls" , false , "pass in order to have wings generate and manage it's own SSL certificates using Let's Encrypt" )
rootCommand . Flags ( ) . String ( "tls-hostname" , "" , "required with --auto-tls, the FQDN for the generated SSL certificate" )
rootCommand . Flags ( ) . Bool ( "ignore-certificate-errors" , false , "ignore certificate verification errors when executing API calls" )
rootCommand . AddCommand ( versionCommand )
rootCommand . AddCommand ( configureCmd )
2021-01-15 04:32:38 +00:00
rootCommand . AddCommand ( newDiagnosticsCommand ( ) )
2020-04-17 21:27:06 +00:00
}
2021-01-09 06:19:23 +00:00
func rootCmdRun ( cmd * cobra . Command , _ [ ] string ) {
switch cmd . Flag ( "profiler" ) . Value . String ( ) {
2020-11-08 20:12:21 +00:00
case "cpu" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . CPUProfile ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "mem" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . MemProfile ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "alloc" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . MemProfile , profile . MemProfileAllocs ( ) ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "heap" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . MemProfile , profile . MemProfileHeap ( ) ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "routines" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . GoroutineProfile ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "mutex" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . MutexProfile ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "threads" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . ThreadcreationProfile ) . Stop ( )
2020-11-08 20:12:21 +00:00
case "block" :
2020-11-08 20:33:24 +00:00
defer profile . Start ( profile . BlockProfile ) . Stop ( )
2020-04-10 22:33:30 +00:00
}
2020-04-10 21:39:07 +00:00
2020-04-10 13:12:18 +00:00
printLogo ( )
2021-01-13 05:14:57 +00:00
log . Debug ( "running in debug mode" )
2021-01-15 04:11:01 +00:00
log . WithField ( "config_file" , configPath ) . Info ( "loading configuration from file" )
2020-04-10 13:12:18 +00:00
2021-01-09 06:19:23 +00:00
if ok , _ := cmd . Flags ( ) . GetBool ( "ignore-certificate-errors" ) ; ok {
2020-12-12 17:56:01 +00:00
log . Warn ( "running with --ignore-certificate-errors: TLS certificate host chains and name will not be verified" )
2020-04-10 13:12:18 +00:00
http . DefaultTransport . ( * http . Transport ) . TLSClientConfig = & tls . Config {
InsecureSkipVerify : true ,
}
}
2021-01-13 05:14:57 +00:00
if err := config . ConfigureTimezone ( ) ; err != nil {
2020-10-17 18:35:20 +00:00
log . WithField ( "error" , err ) . Fatal ( "failed to detect system timezone or use supplied configuration value" )
}
2021-01-15 04:11:01 +00:00
log . WithField ( "timezone" , config . Get ( ) . System . Timezone ) . Info ( "configured wings with system timezone" )
2021-01-13 05:14:57 +00:00
if err := config . ConfigureDirectories ( ) ; err != nil {
2020-09-04 03:13:51 +00:00
log . WithField ( "error" , err ) . Fatal ( "failed to configure system directories for pterodactyl" )
return
}
2021-01-13 05:14:57 +00:00
if err := config . EnableLogRotation ( ) ; err != nil {
2020-09-04 03:13:51 +00:00
log . WithField ( "error" , err ) . Fatal ( "failed to configure log rotation on the system" )
2020-08-04 23:19:04 +00:00
return
2020-04-17 21:27:06 +00:00
}
2021-01-15 04:11:01 +00:00
log . WithField ( "username" , config . Get ( ) . System . User ) . Info ( "checking for pterodactyl system user" )
2021-01-13 05:14:57 +00:00
if err := config . EnsurePterodactylUser ( ) ; err != nil {
2020-09-04 03:29:53 +00:00
log . WithField ( "error" , err ) . Fatal ( "failed to create pterodactyl system user" )
2020-04-10 13:12:18 +00:00
}
2021-01-13 05:14:57 +00:00
log . WithFields ( log . Fields {
2021-01-15 04:11:01 +00:00
"username" : config . Get ( ) . System . Username ,
"uid" : config . Get ( ) . System . User . Uid ,
"gid" : config . Get ( ) . System . User . Gid ,
2021-01-13 05:14:57 +00:00
} ) . Info ( "configured system user successfully" )
2020-04-10 13:12:18 +00:00
2021-01-26 04:28:24 +00:00
pclient := remote . CreateClient (
2021-01-08 23:14:56 +00:00
config . Get ( ) . PanelLocation ,
config . Get ( ) . AuthenticationTokenId ,
config . Get ( ) . AuthenticationToken ,
2021-01-22 22:38:11 +00:00
remote . WithTimeout ( time . Second * time . Duration ( config . Get ( ) . RemoteQuery . Timeout ) ) ,
2021-01-08 23:14:56 +00:00
)
2021-01-26 04:28:24 +00:00
manager , err := server . NewManager ( cmd . Context ( ) , pclient )
if err != nil {
2020-05-29 05:07:53 +00:00
log . WithField ( "error" , err ) . Fatal ( "failed to load server configurations" )
2020-04-10 13:12:18 +00:00
}
2021-01-13 05:14:57 +00:00
if err := environment . ConfigureDocker ( cmd . Context ( ) ) ; err != nil {
2020-05-29 05:07:53 +00:00
log . WithField ( "error" , err ) . Fatal ( "failed to configure docker environment" )
2020-04-10 13:12:18 +00:00
}
2021-01-15 04:11:01 +00:00
if err := config . WriteToDisk ( config . Get ( ) ) ; err != nil {
log . WithField ( "error" , err ) . Fatal ( "failed to write configuration to disk" )
2020-04-10 23:55:36 +00:00
}
2020-04-10 13:12:18 +00:00
// Just for some nice log output.
2021-01-26 04:28:24 +00:00
for _ , s := range manager . All ( ) {
log . WithField ( "server" , s . Id ( ) ) . Info ( "finished loading configuration for server" )
2020-04-10 13:12:18 +00:00
}
2021-01-26 04:28:24 +00:00
states , err := manager . ReadStates ( )
2020-09-13 05:08:50 +00:00
if err != nil {
2020-11-28 23:57:10 +00:00
log . WithField ( "error" , err ) . Error ( "failed to retrieve locally cached server states from disk, assuming all servers in offline state" )
2020-09-13 05:08:50 +00:00
}
2021-01-26 04:28:24 +00:00
ticker := time . NewTicker ( time . Minute )
// Every minute, write the current server states to the disk to allow for a more
// seamless hard-reboot process in which wings will re-sync server states based
// on it's last tracked state.
go func ( ) {
for {
select {
case <- ticker . C :
if err := manager . PersistStates ( ) ; err != nil {
log . WithField ( "error" , err ) . Warn ( "failed to persist server states to disk" )
}
case <- cmd . Context ( ) . Done ( ) :
ticker . Stop ( )
return
}
}
} ( )
2020-08-01 04:56:44 +00:00
// Create a new workerpool that limits us to 4 servers being bootstrapped at a time
2020-04-10 13:12:18 +00:00
// on Wings. This allows us to ensure the environment exists, write configurations,
// and reboot processes without causing a slow-down due to sequential booting.
2020-08-01 04:56:44 +00:00
pool := workerpool . New ( 4 )
2021-01-26 04:28:24 +00:00
for _ , serv := range manager . All ( ) {
2020-08-01 04:56:44 +00:00
s := serv
2020-05-29 05:07:53 +00:00
2021-01-23 18:45:29 +00:00
// For each server we encounter make sure the root data directory exists.
if err := s . EnsureDataDirectoryExists ( ) ; err != nil {
s . Log ( ) . Error ( "could not create root data directory for server: not loading server..." )
continue
}
2020-08-01 04:56:44 +00:00
pool . Submit ( func ( ) {
2020-09-13 05:08:50 +00:00
s . Log ( ) . Info ( "configuring server environment and restoring to previous state" )
var st string
if state , exists := states [ s . Id ( ) ] ; exists {
st = state
2020-04-10 13:12:18 +00:00
}
r , err := s . Environment . IsRunning ( )
2020-09-13 05:08:50 +00:00
// We ignore missing containers because we don't want to actually block booting of wings at this
// point. If we didn't do this and you pruned all of the images and then started wings you could
// end up waiting a long period of time for all of the images to be re-pulled on Wings boot rather
// than when the server itself is started.
if err != nil && ! client . IsErrNotFound ( err ) {
2020-05-29 05:07:53 +00:00
s . Log ( ) . WithField ( "error" , err ) . Error ( "error checking server environment status" )
2020-04-10 13:12:18 +00:00
}
2020-09-13 05:08:50 +00:00
// Check if the server was previously running. If so, attempt to start the server now so that Wings
// can pick up where it left off. If the environment does not exist at all, just create it and then allow
// the normal flow to execute.
2020-04-10 13:12:18 +00:00
//
2020-09-13 05:08:50 +00:00
// This does mean that booting wings after a catastrophic machine crash and wiping out the Docker images
// as a result will result in a slow boot.
if ! r && ( st == environment . ProcessRunningState || st == environment . ProcessStartingState ) {
if err := s . HandlePowerAction ( server . PowerActionStart ) ; err != nil {
2020-11-28 23:57:10 +00:00
s . Log ( ) . WithField ( "error" , err ) . Warn ( "failed to return server to running state" )
2020-09-13 05:08:50 +00:00
}
} else if r || ( ! r && s . IsRunning ( ) ) {
// If the server is currently running on Docker, mark the process as being in that state.
// We never want to stop an instance that is currently running external from Wings since
// that is a good way of keeping things running even if Wings gets in a very corrupted state.
//
// This will also validate that a server process is running if the last tracked state we have
// is that it was running, but we see that the container process is not currently running.
2020-05-29 05:07:53 +00:00
s . Log ( ) . Info ( "detected server is running, re-attaching to process..." )
2020-08-20 02:20:46 +00:00
2020-11-07 05:53:00 +00:00
s . Environment . SetState ( environment . ProcessRunningState )
2020-08-20 02:20:46 +00:00
if err := s . Environment . Attach ( ) ; err != nil {
2020-11-28 23:57:10 +00:00
s . Log ( ) . WithField ( "error" , err ) . Warn ( "failed to attach to running server environment" )
2020-04-10 13:12:18 +00:00
}
2020-11-11 05:21:20 +00:00
} else {
// At this point we've determined that the server should indeed be in an offline state, so we'll
// make a call to set that state just to ensure we don't ever accidentally end up with some invalid
// state being tracked.
s . Environment . SetState ( environment . ProcessOfflineState )
2020-04-10 13:12:18 +00:00
}
2020-08-01 04:56:44 +00:00
} )
2020-04-10 13:12:18 +00:00
}
2020-08-04 23:19:04 +00:00
// Wait until all of the servers are ready to go before we fire up the SFTP and HTTP servers.
2020-08-01 04:56:44 +00:00
pool . StopWait ( )
2021-01-15 04:11:01 +00:00
defer func ( ) {
// Cancel the context on all of the running servers at this point, even though the
// program is just shutting down.
2021-01-26 04:28:24 +00:00
for _ , s := range manager . All ( ) {
2021-01-15 04:11:01 +00:00
s . CtxCancel ( )
}
} ( )
2020-04-10 13:12:18 +00:00
2021-01-10 23:59:45 +00:00
go func ( ) {
// Run the SFTP server.
2021-01-26 04:28:24 +00:00
if err := sftp . New ( manager ) . Run ( ) ; err != nil {
2021-01-10 23:59:45 +00:00
log . WithError ( err ) . Fatal ( "failed to initialize the sftp server" )
return
}
} ( )
2020-04-10 13:12:18 +00:00
2021-01-15 04:11:01 +00:00
sys := config . Get ( ) . System
2020-04-10 13:12:18 +00:00
// Ensure the archive directory exists.
2021-01-15 04:11:01 +00:00
if err := os . MkdirAll ( sys . ArchiveDirectory , 0755 ) ; err != nil {
2020-05-29 05:07:53 +00:00
log . WithField ( "error" , err ) . Error ( "failed to create archive directory" )
2020-04-10 13:12:18 +00:00
}
2020-04-10 22:33:30 +00:00
// Ensure the backup directory exists.
2021-01-15 04:11:01 +00:00
if err := os . MkdirAll ( sys . BackupDirectory , 0755 ) ; err != nil {
2020-05-29 05:07:53 +00:00
log . WithField ( "error" , err ) . Error ( "failed to create backup directory" )
2020-04-10 22:33:30 +00:00
}
2021-01-09 06:19:23 +00:00
autotls , _ := cmd . Flags ( ) . GetBool ( "auto-tls" )
tlshostname , _ := cmd . Flags ( ) . GetString ( "tls-hostname" )
if autotls && tlshostname == "" {
autotls = false
}
2021-01-15 04:11:01 +00:00
api := config . Get ( ) . Api
2020-05-29 05:07:53 +00:00
log . WithFields ( log . Fields {
2021-01-15 04:11:01 +00:00
"use_ssl" : api . Ssl . Enabled ,
2021-01-09 06:19:23 +00:00
"use_auto_tls" : autotls ,
2021-01-15 04:11:01 +00:00
"host_address" : api . Host ,
"host_port" : api . Port ,
2020-07-01 04:34:47 +00:00
} ) . Info ( "configuring internal webserver" )
2020-04-10 13:12:18 +00:00
2021-01-15 04:11:01 +00:00
// Create a new HTTP server instance to handle inbound requests from the Panel
// and external clients.
2020-08-04 23:19:04 +00:00
s := & http . Server {
2021-01-15 04:11:01 +00:00
Addr : api . Host + ":" + strconv . Itoa ( api . Port ) ,
2021-02-02 05:28:46 +00:00
Handler : router . Configure ( manager , pclient ) ,
2021-01-15 04:11:01 +00:00
TLSConfig : config . DefaultTLSConfig ,
2020-08-04 23:19:04 +00:00
}
// Check if the server should run with TLS but using autocert.
2021-01-09 06:19:23 +00:00
if autotls {
2020-07-01 04:34:47 +00:00
m := autocert . Manager {
2020-07-04 22:18:29 +00:00
Prompt : autocert . AcceptTOS ,
2021-01-15 04:11:01 +00:00
Cache : autocert . DirCache ( path . Join ( sys . RootDirectory , "/.tls-cache" ) ) ,
2021-01-09 06:19:23 +00:00
HostPolicy : autocert . HostWhitelist ( tlshostname ) ,
2020-07-01 04:34:47 +00:00
}
2021-01-15 04:11:01 +00:00
log . WithField ( "hostname" , tlshostname ) . Info ( "webserver is now listening with auto-TLS enabled; certificates will be automatically generated by Let's Encrypt" )
2020-07-01 04:34:47 +00:00
2020-08-04 23:19:04 +00:00
// Hook autocert into the main http server.
s . TLSConfig . GetCertificate = m . GetCertificate
s . TLSConfig . NextProtos = append ( s . TLSConfig . NextProtos , acme . ALPNProto ) // enable tls-alpn ACME challenges
// Start the autocert server.
go func ( ) {
if err := http . ListenAndServe ( ":http" , m . HTTPHandler ( nil ) ) ; err != nil {
log . WithError ( err ) . Error ( "failed to serve autocert http server" )
}
} ( )
// Start the main http server with TLS using autocert.
2020-07-01 04:34:47 +00:00
if err := s . ListenAndServeTLS ( "" , "" ) ; err != nil {
2021-01-15 04:11:01 +00:00
log . WithFields ( log . Fields { "auto_tls" : true , "tls_hostname" : tlshostname , "error" : err } ) . Fatal ( "failed to configure HTTP server using auto-tls" )
2020-07-01 04:34:47 +00:00
}
2020-08-04 23:19:04 +00:00
return
}
2021-01-15 04:11:01 +00:00
// Check if main http server should run with TLS. Otherwise reset the TLS
// config on the server and then serve it over normal HTTP.
if api . Ssl . Enabled {
if err := s . ListenAndServeTLS ( strings . ToLower ( api . Ssl . CertificateFile ) , strings . ToLower ( api . Ssl . KeyFile ) ) ; err != nil {
2020-07-01 04:34:47 +00:00
log . WithFields ( log . Fields { "auto_tls" : false , "error" : err } ) . Fatal ( "failed to configure HTTPS server" )
2020-04-10 13:12:18 +00:00
}
2020-08-04 23:19:04 +00:00
return
}
s . TLSConfig = nil
if err := s . ListenAndServe ( ) ; err != nil {
log . WithField ( "error" , err ) . Fatal ( "failed to configure HTTP server" )
2020-04-10 13:12:18 +00:00
}
}
2020-12-25 19:21:09 +00:00
2021-01-15 04:11:01 +00:00
// Reads the configuration from the disk and then sets up the global singleton
// with all of the configuration values.
2021-01-13 05:14:57 +00:00
func initConfig ( ) {
2021-01-15 04:11:01 +00:00
if ! strings . HasPrefix ( configPath , "/" ) {
d , err := os . Getwd ( )
if err != nil {
log2 . Fatalf ( "cmd/root: could not determine directory: %s" , err )
}
configPath = path . Clean ( path . Join ( d , configPath ) )
2021-01-13 05:14:57 +00:00
}
2021-01-15 04:11:01 +00:00
err := config . FromFile ( configPath )
if err != nil {
if errors . Is ( err , os . ErrNotExist ) {
2021-01-13 05:14:57 +00:00
exitWithConfigurationNotice ( )
}
2021-01-15 04:11:01 +00:00
log2 . Fatalf ( "cmd/root: error while reading configuration file: %s" , err )
}
if debug && ! config . Get ( ) . Debug {
config . SetDebugViaFlag ( debug )
2021-01-13 05:14:57 +00:00
}
}
2020-04-10 13:12:18 +00:00
// Configures the global logger for Zap so that we can call it from any location
// in the code without having to pass around a logger instance.
2021-01-13 05:14:57 +00:00
func initLogging ( ) {
2021-01-15 04:11:01 +00:00
dir := config . Get ( ) . System . LogDirectory
2021-01-13 05:14:57 +00:00
if err := os . MkdirAll ( path . Join ( dir , "/install" ) , 0700 ) ; err != nil {
log2 . Fatalf ( "cmd/root: failed to create install directory path: %s" , err )
2020-08-24 17:29:40 +00:00
}
2021-01-13 05:14:57 +00:00
p := filepath . Join ( dir , "/wings.log" )
2020-08-04 03:35:48 +00:00
w , err := logrotate . NewFile ( p )
if err != nil {
2021-01-13 05:14:57 +00:00
log2 . Fatalf ( "cmd/root: failed to create wings log: %s" , err )
2020-08-04 03:35:48 +00:00
}
2021-01-09 06:19:23 +00:00
log . SetLevel ( log . InfoLevel )
2021-01-15 04:11:01 +00:00
if config . Get ( ) . Debug {
2020-09-04 03:29:53 +00:00
log . SetLevel ( log . DebugLevel )
}
2021-01-09 06:19:23 +00:00
log . SetHandler ( multi . New ( cli . Default , cli . New ( w . File , false ) ) )
2020-08-04 03:35:48 +00:00
log . WithField ( "path" , p ) . Info ( "writing log files to disk" )
2020-04-10 13:12:18 +00:00
}
// Prints the wings logo, nothing special here!
func printLogo ( ) {
2020-08-04 03:35:48 +00:00
fmt . Printf ( colorstring . Color ( `
____
__ [ blue ] [ bold ] Pterodactyl [ reset ] _____ / ___ / _______ _______ ______
\ _____ \ \ / \ / / / / __ / ___ /
\ ___ \ / / / / / _ / / ___ /
\ ___ / \ ___ / ___ / ___ / ___ / ___ / ______ /
2021-01-07 23:44:09 +00:00
/ _______ / [ bold ] % s [ reset ]
2020-08-04 03:35:48 +00:00
2020-12-27 19:54:18 +00:00
Copyright © 2018 - 2021 Dane Everitt & Contributors
2020-08-04 03:35:48 +00:00
Website : https : //pterodactyl.io
Source : https : //github.com/pterodactyl/wings
License : https : //github.com/pterodactyl/wings/blob/develop/LICENSE
This software is made available under the terms of the MIT license .
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software . % s ` ) , system . Version , "\n\n" )
2020-04-10 13:12:18 +00:00
}
2020-05-09 22:37:49 +00:00
func exitWithConfigurationNotice ( ) {
fmt . Print ( colorstring . Color ( `
[ _red_ ] [ white ] [ bold ] Error : Configuration File Not Found [ reset ]
Wings was not able to locate your configuration file , and therefore is not
2021-01-13 05:14:57 +00:00
able to complete its boot process . Please ensure you have copied your instance
configuration file into the default location below .
2020-05-09 22:37:49 +00:00
Default Location : / etc / pterodactyl / config . yml
[ yellow ] This is not a bug with this software . Please do not make a bug report
for this issue , it will be closed . [ reset ]
` ) )
os . Exit ( 1 )
}