diff --git a/Makefile b/Makefile index 0b8600b..724a8d3 100644 --- a/Makefile +++ b/Makefile @@ -11,9 +11,11 @@ all: stat run: go run \ nethack-launcher/check_dir.go \ + nethack-launcher/cleanup.go \ nethack-launcher/create_initial_files.go \ nethack-launcher/create_user_files.go \ nethack-launcher/janitor.go \ + nethack-launcher/library.go \ nethack-launcher/nethack-launcher.go \ nethack-launcher/print_change_password_screen.go \ nethack-launcher/print_high_scores.go \ @@ -35,7 +37,7 @@ stat: dependencies: go get github.com/gorilla/mux -build_docker: +build_docker: fetch_sotw mkdir -p stage.tmp/ cp deps/Dockerfile stage.tmp/ cp config.gcfg stage.tmp/config.gcfg @@ -46,9 +48,19 @@ build_docker: cp deps/chowner.sh stage.tmp/ cp deps/run_nethack.sh stage.tmp/ cp deps/reclist.c stage.tmp/ + cp deps/link_sotw.sh stage.tmp/ + cp deps/clean.sh stage.tmp/ + cp deps/scores.dat stage.tmp/ + wget -O stage.tmp/redis-server https://cryo.unixvoid.com/bin/redis/5.0.7/redis-server + chmod +x stage.tmp/redis-server cd stage.tmp/ && \ $(OS_PERMS) docker build -t $(IMAGE_NAME) . +fetch_sotw: + mkdir -p stage.tmp/ + wget https://cryo.unixvoid.com/bin/misc/sotw/shadow-of-the-wyrm-release-1.2.2.tar.gz + mv shadow-of-the-wyrm-release-1.2.2.tar.gz stage.tmp/sotw.tar.gz + run_docker: $(OS_PERMS) docker run \ -d \ diff --git a/config.gcfg b/config.gcfg index 7ff46cc..219b199 100644 --- a/config.gcfg +++ b/config.gcfg @@ -6,6 +6,10 @@ nhdatlocation = "/NetHack/dat/nhdat" recoverbinary = "/NetHack/util/recover" sysconflocation = "/NetHack/sys/unix/sysconf" + ttyreccache = "10" + sotwversion = "1.2.2" + sotwroot = "/sotw" + sotwdumpcache = "5" bootstrapdelay = 1 [redis] diff --git a/deps/Dockerfile b/deps/Dockerfile index 597351e..1f3fb5e 100644 --- a/deps/Dockerfile +++ b/deps/Dockerfile @@ -1,13 +1,13 @@ -FROM debian +FROM debian:stretch # install needed packages RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y \ - redis-server \ openssh-server \ autoconf \ bison \ bsdmainutils \ + bzip2 \ flex \ gcc \ gdb \ @@ -15,14 +15,26 @@ RUN apt-get update && \ groff \ libncursesw5-dev \ libsqlite3-dev \ + libboost1.62-dev \ + libboost-all-dev \ + libncurses5-dev \ + libncursesw5-dev \ + lua5.1 \ + liblua5.1-0-dev \ + libsdl2-dev \ + libsdl2-image-2.0-0 \ + libsdl2-image-dev \ + libxerces-c-dev \ make \ ncurses-dev \ + premake4 \ sqlite3 \ tar \ telnetd \ xinetd \ locales \ - git \ + wget \ + zlibc \ vim RUN apt-get clean @@ -89,6 +101,7 @@ RUN gcc reclist.c -o /bin/reclist RUN rm reclist.c # copy in files +COPY redis-server /usr/bin/ COPY config.gcfg / COPY nethack-launcher / COPY redis.conf / @@ -96,5 +109,13 @@ COPY run.sh / COPY chowner.sh /bin/ COPY nethackrc /.nethackrc COPY run_nethack.sh / +COPY link_sotw.sh /bin/ +COPY clean.sh /bin/ +ADD sotw.tar.gz / +RUN mv /shadow-of-the-wyrm* /sotw/ +COPY scores.dat /sotw/ +# update ini to match current season by default +RUN cd /sotw && \ + sed -i 's/current_month_is_start_month=0/current_month_is_start_month=1/g' swyrm.ini CMD ["/run.sh"] diff --git a/deps/chowner.sh b/deps/chowner.sh index 01db8ae..859025a 100755 --- a/deps/chowner.sh +++ b/deps/chowner.sh @@ -2,7 +2,12 @@ while : do + # chown nethack directory chown -R nethack:nethack /hack 2> /dev/null chmod -R 744 /hack 2> /dev/null + + # chown + chown -R nethack:nethack /sotw 2> /dev/null + chmod -R 744 /sotw 2> /dev/null sleep 2 done diff --git a/deps/clean.sh b/deps/clean.sh new file mode 100755 index 0000000..819a389 --- /dev/null +++ b/deps/clean.sh @@ -0,0 +1,30 @@ +#!/bin/bash +HACKDIR=$1 +USERDIR=$2 +TTYRECCACHE=$3 +SOTWDUMPCACHE=$4 + +### +# clean old ttyrecs +### +cd /$HACKDIR/user/$USERDIR/ttyrec/ +ls -1tr | head -n -$TTYRECCACHE | xargs -d '\n' rm -f -- + + +### +# clean sotw files +### +cd /$HACKDIR/user/$USERDIR/sotw/ + +# there are always 4 symlinked files +# leave 5 of the latest dumps +DUMPFILES=$(expr $SOTWDUMPCACHE + 4) + +FILES=$(ls -1tr *.txt | head -n -$DUMPFILES) + +for f in $FILES; do + #test -h $f || echo "removing old file $f" + if [ ! -h $f ]; then + rm -rf $f + fi +done diff --git a/deps/link_sotw.sh b/deps/link_sotw.sh new file mode 100755 index 0000000..aeacdcc --- /dev/null +++ b/deps/link_sotw.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +FILES=/sotw/* +USERDIR=$1 +HACKDIR=$2 + +# create initial dir if it does not exist +mkdir -p $USERDIR/sotw/ + +# link all game files to user dir +for f in $FILES +do + echo "linking $f" + ln -s $f $USERDIR/sotw/ +done + +# remove inital score file +rm -rf $USERDIR/sotw/scores.dat +# link in shared score file from hackdir +ln -s $HACKDIR/scores.dat $USERDIR/sotw/ + +# remove initial config file +rm -rf $USERDIR/sotw/swyrm.ini +# copy in sotw config file +cp $HACKDIR/swyrm.ini $USERDIR/sotw/ diff --git a/deps/scores.dat b/deps/scores.dat new file mode 100755 index 0000000..716c90d Binary files /dev/null and b/deps/scores.dat differ diff --git a/nethack-launcher/cleanup.go b/nethack-launcher/cleanup.go new file mode 100644 index 0000000..e1f81ac --- /dev/null +++ b/nethack-launcher/cleanup.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "os/exec" +) + +func runCleanup(username string) { + // clean up ttyrecs in //user//ttyrec/ + // clean up sotw dumps, aka: + // non-symlinked .txt files in //user//sotw/ + + // run cleanup file + out, err := exec.Command("/bin/clean.sh", config.NethackLauncher.HackDir, username, config.NethackLauncher.TTYRecCache, config.NethackLauncher.SotwDumpCache).Output() + if err != nil { + fmt.Println(err) + } + fmt.Printf("%s\n", out) +} diff --git a/nethack-launcher/create_initial_files.go b/nethack-launcher/create_initial_files.go index e289db1..fbd0563 100644 --- a/nethack-launcher/create_initial_files.go +++ b/nethack-launcher/create_initial_files.go @@ -51,4 +51,9 @@ func createInitialFiles() { } } + + // make sure initial sotw scores file exists + if _, err := os.Stat(fmt.Sprintf("%s/scores.dat", config.NethackLauncher.HackDir)); os.IsNotExist(err) { + exec.Command("cp", fmt.Sprintf("%s/scores.dat", config.NethackLauncher.SotwRoot), config.NethackLauncher.HackDir).Run() + } } diff --git a/nethack-launcher/create_user_files.go b/nethack-launcher/create_user_files.go index 6f6a22d..2248851 100644 --- a/nethack-launcher/create_user_files.go +++ b/nethack-launcher/create_user_files.go @@ -39,4 +39,9 @@ func createUserFiles(username string) { // move in nhdat file if it does not exist exec.Command("cp", fmt.Sprintf("%s/nhdat", config.NethackLauncher.HackDir), userpath).Run() + + // run sotw prep script + if _, err := os.Stat(fmt.Sprintf("%s/sotw/", userpath)); os.IsNotExist(err) { + exec.Command("/bin/link_sotw.sh", userpath, config.NethackLauncher.HackDir).Run() + } } diff --git a/nethack-launcher/library.go b/nethack-launcher/library.go new file mode 100644 index 0000000..652ec0f --- /dev/null +++ b/nethack-launcher/library.go @@ -0,0 +1,144 @@ +package main + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "os/exec" + "strconv" + "strings" + + "gopkg.in/redis.v5" +) + +func printLibraryScreen(redisClient *redis.Client, username string) { + // print header + fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) + println("") + + println(" Choose a users library to view ('enter' without selection returns)") + println("") + + us, err := ioutil.ReadDir(fmt.Sprintf("/%s/user", config.NethackLauncher.HackDir)) + if err != nil { + println(" No users exist ('enter' without selection returns)") + panic(err) + } + + users := make(map[int]string) + usersTimer := 1 + for _, u := range us { + fmt.Printf(" %d) %s\n", usersTimer, u.Name()) + + // add user to line map + users[usersTimer] = fmt.Sprintf("%s", u.Name()) + usersTimer++ + } + + fmt.Println("") + fmt.Printf(">> ") + // start user input + + // disable input buffering + exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() + // do not display entered characters on the screen + exec.Command("stty", "-F", "/dev/tty", "-echo").Run() + + var b []byte = make([]byte, 1) + for { + os.Stdin.Read(b) + s, _ := strconv.Atoi(string(b)) + + // check if user is trying to navigate + if string(b) == "\n" { + exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() + clearScreen() + if username == "" { + clearScreen() + printWelcomeScreen(redisClient) + } else { + clearScreen() + printUserScreen(redisClient, username) + } + } + // check if selection is in out map + if users[s] != "" { + user := strings.Split(users[s], ":") + fmt.Printf("going to spectate '%s'\n", user[0]) + clearScreen() + printUserLibraryScreen(redisClient, username, user[0]) + } + } +} + +func printUserLibraryScreen(redisClient *redis.Client, username, currentUser string) { + // print header + fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) + println("") + + println(" Choose a ttyrec to view ('enter' without selection returns)") + println(" The following keybinds are available during playback:") + println(" • + or f: double the speed of playback") + println(" • - or s: halve the speed of playback") + println(" • 0: set playback speed to 0, pausing playback") + println(" • 1: set playback speed to 1 again") + println("") + + t, err := ioutil.ReadDir(fmt.Sprintf("/%s/user/%s/ttyrec", config.NethackLauncher.HackDir, currentUser)) + if err != nil { + println(" No ttyrecs exist ('enter' without selection returns)") + panic(err) + } + + ttyrecs := make(map[int]string) + ttyrecsTimer := 1 + for _, ty := range t { + fmt.Printf(" %d) %s\n", ttyrecsTimer, ty.Name()) + + // add user to line map + ttyrecs[ttyrecsTimer] = fmt.Sprintf("%s", ty.Name()) + ttyrecsTimer++ + } + + fmt.Println("") + fmt.Printf(">> ") + // start user input + + // disable input buffering + exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run() + // enable showing input on screen + exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() + + scanner := bufio.NewScanner(os.Stdin) + for scanner.Scan() { + if scanner.Text() == "" { + clearScreen() + printLibraryScreen(redisClient, username) + } + // save input + s := scanner.Text() + sel, err := strconv.Atoi(s) + if err != nil { + fmt.Printf(" There was a problem with your last entry.\n>> ") + } + // check if selection is in our map + if ttyrecs[sel] != "" { + fmt.Printf("going to watch '%s'\n", ttyrecs[sel]) + clearScreen() + + // set ttyrec path + ttyrecPath := fmt.Sprintf("%s/user/%s/ttyrec/%s", config.NethackLauncher.HackDir, username, ttyrecs[sel]) + tp := exec.Command("ttyplay", ttyrecPath) + tp.Stdout = os.Stdout + tp.Stdin = os.Stdin + tp.Stderr = os.Stderr + err := tp.Run() + if err != nil { + fmt.Print(err) + } + } + clearScreen() + printUserLibraryScreen(redisClient, username, currentUser) + } +} diff --git a/nethack-launcher/nethack-launcher.go b/nethack-launcher/nethack-launcher.go index a3c8aa1..ac2547e 100644 --- a/nethack-launcher/nethack-launcher.go +++ b/nethack-launcher/nethack-launcher.go @@ -20,6 +20,10 @@ type Config struct { NhdatLocation string RecoverBinary string SysconfLocation string + TTYRecCache string + SotwVersion string + SotwRoot string + SotwDumpCache string BootstrapDelay time.Duration } diff --git a/nethack-launcher/print_login_screen.go b/nethack-launcher/print_login_screen.go index f82e33f..9fd5841 100644 --- a/nethack-launcher/print_login_screen.go +++ b/nethack-launcher/print_login_screen.go @@ -52,6 +52,7 @@ func printLoginScreen(redisClient *redis.Client) { typedHash := sha3.Sum512([]byte(typedAuth)) if fmt.Sprintf("%x", typedHash) == storedHash { // user authed + createUserFiles(username) printUserScreen(redisClient, username) noPass = false } else { diff --git a/nethack-launcher/print_user_screen.go b/nethack-launcher/print_user_screen.go index e4be8d8..70b601a 100644 --- a/nethack-launcher/print_user_screen.go +++ b/nethack-launcher/print_user_screen.go @@ -12,17 +12,25 @@ import ( func printUserScreen(redisClient *redis.Client, username string) string { clearScreen() fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) - println("") fmt.Printf(" Logged in as: %s\n", username) println("") + println("Global") println(" l) Logout") println(" c) Change password") println(" w) Watch games in progress") - println(" h) View highscores") - println(" a) View all scores") - println(" e) Edit config") - println(" r) Recover from crash") + println(" x) Browse library of ttyrecs") + println("") + println("Shadow of the Wyrm") + println(" f) Edit SOTW config") + fmt.Printf(" o) Play Shadow of the Wyrm %s\n", config.NethackLauncher.SotwVersion) + println("") + println("Nethack") + println(" h) View NetHack highscores") + println(" a) View all NetHack scores") + println(" e) Edit NetHack config") + println(" r) Recover NetHack from crash") fmt.Printf(" p) Play NetHack %s\n", config.NethackLauncher.NethackVersion) + println("") println(" q) Quit") println("") fmt.Printf(">> ") @@ -45,7 +53,30 @@ func printUserScreen(redisClient *redis.Client, username string) string { hackRCLoc := fmt.Sprintf("%s/user/%s/.nethackrc", config.NethackLauncher.HackDir, username) exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() clearScreen() - nh := exec.Command("vim", "-Z", hackRCLoc) + nh := exec.Command("vim", "-i", "NONE", "-Z", hackRCLoc) + nh.Stdout = os.Stdout + nh.Stdin = os.Stdin + nh.Stderr = os.Stderr + nh.Run() + clearScreen() + printUserScreen(redisClient, username) + case "f": + // check if the file has been uplifted + SOTWLoc := fmt.Sprintf("%s/user/%s/sotw/swyrm.ini", config.NethackLauncher.HackDir, username) + il, _ := os.Lstat(SOTWLoc) + if il.Mode()&os.ModeSymlink == os.ModeSymlink { + // the file is still a legacy symlink, remove and recopy + err := os.Remove(SOTWLoc) + if err != nil { + fmt.Println(err) + } + SOTWOrigin := fmt.Sprintf("%s/swyrm.ini", config.NethackLauncher.SotwRoot) + exec.Command("cp", SOTWOrigin, SOTWLoc).Run() + } + + exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() + clearScreen() + nh := exec.Command("vim", "-i", "NONE", "-Z", SOTWLoc) nh.Stdout = os.Stdout nh.Stdin = os.Stdin nh.Stderr = os.Stderr @@ -58,6 +89,9 @@ func printUserScreen(redisClient *redis.Client, username string) string { case "w": clearScreen() printProgressScreen(redisClient, username) + case "x": + clearScreen() + printLibraryScreen(redisClient, username) case "h": clearScreen() printHighScores(redisClient, username) @@ -69,6 +103,17 @@ func printUserScreen(redisClient *redis.Client, username string) string { currentTime := time.Now().UTC() fulltime := currentTime.Format("2006-01-02.03:04:05") go runGame(username, fulltime) + go runCleanup(username) + watcher := startWatcher(username, fulltime, redisClient) + wg.Wait() + close(watcher) + printUserScreen(redisClient, username) + case "o": + wg.Add(1) + currentTime := time.Now().UTC() + fulltime := currentTime.Format("2006-01-02.03:04:05") + go runSotwGame(username, fulltime) + go runCleanup(username) watcher := startWatcher(username, fulltime, redisClient) wg.Wait() close(watcher) diff --git a/nethack-launcher/print_welcome_screen.go b/nethack-launcher/print_welcome_screen.go index 61e568b..9d2c81c 100644 --- a/nethack-launcher/print_welcome_screen.go +++ b/nethack-launcher/print_welcome_screen.go @@ -11,14 +11,11 @@ import ( func printWelcomeScreen(redisClient *redis.Client) string { clearScreen() fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) - println("") println(" Not logged in.") println("") println(" l) Login") println(" r) Register new user") println(" w) Watch games in progress") - println(" h) View highscores") - println(" a) View all scores") println(" q) Quit") println("") fmt.Printf(">> ") @@ -45,12 +42,6 @@ func printWelcomeScreen(redisClient *redis.Client) string { case "w": clearScreen() printProgressScreen(redisClient, "") - case "h": - clearScreen() - printHighScores(redisClient, "") - case "a": - clearScreen() - printAllScores(redisClient, "") case "q": exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() clearScreen() diff --git a/nethack-launcher/run_game.go b/nethack-launcher/run_game.go index 779c339..f2c9eeb 100644 --- a/nethack-launcher/run_game.go +++ b/nethack-launcher/run_game.go @@ -40,6 +40,27 @@ func runGame(username, timestamp string) { wg.Done() } +func runSotwGame(username, timestamp string) { + exec.Command("stty", "-F", "/dev/tty", "echo", "-cbreak").Run() + clearScreen() + + // put together users home dir + //homeDir := fmt.Sprintf("%s/user/%s/", config.NethackLauncher.HackDir, username) + ttyrecPath := fmt.Sprintf("%s/user/%s/ttyrec/%s.ttyrec", config.NethackLauncher.HackDir, username, timestamp) + + nh := exec.Command("ttyrec", "-f", ttyrecPath, "--", "./ShadowOfTheWyrm") + nh.Dir = (fmt.Sprintf("%s/user/%s/sotw/", config.NethackLauncher.HackDir, username)) + nh.Stdout = os.Stdout + nh.Stdin = os.Stdin + nh.Stderr = os.Stderr + err := nh.Run() + if err != nil { + fmt.Print(err) + } + exec.Command("exit").Run() + wg.Done() +} + func runtimeRecover(username string) { fmt.Printf(" %s\n", config.NethackLauncher.ServerDisplay) println("")