Tag: GNU-Linux

Comprobar permisos de Administrador en una ejecución (estilo 🐍 Python)

Habitualmente nos encontramos con la situación en la que debemos ejecutar ciertos programas o flujos de ejecución y necesitamos permisos de Administrador. Estas situaciones se nos presentan cuando tenemos que manejar carpetas y ficheros en acciones que impliquen lectura, escritura o ejecución en directorios protegidos del sistema. Para ello se requiere una elevación de permisos que deberemos comprobar si existe antes de lanzar nuestra ejecución.

Aprovechando que seguramente tengamos que hacer esto varias veces, creo que la mejor opción de hacerlo es con el mejor lenguaje pegamento que existe hasta la fecha, que no es ni más ni menos que Python. Por ello la solución final que vamos a plantear es excelente tanto para administradores de sistemas como para desarrolladores, siendo además multiplataforma GNU/Linux y Windows.

Tenemos que tener en cuenta que GNU/Linux y Windows no funcionan igual para la gestión de permisos. En Windows preguntaremos desde la API disponible si el usuario es administrador, mientras que en GNU/Linux preguntaremos por si el usuario tiene permisos root.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0

    print ("Admin privileges: {}".format(is_admin))
    return is_admin

Para mirar los permisos en Windows usaremos la librería ctypes y para los permisos en GNU/Linux usaremos la librería os. Ambas están incluida en Python. Por una razón arbitraría se ha elegido comprobar primero GNU/Linux y después Windows.

Cuando se comprueba en GNU/Linux invocamos a la función os.getuid que nos devolverá 0 en el caso de ser root. Si llamamos a la función desde Windows con Python nos elevará una excepción del tipo AttributeError, que aprovecharemos a capturar para hacer la comprobación en Windows invocando a la función ctypes.windll.shell32.IsUserAnAdmin. En ambos casos guardaremos el booleano de la evaluación y lo devolveremos al final de la función.

Hasta aquí sencillo, pero esto es código Python y es necesario tener el interprete de Python, con el que obliga al sistema a tener instalado el intérprete de Python. Para solucionarlo, deberíamos convertir nuestro código en un programa añadiendo un punto de entrada y devolviendo un código de retorno tras la ejecución.

import ctypes, os
from sys import exit


def is_admin():
    is_admin = False
    is_win = False
    try:
        is_admin = os.getuid() == 0
    except AttributeError:
        is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0
        is_win = True

    print ("Admin privileges: {}".format(is_admin))
    return is_admin, is_win

Antes modificaremos un poco la función is_admin()añadiendo un valor is_win que usaremos para saber que estamos comprobando en GNU/Linux o Windows los permisos de administrador, devolviendo el valor como un segundo valor en el retorno de la función.

Recordad que Python es posible recoger múltiples valores de retorno en una variable como una tupla, en variables separadas o incluso tirar los valores que no nos interesen.

def my_function():
    a = 2
    b = 3
    return a, b

a = my_function()
print(a)  # out: (2, 3)

a, b = my_function()
print(a)  # out: 2
print(b)  # out: 3

a, _ = my_function()  #  b es ignorado
print(a)  # out: 2

Además añadimos un punto de entrada debajo de la función is_admin().

if __name__ == "__main__":
    is_admin, is_win = is_admin()
    # Converting boolean to integer 
    ret = int(is_admin == False)
    if is_win:
        exit(ret)
    else:
        exit(ret * -1)

Comprobar si __name__ es __main__, es una excelente forma de hacer que cada unidad de fichero Python sea ejecutable si se quiere. El nombre de __main__ sólo es dado al fichero Python que es dado al intérprete para ser ejecutado y en caso de no serlo, siempre puede ser importado en otro fichero Python, sin que el punto de entrada afecte.

Como vemos, ahora estamos recogiendo en el retorno is_admin e is_win. Necesitamos el segundo valor para decidir el valor de retorno, ya que los códigos de error en Windows son enteros positivos y en GNU/Linux son enteros negativos. El único punto en común es que devolver un 0 es señal de que la ejecución fue correcta. Sabiendo esto debemos adaptar el retorno de los errores dependiendo el sistema.

💾 Binarizando nuestra aplicación Python

Para convertirlo en un ejecutable usaremos PyInstaller, el cual podéis instalar con un simple pip install pyinstaller. Para binarizar nuestra aplicación en un ejecutable autocontenido con el interprete de Python ejecutaremos:

pyinstaller -F is_admin.py

De esta forma resultará, mucho mas sencilla la distribución de nuestra aplicación.

👔 Puesta en Producción

