MySQL Server on Ubuntu 22.04 (WSL)

Install and Setup

sudo apt update
sudo apt upgrade
sudo apt install mysql-server
sudo service mysql start
sudo service mysql status
sudo mysql
sql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by 'password';
sudo mysql_secure_installation

Add User

mysql -u root -p
sql> CREATE USER 'MyUsername'@'localhost' IDENTIFIED BY 'password';
sql> CREATE DATABASE datebase_name;
sql> quit

Access Database

mysql -u MyUsername -p
sql> show databases;
sql> use database_name;

C# JSON Client-Server Implementation


A Socket is one end of a two way communication link. The major protocols for socket communication are TCP and UDP. The primary difference is that TCP guarantees data delivery and the order of data packets. UDP does not make such guarantees, but as a consequence it is faster. Developers typically default to TCP.

The C# socket library is found inthe System.Net package.

using System.Net;
using System.Net.Sockets;

IP Endpoint

An IPEndpoint is the pairing of an IPAddress and a port number. You can use DNS lookup to obtain an IP address.

IPHostEntry ipHostInfo = await Dns.GetHostEntryAsync("");
IPAddress ipAddress = ipHostInfo.AddressList[0];
IPEndPoint ipEndPoint = new(ipAddress, 7000);

When creating the server endpoint, you can specify ‘any’ for the ip address.

IPEndPoint ipEndPoint = new(IPAddress.Any, 7000);

Terminate a Socket

Shutdown disables sends and receives on a Socket.

Close will terminate the Socket connection and releases all associated resources.



A server must first listen for and accept connections. Then, when a connection is made, listen for data on a seperate Socket.

Creating a Socket

Bind associates a socket with an endpoint.

Listen causes a connection-oriented (server) Socket to listen for incoming connection attempts.

Socket socket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

Accepting Connections

Accept blocks until an incoming connection attempt is queued then it extracts the first pending request from a queue. It then creates and returns a new Socket. You can call the RemoteEndPoint method of the returned Socket to identify the remote host’s network address and port number.

See AcceptAsync for the asynchronous accept call.

Socket handler = this.listener.Accept();


You create the client side socket in the same manner as the server. The difference being, instead of Bind and Listen, the client uses Connect.

