β™Ÿ Use Stockfish on the cloud#

βš– Disclaimer#

  • I’m not a coding in GO, the program used here to proxy SSH is SSH-Engine from Matt Nohr, it’s released in GNU GENERAL PUBLIC LICENSE. I just have made some modification to the go.mod requirements.

  • I’m not working in chess engines and I’m a really poor player, so the engine used here is stockfish released in GPL-3.0, thanks for them.

  • I’m totally irresponsible for everything, money lost, burning servers, meteorite crashing on earth. Everything…

  • My inspiration comes from several websites, which included this one

  • I’m not supported by any company, I don’t earn a cent and I lose everyday money for this website.

✨ Creating an instance on Scaleway#

πŸ” Security#

πŸͺŸ Using WSL2#

Windows 11 include Windows Subsystem for 🐧Linux (WSL), it’s a compatibility layer for running Linux binary executables (in ELF format) natively. I don’t touch anymore putty or kitty. For more information about installing and using WSL2, please see this Microsoft Article.

Image 1

πŸ”‘ Creating 2 SSH-Keys#

We need to create 2 keys, one for root which need a passphrase, and another one for our chess without passphrase in order to connect without login and interaction.

cd ~
cd .ssh
ssh-keygen -t rsa -b 4096
# id_root with password
ssh-keygen -t rsa -b 4096
# id_chess without password

We need to transfer these keys on windows :