Ahora vamos a plantear los dos casos de uso para Windows y GNU/Linux. Como aclaración, decir que los ejemplos expuestos se pueden usar tanto con la versión binarizada o con la de código invocando al intérprete de Python.

Windows

@echo off

cd
cd /D "%~dp0"
cd

#REM python is_admin.py
is_admin.exe

if errorlevel 1 (
   echo Exit Code is %errorlevel%
   echo.
   echo Admin privileges required
   echo Run it again as Administrator
   echo.
   pause
   exit /b %errorlevel%
)

#REM [your_executable].exe

pause

👀 Puedes descargar la versión binarizada de is_admin.exe

GNU/Linux

#!/bin/bash

pwd
cd `dirname $0`
SCRIPTDIR=`pwd`
pwd

#python is_admin.py
is_admin


if [ $? -eq 0 ]
then
  # [your_executable]
else
  echo Exit Code is $?
  echo.
  echo Admin privileges required
  echo Run it again as Administrator
  echo.
  read -rsp $'Press any key to continue...\n' -n 1 key
  exit $?
fi

read -rsp $'Press any key to continue...\n' -n 1 key

Si no estás tan familiarizado con el Bash te invito a que visites el proyecto ExplainShell para que puedas obtener la explicación de los diferentes comandos y argumentos.

🥅 Conclusiones

La solución presentada, sin ser seguramente perfecta, asegura tanto a administradores de sistemas como desarrolladores, comprobar si existen los permisos de administrador necesarios de manera sencilla; pudiendo hacerlo con una versión binarizada autocontenida que incluya el intérpretete Python, ejecutándolo usando el intérprete de Python del sistema gracias a ser todo dependencias internas, o llamando directamente a la función is_admin() si se quiere integrar en un desarrollo.

El código completo puedes encontrarlo en el siguiente gist de GitHub:

Espero que esta forma de trabajar te resulte útil y puedas extrapolarla y usar para otros casos Python como tu lenguaje pegamento generando código y aplicaciones altamente reutilizables.

Configuración de entorno Python2 y Python3 para uso simultáneo en Debian

Si eres desarrollador de Python o usuario de su ecosistema, te habrás dado cuenta de la brecha existente entre ambos, significando una ruptura de compatibilidad el salto de Python2 a Python3.

Pese a que Python2 tiene fecha de caducidad todavía existen aplicaciones y librerías que bien porque no han sido migradas a Python3, o porque tienen dependencias con librerías que aún no se han migrado a Python3, siguen funcionado exclusivamente sobre Python2. Por eso se hace necesario mantener los dos interpretes de Python con su respectivo respectivo gestor de paquetes pip.

Quizás a la hora de abordar esta situación, la plataforma de Windows tenga la aproximación más razonable e interesante. En la instalación de Windows pueden convivir ambos intérpretes, siendo Python3 el que prevalece por defecto si ambos existen, quedando vinculadas las palabras python y pip al intérprete y gestor de paquetes de Python3 respectivamente.

Si queremos diferenciar la ejecución entre ambos, tenemos py2 para referirnos al intérprete de Python2 y py3 para el de Python3. De la misma manera, si queremos referirnos al gestor de paquetes de Python2 lo haremos con pip2 y para referirnos al de Python3 lo haremos con pip3.

Esta estupenda idea no se da en sistemas Debian y derivados como Ubuntu. A pesar de todo Debian 8 (Jessie) trae Python 3.4 y Debian 9 (Stretch) Python 3.5, en ambos casos junto con Python 2.7, siendo Python 2.7 el intérprete Python por defecto. Para tener ambos entornos vamos a realizar las una serie de configuraciones.

Para ellos vamos a crear los siguientes alias:

alias py2='/usr/bin/python2.7'
alias py3='/usr/bin/python3.4' # Para Debian 8 Jessie
alias py3='/usr/bin/python3.5' # Para Debian 9 Stretch

Con esto si ejecutamos py2 o py3 tendremos configurado ambos intérpretes para usarlos según nos convenga.

Ahora tenemos que instalar los gestores de paquetes pip para Python2 y Python3, para ello ejecutamos los siguientes comandos:

apt-get install python-pip  # python2
apt-get install python3-pip  # python3

Ahora generamos los alias:

alias pip2='/usr/bin/pip' # python2
alias pip3='/usr/bin/pip3' # python3

Si deseamos listar los alias registrar podemos hacerlo escribiendo "alias" o "alias -p". Si deseamos eliminar un alias sería "unalias nombre_del_alias" o "unalias -a" si queremos borrar todos.

Hasta aquí ya tenemos nuestros dos entornos para funcionar simultáneamente, pero cuidado:

