# thaerious

## MySQL Server on Ubuntu 22.04 (WSL)

### Install and Setup

sudo apt update
sudo apt install mysql-server
sudo service mysql start
sudo service mysql status

sudo mysql
sudo mysql_secure_installation


mysql -u root -p
sql> CREATE DATABASE datebase_name;
sql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, DROP, ALTER, CREATE TEMPORARY TABLES, LOCK TABLES ON database_name.* TO 'MyUsername'@'localhost';
sql> quit


### Access Database

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


## Background

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("google.com");


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.

socket.Shutdown(SocketShutdown.Both);
socket.Close();


### Server

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);
socket.Bind(ipEndPoint);
socket.Listen(port);


### 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();


### Client

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("127.0.0.1");
IPEndPoint ipEndPt = new IPEndPoint(ipAdd, port);
socket.Connect(ipEndPt);


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.

#### Write

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);
this.socket.Send(msg);


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).

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];
int size = BitConverter.ToInt32(bytes, 0);

byte[] bytes = new byte[size];
string data = Encoding.ASCII.GetString(bytes, 0, count);


## Implementation

### JSON

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;


### Layout

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.

#### Server

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()){
connection.WriteString("ack");
connection.Close();
}
}));


#### Connection

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("127.0.0.1", 7000);
server.Stop();


## C# Creating a Class Library

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

## Contents

• 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.  <ItemGroup> <Reference Include="StringLibrary"> <HintPath>./lib/StringLibrary.dll</HintPath> </Reference> </ItemGroup> ## 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. Source https://en.wikipedia.org/wiki/File:Overview_of_the_Common_Language_Infrastructure_2015.svg ### Install the SDK source 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; [System.Serializable] 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. https://github.com/googleapis/google-api-nodejs-client 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. ### List 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(res.data); }); }); } ### Insert 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(res.data); }); }); } ### Delete 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(res.data); }); }); } ### 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, }, }; this.calendar.events.insert(options, (err, res) => { if (err) reject(err); if (res) resolve(res.data); }); }); } ## References 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. \begin{align} \begin{gather*} \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} \end{gather*} \end{align} 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. \begin{align} \begin{gather*} N = \mathcal{B}_r^n\\ \\ m = \lfloor N/s \rfloor\\ \\ \sum_{i=1}^{n}\lfloor ir \rfloor + \sum_{j=1}^m \lfloor js \rfloor = \frac{N^2 + N}{2} \end{gather*} \end{align} ### 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: \begin{align} S(n, r) = \sum_{i=1}^{n}\mathcal{B}_r^i \end{align} The sum of the complementary sequence: \begin{align} S(m, s) = \sum_{j=1}^{m}\mathcal{B}_s^j | s = \frac{r}{r-1} \end{align} 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. \begin{align} \begin{gather*} S(n,r) + S(n,s) = \sum_{i=1}^Ni \\ \downarrow \\ S(n,r) = \sum_{i=1}^Ni - S(n,s) \end{gather*} \end{align} 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. \begin{align} r = \sqrt{2}, s= \frac{r}{r-1} \end{align} Substitute r for \sqrt{2} \begin{align} s= \frac{\sqrt{2}}{\sqrt{2}-1} \end{align} Factor out \sqrt{2} \begin{align} \begin{gather*} s= 1-\frac{\sqrt{2}(\sqrt{2}+1)}{(\sqrt{2}-1)(\sqrt{2}+1)} \\ \downarrow\\ s= \frac{2+\sqrt{2}}{1} \\ \downarrow\\ s=2+\sqrt{2} \end{gather*} \end{align} Whole integers can be brought out of the floor function. \begin{align} \begin{gather*} \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 \\ \end{gather*} \end{align} Express \mathcal{B}_s^i in terms of \mathcal{B}_r^i. \begin{align} \begin{gather*} \text{since } \mathcal{B}_r^i=\lfloor i\sqrt{2}\rfloor \\ \downarrow\\ \mathcal{B}_s^i = \mathcal{B}_r^i + 2i \end{gather*} \end{align} ### Recursive Function Express the complement sum in terms of the original sum. \begin{align} \begin{gather*} S(n,s)= \sum_{i=1}^n \mathcal{B}_s^i\\ \downarrow\\ \sum_{i=1}^n(\mathcal{B}_r^i+2i) \\ \downarrow\\ \sum_{i=1}^n\mathcal{B}_r^i+\sum_{i=1}^n2i \\ \downarrow\\ \sum_{i=1}^n\mathcal{B}_r^i+2\sum_{i=1}^ni \\ \downarrow\\ S(n, r) + 2(\frac{n(n+1)}{2}) \\ \downarrow\\ S(n,s)=S(n, r) + n(n+1) \end{gather*} \end{align} From (5) using (11) \begin{align} \begin{gather*} S(n,r) = \sum_{i=1}^Ni - S(m,s) \\ \downarrow \\ S(n,r) = \sum_{i=1}^Ni - S(m,r) - m(m-1) \end{gather*} \end{align} Expressed as a recursive function. \mathcal{S}(n, r)= \begin{cases} 0 \leq n \leq 1 & 1 \\ n > 1 & \sum_{i=1}^Ni - S(m,r) - m(m-1) \end{cases} ### External References https://en.wikipedia.org/wiki/Beatty_sequence ## WSL and Powershell Ports ## Connecting to WSL from Windows #### In WSL  nc -l 8080
hello world

#### In Powershell

$ncat 127.0.0.1 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 127.0.0.1 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 http://127.0.0.1:8080/ 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 https://nmap.org/.

$ncat -l 8080 hello world #### In WSL $ cat /etc/resolv.conf
nameserver 172.59.192.1

$nc 172.59.192.1 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 cp config.sample.inc.php config.inc.php

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.

## Usage

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.

## Securing

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 https://nodejs.org/en/download/ and download the linux binaries. Alternatively use wget.

cd /opt/node
sudo wget https://nodejs.org/dist/v17.0.1/node-v17.0.1-linux-x64.tar.xz
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