# Automatización con Shell Scripts

Hace unos días tuve la necesidad de crear un script para automatizar el proceso
de instalación de un proyecto y me tocó aprender Shell Scripting. Acá las cosas
que aprendía creándolo.

> _Nota_: Esto está basado en un script que tuve que hacer para un proyecto en
> el que estoy trabajando.

## Creando el archivo

Lo primero es crear el archivo, vamos a llamarlo `setup.sh`. Lo podemos colocar
en cualquier carpeta, por ejemplo en `~/` haciendo `touch ~/setup.sh`.

## Condiciones

Luego vamos a ir agregando el código. Vamos a hacer que nuestro script tenga dos
funcionalidades, `install` y `run`, para esto necesitamos una simple condición.

```shell
if [ "$1" = "install" ]
then
  # Install the app
elif [ "$1" = "run" ]
then
  # Run the app
else
  # Throw an error
fi
```

Si vemos estamos usando algo llamado `$1`, eso es una variable, en este caso
hacer referencia al primer argumento que le pasemos a nuestro script al
ejecutarlo. Por ejemplo, si para ejecutarlo hacemos esto:

```bash
bash ~/setup.sh install
```

Entonces `install` es el valor de `$1`.

Lo siguiente que vemos es como hacen las condiciones, la sintaxis es:

```shell
if [ condición ]
then
  # algo
elif [ otra condición]
then
  # otra cosa
else
  # algo más
fi
```

La condición se inicia con `if`. Después del `if` va la primer condición entre
corchetes y en la siguiente línea un `then`, luego de este va el código a
ejecutar si se pasa la condición, para hacer un `else if` se usa la palabra
clave`elif` seguida por la condición entre corchetes y el `then`, igual que un
`if` normal. Para el último caso hacemos `else` sin necesidad de poner un
`then`. Por último ponemos un `fi` que sirve para terminar el bloque de
condiciones.

## Funciones

Vamos a crear algunas funciones para ordenar nuestro código. Creemos una función
que nos permita mostrarle texto al usuario de distintos colores dependiendo de
que ocurrió, vamos a tener entonces `throw`, `warn` y `print` que va a mostrar
texto en rojo, amarillo y verde, respectivamente.

```shell
NC='\\033[0m'

throw()
{
  COLOR='\\033[0;31m'
  >&2 echo -e ${COLOR}$1${NC}
}

print()
{
  COLOR='\\033[0;32m'
  echo -e ${COLOR}$1${NC}
}

warn()
{
  COLOR='\\033[0;33m'
  echo -e ${COLOR}$1${NC}
}
```

La forma de definir una función es simplemente poner el nombre de la función
seguida por `()`. Luego creamos un bloque de código usando llaves y dentro va el
código de la función. En nuestro caso son funciones más o menos similares, en
todas creamos una variable llamada `COLOR` con su valor, `'\\033[0;31m'`
significa rojo, `'\\033[0;32m'` significa verde y `'\\033[0;33m'` significa
amarillo, la variable `NC` que creamos antes de las funciones significa _No
Color_ y la vamos a usar para hacer que el texto deje de tener color.

Lo siguiente que hacemos es hacer un `echo` para mostrar texto en pantalla, le
pasamos el flag `-e` para soportar `\\`, esto es necesario para usar colores, y
le pasamos como valor a mostrar en pantalla `${COLOR}$1${NC}` ¿Qué significa
esto? Primero ponemos el contenido de nuestra variable de color, después
colocamos `$1` que, si recordás, es el primer argumento que le pasamos a nuestro
script, resulta que dentro de una función es el primer argumento que le pasemos
a la función, en nuestro caso el texto, y por último ponemos `${NC}` que que no
tenga color otra vez y no se quede el resto del texto en rojo, verde o amarillo.

Hay un caso diferente al resto que es la función `throw`, antes del `echo` se
agrega `>&2`. Eso significa que el contenido del `echo` lo debe pasar a `stderr`
en vez de `stdout` (lo normal). Esto es para que si algún programa usa nuestro
script va a poder identificar los `throw` como errores correctamente.