IPAddress ipAdd = IPAddress.Parse("");
IPEndPoint ipEndPt = new IPEndPoint(ipAdd, port);
Socket socket = new Socket(ipAdd.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

Reading & Writing

Reading and writing date on a socket is the same for both the server and client side socket. Once the actual connection is made, the difference between the two is arbitrary. Both the read and operations require a byte array to act as a buffer. So your data will need to be converted to and from an array of bytes.


The Send method is used to write to a socket. While there are a number of different method flavours, the most common is to send the entire contents of a byte array.

byte[] msg = Encoding.ASCII.GetBytes(aString);

If you are using a connection-oriented protocol, Send will block until the requested number of bytes are sent, unless a time-out was set by using Socket.SendTimeout. If the time-out value was exceeded, the Send call will throw a SocketException

If the receiving socket (server) has not been started a SocketException will be thrown. If the server has been started but Accept has not been called reading and writing will hang. This can be remedied by setting a timeout (in ms).

Set Read/Write Timeouts

socket.ReceiveTimeout = 1000;
scoket.SentTimeout = 1000;


If the remote host shuts down the Socket connection with the Shutdown method, and all available data has been received, the Receive method will complete immediately and return zero bytes. This allows you to detect a clean shutdown.

There are many flavours of the Receive method, but we will only concern ourselves with two of them. The first reads all bytes from a socket. It returns the number of bytes read. You will need to provide your own EOF indicator or wait until 0 bytes are read which means the socket has finished writing and closed. This is useful if you are only connecting the sockets for a single read-write operation.

byte[] bytes = new byte[BUFFER_SIZE];

The second, and the one we will be using, is to read a specific number of bytes from the socket.

int count = socket.Receive(bytes, nextReadSize, SocketFlags.None);

We will use this to first read the size of the data, then read the body of the data.

// read size
byte[] bytes = new byte[INT_BUFFER_SIZE];
socket.Receive(bytes, INT_BUFFER_SIZE, SocketFlags.None);
int size = BitConverter.ToInt32(bytes, 0);

// read message
byte[] bytes = new byte[size];
socket.Receive(bytes, size, SocketFlags.None)
string data = Encoding.ASCII.GetString(bytes, 0, count);



The json encoding and decoding will be handled by the Newtonsoft Json.Net library.

dotnet add package Newtonsoft.Json --version 13.0.1

The include statements for this library are.

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;


There are 3 source files in the project:

  • Server.cs: Manages new client connecting to the server and emits Connection objects.
  • Connection.cs: Wraps a socket providing the read-write capabilities.
  • Client.cs: A connection object that connects to its own socket.


Most of the details for this class are provided in the above background section. The main detail here is the way new connections are managed. I have put the loop inside a IEnumerable method. This should be looped to hand off new connection objects. The following example does this in a new thread.

Thread thread = new Thread(new ThreadStart(()=>{    
    foreach(Connection connection in server.Connections()){


The connection object reads and writes JSON objects or Strings in two parts. First it writes four bytes representing the integer size of the data. Next it writes the data it’s self.

Client client = new Client().Connect("", 7000);
client.socket.ReceiveTimeout = 3000;
Console.WriteLine("> " + client.ReadString());

C# Creating a Class Library

This instruction post is an abbreviation of Microsoft tutorial found HERE.


  • Create the Library
  • Using the Library
  • Unit Testing
  • Including the .dss Directly
  • Setup

    Create an SLN (project solution file)

    An SLN is a Visual Studio solution file that keeps information about the organization of projects in a file.

    dotnet new sln

    Create the class library project

    The -o (–output) flag specifies the directory to place the project. It will create the directory.

    dotnet new classlib -o StringLibrary

    Add the project to the solution

    dotnet sln add StringLibrary/StringLibrary.csproj

    Add code to the library

    Add the following code to the StringLibrary/Class1.cs file

    namespace UtilityLibraries;
    public static class StringLibrary
        public static bool StartsWithUpper(this string? str)
            if (string.IsNullOrWhiteSpace(str))
                return false;
            char ch = str[0];
            return char.IsUpper(ch);

    Build the solution

    dotnet build

    Using the Library

    Create a console app

    dotnet new console -o ShowCase

    Add the console app to the solution

    dotnet sln add ShowCase/ShowCase.csproj

    Modify the ShowCase/Program.cs file

    using UtilityLibraries;
    class Program {
        static void Main(string[] args) {
            foreach(string s in args){
                var uc = s.StartsWithUpper();
                Console.WriteLine($"String {s} starts with uppser case {uc}");

    Add a ‘StringLibrary’ reference to ‘ShowCase’

    dotnet add ShowCase/ShowCase.csproj reference StringLibrary/StringLibrary.csproj

    Run ‘ShowCase’

    dotnet run --project ShowCase/ShowCase.csproj AB ab

    Unit Testing

    Create the unit test project and add a reference

    dotnet new mstest -o StringLibraryTest
    dotnet add StringLibraryTest/StringLibraryTest.csproj reference StringLibrary/StringLibrary.csproj

    Run the unit test

    dotnet test StringLibraryTest/StringLibraryTest.csproj

    Including the .dll directly

    Add the following to your project’s .csproj file. Change the StringLibrary project name as necessary.

        <Reference Include="StringLibrary">

    Installing C# on Ubuntu Linux

  • Background
  • Install the SDK
  • Install the Runtime
  • Create an Application
  • External Reading
  • Background

    The .NET framework was main implementation of the open specification, Common Language Infrastructure (CLI) which is platform independent target for high level languages.

    The Common Language Runtime (CLR) is synonymous to Java’s JRE. Compilers target the CLR and emit managed code. This code permits cross-language integration and exception handling.

    The .NET framework was cross-platform by design only, not on implementation. Until about 2012, the .NET Runtime (CLR) was for Windows, and the .Net Framework was Windows-centric. There was limited cross-platform support from the Mono project, being unsupported, it lagged behind the official releases.

    In June of 2016, Microsoft released .NET Core. This was a port of the CLR to Linux with a rewriting of the framework from the ground up. Version 2.0 was released in 2017, and version 3.0 in 2019. This numbering system conflicted with the Windows proprietary .NET framework numbering. As such, version 4 was skipped and .NET 5.0 was released in 2020 followed by .NET 6.0 in 2021.

    As of version 5.0, the old .NET Framework was given “legacy support” status, and .NET Core was renamed to just .NET. This is why the confusion really gets bad.

    In summary, .NET Framework had versions 1.0, 1.1, 2.0, 3.0, 3.5, and 4.0-4.8. It will never have a 5.0 version. While .NET Core had versions 1.0, 2.0, 2.1, 3.0, and 3.1. Then it was renamed, to just .NET 5.

    Future versions of .NET will provide LTS for even-numbered versions. New versions release every November.

    This image is licensed under the Creative Commons Attribution-Share Alike 4.0 International license.


    Install the SDK


    The .NET SDK allows you to develop apps with .NET. If you install the .NET SDK, you don’t need to install the runtime.

    sudo apt-get update
    sudo apt-get install -y dotnet6

    Install the runtime (optional)

    The ASP.NET Core Runtime allows you to run apps that were made with .NET that didn’t provide the runtime.

    sudo apt-get update
    sudo apt-get install -y aspnetcore-runtime-6.0

    You can install the .NET Runtime, which doesn’t include ASP.NET Core.

    sudo apt-get update
    sudo apt-get install -y dotnet-runtime-6.0

    Create an application

    Create a new .NET application called “HelloWorld”

    1. Open your terminal
    2. Create and enter a new directory called “HelloWorld”
    3. Use the “dotnet” command to create a new application.
    dotnet new console --framework net6.0

    In your IDE, replace the contents of Program.cs with the following code:

    namespace HelloWorld {
        class Program {
            static void Main(string[] args) {
                Console.WriteLine("Hello World!");

    Alternatively you can create a new console app with the directory structure by including the output flag (-o).

    dotnet new console -o HelloWorld

    External Reading

    Create a Class Library

    JSON Serialization in Unity

    JSONUtility (Unity Class)

    Unity has it’s own JSON serialization package [ref] which is very close in usage to the native C# serialization. It works with public field, or private fields marked with the ‘SerializeField’ attribute. It does not work with class properties, or fields marked ‘readonly’. To otherwise skip a field mark it with the ‘NonSerialized’ attribute.

    // Will serialize
    public string name;
    [SerializeField] private string value;
    // Will not serialize
    public readonly string name;
    public string name {get; set;}
    [NonSerialized] int age;

    The serialization method does not require type information, it will simply create json text based on the public field values. This means you do not need to deserialize into the same object you serialized from. The class it’s self must be marked with the ‘Serializable’ attribute.

    using UnityEngine;
    public class PlayerState : MonoBehaviour{
        public string name;
        public int age;
        public string SaveToString(){
            return JsonUtility.ToJson(this);
        // Given:
        // name = "Dr Charles"
        // age = 33
        // SaveToString returns:
        // {"name":"Dr Charles","age":33}

    Unlike serialization, deserialization requires a type. Any values found in the JSON text that are not fields in the provided type will be ignored.

    PlayerState playerState = JsonUtility.FromJson<PlayerState>(string);

    JSONEncoder (Helper Class)

    In order to automatically decode a json object to the correct type I have included the type name in the JSON text object. The emitted JSON has two root fields: type & instance.

    using UnityEngine;
    using System;
    public class JSONDetails<T>{
        public T instance;
        public string type;
        public JSONDetails(T instance) {
            this.instance = instance;
            this.type = instance.GetType().ToString();
    public class JSONEncoder {
        public string type = "";
        public JSONEncoder() { }
        public static string Serialize<T>(T anObject) {
            return JsonUtility.ToJson(new JSONDetails<T>(anObject), true);
        public static object Deserialize(string json) {
            JSONEncoder wrapper = JsonUtility.FromJson<JSONEncoder>(json);
            Type g = Type.GetType(wrapper.type);
            Type t = typeof(JSONDetails<>).MakeGenericType(new Type[] { g });
            var hydrated = JsonUtility.FromJson(json, t);   
            return t.GetField("instance").GetValue(hydrated);

    At times serialization may require type information to ensure the correct type is serialized. Deserialization returns an object which can be cast as necessary. Typically this should used to detect the presence of an interface and cast accordingly.

    public void ReceiveEvent(string json, string source) {
        var isEvent = JSONEncoder.Deserialize(json);
        if (isEvent is IModelEvent<M> != false) {
            IModelEvent<M> modelEvent = (IModelEvent<M>)isEvent;
            this.model = modelEvent.UpdateModel(this.model);
        if (isEvent is IModelEvent<N> != false) {
            this.Broadcast(isEvent, source);

    Google API Service Accounts With NodeJS

    For a recent project I needed update to a single shared Google calendar. I decided to go with a Google service account to manage this. I’m going to post here how I did it.

    What is a Service Account

    A service account is used by an application as opposed to a real living person. It allows your application to make authorized API calls directly. A service account is identified by its email address, which is unique to the account.

    • Service accounts can not be logged into with browsers.
    • Authorization is done with a public-private RSA key.
    • Service accounts are not part of your Google domain. They do not share global resources like a live user.

    Create a Service Account

    At the very top choose the project drop down. This will open up the project window where you can select an ongoing project or create a new one. Each service account is located in a project. After you create a service account, you cannot move it to a different project.

    In the Google Developers console (link) under the leftmost APIs & Services menu you will find the credentials option. At the top of the screen click the Create Credentials option. You will have a choice as to which type of credential you would like to create. Select service account. Most, though not all, API’s should work with service accounts.

    Service Account Authentication

    There are a number of ways to authenticate an application using a service account (link). If you are deployed on Google cloud you can use an attached service account. You can use a Workload Identity with Kubernetes pods. There is also the Workloads Identity Federation that works with other service providers. Here we will be using a service account key which allows us to deploy on our own cloud provider.

    Create a Public-Private key.

    Click on your service account name to bring up the management menu. There is a number of menus near the top of the screen; select keys. Press the add keys dropdown menu and select create new key. Use json unless you have a reason to do otherwise. Remember not to add this key to your git repository or put it anywhere the public can see it.

    Create and Share a Calendar

    Create a calendar in Google Calendars and look at it’s settings (3 little dots next to the name). Find “share the calendar with specific people” and add the email address of your service account. Give your service account the role “make changes to events”. Since the service account isn’t a real user, you don’t get a confirmation email, and the calendar won’t immediately show up using the Google Calendar API list method.

    Find the calendar id under the integrate calendar heading on the settings page. It should look like an email address. Save this, we will be using it in a bit.

    Getting Started

    Install the google api package.

    npm install googleapis

    View the Google APIs documentation on github.

    In the Google console, you will find “APIs and Services > Enabled APIs and Services” add the Calendar API to your project.

    Code Pre-requisites

    The following are three preliminary steps we need to preform before accessing the calendar API. After this we can start accessing the API methods. I will implement them in a class structure just to keep things clean.

    • Import the google api library
    • Create a new authentication object.
    • Obtain an instance of the calendar interface.
    import { google } from "googleapis";
    constructor() {
        const auth = new google.auth.GoogleAuth({
            keyFilename: GoogleCalendar.KEY_FILENAME,
            scopes: GoogleCalendar.SCOPES,
        this.calendar = google.calendar({
            version: "v3",
            auth: auth,

    API Methods

    Now that the environment is setup we will now go through a few of the available API calls. For a full list of see the Calendar API Documentation.


    The list method allows you to view available calendars. It is found in the calendar.calendarList implementation. This is also where you find the create, get, and delete calendar methods. This is one of the simpler API calls.

    list() {
        return new Promise((resolve, reject) => {
            this.calendar.calendarList.list((err, res) => {
                if (err) reject(err);
                if (res) resolve(;


    Newly added calendars won’t show up in the list until you have inserted them. We use the calendar identifier we saved above. In this case we also pass in an options object, containing a resource object, which in turn has the calendar id we want to add.

    insert(id) {
        return new Promise((resolve, reject) => {
            const options = {
                resource: {
                    id: id,
            this.calendar.calendarList.insert(options, (err, res) => {
                if (err) reject(err);
                if (res) resolve(;


    When you look at the API documentation for delete you notice that calendar id is in the url. This implies that we use a calendarId field in our options object. The resource field, as used above, goes in the body. This is something to watch out for when interpreting HTTP calls to NodeJS API calls.

    remove(id) {
        return new Promise((resolve, reject) => {
            const options = {
                calendarId: id,
            this.calendar.calendarList.delete(options, (err, res) => {
                if (err) reject(err);
                if (res) resolve(;

    Add Event

    To add an event we access the events property of the calendar API. In this case we include both the calendar id in the URL as well as body properties.

    addEvent(id, start, end, summary){
        return new Promise((resolve, reject) => {
            const options = {
                calendarId: id,
                resource: {
                    start: {
                        date: "2022-05-21",
                    end: {
                        date: "2022-05-23",
                    summary: summary,
  , (err, res) => {
                if (err) reject(err);
                if (res) resolve(;


    Google Event Object

    Google Calendar Create Event

    Summation of a Partial Beatty Sequence

     \sum_{i=1}^{n}\mathcal{B}_{r\in\mathbb{Z}}^i = \sum_{i=1}^{n}\lfloor ir \rfloor

    A partial Beatty sequence is the sum of all the Integer portions of positive multiples between 1 and n of an Irrational number.

    Raleigh-Beatty Theorem

    Rayleigh’s theorem, states that a sequence, consisting of all the positive integers that are not in the sequence, is itself a Beatty sequence generated by a different irrational number.

    For any Beatty \mathcal{B}_r^n sequence where r > 1 there exists a complementary sequence \mathcal{B}_s^n where s>1 is defined as \frac{1}{r}+\frac{1}{s}=1. The union of both sequences is the set of all Integers from 1 to n.

    We can find an equation for s in terms of r.

    \frac{1}{s} + \frac{1}{r} = 1 \\ \downarrow \\
    \frac{1}{s} = 1 - \frac{1}{r} \\ \downarrow \\
    \frac{1}{s} = \frac{r}{r} - \frac{1}{r} \\ \downarrow \\
    \frac{1}{s} = \frac{r-1}{r} \\ \downarrow \\
    s = \frac{r}{r-1}

    The complement consists of all integers not in the sequence. For any sequence dividing the value by the equation will give you the index of the value that is less than N = \mathcal{B}_r^n. This allows us to find the last index of the complement because the last value of the complement is lower than that of the sequence. \lfloor \frac{\mathcal{B}_r^i}{s} \rfloor.

    \lfloor ir \rfloor = \mathcal{B}_r^i \rightarrow \lfloor \frac{\mathcal{B}_r^i}{r} \rfloor = i-1

    \mathcal{B}_r^n is the largest term in the sequence, dividing that by ‘r’ would give you the index of the sequence .

    For clarity we will express the largest value of the sequence as N = \mathcal{B}_r^i , and the upper index of the complement as m = \lfloor N/s \rfloor.

    N = \mathcal{B}_r^n\\ \\
    m = \lfloor N/s \rfloor\\ \\
    \sum_{i=1}^{n}\lfloor ir \rfloor + 
    \lfloor js \rfloor = 
    \frac{N^2 + N}{2}

    Building up to the Solution

    Since the union of any Beatty sequence and its complement will cover all the positive integers up to n. This leads to the realization that any Integer between 1 and n can be expressed at either \lfloor ir \rfloor or \lfloor j(r+1) \rfloor.

    The sum of the sequence in question:

    S(n, r) = \sum_{i=1}^{n}\mathcal{B}_r^i

    The sum of the complementary sequence:

    S(m, s) = \sum_{j=1}^{m}\mathcal{B}_s^j | s = \frac{r}{r-1}

    By way of the Raleigh-Beatty Theorem, the two sums are equivalent to the sum of the Integers from 1 to the highest Beatty sequence value (\mathcal{B}_r^n). This can be rearrange to say; the sum of a Beaty sequence is equal to the difference sum of all the encompassed Integers and the sequence’s complement.

    S(n,r) + S(n,s) =
    \sum_{i=1}^Ni \\
    \downarrow \\
    S(n,r) = 
    \sum_{i=1}^Ni - 

    At this point we want to express the sum of the complement in terms of sum the original sequence. We will do this by extracting \mathcal{B}_r^i from \mathcal{B}_i^s. So we need to define a value for s, we will use \sqrt{2}.

    Expressing the Complement in Terms of the Original

    Starting with r and s (1), we assign a value to r.

    r = \sqrt{2}, s= \frac{r}{r-1}

    Substitute r for \sqrt{2}

    s= \frac{\sqrt{2}}{\sqrt{2}-1}

    Factor out \sqrt{2}

    s= 1-\frac{\sqrt{2}(\sqrt{2}+1)}{(\sqrt{2}-1)(\sqrt{2}+1)} \\
    s= \frac{2+\sqrt{2}}{1} \\

    Whole integers can be brought out of the floor function.

    \mathcal{B}_s^i = \lfloor is\rfloor=\lfloor i(2+\sqrt{2})\rfloor \\
    \downarrow \\
    \lfloor 2i+i\sqrt{2}\rfloor \\
    \downarrow \\
    2i+\lfloor i\sqrt{2}\rfloor \\

    Express \mathcal{B}_s^i in terms of \mathcal{B}_r^i.

    \text{since } \mathcal{B}_r^i=\lfloor i\sqrt{2}\rfloor \\ 
    \mathcal{B}_s^i = \mathcal{B}_r^i + 2i

    Recursive Function

    Express the complement sum in terms of the original sum.

    \sum_{i=1}^n \mathcal{B}_s^i\\
    \sum_{i=1}^n(\mathcal{B}_r^i+2i) \\
    \sum_{i=1}^n\mathcal{B}_r^i+\sum_{i=1}^n2i \\
    \sum_{i=1}^n\mathcal{B}_r^i+2\sum_{i=1}^ni \\
    S(n, r) + 2(\frac{n(n+1)}{2}) \\
    S(n,s)=S(n, r) + n(n+1)

    From (5) using (11)

    S(n,r) =  
    \sum_{i=1}^Ni -
    S(m,s) \\
    \downarrow \\
    S(n,r) = 
    \sum_{i=1}^Ni - S(m,r) - m(m-1)

    Expressed as a recursive function.

    \mathcal{S}(n, r)= 
    0 \leq n \leq 1 & 1 \\
    n > 1 & 
    \sum_{i=1}^Ni - S(m,r) - m(m-1)

    External References

    WSL and Powershell Ports

    Connecting to WSL from Windows

    In WSL

    $ nc -l 8080
    hello world

    In Powershell

    $ ncat 8080
    $ hello world

    In WSL the nc -l 8080 command listens on port 8080 for any incoming data, then prints it to the screen. In Powershell ncat 8080 sends everything you type to port 8080. You should see what you type both in the Powershell terminal and in the WSL terminal. Alternately you can open your browser and enter into your URL bar, and the WSL terminal will print out the HTML request.

    Connecting to Windows from WSL

    Install windows version of ncat [direct link] from

    In Powershell

    $ ncat -l 8080
    hello world

    In WSL

    $ cat /etc/resolv.conf
    $ nc 8080
    $ hello world



    The phpMyAdmin tool is an SQL database manager written in PHP. It resides in the web root on your server, making it easily accessed from a web browser.

    There are a number important security considerations when using software like phpMyAdmin, since it:

    • Communicates directly with your MySQL installation.
    • Handles authentication using MySQL credentials.
    • Executes and returns results for arbitrary SQL queries.

    The phpMyAdmin tool is available for a number of different operating systems. This article will focus solely on the Linux installation.

    Manual Installation

    While there are a number of ways to install phpMyAdmin, there is an easy quick install method. This involves downloading and extracting it directly to your web root directory. While you will need at least basic bash terminal knowledge, it is relatively trivial to set up. However, you will require sudo privileges or access to the web-user-account.

    Following are the condensed steps for the quick install found in the phpMyAdmin documentation.

    cd /www
    sudo wget -O phpMyAdmin.tar.gz
    sudo tar -xvf phpMyAdmin.tar.gz
    sudo chown -R wp-user:wp-user phpMyAdmin-5.1.1-english/
    sudo cp
    1. Change directory to your web root.
    2. Download the tar file. You can choose from a number of options here. We rename it here with the -O flag
    3. Unpack the tar, it will be placed in it’s own directory.
    4. Give ownership of the directory to the web user account.
    5. Create the configuration file by copying the sample configuration file.


    When a user logs into phpMyAdmin the username and password are sent directly to the SQL database. It is just an interface to the database, and any operation it does can be done on the command line. As such, all users must be valid database users.


    You should use cookie mode authentication so that your user/password pair are not kept in the configuration file. The variable may be set in the example config file as:

    $cfg['Servers'][$i]['auth_type'] = 'cookie';

    You will need to also add a 'blowfish secret' value to the config file.

    $cfg['blowfish_secret'] = 'anyrandomtextyouwant';

    Deny access to the temp, libraries, and templates subdirectories. Put the following in the server directive of your nginx enabled sites file.

    location /phpmyadmin/libraries { deny all; }
    location /phpmyadmin/templates { deny all; }
    location /phpmyadmin/tmp { deny all; }


    Installing NodeJS Manually on Linux

    If you are installing NodeJS on a machine that you have admin privileges to, you can install NodeJS by downloading the binaries directly. This allows you to bypass any special setup that a version manager may require. Browse to and download the linux binaries. Alternatively use wget.

    cd /opt/node
    sudo wget
    sudo tar -xvf node-v17.0.1-linux-x64.tar.xz
    sudo mv node-v17.0.1-linux-x64 17.0.1

    You would of course replace the version numbers with the version you are interested in. Also, the move (mv) command is not required, I just prefer to keep the version numbers simple. If this is your first NodeJS install you will need to add sudo mkdir /opt/node.

    After you have installed the binary files into the /opt directory you will want to create links so that you can execute them from anywhere.

    sudo ln -s /opt/node/17.0.1/bin/node node
    sudo ln -s /opt/node/17.0.1/bin/npm npm
    sudo ln -s /opt/node/17.0.1/bin/npx npx