⚠️ Los alias se pierden al cerrar la sesión

Para arreglarlo sólo tenemos que poner los alias que queramos tener en la sesión en el archivo ~/.bash_aliases:

alias py2='/usr/bin/python2.7'
alias py3='/usr/bin/python3.5' # Para Debian 8 Jessie
alias py3='/usr/bin/python3.5' # Para Debian 9 Stretch
alias pip2='/usr/bin/pip'
alias pip3='/usr/bin/pip3'

Para que se carguen al iniciar sesión tenemos que asegurarnos que en el archivo ~/.bashrc existan la siguientes líneas:

if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi

En caso de no encontrarlas, puedes añadirlas al final del fichero. En el caso de estar usando el usuario root es muy probable que tengas que añadir las líneas.

Para abrir y editar los archivos puedes usar nano:

nano ~/.bashrc
nano ~/.bash_aliases

Llegados a este punto ya tenemos los entornos de Python2 y Python3 completamente configurados pudiendo elegir con qué ejecutar nuestras aplicaciones y pudiendo lanzar ejecuciones con ambos intérpretes de manera simultánea. 😉

 

Hidden Console Start (HCS) – Esconde la consola y lanza procesos/aplicaciones en segundo plano

Dicen que no te das cuenta de algo hasta que lo pierdes... Esto es lo que me ha pasado cuando recientemente mi orientación profesional ha cambiado y he tenido  que trabajar con sistemas Windows en vez de GNU-Linux.

Acostumbrado a tener corriendo servicios en distintas máquinas con GNU-Linux acabas echando de menos su shell y la bestialidad de herramientas que hay. Vale sí lo reconozco, al final trabajaba en una máquina Windows, pero porque sigo pensando que las herramientas de desarrollo siguen siendo mejor que las que hay disponibles en GNU-Linux. Hay que reconocer que un Windows lo uso sólo para desarrollar (eso sí multiplataforma) y para jugar, para lo demás GNU-Linux sin duda!!!

Una de las cosas que he echado en falta recientemente es la posibilidad de usar el operador & de background tan potente en GNU-Linux. Este operador permite lanzar un proceso que se sigue ejecutando sin bloquear la shell y si la cerramos, éste sigue en segundo plano. Pues esto tan chulo, no puedes hacerlo en un Windows, porque su comando start no lo permite y la opción /B del comando no impide que al cerrarse la consola de comandos el proceso que has lanzado muera. A no ser que el proceso que has lazando cree su propio hilo no dependiente del proceso de la consola. Y tampoco es posible lanzar un proceso de consola de comandos sin consola, a no ser que esté programado explícitamente que la consola se oculte o no aparezca. Windows en este aspecto se cubre mucho, porque la única forma de crear procesos en background es construyendo servicios de Windows que tienen sus propias reglas a cumplir y por consiguiente hace falta desarrollo específico para que algo corra en segundo plano.

Visto el panorama y teniendo cierto conocimiento e idea, me decidí a generar una herramienta similar que funcionase en Windows como el operador &, o por lo menos a intentarlo. Para ello lo primero pensé en que quizás lo más adecuado fuese hacerlo en multiplataforma y generar una solución que funcionase tanto en Windows como en GNU-Linux. Atendiendo a este requisito, enseguida a mi mente llegó Python, porque además su instalación de paquetes es sencilla y potente a partes iguales, por no decir que es posible generar binarios (por ejemplo un .exe en Windows) si hiciera falta gracias a herramientas como py2exe y PyInstaller.

Con el punto de partida claro y unas horas de desarrollo, consigo lo que bautizo como Hidden Console Start o HCS. El proyecto puedes encontrarlo en Github:

Y su instalación es sencilla si tienes ya Python en tu equipo. En caso de no tener Python, pásate por la web de Python y descárgate la última versión disponible. Una vez instalado Python, solo tienes que ejecutar el siguiente comando en el CMD o PowerShell para instalar HCS:

pip install hcs --upgrade

Una vez instalado puedes ejecutar el proceso o aplicación que se quiera ejecutando HCS de la siguiente forma:

hcs -e "P1" "P2" ... "Pn-1" "Pn"

Pongamos un ejemplo:

hcs -e "ping 127.0.0.1 > log1.txt" "ping 192.168.1.17 > log2.txt"

En el ejemplo se lanzan dos comandos ping a distintas direcciones que son guardados en log1.txt y log2.txt respectivamente. Como se puede ver la consola de comandos no queda bloqueada y la información de los comandos ejecutados se va guardando el los ficheros.

Si por alguna razón tus procesos o aplicaciones no mueren o acaban, puedes finalizarlos en el caso de Windows abriendo el administrador de tareas:

Y en el caso de GNU-Linux con htop:

De esta forma podemos lanzar procesos y aplicaciones en segundo plano en sistemas Windows de una forma más o menos equivalente a como lo haríamos en GNU-Linux. Y obviamente podríamos usar HCS en GNU-Linux porque también funciona, pudiendo usarlo de la misma manera que en Windows.

Aumentar espacio de tu Raspberry Pi usando un dispositivo de almacenamiento USB

Una de las bondades de los sistemas GNU/Linux es la posibilidad de montar una unidad USB y  formatearla convenientemente. La cual puede ser montada a voluntad o meterla en la fstab (File Systems TABle) para que se monte con el arranque del sistema si lo deseamos. Para estas cosas GNU/Linux es la caña y es super flexible!! 😉

Un opción que podría ejemplificar fácilmente el uso de este procedimiento, puede ser una Raspberry Pi a la que queráis darle más espacio que el que tenéis disponible en la microSD donde tenéis montado el sistema operativo. Actualmente las memorias USB han bajado mucho su precio y han aumentado los tamaños máximos de las mismas, encontrándonos a precios razonables memorias de 128GB o256GB.

Empecemos con lo básico que es conectar la memoria USB a la Rapsberry Pi (obvio pero por si acaso lo digo 😉 ). Con la memoria USB conectada ejecutamos el siguiente comando:

ls -l /dev/disk/by-uuid/

El comando nos responde identificado las unidades disponibles. Las unidades "mmcblk0p1" y "mmcblk0p2" corresponden a la partición de boot del la Rapsberry Pi y a la de ficheros de Raspbian (Sistema Operativo) respectivamente. La que nos insteresa es la "sda1". Anota el identificador (UUID) que tiene, en mi ejemplo el mio es "51a4dc12-f769-4286-bdb6-58f40f6a7ce2".

Ahora vamos a preparar el punto de montaje de la unidad. Para ello ejecuta el siguiente comando para crear la ruta "media/usb" donde montaremos la unidad:

sudo mkdir /media/usb

Puedes usar otra ruta si lo deseas, pero intenta que sea corta, ya que tendrás que escribirla tantas veces como quieras acceder a la unidad.

Ahora hay que asegurarnos que el usuario "pi" es propietario de la nueva carpeta donde montaremos la unidad. Para ello ejecuta:

sudo chown -R pi:pi /media/usb

Para montar la unidad lo haremos con el siguiente comando:

sudo mount /dev/sda1 /media/usb

Si ejecutamos el comando:

df -h -T

Veremos que aparece la unidad en el punto de montaje "media/usb" indicado, así como el tipo de sistema de ficheros. Si has usado otro punto de montaje, aparecerá que el que hayas puesto.

Como usar la memoria USB con el formato FAT32 (aparece como vfat) no es lo más óptimo, formatearemos la unidad a EXT4 mucho más óptimo para nuestro sistema Raspbian. Puedes formatear fácilmente la unidad con el siguiente comando tras desmontar la unidad con:

sudo umount /media/usb
sudo mkfs.ext4 /dev/sda

⚠️ Ten en cuenta que si ejecutas los comandos para formatear la memoria USB, perderás los datos que hubiese dentro. ⚠️ Si estamos conformes con el formateo decimos que sí.

Si quisiéramos que la unidad se montase automáticamente, tendremos que incluir la nueva unidad en fstab. Para ello abriremos primero el fichero de configuración de fstab:

sudo nano /etc/fstab

Y añadiremos tras la última línea esta otra:

UUID=51a4dc12-f769-4286-bdb6-58f40f6a7ce2 /media/usb ext4 auto,nofail,noatime,users,exec,rw 0 0

El parámetro más importante es el "UUID" que anotamos al principio del artículo. Si finalmente optaste por formatear la memoria USB a EXT4, te recomiendo que vuelvas a ejecutar el comando porque el valor habrá cambiado.

Para concer específicamente el significado de cada parámetro te dejo la siguiente referencia:

Si finalmente decidiste NO formatear la memoria USB, la línea cambia un poco y se sustituye "ext4" por "vfat":

UUID=51a4dc12-f769-4286-bdb6-58f40f6a7ce2 /media/usb ext4 auto,nofail,noatime,users,exec,rw 0 0

Ahora solo nos resta comprobar si efectivamente la unidad se monta sola y para ello ejecutaremos un reinicio:

reboot

Si lo hemos hecho bien deberíamos poder ver la memoria USB en "/media/usb". Ya tenemos más espacio en nuestra Raspberry Pi!! 😉