Ahora podríamos empezar a usar estas funciones, por ejemplo agreguemos que en
nuestro `else` se muestre un mensaje de error en rojo.

```shell
NC='\\033[0m'

throw()
{
  COLOR='\\033[0;31m'
  >&2 echo -e ${COLOR}$1${NC}
}

print()
{
  COLOR='\\033[0;32m'
  echo -e ${COLOR}$1${NC}
}

warn()
{
  COLOR='\\033[0;33m'
  echo -e ${COLOR}$1${NC}
}

if [ "$1" = "install" ]
then
  # Install the app
elif [ "$1" = "run" ]
then
  # Run the app
else
throw "Invalid command, available values are 'install' or 'run'"
fi
```

Con esto ya vamos avanzando. Ahora hagamos la instalación.

## Función de instalación

Creemos una función llamada `install` que nos va a instalar nuestra aplicación.
El proceso para instalar debe cubrir todo, la idea es que podamos correr este
script en una computadora nueva y nos deje el entorno listo.

Para esto vamos a necesitar realizar varios pasos:

- Instalar Homebrew
- Instalar Git
- Crear una clave SSH
- Agregarla a GitHub
- Instalar Yarn y Node.js
- Clonar desde GitHub nuestro repositorio
- Instalar dependencias

En código esto sería algo así

```shell
confirm_ssh()
{
  read -p "Have you added the SSH Key? [y/N] " CONFIRM_SSH
  if [[ $CONFIRM_SSH -ne "y" || $CONFIRM_SSH -ne "Y" ]]
  then
    warn "Please add it to continue."
    confirm_ssh
  fi
}

install()
{
  /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  print "Homebrew installed"

  brew install git
  print "Git installed"

  warn "Please, enter your GitHub email address and name"
  read -p "Email address: " EMAIL
  read -p "Name: " NAME
  git config --global user.name "$NAME"
  git config --global user.email "$EMAIL"
  echo "Generating SSH Key"
  ssh-keygen -t rsa -b 4096 -C "$EMAIL"
  eval "$(ssh-agent -s)"
  ssh-add -K ~/.ssh/id_rsa
  pbcopy < ~/.ssh/id_rsa.pub
  warn "We have copied your new SSH Key to your clipboard, please add it to your GitHub account going to https://github.com/settings/keys"
  sleep 2.5
  open "https://github.com/settings/keys"
  confirm_ssh
  print "Git & GitHub configured"

  brew install yarn
  print "Yarn & Node.js installed"

  git clone git@github.com:sergiodxa/personal-site.git ~/website
  print "Repository clonned"

  cd ~/website && npm install ; cd -
  print "Dependencies installed"

  print "Project succesfully installed, you could run it with 'bash ~/setup.sh run'"
}
```

Veamos que hacen estas funciones `install` y `confirm_ssh`.

Empecemos por `install`, lo primero que hacemos es instalar Homebrew, al
terminar mostramos un mensaje diciendo que fue instalado. Después usamo Homebrew
para instalar Git, otra vez le avisamos al usuario que fue instalado.

Le advertimos al usuario que vamos a necesitar su nombre y dirección de email y
usamos `read` para pedirle que ingrese estos valores, el flag `-p` nos permite
pasar un texto para mostrarse a la izquierda de donde el usuario va a ingresar
su nombre y email, por último a `read` le pasamos el nombre de la variable donde
queremos guardar lo que el usuario escriba.

Una vez tenemos `$NAME` y `$EMAIL` procedemos a configurar Git para que use
estos valores y generamos una nueva clave SSH cuyo valor copiamos al
portapapeles usando `pbcopy < ~/.ssh/id_rsa.pub`. Le advertimos al usuario que
copiamos su clave SSH al portapapeles y que tiene que agregarla a su cuenta de
GitHub, esperamos dos segundos y medio y abrimos en el navegador la URL donde se
agrega la clave SSH, esta espera es para dar tiempo a leer.

Después de esto llamamos a la función `confirm_ssh`. Esta función muy simple usa
`read` como ya vimos para preguntarle al usuario si ya agregó la clave SSH, si
el usuario no escribe `y` o `Y` entonces se muestra el mensaje la advertencia
"Please add it to continue" y se vuelve a llamar a la función `confirm_shh`
recursivamente, de esta forma mientras el usuario no escriba `y` o `Y` no va a
pasar de `confirm_ssh`.