# Copy the key on my D drive
cp /home/remi/.ssh/* /mnt/d/

And restrict the access for Linux AND Windows :

chmod 600 /home/remi/.ssh/id_*

For Windows it’s a bit more complex (clearly, you need to click a lot) and we just need to do this for the id_chess private key :

Image 1

Image 1

Image 1

Image 1

Image 1

✨ Give the public key to scaleway#

The id_root.pub contains the public key that we need to communicates to Scaleway.

Image 1

Image 1

πŸ’° Rent an instance for 1 hour#

Go to Compute > Instance > Add > Create

Location

PARIS 1

Instance

PLAY2-PICO

Image

Ubuntu 22.04

Volume

10 Go

SSH Keys

My_Root_Key

Image 1

You can connect the server with :

# delete old known_hosts
ssh-keygen -f "/home/remi/.ssh/known_hosts" -R "51.158.***.***"
# Connect
ssh -i /home/remi/.ssh/id_root root@51.158.***.*** -p 22

β™» Update the server#

sudo apt-get update -y
sudo apt-get upgrade -y
sudo apt-get dist-upgrade -y
sudo apt-get autoremove --purge -y
sudo apt-get install -y unzip
sudo timedatectl set-timezone Europe/Paris
sudo reboot

πŸ‘¨πŸ» Create the chess account#

Create the account

# Add an account with a strong password
adduser chess

Transfer the public key to our server with scp

# Add an account with a strong password
scp -i /home/remi/.ssh/id_root /home/remi/.ssh/id_chess.pub root@51.158.***.***:/home/chess/id_chess.pub

Install the SSH key on the new server for the account chess

cd /home/chess
chown chess:chess id_chess.pub
su chess
mkdir /home/chess/.ssh
mv /home/chess/id_chess.pub /home/chess/.ssh/authorized_keys
chmod 600 /home/chess/.ssh/id_chess.pub

Try to connect to our new account, it must not ask password or anything else

ssh -i /home/remi/.ssh/id_chess chess@51.158.***.*** -p 22

You could try it on windows terminal :

ssh -i D:\Keys\id_chess chess@51.158.***.***

🐟 Install stockfish 15#

Connect to root

ssh -i /home/remi/.ssh/id_root root@51.158.***.*** -p 22

Get the last version of stockfish compatible with your CPU, here on scaleway AVX2 (AMD EPYC).

Image 1

cd /tmp
wget -q https://stockfishchess.org/files/stockfish_15_linux_x64_avx2.zip
unzip -q stockfish_15_linux_x64_avx2.zip
cd stockfish_15_linux_x64_avx2
chmod 775 /tmp/stockfish_15_linux_x64_avx2/stockfish_15_x64_avx2
cp /tmp/stockfish_15_linux_x64_avx2/stockfish_15_x64_avx2 /opt/stockfish
sudo ln -s /opt/stockfish /usr/bin/stockfish

πŸͺŸ Check if everything works fine#

Test Stockfish on chess account

su chess
stockfish

Image 1

Test Stockfish from πŸͺŸ Windows, this command from terminal must run directly stockfish

ssh -i D:\Keys\id_chess chess@51.158.***.*** stockfish

Image 1

πŸ’Ύ Build an image/snapshot of our instance#

Just save the instance on Scaleway, you could next load this image to create a big instance directly in two clicks using it !

Image 1

πŸ›£οΈ Build an SSH-Proxy#

In this part, we will code and build a program which will redirect all SSH input/output. We are doing this cause Chessbase program need an executable .exe file to interact with the engine.

πŸ“ Install GO for Windows#

Go the Go Website

Image 1

Install the program

Image 1

πŸ‘¨πŸ»β€πŸ’» Code the program#

Create a first file go.mod with the requirements for our program ! Warning Matt Nohr go.mod is outdated and will cause connection problem.

module ssh-engine

go 1.19

require (
	github.com/spf13/viper latest
	golang.org/x/crypto latest
)

Create the program SSHProxy.go actually, just a copy/paste from Matt Nohr

package main

import (
	"bufio"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"github.com/spf13/viper"
	"golang.org/x/crypto/ssh"
)

func main() {
	// log.Println("Running on " + runtime.GOOS)

	// Read configuration
	configuration := readConfiguration()
	debugLogging := false

	// Setup logging if a log file name was passed in
	if configuration.LogFileName != "" {
		file, err := os.OpenFile("engine.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
		if err != nil {
			log.Fatal(err)
		}
		defer file.Close()

		log.SetOutput(file)

		debugLogging = true
	}

	server := fmt.Sprintf("%s:%s", configuration.Host, configuration.Port)

	// Setup the client configuration
	sshConfig := getSshConfig(configuration)

	// Start the connection
	client, err := ssh.Dial("tcp", server, sshConfig)
	if err != nil {
		fmt.Printf("Could not connect to ssh (failed to dial). Error is: %s\n", err)
		os.Exit(1)
	}

	// Start a session
	session, err := client.NewSession()
	if err != nil {
		fmt.Printf("Failed to create ssh session. Error is: %s\n", err)
		os.Exit(1)
	}
	defer session.Close()

	session.Stdout = os.Stdout
	session.Stderr = os.Stderr

	// StdinPipe for commands
	stdin, _ := session.StdinPipe()

	// Start remote shell
	if err := session.Shell(); err != nil {
		fmt.Printf("Failed to start shell. Error is: %s\n", err)
		os.Exit(1)
	}

	// Run the supplied command first
	fmt.Fprintf(stdin, "%s\n", configuration.RemoteCommand)

	// Accepting commands
	scanner := bufio.NewScanner(os.Stdin)

	for scanner.Scan() {
		if debugLogging {
			log.Println("Input: " + scanner.Text())
		}

		fmt.Fprintf(stdin, "%s\n", scanner.Text())
		if scanner.Text() == "quit" {
			if debugLogging {
				log.Println("Quit sent")
			}

			break
		}
	}
}

func getSshConfig(configuration Configurations) *ssh.ClientConfig {
	key, err := getKeyFile(configuration.PrivateKeyFile)
	if err != nil {
		fmt.Printf("Could not read privateKeyFile at %s\n", configuration.PrivateKeyFile)
		os.Exit(1)
	}

	sshConfig := &ssh.ClientConfig{
		User: configuration.User,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(key),
		},
	}
	sshConfig.HostKeyCallback = ssh.InsecureIgnoreHostKey()

	return sshConfig
}

func getKeyFile(file string) (key ssh.Signer, err error) {
	buf, err := ioutil.ReadFile(file)
	if err != nil {
		fmt.Printf("Error reading the key file. Error is: %s\n", err)
		return
	}
	key, err = ssh.ParsePrivateKey(buf)
	if err != nil {
		fmt.Printf("Error parsing the private key file. Is this a valid private key? Error is: %s\n", err)
		return
	}
	return
}

func readConfiguration() Configurations {
	if _, err := os.Stat("engine.yml"); errors.Is(err, os.ErrNotExist) {
		// path/to/whatever does not exist
		fmt.Println("The file 'engine.yml' could not be found in the current directory")
		os.Exit(1)
	}

	// Set the file name of the configurations file
	viper.SetConfigName("engine")
	viper.SetConfigType("yml")

	// Set the path to look for the configurations file
	viper.AddConfigPath(".")

	// Read the Configuration
	var configuration Configurations
	if err := viper.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			// Config file not found; ignore error if desired
			fmt.Println("No such config file")
			os.Exit(1)
		} else {
			// Config file was found but another error was produced
			fmt.Printf("Error reading the engine.yml file, %s", err)
			os.Exit(1)
		}

	}

	// Set undefined variables
	viper.SetDefault("logFileName", "")

	err := viper.Unmarshal(&configuration)
	if err != nil {
		fmt.Printf("Unable to decode the engine.yml file, %v", err)
		os.Exit(1)
	}

	return configuration
}

type Configurations struct {
	User           string
	PrivateKeyFile string
	Host           string
	Port           string
	RemoteCommand  string
	LogFileName    string
}

βš’ Configure SSHPROXY.go#

As you can read in the code

func readConfiguration() Configurations {
	if _, err := os.Stat("engine.yml"); errors.Is(err, os.ErrNotExist) {
		// path/to/whatever does not exist
		fmt.Println("The file 'engine.yml' could not be found in the current directory")
		os.Exit(1)
	}

We need to create and engine.yml file with all the properties to connect

user: "chess"
privateKeyFile: "C:\\Users\\remi\\.ssh\\id_rsa"
host: "51.158.***.***"
port: "22"
remoteCommand: "stockfish"

βš’ Test and build the proxy#

Open a terminal in the folder where all the files are on windows and go get requirements

go get ssh-engine

Image 1

Run the program to test it

go run SSHProxy.go

Image 1

Nice πŸ‘ŒπŸ» it’s working ! Build and executable SSHProxy.exe

go build SSHProxy.go

Put-it in C:\ProgramData\ChessBase\Engines

Image 1

β™Ÿ Configure Chessbase 16#

We are now fully ready to use our cloud Stockfish in Chessbase 16 or Fritz 19 ! This solution is really cheaper than other solutions. First thing to do is Create a New Engine.

πŸ€– Create a New Engine#

  • Run Chessbase

  • Open a game

  • Go to Home

  • Create UCI Engine

Image 1

Be careful when configuring your Engine. Give it a name and click on Parameter ! Our instance has 1 Thread so we need to change it.

Image 1

A last thing, we have a tiny tiny instance, so we need to be cool with the RAM

  • Go to the home screen of Chessbase

  • Click on File > Options

  • Choose Engines

  • Browse

  • Select our Cloud Engine

  • Click on Advanced

  • And change Hashtable Size to 1024 MB for our tiny instance

🎭 Use our stockfish on the cloud#

  • Run Chessbase

  • Open a game

  • Go to Home tab

  • Click to Add Kibitzer

  • Choose our Cloud engine

  • Click on Advanced

  • And change Hashtable Size to 1024 MB for our tiny instance

Image 1

Image 1

Have fun !

Image 1

Our tiny instance work at a speed of 1 430 000 Nodes per second.

☠ Remove our instance#

Image 1

πŸš€ Create a big one#

Choose a big one, like this one 64 cores (AMD EPYC) and 256 GB of RAM !

Image 1

Choose our image

Image 1

On my side I get 81 MN/s which is really good for 2.7 € per hour and a 64 cores searching in different ways !

πŸ’Έ Compare#

To conclude, just compare the price by 10 Mega Nodes per seconds for one hour on different services !

β™ŸChessbase cloud engine#

Image 1

Pro

  • Really easy to use, directly integrated in chessbase 16

  • Rent your server to earn ducat

  • Need to buy ducats from Chessbase

Cons

  • It cost 1 Ducat per minute for 100 MN/sec, 60 Ducats = 6 € per hour for 100 MN/sec, so it cost 60 cents per hour

🌫 Chessify.me#

Image 1

Pro

  • It cost 12,34 € per hour for 300 MN/sec, so it cost 41.13 cents per hour

  • Billable per minute

Cons

  • you need to install a plugin

  • and the system is not as flawless as ours

πŸ€– Our bot#

Image 1

Pro

  • With taxes it cost 2,7 € per hour for 81 MN/sec, so it cost 33.33 cents per hour

  • Totally transparent, use your cloud engine as it was on your computer

  • Full control if you need more CPU, just rent a bigger instance

Cons

  • Billable per hour

  • Need to mount a server