Luego de todo esto instalamos Yarn usando Homebrew y este además instala
Node.js, lo que nos da un dos por uno. Clonamos el repositorio de nuestro
proyecto a una carpeta ya conocida, también podríamos usar `read` para
preguntarle al usuario a que carpeta clonar el repositorio.

Después de esto ejecutamos `cd ~/website && yarn ; cd -`, esto lo que hace es
movernos a la carpeta donde clonamos nuestro proyecto, ejecutar `yarn` y al
terminar de instalar las dependencias volver a la carpeta donde estábamos
originalmente.

Por último se muestra un mensaje indicando como ejecutar el proyecto.

## Función de ejecución

Ahora que ya instalamos todo, vamos a crear la función para iniciar nuestro
proyecto.

```shell
run()
{
  print "Running project"
  cd ~/website && yarn dev ; cd -
}
```

Eso es todo, mostramos un mensaje diciendo que vamos a correr el proyecto, nos
movemos a la carpeta donde clonamos el repositorio y ejecutamos `yarn dev` para
iniciarlo, este script `dev` lo uso en mi sitio personal para iniciarlo en modo
desarrollo. Al terminar va a volver a la carpeta inicial.

## Poniendo todo junto

Ahora juntemos todo, de forma ordenada y ejecutemos nuestras nuevas funciones en
las condiciones correspondientes.

```shell
NC='\\033[0m'

throw()
{
  COLOR='\\033[0;31m'
  >&2 echo -e ${COLOR}$1${NC}
}

print()
{
  COLOR='\\033[0;32m'
  echo -e ${COLOR}$1${NC}
}

warn()
{
  COLOR='\\033[0;33m'
  echo -e ${COLOR}$1${NC}
}

run()
{
  print "Running project"
  cd ~/website && yarn dev ; cd -
}

confirm_ssh()
{
  read -p "Have you added the SSH Key? [y/N] " CONFIRM_SSH
  if [[ $CONFIRM_SSH -ne "y" || $CONFIRM_SSH -ne "Y" ]]
  then
    warn "Please add it to continue."
    confirm_ssh
  fi
}

install()
{
  /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  print "Homebrew installed"

  brew install git
  print "Git installed"

  warn "Please, enter your GitHub email address and name"
  read -p "Email address: " EMAIL
  read -p "Name: " NAME
  git config --global user.name "$NAME"
  git config --global user.email "$EMAIL"
  echo "Generating SSH Key"
  ssh-keygen -t rsa -b 4096 -C "$EMAIL"
  eval "$(ssh-agent -s)"
  ssh-add -K ~/.ssh/id_rsa
  pbcopy < ~/.ssh/id_rsa.pub
  warn "We have copied your new SSH Key to your clipboard, please add it to your GitHub account going to https://github.com/settings/keys"
  sleep 2.5
  open "https://github.com/settings/keys"
  confirm_ssh
  print "Git & GitHub configured"

  brew install yarn
  print "Yarn & Node.js installed"

  git clone git@github.com:sergiodxa/personal-site.git ~/website
  print "Repository clonned"

  cd ~/website && npm install ; cd -
  print "Dependencies installed"

  print "Project succesfully installed, you could run it with 'bash ~/setup.sh run'"
}

if [ "$1" = "install" ]
then
  install
elif [ "$1" = "run" ]
then
  run
else
  throw "Invalid command, available values are 'install' or 'run'"
fi
```

Eso es todo, si copias el contenido de este archivo a `~/setup.sh` y lo
ejecutamos con `bash ~/setup.sh install` o `bash ~/setup.sh run` vamos a tener
todo listo.

Adicionalmente podríamos llamar la función `run` al final de la instalación para
ya tener todo listo.

## Palabras finales

Al principio usar Shell Scripting fue algo raro ya que nunca había tenido la
necesidad de usarlo antes, pero es bastante simple y divertido y estoy pensando
que otras cosas se podrían automatizar mediante scripts de Shell para hacer
tareas de forma más sencilla.